Skip to content

Commit

Permalink
Timeslider updates & some fixes
Browse files Browse the repository at this point in the history
- Fix deadlock on unsubscribeCanvasEvents
- Fix timeslider zoom
- Add timeslider time input by clicking/dragging
- Interpolate images when zoomed out on canvas
- Delete invalid chunks after some time
- Fix chunks getting stuck in download state
- Update README.md
  • Loading branch information
Dadido3 committed Jul 14, 2019
1 parent 6bb0f94 commit 144ad5d
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 79 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Here is a list of implemented features or soon to be implemented things/ideas:
- [ ] Nice looking UI to control everything
- [x] Reconnects, downloads and re-downloads automatically and as needed
- [x] View canvas as you can on the game's website
- [x] Record canvas events (Relatively compact: ~10-20 MB/day, can be reduced further later)
- [x] Record canvas events (Relatively compact: ~10-20 MB/day (for an area of ~400 megapixels), can be reduced further later)
- [x] Play back recordings (Freely seekable)
- [x] Export image sequence from recordings (Subset of the recorded canvas, timelapses, ...)
- [x] Multitasking. You can run many game instances/tasks from a single application
Expand Down
6 changes: 2 additions & 4 deletions canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ func newCanvas(chunkSize pixelSize, origin image.Point, canvasRect image.Rectang
switch chunk.getQueryState(resetTime) {
case chunkDelete:
can.Lock()
//delete(can.Chunks, can.ChunkSize.getChunkCoord(chunk.Rect.Min)) // TODO: Add option to not delete old chunks (For replay)
// TODO: IDEA: Only delete invalid chunks, and add option to clean up canvas (invalidate chunks outside of rects)
// TODO: Fix chunks getting stuck in downloading state. Reset downloading state if it failed!
delete(can.Chunks, can.ChunkSize.getChunkCoord(chunk.Rect.Min, can.Origin))
can.Unlock()
case chunkDownload:
select {
Expand Down Expand Up @@ -203,7 +201,7 @@ func newCanvas(chunkSize pixelSize, origin image.Point, canvasRect image.Rectang
case event, ok := <-can.EventChan:
if !ok {
// Close goroutine, as the channel is gone
log.Trace("Broadcaster closed!")
log.Trace("Canvas event broadcaster closed")
return
}
switch event := event.(type) {
Expand Down
35 changes: 22 additions & 13 deletions chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import (
)

const (
chunkDeleteTime = 5 * time.Minute
chunkDeleteNoQueryDuration = 5 * time.Minute
chunkDeleteInvalidDuration = 5 * time.Minute
)

type pixelQueueElement struct {
Expand All @@ -39,20 +40,22 @@ type chunk struct {
Rect image.Rectangle
Image image.Image // TODO: Compress or unload image when not needed

PixelQueue []pixelQueueElement // Queued pixels, that are set while the image is downloading
Valid, Downloading bool // Valid: Data is in sync with the game. Downloading: Data is being downloaded. Both flags can't be true at the same time
LastQueryTime time.Time // Point in time, when that chunk was queried last time. If this chunk hasn't been queried for some period, it will be unloaded.
PixelQueue []pixelQueueElement // Queued pixels, that are set while the image is downloading
Valid, Downloading bool // Valid: Data is in sync with the game. Downloading: Data is being downloaded. Both flags can't be true at the same time
LastQueryTime time.Time // Point in time, when that chunk was queried last. If this chunk hasn't been queried for some period, it will be unloaded.
LastInvalidationTime time.Time // Point in time, when that chunk was invalidated last.
}

// Create new empty chunk with rect
func newChunk(rect image.Rectangle) *chunk {
cRect := rect.Canon()

chunk := &chunk{
Rect: cRect,
Image: &cRect,
PixelQueue: []pixelQueueElement{},
LastQueryTime: time.Now(),
Rect: cRect,
Image: &cRect,
PixelQueue: []pixelQueueElement{},
LastQueryTime: time.Now(),
LastInvalidationTime: time.Now(),
}

return chunk
Expand Down Expand Up @@ -233,6 +236,7 @@ func (chu *chunk) invalidateImage() {
defer chu.Unlock()

chu.Valid = false
chu.LastInvalidationTime = time.Now()

return
}
Expand Down Expand Up @@ -265,7 +269,7 @@ func (chu *chunk) signalDownload() bool {
}

chu.PixelQueue = []pixelQueueElement{} // Empty queue on new download.
chu.Downloading = true
chu.Downloading = true // TODO: Fix chunks getting stuck in downloading state. Reset downloading state if the download failed!

return true
}
Expand All @@ -286,17 +290,22 @@ func (chu *chunk) getQueryState(resetTime bool) chunkQueryResult {
chu.Lock()
defer chu.Unlock()

if chu.LastQueryTime.Add(chunkDeleteTime).Before(time.Now()) {
// TODO: Add option to not delete old chunks (For replay)
// TODO: Add option to ignore chunkDeleteInvalidDuration
// Delete chunks that were invalid for some time and haven't been queried for some time
if !chu.Valid && chu.LastInvalidationTime.Add(chunkDeleteInvalidDuration).Before(time.Now()) && chu.LastQueryTime.Add(chunkDeleteNoQueryDuration).Before(time.Now()) {
return chunkDelete
}
if !chu.Valid && !chu.Downloading {
return chunkDownload
}

// Only set the time when the chunk is not downloading. So it will be deleted after some time if it is "stuck"
if !chu.Downloading && resetTime {
chu.LastQueryTime = time.Now()
}

// Suggest downloading of the chunk if it is invalid and not downloading already
if !chu.Valid && !chu.Downloading {
return chunkDownload
}

return chunkKeep
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/Dadido3/D3pixelbot

require (
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355
github.com/GeertJohan/go.rice v1.0.0
github.com/coreos/go-semver v0.2.0
github.com/gorilla/websocket v1.4.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430 h1:JXh5RmjAlkYbozWovdod8DdWnkCYXsNtPs0EZ7UJO7w=
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430/go.mod h1:KfXVxcubR3ifH2yWnkvb8YKCX1jxIdGxgfrFYHxFKQQ=
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355 h1:8t26Wb9hbzuJwYPLJ8yin+Zcwvrbr7CGCCWxDFSo5dw=
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355/go.mod h1:KfXVxcubR3ifH2yWnkvb8YKCX1jxIdGxgfrFYHxFKQQ=
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func init() {
log.Fatalf("Can't get working directory")
}

version, err = semver.NewVersion("0.1.3-travis-test")
version, err = semver.NewVersion("0.1.4-pre")
if err != nil {
fmt.Println(err.Error())
}
Expand Down
2 changes: 1 addition & 1 deletion pixelcanvas.io.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func newPixelcanvasio() (connection, *canvas) {
u.RawQuery = "fingerprint=" + con.Fingerprint

// Connect to websocket server
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) // TODO: Connecting pinging and timeouts
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) // TODO: Ping websocket connection and set timeouts
if err != nil {
log.Errorf("Failed to connect to websocket server %v: %v", u.String(), err)
continue
Expand Down
42 changes: 24 additions & 18 deletions scitercanvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,23 +132,30 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
return sciter.NewValue("Wrong number of parameters")
}

sca.ClosedMutex.Lock()
defer sca.ClosedMutex.Unlock()
// unsubscribeCanvasEvents is non blocking, but an Unsubscribed event is sent to the callback
go func() {
sca.ClosedMutex.Lock()
defer sca.ClosedMutex.Unlock()

if sca.handlerChan == nil {
log.Errorf("Not subscribed")
return sciter.NewValue("Not subscribed")
}
if sca.handlerChan == nil {
log.Errorf("Not subscribed")
return
}

err := can.unsubscribeListener(sca)
if err != nil {
log.Errorf("Can't unsubscribe to canvas: %v", err)
return sciter.NewValue(fmt.Sprintf("Can't unsubscribe to canvas: %v", err))
}
err := can.unsubscribeListener(sca)
if err != nil {
log.Errorf("Can't unsubscribe from canvas: %v", err)
return
}

val := sciter.NewValue()
val.Set("Type", "Unsubscribed")
sca.handlerChan <- val

close(sca.handlerChan)
sca.handlerChan = nil // Goroutine has its own reference to this channel
sca.Closed = true
close(sca.handlerChan)
sca.handlerChan = nil // Goroutine has its own reference to this channel
sca.Closed = true
}()

return nil
})
Expand Down Expand Up @@ -225,7 +232,6 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
return nil
})

// TODO: Hide UI if connection doesn't have the replayTime method
w.DefineFunction("hasReplayTime", func(args ...*sciter.Value) (val *sciter.Value) {
val = sciter.NewValue()

Expand All @@ -248,8 +254,8 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
sciterRecs := sciter.NewValue()
for i, rec := range recs {
sciterRec := sciter.NewValue()
sciterRec.Set("StartTime", rec.StartTime.Format(time.RFC3339Nano))
sciterRec.Set("EndTime", rec.EndTime.Format(time.RFC3339Nano))
sciterRec.Set("StartTime", rec.StartTime)
sciterRec.Set("EndTime", rec.EndTime)
sciterRec.Set("FileName", rec.FileName)
sciterRecs.SetIndex(i, sciterRec)
}
Expand Down Expand Up @@ -547,7 +553,7 @@ func (s *sciterCanvas) handleSetTime(t time.Time) error {

val := sciter.NewValue()
val.Set("Type", "SetTime")
val.Set("RFC3339Nano", t.Format(time.RFC3339Nano))
val.Set("Time", t)

s.handlerChan <- val

Expand Down
2 changes: 1 addition & 1 deletion scitermain.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func sciterOpenMain() {
//sciter.SetOption(sciter.SCITER_SET_DEBUG_MODE, 1)
sciter.SetOption(sciter.SCITER_SET_SCRIPT_RUNTIME_FEATURES, sciter.ALLOW_FILE_IO|sciter.ALLOW_SOCKET_IO|sciter.ALLOW_EVAL|sciter.ALLOW_SYSINFO) // Needed for the inspector to work!

w, err := window.New(sciter.SW_MAIN|sciter.SW_RESIZEABLE|sciter.SW_TITLEBAR|sciter.SW_CONTROLS|sciter.SW_ENABLE_DEBUG|sciter.SW_GLASSY, sciter.NewRect(300, 300, 500, 400)) // TODO: Store/Restore window position and or open it in screen center
w, err := window.New(sciter.SW_MAIN|sciter.SW_RESIZEABLE|sciter.SW_TITLEBAR|sciter.SW_CONTROLS|sciter.SW_ENABLE_DEBUG|sciter.SW_GLASSY, sciter.NewRect(300, 300, 500, 400)) // TODO: Store/Restore window position or open it in screen center
if err != nil {
log.Fatal(err)
}
Expand Down
24 changes: 17 additions & 7 deletions ui/canvas.htm
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,18 @@
var formValues = $(#replay-time).value;
var rd = formValues.Date;
var rt = formValues.Time;
view.setReplayTime(Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second));
var t = Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second);
$(timeslider).replayTime = t;
view.setReplayTime(t);
});

$(#replay-time > input(Time)).on("change", function() {
var formValues = $(#replay-time).value;
var rd = formValues.Date;
var rt = formValues.Time;
view.setReplayTime(Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second));
var t = Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second);
$(timeslider).replayTime = t;
view.setReplayTime(t);
});

var timeTrigger;
Expand All @@ -161,6 +165,8 @@
Time: t
};

$(timeslider).currentTime = t;

if ($(#output).value.Autosave && timeTrigger && t.valueOf() >= timeTrigger.valueOf()) {
saveImage();
}
Expand Down Expand Up @@ -202,6 +208,7 @@
Time: t
};

$(timeslider).replayTime = t;
view.setReplayTime(t);

if ($(#output).value.Autosave) {
Expand All @@ -221,16 +228,19 @@

var result = view.hasReplayTime();
if (result.Recs && result.Recs.length > 0) {
for (var (k, v) in result.Recs) {
v.StartTime = new Date(v.StartTime);
v.EndTime = new Date(v.EndTime);
result.Recs[k] = v;
}
$(timeslider).recordings = result.Recs;
$(#replay-time).value = {
Date: result.Recs[0].StartTime,
Time: result.Recs[0].StartTime
};
$(timeslider).replayTime = result.Recs[0].StartTime;
$(timeslider).replayTimeCallback = function (t) {
$(#replay-time).value = {
Date: t,
Time: t
};
view.setReplayTime(t);
};
} else {
for (var elem in $$(.replay-hide)) {
elem.attributes.toggleClass("hidden", true);
Expand Down
3 changes: 0 additions & 3 deletions ui/main.htm
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,16 @@
$(#btn-local-open).on("click", function() {
var values = $(#local-settings).value;
var res = view.openLocal(values.game);
debug : res;
});

$(#btn-local-record).on("click", function() {
var values = $(#local-settings).value;
var res = view.recordLocal(values.game);
debug : res;
});

$(#btn-local-replay).on("click", function() {
var values = $(#local-settings).value;
var res = view.replayLocal(values.game);
debug : res;
});

function self.ready() {
Expand Down
4 changes: 4 additions & 0 deletions ui/prototypes/pixcanvas/pixcanvas.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pixcanvas .chunk > img {
image-rendering: pixelated;
}

pixcanvas.smoothImage .chunk > img {
image-rendering: default !important;
}

pixcanvas span {
width: 100%;
height: 100%;
Expand Down
4 changes: 3 additions & 1 deletion ui/prototypes/pixcanvas/pixcanvas.tis
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class PixCanvas : Behavior {
this.zoomLevel = zoomLevel;
this.zoom = Math.pow(Math.pow(2, 1.0/4), zoomLevel);

this.attributes.toggleClass("smoothImage", (zoomLevel < 0));

this.$(.canvasContainer).style.set { // TODO: Use zoom property
width: this.canvasWidth * this.zoom,
height: this.canvasHeight * this.zoom
Expand Down Expand Up @@ -256,7 +258,7 @@ class PixCanvas : Behavior {
}

function eventSetTime(event) {
this.time = new Date(event.RFC3339Nano);
this.time = event.Time;

if (this.timeCallback) {
this.timeCallback(this.time);
Expand Down
1 change: 1 addition & 0 deletions ui/prototypes/timeslider/timeslider.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ timeslider .timeContainer > div {
height: 60dip;
top: 20dip;
background-color: rgba(255, 0, 0, 0.25);
border: solid 1dip red;
}
Loading

0 comments on commit 144ad5d

Please sign in to comment.