JasonXuDeveloper / jobjectpool
Install for your project team
Run this command in your project directory to install the skill for your entire team:
mkdir -p .claude/skills/jobjectpool && curl -L -o skill.zip "https://fastmcp.me/Skills/Download/3344" && unzip -o skill.zip -d .claude/skills/jobjectpool && rm skill.zip
Project Skills
This skill will be saved in .claude/skills/jobjectpool/ and checked into git. All team members will have access to it automatically.
Important: Please verify the skill by reviewing its instructions before using it.
JObjectPool thread-safe object pooling for Unity. Triggers on: object pool, GC optimization, reusable instances, bullet pool, enemy pool, effect pool, spawn pool, reduce garbage collection, memory optimization, pool prewarm, Rent Return pattern, lock-free pool
0 views
0 installs
Skill Content
---
name: jobjectpool
description: JObjectPool thread-safe object pooling for Unity. Triggers on: object pool, GC optimization, reusable instances, bullet pool, enemy pool, effect pool, spawn pool, reduce garbage collection, memory optimization, pool prewarm, Rent Return pattern, lock-free pool
---
# JObjectPool - Thread-Safe Object Pooling
Thread-safe, lock-free generic object pooling for Unity using CAS operations. Works with job system and async operations.
## When to Use
- Frequently instantiated objects (bullets, enemies, effects)
- Reducing GC during gameplay
- Multi-threaded object reuse (safe from any thread)
- High-frequency object lifecycle operations
## Core API
### Constructor
```csharp
new JObjectPool<T>(
int maxSize = 64, // Maximum pooled objects (excess discarded)
Action<T> onRent = null, // Called when renting (for initialization)
Action<T> onReturn = null // Called when returning (for cleanup)
)
```
Note: `T` must be a reference type with a parameterless constructor (`where T : class, new()`).
### Methods
- `.Rent()` - Get object from pool or create new
- `.Return(T obj)` - Return object to pool (null values ignored)
- `.Clear()` - Remove all pooled objects
- `.Prewarm(int count)` - Pre-allocate objects (won't exceed maxSize)
- `.Count` - Current available count (approximate, thread-safe)
### Static Access
- `JObjectPool.Shared<T>()` - Global shared pool per type (default config: maxSize=64)
## Patterns
### Basic Pool
```csharp
var pool = new JObjectPool<Bullet>(maxSize: 100);
var bullet = pool.Rent();
// ... use bullet ...
pool.Return(bullet);
```
### With Initialization on Rent
```csharp
var pool = new JObjectPool<Enemy>(
maxSize: 50,
onRent: static enemy => enemy.Reset()
);
```
### Reset State on Return (RECOMMENDED)
```csharp
var pool = new JObjectPool<List<int>>(
maxSize: 32,
onReturn: static list => list.Clear()
);
```
### Prewarm During Loading
```csharp
var pool = new JObjectPool<Effect>();
pool.Prewarm(50); // Pre-create during loading screen
```
### Shared Pool (Simple Objects)
```csharp
// Good for simple reusable objects without custom callbacks
var sb = JObjectPool.Shared<StringBuilder>().Rent();
sb.Append("Hello");
sb.Clear(); // Clean up before returning
JObjectPool.Shared<StringBuilder>().Return(sb);
```
## Game Patterns (Zero-GC)
### Bullet Pool with Struct Config
```csharp
public sealed class Bullet
{
public Vector3 Position;
public Vector3 Velocity;
public float Damage;
public float Lifetime;
public void Reset()
{
Position = default;
Velocity = default;
Damage = 0;
Lifetime = 0;
}
}
public sealed class BulletManager
{
private readonly JObjectPool<Bullet> _pool = new(
maxSize: 200,
onReturn: static b => b.Reset());
public void Initialize() => _pool.Prewarm(100);
public Bullet Fire(in Vector3 pos, in Vector3 dir, float speed, float damage)
{
var bullet = _pool.Rent();
bullet.Position = pos;
bullet.Velocity = dir * speed;
bullet.Damage = damage;
return bullet;
}
public void Return(Bullet b) => _pool.Return(b);
}
```
### Enemy Spawner (Zero-GC State)
```csharp
public sealed class Enemy : IPoolable
{
public float Health { get; set; }
public Vector3 Position { get; set; }
public event Action OnDeath;
public void OnSpawn()
{
Health = 100f;
}
public void OnDespawn()
{
OnDeath = null; // Clear delegates to prevent leaks
}
}
public sealed class EnemySpawner
{
private readonly JObjectPool<Enemy> _pool;
public EnemySpawner(int maxSize = 50)
{
_pool = new(
maxSize,
onRent: static e => e.OnSpawn(),
onReturn: static e => e.OnDespawn());
}
public Enemy Spawn(in Vector3 position)
{
var enemy = _pool.Rent();
enemy.Position = position;
return enemy;
}
public void Despawn(Enemy e) => _pool.Return(e);
}
```
### Temporary Collection (Zero-GC in Update)
```csharp
// Use in hot paths to avoid List<T> allocations
public void ProcessNearbyEnemies(in Vector3 center, float radius)
{
var list = JObjectPool.Shared<List<Enemy>>().Rent();
try
{
FindEnemiesNonAlloc(center, radius, list);
foreach (var enemy in list)
{
ProcessEnemy(enemy);
}
}
finally
{
list.Clear();
JObjectPool.Shared<List<Enemy>>().Return(list);
}
}
```
### StringBuilder Pool (Zero-GC String Building)
```csharp
public static string FormatDamage(float damage, string targetName)
{
var sb = JObjectPool.Shared<StringBuilder>().Rent();
try
{
sb.Append(targetName);
sb.Append(" took ");
sb.Append(damage.ToString("F1"));
sb.Append(" damage");
return sb.ToString();
}
finally
{
sb.Clear();
JObjectPool.Shared<StringBuilder>().Return(sb);
}
}
```
## Best Practices
1. **Pre-allocate during loading** to prevent in-game allocation spikes
2. **Reset state on return** to prevent data leaks between reuses
3. **Set appropriate maxSize** based on expected concurrent usage
4. **Use shared pools** for simple objects without custom callbacks
5. **Monitor pool Count** to optimize sizing
## Troubleshooting
### Objects Not Reused
- **Forgot to Return:** Always call `pool.Return(obj)` when done
- **Pool at capacity:** Increase maxSize if concurrent usage exceeds limit
- **Returning null:** Null values are silently ignored
### Stale Data / Memory Leaks
- **Not resetting state:** Clear all fields in onReturn callback
- **Event delegates:** Unsubscribe all events in onReturn to prevent leaks
- **References to disposed objects:** Null out references in onReturn
### Performance Issues
- **Not pre-warming:** Call `Prewarm()` during loading screens
- **maxSize too low:** Causes allocations when pool empties
- **maxSize too high:** Wastes memory
### Thread Safety
- JObjectPool IS thread-safe (lock-free CAS)
- Safe to Rent/Return from any thread
- onRent/onReturn run on calling thread
## Common Mistakes
- Returning null to pool (ignored, but wasteful)
- Not clearing object state on return (causes bugs from stale data)
- Forgetting to return objects (pool becomes ineffective)
- Setting maxSize too low (causes frequent allocations)
- Setting maxSize too high (wastes memory)