Skip to content

Commit 144ad5d

Browse files
committed
Timeslider updates & some fixes
- 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
1 parent 6bb0f94 commit 144ad5d

15 files changed

+164
-79
lines changed

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Here is a list of implemented features or soon to be implemented things/ideas:
1818
- [ ] Nice looking UI to control everything
1919
- [x] Reconnects, downloads and re-downloads automatically and as needed
2020
- [x] View canvas as you can on the game's website
21-
- [x] Record canvas events (Relatively compact: ~10-20 MB/day, can be reduced further later)
21+
- [x] Record canvas events (Relatively compact: ~10-20 MB/day (for an area of ~400 megapixels), can be reduced further later)
2222
- [x] Play back recordings (Freely seekable)
2323
- [x] Export image sequence from recordings (Subset of the recorded canvas, timelapses, ...)
2424
- [x] Multitasking. You can run many game instances/tasks from a single application

Diff for: canvas.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,7 @@ func newCanvas(chunkSize pixelSize, origin image.Point, canvasRect image.Rectang
115115
switch chunk.getQueryState(resetTime) {
116116
case chunkDelete:
117117
can.Lock()
118-
//delete(can.Chunks, can.ChunkSize.getChunkCoord(chunk.Rect.Min)) // TODO: Add option to not delete old chunks (For replay)
119-
// TODO: IDEA: Only delete invalid chunks, and add option to clean up canvas (invalidate chunks outside of rects)
120-
// TODO: Fix chunks getting stuck in downloading state. Reset downloading state if it failed!
118+
delete(can.Chunks, can.ChunkSize.getChunkCoord(chunk.Rect.Min, can.Origin))
121119
can.Unlock()
122120
case chunkDownload:
123121
select {
@@ -203,7 +201,7 @@ func newCanvas(chunkSize pixelSize, origin image.Point, canvasRect image.Rectang
203201
case event, ok := <-can.EventChan:
204202
if !ok {
205203
// Close goroutine, as the channel is gone
206-
log.Trace("Broadcaster closed!")
204+
log.Trace("Canvas event broadcaster closed")
207205
return
208206
}
209207
switch event := event.(type) {

Diff for: chunk.go

+22-13
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import (
2525
)
2626

2727
const (
28-
chunkDeleteTime = 5 * time.Minute
28+
chunkDeleteNoQueryDuration = 5 * time.Minute
29+
chunkDeleteInvalidDuration = 5 * time.Minute
2930
)
3031

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

42-
PixelQueue []pixelQueueElement // Queued pixels, that are set while the image is downloading
43-
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
44-
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.
43+
PixelQueue []pixelQueueElement // Queued pixels, that are set while the image is downloading
44+
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
45+
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.
46+
LastInvalidationTime time.Time // Point in time, when that chunk was invalidated last.
4547
}
4648

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

5153
chunk := &chunk{
52-
Rect: cRect,
53-
Image: &cRect,
54-
PixelQueue: []pixelQueueElement{},
55-
LastQueryTime: time.Now(),
54+
Rect: cRect,
55+
Image: &cRect,
56+
PixelQueue: []pixelQueueElement{},
57+
LastQueryTime: time.Now(),
58+
LastInvalidationTime: time.Now(),
5659
}
5760

5861
return chunk
@@ -233,6 +236,7 @@ func (chu *chunk) invalidateImage() {
233236
defer chu.Unlock()
234237

235238
chu.Valid = false
239+
chu.LastInvalidationTime = time.Now()
236240

237241
return
238242
}
@@ -265,7 +269,7 @@ func (chu *chunk) signalDownload() bool {
265269
}
266270

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

270274
return true
271275
}
@@ -286,17 +290,22 @@ func (chu *chunk) getQueryState(resetTime bool) chunkQueryResult {
286290
chu.Lock()
287291
defer chu.Unlock()
288292

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

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

305+
// Suggest downloading of the chunk if it is invalid and not downloading already
306+
if !chu.Valid && !chu.Downloading {
307+
return chunkDownload
308+
}
309+
301310
return chunkKeep
302311
}

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module github.com/Dadido3/D3pixelbot
22

33
require (
4-
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430
4+
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355
55
github.com/GeertJohan/go.rice v1.0.0
66
github.com/coreos/go-semver v0.2.0
77
github.com/gorilla/websocket v1.4.0

Diff for: go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
33
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4-
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430 h1:JXh5RmjAlkYbozWovdod8DdWnkCYXsNtPs0EZ7UJO7w=
5-
github.com/Dadido3/go-sciter v0.5.1-0.20190712151119-1d1f10e8d430/go.mod h1:KfXVxcubR3ifH2yWnkvb8YKCX1jxIdGxgfrFYHxFKQQ=
4+
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355 h1:8t26Wb9hbzuJwYPLJ8yin+Zcwvrbr7CGCCWxDFSo5dw=
5+
github.com/Dadido3/go-sciter v0.5.1-0.20190714161430-381960429355/go.mod h1:KfXVxcubR3ifH2yWnkvb8YKCX1jxIdGxgfrFYHxFKQQ=
66
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
77
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
88
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=

Diff for: main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func init() {
4949
log.Fatalf("Can't get working directory")
5050
}
5151

52-
version, err = semver.NewVersion("0.1.3-travis-test")
52+
version, err = semver.NewVersion("0.1.4-pre")
5353
if err != nil {
5454
fmt.Println(err.Error())
5555
}

Diff for: pixelcanvas.io.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func newPixelcanvasio() (connection, *canvas) {
247247
u.RawQuery = "fingerprint=" + con.Fingerprint
248248

249249
// Connect to websocket server
250-
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) // TODO: Connecting pinging and timeouts
250+
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) // TODO: Ping websocket connection and set timeouts
251251
if err != nil {
252252
log.Errorf("Failed to connect to websocket server %v: %v", u.String(), err)
253253
continue

Diff for: scitercanvas.go

+24-18
Original file line numberDiff line numberDiff line change
@@ -132,23 +132,30 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
132132
return sciter.NewValue("Wrong number of parameters")
133133
}
134134

135-
sca.ClosedMutex.Lock()
136-
defer sca.ClosedMutex.Unlock()
135+
// unsubscribeCanvasEvents is non blocking, but an Unsubscribed event is sent to the callback
136+
go func() {
137+
sca.ClosedMutex.Lock()
138+
defer sca.ClosedMutex.Unlock()
137139

138-
if sca.handlerChan == nil {
139-
log.Errorf("Not subscribed")
140-
return sciter.NewValue("Not subscribed")
141-
}
140+
if sca.handlerChan == nil {
141+
log.Errorf("Not subscribed")
142+
return
143+
}
142144

143-
err := can.unsubscribeListener(sca)
144-
if err != nil {
145-
log.Errorf("Can't unsubscribe to canvas: %v", err)
146-
return sciter.NewValue(fmt.Sprintf("Can't unsubscribe to canvas: %v", err))
147-
}
145+
err := can.unsubscribeListener(sca)
146+
if err != nil {
147+
log.Errorf("Can't unsubscribe from canvas: %v", err)
148+
return
149+
}
150+
151+
val := sciter.NewValue()
152+
val.Set("Type", "Unsubscribed")
153+
sca.handlerChan <- val
148154

149-
close(sca.handlerChan)
150-
sca.handlerChan = nil // Goroutine has its own reference to this channel
151-
sca.Closed = true
155+
close(sca.handlerChan)
156+
sca.handlerChan = nil // Goroutine has its own reference to this channel
157+
sca.Closed = true
158+
}()
152159

153160
return nil
154161
})
@@ -225,7 +232,6 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
225232
return nil
226233
})
227234

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

