From 517dbb99c0eb8ae54f731ee6742e96daf50400eb Mon Sep 17 00:00:00 2001 From: alicesaidhi <166900055+alicesaidhi@users.noreply.github.com> Date: Tue, 7 May 2024 18:37:14 +0200 Subject: [PATCH 1/7] Add svg images (#18) * svg logo * fix light mode * Create logo_old.png --- README.md | 3 ++- jecs_darkmode.svg | 6 ++++++ jecs_lightmode.svg | 6 ++++++ logo.png => logo_old.png | Bin 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 jecs_darkmode.svg create mode 100644 jecs_lightmode.svg rename logo.png => logo_old.png (100%) diff --git a/README.md b/README.md index 96f82a8..0386756 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@

- + +

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache-blue.svg?style=for-the-badge)](LICENSE-APACHE) diff --git a/jecs_darkmode.svg b/jecs_darkmode.svg new file mode 100644 index 0000000..f64b173 --- /dev/null +++ b/jecs_darkmode.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/jecs_lightmode.svg b/jecs_lightmode.svg new file mode 100644 index 0000000..dbcd08c --- /dev/null +++ b/jecs_lightmode.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/logo.png b/logo_old.png similarity index 100% rename from logo.png rename to logo_old.png From 887c892c2ebb4406e5e6f7f7d3be4e018b031046 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 7 May 2024 21:30:36 +0200 Subject: [PATCH 2/7] Move root archetype (#33) --- lib/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/init.lua b/lib/init.lua index 8c0b5bd..ce0951e 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -190,6 +190,7 @@ function World.new() nextEntityId = 0; ROOT_ARCHETYPE = (nil :: any) :: Archetype; }, World) + self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil) return self end @@ -272,6 +273,7 @@ local function ensureEdge(archetype: Archetype, componentId: i53) end local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype + from = from or world.ROOT_ARCHETYPE if not from then -- If there was no source archetype then it should return the ROOT_ARCHETYPE local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE From bf5908a8f5da83641be19bff397bdf54e852b1a4 Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 7 May 2024 21:32:56 +0200 Subject: [PATCH 3/7] Adds symmetic and idempotent function add (#26) * Adds symmetic function add * Should be componentId not entityId --- lib/init.lua | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index ce0951e..250a56f 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -274,15 +274,6 @@ end local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype from = from or world.ROOT_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 - from = ROOT_ARCHETYPE - end local edge = ensureEdge(from, componentId) local add = edge.add @@ -307,7 +298,23 @@ local function ensureRecord(entityIndex, entityId: i53): Record return record :: Record end -function World.set(world: World, entityId: i53, componentId: i53, data: unknown) + +function World.add(world: World, entityId: i53, componentId: i53) + local record = ensureRecord(world.entityIndex, entityId) + local from = record.archetype + local to = archetypeTraverseAdd(world, componentId, from) + if from then + moveEntity(world.entityIndex, entityId, record, to) + else + if #to.types > 0 then + newEntity(entityId, record, to) + onNotifyAdd(world, to, from, record.row, { componentId }) + end + end +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 from = record.archetype local to = archetypeTraverseAdd(world, componentId, from) @@ -331,7 +338,7 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown) onNotifyAdd(world, to, from, record.row, {componentId}) end end - + local archetypeRecord = to.records[componentId] to.columns[archetypeRecord][record.row] = data end From e8b78f7b50033d02c33fc755c7ec584da12c3acb Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 7 May 2024 21:33:42 +0200 Subject: [PATCH 4/7] Add world.delete (#22) --- lib/init.lua | 50 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index 250a56f..defc1b3 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -596,15 +596,51 @@ function World.entity(world: World) return nextEntityId + REST end -function World.delete(world: World, entityId: i53) +-- should reuse this logic in World.set instead of swap removing in transition archetype +local function destructColumns(columns, count, row) + if row == count then + for _, column in columns do + column[count] = nil + end + else + for _, column in columns do + column[row] = column[count] + column[count] = nil + end + end +end + +local function archetypeDelete(entityIndex, archetype: Archetype, row: i24, destruct: boolean) + local entities = archetype.entities + local last = #entities + + local entityToMove = entities[last] + --local entityToDelete = entities[row] + entities[row] = entityToMove + entities[last] = nil + + if row ~= last then + local recordToMove = entityIndex[entityToMove] + if recordToMove then + recordToMove.row = row + end + end + + local columns = archetype.columns + + if not destruct then + return + end + + destructColumns(columns, last, row) +end + +function World.delete(world: World, entityId: i53) local entityIndex = world.entityIndex local record = entityIndex[entityId] - moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE) - -- Since we just appended an entity to the ROOT_ARCHETYPE we have to remove it from - -- the entities array and delete the record. We know there won't be the hole since - -- we are always removing the last row. - --world.ROOT_ARCHETYPE.entities[record.row] = nil - --entityIndex[entityId] = nil + local archetype = record.archetype + archetypeDelete(entityIndex, archetype, record.row, true) + entityIndex[entityId] = nil end function World.observer(world: World, ...) From 91d3fcabc3d63d54ed3362da659d262fa9806286 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 8 May 2024 00:57:22 +0200 Subject: [PATCH 5/7] 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 --- .gitignore | 3 --- lib/init.lua | 51 ++++++++++++++++++++++++++++++------------------- tests/test1.lua | 21 ++++++++++++++++++-- 3 files changed, 50 insertions(+), 25 deletions(-) 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() From 1de41447b62caae571bd1b34a254ea9518b69311 Mon Sep 17 00:00:00 2001 From: Marcus Date: Wed, 8 May 2024 01:04:11 +0200 Subject: [PATCH 6/7] Remove observer for now (#34) * Add case for when component is not found in archetype * Check only destination archetype first * Omit onNotifyAdd * Remove observers --- lib/init.lua | 53 ----------------------------------------------- lib/init.spec.lua | 16 -------------- 2 files changed, 69 deletions(-) diff --git a/lib/init.lua b/lib/init.lua index a57ae9e..29396a5 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -654,59 +654,6 @@ function World.delete(world: World, entityId: i53) entityIndex[entityId] = nil end -function World.observer(world: World, ...) - local componentIds = {...} - local idsCount = #componentIds - local hooks = world.hooks - - return { - event = function(event) - local hook = hooks[event] - hooks[event] = nil - - local last, change - return function() - last, change = next(hook, last) - if not last then - return - end - - local matched = false - local ids = change.ids - - while not matched do - local skip = false - for _, id in ids do - if not table.find(componentIds, id) then - skip = true - break - end - end - - if skip then - last, change = next(hook, last) - ids = change.ids - continue - end - - matched = true - end - - local queryOutput = table.create(idsCount) - local row = change.offset - local archetype = change.archetype - local columns = archetype.columns - local archetypeRecords = archetype.records - for index, id in componentIds do - queryOutput[index] = columns[archetypeRecords[id]][row] - end - - return archetype.entities[row], unpack(queryOutput, 1, idsCount) - end - end; - } -end - function World.__iter(world: World): () -> (number?, unknown?) local entityIndex = world.entityIndex local last diff --git a/lib/init.spec.lua b/lib/init.spec.lua index 98f485b..553c9a4 100644 --- a/lib/init.spec.lua +++ b/lib/init.spec.lua @@ -176,22 +176,6 @@ return function() expect(added).to.equal(0) end) - it("track changes", function() - local Position = world:entity() - - local moving = world:entity() - world:set(moving, Position, Vector3.new(1, 2, 3)) - - local count = 0 - - for e, position in world:observer(Position).event(jecs.ON_ADD) do - count += 1 - expect(e).to.equal(moving) - expect(position).to.equal(Vector3.new(1, 2, 3)) - end - expect(count).to.equal(1) - end) - it("should query all matching entities", function() local world = jecs.World.new() From 87d49e513422a47d5022932c1c8143a4dbd0504a Mon Sep 17 00:00:00 2001 From: Ukendio Date: Thu, 9 May 2024 02:20:54 +0200 Subject: [PATCH 7/7] Reorganize file --- lib/a.lua | 5 +++ lib/init.lua | 94 ++++++++++++++++++++++++++-------------------------- 2 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 lib/a.lua diff --git a/lib/a.lua b/lib/a.lua new file mode 100644 index 0000000..68b844c --- /dev/null +++ b/lib/a.lua @@ -0,0 +1,5 @@ +local test = { + ez = "godo" +} + + test.ez = "good" \ No newline at end of file diff --git a/lib/init.lua b/lib/init.lua index 29396a5..93e5021 100644 --- a/lib/init.lua +++ b/lib/init.lua @@ -143,14 +143,14 @@ local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archet end end -local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype +local function archetypeOf(world: any, types: {i24}, prev: Archetype?): Archetype local ty = hash(types) local id = world.nextArchetypeId + 1 world.nextArchetypeId = id local length = #types - local columns = table.create(length) :: {any} + local columns = table.create(length) for index in types do columns[index] = {} @@ -174,51 +174,6 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet return archetype end -local World = {} -World.__index = World -function World.new() - local self = setmetatable({ - archetypeIndex = {}; - archetypes = {}; - componentIndex = {}; - entityIndex = {}; - hooks = { - [ON_ADD] = {}; - }; - nextArchetypeId = 0; - nextComponentId = 0; - nextEntityId = 0; - ROOT_ARCHETYPE = (nil :: any) :: Archetype; - }, World) - self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil) - return self -end - -local function emit(world, eventDescription) - local event = eventDescription.event - - table.insert(world.hooks[event], { - archetype = eventDescription.archetype; - ids = eventDescription.ids; - offset = eventDescription.offset; - otherArchetype = eventDescription.otherArchetype; - }) -end - -local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty) - if #added > 0 then - emit(world, { - archetype = archetype; - event = ON_ADD; - ids = added; - offset = row; - otherArchetype = otherArchetype; - }) - end -end - -export type World = typeof(World.new()) - local function ensureArchetype(world: World, types, prev) if #types < 1 then return world.ROOT_ARCHETYPE @@ -306,6 +261,51 @@ local function ensureRecord(world, entityId: i53): Record return record end +local World = {} +World.__index = World +function World.new() + local self = setmetatable({ + archetypeIndex = {}; + archetypes = {}; + componentIndex = {}; + entityIndex = {}; + hooks = { + [ON_ADD] = {}; + }; + nextArchetypeId = 0; + nextComponentId = 0; + nextEntityId = 0; + ROOT_ARCHETYPE = (nil :: any) :: Archetype; + }, World) + self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil) + return self +end + +local function emit(world, eventDescription) + local event = eventDescription.event + + table.insert(world.hooks[event], { + archetype = eventDescription.archetype; + ids = eventDescription.ids; + offset = eventDescription.offset; + otherArchetype = eventDescription.otherArchetype; + }) +end + +local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty) + if #added > 0 then + emit(world, { + archetype = archetype; + event = ON_ADD; + ids = added; + offset = row; + otherArchetype = otherArchetype; + }) + end +end + +export type World = typeof(World.new()) + function World.add(world: World, entityId: i53, componentId: i53) local record = ensureRecord(world, entityId)