File size: 1,749 Bytes
84e8204 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | extends Node
##
## MeshPool - recycles ArrayMesh instances to avoid allocation churn.
##
## On low-RAM Android, creating and freeing ArrayMesh objects every frame causes
## GC spikes and fragmentation. This pool keeps a bounded number of meshes alive
## and reuses them. Meshes handed back to the pool have their surfaces cleared,
## freeing their GPU buffers while keeping the object (and its native handle).
##
signal pool_grew(size: int)
signal pool_trimmed(freed: int)
## Hard cap so the pool itself cannot become the memory leak.
@export var max_pooled: int = 32
var _free: Array[ArrayMesh] = []
var _live_count: int = 0
var _peak_live: int = 0
## Get a cleared mesh. Reuses a pooled one if available.
func acquire_mesh() -> ArrayMesh:
var mesh: ArrayMesh
if _free.size() > 0:
mesh = _free.pop_back()
else:
mesh = ArrayMesh.new()
_live_count += 1
_peak_live = maxi(_peak_live, _live_count)
return mesh
## Return a mesh for reuse. Clears surfaces so its VRAM is released immediately.
func release_mesh(mesh: ArrayMesh) -> void:
if mesh == null:
return
_live_count = maxi(0, _live_count - 1)
# Drop all surfaces: this frees the vertex buffers on the GPU side.
for i in range(mesh.get_surface_count()):
pass
mesh.clear_surfaces()
if _free.size() < max_pooled:
_free.append(mesh)
else:
# Pool is full: let Godot free this one (mesh has no surfaces = cheap).
pass
## Remove pooled-but-unused meshes down to `keep`. Call on scene transitions.
func trim(keep: int = 8) -> void:
var freed := 0
while _free.size() > keep:
_free.pop_back()
freed += 1
if freed > 0:
pool_trimmed.emit(freed)
func stats() -> Dictionary:
return {
"pooled": _free.size(),
"live": _live_count,
"peak_live": _peak_live,
}
|