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.

Groups let you create isolated replication namespaces within a single feces instance. Each group has its own entity lookup table, so you can call feces:delta(group) to get only the changes for entities that belong to that group. Entities not in the group are skipped entirely during the delta pass, keeping packets small and targeted.

Why Use Groups?

By default, feces:delta() tracks all replicated entities in one flat namespace. Groups let you split that tracking by any logical boundary that makes sense for your game:
  • Streaming zones — only replicate entities in the zone a player is currently in.
  • Dungeon instances — give each dungeon its own group so players only receive updates for the instance they are inside.
  • Team or region partitions — send different subsets of the world to different sets of players.
Without groups, you would need to filter entities manually after calling feces:delta(). Groups push that filtering into feces itself, so the delta pass never even considers entities outside the target namespace.

Creating a Group

Use feces.reserve(size) to allocate a new group. reserve is a module-level function, not an instance method.
local group = feces.reserve(16)
reserve returns a Group table with the following fields:
FieldTypeDescription
originstringDebug location string (script name + line number) where the group was created.
sizenumberMaximum number of entities the group was pre-allocated for.
lookupstableMaps LocalEntityLookupEntity (index into refs).
refstableArray of LocalEntity values in insertion order.
The size parameter is a soft pre-allocation hint. If more entities than size are added to the group, feces will emit a warning: [feces] Group is full, consider reserving a larger group size. Reserve a generous size up front to avoid noise in the output. There is no hard cap — entities can still be added beyond size, but you should treat the warning as a signal to increase your reservation.

Adding Entities to a Group

Use feces:setGroup(group, entity) to move an entity from the default feces namespace into a group. Note the argument order: group first, entity second.
local group = feces.reserve(32)

local entity = world:entity()
world:add(entity, feces.replicated)

-- Move the entity into the group. It will no longer appear in feces:delta()
-- without a group argument, only in feces:delta(group).
feces:setGroup(group, entity)
After setGroup, the entity is tracked in group.lookups and group.refs, and is removed from the default feces lookup tables.

Getting an Entity’s Group

Use feces:getGroup(entity) to retrieve the group an entity currently belongs to. If the entity has not been assigned to any group, it returns the feces instance itself (the default namespace).
local currentGroup = feces:getGroup(entity)
if currentGroup == group then
    print("entity is in the dungeon group")
end

Getting the Delta for a Group

Pass the group to feces:delta(group) to get only the changes for entities inside that group.
local changes, deleted = feces:delta(group)

if not next(changes) and not next(deleted) then
    return -- nothing changed for this group this frame
end

for player, packet in feces.combine(changes, deleted) do
    remote:FireClient(player, packet)
end
Calling feces:delta() without an argument returns changes for entities in the default namespace only. Entities assigned to a group are excluded from the default delta pass.

Moving Entities Between Groups

feces:setGroup also handles moving an entity from one group to another. Calling it with a different group removes the entity from its current group and registers it in the new one.
local groupA = feces.reserve(16)
local groupB = feces.reserve(16)

-- Start in groupA
feces:setGroup(groupA, entity)

-- Later, move to groupB (automatically removed from groupA)
feces:setGroup(groupB, entity)
After the move, groupA.lookups[entity] and groupA.refs no longer contain the entity. The next call to feces:delta(groupA) will not include it; feces:delta(groupB) will.

Full Example

The following pattern mirrors what the feces test suite verifies for the group workflow:
local jecs = require(ReplicatedStorage.jecs)
local lib = require(ReplicatedStorage.feces)

local world = jecs.world()
local feces = lib.new(jecs, world)

local Position = world:component()
local group = feces.reserve(16)

-- Create two entities; only add one to the group
local E1 = world:entity()
local E2 = world:entity()

world:set(E1, Position, Vector3.new(1, 0, 0))
world:set(E2, Position, Vector3.new(2, 0, 0))

world:add(E1, feces.replicated)
world:add(E2, feces.replicated)

-- E1 goes into the group, E2 stays in the default namespace
feces:setGroup(group, E1)

-- Only E1's changes are returned here
local groupChanges, groupDeleted = feces:delta(group)

-- Only E2's changes are returned here
local defaultChanges, defaultDeleted = feces:delta()

Build docs developers (and LLMs) love