From 52060dbb0666ab8cf57830b8b593142a0cb47b23 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sun, 4 Aug 2024 22:26:05 +0200 Subject: [PATCH] Add systems to demo --- demo.project.json | 17 +-- demo/README.md | 14 +- demo/src/ReplicatedStorage/std/bt.luau | 40 ++++++ .../std/changetracker.luau} | 135 +----------------- demo/src/ReplicatedStorage/std/collect.luau | 67 +++++++++ .../src/ReplicatedStorage/std/components.luau | 14 ++ demo/src/ReplicatedStorage/std/ctx.luau | 11 ++ demo/src/ReplicatedStorage/std/handle.luau | 52 +++++++ demo/src/ReplicatedStorage/std/init.luau | 20 +++ demo/src/ReplicatedStorage/std/interval.luau | 19 +++ demo/src/ReplicatedStorage/std/ref.luau | 18 +++ demo/src/ReplicatedStorage/std/scheduler.luau | 64 +++++++++ demo/src/ReplicatedStorage/std/world.luau | 4 + demo/src/ServerScriptService/main.server.luau | 4 + .../ServerScriptService/systems/mobsMove.luau | 54 +++++++ .../src/ServerScriptService/systems/move.luau | 29 ++++ .../ServerScriptService/systems/players.luau | 37 +++++ .../systems/spawnMobs.luau | 37 +++++ demo/src/client/init.client.luau | 1 - demo/src/server/init.server.luau | 1 - src/init.luau | 3 + 21 files changed, 489 insertions(+), 152 deletions(-) create mode 100644 demo/src/ReplicatedStorage/std/bt.luau rename demo/src/{shared/common.luau => ReplicatedStorage/std/changetracker.luau} (50%) create mode 100644 demo/src/ReplicatedStorage/std/collect.luau create mode 100644 demo/src/ReplicatedStorage/std/components.luau create mode 100644 demo/src/ReplicatedStorage/std/ctx.luau create mode 100644 demo/src/ReplicatedStorage/std/handle.luau create mode 100644 demo/src/ReplicatedStorage/std/init.luau create mode 100644 demo/src/ReplicatedStorage/std/interval.luau create mode 100644 demo/src/ReplicatedStorage/std/ref.luau create mode 100644 demo/src/ReplicatedStorage/std/scheduler.luau create mode 100644 demo/src/ReplicatedStorage/std/world.luau create mode 100644 demo/src/ServerScriptService/main.server.luau create mode 100644 demo/src/ServerScriptService/systems/mobsMove.luau create mode 100644 demo/src/ServerScriptService/systems/move.luau create mode 100644 demo/src/ServerScriptService/systems/players.luau create mode 100644 demo/src/ServerScriptService/systems/spawnMobs.luau delete mode 100644 demo/src/client/init.client.luau delete mode 100644 demo/src/server/init.server.luau diff --git a/demo.project.json b/demo.project.json index 54314ef..e02ae7c 100644 --- a/demo.project.json +++ b/demo.project.json @@ -3,24 +3,15 @@ "tree": { "$className": "DataModel", "ReplicatedStorage": { - "Shared": { - "$path": "demo/src/shared" - }, + "$className": "ReplicatedStorage", + "$path": "demo/src/ReplicatedStorage", "ecs": { "$path": "src" } }, "ServerScriptService": { - "Server": { - "$path": "demo/src/server" - } - }, - "StarterPlayer": { - "StarterPlayerScripts": { - "Client": { - "$path": "demo/src/client" - } - } + "$className": "ServerScriptService", + "$path": "demo/src/ServerScriptService" }, "Workspace": { "$properties": { diff --git a/demo/README.md b/demo/README.md index 5223a3e..241cc25 100644 --- a/demo/README.md +++ b/demo/README.md @@ -1,17 +1,15 @@ -# example -Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1. +# Demo -## Getting Started -To build the place from scratch, use: +## Build with Rojo +To build the place, run the following commands from the root of the repository: ```bash -rojo build -o "example.rbxlx" +cd demo +rojo build -o "demo.rbxl" ``` -Next, open `example.rbxlx` in Roblox Studio and start the Rojo server: +Next, open `demo.rbxl` in Roblox Studio and start the Rojo server: ```bash rojo serve ``` - -For more help, check out [the Rojo documentation](https://rojo.space/docs). \ No newline at end of file diff --git a/demo/src/ReplicatedStorage/std/bt.luau b/demo/src/ReplicatedStorage/std/bt.luau new file mode 100644 index 0000000..9cb6d1c --- /dev/null +++ b/demo/src/ReplicatedStorage/std/bt.luau @@ -0,0 +1,40 @@ +--!optimize 2 +--!native + +-- original author @centau + +local SUCCESS = 0 +local FAILURE = 1 +local RUNNING = 2 + +local function SEQUENCE(nodes) + return function(...) + for _, node in nodes do + local status = node(...) + if status == FAILURE or 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 == SUCCESS or status == RUNNING then + return status + end + end + return FAILURE + end +end + +local bt = { + SEQUENCE = SEQUENCE, + FALLBACK = FALLBACK, + RUNNING = RUNNING +} + +return bt diff --git a/demo/src/shared/common.luau b/demo/src/ReplicatedStorage/std/changetracker.luau similarity index 50% rename from demo/src/shared/common.luau rename to demo/src/ReplicatedStorage/std/changetracker.luau index 38b1ff3..bd5051a 100644 --- a/demo/src/shared/common.luau +++ b/demo/src/ReplicatedStorage/std/changetracker.luau @@ -1,73 +1,8 @@ ---!optimize 2 ---!native - local jecs = require(game:GetService("ReplicatedStorage").ecs) -type World = jecs.WorldShim -type Entity = jecs.Entity - -local function panic(str) - -- We don't want to interrupt the loop when we error - task.spawn(error, str) -end - -local function Scheduler(world, ...) - local systems = { ... } - local systemsNames = {} - local N = #systems - local system - local dt - - for i, module in systems do - local sys = require(module) - systems[i] = sys - local file, line = debug.info(2, "sl") - systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}` - end - - local function run() - local name = systemsNames[system] - - debug.profilebegin(name) - debug.setmemorycategory(name) - system(world, dt) - debug.profileend() - end - - local function loop(sinceLastFrame) - debug.profilebegin("loop()") - - for i = N, 1, -1 do - system = systems[i] - - dt = sinceLastFrame - - local didNotYield, why = xpcall(function() - for _ in run do end - end, debug.traceback) - - if didNotYield then - continue - end - - if string.find(why, "thread is not yieldable") then - N -= 1 - local name = table.remove(systems, i) - panic("Not allowed to yield in the systems." - .. "\n" - .. `System: {name} has been ejected` - ) - else - panic(why) - end - end - - debug.profileend() - debug.resetmemorycategory() - end - - return loop -end +local world = require(script.Parent.world) +local sparse = ((world :: any) :: jecs.World).entityIndex.sparse +type World = world.World type Tracker = { track: (world: World, fn: (changes: { added: () -> () -> (number, T), @@ -97,7 +32,7 @@ local function diff(a, b) return false end -local function ChangeTracker(world, T: Entity): Tracker +local function ChangeTracker(T: Entity): Tracker local PreviousT = jecs.pair(jecs.Rest, T) local add = {} local added @@ -142,7 +77,7 @@ local function ChangeTracker(world, T: Entity): Tracker id, new, old = q.next() end - local record = world.entityIndex.sparse[id] + local record = sparse[id] local archetype = record.archetype local column = archetype.records[PreviousT].column local data = if is_trivial then new else table.clone(new) @@ -197,62 +132,4 @@ local function ChangeTracker(world, T: Entity): Tracker return tracker end -local bt -do - local SUCCESS = 0 - local FAILURE = 1 - local RUNNING = 2 - - local function SEQUENCE(nodes) - return function(...) - for _, node in nodes do - local status = node(...) - if status == FAILURE or 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 == SUCCESS or status == RUNNING then - return status - end - end - return FAILURE - end - end - bt = { - SEQUENCE = SEQUENCE, - FALLBACK = FALLBACK, - RUNNING = RUNNING - } -end - -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 { - Scheduler = Scheduler, - ChangeTracker = ChangeTracker, - interval = interval, - BehaviorTree = bt -} +return ChangeTracker diff --git a/demo/src/ReplicatedStorage/std/collect.luau b/demo/src/ReplicatedStorage/std/collect.luau new file mode 100644 index 0000000..edd9870 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/collect.luau @@ -0,0 +1,67 @@ +--!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 new file mode 100644 index 0000000..695b9ff --- /dev/null +++ b/demo/src/ReplicatedStorage/std/components.luau @@ -0,0 +1,14 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.Parent.world) + +local components = { + Character = world:component(), + Mob = world:component(), + Model = world:component() :: jecs.Entity, + Player = world:component(), + Target = world:component(), + Transform = world:component(), + Velocity = world:component(), +} + +return table.freeze(components) diff --git a/demo/src/ReplicatedStorage/std/ctx.luau b/demo/src/ReplicatedStorage/std/ctx.luau new file mode 100644 index 0000000..9a4cbf8 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/ctx.luau @@ -0,0 +1,11 @@ +local world = require(script.Parent.world) +local handle = require(script.Parent.handle) + +local singleton = world:entity() + +local function ctx() + -- Cannot cache handles because they will get invalidated + return handle(singleton) +end + +return ctx diff --git a/demo/src/ReplicatedStorage/std/handle.luau b/demo/src/ReplicatedStorage/std/handle.luau new file mode 100644 index 0000000..ec108d8 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/handle.luau @@ -0,0 +1,52 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.Parent.world) + +type Handle = { + has: (self: Handle, id: jecs.Entity) -> boolean, + get: (self: Handle, id: jecs.Entity) -> T?, + add: (self: Handle, id: jecs.Entity) -> Handle, + set: (self: Handle, id: jecs.Entity, value: T) -> Handle +} + +local handle: (e: jecs.Entity) -> Handle + +do + local e + local function has(_, id) + return world:has(e, id) + end + local function get(_, id) + return world:get(e, id) + end + local function set(self, id, value) + world:set(e, id, value) + return self + end + local function add(self, id) + world:add(e, id) + return self + end + local function clear(self) + world:clear(e) + return self + end + local function id() + return e + end + + local entity = { + has = has, + get = get, + set = set, + add = add, + clear = clear, + id = id, + } + + function handle(id) + e = id + return entity + end +end + +return handle diff --git a/demo/src/ReplicatedStorage/std/init.luau b/demo/src/ReplicatedStorage/std/init.luau new file mode 100644 index 0000000..23f6cc3 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/init.luau @@ -0,0 +1,20 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +local world = require(script.world) +export type World = world.World + +local std = { + ChangeTracker = require(script.changetracker), + Scheduler = require(script.scheduler), + bt = require(script.bt), + collect = require(script.collect), + components = require(script.components), + ctx = require(script.ctx), + handle = require(script.handle), + interval = require(script.interval), + ref = require(script.ref), + world = world, + pair = jecs.pair, + __ = jecs.w, +} + +return std diff --git a/demo/src/ReplicatedStorage/std/interval.luau b/demo/src/ReplicatedStorage/std/interval.luau new file mode 100644 index 0000000..fadae90 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/interval.luau @@ -0,0 +1,19 @@ +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 diff --git a/demo/src/ReplicatedStorage/std/ref.luau b/demo/src/ReplicatedStorage/std/ref.luau new file mode 100644 index 0000000..25b61b3 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/ref.luau @@ -0,0 +1,18 @@ +local world = require(script.Parent.world) +local handle = require(script.Parent.handle) +local refs = {} + +local function ref(key) + if not key then + return handle(world:entity()) + 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 handle(e) +end + +return ref diff --git a/demo/src/ReplicatedStorage/std/scheduler.luau b/demo/src/ReplicatedStorage/std/scheduler.luau new file mode 100644 index 0000000..c0aac28 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/scheduler.luau @@ -0,0 +1,64 @@ +local function panic(str) + -- We don't want to interrupt the loop when we error + task.spawn(error, str) +end + +local function Scheduler(...) + local systems = { ... } + local systemsNames = {} + local N = #systems + local system + local dt + + for i, module in systems do + local sys = require(module) + systems[i] = sys + local file, line = debug.info(2, "sl") + systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}` + end + + local function run() + local name = systemsNames[system] + + debug.profilebegin(name) + debug.setmemorycategory(name) + system(dt) + debug.profileend() + end + + local function loop(sinceLastFrame) + debug.profilebegin("loop()") + + for i = N, 1, -1 do + system = systems[i] + + dt = sinceLastFrame + + local didNotYield, why = xpcall(function() + for _ in run do end + end, debug.traceback) + + if didNotYield then + continue + end + + if string.find(why, "thread is not yieldable") then + N -= 1 + local name = table.remove(systems, i) + panic("Not allowed to yield in the systems." + .. "\n" + .. `System: {name} has been ejected` + ) + else + panic(why) + end + end + + debug.profileend() + debug.resetmemorycategory() + end + + return loop +end + +return Scheduler diff --git a/demo/src/ReplicatedStorage/std/world.luau b/demo/src/ReplicatedStorage/std/world.luau new file mode 100644 index 0000000..d7b6da6 --- /dev/null +++ b/demo/src/ReplicatedStorage/std/world.luau @@ -0,0 +1,4 @@ +local jecs = require(game:GetService("ReplicatedStorage").ecs) +export type World = jecs.WorldShim + +return jecs.World.new() diff --git a/demo/src/ServerScriptService/main.server.luau b/demo/src/ServerScriptService/main.server.luau new file mode 100644 index 0000000..6a521b2 --- /dev/null +++ b/demo/src/ServerScriptService/main.server.luau @@ -0,0 +1,4 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local std = require(ReplicatedStorage.std) +local loop = std.Scheduler(unpack(script.Parent.systems:GetChildren())) +game:GetService("RunService").Heartbeat:Connect(loop) diff --git a/demo/src/ServerScriptService/systems/mobsMove.luau b/demo/src/ServerScriptService/systems/mobsMove.luau new file mode 100644 index 0000000..0894ac5 --- /dev/null +++ b/demo/src/ServerScriptService/systems/mobsMove.luau @@ -0,0 +1,54 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local jecs = require(ReplicatedStorage.ecs) +local pair = jecs.pair +local __ = jecs.Wildcard + +local std = require(ReplicatedStorage.std) +local world = std.world + +local cts = std.components + +local Mob = cts.Mob +local Model = cts.Model +local Transform = cts.Transform +local Velocity = cts.Velocity +local Target = cts.Target +local Player = cts.Player +local Character = cts.Character + +local function mobsMove(dt: number) + local players = world:query(Character):with(Player) + + for mob, cf, v in world:query(Transform, Velocity) + :with(Mob, Model) + do + local p = cf.Position + + local target + for playerId, character in players do + local pos = character.PrimaryPart.Position + if true then + target = pos + break + end + if not target then + target = pos + elseif (p - pos).Magnitude < (p - target) then + target = pos + end + end + + if not target then + continue + end + + local moving = CFrame.new(p + (target - p).Unit * dt * v) + --local record = world.entityIndex.sparse[mob] + --local archetype = record.archetype + --archetype.columns[archetype.records[Transform].column][record.row] = moving + world:set(mob, Transform, moving) + end +end + +return mobsMove diff --git a/demo/src/ServerScriptService/systems/move.luau b/demo/src/ServerScriptService/systems/move.luau new file mode 100644 index 0000000..3e5ecc6 --- /dev/null +++ b/demo/src/ServerScriptService/systems/move.luau @@ -0,0 +1,29 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local std = require(ReplicatedStorage.std) +local world = std.world + +local cts = std.components + +local Model = cts.Model +local Transform = cts.Transform + +local function move(dt: number) + -- for i, archetype in world:query(Transform, Model):archetypes() do + -- local columns = archetype.columns + -- local records = archetype.records + -- local M = columns[records[Model].column] + -- local CF = columns[records[Transform].column] + + -- for row, entity in archetype.entities do + -- local model, cf = M[row], CF[row] + + -- model.PrimaryPart.CFrame = cf + -- end + -- end + for _, cf, model in world:query(Transform, Model) do + model.PrimaryPart.CFrame = cf + end +end + +return move diff --git a/demo/src/ServerScriptService/systems/players.luau b/demo/src/ServerScriptService/systems/players.luau new file mode 100644 index 0000000..25e30a7 --- /dev/null +++ b/demo/src/ServerScriptService/systems/players.luau @@ -0,0 +1,37 @@ +local Players = game:GetService("Players") +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local std = require(ReplicatedStorage.std) +local ref = std.ref +local collect = std.collect + +local cts = std.components +local Player = cts.Player +local Character = cts.Character + +local playersAdded = collect(Players.PlayerAdded) +local playersRemoved = collect(Players.PlayerRemoving) + +local connections = {} + +local function players() + for _, player in playersAdded do + local e = ref(player.UserId):set(Player, player) + + connections[e.id()] = player.CharacterAdded:Connect( + function(character) + while character.Parent ~= workspace do + task.wait() + end + e:set(Character, character) + end) + end + + for _, player in playersRemoved do + local id = ref(player.UserId):clear().id() + connections[id]:Disconnect() + connections[id] = nil + end +end + +return players diff --git a/demo/src/ServerScriptService/systems/spawnMobs.luau b/demo/src/ServerScriptService/systems/spawnMobs.luau new file mode 100644 index 0000000..73e201e --- /dev/null +++ b/demo/src/ServerScriptService/systems/spawnMobs.luau @@ -0,0 +1,37 @@ +local std = require(game:GetService("ReplicatedStorage").std) + +local ref = std.ref +local interval = std.interval +local cts = std.components + +local Mob = cts.Mob +local Model = cts.Model +local Transform = cts.Transform +local Velocity = cts.Velocity + +local throttle = interval(5) + +local function spawnMobs(world: std.World) + if throttle() then + local p = Vector3.new(0, 5, 0) + local cf = CFrame.new(p) + local v = 5 + local part = Instance.new("Part") + part.Anchored = true + part.CanCollide = false + part.BrickColor = BrickColor.Blue() + part.Size = Vector3.one * 5 + local model = Instance.new("Model") + part.Parent = model + model.PrimaryPart = part + model.Parent = workspace + + ref() + :set(Velocity, v) + :set(Transform, cf) + :set(Model, model) + :add(Mob) + end +end + +return spawnMobs diff --git a/demo/src/client/init.client.luau b/demo/src/client/init.client.luau deleted file mode 100644 index 505f71c..0000000 --- a/demo/src/client/init.client.luau +++ /dev/null @@ -1 +0,0 @@ -print("Hello world, from client!") \ No newline at end of file diff --git a/demo/src/server/init.server.luau b/demo/src/server/init.server.luau deleted file mode 100644 index 8b13789..0000000 --- a/demo/src/server/init.server.luau +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/init.luau b/src/init.luau index c5f63e3..24ef69d 100644 --- a/src/init.luau +++ b/src/init.luau @@ -732,6 +732,9 @@ do replace = noop :: (Query, ...any) -> (), with = Arm, without = Arm, + archetypes = function() + return {} + end } setmetatable(EmptyQuery, EmptyQuery)