Table of Contents

Finding entities

The manager answers three questions: "all entities of a type" (GetEntities<T> — a live filter), "tell me when one appears or dies" (filter subscriptions), and "the entity behind this reference" (GetEntityById for EntitySharedReference).

When to use this

  • Iterating everything of a kind — all players for a scoreboard, all pickups for a minimap.
  • Reacting to spawns and despawns without polling — SubscribeToConstructed / OnDestroyed.
  • Storing entity references inside game state — always as EntitySharedReference, resolved on use.

When not to

  • One-per-world services — GetSingleton<T>() is direct and typed.
  • The local player's controller — GetPlayerController<T>() on the client, GetPlayerController(player) on the server.
  • Hand-rolled static registries of entities — a filter already is that list, maintained for you.

Minimal example

A client-side minimap:

Minimap.cs (client)

using LiteEntitySystem;

public class Minimap
{
    public Minimap(ClientEntityManager manager)
    {
        EntityFilter<BasePlayer> players = manager.GetEntities<BasePlayer>();
        players.SubscribeToConstructed(AddBlip, callOnExisting: true);
        players.OnDestroyed += RemoveBlip;
    }

    public void Refresh(ClientEntityManager manager)
    {
        foreach (var player in manager.GetEntities<BasePlayer>())
            MoveBlip(player, player.Position);
    }

    private void AddBlip(BasePlayer player) { /* create marker */ }
    private void RemoveBlip(BasePlayer player) { /* remove marker */ }
    private void MoveBlip(BasePlayer player, object pos) { /* update marker */ }
}

How it works

Filters are live queries

GetEntities<T>() returns an EntityFilter<T> — not a snapshot. It is created lazily, backfilled with matching entities that already exist, and maintained as entities construct and die. Iteration order is creation order. Matching includes subclasses: GetEntities<BasePlayer>() yields ServerPlayer/ClientPlayer instances from the side-variant pattern as well as any other derived class.

Spawn and despawn callbacks

SubscribeToConstructed(callback, callOnExisting) fires for every entity of the type once it is fully constructed and synced; callOnExisting: true replays it for entities already in the filter — essential on the client, where the baseline may have constructed the world before your subscription. OnDestroyed fires as entities are removed from the filter; UnsubscribeToConstructed detaches.

Entity references

EntitySharedReference is the storable form of "that entity": id plus version, implicit conversion from an entity, Empty/IsValid for null-checks. Resolve with GetEntityById<T>(reference) — it returns null for an empty reference, a type mismatch, or a stale version after id reuse — or with TryGetEntityById<T>(reference, out var entity):

public SyncVar<EntitySharedReference> LastAttacker;

if (EntityManager.TryGetEntityById(LastAttacker, out BasePlayer attacker))
    ShowKillerName(attacker);

Resolve-on-use is the pattern: the reference lives in state, the object reference lives on the stack.

Behavior details

Filters see the whole world — every synced entity plus server-only AI controllers. The library's own loops (input application over GetEntities<HumanControllerLogic>, for instance) run on the same filters your code uses.

Warning

Common mistakes

  • Keeping plain C# references to entities in long-lived state — after destruction and id reuse they point at the wrong object; store EntitySharedReference, resolve on use.
  • Destroying entities while iterating their filter — copy to a temporary array first (the library does exactly this in its own bulk operations).
  • Subscribing on the client with callOnExisting: false — everything constructed from the baseline before your subscription is silently missed.
  • Expecting client filters to match the server's — the client sees only its slice of the world; counts and iteration results legitimately differ per side.