Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/NeonD00m/feces/llms.txt

Use this file to discover all available pages before exploring further.

feces uses several structured types to represent replication data. Understanding these types helps when building custom networking layers or extending feces behavior. All types below are exported from feces/types.luau and referenced throughout the library — you will encounter them when calling delta, combine, apply, and full.

TestPlayer

export type TestPlayer = Player | string
A union alias used throughout the library wherever a player identifier appears. In a live Roblox game this will be a Player instance; in non-Roblox or test environments a plain string is accepted instead. TestPlayer is the key type in Changes, Deletes, and the return value of combine.

LocalEntity

export type LocalEntity = Entity
A type alias for a jecs Entity (a numeric ID) that lives in the local world — whichever side (server or client) created it. LocalEntity values are world-specific and are never sent directly over the network; they are always translated to a LookupEntity first.

LookupEntity

export type LookupEntity = Entity
A type alias for a jecs Entity used as a stable replication index. The same lookup ID refers to the conceptually same entity across both the server and client worlds, making it safe to include in network packets. LookupEntity values are stored in Group.lookups and Group.refs, and appear as keys in Applyable, Changes, and Deletes.

PlayerObject

-- TestPlayer = Player | string
export type PlayerObject = TestPlayer? | { TestPlayer } | (TestPlayer) -> boolean
The value type stored in the feces.replicated component. It controls which players receive replication data for a given entity. feces supports Roblox Player instances as well as plain strings for non-Roblox environments via the TestPlayer alias.
PlayerObject
TestPlayer?
A single player (or string identifier), or nil for public replication. Only that player receives updates for this entity; nil means all players.
PlayerObject
{ TestPlayer }
An array of players. Each listed player receives updates for this entity.
PlayerObject
(TestPlayer) -> boolean
A filter function. Called once per connected player; returns true if that player should receive updates.
See Player Filters for detailed usage examples.

Group

export type Group = {
    origin:  string,
    size:    number,
    lookups: { [LocalEntity]: LookupEntity },
    refs:    { [LookupEntity]: LocalEntity },
}
A Group represents an isolated lookup namespace used to scope replication. The feces instance itself acts as the default group; additional groups are created with feces.reserve().
origin
string
required
A human-readable debug string identifying where this group was created (e.g. "script:23 reserve()"). Emitted in warnings when the group’s size is exceeded.
size
number
required
The maximum number of entities this group can hold. feces emits a warning if more entities than size are assigned to the group. Use this to bound how many bits are needed to serialize a lookup ID on the wire.
lookups
{ [LocalEntity]: LookupEntity }
required
Maps local entity IDs (in feces.world) to their stable lookup IDs. Use this table for a non-allocating lookup check; call feces:lookup(entity, group) if you need to allocate on miss.
refs
{ [LookupEntity]: LocalEntity }
required
Maps stable lookup IDs back to local entity IDs. Use this table for a non-allocating ref check; call feces:ref(lookupId, group) if you need to allocate on miss.

Changes

export type Changes = {
    [Component]: {
        [TestPlayer]: {
            value:  { [LookupEntity]: any },
            remove: { LookupEntity },
            none:   { LookupEntity },
        }
    }
}
Produced by feces:delta(). Organizes pending component mutations by component, then by player (TestPlayer), and finally by the type of change. Pass a Changes object to feces.combine() to produce per-player Applyable packets.
[Component]
table
Top-level key is the jecs component entity ID.
none vs remove: none means world:set(entity, component, nil) was called — the component still exists on the entity as a tag with no value. remove means world:remove(entity, component) was called — the component is fully detached from the entity. These produce different mutations when applied on the receiving end.

Deletes

export type Deletes = {
    [TestPlayer]: { LookupEntity }
}
Produced alongside Changes by feces:delta(). Maps each TestPlayer to an array of lookup IDs for entities that were deleted or had their replicated tag removed since the last delta() call. Pass a Deletes object (optionally) to feces.combine() alongside Changes.
[TestPlayer]
{ LookupEntity }
Array of lookup IDs that should be deleted in the receiving player’s local world. These become entries in Applyable.__d after combine() processes them.

Applyable

