mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-23 16:39:17 +00:00
Compare commits
1 commit
c72b6ec682
...
f1e591e535
Author | SHA1 | Date | |
---|---|---|---|
|
f1e591e535 |
6 changed files with 70 additions and 207 deletions
|
@ -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
45
jecs.d.ts
vendored
|
@ -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;
|
|
||||||
|
|
10
jecs.luau
10
jecs.luau
|
@ -2456,9 +2456,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
|
||||||
|
@ -2556,9 +2557,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
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
173
test/tests.luau
173
test/tests.luau
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue