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
parent
315c8a81a0
commit
ffc2c6f69b
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue