Skip to content

Commit f287d21

Browse files
committed
Introduce a new mechanism for clearing the model cache
This attempts to control memory leaking from programs that create and discard a large number of models (usually CSG-related).
1 parent b372ccd commit f287d21

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

src/flitter/engine/control.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,15 +329,14 @@ async def run(self):
329329
logger.trace("State dictionary size: {} keys", len(self.state))
330330
if run_program is not None and run_program.stack is not None:
331331
logger.trace("VM stack size: {:d}", run_program.stack.size)
332+
Model.flush_caches()
332333

333334
finally:
334335
self.global_state = {}
335336
self._references = {}
336337
self.pages = []
337338
program = run_program = current_program = context = None
338-
count = Model.flush_cache()
339-
if count:
340-
logger.trace("Flushed {} 3D models", count)
339+
Model.flush_caches(0, 0)
341340
SharedCache.clean(0)
342341
for renderers in self.renderers.values():
343342
while renderers:

src/flitter/render/window/models.pxd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@ cdef int64_t DefaultSegments
1010

1111
cdef class Model:
1212
cdef readonly str name
13+
cdef double touch_timestamp
14+
cdef double cache_timestamp
1315
cdef readonly dict cache
14-
cdef Vector bounds
1516
cdef set dependents
1617
cdef list buffer_caches
1718

19+
cpdef void unload(self)
1820
cpdef void check_for_changes(self)
1921
cpdef bint is_manifold(self)
2022
cpdef object build_trimesh(self)
2123
cpdef object build_manifold(self)
2224

2325
cpdef void add_dependent(self, Model model)
26+
cpdef void remove_dependent(self, Model model)
2427
cpdef void invalidate(self)
2528
cpdef Vector get_bounds(self)
2629
cpdef object get_trimesh(self)

src/flitter/render/window/models.pyx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ from libc.stdint cimport int32_t, int64_t
1111
from ... import name_patch
1212
from ...cache import SharedCache
1313
from ...model cimport true_
14+
from ...timer cimport perf_counter
1415

1516

1617
logger = name_patch(logger, __name__)
@@ -25,13 +26,30 @@ cdef Matrix44 IdentityTransform = Matrix44._identity()
2526

2627
cdef class Model:
2728
@staticmethod
28-
def flush_cache():
29+
def flush_caches(double min_age=30, int64_t min_size=2000):
30+
cdef double cutoff = perf_counter() - min_age
2931
cdef Model model
30-
count = len(ModelCache)
32+
cdef int64_t unload_count=0, dump_count=0
33+
cdef list unloaded=[]
34+
while len(ModelCache) > min_size:
35+
for model in ModelCache.values():
36+
if model.touch_timestamp < cutoff and not model.dependents:
37+
unloaded.append(model)
38+
if not unloaded:
39+
break
40+
while unloaded:
41+
model = unloaded.pop()
42+
del ModelCache[model.name]
43+
model.unload()
44+
unload_count += 1
3145
for model in ModelCache.values():
32-
model.invalidate()
33-
ModelCache.clear()
34-
return count
46+
if model.cache_timestamp < cutoff and model.cache is not None:
47+
model.cache = None
48+
dump_count += 1
49+
if dump_count:
50+
logger.trace("Dumped sub-caches on {} models", dump_count)
51+
if unload_count:
52+
logger.trace("Unloaded {} models from cache, {} remaining", unload_count, len(ModelCache))
3553

3654
@staticmethod
3755
def by_name(str name):
@@ -62,6 +80,16 @@ cdef class Model:
6280
def __repr__(self):
6381
return f'<Model: {self.name}>'
6482

83+
cpdef void unload(self):
84+
assert not self.dependents
85+
self.dependents = None
86+
self.cache = None
87+
if self.buffer_caches is not None:
88+
for cache in self.buffer_caches:
89+
if self.name in cache:
90+
del cache[self.name]
91+
self.buffer_caches = None
92+
6593
cpdef bint is_manifold(self):
6694
raise NotImplementedError()
6795

@@ -103,6 +131,9 @@ cdef class Model:
103131
self.dependents = set()
104132
self.dependents.add(model)
105133

134+
cpdef void remove_dependent(self, Model model):
135+
self.dependents.remove(model)
136+
106137
cpdef void invalidate(self):
107138
cdef Model model
108139
cdef dict cache
@@ -117,6 +148,7 @@ cdef class Model:
117148
del cache[self.name]
118149

119150
cpdef object get_trimesh(self):
151+
self.cache_timestamp = perf_counter()
120152
if self.cache is None:
121153
self.cache = {}
122154
elif 'trimesh' in self.cache:
@@ -126,6 +158,7 @@ cdef class Model:
126158
return trimesh_model
127159

128160
cpdef object get_manifold(self):
161+
self.cache_timestamp = perf_counter()
129162
if self.cache is None:
130163
self.cache = {}
131164
elif 'manifold' in self.cache:
@@ -135,6 +168,7 @@ cdef class Model:
135168
return manifold
136169

