Compare commits

..

2 commits

Author SHA1 Message Date
PepeElToro41
42278ce414
Merge f912866fcb into add9ad3939 2025-08-01 22:44:36 -07:00
Ukendio
add9ad3939 Support setting signal on cached Relation
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-08-02 06:20:53 +02:00
5 changed files with 64 additions and 85 deletions

View file

@ -24,10 +24,10 @@ export type Archetype = {
export type QueryInner = { export type QueryInner = {
compatible_archetypes: { Archetype }, compatible_archetypes: { Archetype },
ids: { i53 }, ids: { Id },
filter_with: { i53 }, filter_with: { Id },
filter_without: { i53 }, filter_without: { Id },
next: () -> (number, ...any), next: () -> (Entity, ...any),
world: World, world: World,
} }
@ -54,6 +54,8 @@ export type Query<T...> = typeof(setmetatable(
archetypes: (self: Query<T...>) -> { Archetype }, archetypes: (self: Query<T...>) -> { Archetype },
cached: (self: Query<T...>) -> Query<T...>, cached: (self: Query<T...>) -> Query<T...>,
ids: { Id<any> }, ids: { Id<any> },
patch: (self: Query<T...>, fn: (T...) -> (T...)) -> (),
view: (self: Query<T...>) -> View<T...>,
-- world: World -- world: World
}, },
{} :: { {} :: {
@ -68,6 +70,12 @@ export type Observer = {
query: QueryInner, query: QueryInner,
} }
export type observer = {
callback: (archetype: archetype) -> (),
query: query,
}
type archetype = { type archetype = {
id: number, id: number,
types: { i53 }, types: { i53 },
@ -508,7 +516,7 @@ local function ecs_pair_second(world: world, e: i53)
return ecs_get_alive(world, obj) return ecs_get_alive(world, obj)
end end
local function query_match(query: QueryInner, archetype: archetype) local function query_match(query: query, archetype: archetype)
local columns_map = archetype.columns_map local columns_map = archetype.columns_map
local with = query.filter_with local with = query.filter_with
@ -530,7 +538,7 @@ local function query_match(query: QueryInner, archetype: archetype)
return true return true
end end
local function find_observers(world: world, event: i53, component: i53): { Observer }? local function find_observers(world: world, event: i53, component: i53): { observer }?
local cache = world.observable[event] local cache = world.observable[event]
if not cache then if not cache then
return nil return nil
@ -2628,7 +2636,7 @@ local function world_new()
table.insert(listeners, existing_hook) table.insert(listeners, existing_hook)
end end
local idr = world.component_index[component] local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
if idr then if idr then
idr.on_add = on_add idr.on_add = on_add
else else
@ -2663,7 +2671,7 @@ local function world_new()
table.insert(listeners, existing_hook) table.insert(listeners, existing_hook)
end end
local idr = world.component_index[component] local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
if idr then if idr then
idr.on_change = on_change idr.on_change = on_change
else else
@ -2694,7 +2702,7 @@ local function world_new()
table.insert(listeners, existing_hook) table.insert(listeners, existing_hook)
end end
local idr = world.component_index[component] local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
if idr then if idr then
idr.on_remove = on_remove idr.on_remove = on_remove
else else

View file

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

View file

@ -1064,40 +1064,77 @@ end)
TEST("world:added", function() TEST("world:added", function()
local world = jecs.world() local world = jecs.world()
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()
world:set(world:entity(), A, 2) world:set(world:entity(), A, 2)
local ran = false local ran = false
world:added(A, function() world:added(A, function()
ran = true ran = true
end) end)
local entity = world:entity() local entity = world:entity()
world:set(entity, A, 3) world:set(entity, A, 3)
CHECK(ran)
end
do CASE "Should work even if set after the pair has been used"
local A = world:component()
local B = world:component()
world:set(world:entity(), A, 2)
world:set(world:entity(), pair(A, B), 2)
world:added(A, function()
ran = true
end)
local entity = world:entity()
world:set(entity, pair(A, B), 3)
CHECK(ran)
end
do CASE "Should allow setting signal after Relation has been used as a component"
local A = world:component()
local B = world:component()
world:add(world:entity(), A)
world:added(A, function()
ran = true
end)
world:add(world:entity(), pair(A, B))
CHECK(ran)
end
do CASE "Should invoke signal for the Relation being set as a key despite a pair with Relation having been cached"
local A = world:component()
local B = world:component()
world:add(world:entity(), pair(A, B))
world:added(A, function()
ran = true
end)
world:add(world:entity(), A)
CHECK(ran) CHECK(ran)
end end
do CASE "Should not override hook" do CASE "Should not override hook"
local A = world:component() local A = world:component()
local count = 1 local count = 1
local function counter() local function counter()
count += 1 count += 1
end end
world:set(A, jecs.OnAdd, counter) world:set(A, jecs.OnAdd, counter)
world:added(A, counter) world:added(A, counter)
world:set(world:entity(), A, false) world:set(world:entity(), A, false)
CHECK(count == (1 + 2)) CHECK(count == (1 + 2))
world:set(world:entity(), A, false) world:set(world:entity(), A, false)
CHECK(count == (1 + (2 * 2))) CHECK(count == (1 + (2 * 2)))
end end
end) end)
TEST("world:range()", function() TEST("world:range()", function()

View file

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

View file

@ -1,66 +0,0 @@
local jecs = require("@jecs")
local world = jecs.world()
local pair = jecs.pair
local IsA = world:entity()
local traits = {
IsA = IsA
}
world:set(IsA, jecs.OnAdd, function(component, id)
local second = jecs.pair_second(world, id)
assert(second ~= component, "circular")
local is_tag = jecs.is_tag(world, second)
world:added(component, function(entity, _, value)
if is_tag then
world:add(entity, second)
else
world:set(entity, second, value)
end
end)
world:removed(component, function(entity)
world:remove(entity, second)
end)
if not is_tag then
world:changed(component, function(entity, _, value)
world:set(entity, second, value)
end)
end
local r = jecs.record(world, second) :: jecs.Record
local archetype = r.archetype
if not archetype then
return
end
local types = archetype.types
for _, id in types do
if jecs.is_tag(world, id) then
world:add(component, id)
else
local metadata = world:get(second, id)
if not world:has(component, id) then
world:set(component, id, metadata)
end
end
end
-- jecs.bulk_insert(world, component, ids, values)
end)
local Witch = world:entity()
local Werewolf = world:entity()
local WereWitch = world:entity()
world:add(WereWitch, pair(IsA, Witch))
world:add(WereWitch, pair(IsA, Werewolf))
local e = world:entity()
world:add(e, WereWitch)
print(world:has(e, pair(IsA, Witch))) -- false
print(world:has(e, Witch)) -- true