Compare commits

...

8 commits

Author SHA1 Message Date
Clown
cf30621a20
Merge 96bed9bd7e into 582a1c25ba 2025-06-12 11:20:43 -07:00
Ukendio
582a1c25ba Bump
Some checks failed
analysis / Run Luau Analyze (push) Has been cancelled
deploy-docs / build (push) Has been cancelled
publish-npm / publish (push) Has been cancelled
unit-testing / Run Luau Tests (push) Has been cancelled
deploy-docs / Deploy (push) Has been cancelled
2025-06-10 19:42:52 +02:00
Ukendio
6f6cc3b514 Fix overloads 2025-06-10 16:35:09 +02:00
Ukendio
90c963c55d 0.6.1 2025-06-10 16:16:04 +02:00
Ukendio
3174e8d80b Handle removal of (*, R) pairs 2025-06-10 16:16:04 +02:00
Ukendio
0fea5a259d Union entity types with number 2025-06-10 16:10:41 +02:00
Ukendio
46d363ad5f Initial commit with union 2025-06-10 16:10:41 +02:00
YetAnotherClown
96bed9bd7e Empty Commit 2025-02-25 11:28:04 -05:00
7 changed files with 65 additions and 25 deletions

View file

@ -2,6 +2,14 @@
## Unreleased ## 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 ## 0.6.0
### Added ### Added

3
jecs.d.ts vendored
View file

