From 57e653fa78bfd838ef4a5ad0734449579cf740ef Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 29 Sep 2025 23:02:54 +0200 Subject: [PATCH] Update replication demo --- .../systems/receive_replication.luau | 51 ++++-- .../systems/replication.luau | 151 +++++++++--------- 2 files changed, 117 insertions(+), 85 deletions(-) diff --git a/demo/src/ReplicatedStorage/systems/receive_replication.luau b/demo/src/ReplicatedStorage/systems/receive_replication.luau index 661d028..5a38bc1 100755 --- a/demo/src/ReplicatedStorage/systems/receive_replication.luau +++ b/demo/src/ReplicatedStorage/systems/receive_replication.luau @@ -12,7 +12,7 @@ local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity) local ser_id = id local deser_id = client_ids[ser_id] - if deser_id then + if deser_id then if deser_id == 0 then local new_id = world:entity() client_ids[ser_id] = new_id @@ -40,11 +40,18 @@ end -- local rel_render = `e{jecs.ECS_ID(rel)}v{jecs.ECS_GENERATION(rel)}` -- local tgt_render = `e{jecs.ECS_ID(tgt)}v{jecs.ECS_GENERATION(tgt)}` -local function ecs_deser_pairs(world, token) - local tokens = string.split(token, ",") - local rel = tonumber(tokens[1]) - local tgt = tonumber(tokens[2]) +-- local function ecs_deser_pairs_str(world, token) +-- local tokens = string.split(token, ",") +-- local rel = tonumber(tokens[1]) :: jecs.Entity +-- local tgt = tonumber(tokens[2]) :: jecs.Entity +-- rel = ecs_ensure_entity(world, rel) +-- tgt = ecs_ensure_entity(world, tgt) + +-- return jecs.pair(rel, tgt) +-- end + +local function ecs_deser_pairs(world, rel, tgt) rel = ecs_ensure_entity(world, rel) tgt = ecs_ensure_entity(world, tgt) @@ -53,18 +60,24 @@ end local snapshots = collect(remotes.replication.OnClientEvent) -return function(world: types.World) +return function(world: jecs.World) for entity in world:each(components.Destroy) do client_ids[entity] = nil end for snapshot in snapshots do for ser_id, map in snapshot do - local id = tonumber(ser_id) - if not id then - id = ecs_deser_pairs(world, ser_id) - else + local id = (tonumber(ser_id) :: any) :: jecs.Entity + if jecs.IS_PAIR(id) and map.pair then + id = ecs_deser_pairs(world, map.relation, map.target) + elseif id then id = ecs_ensure_entity(world, id) end + -- if not id then + -- id = ecs_deser_pairs_str(world, ser_id) + -- else + -- id = ecs_ensure_entity(world, id) + -- end + local members = world:get(id, components.NetworkedMembers) local set = map.set if set then @@ -74,11 +87,23 @@ return function(world: types.World) world:add(entity, id) end else - local t = os.clock() - local values = map.values + local values = map.values :: { any } for i, entity in set do entity = ecs_ensure_entity(world, entity) - world:set(entity, id, values[i]) + local value = values[i] + if members then + for _, member in members do + local data = value[member] :: {jecs.Entity} | jecs.Entity -- targets + if typeof(data) == "table" then + for pos, tgt in data :: { jecs.Entity } do + data[pos] = ecs_ensure_entity(world, tgt) + end + else + value[member] = ecs_ensure_entity(world, data :: any) + end + end + end + world:set(entity, id, value) end end end diff --git a/demo/src/ServerScriptService/systems/replication.luau b/demo/src/ServerScriptService/systems/replication.luau index 5ae440e..24bf995 100755 --- a/demo/src/ServerScriptService/systems/replication.luau +++ b/demo/src/ServerScriptService/systems/replication.luau @@ -8,24 +8,16 @@ local jecs = require(ReplicatedStorage.ecs) local collect = require(ReplicatedStorage.collect) local ty = require(ReplicatedStorage.types) -return function(world: ty.World) - - --- integration test - - -- for _ = 1, 10 do - -- local e = world:entity() - -- world:set(e, ct.TestA, true) - -- end - +return function(world: jecs.World) local storages = {} :: { [jecs.Entity]: {[jecs.Entity]: any }} local networked_components = {} local networked_pairs = {} - for component in world:each(ct.Networked) do - local name = assert(world:get(component, jecs.Name), "Invalid component") + for component in world:each(ct.Networked) do + local name = world:get(component, jecs.Name) + assert(name) if components[name] == nil then error("Invalid component:"..name) - end storages[component] = {} @@ -34,7 +26,8 @@ return function(world: ty.World) end for relation in world:each(ct.NetworkedPair) do - local name = world:get(relation, jecs.Name) :: string + local name = world:get(relation, jecs.Name) + assert(name) if not components[name] then error("Invalid component") end @@ -43,7 +36,7 @@ return function(world: ty.World) for _, component in networked_components do local name = world:get(component, jecs.Name) - if not components[name] then + if not name or not components[name] then -- error("Invalid component") error(`Networked Component (%id{component}%name{name})`) end @@ -68,51 +61,51 @@ return function(world: ty.World) end for _, relation in networked_pairs do - world:added(relation, function(entity, id, value) - local is_tag = jecs.is_tag(world, id) - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - if is_tag then - storage[entity] = true - else + world:added(relation, function(entity: jecs.Entity, id: jecs.Id, value) + local is_tag = jecs.is_tag(world, id) + local storage = storages[id] + if not storage then + storage = {} + storages[id] = storage + end + if is_tag then + storage[entity] = true + else + storage[entity] = value + end + end) + + world:changed(relation, function(entity: jecs.Id, id: jecs.Id, value) + local is_tag = jecs.is_tag(world, id) + if is_tag then + return + end + + local storage = storages[id] + if not storage then + storage = {} + storages[id] = storage + end + storage[entity] = value - end - end) + end) - world:changed(relation, function(entity, id, value) - local is_tag = jecs.is_tag(world, id) - if is_tag then - return - end + world:removed(relation, function(entity, id) + local storage = storages[id] + if not storage then + storage = {} + storages[id] = storage + end - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - - storage[entity] = value - end) - - world:removed(relation, function(entity, id) - local storage = storages[id] - if not storage then - storage = {} - storages[id] = storage - end - - storage[entity] = "jecs.Remove" - end) + storage[entity] = "jecs.Remove" + end) end -- local requested_snapshots = collect(remotes.request_snapshot.OnServerEvent) local players_added = collect(Players.PlayerAdded) - return function() - local snapshot_lazy: ty.Snapshot + return function(_, dt: number) + local snapshot_lazy: ty.snapshot local set_ids_lazy: { jecs.Entity } -- In the future maybe it should be requested by the player instead when they @@ -132,9 +125,7 @@ return function(world: ty.World) local entities = archetype.entities local entities_len = #entities table.move(entities, 1, entities_len, set_n + 1, set_ids_lazy) - if is_tag then - set_values = table.create(entities_len, true) - else + if not is_tag then local column = archetype.columns_map[component] table.move(column, 1, entities_len, set_n + 1, set_values) end @@ -144,25 +135,31 @@ return function(world: ty.World) local set = table.move(set_ids_lazy, 1, set_n, 1, {}) - local ser_id: string = nil :: any - - if jecs.IS_PAIR(component) then - ser_id = `{jecs.pair_first(world, component)},{jecs.pair_first(world, component)}` - else - ser_id = tostring(component) - end - - snapshot_lazy[ser_id] = { + local map = { set = if set_n > 0 then set else nil, values = if set_n > 0 then set_values else nil, } + + if jecs.IS_PAIR(component) then + map.relation = jecs.pair_first(world, component) + map.target = jecs.pair_second(world, component) + map.pair = true + end + snapshot_lazy[tostring(component)] = map end end remotes.replication:FireClient(player, snapshot_lazy) end - local snapshot = {} :: ty.Snapshot + -- accumulator += dt + + -- Purposely sending less diffs of the world because doing it at 60hz + -- gets expensive. But this requires interpolated elements in the scene. + -- if accumulator > 1/60 then + -- accumulator = 0 + + local snapshot = {} :: ty.snapshot local set_ids = {} local removed_ids = {} @@ -193,23 +190,33 @@ return function(world: ty.World) if dirty then local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity } local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity } - local ser_id: string = nil :: any - if jecs.IS_PAIR(component) then - ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}` - else - ser_id = tostring(component) - end + -- local ser_id: string = nil :: any - snapshot[ser_id] = { + -- if jecs.IS_PAIR(component) then + -- ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}` + -- else + -- ser_id = tostring(component) + -- end + + local map = { set = if set_n > 0 then set else nil, values = if set_n > 0 then set_values else nil, - removed = if removed_n > 0 then removed else nil + removed = if removed_n > 0 then removed else nil, } + + if jecs.IS_PAIR(component) then + map.relation = jecs.pair_first(world, component) + map.target = jecs.pair_second(world, component) + map.pair = true + end + + snapshot[tostring(component)] = map end end if next(snapshot) ~= nil then remotes.replication:FireAllClients(snapshot) + -- print(snapshot) end end end