Performance?: Don't unload chunks so eagerly

Previously: the Chunker tracks with chunks were gotten during the
current game tick and the N-1 and N-2 ticks, and chunks not accessed in
two ticks were freed immediately.

Now: they go into a "garbage collection" pool with a minimum number of
game ticks to free. So if they're needed again, they're saved from the
gc pool. F3 overlay data shows the count of the gc pool.
pull/84/head
Noah 2022-05-07 17:16:03 -07:00
parent 315c8a81a0
commit ffc2c6f69b
6 changed files with 29 additions and 9 deletions

View File

@ -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.

View File

@ -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

View File

@ -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?

View File

@ -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)
}

View File

@ -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)

View File

@ -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))