Skip to content

Instantly share code, notes, and snippets.

@nezuo
Last active November 8, 2025 19:33
Show Gist options
  • Select an option

  • Save nezuo/7e9f510b30afe3d44edac147d74efc8f to your computer and use it in GitHub Desktop.

Select an option

Save nezuo/7e9f510b30afe3d44edac147d74efc8f to your computer and use it in GitHub Desktop.
Jecs Replication
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ClientRemotes = require(ReplicatedStorage.Client.ClientRemotes)
local ClientScheduler = require(ReplicatedStorage.Client.ClientScheduler)
local Jecs = require(ReplicatedStorage.Packages.Jecs)
local Reader = require(ReplicatedStorage.Shared.Buffer.Reader)
local ReplicatedComponents = require(ReplicatedStorage.Shared.ReplicatedComponents)
local World = require(ReplicatedStorage.Shared.World)
local serverToClientId = {}
local function getOrSpawnEntity(world: World.World, serverId: number): number
if serverToClientId[serverId] ~= nil then
return serverToClientId[serverId]
end
local id = world:entity() :: number
serverToClientId[serverId] = id
return id
end
local function receive(world: World.World)
ClientRemotes.Replication.UpdateComponents.setCallback(function(changes, values)
local reader = Reader.new(changes)
local valueIndex = 1
while not reader.consumed() do
local replicatedComponentId = reader.readu8()
local replicatedComponent = ReplicatedComponents[replicatedComponentId + 1]
local component = replicatedComponent.component
local isTag = Jecs.is_tag(world, component)
if replicatedComponent.relationship then
local removalCount = reader.readu8()
for _ = 1, removalCount do
local sourceServerId = reader.readf64()
local targetServerId = reader.readf64()
local source = serverToClientId[sourceServerId]
local target = serverToClientId[targetServerId]
if source ~= nil and target ~= nil then
world:remove(source, Jecs.pair(component, target))
end
end
local addedCount = reader.readu8()
for _ = 1, addedCount do
local sourceServerId = reader.readf64()
local targetServerId = reader.readf64()
local source = getOrSpawnEntity(world, sourceServerId)
local target = getOrSpawnEntity(world, targetServerId)
if isTag then
world:add(source, Jecs.pair(component, target))
else
local value = (values :: any)[valueIndex]
valueIndex += 1
world:set(source, Jecs.pair(component, target), value)
end
end
else
local removalCount = reader.readu8()
for _ = 1, removalCount do
local serverId = reader.readf64()
if serverToClientId[serverId] ~= nil then
world:remove(serverToClientId[serverId], component)
end
end
local changeCount = reader.readu8()
for _ = 1, changeCount do
local serverId = reader.readf64()
local entity = getOrSpawnEntity(world, serverId)
if isTag then
world:add(entity, component)
else
local value = (values :: any)[valueIndex]
valueIndex += 1
world:set(entity, component, value)
end
end
end
end
world:applyCommands()
end)
ClientRemotes.Replication.DeleteEntities.setCallback(function(entities)
for entity in entities do
if serverToClientId[entity] ~= nil then
world:delete(serverToClientId[entity])
serverToClientId[entity] = nil
end
end
world:applyCommands()
end)
end
local function Replication(scheduler: ClientScheduler.ClientScheduler)
scheduler:addSystems("Startup", receive):addSystems("Last", ClientRemotes.sendEvents)
end
return Replication
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Health = require(ReplicatedStorage.Shared.Components.Health)
local Jecs = require(ReplicatedStorage.Packages.Jecs)
local ReplicatedComponents: { { component: Jecs.Entity, relationship: boolean? } } = {
{ component = Health },
{ component = Jecs.ChildOf, relationship = true },
}
return ReplicatedComponents
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local Jecs = require(ReplicatedStorage.Packages.Jecs)
local Replicated = require(ServerScriptService.Server.Components.Replicated)
local ReplicatedComponents = require(ReplicatedStorage.Shared.ReplicatedComponents)
local ServerRemotes = require(ServerScriptService.Server.ServerRemotes)
local ServerScheduler = require(ServerScriptService.Server.ServerScheduler)
local World = require(ReplicatedStorage.Shared.World)
local Writer = require(ReplicatedStorage.Shared.Buffer.Writer)
local chain = require(ReplicatedStorage.Shared.Scheduler.chain)
type ReplicatedComponentId = number
type Source = Jecs.Entity
type ComponentChanges = { [Source]: any }
local REMOVED = {}
local changes: { [ReplicatedComponentId]: ComponentChanges } = {}
local deletions: { [Source]: true } = {}
local addedPlayers: { [Player]: true } = {}
local readyPlayers: { [Player]: true } = {}
local function playerLifecycle()
Players.PlayerAdded:Connect(function(player)
addedPlayers[player] = true
end)
for _, player in Players:GetPlayers() do
addedPlayers[player] = true
end
Players.PlayerRemoving:Connect(function(player)
addedPlayers[player] = nil
readyPlayers[player] = nil
end)
end
local function setRelationshipChange(
componentChanges: ComponentChanges,
source: Jecs.Entity,
target: Jecs.Entity,
value: any
)
if componentChanges[source] == nil then
componentChanges[source] = {}
end
componentChanges[source][target] = value
end
local function collectRelationshipChanges(
world: World.World,
componentChanges: ComponentChanges,
replicatedComponent: ReplicatedComponents.ReplicatedComponent
)
local component = replicatedComponent.component
local isTag = Jecs.is_tag(world, component)
local function changed(entity, pair, value)
if not world:has(entity, Replicated) then
return
end
local target = Jecs.pair_second(world, pair)
if not world:has(target, Replicated) then
return
end
if isTag then
setRelationshipChange(componentChanges, entity, target, true)
else
setRelationshipChange(componentChanges, entity, target, value)
end
end
world:added(component, changed)
world:changed(component, changed)
world:removed(component, function(entity: Jecs.Entity, pair)
if not world:has(entity, Replicated) then
return
end
local target = Jecs.pair_second(world, pair)
setRelationshipChange(componentChanges, entity, target, REMOVED)
end)
end
local function addRelationshipOnReplicated(
world: World.World,
componentChanges: ComponentChanges,
replicatedComponent: ReplicatedComponents.ReplicatedComponent,
entity: Jecs.Entity
)
local component = replicatedComponent.component
local isTag = Jecs.is_tag(world, component)
-- Add relationships where entity is the target.
for source, value in world:query(Jecs.pair(component, entity)):with(Replicated) do
if isTag then
setRelationshipChange(componentChanges, source, entity, true)
else
setRelationshipChange(componentChanges, source, entity, value)
end
end
if not world:has(entity, component) then
return
end
-- Add relationships where entity is the source.
for target, value in world:targets(entity, component) do
if not world:has(target, Replicated) then
continue
end
if isTag then
setRelationshipChange(componentChanges, entity, target, true)
else
setRelationshipChange(componentChanges, entity, target, value)
end
end
end
local function collectComponentChanges(
world: World.World,
componentChanges: ComponentChanges,
replicatedComponent: ReplicatedComponents.ReplicatedComponent
)
local component = replicatedComponent.component
if Jecs.is_tag(world, component) then
world:added(component, function(entity)
if not world:has(entity, Replicated) then
return
end
componentChanges[entity] = true
end)
else
local function changed(entity, _, value)
if not world:has(entity, Replicated) then
return
end
componentChanges[entity] = value
end
world:added(component, changed)
world:changed(component, changed)
end
world:removed(component, function(entity: Jecs.Entity)
if not world:has(entity, Replicated) then
return
end
componentChanges[entity] = REMOVED
end)
end
local function addComponentOnReplicated(
world: World.World,
componentChanges: ComponentChanges,
replicatedComponent: ReplicatedComponents.ReplicatedComponent,
entity: Jecs.Entity
)
local component = replicatedComponent.component
if not world:has(entity, component) then
return
end
if Jecs.is_tag(world, component) then
componentChanges[entity] = true
else
componentChanges[entity] = world:get(entity, component)
end
end
local function collectChanges(world: World.World)
for index in ReplicatedComponents do
changes[index] = {}
end
for index, replicatedComponent in ReplicatedComponents do
local componentChanges = changes[index]
if replicatedComponent.relationship then
collectRelationshipChanges(world, componentChanges, replicatedComponent)
else
collectComponentChanges(world, componentChanges, replicatedComponent)
end
end
world:added(Replicated, function(entity)
for index, replicatedComponent in ReplicatedComponents do
if replicatedComponent.relationship then
addRelationshipOnReplicated(world, changes[index], replicatedComponent, entity)
else
addComponentOnReplicated(world, changes[index], replicatedComponent, entity)
end
end
end)
world:removed(Replicated, function(entity)
for _, componentChanges in changes do
componentChanges[entity] = nil
end
deletions[entity] = true
end)
end
local function replicateChanges(world: World.World)
local writer = Writer.new()
if next(deletions) ~= nil then
ServerRemotes.Replication.DeleteEntities.fireSet(readyPlayers, deletions :: { [number]: true })
end
local values = {}
local hadChanges = false
for index, replicatedComponent in ReplicatedComponents do
local component = replicatedComponent.component
local componentChanges = changes[index]
if replicatedComponent.relationship then
local isTag = Jecs.is_tag(world, component)
local addedCount = 0
local removalCount = 0
local added = {}
local removals = {}
for source, sourcePairs in componentChanges do
for target, value in sourcePairs do
if deletions[target] then
continue
end
if value == REMOVED then
if removals[source] == nil then
removals[source] = {}
end
removals[source][target] = true
removalCount += 1
else
if added[source] == nil then
added[source] = {}
end
if isTag then
added[source][target] = true
else
added[source][target] = value
end
addedCount += 1
end
end
end
if addedCount == 0 and removalCount == 0 then
continue
end
hadChanges = true
writer.writeu8(index - 1)
writer.writeu8(removalCount)
for source, targets in removals do
for target in targets do
writer.writef64(source)
writer.writef64(target)
end
end
writer.writeu8(addedCount)
for source, targets in added do
for target, value in targets do
writer.writef64(source)
writer.writef64(target)
if not isTag then
table.insert(values, value)
end
end
end
table.clear(componentChanges)
else
local changeCount = 0
local changed = {}
local removals = {}
for source, value in componentChanges do
if value == REMOVED then
table.insert(removals, source)
else
changed[source] = value
changeCount += 1
end
end
if changeCount == 0 and #removals == 0 then
continue
end
hadChanges = true
writer.writeu8(index - 1)
writer.writeu8(#removals)
for _, source in removals do
writer.writef64(source)
end
writer.writeu8(changeCount)
for source, value in changed do
writer.writef64(source)
if not Jecs.is_tag(world, component) then
table.insert(values, value)
end
end
table.clear(componentChanges)
end
end
table.clear(deletions)
if hadChanges then
ServerRemotes.Replication.UpdateComponents.fireSet(readyPlayers, writer.finish(), values)
end
end
local function replicateInitial(world: World.World)
if next(addedPlayers) == nil then
return
end
local writer = Writer.new()
local values = {}
for index, replicatedComponent in ReplicatedComponents do
local component = replicatedComponent.component
if replicatedComponent.relationship then
local isTag = Jecs.is_tag(world, component)
local addedPairs = {}
local count = 0
for entity in world:query(Jecs.pair(component, Jecs.Wildcard)):with(Replicated) do
for target, value in world:targets(entity, component) do
if world:has(target, Replicated) then
count += 1
if addedPairs[entity] == nil then
addedPairs[entity] = {}
end
if isTag then
addedPairs[entity][target] = true
else
addedPairs[entity][target] = value
end
end
end
end
if count == 0 then
continue
end
writer.writeu8(index - 1)
writer.writeu8(0)
writer.writeu8(count)
for source, targets in addedPairs do
for target, value in targets do
writer.writef64(source :: number)
writer.writef64(target :: number)
if not isTag then
table.insert(values, value)
end
end
end
else
local isTag = Jecs.is_tag(world, component)
local componentValues = {}
local count = 0
for id, value in world:query(component):with(Replicated) do
componentValues[id] = if isTag then true else value
count += 1
end
if count == 0 then
continue
end
writer.writeu8(index - 1)
writer.writeu8(0)
writer.writeu8(count)
for id, value in componentValues do
writer.writef64(id :: number)
if not isTag then
table.insert(values, value)
end
end
end
end
ServerRemotes.Replication.UpdateComponents.fireSet(addedPlayers, writer.finish(), values)
for player in addedPlayers do
readyPlayers[player] = true
end
table.clear(addedPlayers)
end
local function Replication(scheduler: ServerScheduler.ServerScheduler)
scheduler
:addSystems("Startup", { playerLifecycle, collectChanges })
:addSystems("Last", chain({ replicateChanges, replicateInitial, ServerRemotes.sendEvents }))
end
return Replication

This example doesn't provide the code for the scheduling or zap file.

Note: You may want to start replicating to players when they tell the server they are ready instead of on PlayerAdded. This is so clients with setup logic using observers won't be added after initial replication.

--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Jecs = require(ReplicatedStorage.Packages.Jecs)
type ModifyCommand =
{ kind: "Add", id: number, component: number }
| { kind: "Set", id: number, component: number, data: any }
| { kind: "Remove", id: number, component: number }
| { kind: "Clear", id: number }
export type World = Jecs.World & {
added: <T>(
self: World,
component: Jecs.Id<T>,
(entity: Jecs.Entity, id: Jecs.Id<T>, value: T) -> ()
) -> () -> (),
removed: <T>(self: World, component: Jecs.Id<T>, (entity: Jecs.Entity, id: Jecs.Id<T>) -> ()) -> () -> (),
changed: <T>(
self: World,
component: Jecs.Id<T>,
(entity: Jecs.Entity, id: Jecs.Id<T>, value: T) -> ()
) -> () -> (),
targets: <T>(self: World, entity: Jecs.Entity, relation: Jecs.Entity<T>) -> () -> (Jecs.Entity?, T?),
resource: <T>(self: World, resource: Jecs.Entity<T>) -> T,
setResource: <T>(self: World, resource: Jecs.Entity<T>, data: T) -> (),
clearResource: <T>(self: World, resource: Jecs.Entity<T>) -> (),
applyCommands: (self: World) -> (),
}
local World = {}
function World.new(jecsWorld: Jecs.World): World
local spawnCommands: { Jecs.Entity } = {}
local modifyCommands: { ModifyCommand } = {}
local deleteCommands: { Jecs.Entity } = {}
local worldEntity = jecsWorld.entity
local worldAdd = jecsWorld.add
local worldSet = jecsWorld.set
local worldRemove = jecsWorld.remove
local worldClear = jecsWorld.clear
local worldDelete = jecsWorld.delete
local resources = {}
jecsWorld.entity = function<T>(_: Jecs.World, id: Jecs.Entity<T>?): Jecs.Entity<T>
local newId = id or Jecs.entity_index_new_id(jecsWorld.entity_index :: any)
table.insert(spawnCommands, newId)
return newId
end
jecsWorld.add = function<T, a>(_: Jecs.World, id: Jecs.Entity<T>, component: Jecs.Id<a>)
table.insert(modifyCommands, {
kind = "Add",
id = id,
component = component,
})
end
jecsWorld.set = function<T, a>(_: Jecs.World, id: Jecs.Entity<T>, component: Jecs.Id<a>, data: a)
table.insert(modifyCommands, {
kind = "Set",
id = id,
component = component,
data = data,
})
end
jecsWorld.remove = function<T, a>(_: Jecs.World, id: Jecs.Entity<T>, component: Jecs.Id<a>)
table.insert(modifyCommands, { kind = "Remove", id = id, component = component })
end
jecsWorld.clear = function<a>(_: Jecs.World, id: Jecs.Id<a>)
table.insert(modifyCommands, { kind = "Clear", id = id })
end
jecsWorld.delete = function<T>(_: Jecs.World, id: Jecs.Entity<T>)
table.insert(deleteCommands, id)
end
local extendedWorld = jecsWorld :: World
local addedCallbacks = {}
local changedCallbacks = {}
local removedCallbacks = {}
extendedWorld.added = function<T>(_: World, component: Jecs.Id<T>, callback): () -> ()
local callbacks = addedCallbacks[component]
if callbacks == nil then
callbacks = {}
addedCallbacks[component] = callbacks
local function onAdd(entity, id, value)
for _, addedCallback in callbacks do
addedCallback(entity, id, value)
end
end
local existingHook = jecsWorld:get(component, Jecs.OnAdd)
if existingHook then
table.insert(callbacks, existingHook)
end
local idr = jecsWorld.component_index[component]
if idr then
idr.hooks.on_add = onAdd
else
worldSet(jecsWorld, component, Jecs.OnAdd, onAdd)
end
end
table.insert(callbacks, callback)
return function()
local i = table.find(callbacks, callback)
if i == nil then
return
end
local n = #callbacks
callbacks[i] = callbacks[n]
callbacks[n] = nil
end
end
extendedWorld.changed = function<T>(_: World, component: Jecs.Id<T>, callback)
local callbacks = changedCallbacks[component]
if callbacks == nil then
callbacks = {}
changedCallbacks[component] = callbacks
local function onChange(entity, id, value)
for _, changedCallback in callbacks do
changedCallback(entity, id, value)
end
end
local existingHook = jecsWorld:get(component, Jecs.OnChange)
if existingHook then
table.insert(callbacks, existingHook)
end
local idr = jecsWorld.component_index[component]
if idr then
idr.hooks.on_change = onChange
else
jecsWorld:set(component, Jecs.OnChange, onChange)
end
end
table.insert(callbacks, callback)
return function()
local i = table.find(callbacks, callback)
if i == nil then
return
end
local n = #callbacks
callbacks[i] = callbacks[n]
callbacks[n] = nil
end
end
extendedWorld.removed = function<T>(_: World, component: Jecs.Id<T>, callback)
local callbacks = removedCallbacks[component]
if callbacks == nil then
callbacks = {}
removedCallbacks[component] = callbacks
local function onRemove(entity, id)
for _, removedCallback in callbacks do
removedCallback(entity, id)
end
end
local existingHook = jecsWorld:get(component, Jecs.OnRemove)
if existingHook then
table.insert(callbacks, existingHook)
end
local idr = jecsWorld.component_index[component]
if idr then
idr.hooks.on_remove = onRemove
else
jecsWorld:set(component, Jecs.OnRemove, onRemove)
end
end
table.insert(callbacks, callback)
return function()
local i = table.find(callbacks, callback)
if i == nil then
return
end
local n = #callbacks
callbacks[i] = callbacks[n]
callbacks[n] = nil
end
end
extendedWorld.targets = function<T>(_: World, entity: Jecs.Entity, relation: Jecs.Entity<T>): () -> (Jecs.Entity?, T?)
local r = Jecs.entity_index_try_get(jecsWorld.entity_index :: any, entity :: number)
if not r or not r.archetype then
return function()
return nil, nil
end
end
local relationWildcard = Jecs.pair(relation, Jecs.Wildcard)
local archetype = r.archetype
local count = archetype.counts[relationWildcard :: number]
if not count then
return function()
return nil, nil
end
end
local types = archetype.types
local tr = archetype.records[relationWildcard :: number]
local columns = archetype.columns
local row = r.row
local i = 0
return function()
if i == count then
return nil, nil
end
local index = i + tr
i += 1
return Jecs.pair_second(jecsWorld, types[index]), columns[index][row]
end
end
extendedWorld.resource = function<T>(_: World, resource: Jecs.Entity<T>): T
local value = resources[resource]
if value == nil then
error(`resource {resource} is nil`)
end
return value
end
extendedWorld.setResource = function<T>(_: World, resource: Jecs.Entity<T>, data: T): ()
resources[resource] = data
end
extendedWorld.clearResource = function<T>(_: World, resource: Jecs.Entity<T>): ()
resources[resource] = nil
end
extendedWorld.applyCommands = function()
for _, id in spawnCommands do
worldEntity(jecsWorld, id)
end
table.clear(spawnCommands)
for _, modifyCommand in modifyCommands do
if modifyCommand.kind == "Set" then
worldSet(jecsWorld, modifyCommand.id, modifyCommand.component, modifyCommand.data)
elseif modifyCommand.kind == "Add" then
worldAdd(jecsWorld, modifyCommand.id, modifyCommand.component)
elseif modifyCommand.kind == "Remove" then
worldRemove(jecsWorld, modifyCommand.id, modifyCommand.component)
else
worldClear(jecsWorld, modifyCommand.id)
end
end
table.clear(modifyCommands)
for _, id in deleteCommands do
worldDelete(jecsWorld, id)
end
table.clear(deleteCommands)
end
return extendedWorld
end
return World
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment