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.
This commit is contained in:
parent
315c8a81a0
commit
ffc2c6f69b
|
@ -30,6 +30,8 @@ New levels:
|
||||||
* **Swimming** (Tutorial, Lesson 5) - a tutorial level to learn how
|
* **Swimming** (Tutorial, Lesson 5) - a tutorial level to learn how
|
||||||
"water pixels" work with some moderately safe platforming puzzles
|
"water pixels" work with some moderately safe platforming puzzles
|
||||||
included.
|
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
|
* 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
|
of newer game features, such as the water being re-done for the Castle
|
||||||
level.
|
level.
|
||||||
|
|
|
@ -134,8 +134,9 @@ var (
|
||||||
EagerRenderLevelChunks = true
|
EagerRenderLevelChunks = true
|
||||||
|
|
||||||
// Number of chunks margin outside the Canvas Viewport for the LoadingViewport.
|
// Number of chunks margin outside the Canvas Viewport for the LoadingViewport.
|
||||||
LoadingViewportMarginChunks = render.NewPoint(8, 4) // hoz, vert
|
LoadingViewportMarginChunks = render.NewPoint(10, 8) // hoz, vert
|
||||||
CanvasLoadUnloadModuloTicks uint64 = 2
|
CanvasLoadUnloadModuloTicks uint64 = 4
|
||||||
|
CanvasChunkFreeChoppingBlockTicks uint64 = 128 // number of ticks old a chunk is to free it
|
||||||
)
|
)
|
||||||
|
|
||||||
// Edit Mode Values
|
// Edit Mode Values
|
||||||
|
|
|
@ -294,7 +294,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
||||||
}
|
}
|
||||||
if s.UI.Canvas != nil {
|
if s.UI.Canvas != nil {
|
||||||
inside, outside := s.UI.Canvas.LoadUnloadMetrics()
|
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?
|
// Has the window been resized?
|
||||||
|
|
|
@ -40,8 +40,9 @@ type Chunker struct {
|
||||||
// are actively wanted by the game.
|
// are actively wanted by the game.
|
||||||
lastTick uint64 // NOTE: tracks from shmem.Tick
|
lastTick uint64 // NOTE: tracks from shmem.Tick
|
||||||
chunkRequestsThisTick map[render.Point]interface{}
|
chunkRequestsThisTick map[render.Point]interface{}
|
||||||
requestsN1 map[render.Point]interface{}
|
requestsN1 map[render.Point]interface{} // chunks accessed last tick
|
||||||
requestsN2 map[render.Point]interface{}
|
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
|
requestMu sync.Mutex
|
||||||
|
|
||||||
// The palette reference from first call to Inflate()
|
// The palette reference from first call to Inflate()
|
||||||
|
@ -57,6 +58,7 @@ func NewChunker(size int) *Chunker {
|
||||||
chunkRequestsThisTick: map[render.Point]interface{}{},
|
chunkRequestsThisTick: map[render.Point]interface{}{},
|
||||||
requestsN1: map[render.Point]interface{}{},
|
requestsN1: map[render.Point]interface{}{},
|
||||||
requestsN2: 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{}
|
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 {
|
for coord := range requestsN2 {
|
||||||
// Old point not requested recently?
|
// Old point not requested recently?
|
||||||
if _, ok := requestsThisTick[coord]; !ok {
|
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)
|
delete_coords = append(delete_coords, coord)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free any eligible chunks NOW.
|
||||||
for _, coord := range delete_coords {
|
for _, coord := range delete_coords {
|
||||||
|
delete(c.chunksToFree, coord)
|
||||||
c.FreeChunk(coord)
|
c.FreeChunk(coord)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ func (c *Chunker) MigrateZipfile(zf *zip.Writer) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Copy existing chunk %s", file.Name)
|
log.Debug("Copy existing chunk %s", file.Name)
|
||||||
if err := zf.Copy(file); err != nil {
|
if err := zf.Copy(file); err != nil {
|
||||||
return err
|
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())
|
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)
|
chunk.ToZipfile(zf, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +130,11 @@ func (c *Chunker) CacheSize() int {
|
||||||
return len(c.Chunks)
|
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.
|
// ToZipfile writes just a chunk's data into a zipfile.
|
||||||
func (c *Chunk) ToZipfile(zf *zip.Writer, filename string) error {
|
func (c *Chunk) ToZipfile(zf *zip.Writer, filename string) error {
|
||||||
writer, err := zf.Create(filename)
|
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.debViewport = s.drawing.Viewport().String()
|
||||||
*s.debScroll = s.drawing.Scroll.String()
|
*s.debScroll = s.drawing.Scroll.String()
|
||||||
inside, outside := s.drawing.LoadUnloadMetrics()
|
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.
|
// Update the timer.
|
||||||
s.timerLabel.Text = savegame.FormatDuration(time.Since(s.startTime))
|
s.timerLabel.Text = savegame.FormatDuration(time.Since(s.startTime))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user