ワンクリックで
dev-patterns-object-pooling
Object pooling for high-performance R3F components (decals, particles, projectiles)
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Object pooling for high-performance R3F components (decals, particles, projectiles)
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Complete Developer workflow orchestration - task research sequence, implementation flow, validation gates, PRD synchronization, exit conditions.
Complete Game Designer workflow - skill invocation protocol, GDD creation, playtest flow with GDD review, design sessions. MUST load before starting assignments.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete PM Coordinator workflow - task assignment, project orchestration, PRD management, worker coordination. Use proactively when starting PM agent work.
Complete QA Validator workflow orchestration. References specialized skills for each validation step. Load at session startup for full protocol.
Base instructions and guidelines for all agents in the system. This skill provides foundational behaviors and communication protocols that all agents should follow.
| name | dev-patterns-object-pooling |
| description | Object pooling for high-performance R3F components (decals, particles, projectiles) |
| category | patterns |
"Pre-allocate, reuse, recycle – eliminate runtime GC pauses."
Use when:
// Basic object pool pattern
const POOL_SIZE = 500;
const MAX_ACTIVE = 200;
interface PoolSlot<T> {
obj: T;
active: boolean;
lastUsed: number;
}
function useObjectPool<T>(
create: () => T,
activate: (obj: T) => void,
deactivate: (obj: T) => void
) {
const poolRef = useRef<PoolSlot<T>[]>([]);
// Initialize pool on mount
useEffect(() => {
poolRef.current = Array.from({ length: POOL_SIZE }, () => ({
obj: create(),
active: false,
lastUsed: 0,
}));
return () => {
// Cleanup
poolRef.current.forEach(slot => {
if (slot.obj?.dispose) slot.obj.dispose();
});
};
}, []);
const acquire = useCallback(() => {
const pool = poolRef.current;
// Find inactive slot
let slot = pool.find(s => !s.active);
// If pool full, recycle LRU
if (!slot) {
slot = pool.reduce((oldest, s) =>
s.lastUsed < oldest.lastUsed ? s : oldest
);
deactivate(slot.obj);
}
slot.active = true;
slot.lastUsed = performance.now();
activate(slot.obj);
return slot.obj;
}, [activate]);
const release = useCallback((obj: T) => {
const slot = poolRef.current.find(s => s.obj === obj);
if (slot) slot.active = false;
}, []);
return { acquire, release };
}
| Scenario | Use Pool? | Reason |
|---|---|---|
| Bullets (max ~100 active) | Yes | High create/destroy rate |
| Decals (max ~200 visible) | Yes | Geometry allocation costly |
| Particles (max ~500) | Yes | Per-frame creation |
| UI overlays (dynamic count) | No | Unpredictable count |
| Player characters (1-32) | No | Low churn, complex init |
| Static props | No | Never destroyed |
When the pool is full, evict the Least Recently Used item:
// LRU recycling
let slot = pool.find(s => !s.active);
if (!slot) {
// Pool exhausted - recycle oldest decal
slot = pool.reduce((oldest, s) =>
s.lastUsed < oldest.lastUsed ? s : oldest
);
// Fade out before recycling
fadeOutDecal(slot.obj);
}
Why LRU?
// BAD: Creates new objects every frame
useFrame(() => {
const position = new Vector3();
const quaternion = new Quaternion();
// ... do work
});
// GOOD: Reuse temp objects
const _tempVec = useRef(new Vector3()).current;
const _tempQuat = useRef(new Quaternion()).current;
useFrame(() => {
_tempVec.set(0, 0, 0); // Reset, don't reallocate
_tempQuat.identity();
// ... do work
});
| Object Type | Suggested Pool Size | Max Active | Rationale |
|---|---|---|---|
| Bullets | 200 | 100 | Fast fire rate ~10/sec |
| Particles | 1000 | 500 | Explosions spawn many at once |
| Decals | 500 | 200 | Persist 60s, but limited visibility |
| Audio sources | 32 | 16 | WebAudio limit |
Rule of thumb: poolSize = maxActive * 2 to maxActive * 3
active flag to track in-use slotslastUsed timestamp for LRU evictionuseRef or class fieldsfrustumCulled={false} for small objects| Pitfall | Symptom | Fix |
|---|---|---|
| Sharing material across slots | All decals same color | Create unique material per slot |
| Forgetting to reset state | Stale data on reuse | Reset all props in activate() |
| Pool too small | Visible popping | Increase pool or maxActive |
| No disposal in useEffect | Memory leak | Add cleanup function |
Using new in useFrame | GC stutter | Use temp refs |
See: src/components/game/effects/PaintDecalManager.tsx
Key sections: