Compare commits

..

No commits in common. "ac4441eb84baa449c4936a318bc9d4d07f579069" and "8f9530987145c1f42b75fa2f7024587df6d0ad0c" have entirely different histories.

6 changed files with 70 additions and 207 deletions

View file

@ -355,8 +355,45 @@ This operation is the same as calling:
world:target(entity, jecs.ChildOf, 0) world:target(entity, jecs.ChildOf, 0)
``` ```
::: ## contains
Checks if an entity or component (id) exists in the world.
```luau
function World:contains(
entity: Entity,
): boolean
```
Example:
::: code-group
```luau [luau]
local entity = world:entity()
print(world:contains(entity))
print(world:contains(1))
print(world:contains(2))
-- Outputs:
-- true
-- true
-- false
```
```ts [typescript]
const entity = world.entity();
print(world.contains(entity));
print(world.contains(1));
print(world.contains(2));
// Outputs:
// true
// true
// false
```
:::
## remove ## remove
@ -423,11 +460,11 @@ Example:
```luau [luau] ```luau [luau]
local entity = world:entity() local entity = world:entity()
print(world:contains(entity)) print(world:has(entity))
world:delete(entity) world:delete(entity)
print(world:contains(entity)) print(world:has(entity))
-- Outputs: -- Outputs:
-- true -- true
@ -564,7 +601,7 @@ print(retrievedParent === parent) // true
## contains ## contains
Checks if an entity or component (id) exists and is alive in the world. Checks if an entity exists and is alive in the world.
```luau ```luau
function World:contains( function World:contains(

45
jecs.d.ts vendored
View file

@ -43,18 +43,18 @@ type InferComponents<A extends Id[]> = { [K in keyof A]: InferComponent<A[K]> };
type ArchetypeId = number; type ArchetypeId = number;
export type Column<T> = T[]; export type Column<T> = T[];
export type Archetype<T extends Id[]> = { export type Archetype<T extends unknown[]> = {
id: number; id: number;
types: number[]; types: number[];
type: string; type: string;
entities: number[]; entities: number[];
columns: Column<unknown>[]; columns: Column<unknown>[];
columns_map: { [K in T[number]]: Column<InferComponent<K>> }; columns_map: Record<Id, Column<T[number]>>
}; };
type Iter<T extends Id[]> = IterableFunction<LuaTuple<[Entity, ...InferComponents<T>]>>; type Iter<T extends unknown[]> = IterableFunction<LuaTuple<[Entity, ...T]>>;
export type CachedQuery<T extends Id[]> = { export type CachedQuery<T extends unknown[]> = {
/** /**
* Returns an iterator that produces a tuple of [Entity, ...queriedComponents]. * Returns an iterator that produces a tuple of [Entity, ...queriedComponents].
*/ */
@ -67,7 +67,7 @@ export type CachedQuery<T extends Id[]> = {
archetypes(): Archetype<T>[]; archetypes(): Archetype<T>[];
} & Iter<T>; } & Iter<T>;
export type Query<T extends Id[]> = { export type Query<T extends unknown[]> = {
/** /**
* Returns an iterator that produces a tuple of [Entity, ...queriedComponents]. * Returns an iterator that produces a tuple of [Entity, ...queriedComponents].
*/ */
@ -246,11 +246,11 @@ export class World {
* @param components The list of components to query. * @param components The list of components to query.
* @returns A Query object to iterate over results. * @returns A Query object to iterate over results.
*/ */
query<T extends Id[]>(...components: T): Query<T>; query<T extends Id[]>(...components: T): Query<InferComponents<T>>;
added<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void; added<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
changed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void; changed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>, value: T) => void): () => void
removed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>) => void): () => void; removed<T>(component: Entity<T>, listener: (e: Entity, id: Id<T>) => void): () => void
} }
export function world(): World; export function world(): World;
@ -297,11 +297,11 @@ export function ECS_PAIR_FIRST(pair: Pair): number;
export function ECS_PAIR_SECOND(pair: Pair): number; export function ECS_PAIR_SECOND(pair: Pair): number;
type StatefulHook = Entity<<T>(e: Entity<T>, id: Id<T>, data: T) => void> & { type StatefulHook = Entity<<T>(e: Entity<T>, id: Id<T>, data: T) => void> & {
readonly __nominal_StatefulHook: unique symbol; readonly __nominal_StatefulHook: unique symbol,
}; }
type StatelessHook = Entity<<T>(e: Entity<T>, id: Id<T>) => void> & { type StatelessHook = Entity<<T>(e: Entity<T>, id: Id<T>) => void> & {
readonly __nominal_StatelessHook: unique symbol; readonly __nominal_StatelessHook: unique symbol,
}; }
export declare const OnAdd: StatefulHook; export declare const OnAdd: StatefulHook;
export declare const OnRemove: StatelessHook; export declare const OnRemove: StatelessHook;
@ -319,17 +319,12 @@ export declare const Exclusive: Tag;
export declare const Rest: Entity; export declare const Rest: Entity;
export type ComponentRecord = { export type ComponentRecord = {
records: Map<Id, number>; records: Map<Id, number>,
counts: Map<Id, number>; counts: Map<Id, number>,
size: number; size: number,
}; }
export function component_record(world: World, id: Id): ComponentRecord; export function component_record(world: World, id: Id): ComponentRecord
export function bulk_insert<const C extends Id[]>( export function bulk_insert<const C extends Id[]>(world: World, entity: Entity, ids: C, values: InferComponents<C>): void
world: World, export function bulk_remove(world: World, entity: Entity, ids: Id[]): void
entity: Entity,
ids: C,
values: InferComponents<C>,
): void;
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void;

View file

