Skip to content

Commit

Permalink
Add the ability to buffer commands (#88)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcus <[email protected]>
  • Loading branch information
memorycode and Ukendio authored Aug 26, 2024
1 parent 2604284 commit c3a88a6
Show file tree
Hide file tree
Showing 8 changed files with 940 additions and 867 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ The format is based on [Keep a Changelog][kac], and this project adheres to

## [Unreleased]

### Added

- Implemented a deferred command mode for the registry.
- The Loop turns deferring on for all worlds given to it.
- The command buffer is flushed between systems.
- Iterator invalidation is now only prevented in deferred mode.

### Deprecated

- Deprecated the return type of `World:remove()` because it can now be inaccurate.
- Deprecated `World:optimizeQueries()` because it no longer does anything.

## [0.8.4] - 2024-08-15

### Added
Expand Down
48 changes: 36 additions & 12 deletions lib/Loop.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
local RunService = game:GetService("RunService")

local World = require(script.Parent.World)
local rollingAverage = require(script.Parent.rollingAverage)
local topoRuntime = require(script.Parent.topoRuntime)

Expand Down Expand Up @@ -365,16 +367,25 @@ function Loop:begin(events)

generation = not generation

local dirtyWorlds: { [any]: true } = {}
local profiling = self.profiling

local worlds: { World.World } = {}
for _, stateArgument in self._state do
if typeof(stateArgument) == "table" and getmetatable(stateArgument) == World then
table.insert(worlds, stateArgument)
end
end

for _, world in worlds do
world:startDeferring()
end

for _, system in ipairs(self._orderedSystemsByEvent[eventName]) do
topoRuntime.start({
system = self._systemState[system],
frame = {
generation = generation,
deltaTime = deltaTime,
dirtyWorlds = dirtyWorlds,
logs = self._systemLogs[system],
},
currentSystem = system,
Expand All @@ -383,13 +394,26 @@ function Loop:begin(events)
if profiling then
profiling[system] = nil
end

return
end

local fn = systemFn(system)
debug.profilebegin("system: " .. systemName(system))

local thread = coroutine.create(fn)
local name = systemName(system)

debug.profilebegin("system: " .. name)
local commitFailed = false
local thread = coroutine.create(function(...)
fn(...)

for _, world in worlds do
local ok, err = pcall(world.commitCommands, world)
if not ok then
commitFailed = true
error(err)
end
end
end)

local startTime = os.clock()
local success, errorValue = coroutine.resume(thread, unpack(self._state, 1, self._stateLength))
Expand Down Expand Up @@ -431,20 +455,20 @@ function Loop:begin(events)
)
end

for world in dirtyWorlds do
world:optimizeQueries()
end
table.clear(dirtyWorlds)

if not success then
if os.clock() - recentErrorLastTime > 10 then
recentErrorLastTime = os.clock()
recentErrors = {}
end

local errorString = systemName(system)
local errorString = name
.. ": "
.. tostring(errorValue)
.. (
if commitFailed
-- Strip irrelevant line numbers that point to Loop / World
then string.gsub(errorValue, "%[.+%]:%d+: ", "Failed to apply commands: ")
else errorValue
)
.. "\n"
.. debug.traceback(thread)

Expand Down
24 changes: 0 additions & 24 deletions lib/Loop.spec.luau
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
local Loop = require(script.Parent.Loop)
local useHookState = require(script.Parent.topoRuntime).useHookState
local World = require(script.Parent.World)
local component = require(script.Parent).component
local BindableEvent = require(script.Parent.mock.BindableEvent)

local bindable = BindableEvent.new()
Expand Down Expand Up @@ -609,27 +607,5 @@ return function()
expect(called[2]).to.equal(2)
expect(called[3]).to.equal(3)
end)

it("should optimize queries of worlds used inside it", function()
local world = World.new()
local loop = Loop.new(world)

local A = component()

world:spawn(A())

loop:scheduleSystem(function(world)
world:query(A)
end)

local bindable = BindableEvent.new()
loop:begin({
default = bindable.Event,
})

bindable:Fire()

expect(#world._storages).to.equal(1)
end)
end)
end
Loading

0 comments on commit c3a88a6

Please sign in to comment.