jecs/addons/observers.luau

321 lines
7.2 KiB
Text
Raw Normal View History

2025-07-06 15:07:40 +00:00
--!strict
2025-03-30 19:31:18 +00:00
local jecs = require("@jecs")
2025-04-10 17:52:07 +00:00
export type PatchedWorld = jecs.World & {
2025-07-06 15:07:40 +00:00
added: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T?) -> ()) -> () -> (),
2025-05-19 22:33:16 +00:00
removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
2025-07-06 15:07:40 +00:00
changed: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T) -> ()) -> () -> (),
2025-07-05 19:26:05 +00:00
observer: <T...>(
2025-06-07 00:11:26 +00:00
PatchedWorld,
2025-07-05 19:26:05 +00:00
jecs.Query<T...>,
2025-07-05 19:26:05 +00:00
(<a>(jecs.Entity, jecs.Id<a>, a) -> ())?
2025-07-05 19:26:05 +00:00
) -> () -> (jecs.Entity),
2025-06-07 00:11:26 +00:00
monitor: (
PatchedWorld,
any,
2025-07-06 07:48:11 +00:00
(<a>(jecs.Entity, jecs.Id<a>) -> ())?
2025-06-07 00:11:26 +00:00
) -> ()
2025-04-10 17:52:07 +00:00
}
2025-03-30 19:31:18 +00:00
2025-07-06 15:07:40 +00:00
local function observers_new(
world: PatchedWorld,
query: any,
callback: (<T, a>(jecs.Entity<T>, jecs.Id<a>, value: a?) -> ())?
)
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
2025-03-30 19:31:18 +00:00
end
2025-04-10 17:52:07 +00:00
local entity_index = world.entity_index :: any
2025-07-05 19:26:05 +00:00
local i = 0
local entities = {}
2025-03-30 19:31:18 +00:00
2025-07-06 15:07:40 +00:00
local function emplaced<T, a>(
entity: jecs.Entity<T>,
id: jecs.Id<a>,
value: a?
)
local r = jecs.entity_index_try_get_fast(
entity_index, entity :: any) :: jecs.Record
2025-04-04 22:41:38 +00:00
2025-03-30 19:31:18 +00:00
local archetype = r.archetype
2025-07-06 15:07:40 +00:00
if archetypes[archetype.id] then
2025-07-05 19:26:05 +00:00
i += 1
entities[i] = entity
2025-07-05 19:26:05 +00:00
if callback ~= nil then
callback(entity, id, value)
end
2025-03-30 19:31:18 +00:00
end
end
for _, term in terms do
2025-03-30 20:14:22 +00:00
world:added(term, emplaced)
world:changed(term, emplaced)
2025-03-30 19:31:18 +00:00
end
2025-07-05 19:26:05 +00:00
2025-07-06 07:52:06 +00:00
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
2025-03-30 19:31:18 +00:00
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
2025-05-07 17:21:12 +00:00
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
2025-06-07 00:11:26 +00:00
local function monitors_new(world, query, callback)
2025-07-06 15:07:40 +00:00
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
2025-04-10 17:52:07 +00:00
end
2025-03-30 19:31:18 +00:00
2025-04-10 17:52:07 +00:00
local entity_index = world.entity_index :: any
2025-07-06 07:48:11 +00:00
local i = 0
local entities = {}
2025-03-30 19:31:18 +00:00
2025-07-06 15:07:40 +00:00
local function emplaced<T, a>(
entity: jecs.Entity<T>,
id: jecs.Id<a>,
value: a?
)
local r = jecs.entity_index_try_get_fast(
entity_index, entity :: any) :: jecs.Record
2025-04-04 22:41:38 +00:00
2025-03-30 19:31:18 +00:00
local archetype = r.archetype
2025-07-06 15:07:40 +00:00
if archetypes[archetype.id] then
2025-07-06 07:48:11 +00:00
i += 1
entities[i] = entity
if callback ~= nil then
callback(entity, id, value)
end
2025-03-30 19:31:18 +00:00
end
end
2025-04-10 17:52:07 +00:00
local function removed(entity: jecs.Entity, component: jecs.Id)
2025-07-05 19:26:05 +00:00
local EcsOnRemove = jecs.OnRemove :: jecs.Id
2025-07-06 07:48:11 +00:00
if callback ~= nil then
callback(entity, EcsOnRemove)
end
2025-03-30 19:31:18 +00:00
end
for _, term in terms do
world:added(term, emplaced)
2025-04-10 17:52:07 +00:00
world:removed(term, removed)
2025-03-30 19:31:18 +00:00
end
2025-07-06 07:52:06 +00:00
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
2025-03-30 19:31:18 +00:00
end
2025-05-07 17:21:12 +00:00
local function observers_add(world: jecs.World): PatchedWorld
type Signal = { [jecs.Entity]: { (...any) -> () } }
2025-05-07 17:21:12 +00:00
local world_mut = world :: jecs.World & {[string]: any}
2025-03-30 19:31:18 +00:00
local signals = {
added = {} :: Signal,
emplaced = {} :: Signal,
removed = {} :: Signal
2025-03-30 19:31:18 +00:00
}
2025-04-10 17:52:07 +00:00
2025-05-07 17:21:12 +00:00
world_mut.added = function<T>(
_: jecs.World,
component: jecs.Id<T>,
fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
)
2025-03-30 19:31:18 +00:00
local listeners = signals.added[component]
if not listeners then
listeners = {}
signals.added[component] = listeners
2025-06-07 00:11:26 +00:00
local function on_add(entity, id, value)
2025-04-22 02:49:52 +00:00
for _, listener in listeners :: any do
listener(entity, id, value)
2025-03-30 19:31:18 +00:00
end
end
2025-05-19 22:33:16 +00:00
local existing_hook = world:get(component, jecs.OnAdd)
if existing_hook then
table.insert(listeners, existing_hook)
2025-05-07 17:21:12 +00:00
end
2025-05-19 22:33:16 +00:00
2025-06-07 00:11:26 +00:00
local idr = world.component_index[component]
if idr then
idr.on_add = on_add
2025-06-07 00:11:26 +00:00
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
2025-03-30 19:31:18 +00:00
end
2025-05-07 17:21:12 +00:00
world_mut.changed = function<T>(
_: jecs.World,
component: jecs.Id<T>,
fn: (e: jecs.Entity, id: jecs.Id, value: T) -> ()
)
2025-03-30 19:31:18 +00:00
local listeners = signals.emplaced[component]
if not listeners then
listeners = {}
signals.emplaced[component] = listeners
2025-06-07 00:11:26 +00:00
local function on_change(entity, id, value: any)
2025-04-22 02:49:52 +00:00
for _, listener in listeners :: any do
listener(entity, id, value)
2025-03-30 19:31:18 +00:00
end
end
2025-05-19 22:33:16 +00:00
local existing_hook = world:get(component, jecs.OnChange)
if existing_hook then
table.insert(listeners, existing_hook)
2025-05-07 17:21:12 +00:00
end
2025-06-07 00:11:26 +00:00
local idr = world.component_index[component]
if idr then
idr.on_change = on_change
2025-06-07 00:11:26 +00:00
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
2025-03-30 19:31:18 +00:00
end
end
2025-05-07 17:21:12 +00:00
world_mut.removed = function<T>(
_: jecs.World,
component: jecs.Id<T>,
fn: (e: jecs.Entity, id: jecs.Id) -> ()
)
2025-03-30 19:31:18 +00:00
local listeners = signals.removed[component]
if not listeners then
listeners = {}
signals.removed[component] = listeners
2025-06-07 00:11:26 +00:00
local function on_remove(entity, id)
2025-04-22 02:49:52 +00:00
for _, listener in listeners :: any do
2025-06-07 00:11:26 +00:00
listener(entity, id)
2025-03-30 19:31:18 +00:00
end
end
2025-05-19 22:33:16 +00:00
local existing_hook = world:get(component, jecs.OnRemove)
if existing_hook then
table.insert(listeners, existing_hook)
2025-05-07 17:21:12 +00:00
end
2025-05-19 22:33:16 +00:00
2025-06-07 00:11:26 +00:00
local idr = world.component_index[component]
if idr then
idr.on_remove = on_remove
2025-06-07 00:11:26 +00:00
else
world:set(component, jecs.OnRemove, on_remove)
end
2025-03-30 19:31:18 +00:00
end
2025-06-07 00:11:26 +00:00
table.insert(listeners, fn)
2025-06-07 00:11:26 +00:00
return function()
local n = #listeners
local i = table.find(listeners, fn)
listeners[i] = listeners[n]
listeners[n] = nil
end
2025-03-30 19:31:18 +00:00
end
2025-05-07 17:21:12 +00:00
world_mut.signals = signals
2025-03-30 19:31:18 +00:00
2025-05-07 17:21:12 +00:00
world_mut.observer = observers_new
2025-04-10 17:52:07 +00:00
2025-05-07 17:21:12 +00:00
world_mut.monitor = monitors_new
2025-04-10 17:52:07 +00:00
2025-05-07 17:21:12 +00:00
world_mut.trackers = {}
2025-05-07 17:21:12 +00:00
return world_mut :: PatchedWorld
2025-03-30 19:31:18 +00:00
end
2025-04-10 19:10:42 +00:00
return observers_add