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.
This guide walks you through a complete feces setup from scratch: installing the library, creating a feces instance on both the server and client, marking entities for replication, sending delta packets every frame via a RemoteEvent, handling full snapshots for players who join late, and applying incoming packets on the client side to keep its world in sync.
feces is networking-agnostic — the RemoteEvent used below is just one option. You can swap it for Blink, BridgeNet2, or any other remote library without changing any feces code.
Install feces
Add feces to your project with pesde:Once installed, require the library wherever you set up your ECS worlds:local feces = require("@killergg/feces")
Create a feces instance
Call feces.new(jecs, world) on both the server and the client, passing the jecs module and the respective world. feces registers component observers at this point, so all components must already exist before this call.-- Shared module or both server and client scripts
local jecs = require("@rbx/jecs")
local feces = require("@killergg/feces")
local world = jecs.World.new()
-- Create all your components BEFORE this line
local Transform = world:component()
local Health = world:component()
local instance = feces.new(jecs, world)
The returned instance holds instance.replicated — the component tag you use to mark entities for replication — as well as all replication methods. Mark entities for replication
Tag any entity you want to replicate by adding the replicated component. You can target all players, a specific player, a list of players, or use a filter function.local entity = world:entity()
world:add(entity, Transform)
world:set(entity, Transform, { x = 0, y = 5, z = 0 })
-- Send this entity's components to every player
world:add(entity, instance.replicated)
-- Or send only the Transform component to a specific player
world:set(entity, jecs.pair(instance.replicated, Transform), Player1)
Send delta packets each frame
On the server, call instance:delta() once per frame (e.g. inside a RunService.Heartbeat or your game loop). It returns the changes and deletions since the last call. If nothing changed, skip the send. Use feces.combine(changes, deleted) to merge both tables into a single per-player packet, then fire each player’s remote.local remote = game:GetService("ReplicatedStorage"):WaitForChild("FecesRemote")
local function replicate()
local changes, deleted = instance:delta()
-- Skip the send if there is nothing new
if not next(changes) and not next(deleted) then
return
end
for player, packet in feces.combine(changes, deleted) do
remote:FireClient(player, packet)
end
end
game:GetService("RunService").Heartbeat:Connect(replicate)
Handle full snapshots for new players
Players who join mid-session need a full snapshot of the current world state rather than just deltas. Use a separate requestFullPacket RemoteEvent: when the client fires it, the server responds with instance:full().local requestFullPacket = game:GetService("ReplicatedStorage"):WaitForChild("RequestFullPacket")
-- Server: respond to snapshot requests
requestFullPacket.OnServerEvent:Connect(function(player)
remote:FireClient(player, instance:full())
end)
Apply packets on the client
On the client, listen for incoming packets on the same remote and pass each one straight to instance:apply(packet). This works for both full snapshots and delta packets — the format is identical.-- Client
requestFullPacket:FireServer() -- request initial snapshot on join
remote.OnClientEvent:Connect(function(data)
instance:apply(data)
end)
Complete example
Here is a minimal but fully working server + client pair that puts all the steps together.
Server
-- ServerScript
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require("@rbx/jecs")
local feces = require("@killergg/feces")
-- Remotes (create these in ReplicatedStorage beforehand)
local remote = ReplicatedStorage:WaitForChild("FecesRemote")
local requestFullPacket = ReplicatedStorage:WaitForChild("RequestFullPacket")
-- World setup
local world = jecs.World.new()
local Transform = world:component()
local Health = world:component()
-- Create feces AFTER all components exist
local instance = feces.new(jecs, world)
-- Spawn an entity and mark it for replication
local hero = world:entity()
world:set(hero, Transform, { x = 0, y = 5, z = 0 })
world:set(hero, Health, 100)
world:add(hero, instance.replicated) -- replicate all components to all players
-- Full snapshot for late-joining players
requestFullPacket.OnServerEvent:Connect(function(player)
remote:FireClient(player, instance:full())
end)
-- Delta replication each frame
RunService.Heartbeat:Connect(function()
local changes, deleted = instance:delta()
if not next(changes) and not next(deleted) then
return
end
for player, packet in feces.combine(changes, deleted) do
remote:FireClient(player, packet)
end
end)
Client
-- LocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require("@rbx/jecs")
local feces = require("@killergg/feces")
local remote = ReplicatedStorage:WaitForChild("FecesRemote")
local requestFullPacket = ReplicatedStorage:WaitForChild("RequestFullPacket")
-- Mirror the server's component creation order exactly
local world = jecs.World.new()
local Transform = world:component() -- must match server ID
local Health = world:component() -- must match server ID
local instance = feces.new(jecs, world)
-- Request the full world snapshot on join
requestFullPacket:FireServer()
-- Apply every incoming packet (deltas and full snapshots share the same format)
remote.OnClientEvent:Connect(function(data)
instance:apply(data)
end)