diff --git a/Changes.md b/Changes.md index b7e16cf..67776bd 100644 --- a/Changes.md +++ b/Changes.md @@ -30,6 +30,8 @@ New levels: * **Swimming** (Tutorial, Lesson 5) - a tutorial level to learn how "water pixels" work with some moderately safe platforming puzzles included. +* **Night Sky** (Azulian Tag) - a moderately difficult Azulian Tag level + with relatively few enemies but plenty of tricky platforming. * Some of the existing levels have had minor updates to take advantage of newer game features, such as the water being re-done for the Castle level. diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 3407103..5376737 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -134,8 +134,9 @@ var ( EagerRenderLevelChunks = true // Number of chunks margin outside the Canvas Viewport for the LoadingViewport. - LoadingViewportMarginChunks = render.NewPoint(8, 4) // hoz, vert - CanvasLoadUnloadModuloTicks uint64 = 2 + LoadingViewportMarginChunks = render.NewPoint(10, 8) // hoz, vert + CanvasLoadUnloadModuloTicks uint64 = 4 + CanvasChunkFreeChoppingBlockTicks uint64 = 128 // number of ticks old a chunk is to free it ) // Edit Mode Values diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index 4e86b49..61848cd 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -294,7 +294,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error { } if s.UI.Canvas != nil { inside, outside := s.UI.Canvas.LoadUnloadMetrics() - *s.debLoadingViewport = fmt.Sprintf("%d in %d out %d cached", inside, outside, s.UI.Canvas.Chunker().CacheSize()) + *s.debLoadingViewport = fmt.Sprintf("%d in %d out %d cached %d gc", inside, outside, s.UI.Canvas.Chunker().CacheSize(), s.UI.Canvas.Chunker().GCSize()) } // Has the window been resized? diff --git a/pkg/level/chunker.go b/pkg/level/chunker.go index 96b631b..614bfda 100644 --- a/pkg/level/chunker.go +++ b/pkg/level/chunker.go @@ -40,8 +40,9 @@ type Chunker struct { // are actively wanted by the game. lastTick uint64 // NOTE: tracks from shmem.Tick chunkRequestsThisTick map[render.Point]interface{} - requestsN1 map[render.Point]interface{} - requestsN2 map[render.Point]interface{} + requestsN1 map[render.Point]interface{} // chunks accessed last tick + requestsN2 map[render.Point]interface{} // 2 ticks ago (to free soon) + chunksToFree map[render.Point]uint64 // chopping block (free after X ticks) requestMu sync.Mutex // The palette reference from first call to Inflate() @@ -57,6 +58,7 @@ func NewChunker(size int) *Chunker { chunkRequestsThisTick: map[render.Point]interface{}{}, requestsN1: map[render.Point]interface{}{}, requestsN2: map[render.Point]interface{}{}, + chunksToFree: map[render.Point]uint64{}, } } @@ -388,15 +390,25 @@ func (c *Chunker) FreeCaches() int { delete_coords = []render.Point{} ) - // Chunks not requested this last tick, unload from the cache. + // Chunks requested 2 ticks ago but not this tick, put on the chopping + // block to free them later. for coord := range requestsN2 { // Old point not requested recently? if _, ok := requestsThisTick[coord]; !ok { + c.chunksToFree[coord] = shmem.Tick + balance.CanvasChunkFreeChoppingBlockTicks + } + } + + // From the chopping block, see if scheduled chunks to free are ready. + for coord, expireAt := range c.chunksToFree { + if shmem.Tick > expireAt { delete_coords = append(delete_coords, coord) } } + // Free any eligible chunks NOW. for _, coord := range delete_coords { + delete(c.chunksToFree, coord) c.FreeChunk(coord) } diff --git a/pkg/level/chunker_zipfile.go b/pkg/level/chunker_zipfile.go index bb2fa5e..205f3f5 100644 --- a/pkg/level/chunker_zipfile.go +++ b/pkg/level/chunker_zipfile.go @@ -82,7 +82,7 @@ func (c *Chunker) MigrateZipfile(zf *zip.Writer) error { continue } - log.Info("Copy existing chunk %s", file.Name) + log.Debug("Copy existing chunk %s", file.Name) if err := zf.Copy(file); err != nil { return err } @@ -105,7 +105,7 @@ func (c *Chunker) MigrateZipfile(zf *zip.Writer) error { } filename := fmt.Sprintf("chunks/%d/%s.json", c.Layer, coord.String()) - log.Info("Flush in-memory chunks to %s", filename) + log.Debug("Flush in-memory chunks to %s", filename) chunk.ToZipfile(zf, filename) } @@ -130,6 +130,11 @@ func (c *Chunker) CacheSize() int { return len(c.Chunks) } +// GCSize returns the number of chunks pending free (not accessed in 2+ ticks) +func (c *Chunker) GCSize() int { + return len(c.chunksToFree) +} + // ToZipfile writes just a chunk's data into a zipfile. func (c *Chunk) ToZipfile(zf *zip.Writer, filename string) error { writer, err := zf.Create(filename) diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 5d531f3..fbdfd8e 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -711,7 +711,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error { *s.debViewport = s.drawing.Viewport().String() *s.debScroll = s.drawing.Scroll.String() inside, outside := s.drawing.LoadUnloadMetrics() - *s.debLoadUnload = fmt.Sprintf("%d in %d out %d cached", inside, outside, s.drawing.Chunker().CacheSize()) + *s.debLoadUnload = fmt.Sprintf("%d in %d out %d cached %d gc", inside, outside, s.drawing.Chunker().CacheSize(), s.drawing.Chunker().GCSize()) // Update the timer. s.timerLabel.Text = savegame.FormatDuration(time.Since(s.startTime))