@@ -248,8 +254,8 @@ func sciterOpenCanvas(con connection, can *canvas) (closedChan chan struct{}) {
248254
sciterRecs := sciter.NewValue()
249255
for i, rec := range recs {
250256
sciterRec := sciter.NewValue()
251-
sciterRec.Set("StartTime", rec.StartTime.Format(time.RFC3339Nano))
252-
sciterRec.Set("EndTime", rec.EndTime.Format(time.RFC3339Nano))
257+
sciterRec.Set("StartTime", rec.StartTime)
258+
sciterRec.Set("EndTime", rec.EndTime)
253259
sciterRec.Set("FileName", rec.FileName)
254260
sciterRecs.SetIndex(i, sciterRec)
255261
}
@@ -547,7 +553,7 @@ func (s *sciterCanvas) handleSetTime(t time.Time) error {
547553

548554
val := sciter.NewValue()
549555
val.Set("Type", "SetTime")
550-
val.Set("RFC3339Nano", t.Format(time.RFC3339Nano))
556+
val.Set("Time", t)
551557

552558
s.handlerChan <- val
553559

Diff for: scitermain.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func sciterOpenMain() {
2929
//sciter.SetOption(sciter.SCITER_SET_DEBUG_MODE, 1)
3030
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!
3131

32-
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
32+
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
3333
if err != nil {
3434
log.Fatal(err)
3535
}

Diff for: ui/canvas.htm

+17-7
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,18 @@
128128
var formValues = $(#replay-time).value;
129129
var rd = formValues.Date;
130130
var rt = formValues.Time;
131-
view.setReplayTime(Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second));
131+
var t = Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second);
132+
$(timeslider).replayTime = t;
133+
view.setReplayTime(t);
132134
});
133135

