diff --git a/.gitignore b/.gitignore index 9143a00..a43fa5f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,3 @@ WallyPatches roblox.toml sourcemap.json drafts/*.lua - -*.code-workspace -roblox.yml diff --git a/lib/init.lua b/lib/init.lua index defc1b3..a57ae9e 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -287,23 +287,31 @@ local function archetypeTraverseAdd(world: World, componentId: i53, from: Archet return add end -local function ensureRecord(entityIndex, entityId: i53): Record +local function ensureRecord(world, entityId: i53): Record + local entityIndex = world.entityIndex local record = entityIndex[entityId] - if not record then - record = {} - entityIndex[entityId] = record + if record then + return record end - return record :: Record + local ROOT = world.ROOT_ARCHETYPE + local row = #ROOT.entities + 1 + ROOT.entities[row] = entityId + record = { + archetype = ROOT, + row = row + } + entityIndex[entityId] = record + return record end function World.add(world: World, entityId: i53, componentId: i53) - local record = ensureRecord(world.entityIndex, entityId) + local record = ensureRecord(world, entityId) local from = record.archetype local to = archetypeTraverseAdd(world, componentId, from) - if from then + if from and not (from == world.ROOT_ARCHETYPE) then moveEntity(world.entityIndex, entityId, record, to) else if #to.types > 0 then @@ -315,19 +323,20 @@ end -- Symmetric like `World.add` but idempotent function World.set(world: World, entityId: i53, componentId: i53, data: unknown) - local record = ensureRecord(world.entityIndex, entityId) + local record = ensureRecord(world, entityId) local from = record.archetype - local to = archetypeTraverseAdd(world, componentId, from) - if from == to then + local archetypeRecord = from.records[componentId] + if archetypeRecord then -- If the archetypes are the same it can avoid moving the entity -- and just set the data directly. - local archetypeRecord = to.records[componentId] from.columns[archetypeRecord][record.row] = data -- Should fire an OnSet event here. return end + local to = archetypeTraverseAdd(world, componentId, from) + if from then -- If there was a previous archetype, then the entity needs to move the archetype moveEntity(world.entityIndex, entityId, record, to) @@ -335,22 +344,25 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown) if #to.types > 0 then -- When there is no previous archetype it should create the archetype newEntity(entityId, record, to) - onNotifyAdd(world, to, from, record.row, {componentId}) + --onNotifyAdd(world, to, from, record.row, {componentId}) end end - local archetypeRecord = to.records[componentId] + archetypeRecord = to.records[componentId] to.columns[archetypeRecord][record.row] = data end -local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype - local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype +local function archetypeTraverseRemove(world: World, componentId: i53, from: Archetype): Archetype local edge = ensureEdge(from, componentId) local remove = edge.remove if not remove then local to = table.clone(from.types) - table.remove(to, table.find(to, componentId)) + local at = table.find(to, componentId) + if not at then + return from + end + table.remove(to, at) remove = ensureArchetype(world, to, from) edge.remove = remove :: never end @@ -359,13 +371,12 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype end function World.remove(world: World, entityId: i53, componentId: i53) - local entityIndex = world.entityIndex - local record = ensureRecord(entityIndex, entityId) + local record = ensureRecord(world, entityId) local sourceArchetype = record.archetype local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) if sourceArchetype and not (sourceArchetype == destinationArchetype) then - moveEntity(entityIndex, entityId, record, destinationArchetype) + moveEntity(world.entityIndex, entityId, record, destinationArchetype) end end @@ -732,4 +743,4 @@ return table.freeze({ ON_ADD = ON_ADD; ON_REMOVE = ON_REMOVE; ON_SET = ON_SET; -}) +}) \ No newline at end of file diff --git a/tests/test1.lua b/tests/test1.lua index 7ff3b5a..3fe86da 100644 --- a/tests/test1.lua +++ b/tests/test1.lua @@ -35,14 +35,17 @@ TEST("world:query", function() local world = jecs.World.new() local A = world:component() local B = world:component() + local C = world:component() local entities = {} for i = 1, N do local id = world:entity() - world:set(id, A, true) + -- specifically put them in disorder to track regression + -- https://github.com/Ukendio/jecs/pull/15 world:set(id, B, true) - if i > 5 then world:remove(id, B, true) end + world:set(id, A, true) + if i > 5 then world:remove(id, B) end entities[i] = id end @@ -110,6 +113,20 @@ TEST("world:query", function() CHECK(world:get(id, Health) == nil) end + do CASE "show allow remove that doesn't exist on entity" + local world = jecs.World.new() + + local Health = world:entity() + local Poison = world:component() + + local id = world:entity() + world:set(id, Health, 50) + world:remove(id, Poison) + + CHECK(world:get(id, Poison) == nil) + CHECK(world:get(id, Health) == 50) + end + do CASE "Should allow iterating the whole world" local world = jecs.World.new()