Add systems to demo

This commit is contained in:
Ukendio 2024-08-04 22:26:05 +02:00
parent ecd7b9f89e
commit 52060dbb06
21 changed files with 489 additions and 152 deletions

View file

@ -3,24 +3,15 @@
"tree": { "tree": {
"$className": "DataModel", "$className": "DataModel",
"ReplicatedStorage": { "ReplicatedStorage": {
"Shared": { "$className": "ReplicatedStorage",
"$path": "demo/src/shared" "$path": "demo/src/ReplicatedStorage",
},
"ecs": { "ecs": {
"$path": "src" "$path": "src"
} }
}, },
"ServerScriptService": { "ServerScriptService": {
"Server": { "$className": "ServerScriptService",
"$path": "demo/src/server" "$path": "demo/src/ServerScriptService"
}
},
"StarterPlayer": {
"StarterPlayerScripts": {
"Client": {
"$path": "demo/src/client"
}
}
}, },
"Workspace": { "Workspace": {
"$properties": { "$properties": {

View file

@ -1,17 +1,15 @@
# example # Demo
Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
## Getting Started ## Build with Rojo
To build the place from scratch, use: To build the place, run the following commands from the root of the repository:
```bash ```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 ```bash
rojo serve rojo serve
``` ```
For more help, check out [the Rojo documentation](https://rojo.space/docs).

View file

@ -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

View file

@ -1,73 +1,8 @@
--!optimize 2
--!native
local jecs = require(game:GetService("ReplicatedStorage").ecs) local jecs = require(game:GetService("ReplicatedStorage").ecs)
type World = jecs.WorldShim local world = require(script.Parent.world)
type Entity<T = any> = jecs.Entity<T> local sparse = ((world :: any) :: jecs.World).entityIndex.sparse
type World = world.World
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
type Tracker<T> = { track: (world: World, fn: (changes: { type Tracker<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T), added: () -> () -> (number, T),
@ -97,7 +32,7 @@ local function diff(a, b)
return false return false
end end
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T> local function ChangeTracker<T>(T: Entity<T>): Tracker<T>
local PreviousT = jecs.pair(jecs.Rest, T) local PreviousT = jecs.pair(jecs.Rest, T)
local add = {} local add = {}
local added local added
@ -142,7 +77,7 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
id, new, old = q.next() id, new, old = q.next()
end end
local record = world.entityIndex.sparse[id] local record = sparse[id]
local archetype = record.archetype local archetype = record.archetype
local column = archetype.records[PreviousT].column local column = archetype.records[PreviousT].column
local data = if is_trivial then new else table.clone(new) local data = if is_trivial then new else table.clone(new)
@ -197,62 +132,4 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
return tracker return tracker
end end
local bt return ChangeTracker
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
}

View file

@ -0,0 +1,67 @@
--!nonstrict
--[[
local signal = Signal.new() :: Signal.Signal<string, string>
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<T...> = { [any]: any }
local function collect<T...>(event: Signal<T...>)
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

View file

@ -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<Model>,
Player = world:component(),
Target = world:component(),
Transform = world:component(),
Velocity = world:component(),
}
return table.freeze(components)

View file

@ -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

View file

@ -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: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
set: <T>(self: Handle, id: jecs.Entity<T>, 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,4 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)
export type World = jecs.WorldShim
return jecs.World.new()

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
print("Hello world, from client!")

View file

@ -1 +0,0 @@

View file

@ -732,6 +732,9 @@ do
replace = noop :: (Query, ...any) -> (), replace = noop :: (Query, ...any) -> (),
with = Arm, with = Arm,
without = Arm, without = Arm,
archetypes = function()
return {}
end
} }
setmetatable(EmptyQuery, EmptyQuery) setmetatable(EmptyQuery, EmptyQuery)