134136
$(#replay-time > input(Time)).on("change", function() {
135137
var formValues = $(#replay-time).value;
136138
var rd = formValues.Date;
137139
var rt = formValues.Time;
138-
view.setReplayTime(Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second));
140+
var t = Date.local(rd.year, rd.month, rd.day, rt.hour, rt.minute, rt.second);
141+
$(timeslider).replayTime = t;
142+
view.setReplayTime(t);
139143
});
140144

141145
var timeTrigger;
@@ -161,6 +165,8 @@
161165
Time: t
162166
};
163167

168+
$(timeslider).currentTime = t;
169+
164170
if ($(#output).value.Autosave && timeTrigger && t.valueOf() >= timeTrigger.valueOf()) {
165171
saveImage();
166172
}
@@ -202,6 +208,7 @@
202208
Time: t
203209
};
204210

211+
$(timeslider).replayTime = t;
205212
view.setReplayTime(t);
206213

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

222229
var result = view.hasReplayTime();
223230
if (result.Recs && result.Recs.length > 0) {
224-
for (var (k, v) in result.Recs) {
225-
v.StartTime = new Date(v.StartTime);
226-
v.EndTime = new Date(v.EndTime);
227-
result.Recs[k] = v;
228-
}
229231
$(timeslider).recordings = result.Recs;
230232
$(#replay-time).value = {
231233
Date: result.Recs[0].StartTime,
232234
Time: result.Recs[0].StartTime
233235
};
236+
$(timeslider).replayTime = result.Recs[0].StartTime;
237+
$(timeslider).replayTimeCallback = function (t) {
238+
$(#replay-time).value = {
239+
Date: t,
240+
Time: t
241+
};
242+
view.setReplayTime(t);
243+
};
234244
} else {
235245
for (var elem in $$(.replay-hide)) {
236246
elem.attributes.toggleClass("hidden", true);

Diff for: ui/main.htm

-3
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,16 @@
4949
$(#btn-local-open).on("click", function() {
5050
var values = $(#local-settings).value;
5151
var res = view.openLocal(values.game);
52-
debug : res;
5352
});
5453

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

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

6764
function self.ready() {

Diff for: ui/prototypes/pixcanvas/pixcanvas.css

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ pixcanvas .chunk > img {
2525
image-rendering: pixelated;
2626
}
2727

28+
pixcanvas.smoothImage .chunk > img {
29+
image-rendering: default !important;
30+
}
31+
2832
pixcanvas span {
2933
width: 100%;
3034
height: 100%;

Diff for: ui/prototypes/pixcanvas/pixcanvas.tis

+3-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class PixCanvas : Behavior {
102102
this.zoomLevel = zoomLevel;
103103
this.zoom = Math.pow(Math.pow(2, 1.0/4), zoomLevel);
104104

105+
this.attributes.toggleClass("smoothImage", (zoomLevel < 0));
106+
105107
this.$(.canvasContainer).style.set { // TODO: Use zoom property
106108
width: this.canvasWidth * this.zoom,
107109
height: this.canvasHeight * this.zoom
@@ -256,7 +258,7 @@ class PixCanvas : Behavior {
256258
}
257259

258260
function eventSetTime(event) {
259-
this.time = new Date(event.RFC3339Nano);
261+
this.time = event.Time;
260262

261263
if (this.timeCallback) {
262264
this.timeCallback(this.time);

Diff for: ui/prototypes/timeslider/timeslider.css

+1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ timeslider .timeContainer > div {
2121
height: 60dip;
2222
top: 20dip;
2323
background-color: rgba(255, 0, 0, 0.25);
24+
border: solid 1dip red;
2425
}

0 commit comments

Comments
 (0)