diff --git a/newMatter.lua b/newMatter.lua index 4a50791..b1ebdd0 100644 --- a/newMatter.lua +++ b/newMatter.lua @@ -447,19 +447,23 @@ local function transitionArchetype( targetColumn[destinationRow] = column[sourceRow] end - if sourceRow ~= #column then - column[sourceRow] = column[#column] - column[#column] = nil + local last = #column + if sourceRow ~= last then + column[sourceRow] = column[last] end + + column[last] = nil end - destinationEntities[destinationRow] = sourceEntities[sourceRow] - entityIndex[sourceEntities[sourceRow]].row = destinationRow + local atSourceRow = sourceEntities[sourceRow] + destinationEntities[destinationRow] = atSourceRow + entityIndex[atSourceRow].row = destinationRow local movedAway = #sourceEntities if sourceRow ~= movedAway then - sourceEntities[sourceRow] = sourceEntities[movedAway] - entityIndex[sourceEntities[movedAway]].row = sourceRow + local atMovedAway = sourceEntities[movedAway] + sourceEntities[sourceRow] = atMovedAway + entityIndex[atMovedAway].row = sourceRow end sourceEntities[movedAway] = nil @@ -492,17 +496,20 @@ local function hash(arr): string | number end local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype) - local destinationCount = #to.types local destinationIds = to.types + local records = to.records + local id = to.id - for i = 1, destinationCount do - local destinationId = destinationIds[i] + for i, destinationId in destinationIds do + local archetypesMap = componentIndex[destinationId] - if not componentIndex[destinationId] then - componentIndex[destinationId] = { sparse = {}, size = 0 } + if not archetypesMap then + archetypesMap = { size = 0, sparse = {} } + componentIndex[destinationId] = archetypesMap end - componentIndex[destinationId].sparse[to.id] = i - to.records[destinationId] = i + + archetypesMap.sparse[id] = i + records[destinationId] = i end end @@ -512,10 +519,11 @@ local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Arch world.nextArchetypeId = (world.nextArchetypeId :: number) + 1 local id = world.nextArchetypeId - local columns = {} :: { any } + local length = #types + local columns = table.create(length) :: { any } - for _ in types do - table.insert(columns, {}) + for index in types do + columns[index] = {} end local archetype = { @@ -531,8 +539,8 @@ local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Arch world.archetypeIndex[ty] = archetype world.archetypes[id] = archetype - if #types > 0 then - createArchetypeRecords(world.componentIndex, archetype, prev) + if length > 0 then + createArchetypeRecords(world.componentIndex, archetype) end return archetype @@ -552,9 +560,9 @@ function World.new() nextArchetypeId = 0, _size = 0, _changedStorage = {}, + ROOT_ARCHETYPE = (nil :: any) :: Archetype, }, World) - self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil) return self end @@ -575,9 +583,7 @@ local function ensureArchetype(world: World, types, prev) end local function findInsert(types: { i53 }, toAdd: i53) - local count = #types - for i = 1, count do - local id = types[i] + for i, id in types do if id == toAdd then return -1 end @@ -585,7 +591,8 @@ local function findInsert(types: { i53 }, toAdd: i53) return i end end - return count + 1 + + return #types + 1 end local function findArchetypeWith(world: World, node: Archetype, componentId: i53) @@ -601,21 +608,49 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53 end local function ensureEdge(archetype: Archetype, componentId: i53) - if not archetype.edges[componentId] then - archetype.edges[componentId] = {} :: any + local edges = archetype.edges + local edge = edges[componentId] + if not edge then + edge = {} :: any + edges[componentId] = edge end - return archetype.edges[componentId] + + return edge end -local function archetypeTraverseAdd(world: World, componentId: i53, archetype: Archetype?): Archetype - local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype - local edge = ensureEdge(from, componentId) +local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype?): Archetype + if not from then + -- If there was no source archetype then it should return the ROOT_ARCHETYPE + local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE + if not ROOT_ARCHETYPE then + ROOT_ARCHETYPE = archetypeOf(world, {}, nil) + world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never + end - if not edge.add then - edge.add = findArchetypeWith(world, from, componentId) + from = ROOT_ARCHETYPE end - return edge.add + local edge = ensureEdge(from :: Archetype, componentId) + local add = edge.add + if not add then + -- Save an edge using the component ID to the archetype to allow + -- faster traversals to adjacent archetypes. + add = findArchetypeWith(world, from :: Archetype, componentId) + edge.add = add :: never + end + + return add +end + +local function ensureRecord(entityIndex, entityId): Record + local record = entityIndex[entityId] + + if not record then + record = {} + entityIndex[entityId] = record + end + + return record :: Record end local function componentAdd(world: World, entityId: i53, componentInstance) @@ -626,7 +661,7 @@ local function componentAdd(world: World, entityId: i53, componentInstance) -- This never gets cleaned up world.componentIdToComponent[componentId] = component - local record = world:ensureRecord(entityId) + local record = ensureRecord(world.entityIndex, entityId) local sourceArchetype = record.archetype local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype) @@ -649,29 +684,22 @@ local function componentAdd(world: World, entityId: i53, componentInstance) destinationArchetype.columns[archetypeRecord][record.row] = componentInstance end -function World.ensureRecord(world: World, entityId: i53) - local entityIndex = world.entityIndex - local id = entityId - if not entityIndex[id] then - entityIndex[id] = {} :: Record - end - return entityIndex[id] -end - local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype local edge = ensureEdge(from, componentId) - if not edge.remove then + local remove = edge.remove + if not remove then local to = table.clone(from.types) table.remove(to, table.find(to, componentId)) - edge.remove = ensureArchetype(world, to, from) + remove = ensureArchetype(world, to, from) + edge.remove = remove :: never end - return edge.remove + return remove end -local function get(componentIndex: ComponentIndex, record: Record, componentId: i24): ComponentInstance? +local function get(record: Record, componentId: i24): ComponentInstance? local archetype = record.archetype if archetype == nil then return nil @@ -685,15 +713,19 @@ local function get(componentIndex: ComponentIndex, record: Record, componentId: return archetype.columns[archetypeRecord][record.row] end -local function componentRemove(world: World, entityId: i53, component: Component) +local function componentRemove(world: World, entityId: i53, component: Component): ComponentInstance? local componentId = #component - local record = world:ensureRecord(entityId) + local record = ensureRecord(world.entityIndex, entityId) local sourceArchetype = record.archetype local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) -- TODO: -- There is a better way to get the component for returning - local componentInstance = get(world.componentIndex, record, componentId) + local componentInstance = get(record, componentId) + if componentInstance == nil then + return nil + end + if sourceArchetype and not (sourceArchetype == destinationArchetype) then moveEntity(world.entityIndex, entityId, record, destinationArchetype) end @@ -720,40 +752,35 @@ function World.remove(world: World, entityId: i53, ...) local length = select("#", ...) local removed = {} for i = 1, length do - table.insert(removed, componentRemove(world, entityId, select(i, ...))) + local oldComponent = componentRemove(world, entityId, select(i, ...)) + if not oldComponent then + continue + end + + table.insert(removed, oldComponent) + + world:_trackChanged(select(i, ...), entityId, oldComponent, nil) end return unpack(removed, 1, length) end -function World.get( - world: World, - entityId: i53, - a: Component, - b: Component?, - c: Component?, - d: Component?, - e: Component? -): any +function World.get(world: World, entityId: i53, ...: Component): any local componentIndex = world.componentIndex local record = world.entityIndex[entityId] if not record then return nil end - local va = get(componentIndex, record, #a) - - if b == nil then - return va - elseif c == nil then - return va, get(componentIndex, record, #b) - elseif d == nil then - return va, get(componentIndex, record, #b), get(componentIndex, record, #c) - elseif e == nil then - return va, get(componentIndex, record, #b), get(componentIndex, record, #c), get(componentIndex, record, #d) - else - error("args exceeded") + local length = select("#", ...) + local components = {} + for i = 1, length do + local metatable = select(i, ...) + assertValidComponent(metatable, i) + components[i] = get(record, #metatable) end + + return unpack(components, 1, length) end function World.insert(world: World, entityId: i53, ...) @@ -863,7 +890,7 @@ function World.spawnAt(world: World, entityId: i53, ...: ComponentInstance) end world._size += 1 - world:ensureRecord(entityId) + ensureRecord(world.entityIndex, entityId) local components = {} for i = 1, select("#", ...) do @@ -897,8 +924,14 @@ end function World.despawn(world: World, entityId: i53) local entityIndex = world.entityIndex local record = entityIndex[entityId] - moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE) - world.ROOT_ARCHETYPE.entities[record.row] = nil + + -- TODO: + -- Track despawn changes + if record.archetype then + moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE) + world.ROOT_ARCHETYPE.entities[record.row] = nil + end + entityIndex[entityId] = nil world._size -= 1 end @@ -1481,14 +1514,24 @@ function World.queryChanged(world: World, componentToTrack, ...: nil) table.insert(world._changedStorage[componentToTrack], storage) - local queryResult = world:query(componentToTrack) - + -- TODO: + -- Go back to lazy evaluation of the query + -- Switched because next is not working + local snapshot = world:query(componentToTrack):snapshot() + local last return function(): any - local entityId, component = queryResult:next() + local index, entry = next(snapshot, last) + last = index + if not index then + return + end + + local entityId, component = entry[1], entry[2] if entityId then return entityId, table.freeze({ new = component }) end + return end end