From 46d363ad5fdb447360c2d72bfb5a1007bf2358f1 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 9 Jun 2025 22:18:59 +0200 Subject: [PATCH 1/9] Initial commit with union --- jecs.luau | 12 ++++++------ test/addons/observers.luau | 11 +++++++++++ test/tests.luau | 14 +++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/jecs.luau b/jecs.luau index e5faf96..dc60a9a 100644 --- a/jecs.luau +++ b/jecs.luau @@ -2525,8 +2525,8 @@ end World.new = world_new -export type Entity = { __T: T } -export type Id = { __T: T } +export type Entity = number | { __T: T } +export type Id = number | { __T: T } export type Pair = Id

type ecs_id_t = Id | Pair | Pair<"Tag", T> export type Item = (self: Query) -> (Entity, T...) @@ -2601,10 +2601,10 @@ export type World = { & ((World, Entity, Id, Id, Id, Id) -> (a?, b?, c?, d?)), --- Returns whether the entity has the ID. - has: ((World, Entity, Id) -> boolean) - & ((World, Entity, Id, Id) -> boolean) - & ((World, Entity, Id, Id, Id) -> boolean) - & (World, Entity, Id, Id, Id, Id) -> boolean, + has: ((World, Entity, Id) -> boolean) + & ((World, Entity, Id, Id) -> boolean) + & ((World, Entity, Id, Id, Id) -> boolean) + & (World, Entity, Id, Id, Id, Id) -> boolean, --- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil. parent: (self: World, entity: Entity) -> Entity, diff --git a/test/addons/observers.luau b/test/addons/observers.luau index 0a363cc..182a3ea 100644 --- a/test/addons/observers.luau +++ b/test/addons/observers.luau @@ -7,6 +7,17 @@ local observers_add = require("@addons/observers") TEST("addons/observers", function() local world = observers_add(jecs.world()) + local Test = world:component() :: jecs.Id + type Q = () -> (jecs.Entity, T...) + local query: + ((jecs.World, jecs.Id) -> Q) + & ((jecs.World, jecs.Id, jecs.Id) -> Q) + & ((jecs.World, jecs.Id, jecs.Id, jecs.Id) -> Q) + = 1 :: never + for e, a in query(world, Test) do + + end + do CASE "Should work even if set after the component has been used" local A = world:component() diff --git a/test/tests.luau b/test/tests.luau index 9ce5023..d5e578f 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -661,7 +661,7 @@ TEST("world:range()", function() do CASE "under range start" local world = jecs.world() world:range(400, 1000) - local id = world:entity() + local id = world:entity() :: number local e = world:entity(id + 5) CHECK(e == id + 5) CHECK(world:contains(e)) @@ -669,7 +669,7 @@ TEST("world:range()", function() CHECK(world:contains(e2)) world:delete(e2) CHECK(not world:contains(e2)) - local e2v1 = world:entity(399) + local e2v1 = world:entity(399) :: number CHECK(world:contains(e2v1)) CHECK(ECS_ID(e2v1) == 399) CHECK(ECS_GENERATION(e2v1) == 0) @@ -682,13 +682,13 @@ TEST("world:range()", function() CHECK(world:contains(e2)) world:delete(e2) CHECK(not world:contains(e2)) - local e2v1 = world:entity(405) + local e2v1 = world:entity(405) :: number CHECK(world:contains(e2v1)) CHECK(ECS_ID(e2v1) == 405) CHECK(ECS_GENERATION(e2v1) == 0) world:delete(e2v1) - local e2v2 = world:entity(e2v1) + local e2v2 = world:entity(e2v1) :: number CHECK(ECS_ID(e2v2) == 405) CHECK(ECS_GENERATION(e2v2) == 0) end @@ -697,7 +697,7 @@ end) TEST("world:entity()", function() do CASE "desired id" local world = jecs.world() - local id = world:entity() + local id = world:entity() :: number local e = world:entity(id + 5) CHECK(e == id + 5) CHECK(world:contains(e)) @@ -767,11 +767,11 @@ TEST("world:entity()", function() local e = world:entity() world:delete(e) end - local e = world:entity() :: any + local e = world:entity() :: number CHECK(ECS_ID(e) == pin) CHECK(ECS_GENERATION(e) == 2^16-1) world:delete(e) - e = world:entity() + e = world:entity() :: number CHECK(ECS_ID(e) == pin) CHECK(ECS_GENERATION(e) == 0) end From 0fea5a259d36529c02e34abe338971ec73aadda6 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Mon, 9 Jun 2025 22:18:59 +0200 Subject: [PATCH 2/9] Union entity types with number --- jecs.luau | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/jecs.luau b/jecs.luau index dc60a9a..0033e34 100644 --- a/jecs.luau +++ b/jecs.luau @@ -1,3 +1,4 @@ + --!optimize 2 --!native --!strict @@ -825,7 +826,7 @@ local function world_entity(world: ecs_world_t, entity: i53?): i53 return entity end end - return entity_index_new_id(entity_index, entity) + return entity_index_new_id(entity_index) end local function world_parent(world: ecs_world_t, entity: i53) @@ -1051,7 +1052,7 @@ local function world_remove(world: ecs_world_t, entity: i53, id: i53) end end -local function archetype_fast_delete_last(columns: { Column }, column_count: number, types: { i53 }, entity: i53) +local function archetype_fast_delete_last(columns: { Column }, column_count: number) for i, column in columns do if column ~= NULL_ARRAY then column[column_count] = nil @@ -1059,7 +1060,7 @@ local function archetype_fast_delete_last(columns: { Column }, column_count: num end end -local function archetype_fast_delete(columns: { Column }, column_count: number, row, types, entity) +local function archetype_fast_delete(columns: { Column }, column_count: number, row: number) for i, column in columns do if column ~= NULL_ARRAY then column[row] = column[column_count] @@ -1101,9 +1102,9 @@ local function archetype_delete(world: ecs_world_t, archetype: ecs_archetype_t, entities[last] = nil :: any if row == last then - archetype_fast_delete_last(columns, column_count, id_types, delete) + archetype_fast_delete_last(columns, column_count) else - archetype_fast_delete(columns, column_count, row, id_types, delete) + archetype_fast_delete(columns, column_count, row) end end @@ -1409,8 +1410,8 @@ local function world_delete(world: ecs_world_t, entity: i53) local tr = idr_r_archetype.records[rel] local tr_count = idr_r_archetype.counts[rel] local types = idr_r_archetype.types - for i = tr, tr_count do - ids[types[tr]] = true + for i = tr, tr + tr_count - 1 do + ids[types[i]] = true end local n = #entities table.move(entities, 1, n, count + 1, children) From 3174e8d80b31551da4554518c6685c5916cd766f Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 10 Jun 2025 16:00:41 +0200 Subject: [PATCH 3/9] Handle removal of (*, R) pairs --- CHANGELOG.md | 8 ++++++++ jecs.luau | 2 +- test/tests.luau | 21 +++++++++++++++++++++ wally.toml | 2 +- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12cd1ad..28a4d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 0.6.1 + +### Changed +- Entity types now unions with numbers should allow for easier time casting while not causing regressing previous behaviours + +### Fixed +- Fixed a critical bug with `(*, R)` pairs not being removed when `R` is deleted + ## 0.6.0 ### Added diff --git a/jecs.luau b/jecs.luau index 0033e34..416bc1f 100644 --- a/jecs.luau +++ b/jecs.luau @@ -1413,11 +1413,11 @@ local function world_delete(world: ecs_world_t, entity: i53) for i = tr, tr + tr_count - 1 do ids[types[i]] = true end + local n = #entities table.move(entities, 1, n, count + 1, children) count += n end - for _, child in children do for id in ids do world_remove(world, child, id) diff --git a/test/tests.luau b/test/tests.luau index d5e578f..eefe422 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -289,6 +289,27 @@ TEST("world:contains()", function() end) TEST("world:delete()", function() + do CASE "remove (*, R) pairs when relationship is invalidated" + print("-------") + local world = jecs.world() + local e1 = world:entity() + local e2 = world:entity() + + local A = world:component() + local B = world:component() + local C = world:component() + + world:add(e1, pair(e2, A)) + world:add(e1, B) -- Some stable component that should not be removed in the process + world:add(e1, pair(e2, C)) + world:delete(e2) + + CHECK(not world:contains(e2)) + CHECK(not world:has(e1, pair(e2, A))) + CHECK(world:has(e1, B)) + CHECK(not world:has(e1, pair(e2, C))) + CHECK(world:contains(e1)) + end do CASE "remove pair when relationship is deleted" local world = jecs.world() local e1 = world:entity() diff --git a/wally.toml b/wally.toml index 24905d0..c4480ea 100644 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "ukendio/jecs" -version = "0.6.0" +version = "0.6.1" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" license = "MIT" From 90c963c55de43884035867c2d94e19ccc311fd8d Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 10 Jun 2025 16:16:04 +0200 Subject: [PATCH 4/9] 0.6.1 From 6f6cc3b5149bd8f1b2f46b1a48a34735f2cca9e4 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 10 Jun 2025 16:33:48 +0200 Subject: [PATCH 5/9] Fix overloads --- jecs.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jecs.d.ts b/jecs.d.ts index 4ffb93b..015afc3 100644 --- a/jecs.d.ts +++ b/jecs.d.ts @@ -159,14 +159,13 @@ export class World { */ set(component: Entity, hook: StatefulHook, value: (e: Entity, id: Id, data: T) => void): void; set(component: Entity, hook: StatelessHook, value: (e: Entity, id: Id) => void): void; - /** * Assigns a value to a component on the given entity. * @param entity The target entity. * @param component The component definition (could be a Pair or Entity). * @param value The value to store with that component. */ - set>(entity: Entity, component: E, value: InferComponent): void; + set(entity: Entity, component: Id, value: T): void; /** * Cleans up the world by removing empty archetypes and rebuilding the archetype collections. From 582a1c25ba12d6186b51b42302d95919c37a5707 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Tue, 10 Jun 2025 19:42:52 +0200 Subject: [PATCH 6/9] Bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3cf774..b83e52c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rbxts/jecs", - "version": "0.6.0", + "version": "0.6.1", "description": "Stupidly fast Entity Component System", "main": "jecs.luau", "repository": { From 29512d43515d67dd781ab7c89b43b14e0ce6e047 Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 13 Jun 2025 01:30:18 +0200 Subject: [PATCH 7/9] Bump upload-artifact ver --- .github/workflows/release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a423627..efc68f2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: run: rojo build --output build.rbxm default.project.json - name: Upload Build Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build path: build.rbxm @@ -38,7 +38,7 @@ jobs: uses: actions/checkout@v4 - name: Download Jecs Build - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build path: build From 01c12fccaa55f1ea5e7de4085b6297dcd8f7e09f Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 13 Jun 2025 01:32:44 +0200 Subject: [PATCH 8/9] Update release.yaml --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index efc68f2..cd807d1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,7 +2,7 @@ name: release on: push: - tags: ["v*"] + tags: ["v*". "workflow_dispatch"] jobs: build: From 8420d8832b4b1775800372e465bf63c9753fc54f Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 13 Jun 2025 01:33:54 +0200 Subject: [PATCH 9/9] period --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cd807d1..2e4726e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,7 +2,7 @@ name: release on: push: - tags: ["v*". "workflow_dispatch"] + tags: ["v*", "workflow_dispatch"] jobs: build: