> For the complete documentation index, see [llms.txt](https://docs.parsue.io/aceland-unity-packages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.parsue.io/aceland-unity-packages/core-packages/injection/entry-points-and-pooling.md).

# Entry Points and Pooling

Two runtime conveniences that build on the container:

* **Entry points** let a plain C# service hook into Unity's update loop without being a MonoBehaviour.
* **Object pooling** recycles instances (plain objects or prefabs) so you avoid per-frame allocations and `Instantiate` / `Destroy` churn.

***

### Entry points

Register a service as an **entry point** and the container will drive its lifecycle callbacks from the Unity player loop — no MonoBehaviour required. Implement any combination of these interfaces:

<table><thead><tr><th width="186.40008544921875">Interface</th><th width="259.199951171875">Method</th><th>Called</th></tr></thead><tbody><tr><td><code>IInitializable</code></td><td><code>Initialize()</code></td><td>Once, right after the container is built.</td></tr><tr><td><code>ITickable</code></td><td><code>Tick()</code></td><td>Every frame (<code>Update</code>).</td></tr><tr><td><code>IFixedTickable</code></td><td><code>FixedTick()</code></td><td>Every physics step (<code>FixedUpdate</code>).</td></tr><tr><td><code>ILateTickable</code></td><td><code>LateTick()</code></td><td>Every frame after Tick (<code>LateUpdate</code>).</td></tr><tr><td><code>IAsyncEntryPoint</code></td><td><code>RunAsync(CancellationToken)</code></td><td>Once, asynchronously, at startup.</td></tr><tr><td><code>IOrderedEntryPoint</code></td><td><code>Order</code></td><td>Ordering hint — <strong>lower runs first</strong> (default <code>0</code>).</td></tr></tbody></table>

```csharp
using System.Threading;
using System.Threading.Tasks;
using AceLand.Injection;

public sealed class GameClock : IInitializable, ITickable, IAsyncEntryPoint, IOrderedEntryPoint
{
    private readonly ISaveService _save;
    public int Order => -100;   // runs before default-order entry points

    public GameClock(ISaveService save) => _save = save;   // constructor injection

    public void Initialize() => _save.Load();

    public async Task RunAsync(CancellationToken token)
    {
        // The token is cancelled when the scope is disposed / the app quits.
        await Task.Delay(500, token);
    }

    public void Tick()
    {
        // per-frame logic
    }
}
```

Register it with `AddEntryPoint<T>()`:

```csharp
public sealed class SystemsInstaller : IInstaller
{
    public void Install(IContainerBuilder builder)
    {
        builder.AddEntryPoint<GameClock>();
    }
}
```

{% hint style="info" %}
`AddEntryPoint<T>()` registers `T` as a `Singleton`, exposes it via its implemented interfaces, and wires the loop callbacks. Entry points on the **global** container survive scene loads; entry points on an **Injection Scope** stop when that scope is disposed.
{% endhint %}

{% hint style="warning" %}
`RunAsync` is awaited on the Unity main thread. Always honour the `CancellationToken` so startup work stops cleanly when play mode ends or the scope is disposed.
{% endhint %}

***

### Object pooling

Pooling hands out an `IObjectPool<T>` you can resolve and reuse. There are three ways to register a pool.

#### 1. Pool a plain / registered type

```csharp
// Pools T; resolves it if registered, otherwise constructs it via the container.
builder.RegisterPool<Bullet>(prewarm: 32, maxSize: 128);
```

#### 2. Pool with an explicit factory and callbacks

```csharp
builder.RegisterPool<Bullet>(
    factory:   r => new Bullet(r.Resolve<IPhysics>()),
    onRent:    b => b.Activate(),     // called when handed out
    onReturn:  b => b.Reset(),        // called when returned
    onDestroy: b => b.ReleaseNative(),// called when the pool trims/clears it
    prewarm:   16,
    maxSize:   64);
```

#### 3. Pool a prefab (Components)

```csharp
[SerializeField] private Projectile _projectilePrefab;

// Instances are DI-injected before Awake, just like scene objects.
builder.RegisterPrefabPool<Projectile>(_projectilePrefab, parent: null,
                                        prewarm: 8, maxSize: 200);
```

{% hint style="info" %}
`prewarm` creates that many instances up front; `maxSize` caps how many are kept when returned (`0` = unbounded). The pool is a `Singleton` and is disposed with its owning scope.
{% endhint %}

#### Renting and returning

Resolve the `IObjectPool<T>` and rent instances. The cleanest pattern uses the `PooledObject<T>` handle with a `using` block, which returns the item automatically:

```csharp
public sealed class Gun
{
    private readonly IObjectPool<Bullet> _bullets;
    public Gun(IObjectPool<Bullet> bullets) => _bullets = bullets;

    public void Fire()
    {
        // Rent(out var) returns a disposable handle; leaving the block returns the bullet.
        using (_bullets.Rent(out var bullet))
        {
            bullet.Launch();
        } // bullet returned to the pool here
    }

    public Bullet RentManually()
    {
        var b = _bullets.Rent();   // you are responsible for _bullets.Return(b) later
        return b;
    }
}
```

`IObjectPool<T>` surface:

<table><thead><tr><th width="255">Member</th><th>Purpose</th></tr></thead><tbody><tr><td><code>Rent()</code></td><td>Take an instance; you call <code>Return</code> yourself.</td></tr><tr><td><code>Rent(out T item)</code></td><td>Take an instance wrapped in a <code>PooledObject&#x3C;T></code> for <code>using</code>.</td></tr><tr><td><code>Return(T item)</code></td><td>Return an instance to the pool.</td></tr><tr><td><code>Prewarm(int count)</code></td><td>Pre-create instances.</td></tr><tr><td><code>Clear()</code></td><td>Destroy all inactive instances.</td></tr><tr><td><code>CountActive</code> / <code>CountInactive</code></td><td>Diagnostics counters.</td></tr></tbody></table>

***

### Best Practices

* Give entry points an explicit `Order` when initialisation order matters; otherwise leave it `0`.
* Always respect the `CancellationToken` in `RunAsync`.
* Prefer the `using (pool.Rent(out var item))` pattern so instances are always returned.
* Reset mutable state in `onReturn` (or the object's own reset), never assume a rented instance is fresh.
* Set a sensible `maxSize` for prefab pools to bound memory during spikes.
