Skip to content

Commit d5de1ad

Browse files
committed
Self contained changetracker
1 parent 18ead3e commit d5de1ad

File tree

2 files changed

+133
-103
lines changed

2 files changed

+133
-103
lines changed

src/init.luau

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ do
935935
setmetatable(it, it)
936936

937937
function world_query(world: World, ...: any): Query
938-
-- breaking?
938+
-- breaking
939939
if (...) == nil then
940940
error("Missing components")
941941
end
@@ -1161,26 +1161,28 @@ World.parent = world_parent
11611161

11621162
function World.new()
11631163
local self = setmetatable({
1164-
archetypeIndex = {} :: { [string]: Archetype },
1165-
archetypes = {} :: Archetypes,
1166-
componentIndex = {} :: ComponentIndex,
1167-
entityIndex = {
1168-
dense = {} :: { [i24]: i53 },
1169-
sparse = {} :: { [i53]: Record },
1170-
} :: EntityIndex,
1171-
hooks = {
1172-
[EcsOnAdd] = {},
1173-
},
1174-
nextArchetypeId = 0,
1175-
nextComponentId = 0,
1176-
nextEntityId = 0,
1177-
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
1178-
}, World)
1164+
archetypeIndex = {} :: { [string]: Archetype },
1165+
archetypes = {} :: Archetypes,
1166+
componentIndex = {} :: ComponentIndex,
1167+
entityIndex = {
1168+
dense = {} :: { [i24]: i53 },
1169+
sparse = {} :: { [i53]: Record },
1170+
} :: EntityIndex,
1171+
hooks = {
1172+
[EcsOnAdd] = {},
1173+
},
1174+
nextArchetypeId = 0,
1175+
nextComponentId = 0,
1176+
nextEntityId = 0,
1177+
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
1178+
}, World)
11791179

11801180
self.ROOT_ARCHETYPE = archetype_of(self, {})
11811181

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

11851187
return self
11861188
end

test/tests.luau

Lines changed: 113 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -564,10 +564,48 @@ TEST("world", function()
564564

565565
end)
566566

567+
type Tracker<T> = { track: (world: World, fn: (changes: {
568+
added: () -> () -> (number, T),
569+
removed: () -> () -> number,
570+
changed: () -> () -> (number, T, T)
571+
}) -> ()) -> ()
572+
}
573+
574+
type Entity<T = any> = number & { __nominal_type_dont_use: T }
575+
576+
local ChangeTracker: <T>(component: Entity<T>) -> Tracker<T>
577+
578+
do
579+
local world: World
580+
local T
581+
local PreviousT
582+
local addedComponents
583+
local removedComponents
584+
local isTrivial
585+
local added
586+
local removed
587+
588+
local function changes_added()
589+
added = true
590+
local q = world:query(T):without(PreviousT)
591+
return function()
592+
local id, data = q:next()
593+
if not id then
594+
return nil
595+
end
567596