137170
cpdef Vector get_bounds(self):
171+
self.cache_timestamp = perf_counter()
138172
if self.cache is None:
139173
self.cache = {}
140174
elif 'bounds' in self.cache:
@@ -153,6 +187,7 @@ cdef class Model:
153187
return bounds_vector
154188

155189
cdef tuple get_buffers(self, object glctx, dict objects):
190+
self.cache_timestamp = perf_counter()
156191
cdef str name = self.name
157192
if name in objects:
158193
return objects[name]
@@ -280,6 +315,10 @@ cdef class Model:
280315
cdef class UnaryOperation(Model):
281316
cdef Model original
282317

318+
cpdef void unload(self):
319+
self.original.remove_dependent(self)
320+
super(UnaryOperation, self).unload()
321+
283322
cpdef bint is_manifold(self):
284323
return self.original.is_manifold()
285324

@@ -299,6 +338,7 @@ cdef class Flatten(UnaryOperation):
299338
model.original = original
300339
model.original.add_dependent(model)
301340
ModelCache[name] = model
341+
model.touch_timestamp = perf_counter()
302342
return model
303343

304344
cpdef bint is_manifold(self):
@@ -364,6 +404,7 @@ cdef class Repair(UnaryOperation):
364404
model.original = original
365405
model.original.add_dependent(model)
366406
ModelCache[name] = model
407+
model.touch_timestamp = perf_counter()
367408
return model
368409

369410
cpdef Model repair(self):
@@ -403,6 +444,7 @@ cdef class SnapEdges(UnaryOperation):
403444
model.snap_angle = snap_angle
404445
model.minimum_area = minimum_area
405446
ModelCache[name] = model
447+
model.touch_timestamp = perf_counter()
406448
return model
407449

408450
cpdef bint is_manifold(self):
@@ -441,6 +483,7 @@ cdef class Transform(UnaryOperation):
441483
model.original.add_dependent(model)
442484
model.transform_matrix = transform_matrix
443485
ModelCache[name] = model
486+
model.touch_timestamp = perf_counter()
444487
return model
445488

446489
cpdef Model repair(self):
@@ -481,6 +524,7 @@ cdef class UVRemap(UnaryOperation):
481524
model.original.add_dependent(model)
482525
model.mapping = mapping
483526
ModelCache[name] = model
527+
model.touch_timestamp = perf_counter()
484528
return model
485529

486530
cdef object remap_sphere(self, trimesh_model):
@@ -526,6 +570,7 @@ cdef class Slice(UnaryOperation):
526570
model.origin = origin
527571
model.normal = normal.normalize()
528572
ModelCache[name] = model
573+
model.touch_timestamp = perf_counter()
529574
return model
530575

531576
cpdef bint is_manifold(self):
@@ -596,8 +641,14 @@ cdef class BooleanOperation(Model):
596641
for child_model in collected_models:
597642
child_model.add_dependent(model)
598643
ModelCache[name] = model
644+
model.touch_timestamp = perf_counter()
599645
return model
600646

647+
cpdef void unload(self):
648+
for model in self.models:
649+
model.remove_dependent(self)
650+
super(BooleanOperation, self).unload()
651+
601652
cpdef bint is_manifold(self):
602653
return True
603654

@@ -722,6 +773,7 @@ cdef class Box(PrimitiveModel):
722773
model.name = name
723774
model.uv_map = uv_map
724775
ModelCache[name] = model
776+
model.touch_timestamp = perf_counter()
725777
return model
726778

727779
cpdef object build_trimesh(self):
@@ -742,6 +794,7 @@ cdef class Sphere(PrimitiveModel):
742794
model.name = name
743795
model.segments = segments
744796
ModelCache[name] = model
797+
model.touch_timestamp = perf_counter()
745798
return model
746799

747800
@cython.cdivision(True)
@@ -817,6 +870,7 @@ cdef class Cylinder(PrimitiveModel):
817870
model.name = name
818871
model.segments = segments
819872
ModelCache[name] = model
873+
model.touch_timestamp = perf_counter()
820874
return model
821875

822876
@cython.cdivision(True)
@@ -901,6 +955,7 @@ cdef class Cone(PrimitiveModel):
901955
model.name = name
902956
model.segments = segments
903957
ModelCache[name] = model
958+
model.touch_timestamp = perf_counter()
904959
return model
905960

906961
@cython.cdivision(True)
@@ -969,6 +1024,7 @@ cdef class ExternalModel(Model):
9691024
model.name = filename
9701025
model.cache_path = SharedCache[filename]
9711026
ModelCache[filename] = model
1027+
model.touch_timestamp = perf_counter()
9721028
return model
9731029

9741030
cpdef bint is_manifold(self):

0 commit comments

Comments
 (0)