diff --git a/lib/init.lua b/lib/init.lua index 943cb66..b880eea 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -11,14 +11,14 @@ type ArchetypeId = number type Column = { any } +type ArchetypeEdge = { + add: Archetype, + remove: Archetype, +} + type Archetype = { id: number, - edges: { - [i53]: { - add: Archetype, - remove: Archetype, - }, - }, + edges: { [i53]: ArchetypeEdge }, types: Ty, type: string | number, entities: { number }, @@ -75,7 +75,7 @@ local ECS_ID_FLAGS_MASK = 0x10 local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_GENERATION_MASK = bit32.lshift(1, 16) -local function addFlags(isPair: boolean) +local function addFlags(isPair: boolean): number local typeFlags = 0x0 if isPair then @@ -94,48 +94,17 @@ local function addFlags(isPair: boolean) return typeFlags end -<<<<<<< HEAD -local function newId(source: number, target: number): number - return ((source * 2^28) + target) * ECS_ID_FLAGS_MASK +local function ECS_COMBINE(source: number, target: number): i53 + return ((source * 268435456) + target) * ECS_ID_FLAGS_MASK end local function ECS_IS_PAIR(e: number): boolean - return ( (e % 2^4) // FLAGS_PAIR ) ~= 0 -end - -local function separate(entity: number): (number, number, number) - local type_flags = entity % 0x10 - local entity = entity // ECS_ID_FLAGS_MASK - return new_entity // ECS_ENTITY_MASK, new_entity % ECS_GENERATION_MASK, type_flags -======= -local function ECS_COMBINE(source: number, target: number): i53 - local e = source * 268435456 + target * ECS_ID_FLAGS_MASK - return e -end - -local function ECS_IS_PAIR(e: number) - if e > ECS_ENTITY_MASK then - return (e % 2 ^ 4) // FLAGS_PAIR ~= 0 - end - return false ->>>>>>> eae51988a9e3ca45e39ebcfdbea0f9f8706bd3cd + return if e > ECS_ENTITY_MASK then (((e % 2) ^ 4) // FLAGS_PAIR) ~= 0 else false end -- HIGH 24 bits LOW 24 bits -local function ECS_GENERATION(e: i53) -<<<<<<< HEAD - return (e // 0x10) % ECS_GENERATION_MASK -end - -local function ECS_ID(e: i53) - return (e // 0x10) // ECS_ENTITY_MASK -======= - if e > ECS_ENTITY_MASK then - e = e // 0x10 - return e % ECS_GENERATION_MASK - end - return 0 ->>>>>>> eae51988a9e3ca45e39ebcfdbea0f9f8706bd3cd +local function ECS_GENERATION(e: i53): i24 + return if e > ECS_ENTITY_MASK then (e // 0x10) % ECS_GENERATION_MASK else 0 end local function ECS_GENERATION_INC(e: i53) @@ -144,74 +113,28 @@ local function ECS_GENERATION_INC(e: i53) local id = flags // ECS_ENTITY_MASK local generation = flags % ECS_GENERATION_MASK -<<<<<<< HEAD - return newId(id, generation + 1) + flags -end - --- gets the high ID -local function ECS_PAIR_FIRST(entity: i53): i24 - return (entity // 0x10) % ECS_ENTITY_MASK -end - --- gets the low ID -local ECS_PAIR_SECOND = ECS_ID - -local function ECS_PAIR(first: number, second: number): - local target = WILDCARD - local relation - - if first == WILDCARD then - relation = second - elseif second == WILDCARD then - relation = first - else - relation = second - target = ECS_PAIR_SECOND(first) - end - - return newId( - ECS_PAIR_SECOND(relation), target) + addFlags(--[[isPair]] true) - ) -end - -local function getAlive(entityIndex: EntityIndex, id: i53) - return entityIndex.dense[id] -======= return ECS_COMBINE(id, generation + 1) + flags + else + return ECS_COMBINE(e, 1) end - return ECS_COMBINE(e, 1) ->>>>>>> eae51988a9e3ca45e39ebcfdbea0f9f8706bd3cd end -- FIRST gets the high ID local function ECS_ENTITY_T_HI(e: i53): i24 - if e > ECS_ENTITY_MASK then - e = e // 0x10 - return e % ECS_ENTITY_MASK - end - return e + return if e > ECS_ENTITY_MASK then (e // 0x10) % ECS_ENTITY_MASK else e end -<<<<<<< HEAD -local function nextEntityId(entityIndex, index: i24): i53 - local id = newId(index, 0) -======= -- SECOND local function ECS_ENTITY_T_LO(e: i53): i24 - if e > ECS_ENTITY_MASK then - e = e // 0x10 - return e // ECS_ENTITY_MASK - end - return e + return if e > ECS_ENTITY_MASK then (e // 0x10) // ECS_ENTITY_MASK else e end local function ECS_PAIR(pred: i53, obj: i53): i53 return ECS_COMBINE(ECS_ENTITY_T_LO(obj), ECS_ENTITY_T_LO(pred)) + addFlags(--[[isPair]] true) :: i53 end -local function getAlive(entityIndex: EntityIndex, id: i24) - local entityId = entityIndex.dense[id] - return entityId +local function getAlive(entityIndex: EntityIndex, id: i24): i53 + return entityIndex.dense[id] end -- ECS_PAIR_FIRST, gets the relationship target / obj / HIGH bits @@ -227,7 +150,6 @@ end local function nextEntityId(entityIndex: EntityIndex, index: i24): i53 --local id = ECS_COMBINE(index, 0) local id = index ->>>>>>> eae51988a9e3ca45e39ebcfdbea0f9f8706bd3cd entityIndex.sparse[id] = { dense = index, } :: Record @@ -298,7 +220,7 @@ local function archetypeAppend(entity: number, archetype: Archetype): number return length end -local function newEntity(entityId: i53, record: Record, archetype: Archetype) +local function newEntity(entityId: i53, record: Record, archetype: Archetype): Record local row = archetypeAppend(entityId, archetype) record.archetype = archetype record.row = row @@ -314,7 +236,7 @@ local function moveEntity(entityIndex: EntityIndex, entityId: i53, record: Recor record.row = destinationRow end -local function hash(arr): string +local function hash(arr: { number }): string return table.concat(arr, "_") end @@ -324,10 +246,10 @@ local function ensureComponentRecord( componentId: number, i: number ): ArchetypeMap - local archetypesMap = componentIndex[componentId] + local archetypesMap = componentIndex[componentId] if not archetypesMap then - archetypesMap = ({ size = 0, cache = {} } :: any) :: ArchetypeMap + archetypesMap = ({ size = 0, cache = {} } :: any) :: ArchetypeMap componentIndex[componentId] = archetypesMap end @@ -337,7 +259,7 @@ local function ensureComponentRecord( return archetypesMap end -local function ECS_ID_IS_WILDCARD(e) +local function ECS_ID_IS_WILDCARD(e: i53): boolean assert(ECS_IS_PAIR(e)) local first = ECS_ENTITY_T_HI(e) local second = ECS_ENTITY_T_LO(e) @@ -391,7 +313,8 @@ end local World = {} World.__index = World -function World.new() + +function World.new(): World local self = setmetatable({ archetypeIndex = {} :: { [string]: Archetype }, archetypes = {} :: Archetypes, @@ -409,12 +332,13 @@ function World.new() ROOT_ARCHETYPE = (nil :: any) :: Archetype, }, World) self.ROOT_ARCHETYPE = archetypeOf(self, {}) - return self + + return table.freeze(self) end export type World = typeof(World.new()) -function World.component(world: World) +function World.component(world: World): i53 local componentId = world.nextComponentId + 1 if componentId > HI_COMPONENT_ID then -- IDs are partitioned into ranges because component IDs are not nominal, @@ -425,7 +349,7 @@ function World.component(world: World) return nextEntityId(world.entityIndex, componentId) end -function World.entity(world: World) +function World.entity(world: World): i53 local entityId = world.nextEntityId + 1 world.nextEntityId = entityId return nextEntityId(world.entityIndex, entityId + REST) @@ -473,6 +397,7 @@ local function archetypeDelete(world: World, id: i53) local componentIndex = world.componentIndex local archetypesMap = componentIndex[id] local archetypes = world.archetypes + if archetypesMap then for archetypeId in archetypesMap.cache do for _, entity in archetypes[archetypeId].entities do @@ -534,7 +459,7 @@ local function ensureArchetype(world: World, types, prev): Archetype return archetypeOf(world, types, prev) end -local function findInsert(types: { i53 }, toAdd: i53) +local function findInsert(types: { i53 }, toAdd: i53): number for i, id in types do if id == toAdd then return -1 @@ -546,7 +471,7 @@ local function findInsert(types: { i53 }, toAdd: i53) return #types + 1 end -local function findArchetypeWith(world: World, node: Archetype, componentId: i53) +local function findArchetypeWith(world: World, node: Archetype, componentId: i53): Archetype local types = node.types -- Component IDs are added incrementally, so inserting and sorting -- them each time would be expensive. Instead this insertion sort can find the insertion @@ -564,7 +489,7 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53 return ensureArchetype(world, destinationType, node) end -local function ensureEdge(archetype: Archetype, componentId: i53) +local function ensureEdge(archetype: Archetype, componentId: i53): ArchetypeEdge local edges = archetype.edges local edge = edges[componentId] if not edge then @@ -662,7 +587,7 @@ function World.remove(world: World, entityId: i53, componentId: i53) end -- Keeping the function as small as possible to enable inlining -local function get(record: Record, componentId: i24) +local function get(record: Record, componentId: i24): any local archetype = record.archetype if not archetype then return nil @@ -677,7 +602,7 @@ local function get(record: Record, componentId: i24) return archetype.columns[archetypeRecord][record.row] end -function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): any +function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): ...any local id = entityId local record = world.entityIndex.sparse[id] if not record then @@ -716,13 +641,13 @@ export type Query = typeof(EmptyQuery) type CompatibleArchetype = { archetype: Archetype, indices: { number } } -function World.query(world: World, ...): Query +function World.query(world: World, ...: i53): Query -- breaking? if (...) == nil then - error("Missing components") + error(" Missing components") end - local compatibleArchetypes: { CompatibleArchetype } = {} + local compatibleArchetypes: { CompatibleArchetype } = {} local length = 0 local components = { ... } @@ -772,7 +697,7 @@ function World.query(world: World, ...): Query end local lastArchetype = 1 - local compatibleArchetype: CompatibleArchetype = compatibleArchetypes[lastArchetype] + local compatibleArchetype: CompatibleArchetype = compatibleArchetypes[lastArchetype] if not compatibleArchetype then return EmptyQuery @@ -780,18 +705,18 @@ function World.query(world: World, ...): Query local preparedQuery = {} preparedQuery.__index = preparedQuery - + local queryOutput = {} local i = 1 - local function queryNext() + local function queryNext(): ...any local archetype = compatibleArchetype.archetype local entityId = archetype.entities[i] - while entityId == nil do + while entityId == nil do lastArchetype += 1 - if lastArchetype > #compatibleArchetypes then + if lastArchetype > #compatibleArchetypes then return end compatibleArchetype = compatibleArchetypes[lastArchetype] @@ -801,7 +726,7 @@ function World.query(world: World, ...): Query end local row = i - i+=1 + i += 1 local columns = archetype.columns local tr = compatibleArchetype.indices @@ -856,16 +781,16 @@ function World.query(world: World, ...): Query return entityId, unpack(queryOutput :: any, 1, queryLength) end - - function preparedQuery:__iter() + + function preparedQuery:__iter(): () -> ...any return queryNext end - function preparedQuery:next() - return queryNext() + function preparedQuery:next(): ...any + return queryNext() end - function preparedQuery:without(...) + function preparedQuery:without(...: i53): Query local withoutComponents = { ... } for i = #compatibleArchetypes, 1, -1 do local archetype = compatibleArchetypes[i].archetype @@ -895,40 +820,53 @@ function World.query(world: World, ...): Query return setmetatable({}, preparedQuery) :: any end -function World.__iter(world: World): () -> any +type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53) + +function World.__iter(world: World): WorldIterator local dense = world.entityIndex.dense local sparse = world.entityIndex.sparse local last - return function() - local lastEntity: number?, entityId: number = next(dense, last) - if not lastEntity then - return + -- new solver doesnt like the world iterator type even tho its correct + -- so any cast here i come + local iterator: WorldIterator = ( + function() + local lastEntity: number?, entityId: number = next(dense, last) + if not lastEntity then + -- ignore type error + return + end + + last = lastEntity + + local record = sparse[entityId] + local archetype = record.archetype + if not archetype then + -- Returns only the entity id as an entity without data should not return + -- data and allow the user to get an error if they don't handle the case. + return entityId + end + + local row = record.row + local types = archetype.types + local columns = archetype.columns + local entityData = {} + for i, column in columns do + -- We use types because the key should be the component ID not the column index + entityData[types[i]] = column[row] + end + + return entityId, entityData end + ) :: any - last = lastEntity - - local record = sparse[entityId] - local archetype = record.archetype - if not archetype then - -- Returns only the entity id as an entity without data should not return - -- data and allow the user to get an error if they don't handle the case. - return entityId - end - - local row = record.row - local types = archetype.types - local columns = archetype.columns - local entityData = {} - for i, column in columns do - -- We use types because the key should be the component ID not the column index - entityData[types[i]] = column[row] - end - - return entityId, entityData - end + return iterator end +-- freezing it incase somebody trys doing something stupid and modifying it +-- (unlikely but its easy to add extra safety so) +table.freeze(World) + -- __nominal_type_dont_use could not be any or T as it causes a type error -- or produces a union export type Entity = number & { __nominal_type_dont_use: T }