568-
TEST("changetracker", function()
569-
local world = jecs.World.new()
570-
local Previous = world:component()
597+
if isTrivial == nil then
598+
isTrivial = typeof(data) ~= "table"
599+
end
600+
601+
if not isTrivial then
602+
data = table.clone(data)
603+
end
604+
605+
addedComponents[id] = data
606+
return id, data
607+
end
608+
end
571609

572610
local function shallowEq(a, b)
573611
for k, v in a do
@@ -578,112 +616,102 @@ TEST("changetracker", function()
578616
return true
579617
end
580618

581-
local function ChangeTracker(world, component)
582-
local addedComponents = {}
583-
local removedComponents = {}
584-
local previous = jecs.pair(Previous, component)
585-
local isTrivial = nil
586-
587-
local function track(fn)
588-
local added = false
589-
local removed = false
590-
591-
local changes = {}
592-
function changes.added()
593-
added = true
594-
local q = world:query(component):without(previous)
595-
return function()
596-
local id, data = q:next()
597-
if not id then
598-
return nil
599-
end
619+
local function changes_changed()
620+
local q = world:query(T, PreviousT)
600621

601-
if isTrivial == nil then
602-
isTrivial = typeof(data) ~= "table"
603-
end
622+
return function()
623+
local id, new, old = q:next()
624+
while true do
625+
if not id then
626+
return nil
627+
end
604628

605-
if not isTrivial then
606-
data = table.clone(data)
629+
if not isTrivial then
630+
if not shallowEq(new, old) then
631+
break
607632
end
608-
609-
addedComponents[id] = data
610-
return id, data
633+
elseif new ~= old then
634+
break
611635
end
636+
637+
id, new, old = q:next()
612638
end
613639

614-
function changes.changed()
615-
local q = world:query(component, previous)
616-
617-
return function()
618-
local id, new, old = q:next()
619-
while true do
620-
if not id then
621-
return nil
622-
end
623-
624-
if not isTrivial then
625-
if not shallowEq(new, old) then
626-
break
627-
end
628-
elseif new ~= old then
629-
break
630-
end
631-
632-
id, new, old = q:next()
633-
end
640+
addedComponents[id] = new
634641

635-
addedComponents[id] = new
642+
return id, old, new
643+
end
644+
end
636645

637-
return id, old, new
638-
end
646+
local function changes_removed()
647+
removed = true
648+
649+
local q = world:query(PreviousT):without(T)
650+
return function()
651+
local id = q:next()
652+
if id then
653+
table.insert(removedComponents, id)
639654
end
655+
return id
656+
end
657+
end
640658

641-
function changes.removed()
642-
removed = true
659+
local changes = {
660+
added = changes_added,
661+
changed = changes_changed,
662+
removed = changes_removed,
663+
}
643664

644-
local q = world:query(previous):without(component)
645-
return function()
646-
local id = q:next()
647-
if id then
648-
table.insert(removedComponents, id)
649-
end
650-
return id
651-
end
652-
end
665+
local function track(worldToTrack, fn)
666+
world = worldToTrack
667+
added = true
668+
removed = true
653669

654-
fn(changes)
655-
if not added then
656-
for _ in changes.added() do
657-
end
658-
end
670+
fn(changes)
659671

660-
if not removed then
661-
for _ in changes.removed() do
662-
end
672+
if not added then
673+
for _ in changes_added() do
663674
end
675+
end
664676

665-
for e, data in addedComponents do
666-
world:set(e, previous, if isTrivial then data else table.clone(data))
677+
if not removed then
678+
for _ in changes_removed() do
667679
end
680+
end
668681

669-
for _, e in removedComponents do
670-
world:remove(e, previous)
671-
end
682+
for e, data in addedComponents do
683+
world:set(e, PreviousT, if isTrivial then data else table.clone(data))
672684
end
673685

674-
return {
675-
track = track
676-
}
686+
for _, e in removedComponents do
687+
world:remove(e, PreviousT)
688+
end
689+
end
690+
691+
local tracker = { track = track }
692+
693+
function ChangeTracker<T>(component: Entity<T>): Tracker<T>
694+
T = component
695+
-- We just use jecs.Rest because people will probably not use it anyways
696+
PreviousT = jecs.pair(jecs.Rest, T)
697+
addedComponents = {}
698+
removedComponents = {}
699+
700+
return tracker
677701
end
702+
end
703+
704+
TEST("changetracker", function()
705+
local world = jecs.World.new()
678706

679707
do CASE "should allow change tracking"
680-
local Test = world:component()
681-
local TestTracker = ChangeTracker(world, Test)
708+
local Test = world:component() :: Entity<{ foo: number }>
709+
local TestTracker = ChangeTracker(Test)
682710

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

686-
TestTracker.track(function(changes)
714+
TestTracker.track(world, function(changes)
687715
local added = 0
688716
local changed = 0
689717
local removed = 0
@@ -705,7 +733,7 @@ TEST("changetracker", function()
705733
test.foo = test.foo + 1
706734
end
707735

708-
TestTracker.track(function(changes)
736+
TestTracker.track(world, function(changes)
709737
local added = 0
710738
local changed = 0
711739
local removed = 0
@@ -727,7 +755,7 @@ TEST("changetracker", function()
727755

728756
world:remove(e, Test)
729757

730-
TestTracker.track(function(changes)
758+
TestTracker.track(world, function(changes)
731759
local added = 0
732760
local changed = 0
733761
local removed = 0

0 commit comments

Comments
 (0)