Skip to content

Commit

Permalink
Self contained changetracker
Browse files Browse the repository at this point in the history
  • Loading branch information
Ukendio committed Jul 28, 2024
1 parent 18ead3e commit d5de1ad
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 103 deletions.
38 changes: 20 additions & 18 deletions src/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ do
setmetatable(it, it)

function world_query(world: World, ...: any): Query
-- breaking?
-- breaking
if (...) == nil then
error("Missing components")
end
Expand Down Expand Up @@ -1161,26 +1161,28 @@ World.parent = world_parent

function World.new()
local self = setmetatable({
archetypeIndex = {} :: { [string]: Archetype },
archetypes = {} :: Archetypes,
componentIndex = {} :: ComponentIndex,
entityIndex = {
dense = {} :: { [i24]: i53 },
sparse = {} :: { [i53]: Record },
} :: EntityIndex,
hooks = {
[EcsOnAdd] = {},
},
nextArchetypeId = 0,
nextComponentId = 0,
nextEntityId = 0,
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
}, World)
archetypeIndex = {} :: { [string]: Archetype },
archetypes = {} :: Archetypes,
componentIndex = {} :: ComponentIndex,
entityIndex = {
dense = {} :: { [i24]: i53 },
sparse = {} :: { [i53]: Record },
} :: EntityIndex,
hooks = {
[EcsOnAdd] = {},
},
nextArchetypeId = 0,
nextComponentId = 0,
nextEntityId = 0,
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
}, World)

self.ROOT_ARCHETYPE = archetype_of(self, {})

-- Initialize built-in components
entity_index_new_id(self.entityIndex, EcsChildOf)
for i = HI_COMPONENT_ID + 1, EcsRest do
-- Initialize built-in components
entity_index_new_id(self.entityIndex, i)
end

return self
end
Expand Down
198 changes: 113 additions & 85 deletions test/tests.luau
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,48 @@ TEST("world", function()

end)

type Tracker<T> = { track: (world: World, fn: (changes: {
added: () -> () -> (number, T),
removed: () -> () -> number,
changed: () -> () -> (number, T, T)
}) -> ()) -> ()
}

type Entity<T = any> = number & { __nominal_type_dont_use: T }

local ChangeTracker: <T>(component: Entity<T>) -> Tracker<T>

do
local world: World
local T
local PreviousT
local addedComponents
local removedComponents
local isTrivial
local added
local removed

local function changes_added()
added = true
local q = world:query(T):without(PreviousT)
return function()
local id, data = q:next()
if not id then
return nil
end

TEST("changetracker", function()
local world = jecs.World.new()
local Previous = world:component()
if isTrivial == nil then
isTrivial = typeof(data) ~= "table"
end

if not isTrivial then
data = table.clone(data)
end

addedComponents[id] = data
return id, data
end
end

local function shallowEq(a, b)
for k, v in a do
Expand All @@ -578,112 +616,102 @@ TEST("changetracker", function()
return true
end

local function ChangeTracker(world, component)
local addedComponents = {}
local removedComponents = {}
local previous = jecs.pair(Previous, component)
local isTrivial = nil

local function track(fn)
local added = false
local removed = false

local changes = {}
function changes.added()
added = true
local q = world:query(component):without(previous)
return function()
local id, data = q:next()
if not id then
return nil
end
local function changes_changed()
local q = world:query(T, PreviousT)

if isTrivial == nil then
isTrivial = typeof(data) ~= "table"
end
return function()
local id, new, old = q:next()
while true do
if not id then
return nil
end

if not isTrivial then
data = table.clone(data)
if not isTrivial then
if not shallowEq(new, old) then
break
end

addedComponents[id] = data
return id, data
elseif new ~= old then
break
end

id, new, old = q:next()
end

function changes.changed()
local q = world:query(component, previous)

return function()
local id, new, old = q:next()
while true do
if not id then
return nil
end

if not isTrivial then
if not shallowEq(new, old) then
break
end
elseif new ~= old then
break
end

id, new, old = q:next()
end
addedComponents[id] = new

addedComponents[id] = new
return id, old, new
end
end

return id, old, new
end
local function changes_removed()
removed = true

local q = world:query(PreviousT):without(T)
return function()
local id = q:next()
if id then
table.insert(removedComponents, id)
end
return id
end
end

function changes.removed()
removed = true
local changes = {
added = changes_added,
changed = changes_changed,
removed = changes_removed,
}

local q = world:query(previous):without(component)
return function()
local id = q:next()
if id then
table.insert(removedComponents, id)
end
return id
end
end
local function track(worldToTrack, fn)
world = worldToTrack
added = true
removed = true

fn(changes)
if not added then
for _ in changes.added() do
end
end
fn(changes)

if not removed then
for _ in changes.removed() do
end
if not added then
for _ in changes_added() do
end
end

for e, data in addedComponents do
world:set(e, previous, if isTrivial then data else table.clone(data))
if not removed then
for _ in changes_removed() do
end
end

for _, e in removedComponents do
world:remove(e, previous)
end
for e, data in addedComponents do
world:set(e, PreviousT, if isTrivial then data else table.clone(data))
end

return {
track = track
}
for _, e in removedComponents do
world:remove(e, PreviousT)
end
end

local tracker = { track = track }

function ChangeTracker<T>(component: Entity<T>): Tracker<T>
T = component
-- We just use jecs.Rest because people will probably not use it anyways
PreviousT = jecs.pair(jecs.Rest, T)
addedComponents = {}
removedComponents = {}

return tracker
end
end

TEST("changetracker", function()
local world = jecs.World.new()

do CASE "should allow change tracking"
local Test = world:component()
local TestTracker = ChangeTracker(world, Test)
local Test = world:component() :: Entity<{ foo: number }>
local TestTracker = ChangeTracker(Test)

local e = world:entity()
world:set(e, Test, { foo = 11 })

TestTracker.track(function(changes)
TestTracker.track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
Expand All @@ -705,7 +733,7 @@ TEST("changetracker", function()
test.foo = test.foo + 1
end

TestTracker.track(function(changes)
TestTracker.track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
Expand All @@ -727,7 +755,7 @@ TEST("changetracker", function()

world:remove(e, Test)

TestTracker.track(function(changes)
TestTracker.track(world, function(changes)
local added = 0
local changed = 0
local removed = 0
Expand Down

0 comments on commit d5de1ad

Please sign in to comment.