Skip to content

Commit

Permalink
Add scheduler to demo (#106)
Browse files Browse the repository at this point in the history
* Add example to make scheduler with phases

* Add more builtin phases
  • Loading branch information
Ukendio authored Aug 21, 2024
1 parent 3d8d71b commit f220b95
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 99 deletions.
Binary file added demo.rbxl
Binary file not shown.
7 changes: 6 additions & 1 deletion demo/src/ReplicatedStorage/std/init.luau
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
local jecs = require(game:GetService("ReplicatedStorage").ecs)

local world = require(script.world) :: jecs.World
export type World = jecs.World

local scheduler = require(script.scheduler)
export type Scheduler = scheduler.Scheduler

local std = {
ChangeTracker = require(script.changetracker),
Scheduler = require(script.scheduler),
Scheduler = scheduler.new(world),
bt = require(script.bt),
collect = require(script.collect),
components = require(script.components),
Expand Down
233 changes: 188 additions & 45 deletions demo/src/ReplicatedStorage/std/scheduler.luau
Original file line number Diff line number Diff line change
@@ -1,64 +1,207 @@
local function panic(str)
-- We don't want to interrupt the loop when we error
task.spawn(error, str)
end
--!native
--!optimize 2
local jecs = require(game:GetService("ReplicatedStorage").ecs)
local pair = jecs.pair
type World = jecs.World
type Entity<T=nil> = jecs.Entity<T>

type System = {
callback: (world: World) -> ()
}

type Systems = { System }


type Events = {
RenderStepped: Systems,
Heartbeat: Systems
}

export type Scheduler = {
components: {
Disabled: Entity,
System: Entity<System>,
Phase: Entity,
DependsOn: Entity
},

collect: {
under_event: (event: Entity) -> Systems,
all: () -> Events
},

systems: {
begin: (events: Events) -> (),
new: (callback: (dt: number) -> (), phase: Entity) -> Entity
},

phases: {
RenderStepped: Entity,
Heartbeat: Entity
},

phase: (after: Entity) -> Entity
}

local scheduler_new: (w: World) -> Scheduler

do
local world
local Disabled
local System
local DependsOn
local Phase
local Event
local Name

local RenderStepped
local Heartbeat
local PreAnimation
local PreSimulation

local function Scheduler(...)
local systems = { ... }
local systemsNames = {}
local N = #systems
local system
local dt
local function run()
debug.profilebegin(system.name)
system.callback(dt)
debug.profileend()
end
local function panic(str)
-- We don't want to interrupt the loop when we error
task.spawn(error, str)
end
local function begin(events)
local connections = {}
for event, systems in events do

if not event then continue end
local event_name = tostring(event)
connections[event] = event:Connect(function(last)
debug.profilebegin(event_name)
for _, sys in systems do
system = sys
dt = last

local didNotYield, why = xpcall(function()
for _ in run do end
end, debug.traceback)

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")}`
if didNotYield then
continue
end

if string.find(why, "thread is not yieldable") then
panic("Not allowed to yield in the systems.")
else
panic(why)
end
end
debug.profileend()
end)
end
return connections
end

local function run()
local name = systemsNames[system]
local function scheduler_collect_systems_under_phase_recursive(systems, phase)
for _, system in world:query(System):with(pair(DependsOn, phase)) do
table.insert(systems, system)
end
for dependant in world:query(Phase):with(pair(DependsOn, phase)) do
scheduler_collect_systems_under_phase_recursive(systems, dependant)
end
end

debug.profilebegin(name)
debug.setmemorycategory(name)
system(dt)
debug.profileend()
local function scheduler_collect_systems_under_event(event)
local systems = {}
scheduler_collect_systems_under_phase_recursive(systems, event)
return systems
end

local function loop(sinceLastFrame)
debug.profilebegin("loop()")
local function scheduler_collect_systems_all()
local systems = {}
for phase, event in world:query(Event):with(Phase) do
systems[event] = scheduler_collect_systems_under_event(phase)
end
return systems
end

for i = N, 1, -1 do
system = systems[i]
local function scheduler_phase_new(after)
local phase = world:entity()
world:add(phase, Phase)
local dependency = pair(DependsOn, after)
world:add(phase, dependency)
return phase
end

dt = sinceLastFrame
local function scheduler_systems_new(callback, phase)
local system = world:entity()
local name = debug.info(callback, "n")
world:set(system, System, { callback = callback, name = name })
world:add(system, pair(DependsOn, phase))
return system
end

local didNotYield, why = xpcall(function()
for _ in run do end
end, debug.traceback)
function scheduler_new(w)
world = w
Disabled = world:component()
System = world:component()
Phase = world:component()
DependsOn = world:component()
Event = world:component()

if didNotYield then
continue
end
RenderStepped = world:component()
Heartbeat = world:component()
PreSimulation = world:component()
PreAnimation = world:component()

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
local RunService = game:GetService("RunService")
if RunService:IsClient() then
world:add(RenderStepped, Phase)
world:set(RenderStepped, Event, RunService.RenderStepped)
end

debug.profileend()
debug.resetmemorycategory()
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)

return {
phase = scheduler_phase_new,

phases = {
RenderStepped = RenderStepped,
PreSimulation = PreSimulation,
Heartbeat = Heartbeat,
PreAnimation = PreAnimation
},

return loop
world = world,

components = {
DependsOn = DependsOn,
Disabled = Disabled,
Phase = Phase,
System = System,
},

collect = {
under_event = scheduler_collect_systems_under_event,
all = scheduler_collect_systems_all
},

systems = {
new = scheduler_systems_new,
begin = begin
}
}
end
end

return Scheduler

return {
new = scheduler_new
}
8 changes: 6 additions & 2 deletions demo/src/ServerScriptService/main.server.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
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)
local scheduler = std.Scheduler
for _, module in script.Parent.systems:GetChildren() do
require(module)(scheduler)
end
local events = scheduler.collect.all()
scheduler.systems.begin(events)
29 changes: 16 additions & 13 deletions demo/src/ServerScriptService/systems/mobsMove.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
--!optimize 2
--!native
--!strict

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local blink = require(game:GetService("ServerScriptService").net)
local jecs = require(ReplicatedStorage.ecs)
Expand All @@ -15,25 +18,22 @@ local Player = cts.Player
local Character = cts.Character

