Add case for when component is not found in archetype (#25)

* Add case for when component is not found in archetype

* Check only destination archetype first

* Omit onNotifyAdd
This commit is contained in:
Marcus 2024-05-08 00:57:22 +02:00 committed by GitHub
parent e8b78f7b50
commit 91d3fcabc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 25 deletions

3
.gitignore vendored
View file

@ -50,6 +50,3 @@ WallyPatches
roblox.toml roblox.toml
sourcemap.json sourcemap.json
drafts/*.lua drafts/*.lua
*.code-workspace
roblox.yml

View file

@ -287,23 +287,31 @@ local function archetypeTraverseAdd(world: World, componentId: i53, from: Archet
return add return add
end end
local function ensureRecord(entityIndex, entityId: i53): Record local function ensureRecord(world, entityId: i53): Record
local entityIndex = world.entityIndex
local record = entityIndex[entityId] local record = entityIndex[entityId]
if not record then if record then
record = {} return record
entityIndex[entityId] = record
end 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 end
function World.add(world: World, entityId: i53, componentId: i53) 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 from = record.archetype
local to = archetypeTraverseAdd(world, componentId, from) local to = archetypeTraverseAdd(world, componentId, from)
if from then if from and not (from == world.ROOT_ARCHETYPE) then
moveEntity(world.entityIndex, entityId, record, to) moveEntity(world.entityIndex, entityId, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
@ -315,19 +323,20 @@ end
-- Symmetric like `World.add` but idempotent -- Symmetric like `World.add` but idempotent
function World.set(world: World, entityId: i53, componentId: i53, data: unknown) 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 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 -- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly. -- and just set the data directly.
local archetypeRecord = to.records[componentId]
from.columns[archetypeRecord][record.row] = data from.columns[archetypeRecord][record.row] = data
-- Should fire an OnSet event here. -- Should fire an OnSet event here.
return return
end end
local to = archetypeTraverseAdd(world, componentId, from)
if from then if from then
-- If there was a previous archetype, then the entity needs to move the archetype -- If there was a previous archetype, then the entity needs to move the archetype
moveEntity(world.entityIndex, entityId, record, to) 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 if #to.types > 0 then
-- When there is no previous archetype it should create the archetype -- When there is no previous archetype it should create the archetype
newEntity(entityId, record, to) newEntity(entityId, record, to)
onNotifyAdd(world, to, from, record.row, {componentId}) --onNotifyAdd(world, to, from, record.row, {componentId})
end end
end end
local archetypeRecord = to.records[componentId] archetypeRecord = to.records[componentId]
to.columns[archetypeRecord][record.row] = data to.columns[archetypeRecord][record.row] = data
end end
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype local function archetypeTraverseRemove(world: World, componentId: i53, from: Archetype): Archetype
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
local edge = ensureEdge(from, componentId) local edge = ensureEdge(from, componentId)
local remove = edge.remove local remove = edge.remove
if not remove then if not remove then
local to = table.clone(from.types) 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) remove = ensureArchetype(world, to, from)
edge.remove = remove :: never edge.remove = remove :: never
end end
@ -359,13 +371,12 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype
end end
function World.remove(world: World, entityId: i53, componentId: i53) function World.remove(world: World, entityId: i53, componentId: i53)
local entityIndex = world.entityIndex local record = ensureRecord(world, entityId)
local record = ensureRecord(entityIndex, entityId)
local sourceArchetype = record.archetype local sourceArchetype = record.archetype
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
if sourceArchetype and not (sourceArchetype == destinationArchetype) then if sourceArchetype and not (sourceArchetype == destinationArchetype) then
moveEntity(entityIndex, entityId, record, destinationArchetype) moveEntity(world.entityIndex, entityId, record, destinationArchetype)
end end
end end
@ -732,4 +743,4 @@ return table.freeze({
ON_ADD = ON_ADD; ON_ADD = ON_ADD;
ON_REMOVE = ON_REMOVE; ON_REMOVE = ON_REMOVE;
ON_SET = ON_SET; ON_SET = ON_SET;
}) })

View file

@ -35,14 +35,17 @@ TEST("world:query", function()
local world = jecs.World.new() local world = jecs.World.new()
local A = world:component() local A = world:component()
local B = world:component() local B = world:component()
local C = world:component()
local entities = {} local entities = {}
for i = 1, N do for i = 1, N do
local id = world:entity() 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) 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 entities[i] = id
end end
@ -110,6 +113,20 @@ TEST("world:query", function()
CHECK(world:get(id, Health) == nil) CHECK(world:get(id, Health) == nil)
end 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" do CASE "Should allow iterating the whole world"
local world = jecs.World.new() local world = jecs.World.new()