diff --git a/addons/ob.luau b/addons/ob.luau new file mode 100755 index 0000000..151738b --- /dev/null +++ b/addons/ob.luau @@ -0,0 +1,211 @@ +--!strict +local jecs = require("@jecs") + +type World = jecs.World +type Query = jecs.Query + +type Id = jecs.Id + +type Entity = jecs.Entity + +export type Iter = (Observer) -> () -> (jecs.Entity, T...) + +export type Observer = typeof(setmetatable( + {} :: { + iter: Iter, + entities: { Entity }, + disconnect: (Observer) -> () + }, + {} :: { + __iter: Iter, + } +)) + +local function observers_new( + query: Query, + callback: ((Entity, Id, value: any?) -> ())? +): Observer + + query:cached() + + local world = (query :: Query & { world: World }).world + callback = callback + + local archetypes = {} + local terms = query.ids + local first = terms[1] + + local observers_on_create = world.observable[jecs.ArchetypeCreate][first] + local observer_on_create = observers_on_create[#observers_on_create] + observer_on_create.callback = function(archetype) + archetypes[archetype.id] = true + end + local observers_on_delete = world.observable[jecs.ArchetypeDelete][first] + local observer_on_delete = observers_on_delete[#observers_on_delete] + observer_on_delete.callback = function(archetype) + archetypes[archetype.id] = nil + end + + local entity_index = world.entity_index :: any + local i = 0 + local entities = {} + + local function emplaced( + entity: jecs.Entity, + id: jecs.Id, + value: a? + ) + local r = entity_index.sparse_array[jecs.ECS_ID(entity)] + + local archetype = r.archetype + + if archetypes[archetype.id] then + i += 1 + entities[i] = entity + if callback ~= nil then + callback(entity, id, value) + end + end + end + + for _, term in terms do + world:added(term, emplaced) + world:changed(term, emplaced) + end + + local function disconnect() + table.remove(observers_on_create, table.find( + observers_on_create, + observer_on_create + )) + + table.remove(observers_on_delete, table.find( + observers_on_delete, + observer_on_delete + )) + end + + local function iter() + local row = i + return function() + if row == 0 then + i = 0 + table.clear(entities) + end + local entity = entities[row] + row -= 1 + return entity + end + end + + local observer = { + disconnect = disconnect, + entities = entities, + __iter = iter, + iter = iter + } + + setmetatable(observer, observer) + + return (observer :: any) :: Observer +end + +local function monitors_new( + query: Query, + callback: ((Entity, Id, value: any?) -> ())? +): Observer + + query:cached() + + local world = (query :: Query & { world: World }).world + + local archetypes = {} + local terms = query.ids + local first = terms[1] + + local observers_on_create = world.observable[jecs.ArchetypeCreate][first] + local observer_on_create = observers_on_create[#observers_on_create] + observer_on_create.callback = function(archetype) + archetypes[archetype.id] = true + end + local observers_on_delete = world.observable[jecs.ArchetypeDelete][first] + local observer_on_delete = observers_on_delete[#observers_on_delete] + observer_on_delete.callback = function(archetype) + archetypes[archetype.id] = nil + end + local entity_index = world.entity_index :: any + local i = 0 + local entities = {} + + local function emplaced( + entity: jecs.Entity, + id: jecs.Id, + value: a? + ) + local r = jecs.entity_index_try_get_fast( + entity_index, entity :: any) :: jecs.Record + + local archetype = r.archetype + + if archetypes[archetype.id] then + i += 1 + entities[i] = entity + if callback ~= nil then + callback(entity, id, value) + end + end + end + + local function removed(entity: jecs.Entity, component: jecs.Id) + local EcsOnRemove = jecs.OnRemove :: jecs.Id + if callback ~= nil then + callback(entity, EcsOnRemove) + end + end + + for _, term in terms do + world:added(term, emplaced) + world:removed(term, removed) + end + + local function disconnect() + table.remove(observers_on_create, table.find( + observers_on_create, + observer_on_create + )) + + table.remove(observers_on_delete, table.find( + observers_on_delete, + observer_on_delete + )) + end + + local function iter() + local row = i + return function() + if row == 0 then + i = 0 + table.clear(entities) + end + local entity = entities[row] + row -= 1 + return entity + end + end + + local observer = { + disconnect = disconnect, + entities = entities, + __iter = iter, + iter = iter + } + + setmetatable(observer, observer) + + return (observer :: any) :: Observer +end + +return { + monitor = monitors_new, + observer = observers_new +} diff --git a/addons/observers.luau b/addons/observers.luau deleted file mode 100755 index 72264ba..0000000 --- a/addons/observers.luau +++ /dev/null @@ -1,338 +0,0 @@ ---!strict -local jecs = require("@jecs") - - -export type Iter = (query: Query) -> () -> (jecs.Entity, T...) - -type Id = jecs.Id - -type Query = typeof(setmetatable( - {} :: { - iter: Iter, - with: ((Query, Id) -> Query) - & ((Query, Id, Id) -> Query) - & ((Query, Id, Id, Id) -> Query) - & ((Query, Id, Id, Id) -> Query) - & ((Query, Id, Id, Id, Id) -> Query), - without: ((Query, Id) -> Query) - & ((Query, Id, Id) -> Query) - & ((Query, Id, Id, Id) -> Query) - & ((Query, Id, Id, Id) -> Query) - & ((Query, Id, Id, Id, Id) -> Query), - archetypes: (self: Query) -> { jecs.Archetype }, - cached: (self: Query) -> Query, - }, - {} :: { - __iter: Iter, - } -)) - -export type PatchedWorld = jecs.World & { - added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T?) -> ()) -> () -> (), - removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (), - changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (), - observer: (PatchedWorld, Query, callback: ((jecs.Entity, jecs.Id, any?) -> ())?) -> () -> () -> (jecs.Entity), - monitor: (PatchedWorld, Query, callback: ((jecs.Entity, jecs.Id, any?) -> ())?) -> () -> () -> (jecs.Entity) -} - -local function observers_new( - world: PatchedWorld, - query: Query, - callback: ((jecs.Entity, jecs.Id, value: a?) -> ())? -) - query = query:cached() - callback = callback - - local archetypes = {} - local terms = query.ids - local first = terms[1] - - local observers_on_create = world.observable[jecs.ArchetypeCreate][first] - observers_on_create[#observers_on_create].callback = function(archetype) - archetypes[archetype.id] = true - end - local observers_on_delete = world.observable[jecs.ArchetypeDelete][first] - observers_on_delete[#observers_on_delete].callback = function(archetype) - archetypes[archetype.id] = nil - end - - local entity_index = world.entity_index :: any - local i = 0 - local entities = {} - - local function emplaced( - entity: jecs.Entity, - id: jecs.Id, - value: a? - ) - local r = entity_index.sparse_array[jecs.ECS_ID(entity)] - - local archetype = r.archetype - - if archetypes[archetype.id] then - i += 1 - entities[i] = entity - if callback ~= nil then - callback(entity, id, value) - end - end - end - - for _, term in terms do - world:added(term, emplaced) - world:changed(term, emplaced) - end - - return function() - local row = i - return function() - if row == 0 then - i = 0 - table.clear(entities) - end - local entity = entities[row] - row -= 1 - return entity - end - end -end - -local function join(world, component) - local sparse_array = {} - local dense_array = {} - local values = {} - local max_id = 0 - - world:added(component, function(entity, id, value) - max_id += 1 - sparse_array[entity] = max_id - dense_array[max_id] = entity - values[max_id] = value - end) - - world:removed(component, function(entity, id) - local e_swap = dense_array[max_id] - local v_swap = values[max_id] - - local dense = sparse_array[entity] - dense_array[dense] = e_swap - values[dense] = v_swap - - sparse_array[entity] = nil - dense_array[max_id] = nil - values[max_id] = nil - max_id -= 1 - end) - - world:changed(component, function(entity, id, value) - values[sparse_array[entity]] = value - end) - - return function() - local i = max_id - return function(): ...any - i -= 1 - if i == 0 then - return nil - end - local e = dense_array[i] - return e, values[i] - end - end -end - -local function monitors_new(world, query, callback) - query = query:cached() - - local archetypes = {} - local terms = query.ids - local first = terms[1] - - local observers_on_create = world.observable[jecs.ArchetypeCreate][first] - observers_on_create[#observers_on_create].callback = function(archetype) - archetypes[archetype.id] = true - end - local observers_on_delete = world.observable[jecs.ArchetypeDelete][first] - observers_on_delete[#observers_on_delete].callback = function(archetype) - archetypes[archetype.id] = nil - end - - local entity_index = world.entity_index :: any - local i = 0 - local entities = {} - - local function emplaced( - entity: jecs.Entity, - id: jecs.Id, - value: a? - ) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) :: jecs.Record - - local archetype = r.archetype - - if archetypes[archetype.id] then - i += 1 - entities[i] = entity - if callback ~= nil then - callback(entity, id, value) - end - end - end - - local function removed(entity: jecs.Entity, component: jecs.Id) - local EcsOnRemove = jecs.OnRemove :: jecs.Id - if callback ~= nil then - callback(entity, EcsOnRemove) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:removed(term, removed) - end - - return function() - local row = i - return function() - if row == 0 then - i = 0 - table.clear(entities) - end - local entity = entities[row] - row -= 1 - return entity - end - end -end - -local function observers_add(world: jecs.World): PatchedWorld - type Signal = { [jecs.Entity]: { (...any) -> () } } - - local world_mut = world :: jecs.World & {[string]: any} - - local signals = { - added = {} :: Signal, - emplaced = {} :: Signal, - removed = {} :: Signal - } - - world_mut.added = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () - ) - local listeners = signals.added[component] - if not listeners then - listeners = {} - signals.added[component] = listeners - - local function on_add(entity, id, value) - for _, listener in listeners :: any do - listener(entity, id, value) - end - end - local existing_hook = world:get(component, jecs.OnAdd) - if existing_hook then - table.insert(listeners, existing_hook) - end - - local idr = world.component_index[component] - if idr then - idr.on_add = on_add - else - world:set(component, jecs.OnAdd, on_add) - end - end - table.insert(listeners, fn) - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.changed = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id, value: T) -> () - ) - local listeners = signals.emplaced[component] - if not listeners then - listeners = {} - signals.emplaced[component] = listeners - local function on_change(entity, id, value: any) - for _, listener in listeners :: any do - listener(entity, id, value) - end - end - local existing_hook = world:get(component, jecs.OnChange) - if existing_hook then - table.insert(listeners, existing_hook) - end - local idr = world.component_index[component] - if idr then - idr.on_change = on_change - else - world:set(component, jecs.OnChange, on_change) - end - end - table.insert(listeners, fn) - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.removed = function( - _: jecs.World, - component: jecs.Id, - fn: (e: jecs.Entity, id: jecs.Id) -> () - ) - local listeners = signals.removed[component] - if not listeners then - listeners = {} - signals.removed[component] = listeners - local function on_remove(entity, id) - for _, listener in listeners :: any do - listener(entity, id) - end - end - local existing_hook = world:get(component, jecs.OnRemove) - if existing_hook then - table.insert(listeners, existing_hook) - end - - local idr = world.component_index[component] - if idr then - idr.on_remove = on_remove - else - world:set(component, jecs.OnRemove, on_remove) - end - end - - table.insert(listeners, fn) - - return function() - local n = #listeners - local i = table.find(listeners, fn) - listeners[i] = listeners[n] - listeners[n] = nil - end - end - - world_mut.signals = signals - - world_mut.observer = observers_new - - world_mut.monitor = monitors_new - - world_mut.trackers = {} - - return world_mut :: PatchedWorld -end - -return observers_add diff --git a/demo/src/ReplicatedStorage/observers_add.luau b/demo/src/ReplicatedStorage/observers_add.luau deleted file mode 100755 index c959b88..0000000 --- a/demo/src/ReplicatedStorage/observers_add.luau +++ /dev/null @@ -1,190 +0,0 @@ -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local jecs = require(ReplicatedStorage.ecs) - -type Observer = { - callback: (jecs.Entity) -> (), - query: jecs.Query, -} - -export type PatchedWorld = jecs.World & { - added: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), - removed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id) -> ()) -> (), - changed: (PatchedWorld, jecs.Id, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> (), - -- deleted: (PatchedWorld, () -> ()) -> () -> (), - observer: (PatchedWorld, Observer) -> (), - monitor: (PatchedWorld, Observer) -> (), -} - -local function observers_new(world, description) - local query = description.query - local callback = description.callback - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity: jecs.Entity) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:changed(term, emplaced) - end -end - -local function monitors_new(world, description) - local query = description.query - local callback = description.callback - local terms = query.filter_with :: { jecs.Id } - if not terms then - local ids = query.ids - query.filter_with = ids - terms = ids - end - - local entity_index = world.entity_index :: any - local function emplaced(entity: jecs.Entity) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity, jecs.OnAdd) - end - end - - local function removed(entity: jecs.Entity, component: jecs.Id) - local r = jecs.entity_index_try_get_fast( - entity_index, entity :: any) - - if not r then - return - end - - local archetype = r.archetype - - if jecs.query_match(query, archetype) then - callback(entity, jecs.OnRemove) - end - end - - for _, term in terms do - world:added(term, emplaced) - world:removed(term, removed) - end -end - -local function observers_add(world: jecs.World): PatchedWorld - local signals = { - added = {}, - emplaced = {}, - removed = {}, - deleted = {} - } - - world = world :: jecs.World & {[string]: any} - - world.added = function(_, component, fn) - local listeners = signals.added[component] - if not listeners then - listeners = {} - signals.added[component] = listeners - - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_add(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnAdd, on_add) - idr.hooks.on_add = on_add :: any - idr_r.hooks.on_add = on_add :: any - end - table.insert(listeners, fn) - end - - world.changed = function(_, component, fn) - local listeners = signals.emplaced[component] - if not listeners then - listeners = {} - signals.emplaced[component] = listeners - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_change(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnChange, on_change) - idr.hooks.on_change = on_change :: any - idr_r.hooks.on_change = on_change :: any - end - table.insert(listeners, fn) - end - - world.removed = function(_, component, fn) - local listeners = signals.removed[component] - if not listeners then - listeners = {} - signals.removed[component] = listeners - local idr = jecs.id_record_ensure(world :: any, component :: any) - local rw = jecs.pair(component, jecs.Wildcard) - local idr_r = jecs.id_record_ensure(world :: any, rw :: any) - local function on_remove(entity: number, id: number, value: any) - for _, listener in listeners do - listener(entity, id, value) - end - end - world:set(component, jecs.OnRemove, on_remove) - idr.hooks.on_remove = on_remove :: any - idr_r.hooks.on_remove = on_remove :: any - end - table.insert(listeners, fn) - end - - world.signals = signals - - world.observer = observers_new - - world.monitor = monitors_new - - -- local world_delete = world.delete - - -- world.deleted = function(_, fn) - -- local listeners = signals.deleted - -- table.insert(listeners, fn) - -- end - -- world.delete = function(world, entity) - -- world_delete(world, entity) - -- for _, fn in signals.deleted do - -- fn(entity) - -- end - -- end - - return world :: PatchedWorld -end - -return observers_add diff --git a/demo/src/ServerScriptService/main.server.luau b/demo/src/ServerScriptService/main.server.luau index 833717d..d79675b 100755 --- a/demo/src/ServerScriptService/main.server.luau +++ b/demo/src/ServerScriptService/main.server.luau @@ -2,13 +2,12 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerScriptService = game:GetService("ServerScriptService") local jecs = require(ReplicatedStorage.ecs) local schedule = require(ReplicatedStorage.schedule) -local observers_add = require(ReplicatedStorage.observers_add) local SYSTEM = schedule.SYSTEM local RUN = schedule.RUN require(ReplicatedStorage.components) -local world = observers_add(jecs.world()) +local world = jecs.world() local systems = ServerScriptService.systems diff --git a/docs/index.md b/docs/index.md index a46c274..f80ca32 100755 --- a/docs/index.md +++ b/docs/index.md @@ -15,9 +15,6 @@ hero: - theme: alt text: API References link: /api/jecs.md - - theme: alt - text: Observers - link: /api/observers.md features: - title: Stupidly Fast diff --git a/docs/resources.md b/docs/resources.md index 78fe5de..beb4f24 100755 --- a/docs/resources.md +++ b/docs/resources.md @@ -49,8 +49,8 @@ A tool for inspecting entity lifetimes ### Helpers -#### [jecs_observers](https://github.com/Ukendio/jecs/blob/main/addons/observers.luau) -Observers for queries and signals for components +#### [jecs_ob](https://github.com/Ukendio/jecs/blob/main/addons/ob.luau) +Observers & Monitors for queries ### [hammer](https://github.com/Mark-Marks/hammer) A set of utilities for Jecs diff --git a/jecs.d.ts b/jecs.d.ts index b65835e..965a7bf 100755 --- a/jecs.d.ts +++ b/jecs.d.ts @@ -49,7 +49,7 @@ export type Archetype = { type: string; entities: number[]; columns: Column[]; - columns_map: { [K in keyof T]: Column } + columns_map: Record> }; type Iter = IterableFunction>; @@ -323,4 +323,4 @@ export type ComponentRecord = { export function component_record(world: World, id: Id): ComponentRecord export function bulk_insert(world: World, entity: Entity, ids: C, values: InferComponents): void -export function bulk_remove(world: World, entity: Entity, ids: Id[]): void \ No newline at end of file +export function bulk_remove(world: World, entity: Entity, ids: Id[]): void diff --git a/jecs.luau b/jecs.luau index f50c54a..228f59b 100755 --- a/jecs.luau +++ b/jecs.luau @@ -53,6 +53,8 @@ export type Query = typeof(setmetatable( & ((Query, Id, Id, Id, Id) -> Query), archetypes: (self: Query) -> { Archetype }, cached: (self: Query) -> Query, + ids: { Id }, + -- world: World }, {} :: { __iter: Iter, @@ -66,7 +68,6 @@ export type Observer = { query: QueryInner, } - type archetype = { id: number, types: { i53 }, @@ -129,7 +130,11 @@ type world = { exists: (self: world, entity: i53) -> boolean, each: (self: world, id: i53) -> () -> i53, children: (self: world, id: i53) -> () -> i53, - query: (world, ...i53) -> Query<...any> + query: (world, ...i53) -> Query<...any>, + + added: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (), + changed: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (), + removed: (world, i53, (e: i53, id: i53) -> ()) -> () -> (), } export type World = { @@ -145,6 +150,10 @@ export type World = { observable: Map>, + added: (World, Id, (e: Entity, id: Id, value: T?) -> ()) -> () -> (), + removed: (World, Id, (e: Entity, id: Id) -> ()) -> () -> (), + changed: (World, Id, (e: Entity, id: Id, value: T) -> ()) -> () -> (), + --- Enforce a check on entities to be created within desired range range: (self: World, range_begin: number, range_end: number?) -> (), @@ -2213,6 +2222,14 @@ local function world_new() local observable = {} + type Signal = { [i53]: { Listener } } + + local signals = { + added = {} :: Signal, + changed = {} :: Signal, + removed = {} :: Signal + } + local world = { archetype_edges = archetype_edges, @@ -2226,9 +2243,11 @@ local function world_new() max_component_id = ecs_max_component_id, observable = observable, + signals = signals, } :: world + local ROOT_ARCHETYPE = archetype_create(world, {}, "") world.ROOT_ARCHETYPE = ROOT_ARCHETYPE @@ -2578,6 +2597,108 @@ local function world_new() end end + type Listener = (e: i53, id: i53, value: T?) -> () + + world.added = function(_: world, component: i53, fn: Listener) + local listeners = signals.added[component] + if not listeners then + listeners = {} + signals.added[component] = listeners + + local function on_add(entity, id, value) + for _, listener in listeners :: { Listener } do + listener(entity, id, value) + end + end + local existing_hook = inner_world_get(world, component, EcsOnAdd) :: Listener + if existing_hook then + table.insert(listeners, existing_hook) + end + + local idr = world.component_index[component] + if idr then + idr.on_add = on_add + else + inner_world_set(world, component, EcsOnAdd, on_add) + end + end + table.insert(listeners, fn) + return function() + local n = #listeners + local i = table.find(listeners, fn) + listeners[i] = listeners[n] + listeners[n] = nil + end + end + + world.changed = function( + _: world, + component: i53, + fn: Listener + ) + local listeners = signals.changed[component] + if not listeners then + listeners = {} + signals.changed[component] = listeners + local function on_change(entity, id, value: any) + for _, listener in listeners :: { Listener } do + listener(entity, id, value) + end + end + local existing_hook = inner_world_get(world, component, EcsOnChange) :: Listener + if existing_hook then + table.insert(listeners, existing_hook) + end + + local idr = world.component_index[component] + if idr then + idr.on_change = on_change + else + inner_world_set(world, component, EcsOnChange, on_change) + end + end + table.insert(listeners, fn) + return function() + local n = #listeners + local i = table.find(listeners, fn) + listeners[i] = listeners[n] + listeners[n] = nil + end + end + + world.removed = function(_: world, component: i53, fn: (i53, i53) -> ()) + local listeners = signals.removed[component] + if not listeners then + listeners = {} + signals.removed[component] = listeners + local function on_remove(entity, id) + for _, listener in listeners :: { Listener } do + listener(entity, id) + end + end + local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener + if existing_hook then + table.insert(listeners, existing_hook) + end + + local idr = world.component_index[component] + if idr then + idr.on_remove = on_remove + else + inner_world_set(world, component, EcsOnRemove, on_remove) + end + end + + table.insert(listeners, fn) + + return function() + local n = #listeners + local i = table.find(listeners, fn) + listeners[i] = listeners[n] + listeners[n] = nil + end + end + local function inner_world_has(world: World, entity: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?): boolean @@ -3159,21 +3280,21 @@ return { meta = (ECS_META :: any) :: (id: Entity, id: Id, value: a?) -> Entity, is_tag = (ecs_is_tag :: any) :: (World, Id) -> boolean, - OnAdd = (EcsOnAdd :: any) :: Entity<(entity: Entity, id: Id, data: T) -> ()>, - OnRemove = (EcsOnRemove :: any) :: Entity<(entity: Entity, id: Id) -> ()>, - OnChange = (EcsOnChange :: any) :: Entity<(entity: Entity, id: Id, data: T) -> ()>, + OnAdd = (EcsOnAdd :: any) :: Id<(entity: Entity, id: Id, data: T) -> ()>, + OnRemove = (EcsOnRemove :: any) :: Id<(entity: Entity, id: Id) -> ()>, + OnChange = (EcsOnChange :: any) :: Id<(entity: Entity, id: Id, data: T) -> ()>, ChildOf = (EcsChildOf :: any) :: Entity, Component = (EcsComponent :: any) :: Entity, - Wildcard = (EcsWildcard :: any) :: Entity, - w = (EcsWildcard :: any) :: Entity, + Wildcard = (EcsWildcard :: any) :: Id, + w = (EcsWildcard :: any) :: Id, OnDelete = (EcsOnDelete :: any) :: Entity, OnDeleteTarget = (EcsOnDeleteTarget :: any) :: Entity, Delete = (EcsDelete :: any) :: Entity, Remove = (EcsRemove :: any) :: Entity, - Name = (EcsName :: any) :: Entity, + Name = (EcsName :: any) :: Id, Exclusive = (EcsExclusive :: any) :: Entity, - ArchetypeCreate = EcsOnArchetypeCreate, - ArchetypeDelete = EcsOnArchetypeDelete, + ArchetypeCreate = (EcsOnArchetypeCreate :: any) :: Entity, + ArchetypeDelete = (EcsOnArchetypeDelete :: any) :: Entity, Rest = (EcsRest :: any) :: Entity, pair = ECS_PAIR :: (first: Id

, second: Id) -> Pair, diff --git a/package.json b/package.json index ad92f26..21c2cc4 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rbxts/jecs", - "version": "0.9.0-rc.5", + "version": "0.9.0-rc.6", "description": "Stupidly fast Entity Component System", "main": "jecs.luau", "repository": { diff --git a/test/addons/observers.luau b/test/addons/observers.luau index f18777d..13a59aa 100755 --- a/test/addons/observers.luau +++ b/test/addons/observers.luau @@ -2,42 +2,10 @@ local jecs = require("@jecs") local testkit = require("@testkit") local test = testkit.test() local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK -local observers_add = require("@addons/observers") +local ob = require("@addons/ob") TEST("addons/observers", function() - local world = observers_add(jecs.world()) - - do CASE "Should work even if set after the component has been used" - local A = world:component() - - world:set(world:entity(), A, 2) - local ran = false - world:added(A, function() - ran = true - end) - - local entity = world:entity() - world:set(entity, A, 3) - - CHECK(ran) - end - - do CASE "Should not override hook" - local A = world:component() - - local count = 1 - local function counter() - count += 1 - end - - world:set(A, jecs.OnAdd, counter) - world:added(A, counter) - world:set(world:entity(), A, false) - CHECK(count == (1 + 2)) - world:set(world:entity(), A, false) - - CHECK(count == (1 + (2 * 2))) - end + local world = jecs.world() do CASE "Ensure ordering between signals and observers" local A = world:component() @@ -48,11 +16,15 @@ TEST("addons/observers", function() count += 1 end - world:observer(world:query(A, B), counter) + ob.observer(world:query(A, B), counter) world:added(A, counter) world:added(A, counter) + for _ in world:query(A) do + + end + local e = world:entity() world:add(e, A) CHECK(count == 3) @@ -69,7 +41,7 @@ TEST("addons/observers", function() count += 1 end - world:observer(world:query(A), counter) + ob.observer(world:query(A), counter) local e = world:entity() world:set(e, A, false) @@ -89,7 +61,7 @@ TEST("addons/observers", function() count += 1 end - world:monitor(world:query(A), counter) + ob.monitor(world:query(A), counter) local e = world:entity() world:set(e, A, false) diff --git a/test/tests.luau b/test/tests.luau index ff79788..e7c2bc1 100755 --- a/test/tests.luau +++ b/test/tests.luau @@ -1062,6 +1062,44 @@ TEST("world:each()", function() end end) +TEST("world:added", function() + local world = jecs.world() + + do CASE "Should work even if set after the component has been used" + local A = world:component() + + world:set(world:entity(), A, 2) + local ran = false + world:added(A, function() + ran = true + end) + + local entity = world:entity() + world:set(entity, A, 3) + + CHECK(ran) + end + + do CASE "Should not override hook" + local A = world:component() + + local count = 1 + local function counter() + count += 1 + end + + world:set(A, jecs.OnAdd, counter) + world:added(A, counter) + world:set(world:entity(), A, false) + CHECK(count == (1 + 2)) + world:set(world:entity(), A, false) + + CHECK(count == (1 + (2 * 2))) + end + + +end) + TEST("world:range()", function() do CASE "spawn entity under min range" diff --git a/wally.toml b/wally.toml index 18a80ba..e099f5f 100755 --- a/wally.toml +++ b/wally.toml @@ -1,6 +1,6 @@ [package] name = "ukendio/jecs" -version = "0.9.0-rc.5" +version = "0.9.0-rc.6" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" license = "MIT"