local function mobsMove(dt: number)
local players = world:query(Character):with(Player)
local targets = {}
for _, character in world:query(Character):with(Player):iter() do
table.insert(targets, (character.PrimaryPart :: Part).Position)
end

for mob, cf, v in world:query(Transform, Velocity):with(Mob):iter() do
local p = cf.Position

local target
local closest

for playerId, character in players:iter() do
local pos = (character.PrimaryPart::Part).Position
if true then
target = pos
break
end
if not target then
target = pos
elseif (p - pos).Magnitude
< (p - target).Magnitude
then
for _, pos in targets do
local distance = (p - pos).Magnitude
if not target or distance < closest then
target = pos
closest = distance
end
end

Expand All @@ -47,4 +47,7 @@ local function mobsMove(dt: number)
end
end

return mobsMove
return function(scheduler: std.Scheduler)
return scheduler.systems.new(mobsMove,
scheduler.phases.Heartbeat)
end
5 changes: 4 additions & 1 deletion demo/src/ServerScriptService/systems/players.luau
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ local function players()
end
end

return players
return function(scheduler: std.Scheduler)
return scheduler.systems.new(players,
scheduler.phases.Heartbeat)
end
5 changes: 4 additions & 1 deletion demo/src/ServerScriptService/systems/spawnMobs.luau
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ local function spawnMobs()
end
end

return spawnMobs
return function(scheduler: std.Scheduler)
return scheduler.systems.new(spawnMobs,
scheduler.phases.Heartbeat)
end
9 changes: 6 additions & 3 deletions demo/src/StarterPlayer/StarterPlayerScripts/main.client.luau
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local std = require(ReplicatedStorage.std)
local scheduler = std.Scheduler
for _, module in script.Parent:WaitForChild("systems"):GetChildren() do
require(module)(scheduler)
end
local events = scheduler.collect.all()

print(script.Parent:WaitForChild("systems"):GetChildren())
local loop = std.Scheduler(unpack(script.Parent:WaitForChild("systems"):GetChildren()))
game:GetService("RunService").Heartbeat:Connect(loop)
scheduler.systems.begin(events)
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ local function move(dt: number)
end
end

return move
return function(scheduler: std.Scheduler)
return scheduler.systems.new(move,
scheduler.phases.RenderStepped)
end
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ local function syncMobs()
:set(cts.Model, model)
:add(cts.Mob)
end

end

return syncMobs
return function(scheduler: std.Scheduler)
return scheduler.systems.new(syncMobs,
scheduler.phases.RenderStepped)
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ local function syncTransforms()
end
end

return syncTransforms
return function(scheduler: std.Scheduler)
return scheduler.systems.new(syncTransforms,
scheduler.phases.RenderStepped)
end
Loading

0 comments on commit f220b95

Please sign in to comment.