@ -159,14 +159,13 @@ export class World {
*/ */
set<T>(component: Entity<T>, hook: StatefulHook, value: (e: Entity<T>, id: Id<T>, data: T) => void): void; set<T>(component: Entity<T>, hook: StatefulHook, value: (e: Entity<T>, id: Id<T>, data: T) => void): void;
set<T>(component: Entity<T>, hook: StatelessHook, value: (e: Entity<T>, id: Id<T>) => void): void; set<T>(component: Entity<T>, hook: StatelessHook, value: (e: Entity<T>, id: Id<T>) => void): void;
/** /**
* Assigns a value to a component on the given entity. * Assigns a value to a component on the given entity.
* @param entity The target entity. * @param entity The target entity.
* @param component The component definition (could be a Pair or Entity). * @param component The component definition (could be a Pair or Entity).
* @param value The value to store with that component. * @param value The value to store with that component.
*/ */
set<E extends Id<unknown>>(entity: Entity, component: E, value: InferComponent<E>): void; set<T>(entity: Entity, component: Id<T>, value: T): void;
/** /**
* Cleans up the world by removing empty archetypes and rebuilding the archetype collections. * Cleans up the world by removing empty archetypes and rebuilding the archetype collections.

View file

@ -1,3 +1,4 @@
--!optimize 2 --!optimize 2
--!native --!native
--!strict --!strict
@ -825,7 +826,7 @@ local function world_entity(world: ecs_world_t, entity: i53?): i53
return entity return entity
end end
end end
return entity_index_new_id(entity_index, entity) return entity_index_new_id(entity_index)
end end
local function world_parent(world: ecs_world_t, entity: i53) 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
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 for i, column in columns do
if column ~= NULL_ARRAY then if column ~= NULL_ARRAY then
column[column_count] = nil column[column_count] = nil
@ -1059,7 +1060,7 @@ local function archetype_fast_delete_last(columns: { Column }, column_count: num
end end
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 for i, column in columns do
if column ~= NULL_ARRAY then if column ~= NULL_ARRAY then
column[row] = column[column_count] 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 entities[last] = nil :: any
if row == last then if row == last then
archetype_fast_delete_last(columns, column_count, id_types, delete) archetype_fast_delete_last(columns, column_count)
else else
archetype_fast_delete(columns, column_count, row, id_types, delete) archetype_fast_delete(columns, column_count, row)
end end
end end
@ -1409,14 +1410,14 @@ local function world_delete(world: ecs_world_t, entity: i53)
local tr = idr_r_archetype.records[rel] local tr = idr_r_archetype.records[rel]
local tr_count = idr_r_archetype.counts[rel] local tr_count = idr_r_archetype.counts[rel]
local types = idr_r_archetype.types local types = idr_r_archetype.types
for i = tr, tr_count do for i = tr, tr + tr_count - 1 do
ids[types[tr]] = true ids[types[i]] = true
end end
local n = #entities local n = #entities
table.move(entities, 1, n, count + 1, children) table.move(entities, 1, n, count + 1, children)
count += n count += n
end end
for _, child in children do for _, child in children do
for id in ids do for id in ids do
world_remove(world, child, id) world_remove(world, child, id)
@ -2525,8 +2526,8 @@ end
World.new = world_new World.new = world_new
export type Entity<T = any> = { __T: T } export type Entity<T = any> = number | { __T: T }
export type Id<T = any> = { __T: T } export type Id<T = any> = number | { __T: T }
export type Pair<P, O> = Id<P> export type Pair<P, O> = Id<P>
type ecs_id_t<T=unknown> = Id<T> | Pair<T, "Tag"> | Pair<"Tag", T> type ecs_id_t<T=unknown> = Id<T> | Pair<T, "Tag"> | Pair<"Tag", T>
export type Item<T...> = (self: Query<T...>) -> (Entity, T...) export type Item<T...> = (self: Query<T...>) -> (Entity, T...)
@ -2601,10 +2602,10 @@ export type World = {
& (<T, a, b, c, d>(World, Entity<T>, Id<a>, Id<b>, Id<c>, Id<d>) -> (a?, b?, c?, d?)), & (<T, a, b, c, d>(World, Entity<T>, Id<a>, Id<b>, Id<c>, Id<d>) -> (a?, b?, c?, d?)),
--- Returns whether the entity has the ID. --- Returns whether the entity has the ID.
has: (<T>(World, Entity<T>, Id) -> boolean) has: (<T, a>(World, Entity<T>, Id<a>) -> boolean)
& (<T>(World, Entity<T>, Id, Id) -> boolean) & (<T, a, b >(World, Entity<T>, Id<a>, Id<a>) -> boolean)
& (<T>(World, Entity<T>, Id, Id, Id) -> boolean) & (<T, a, b, c>(World, Entity<T>, Id<a>, Id<b>, Id<c>) -> boolean)
& <T>(World, Entity<T>, Id, Id, Id, Id) -> boolean, & <T, a, b, c, d>(World, Entity<T>, Id<a>, Id<b>, Id<c>, Id<d>) -> boolean,
--- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil. --- Get parent (target of ChildOf relationship) for entity. If there is no ChildOf relationship pair, it will return nil.
parent: <T>(self: World, entity: Entity<T>) -> Entity, parent: <T>(self: World, entity: Entity<T>) -> Entity,

View file

@ -1,6 +1,6 @@
{ {
"name": "@rbxts/jecs", "name": "@rbxts/jecs",
"version": "0.6.0", "version": "0.6.1",
"description": "Stupidly fast Entity Component System", "description": "Stupidly fast Entity Component System",
"main": "jecs.luau", "main": "jecs.luau",
"repository": { "repository": {

View file

@ -7,6 +7,17 @@ local observers_add = require("@addons/observers")
TEST("addons/observers", function() TEST("addons/observers", function()
local world = observers_add(jecs.world()) local world = observers_add(jecs.world())
local Test = world:component() :: jecs.Id<number>
type Q<T...> = () -> (jecs.Entity, T...)
local query:
(<A>(jecs.World, jecs.Id<A>) -> Q<A>)
& (<A, B>(jecs.World, jecs.Id<A>, jecs.Id<B>) -> Q<A, B>)
& (<A, B, C>(jecs.World, jecs.Id<A>, jecs.Id<B>, jecs.Id<C>) -> Q<A, B, C>)
= 1 :: never
for e, a in query(world, Test) do
end
do CASE "Should work even if set after the component has been used" do CASE "Should work even if set after the component has been used"
local A = world:component() local A = world:component()

View file

@ -289,6 +289,27 @@ TEST("world:contains()", function()
end) end)
TEST("world:delete()", function() 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" do CASE "remove pair when relationship is deleted"
local world = jecs.world() local world = jecs.world()
local e1 = world:entity() local e1 = world:entity()
@ -661,7 +682,7 @@ TEST("world:range()", function()
do CASE "under range start" do CASE "under range start"
local world = jecs.world() local world = jecs.world()
world:range(400, 1000) world:range(400, 1000)
local id = world:entity() local id = world:entity() :: number
local e = world:entity(id + 5) local e = world:entity(id + 5)
CHECK(e == id + 5) CHECK(e == id + 5)
CHECK(world:contains(e)) CHECK(world:contains(e))
@ -669,7 +690,7 @@ TEST("world:range()", function()
CHECK(world:contains(e2)) CHECK(world:contains(e2))
world:delete(e2) world:delete(e2)
CHECK(not world:contains(e2)) CHECK(not world:contains(e2))
local e2v1 = world:entity(399) local e2v1 = world:entity(399) :: number
CHECK(world:contains(e2v1)) CHECK(world:contains(e2v1))
CHECK(ECS_ID(e2v1) == 399) CHECK(ECS_ID(e2v1) == 399)
CHECK(ECS_GENERATION(e2v1) == 0) CHECK(ECS_GENERATION(e2v1) == 0)
@ -682,13 +703,13 @@ TEST("world:range()", function()
CHECK(world:contains(e2)) CHECK(world:contains(e2))
world:delete(e2) world:delete(e2)
CHECK(not world:contains(e2)) CHECK(not world:contains(e2))
local e2v1 = world:entity(405) local e2v1 = world:entity(405) :: number
CHECK(world:contains(e2v1)) CHECK(world:contains(e2v1))
CHECK(ECS_ID(e2v1) == 405) CHECK(ECS_ID(e2v1) == 405)
CHECK(ECS_GENERATION(e2v1) == 0) CHECK(ECS_GENERATION(e2v1) == 0)
world:delete(e2v1) world:delete(e2v1)
local e2v2 = world:entity(e2v1) local e2v2 = world:entity(e2v1) :: number
CHECK(ECS_ID(e2v2) == 405) CHECK(ECS_ID(e2v2) == 405)
CHECK(ECS_GENERATION(e2v2) == 0) CHECK(ECS_GENERATION(e2v2) == 0)
end end
@ -697,7 +718,7 @@ end)
TEST("world:entity()", function() TEST("world:entity()", function()
do CASE "desired id" do CASE "desired id"
local world = jecs.world() local world = jecs.world()
local id = world:entity() local id = world:entity() :: number
local e = world:entity(id + 5) local e = world:entity(id + 5)
CHECK(e == id + 5) CHECK(e == id + 5)
CHECK(world:contains(e)) CHECK(world:contains(e))
@ -767,11 +788,11 @@ TEST("world:entity()", function()
local e = world:entity() local e = world:entity()
world:delete(e) world:delete(e)
end end
local e = world:entity() :: any local e = world:entity() :: number
CHECK(ECS_ID(e) == pin) CHECK(ECS_ID(e) == pin)
CHECK(ECS_GENERATION(e) == 2^16-1) CHECK(ECS_GENERATION(e) == 2^16-1)
world:delete(e) world:delete(e)
e = world:entity() e = world:entity() :: number
CHECK(ECS_ID(e) == pin) CHECK(ECS_ID(e) == pin)
CHECK(ECS_GENERATION(e) == 0) CHECK(ECS_GENERATION(e) == 0)
end end

View file

@ -1,6 +1,6 @@
[package] [package]
name = "ukendio/jecs" name = "ukendio/jecs"
version = "0.6.0" version = "0.6.1"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"