From 6a8d99118572e1f5a5bdf5bca582a794456ac2b6 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 28 Apr 2025 23:40:03 +0200 Subject: [PATCH] Networking example --- demo/default.project.json | 131 +++++------- demo/src/ReplicatedStorage/collect.luau | 28 +++ demo/src/ReplicatedStorage/components.luau | 36 ++++ demo/src/ReplicatedStorage/ecs_init.luau | 4 - demo/src/ReplicatedStorage/main.client.luau | 13 ++ demo/src/ReplicatedStorage/observers_add.luau | 190 ++++++++++++++++++ demo/src/ReplicatedStorage/remotes.luau | 50 +++++ demo/src/ReplicatedStorage/schedule.luau | 136 +++++++++++++ demo/src/ReplicatedStorage/start.luau | 34 ---- demo/src/ReplicatedStorage/std/bt.luau | 40 ---- demo/src/ReplicatedStorage/std/collect.luau | 67 ------ .../src/ReplicatedStorage/std/components.luau | 30 --- demo/src/ReplicatedStorage/std/interval.luau | 19 -- demo/src/ReplicatedStorage/std/phases.luau | 14 -- demo/src/ReplicatedStorage/std/ref.luau | 26 --- demo/src/ReplicatedStorage/std/scheduler.luau | 171 ---------------- demo/src/ReplicatedStorage/std/world.luau | 4 - .../systems/receive_replication.luau | 67 ++++++ demo/src/ReplicatedStorage/track.luau | 48 ----- demo/src/ReplicatedStorage/types.luau | 8 + demo/src/ServerScriptService/main.server.luau | 19 +- .../systems/life_is_painful.luau | 12 ++ .../src/ServerScriptService/systems/mobs.luau | 88 -------- .../ServerScriptService/systems/players.luau | 40 ---- .../systems/players_added.luau | 20 ++ .../systems/poison_hurts.luau | 12 ++ .../systems/replication.luau | 122 +++++++++++ .../StarterPlayerScripts/main.client.luau | 4 - .../StarterPlayerScripts/systems/lol.luau | 67 ------ .../StarterPlayerScripts/systems/lol2.luau | 90 --------- .../StarterPlayerScripts/systems/move.luau | 46 ----- .../systems/syncMobs.luau | 31 --- .../StarterPlayerScripts/systems/test.luau | 44 ---- jecs.luau | 4 +- rokit.toml | 1 + 35 files changed, 769 insertions(+), 947 deletions(-) create mode 100644 demo/src/ReplicatedStorage/collect.luau create mode 100644 demo/src/ReplicatedStorage/components.luau delete mode 100644 demo/src/ReplicatedStorage/ecs_init.luau create mode 100644 demo/src/ReplicatedStorage/main.client.luau create mode 100644 demo/src/ReplicatedStorage/observers_add.luau create mode 100644 demo/src/ReplicatedStorage/remotes.luau create mode 100644 demo/src/ReplicatedStorage/schedule.luau delete mode 100644 demo/src/ReplicatedStorage/start.luau delete mode 100644 demo/src/ReplicatedStorage/std/bt.luau delete mode 100644 demo/src/ReplicatedStorage/std/collect.luau delete mode 100644 demo/src/ReplicatedStorage/std/components.luau delete mode 100644 demo/src/ReplicatedStorage/std/interval.luau delete mode 100644 demo/src/ReplicatedStorage/std/phases.luau delete mode 100644 demo/src/ReplicatedStorage/std/ref.luau delete mode 100644 demo/src/ReplicatedStorage/std/scheduler.luau delete mode 100644 demo/src/ReplicatedStorage/std/world.luau create mode 100644 demo/src/ReplicatedStorage/systems/receive_replication.luau delete mode 100644 demo/src/ReplicatedStorage/track.luau create mode 100644 demo/src/ReplicatedStorage/types.luau create mode 100644 demo/src/ServerScriptService/systems/life_is_painful.luau delete mode 100644 demo/src/ServerScriptService/systems/mobs.luau delete mode 100644 demo/src/ServerScriptService/systems/players.luau create mode 100644 demo/src/ServerScriptService/systems/players_added.luau create mode 100644 demo/src/ServerScriptService/systems/poison_hurts.luau create mode 100644 demo/src/ServerScriptService/systems/replication.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau delete mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/test.luau diff --git a/demo/default.project.json b/demo/default.project.json index b3b41f9..e95cf06 100644 --- a/demo/default.project.json +++ b/demo/default.project.json @@ -1,76 +1,55 @@ -{ - "name": "demo", - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "$className": "ReplicatedStorage", - "$path": "src/ReplicatedStorage", - "ecs": { - "$path": "../jecs.luau" - }, - "net": { - "$path": "net/client.luau" - }, - "Packages": { - "$path": "Packages" - } - }, - "ServerScriptService": { - "$className": "ServerScriptService", - "$path": "src/ServerScriptService", - "net": { - "$path": "net/server.luau" - } - }, - "Workspace": { - "$properties": { - "FilteringEnabled": true - }, - "Baseplate": { - "$className": "Part", - "$properties": { - "Anchored": true, - "Color": [ - 0.38823, - 0.37254, - 0.38823 - ], - "Locked": true, - "Position": [ - 0, - -10, - 0 - ], - "Size": [ - 512, - 20, - 512 - ] - } - } - }, - "Lighting": { - "$properties": { - "Ambient": [ - 0, - 0, - 0 - ], - "Brightness": 2, - "GlobalShadows": true, - "Outlines": false, - "Technology": "Voxel" - } - }, - "SoundService": { - "$properties": { - "RespectFilteringEnabled": true - } - }, - "StarterPlayer": { - "StarterPlayerScripts": { - "$path": "src/StarterPlayer/StarterPlayerScripts" - } - } - } -} +{ + "name": "demo", + "emitLegacyScripts": false, + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "$path": "src/ReplicatedStorage", + "ecs": { + "$path": "../jecs.luau" + }, + "Packages": { + "$path": "Packages" + } + }, + "ServerScriptService": { + "$className": "ServerScriptService", + "$path": "src/ServerScriptService" + }, + "Workspace": { + "$properties": { + "FilteringEnabled": true + }, + "Baseplate": { + "$className": "Part", + "$properties": { + "Anchored": true, + "Color": [0.38823, 0.37254, 0.38823], + "Locked": true, + "Position": [0, -10, 0], + "Size": [512, 20, 512] + } + } + }, + "Lighting": { + "$properties": { + "Ambient": [0, 0, 0], + "Brightness": 2, + "GlobalShadows": true, + "Outlines": false, + "Technology": "Voxel" + } + }, + "SoundService": { + "$properties": { + "RespectFilteringEnabled": true + } + }, + "StarterPlayer": { + "StarterPlayerScripts": { + "$path": "src/StarterPlayer/StarterPlayerScripts" + } + } + } +} diff --git a/demo/src/ReplicatedStorage/collect.luau b/demo/src/ReplicatedStorage/collect.luau new file mode 100644 index 0000000..d6df981 --- /dev/null +++ b/demo/src/ReplicatedStorage/collect.luau @@ -0,0 +1,28 @@ +local function collect( + signal: { + Connect: (RBXScriptSignal, fn: (T...) -> ()) -> RBXScriptConnection + } +): () -> (T...) + local enqueued = {} + + local i = 0 + + local connection = (signal :: any):Connect(function(...) + table.insert(enqueued, { ... }) + i += 1 + end) + + return function(): any + if i == 0 then + return + end + + i -= 1 + + local args: any = table.remove(enqueued, 1) + + return unpack(args) + end, connection +end + +return collect diff --git a/demo/src/ReplicatedStorage/components.luau b/demo/src/ReplicatedStorage/components.luau new file mode 100644 index 0000000..5bb3225 --- /dev/null +++ b/demo/src/ReplicatedStorage/components.luau @@ -0,0 +1,36 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jecs = require(ReplicatedStorage.ecs) +local types = require("./types") + +local Networked = jecs.tag() +local NetworkedPair = jecs.tag() + +local Renderable = jecs.component() :: jecs.Id +jecs.meta(Renderable, Networked) + + +local Poison = jecs.component() :: jecs.Id +jecs.meta(Poison, Networked) + +local Health = jecs.component() :: jecs.Id +jecs.meta(Health, Networked) + +local Player = jecs.component() :: jecs.Id +jecs.meta(Player, Networked) + + +local components = { + Renderable = Renderable, + Player = Player, + Poison = Poison, + Health = Health, + + Networked = Networked, + NetworkedPair = NetworkedPair, +} + +for name, component in components do + jecs.meta(component, jecs.Name, name) +end + +return components diff --git a/demo/src/ReplicatedStorage/ecs_init.luau b/demo/src/ReplicatedStorage/ecs_init.luau deleted file mode 100644 index d454567..0000000 --- a/demo/src/ReplicatedStorage/ecs_init.luau +++ /dev/null @@ -1,4 +0,0 @@ -_G.JECS_DEBUG = true -_G.JECS_HI_COMPONENT_ID = 32 -require(game:GetService("ReplicatedStorage").ecs) -return diff --git a/demo/src/ReplicatedStorage/main.client.luau b/demo/src/ReplicatedStorage/main.client.luau new file mode 100644 index 0000000..11dffbc --- /dev/null +++ b/demo/src/ReplicatedStorage/main.client.luau @@ -0,0 +1,13 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jecs = require(ReplicatedStorage.ecs) +local schedule = require(ReplicatedStorage.schedule) +local observers_add = require(ReplicatedStorage.observers_add) + +local SYSTEM = schedule.SYSTEM +local RUN = schedule.RUN +require(ReplicatedStorage.components) +local world = observers_add(jecs.world()) + +local systems = ReplicatedStorage.systems +SYSTEM(world, systems.receive_replication) +RUN(world) diff --git a/demo/src/ReplicatedStorage/observers_add.luau b/demo/src/ReplicatedStorage/observers_add.luau new file mode 100644 index 0000000..c959b88 --- /dev/null +++ b/demo/src/ReplicatedStorage/observers_add.luau @@ -0,0 +1,190 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jecs = require(ReplicatedStorage.ecs) + +type Observer = { + callback: (jecs.Entity) -> (), + query: jecs.Query, +} + +export type PatchedWorld = jecs.World & { + added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), + removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (), + changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), + -- deleted: (PatchedWorld, () -> ()) -> () -> (), + observer: (PatchedWorld, Observer) -> (), + monitor: (PatchedWorld, Observer) -> (), +} + +local function observers_new(world, description) + local query = description.query + local callback = description.callback + local terms = query.filter_with :: { jecs.Id } + if not terms then + local ids = query.ids + query.filter_with = ids + terms = ids + end + + local entity_index = world.entity_index :: any + local function emplaced(entity: jecs.Entity) + local r = jecs.entity_index_try_get_fast( + entity_index, entity :: any) + + if not r then + return + end + + local archetype = r.archetype + + if jecs.query_match(query, archetype) then + callback(entity) + end + end + + for _, term in terms do + world:added(term, emplaced) + world:changed(term, emplaced) + end +end + +local function monitors_new(world, description) + local query = description.query + local callback = description.callback + local terms = query.filter_with :: { jecs.Id } + if not terms then + local ids = query.ids + query.filter_with = ids + terms = ids + end + + local entity_index = world.entity_index :: any + local function emplaced(entity: jecs.Entity) + local r = jecs.entity_index_try_get_fast( + entity_index, entity :: any) + + if not r then + return + end + + local archetype = r.archetype + + if jecs.query_match(query, archetype) then + callback(entity, jecs.OnAdd) + end + end + + local function removed(entity: jecs.Entity, component: jecs.Id) + local r = jecs.entity_index_try_get_fast( + entity_index, entity :: any) + + if not r then + return + end + + local archetype = r.archetype + + if jecs.query_match(query, archetype) then + callback(entity, jecs.OnRemove) + end + end + + for _, term in terms do + world:added(term, emplaced) + world:removed(term, removed) + end +end + +local function observers_add(world: jecs.World): PatchedWorld + local signals = { + added = {}, + emplaced = {}, + removed = {}, + deleted = {} + } + + world = world :: jecs.World & {[string]: any} + + world.added = function(_, component, fn) + local listeners = signals.added[component] + if not listeners then + listeners = {} + signals.added[component] = listeners + + local idr = jecs.id_record_ensure(world :: any, component :: any) + local rw = jecs.pair(component, jecs.Wildcard) + local idr_r = jecs.id_record_ensure(world :: any, rw :: any) + local function on_add(entity: number, id: number, value: any) + for _, listener in listeners do + listener(entity, id, value) + end + end + world:set(component, jecs.OnAdd, on_add) + idr.hooks.on_add = on_add :: any + idr_r.hooks.on_add = on_add :: any + end + table.insert(listeners, fn) + end + + world.changed = function(_, component, fn) + local listeners = signals.emplaced[component] + if not listeners then + listeners = {} + signals.emplaced[component] = listeners + local idr = jecs.id_record_ensure(world :: any, component :: any) + local rw = jecs.pair(component, jecs.Wildcard) + local idr_r = jecs.id_record_ensure(world :: any, rw :: any) + local function on_change(entity: number, id: number, value: any) + for _, listener in listeners do + listener(entity, id, value) + end + end + world:set(component, jecs.OnChange, on_change) + idr.hooks.on_change = on_change :: any + idr_r.hooks.on_change = on_change :: any + end + table.insert(listeners, fn) + end + + world.removed = function(_, component, fn) + local listeners = signals.removed[component] + if not listeners then + listeners = {} + signals.removed[component] = listeners + local idr = jecs.id_record_ensure(world :: any, component :: any) + local rw = jecs.pair(component, jecs.Wildcard) + local idr_r = jecs.id_record_ensure(world :: any, rw :: any) + local function on_remove(entity: number, id: number, value: any) + for _, listener in listeners do + listener(entity, id, value) + end + end + world:set(component, jecs.OnRemove, on_remove) + idr.hooks.on_remove = on_remove :: any + idr_r.hooks.on_remove = on_remove :: any + end + table.insert(listeners, fn) + end + + world.signals = signals + + world.observer = observers_new + + world.monitor = monitors_new + + -- local world_delete = world.delete + + -- world.deleted = function(_, fn) + -- local listeners = signals.deleted + -- table.insert(listeners, fn) + -- end + -- world.delete = function(world, entity) + -- world_delete(world, entity) + -- for _, fn in signals.deleted do + -- fn(entity) + -- end + -- end + + return world :: PatchedWorld +end + +return observers_add diff --git a/demo/src/ReplicatedStorage/remotes.luau b/demo/src/ReplicatedStorage/remotes.luau new file mode 100644 index 0000000..4838770 --- /dev/null +++ b/demo/src/ReplicatedStorage/remotes.luau @@ -0,0 +1,50 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local types = require("../ReplicatedStorage/types") + +type Signal = { + Connect: (Signal, fn: (T...) -> ()) -> RBXScriptConnection +} +type Remote = { + FireClient: (Remote, T...) -> (), + FireAllClients: (Remote, T...) -> (), + FireServer: (Remote) -> (), + OnServerEvent: { + Connect: (any, fn: (Player, T...) -> () ) -> () + }, + OnClientEvent: { + Connect: (any, fn: (T...) -> () ) -> () + } + +} + +local function stream_ensure(name): Remote + local remote = ReplicatedStorage:FindFirstChild(name) + if not remote then + remote = Instance.new("RemoteEvent") + remote.Name = name + remote.Parent = ReplicatedStorage + end + return remote :: any +end + +local function datagram_ensure(name): Remote + local remote = ReplicatedStorage:FindFirstChild(name) + if not remote then + remote = Instance.new("UnreliableRemoteEvent") + remote.Name = name + remote.Parent = ReplicatedStorage + end + return remote :: any +end + +return { + input = datagram_ensure("input") :: Remote, + replication = stream_ensure("replication") :: Remote<{ + [string]: { + set: { types.Entity }?, + values: { any }?, + removed: { types.Entity }? + } + }>, + +} diff --git a/demo/src/ReplicatedStorage/schedule.luau b/demo/src/ReplicatedStorage/schedule.luau new file mode 100644 index 0000000..7030bbe --- /dev/null +++ b/demo/src/ReplicatedStorage/schedule.luau @@ -0,0 +1,136 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jabby = require(ReplicatedStorage.Packages.jabby) +local jecs = require(ReplicatedStorage.ecs) + +jabby.set_check_function(function() return true end) + +local scheduler = jabby.scheduler.create("jabby scheduler") + +jabby.register({ + applet = jabby.applets.scheduler, + name = "Scheduler", + configuration = { + scheduler = scheduler, + }, +}) + +local ContextActionService = game:GetService("ContextActionService") + +local function create_widget(_, state: Enum.UserInputState) + local client = jabby.obtain_client() + if state ~= Enum.UserInputState.Begin then return end + client.spawn_app(client.apps.home, nil) +end + +local RunService = game:GetService("RunService") + +local System = jecs.component() :: jecs.Id<{ + fn: () -> (), + name: string, +}> +local DependsOn = jecs.component() +local Phase = jecs.tag() +local Event = jecs.component() :: jecs.Id + +local pair = jecs.pair + +local types = require(ReplicatedStorage.types) + +local function ECS_PHASE(world, after: types.Entity) + local phase = world:entity() + world:add(phase, Phase) + if after then + local dependency = pair(DependsOn, after) + world:add(phase, dependency) + end + + return phase +end + +local Heartbeat = jecs.tag() +jecs.meta(Heartbeat, Phase) +jecs.meta(Heartbeat, Event, RunService.Heartbeat) + +local PreSimulation = jecs.tag() +jecs.meta(PreSimulation, Phase) +jecs.meta(PreSimulation, Event, RunService.PreSimulation) + +local PreAnimation = jecs.tag() +jecs.meta(PreAnimation, Phase) +jecs.meta(PreAnimation, Event, RunService.PreAnimation) + +local PreRender = jecs.tag() +jecs.meta(PreRender, Phase) +jecs.meta(PreRender, Event, RunService.PreRender) + +local function ECS_SYSTEM(world: types.World, mod: ModuleScript, phase: types.Entity?) + local system = world:entity() + local p = phase or Heartbeat + local fn = require(mod) :: (...any) -> () + world:set(system, System, { + fn = fn(world, 0) or fn, + name = mod.Name, + }) + + local depends_on = DependsOn :: jecs.Entity + world:add(system, pair(depends_on, p)) +end +local function find_systems_w_phase(world: types.World, systems, phase: types.Entity) + local phase_name = world:get(phase, jecs.Name) :: string + for _, s in world:query(System):with(pair(DependsOn, phase)) do + table.insert(systems, { + id = scheduler:register_system({ + phase = phase_name, + name = s.name, + }), + fn = s.fn + }) + end + for after in world:query(Phase, pair(DependsOn, phase)) do + find_systems_w_phase(world, systems, after) + end + return systems +end + +local function ECS_RUN(world: types.World) + + jabby.register({ + applet = jabby.applets.world, + name = "MyWorld", + configuration = { + world = world, + }, + }) + + if RunService:IsClient() then + ContextActionService:BindAction("Open Jabby Home", create_widget, false, Enum.KeyCode.F4) + end + + for phase, event in world:query(Event, Phase) do + local systems = find_systems_w_phase(world, {}, phase) + event:Connect(function(...) + for _, system in systems do + scheduler:run(system.id, system.fn, world, ...) + end + end) + end + +end + +return { + PHASE = ECS_PHASE, + SYSTEM = ECS_SYSTEM, + RUN = ECS_RUN, + phases = { + Heartbeat = Heartbeat, + PreSimulation = PreSimulation, + PreAnimation = PreAnimation, + PreRender = PreRender + }, + components = { + System = System, + DependsOn = DependsOn, + Phase = Phase, + Event = Event, + } +} diff --git a/demo/src/ReplicatedStorage/start.luau b/demo/src/ReplicatedStorage/start.luau deleted file mode 100644 index 1c4f373..0000000 --- a/demo/src/ReplicatedStorage/start.luau +++ /dev/null @@ -1,34 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local RunService = game:GetService("RunService") -local UserInputService = game:GetService("UserInputService") -local jabby = require(ReplicatedStorage.Packages.jabby) -local std = ReplicatedStorage.std -local scheduler = require(std.scheduler) -local world = require(std.world) - -local function start(modules) - for _, module in modules do - require(module) - end - local events = scheduler.COLLECT() - scheduler.BEGIN(events) - jabby.set_check_function(function(player) - return true - end) - if RunService:IsClient() then - local player = game:GetService("Players").LocalPlayer - local playergui = player:WaitForChild("PlayerGui") - local client = jabby.obtain_client() - UserInputService.InputBegan:Connect(function(input) - if input.KeyCode == Enum.KeyCode.F4 then - local home = playergui:FindFirstChild("Home") - if home then - home:Destroy() - end - client.spawn_app(client.apps.home) - end - end) - end -end - -return start diff --git a/demo/src/ReplicatedStorage/std/bt.luau b/demo/src/ReplicatedStorage/std/bt.luau deleted file mode 100644 index 81d11c5..0000000 --- a/demo/src/ReplicatedStorage/std/bt.luau +++ /dev/null @@ -1,40 +0,0 @@ ---!optimize 2 ---!native - --- original author @centau - -local FAILURE = -1 -local RUNNING = 0 -local SUCCESS = 1 - -local function SEQUENCE(nodes) - return function(...) - for _, node in nodes do - local status = node(...) - if status <= RUNNING then - return status - end - end - return SUCCESS - end -end - -local function FALLBACK(nodes) - return function(...) - for _, node in nodes do - local status = node(...) - if status > FAILURE then - return status - end - end - return FAILURE - end -end - -local bt = { - SEQUENCE = SEQUENCE, - FALLBACK = FALLBACK, - RUNNING = RUNNING, -} - -return bt diff --git a/demo/src/ReplicatedStorage/std/collect.luau b/demo/src/ReplicatedStorage/std/collect.luau deleted file mode 100644 index edd9870..0000000 --- a/demo/src/ReplicatedStorage/std/collect.luau +++ /dev/null @@ -1,67 +0,0 @@ ---!nonstrict - ---[[ - local signal = Signal.new() :: Signal.Signal - local events = collect(signal) - local function system(world) - for id, str1, str2 in events do - -- - end - end -]] - ---[[ -original author by @memorycode - -MIT License - -Copyright (c) 2024 Michael - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ---]] - -type Signal = { [any]: any } -local function collect(event: Signal) - local storage = {} - local mt = {} - local iter = function() - local n = #storage - return function() - if n <= 0 then - mt.__iter = nil - return nil - end - - n -= 1 - return n + 1, unpack(table.remove(storage, 1) :: any) - end - end - - local disconnect = event:Connect(function(...) - table.insert(storage, { ... }) - mt.__iter = iter - end) - - setmetatable(storage, mt) - return (storage :: any) :: () -> (number, T...), function() - disconnect() - end -end - -return collect diff --git a/demo/src/ReplicatedStorage/std/components.luau b/demo/src/ReplicatedStorage/std/components.luau deleted file mode 100644 index 0e2d406..0000000 --- a/demo/src/ReplicatedStorage/std/components.luau +++ /dev/null @@ -1,30 +0,0 @@ -local jecs = require(game:GetService("ReplicatedStorage").ecs) -local world = require(script.Parent.world) - -type Entity = jecs.Entity -local components: { - Character: Entity, - Mob: Entity, - Model: Entity, - Player: Entity, - Target: Entity, - Transform: Entity<{ new: CFrame, old: CFrame }>, - Velocity: Entity, - Previous: Entity, -} = - { - Character = world:component(), - Mob = world:component(), - Model = world:component(), - Player = world:component(), - Target = world:component(), - Transform = world:component(), - Velocity = world:component(), - Previous = world:component(), - } - -for name, component in components :: {[string]: jecs.Entity} do - world:set(component, jecs.Name, name) -end - -return table.freeze(components) diff --git a/demo/src/ReplicatedStorage/std/interval.luau b/demo/src/ReplicatedStorage/std/interval.luau deleted file mode 100644 index 99cda27..0000000 --- a/demo/src/ReplicatedStorage/std/interval.luau +++ /dev/null @@ -1,19 +0,0 @@ -local function interval(s) - local pin - - local function throttle() - if not pin then - pin = os.clock() - end - - local elapsed = os.clock() - pin > s - if elapsed then - pin = os.clock() - end - - return elapsed - end - return throttle -end - -return interval \ No newline at end of file diff --git a/demo/src/ReplicatedStorage/std/phases.luau b/demo/src/ReplicatedStorage/std/phases.luau deleted file mode 100644 index 30e6bbe..0000000 --- a/demo/src/ReplicatedStorage/std/phases.luau +++ /dev/null @@ -1,14 +0,0 @@ -local std = game:GetService("ReplicatedStorage").std -local Players = game:GetService("Players") - -local scheduler = require(std.scheduler) -local PHASE = scheduler.PHASE - -return { - PlayerAdded = PHASE({ - event = Players.PlayerAdded - }), - PlayerRemoved = PHASE({ - event = Players.PlayerRemoving - }) -} diff --git a/demo/src/ReplicatedStorage/std/ref.luau b/demo/src/ReplicatedStorage/std/ref.luau deleted file mode 100644 index 6700f14..0000000 --- a/demo/src/ReplicatedStorage/std/ref.luau +++ /dev/null @@ -1,26 +0,0 @@ -local world = require(script.Parent.world) -local jecs = require(game:GetService("ReplicatedStorage").ecs) -local refs: {[any]: jecs.Entity} = {} - -local function fini(key): () -> () - return function() - refs[key] = nil - end -end - -local function noop() end - -local function ref(key): (jecs.Entity, () -> ()) - if not key then - return world:entity(), noop - end - local e = refs[key] - if not e then - e = world:entity() - refs[key] = e - end - -- Cannot cache handles because they will get invalidated - return e, fini(key) -end - -return ref diff --git a/demo/src/ReplicatedStorage/std/scheduler.luau b/demo/src/ReplicatedStorage/std/scheduler.luau deleted file mode 100644 index 5c0092a..0000000 --- a/demo/src/ReplicatedStorage/std/scheduler.luau +++ /dev/null @@ -1,171 +0,0 @@ ---!native ---!optimize 2 -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local RunService = game:GetService("RunService") -local jabby = require(ReplicatedStorage.Packages.jabby) -local jecs = require(ReplicatedStorage.ecs) -local pair = jecs.pair -local Name = jecs.Name - -type World = jecs.World -type Entity = jecs.Entity -type Id = jecs.Id - -type System = { - callback: (world: World) -> (), - id: number, -} - -type Systems = { System } - -type Events = { - RenderStepped: Systems, - Heartbeat: Systems, -} - -local world = require(script.Parent.world) -local Disabled = world:entity() -local System = world:component() :: Id<{ callback: (any) -> (), name: string}> -local DependsOn = world:entity() -local Event = world:component() :: Id -local Phase = world:entity() - -local PreRender = world:entity() -local Heartbeat = world:entity() -local PreAnimation = world:entity() -local PreSimulation = world:entity() - -local sys: System -local dt: number - -local jabby_scheduler = jabby.scheduler.create("Scheduler") - -local a, b, c, d -local function run() - local id = sys.id - jabby_scheduler:run(id, sys.callback, a, b, c, d) - return nil -end - -world:add(Heartbeat, Phase) -world:set(Heartbeat, Event, RunService.Heartbeat) - -world:add(PreSimulation, Phase) -world:set(PreSimulation, Event, RunService.PreSimulation) - -world:add(PreAnimation, Phase) -world:set(PreAnimation, Event, RunService.PreAnimation) - -jabby.register({ - applet = jabby.applets.world, - name = "MyWorld", - configuration = { - world = world, - }, -}) - -jabby.register({ - applet = jabby.applets.scheduler, - name = "Scheduler", - configuration = { - scheduler = jabby_scheduler, - }, -}) - -if RunService:IsClient() then - world:add(PreRender, Phase) - world:set(PreRender, Event, (RunService :: RunService).PreRender) -end - -local function begin(events: { [RBXScriptSignal]: Systems }) - local connections = {} - for event, systems in events do - if not event then - continue - end - local event_name = tostring(event) - connections[event] = event:Connect(function(...) - debug.profilebegin(event_name) - for _, s in systems do - sys = s - a, b, c, d = ... - - for _ in run do - break - end - - end - debug.profileend() - end) - end - return connections -end - -local function scheduler_collect_systems_under_phase_recursive(systems, phase: Entity) - local phase_name = world:get(phase, Name) - for _, s in world:query(System):with(pair(DependsOn, phase)) do - table.insert(systems, { - id = jabby_scheduler:register_system({ - name = s.name, - phase = phase_name, - } :: any), - callback = s.callback, - }) - end - for after in world:query(Phase):with(pair(DependsOn, phase)):iter() do - scheduler_collect_systems_under_phase_recursive(systems, after) - end -end - -local function scheduler_collect_systems_under_event(event) - local systems = {} - scheduler_collect_systems_under_phase_recursive(systems, event) - return systems -end - -local function scheduler_collect_systems_all() - local events = {} - for phase, event in world:query(Event):with(Phase) do - events[event] = scheduler_collect_systems_under_event(phase) - end - return events -end - -local function scheduler_phase_new(d: { after: Entity?, event: RBXScriptSignal? }) - local phase = world:entity() - world:add(phase, Phase) - local after = d.after - if after then - local dependency = pair(DependsOn, after :: Entity) - world:add(phase, dependency) - end - - local event = d.event - if event then - world:set(phase, Event, event) - end - return phase -end - -local function scheduler_systems_new(callback: (any) -> (), phase: Entity?) - local system = world:entity() - world:set(system, System, { callback = callback, name = debug.info(callback, "n") }) - local depends_on = DependsOn :: jecs.Entity - local p: Entity = phase or Heartbeat - world:add(system, pair(depends_on, p)) - - return system -end - -return { - SYSTEM = scheduler_systems_new, - BEGIN = begin, - PHASE = scheduler_phase_new, - COLLECT = scheduler_collect_systems_all, - phases = { - Heartbeat = Heartbeat, - PreSimulation = PreSimulation, - PreAnimation = PreAnimation, - PreRender = PreRender - } -} diff --git a/demo/src/ReplicatedStorage/std/world.luau b/demo/src/ReplicatedStorage/std/world.luau deleted file mode 100644 index 553370b..0000000 --- a/demo/src/ReplicatedStorage/std/world.luau +++ /dev/null @@ -1,4 +0,0 @@ -local jecs = require(game:GetService("ReplicatedStorage").ecs) - --- I like the idea of only having the world be a singleton. -return jecs.World.new() :: jecs.World diff --git a/demo/src/ReplicatedStorage/systems/receive_replication.luau b/demo/src/ReplicatedStorage/systems/receive_replication.luau new file mode 100644 index 0000000..7574f34 --- /dev/null +++ b/demo/src/ReplicatedStorage/systems/receive_replication.luau @@ -0,0 +1,67 @@ +local types = require("../types") +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local remotes = require("../remotes") +local collect = require("../collect") +local client_ids = {} + +local function ecs_map_get(world: types.World, id: types.Entity) + local deserialised_id = 0 + + if not world:exists(id) or not world:contains(id) then + deserialised_id = world:entity(id) + client_ids[id] = deserialised_id + else + deserialised_id = client_ids[id] + end + + return deserialised_id +end + +local function ecs_make_alive_id(world: types.World, id: jecs.Id) + local rel = jecs.ECS_PAIR_FIRST(id) + local tgt = jecs.ECS_PAIR_SECOND(id) + + ecs_map_get(world, rel) + ecs_map_get(world, tgt) +end + +local snapshots = collect(remotes.replication.OnClientEvent) + +return function(world: types.World) + return function() + for snapshot in snapshots do + for key, map in snapshot do + local id = (tonumber(key) :: any) :: jecs.Id + if jecs.IS_PAIR(id) then + ecs_make_alive_id(world, id) + end + + local set = map.set + if set then + if jecs.is_tag(world, id) then + for _, entity in set do + entity = ecs_map_get(world, entity) + world:add(entity, id) + end + else + local values = map.values :: { any } + for i, entity in set do + entity = ecs_map_get(world, entity) + world:set(entity, id, values[i]) + end + end + end + + local removed = map.removed + if removed then + for i, e in removed do + if not world:contains(e) then + continue + end + world:remove(e, id) + end + end + end + end + end +end diff --git a/demo/src/ReplicatedStorage/track.luau b/demo/src/ReplicatedStorage/track.luau deleted file mode 100644 index 79c4006..0000000 --- a/demo/src/ReplicatedStorage/track.luau +++ /dev/null @@ -1,48 +0,0 @@ -local events = {} - -local function trackers_invoke(event, component, entity, ...) - local trackers = events[event][component] - if not trackers then - return - end - - for _, tracker in trackers do - tracker(entity, data) - end -end - -local function trackers_init(event, component, fn) - local ob = events[event] - - return { - connect = function(component, fn) - local trackers = ob[component] - if not trackers then - trackers = {} - ob[component] = trackers - end - - table.insert(trackers, fn) - end, - invoke = function(component, ...) - trackers_invoke(event, component, ...) - end - } - return function(component, fn) - local trackers = ob[component] - if not trackers then - trackers = {} - ob[component] = trackers - end - - table.insert(trackers, fn) - end -end - -local trackers = { - emplace = trackers_init("emplace"), - add = trackers_init("added"), - remove = trackers_init("removed") -} - -return trackers diff --git a/demo/src/ReplicatedStorage/types.luau b/demo/src/ReplicatedStorage/types.luau new file mode 100644 index 0000000..678fb71 --- /dev/null +++ b/demo/src/ReplicatedStorage/types.luau @@ -0,0 +1,8 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local observers_add = require("../ReplicatedStorage/observers_add") + +export type World = typeof(observers_add(jecs.world())) +export type Entity = jecs.Entity +export type Id = jecs.Id + +return {} diff --git a/demo/src/ServerScriptService/main.server.luau b/demo/src/ServerScriptService/main.server.luau index 6d6a5b1..aa9f0f5 100644 --- a/demo/src/ServerScriptService/main.server.luau +++ b/demo/src/ServerScriptService/main.server.luau @@ -1,4 +1,19 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") -local start = require(ReplicatedStorage.start) +local ServerScriptService = game:GetService("ServerScriptService") +local jecs = require(ReplicatedStorage.ecs) +local schedule = require(ReplicatedStorage.schedule) +local observers_add = require(ReplicatedStorage.observers_add) -start(script.Parent:WaitForChild("systems"):GetChildren()) +local SYSTEM = schedule.SYSTEM +local RUN = schedule.RUN + +require(ReplicatedStorage.components) +local world = observers_add(jecs.world()) + +local systems = ServerScriptService.systems + +SYSTEM(world, systems.replication) +SYSTEM(world, systems.players_added) +SYSTEM(world, systems.poison_hurts) +SYSTEM(world, systems.life_is_painful) +RUN(world, 0) diff --git a/demo/src/ServerScriptService/systems/life_is_painful.luau b/demo/src/ServerScriptService/systems/life_is_painful.luau new file mode 100644 index 0000000..333e64b --- /dev/null +++ b/demo/src/ServerScriptService/systems/life_is_painful.luau @@ -0,0 +1,12 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local ct = require(ReplicatedStorage.components) +local types = require(ReplicatedStorage.types) + +return function(world: types.World, dt: number) + for e in world:query(ct.Player):without(ct.Health) do + world:set(e, ct.Health, 100) + end + for e in world:query(ct.Player, ct.Health):without(ct.Poison) do + world:set(e, ct.Poison, 10) + end +end diff --git a/demo/src/ServerScriptService/systems/mobs.luau b/demo/src/ServerScriptService/systems/mobs.luau deleted file mode 100644 index 07b0d9f..0000000 --- a/demo/src/ServerScriptService/systems/mobs.luau +++ /dev/null @@ -1,88 +0,0 @@ ---!optimize 2 ---!native ---!strict - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local blink = require(game:GetService("ServerScriptService").net) -local jecs = require(ReplicatedStorage.ecs) -local __ = jecs.Wildcard -local std = ReplicatedStorage.std -local ref = require(std.ref) -local interval = require(std.interval) - -local world = require(std.world) -local cts = require(std.components) - -local Mob = cts.Mob -local Transform = cts.Transform -local Velocity = cts.Velocity -local Player = cts.Player -local Character = cts.Character - -local characters = world - :query(Character) - :with(Player) - :cached() - - -local moving_mobs = world - :query(Transform, Velocity) - :with(Mob) - :cached() - - -local function mobsMove(dt: number) - local targets = {} - - for _, character in characters do - table.insert(targets, (character.PrimaryPart :: Part).Position) - end - - for mob, transform, v in moving_mobs do - local cf = transform.new - local p = cf.Position - - local target - local closest - - for _, pos in targets do - local distance = (p - pos).Magnitude - if not target or distance < closest then - target = pos - closest = distance - end - end - - if not target then - continue - end - - local moving = CFrame.new(p + (target - p).Unit * dt * v) - transform.new = moving - blink.UpdateTransform.FireAll(mob, moving) - end -end - -local throttle = interval(5) - -local function spawnMobs() - if throttle() then - local p = Vector3.new(0, 5, 0) - local cf = CFrame.new(p) - local v = 5 - - local e = world:entity() - world:set(e, Velocity, v) - world:set(e, Transform, { new = cf }) - world:add(e, Mob) - - blink.SpawnMob.FireAll(e, cf, v) - end -end - -local scheduler = require(std.scheduler) - -scheduler.SYSTEM(spawnMobs) -scheduler.SYSTEM(mobsMove) - -return 0 \ No newline at end of file diff --git a/demo/src/ServerScriptService/systems/players.luau b/demo/src/ServerScriptService/systems/players.luau deleted file mode 100644 index 27dcc8b..0000000 --- a/demo/src/ServerScriptService/systems/players.luau +++ /dev/null @@ -1,40 +0,0 @@ -local Players = game:GetService("Players") -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -local std = ReplicatedStorage.std -local ref = require(std.ref) -local collect = require(std.collect) - -local cts = require(std.components) -local world = require(std.world) -local Player = cts.Player -local Character = cts.Character - -local conn = {} - -local function playersAdded(player: Player) - local e = ref(player.UserId) - world:set(e, Player, player) - local characterAdd = player.CharacterAdded - conn[e] = characterAdd:Connect(function(rig) - while rig.Parent ~= workspace do - task.wait() - end - world:set(e, Character, rig) - end) -end - -local function playersRemoved(player: Player) - local e = ref(player.UserId) - world:clear(e) - local connection = conn[e] - connection:Disconnect() - conn[e] = nil -end - -local scheduler = require(std.scheduler) -local phases = require(std.phases) -scheduler.SYSTEM(playersAdded, phases.PlayerAdded) -scheduler.SYSTEM(playersRemoved, phases.PlayerRemoved) - -return 0 \ No newline at end of file diff --git a/demo/src/ServerScriptService/systems/players_added.luau b/demo/src/ServerScriptService/systems/players_added.luau new file mode 100644 index 0000000..703c98d --- /dev/null +++ b/demo/src/ServerScriptService/systems/players_added.luau @@ -0,0 +1,20 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local collect = require(ReplicatedStorage.collect) +local types = require(ReplicatedStorage.types) +local ct = require(ReplicatedStorage.components) +local Players = game:GetService("Players") + +local player_added = collect(Players.PlayerAdded) +return function(world: types.World, dt: number) + for player in player_added do + local entity = world:entity() + world:set(entity, ct.Player, player) + end + + for entity, player in world:query(ct.Player):without(ct.Renderable) do + local character = player.Character + if character and character.Parent ~= nil then + world:set(entity, ct.Renderable, character) + end + end +end diff --git a/demo/src/ServerScriptService/systems/poison_hurts.luau b/demo/src/ServerScriptService/systems/poison_hurts.luau new file mode 100644 index 0000000..a7e1f3e --- /dev/null +++ b/demo/src/ServerScriptService/systems/poison_hurts.luau @@ -0,0 +1,12 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local ct = require(ReplicatedStorage.components) +return function(world, dt) + for e, poison, health in world:query(ct.Poison, ct.Health) do + local health_after_tick = health - poison * dt * 0.05 + if health_after_tick < 0 then + world:remove(e, ct.Health) + continue + end + world:set(e, ct.Health, health_after_tick) + end +end diff --git a/demo/src/ServerScriptService/systems/replication.luau b/demo/src/ServerScriptService/systems/replication.luau new file mode 100644 index 0000000..fa9f27b --- /dev/null +++ b/demo/src/ServerScriptService/systems/replication.luau @@ -0,0 +1,122 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local types = require("../../ReplicatedStorage/types") +local ct = require("../../ReplicatedStorage/components") +local jecs = require(ReplicatedStorage.ecs) +local remotes = require("../../ReplicatedStorage/remotes") + +return function(world: types.World) + local storages = {} + + for component in world:query(ct.Networked) do + local is_tag = jecs.is_tag(world, component) + local storage = {} :: { [types.Entity]: any } + storages[component] = storage + + if is_tag then + world:added(component, function(entity) + storage[entity] = true + end) + else + world:added(component, function(entity, _, value) + storage[entity] = value + end) + world:changed(component, function(entity, _, value) + storage[entity] = value + end) + end + + world:removed(component, function(entity) + storage[entity] = "jecs.Remove" + end) + end + + for relation in world:query(ct.NetworkedPair) 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 + storage[entity] = value + end + end) + + world:changed(relation, function(entity, 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 :: (types.Entity, types.Id, T) -> ()) + + world:removed(relation, function(entity, id) + local storage = storages[id] + if not storage then + storage = {} + storages[id] = storage + end + + storage[entity] = "jecs.Remove" + end) + end + + return function() + local snapshot = {} :: { + [string]: { + set: { types.Entity }?, + values: { any }?, + removed: { types.Entity }? + } + } + + local set_ids = {} :: { types.Entity } + local removed_ids = {} :: { types.Entity } + + for component, storage in storages do + local set_values = {} + local set_n = 0 + local removed_n = 0 + for e, v in storage do + if v ~= "jecs.Remove" then + set_n += 1 + set_ids[set_n] = e + set_values[set_n] = v or true + elseif world:contains(e) then + removed_n += 1 + removed_ids[removed_n] = e + end + end + + table.clear(storage) + + local dirty = false + + if set_n > 0 or removed_n > 0 then + dirty = true + end + + if dirty then + snapshot[tostring(component)] = { + set = if set_n > 0 then table.move(set_ids, 1, set_n, 1, {}) else nil, + values = if set_n > 0 then set_values else nil, + removed = if removed_n > 0 then table.move(removed_ids, 1, removed_n, 1, {} :: { types.Entity }) else nil + } :: any + end + end + + if next(snapshot) ~= nil then + remotes.replication:FireAllClients(snapshot) + end + end +end diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau b/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau deleted file mode 100644 index 6d6a5b1..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau +++ /dev/null @@ -1,4 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local start = require(ReplicatedStorage.start) - -start(script.Parent:WaitForChild("systems"):GetChildren()) diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau deleted file mode 100644 index 6e09edb..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau +++ /dev/null @@ -1,67 +0,0 @@ ---!optimize 2 ---!native ---!strict - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) -local __ = jecs.Wildcard -local std = ReplicatedStorage.std - -local world = require(std.world) - -local Position = world:component() :: jecs.Entity -local Previous = jecs.Rest -local pre = jecs.pair(Position, Previous) - -local added = world - :query(Position) - :without(pre) - :cached() -local changed = world - :query(Position, pre) - :cached() -local removed = world - :query(pre) - :without(Position) - :cached() - -local children = {} -for i = 1, 10 do - local e = world:entity() - world:set(e, Position, vector.create(i, i, i)) - table.insert(children, e) -end -local function flip() - return math.random() > 0.5 -end -local function system() - for i, child in children do - world:set(child, Position, vector.create(i,i,i)) - end - for e, p in added:iter() do - world:set(e, pre, p) - end - for i, child in children do - if flip() then - world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) - end - end - for e, new, old in changed:iter() do - if new ~= old then - world:set(e, pre, new) - end - end - - for i, child in children do - world:remove(child, Position) - end - - for e in removed:iter() do - world:remove(e, pre) - end -end -local scheduler = require(std.scheduler) - -scheduler.SYSTEM(system) - -return 0 diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau deleted file mode 100644 index dcb695a..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau +++ /dev/null @@ -1,90 +0,0 @@ ---!optimize 2 ---!native ---!strict - -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) -local __ = jecs.Wildcard -local std = ReplicatedStorage.std - -local world = require(std.world) - -local Position = world:component() :: jecs.Entity -local Previous = jecs.Rest -local pre = jecs.pair(Position, Previous) - -local added = world - :query(Position) - :without(pre) - :cached() -local changed = world - :query(Position, pre) - :cached() -local removed = world - :query(pre) - :without(Position) - :cached() - -local children = {} -for i = 1, 10 do - local e = world:entity() - world:set(e, Position, vector.create(i, i, i)) - table.insert(children, e) -end -local function flip() - return math.random() > 0.5 -end -local entity_index = world.entity_index -local function copy(archetypes, id) - for _, archetype in archetypes do - - local to = jecs.archetype_traverse_add(world, pre, archetype) - local columns = to.columns - local records = to.records - local old = columns[records[pre].column] - local new = columns[records[id].column] - - if to ~= archetype then - for _, entity in archetype.entities do - local r = jecs.entity_index_try_get_fast(entity_index, entity) - jecs.entity_move(entity_index, entity, r, to) - end - end - - table.move(new, 1, #new, 1, old) - - end -end -local function system2() - for i, child in children do - world:set(child, Position, vector.create(i,i,i)) - end - for e, p in added:iter() do - end - copy(added:archetypes(), Position) - for i, child in children do - if flip() then - world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) - end - end - - for e, new, old in changed:iter() do - if new ~= old then - end - end - - copy(changed:archetypes(), Position) - - for i, child in children do - world:remove(child, Position) - end - - for e in removed:iter() do - world:remove(e, pre) - end -end -local scheduler = require(std.scheduler) - -scheduler.SYSTEM(system2) - -return 0 diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau deleted file mode 100644 index a00b703..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/systems/move.luau +++ /dev/null @@ -1,46 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local blink = require(ReplicatedStorage.net) -local std = ReplicatedStorage.std -local world = require(std.world) -local ref = require(std.ref) - -local cts = require(std.components) - -local Model = cts.Model -local Transform = cts.Transform - -local moved_models = world:query(Model, Transform):cached() -local updated_models = {} -local i = 0 -local function processed(n) - i += 1 - if i > n then - i = 0 - return true - end - return false -end - -local function move(dt: number) - for entity, model in moved_models do - if updated_models[entity] then - updated_models[entity] = nil - model.PrimaryPart.CFrame = transform - end - end -end - -local function syncTransforms() - for _, id, cf in blink.UpdateTransform.Iter() do - local e = ref("server-" .. tostring(id)) - world:set(e, Transform, cf) - moved_models[e] = true - end -end - -local scheduler = require(std.scheduler) - -scheduler.SYSTEM(move) -scheduler.SYSTEM(syncTransforms) - -return 0 diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau deleted file mode 100644 index c28bbfa..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/systems/syncMobs.luau +++ /dev/null @@ -1,31 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local blink = require(ReplicatedStorage.net) -local std = ReplicatedStorage.std -local ref = require(std.ref) -local world = require(std.world) -local cts = require(std.components) - -local function syncMobs() - for _, id, cf, vel in blink.SpawnMob.Iter() do - local part = Instance.new("Part") - part.Size = Vector3.one * 5 - part.BrickColor = BrickColor.Red() - part.Anchored = true - local model = Instance.new("Model") - model.PrimaryPart = part - part.Parent = model - model.Parent = workspace - - local e = ref("server-" .. tostring(id)) - world:set(e, cts.Transform, { new = cf, old = cf }) - world:set(e, cts.Velocity, vel) - world:set(e, cts.Model, model) - world:add(e, cts.Mob) - end -end - -local scheduler = require(std.scheduler) -scheduler.SYSTEM(syncMobs) - -return 0 - diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/test.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/test.luau deleted file mode 100644 index 250bbf1..0000000 --- a/demo/src/StarterPlayer/StarterPlayerScripts/systems/test.luau +++ /dev/null @@ -1,44 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local std = ReplicatedStorage.std -local world = require(std.world) - -local A = world:component() -local B = world:component() -local C = world:component() -local D = world:component() - -local function flip() - return math.random() >= 0.15 -end - -for i = 1, 2^8 do - local e = world:entity() - if flip() then - world:set(e, A, true) - end - if flip() then - world:set(e, B, true) - end - if flip() then - world:set(e, C, true) - end - if flip() then - world:set(e, D, true) - end -end - -local function uncached() - for _ in world:query(A, B, C, D) do - end -end - -local q = world:query(A, B, C, D):cached() -local function cached() - for _ in q do - end -end - -local scheduler = require(std.scheduler) -scheduler.SYSTEM(uncached) -scheduler.SYSTEM(cached) -return 0 \ No newline at end of file diff --git a/jecs.luau b/jecs.luau index 3a1352c..3d61e96 100644 --- a/jecs.luau +++ b/jecs.luau @@ -2672,8 +2672,8 @@ return { ECS_META_RESET = ECS_META_RESET, IS_PAIR = (ECS_IS_PAIR :: any) :: (pair: Pair) -> boolean, - ECS_PAIR_FIRST = ECS_PAIR_FIRST, - ECS_PAIR_SECOND = ECS_PAIR_SECOND, + ECS_PAIR_FIRST = ECS_PAIR_FIRST :: (pair: Pair) -> Id

, + ECS_PAIR_SECOND = ECS_PAIR_SECOND :: (pair: Pair) -> Id, pair_first = (ecs_pair_first :: any) :: (world: World, pair: Pair) -> Id

, pair_second = (ecs_pair_second :: any) :: (world: World, pair: Pair) -> Id, entity_index_get_alive = entity_index_get_alive, diff --git a/rokit.toml b/rokit.toml index 0f38210..0076059 100644 --- a/rokit.toml +++ b/rokit.toml @@ -3,3 +3,4 @@ wally = "upliftgames/wally@0.3.2" rojo = "rojo-rbx/rojo@7.4.4" stylua = "johnnymorganz/stylua@2.0.1" Blink = "1Axen/Blink@0.14.1" +wally-package-types = "JohnnyMorganz/wally-package-types@1.4.2"