@ -2443,9 +2443,10 @@ local function world_new()
if not idr then if not idr then
idr = component_index[wc] idr = component_index[wc]
end end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
end end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local on_remove = idr.on_remove local on_remove = idr.on_remove
@ -2543,9 +2544,10 @@ local function world_new()
if not idr then if not idr then
idr = component_index[wc] idr = component_index[wc]
end end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
end end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local on_remove = idr.on_remove local on_remove = idr.on_remove

View file

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

View file

@ -24,25 +24,6 @@ type Id<T=unknown> = jecs.Id<T>
local entity_visualiser = require("@tools/entity_visualiser") local entity_visualiser = require("@tools/entity_visualiser")
local dwi = entity_visualiser.stringify local dwi = entity_visualiser.stringify
TEST("", function()
local world = jecs.world()
local a = world:entity()
local b = world:entity()
local c = world:entity()
world:add(a, pair(ChildOf, b))
world:add(a, pair(ChildOf, c))
CHECK(not world:has(a, pair(ChildOf, b)))
CHECK(world:has(a, pair(ChildOf, c)))
world:remove(a, pair(ChildOf, c))
CHECK(not world:has(a, pair(ChildOf, b)))
CHECK(not world:has(a, pair(ChildOf, c)))
end)
TEST("ardi", function() TEST("ardi", function()
local world = jecs.world() local world = jecs.world()
local r = world:entity() local r = world:entity()
@ -338,25 +319,7 @@ TEST("repro", function()
end) end)
TEST("world:add()", function() TEST("world:add()", function()
do CASE "Removing exclusive pair should traverse backwards on edge" do CASE "exclusive relations"
local world = jecs.world()
local a = world:entity()
local b = world:entity()
local c = world:entity()
world:add(a, pair(ChildOf, b))
world:add(a, pair(ChildOf, c))
CHECK(not world:has(a, pair(ChildOf, b)))
CHECK(world:has(a, pair(ChildOf, c)))
world:remove(a, pair(ChildOf, c))
CHECK(not world:has(a, pair(ChildOf, b)))
CHECK(not world:has(a, pair(ChildOf, c)))
CHECK(not world:target(a, ChildOf))
end
do CASE "Exclusive relations"
local world = jecs.world() local world = jecs.world()
local A = world:component() local A = world:component()
world:add(A, jecs.Exclusive) world:add(A, jecs.Exclusive)
@ -2081,140 +2044,6 @@ TEST("world:remove()", function()
end) end)
TEST("world:set()", function() TEST("world:set()", function()
do CASE "Removing exclusive pair should traverse backwards on edge"
local world = jecs.world()
local a = world:entity()
local b = world:entity()
local c = world:entity()
local BattleLink = world:component()
world:add(BattleLink, jecs.Exclusive)
world:set(a, pair(BattleLink, b), {
timestamp = 1,
transform = vector.create(1, 2, 3)
})
world:set(a, pair(BattleLink, c), {
timestamp = 2,
transform = vector.create(1, 2, 3)
})
CHECK(not world:has(a, pair(BattleLink, b)))
CHECK(world:has(a, pair(BattleLink, c)))
world:remove(a, pair(BattleLink, c))
CHECK(not world:has(a, pair(BattleLink, b)))
CHECK(not world:has(a, pair(BattleLink, c)))
CHECK(not world:target(a, BattleLink))
end
do CASE "Exclusive relations"
local world = jecs.world()
local A = world:component()
world:add(A, jecs.Exclusive)
local B = world:component()
local C = world:component()
local e = world:entity()
world:set(e, pair(A, B), true)
world:set(e, pair(A, C), true)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
-- We have to test the path that checks the uncached method
local e1 = world:entity()
world:set(e1, pair(A, B), true)
world:set(e1, pair(A, C), true)
CHECK(world:has(e1, pair(A, B)) == false)
CHECK(world:has(e1, pair(A, C)) == true)
end
do CASE "exclusive relations invoke hooks"
local world = jecs.world()
local A = world:component()
local B = world:component()
local C = world:component()
local e_ptr: jecs.Entity = (jecs.Rest :: any) + 1
world:add(A, jecs.Exclusive)
local on_remove_call = false
world:set(A, jecs.OnRemove, function(e, id)
on_remove_call = true
end)
local on_add_call_count = 0
world:set(A, jecs.OnAdd, function(e, id)
on_add_call_count += 1
end)
local e = world:entity()
CHECK(e == e_ptr)
world:set(e, pair(A, B))
CHECK(on_add_call_count == 1)
world:set(e, pair(A, C))
CHECK(on_add_call_count == 2)
CHECK(on_remove_call)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
-- We have to ensure that it actually invokes hooks everytime it
-- traverses the archetype
e = world:entity()
world:add(e, pair(A, B))
CHECK(on_add_call_count == 3)
world:add(e, pair(A, C))
CHECK(on_add_call_count == 4)
CHECK(on_remove_call)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
end
do CASE "exclusive relations invoke on_remove hooks that should allow side effects"
local world = jecs.world()
local A = world:component()
local B = world:component()
local C = world:component()
local D = world:component()
world:add(A, jecs.Exclusive)
local call_count = 0
world:set(A, jecs.OnRemove, function(e, id)
call_count += 1
if call_count == 1 then
world:set(e, C, true)
else
world:set(e, D, true)
end
end)
local e = world:entity()
world:set(e, pair(A, B), true)
world:set(e, pair(A, C), true)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
CHECK(world:has(e, C))
-- We have to ensure that it actually invokes hooks everytime it
-- traverses the archetype
e = world:entity()
world:set(e, pair(A, B), true)
world:set(e, pair(A, C), true)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
CHECK(world:has(e, D))
end
do CASE "archetype move" do CASE "archetype move"
local world = jecs.world() local world = jecs.world()

View file

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