Skip to content

Commit 71639a7

Browse files
committed
Allow run-time control of 3D model cache
1 parent ff2cf5b commit 71639a7

File tree

4 files changed

+31
-10
lines changed

4 files changed

+31
-10
lines changed

docs/install.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ code and so it may be useful if very fast switching is required – particularly
197197
if a program has very large loops that are being unrolled and there is
198198
insufficient gain from doing the unrolling.
199199

200+
`--modelcache`
201+
: How long to cache unused 3D models for, in seconds (may be specified as a
202+
[time-code](language.md#time-codes)). Set this to `0` to disable clearing of the
203+
model cache completely – this may be a useful performance tweak for long-running
204+
programs that reference a fixed number of models.
205+
200206
`--lockstep`
201207
: Turns on *non-realtime mode*. In this mode, the engine will generate frames
202208
as fast as possible (which may be quite slowly) while maintaining an evenly

src/flitter/engine/__main__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def main():
5151
parser.add_argument('--define', '-D', action='append', default=[], type=keyvalue, dest='defines', help="Define name for evaluation")
5252
parser.add_argument('--vmstats', action='store_true', default=False, help="Report VM statistics")
5353
parser.add_argument('--runtime', type=convert_timecode_to_float, help="Seconds to run for before exiting")
54+
parser.add_argument('--modelcache', type=convert_timecode_to_float, default=300, help="Seconds to cache models for")
5455
parser.add_argument('--offscreen', action='store_true', default=False, help="Swap windows for offscreens")
5556
parser.add_argument('--opengles', action='store_true', default=False, help="Use OpenGL ES")
5657
parser.add_argument('program', nargs='+', help="Program(s) to load")
@@ -67,7 +68,7 @@ def main():
6768
state_file=args.state, reset_on_switch=args.resetonswitch, state_simplify_wait=args.simplifystate,
6869
realtime=not args.lockstep, defined_names=dict(args.defines), vm_stats=args.vmstats,
6970
run_time=args.runtime, offscreen=args.offscreen, disable_simplifier=args.nosimplify,
70-
opengl_es=args.opengles)
71+
opengl_es=args.opengles, model_cache_time=args.modelcache)
7172
for program in args.program:
7273
controller.load_page(program)
7374

src/flitter/engine/control.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class EngineController:
2525

2626
def __init__(self, target_fps=60, screen=0, fullscreen=False, vsync=False, state_file=None,
2727
reset_on_switch=False, state_simplify_wait=0, realtime=True, defined_names=None, vm_stats=False,
28-
run_time=None, offscreen=False, disable_simplifier=False, opengl_es=False):
28+
run_time=None, offscreen=False, disable_simplifier=False, opengl_es=False, model_cache_time=300):
2929
self.default_fps = target_fps
3030
self.target_fps = target_fps
3131
self.realtime = realtime
@@ -34,6 +34,7 @@ def __init__(self, target_fps=60, screen=0, fullscreen=False, vsync=False, state
3434
self.vsync = vsync
3535
self.offscreen = offscreen
3636
self.opengl_es = opengl_es
37+
self.model_cache_time = model_cache_time
3738
self.reset_on_switch = reset_on_switch
3839
self.disable_simplifier = disable_simplifier
3940
self.state_simplify_wait = 0 if self.disable_simplifier else state_simplify_wait / 2
@@ -295,7 +296,8 @@ async def run(self):
295296

296297
del context
297298
SharedCache.clean()
298-
gc_pending |= Model.flush_caches()
299+
if self.model_cache_time > 0:
300+
gc_pending |= Model.flush_caches(max_age=self.model_cache_time)
299301
if gc_pending and (last_gc is None or now > last_gc + self.MINIMUM_GC_INTERVAL):
300302
count = gc.collect(2)
301303
gc_pending = False
@@ -336,21 +338,25 @@ async def run(self):
336338
logger.trace("State dictionary size: {} keys", len(self.state))
337339
if run_program is not None and run_program.stack is not None:
338340
logger.trace("VM stack size: {:d}", run_program.stack.size)
341+
count = Model.cache_size()
342+
if count:
343+
logger.trace("Model cache size: {}", count)
339344

340345
finally:
341346
self.global_state = {}
342347
self._references = {}
343348
self.pages = []
344349
program = run_program = current_program = context = None
345-
Model.flush_caches(0, 0)
350+
Model.flush_caches(max_size=0)
346351
SharedCache.clean(0)
347352
for renderers in self.renderers.values():
348353
while renderers:
349354
await renderers.pop().destroy()
350355
if self.vm_stats:
351356
log_vm_stats()
352357
count = gc.collect(2)
353-
logger.trace("Collected {} objects (full collection)", count)
358+
if count:
359+
logger.trace("Collected {} objects (full collection)", count)
354360
counts = numbers_cache_counts()
355361
if counts:
356362
logger.debug("Numbers cache: {}", ", ".join(f"{size}x{count}" for size, count in counts.items()))

src/flitter/render/window/models.pyx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,15 @@ cpdef void fill_in_normals(vertices_array, faces_array):
108108

109109
cdef class Model:
110110
@staticmethod
111-
def flush_caches(double max_age=300, int64_t max_size=2500):
111+
def cache_size():
112+
return len(ModelCache)
113+
114+
@staticmethod
115+
def flush_caches(double max_age=0, int64_t max_size=-1):
112116
cdef double now = perf_counter()
113117
cdef double cutoff = now - max_age
114118
cdef Model model
115-
cdef int64_t count=len(ModelCache), unload_count=0
119+
cdef int64_t count=len(ModelCache), unload_count=0, runs=0
116120
cdef list unloaded = []
117121
cdef bint aggressive=False, full_collect=False
118122
while True:
@@ -128,11 +132,12 @@ cdef class Model:
128132
full_collect |= model.uncache(True)
129133
else:
130134
full_collect |= model.uncache(False)
131-
if (model.touch_timestamp <= cutoff or aggressive and model.touch_timestamp < now and count > max_size) and model.dependents is None:
135+
if (cutoff > 0 and model.touch_timestamp <= cutoff or aggressive and count > max_size) and model.dependents is None:
132136
unloaded.append(model)
133137
count -= 1
138+
runs += 1
134139
if not unloaded:
135-
if count > max_size and not aggressive:
140+
if max_size >= 0 and count > max_size and not aggressive:
136141
aggressive = True
137142
else:
138143
break
@@ -143,7 +148,10 @@ cdef class Model:
143148
del ModelCache[model.id]
144149
unload_count += 1
145150
if unload_count:
146-
logger.trace("Unloaded {} models from cache, {} remaining", unload_count, count)
151+
if count:
152+
logger.trace("Unloaded {} models from cache in {} run(s), {} remaining", unload_count, runs, count)
153+
else:
154+
logger.trace("Emptied {} models from cache in {} run(s)", unload_count, runs)
147155
return full_collect
148156

149157
@staticmethod

0 commit comments

Comments
 (0)