export type Applyable = {
    [Component]: {
        [LookupEntity]: {
            value:  any,
            action: ChangeType?,
        }
    }
} & { __d: { LookupEntity } }
The per-player replication packet format. Applyable is both the output of feces.combine() and feces:full() and the input of feces:apply(). Serialize this table and send it to each client; on the client, pass the deserialized table to feces:apply().
[Component]
table
A component entity ID. Each component that changed maps to a sub-table of affected entities.
__d
{ LookupEntity }
The special deletion key. Contains an array of lookup IDs for entities that should be deleted in the receiving world. feces:apply() detects this key and calls world:delete() for each referenced entity.
The action constants and deletion key are defined in types.luau:
-- From feces/types.luau
return {
    None   = 0x1,  -- set component to nil (tag behavior)
    Remove = 0x2,  -- remove component from entity entirely
    Delete = "__d", -- entity deletion key in Applyable
}
Applyable is the universal wire format in feces. feces.combine(changes, deletes) and feces:full() both produce an Applyable (one per player for combine, one global snapshot for full). feces:apply(delta) consumes an Applyable. This means the same serialization/deserialization path covers both delta updates and full-state syncs (e.g. for newly joined players).

ChangeType

export type ChangeType = number
A numeric constant that classifies what kind of mutation an entry in Applyable or Storages represents. The two defined values are None = 0x1 and Remove = 0x2, both exported from types.luau. When action is absent (nil), the entry is a plain value set.

PendingDeletes

export type PendingDeletes = {
    [TestPlayer]: { [LookupEntity]: true },
}
An internal type used to accumulate entities pending deletion between delta() calls. Stored at feces._deleted. When delta() runs, entries are drained from PendingDeletes and translated into the Deletes return value.
PendingDeletes is an internal implementation detail managed automatically by the world:removed(replicated, ...) observer set up in feces.new(). Do not read or write feces._deleted directly.

Storages

export type Storages = {
    [Component]: {
        [LocalEntity]: {
            action: ChangeType?,
            value:  any?,
        }
    }
}
An internal type used by feces to accumulate pending component mutations between delta() calls. Each component gets a sub-table keyed by local entity ID. When delta() is called, feces drains Storages, translates local IDs to lookup IDs, and builds Changes.
Storages is an internal implementation detail — it lives at feces._storages and is managed automatically by the jecs observers set up in feces.new(). You should not read or write this table directly.

Lib

export type Lib = {
    new:     (Jecs, World, Group?, boolean?) -> Feces,
    combine: (Changes, Deletes?) -> { [TestPlayer]: Applyable },
    reserve: (number, boolean?) -> Group,
}
The type of the top-level feces module table. It exposes three functions used to bootstrap replication. See the lib reference for full documentation of each function.
new
(Jecs, World, Group?, boolean?) -> Feces
Creates and returns a new Feces instance that observes world for changes.
combine
(Changes, Deletes?) -> { [TestPlayer]: Applyable }
Translates a Changes + Deletes pair into a map of per-player Applyable packets, keyed by TestPlayer.
reserve
(number, boolean?) -> Group
Creates a new Group with an isolated lookup namespace of the given size. The optional second argument enables debug.info origin recording.

Feces

export type Feces = Lib & Group & {
    _debug:     boolean?,
    jecs:       Jecs,
    world:      World,
    replicated: Component<PlayerObject>,

    queries: {
        single: jecs.Query<Component<PlayerObject>>,
        pair:   jecs.Query<jecs.Pair<Component<PlayerObject>, jecs.Entity>>,
    },

    _deleted:  PendingDeletes,
    _storages: Storages,
    _grouped:  { [LocalEntity]: Group },

    debug:   (self: Feces, ...any) -> (),
    lookup:  (self: Feces, key: LocalEntity?,  group: Group?) -> LookupEntity,
    ref:     (self: Feces, key: LookupEntity?, group: Group?) -> LocalEntity,
    apply:   (self: Feces, delta: Applyable,   group: Group?) -> (),
    delta:   (self: Feces, group: Group?)  -> (Changes, Deletes),
    full:    (self: Feces, group: Group?)  -> Applyable,
    added:   (self: Feces, (LocalEntity) -> ()) -> (),
    changed: (self: Feces, (LocalEntity, Component, any) -> ()) -> (),
    removed: (self: Feces, (LocalEntity, Component) -> ()) -> (),
    deleted: (self: Feces, (LocalEntity) -> ()) -> (),
    _hooks: {
        added:   ((LocalEntity) -> ())?,
        changed: ((LocalEntity, Component, any) -> ())?,
        removed: ((LocalEntity, Component) -> ())?,
        deleted: ((LocalEntity) -> ())?,
    },
}
The type of a feces instance returned by feces.new(). It extends both Lib (static helpers) and Group (default lookup namespace), and adds all instance methods. See the feces instance reference for full documentation of each method.
Because Feces extends Group, a feces instance can be passed directly anywhere a Group is expected (e.g. feces:delta(feces) is equivalent to feces:delta()). Named groups created with feces.reserve() share the same interface.

Build docs developers (and LLMs) love