Object Pool¶
Intent: Improve performance and memory use by reusing objects from a fixed pool instead of allocating and freeing them individually.
Acron Mapping¶
Pattern role |
Acron equivalent |
|---|---|
Pool |
Python list of pre-created |
Acquire |
|
Release |
|
Model caching |
|
Built-in particle pool |
|
The Acron-specific trick: hide (node.hide()) inactive pool members instead of removing them from the scene graph. This avoids the cost of re-parenting and re-loading.
How to Implement¶
At startup, pre-create N instances: load the model once (hits
ModelPool), then.copyTo(render)N times.Call
.hide()on every instance. Store them in adequeas the “free list.”acquire()pops from the free list, calls.show(), positions the object, returns it.release(obj)calls.hide(), optionally.setPos(0, 0, -9999)(safety), pushes back to free list.Always remove the per-object task before calling
release().
Code Sketch (Python)¶
from collections import deque
class BulletPool:
def __init__(self, size: int = 64):
self._free = deque()
proto = loader.loadModel('assets/bullet.bam')
for _ in range(size):
node = proto.copyTo(render)
node.hide()
self._free.append(node)
def acquire(self):
if not self._free:
return None # pool exhausted — caller must handle gracefully
node = self._free.popleft()
node.show()
return node
def release(self, node) -> None:
node.hide()
self._free.append(node)
class Bullet:
def __init__(self, pool, pos, velocity):
self._pool = pool
self.node = pool.acquire()
if self.node is None:
return # silently skip if pool is full
self.node.setPos(pos)
self._vel = velocity
self.dead = False
taskMgr.add(self._update, f'bullet_{id(self)}')
def _update(self, task):
if self.dead:
return task.done
dt = globalClock.getDt()
self.node.setPos(self.node.getPos() + self._vel * dt)
if task.time > 3.0:
self.destroy()
return task.cont
def destroy(self):
self.dead = True
taskMgr.remove(f'bullet_{id(self)}')
if self.node:
self._pool.release(self.node)
self.node = None
Pitfalls¶
Pool exhaustion: always check if
acquire()returnsNoneand handle it (skip spawn, play an “ammo empty” sound). Never silently drop theNone.Leaked tasks on release: ensure the per-bullet task is removed before calling
pool.release(). A hidden node with a running task still runs logic every frame.ModelPool ≠ node pool:
ModelPoolcaches the raw model file data, not live scene graph nodes. You still need to manage node pooling yourself for avoiding repeatedcopyTo()/removeNode()calls.