mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-29 19:30:03 +00:00
Compare commits
1 commit
870bd1375c
...
aa507595ca
Author | SHA1 | Date | |
---|---|---|---|
|
aa507595ca |
5 changed files with 436 additions and 232 deletions
|
@ -9,7 +9,7 @@ local function TITLE(title: string)
|
||||||
end
|
end
|
||||||
|
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local mirror = require("@mirror")
|
local mirror = require("../mirror/init")
|
||||||
|
|
||||||
type i53 = number
|
type i53 = number
|
||||||
|
|
||||||
|
|
139
demo/src/ReplicatedStorage/std/changetracker.luau
Normal file
139
demo/src/ReplicatedStorage/std/changetracker.luau
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||||
|
type World = jecs.World
|
||||||
|
|
||||||
|
type Tracker<T> = {
|
||||||
|
track: (
|
||||||
|
world: World,
|
||||||
|
fn: (
|
||||||
|
changes: {
|
||||||
|
added: () -> () -> (number, T),
|
||||||
|
removed: () -> () -> number,
|
||||||
|
changed: () -> () -> (number, T, T),
|
||||||
|
}
|
||||||
|
) -> ()
|
||||||
|
) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity<T = any> = number & { __nominal_type_dont_use: T }
|
||||||
|
|
||||||
|
local function diff(a, b)
|
||||||
|
local size = 0
|
||||||
|
for k, v in a do
|
||||||
|
if b[k] ~= v then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
size += 1
|
||||||
|
end
|
||||||
|
for k, v in b do
|
||||||
|
size -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if size ~= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ChangeTracker<T>(world: World, T: Entity<T>): Tracker<T>
|
||||||
|
local sparse = world.entityIndex.sparse
|
||||||
|
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||||
|
local add = {}
|
||||||
|
local added
|
||||||
|
local removed
|
||||||
|
local is_trivial
|
||||||
|
|
||||||
|
local function changes_added()
|
||||||
|
added = true
|
||||||
|
local q = world:query(T):without(PreviousT):drain()
|
||||||
|
return function()
|
||||||
|
local id, data = q.next()
|
||||||
|
if not id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
is_trivial = typeof(data) ~= "table"
|
||||||
|
|
||||||
|
add[id] = data
|
||||||
|
|
||||||
|
return id, data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function changes_changed()
|
||||||
|
local q = world:query(T, PreviousT):drain()
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local id, new, old = q.next()
|
||||||
|
while true do
|
||||||
|
if not id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not is_trivial then
|
||||||
|
if diff(new, old) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif new ~= old then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
id, new, old = q.next()
|
||||||
|
end
|
||||||
|
|
||||||
|
local record = sparse[id]
|
||||||
|
local archetype = record.archetype
|
||||||
|
local column = archetype.records[PreviousT].column
|
||||||
|
local data = if is_trivial then new else table.clone(new)
|
||||||
|
archetype.columns[column][record.row] = data
|
||||||
|
|
||||||
|
return id, old, new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function changes_removed()
|
||||||
|
removed = true
|
||||||
|
|
||||||
|
local q = world:query(PreviousT):without(T):drain()
|
||||||
|
return function()
|
||||||
|
local id = q.next()
|
||||||
|
if id then
|
||||||
|
world:remove(id, PreviousT)
|
||||||
|
end
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local changes = {
|
||||||
|
added = changes_added,
|
||||||
|
changed = changes_changed,
|
||||||
|
removed = changes_removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function track(fn)
|
||||||
|
added = false
|
||||||
|
removed = false
|
||||||
|
|
||||||
|
fn(changes)
|
||||||
|
|
||||||
|
if not added then
|
||||||
|
for _ in changes_added() do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not removed then
|
||||||
|
for _ in changes_removed() do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for e, data in add do
|
||||||
|
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tracker = { track = track }
|
||||||
|
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
|
||||||
|
return ChangeTracker
|
|
@ -1,5 +1,164 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
|
|
||||||
|
type World = jecs.WorldShim
|
||||||
|
|
||||||
|
type Tracker<T> = {
|
||||||
|
track: (
|
||||||
|
world: World,
|
||||||
|
fn: (
|
||||||
|
changes: {
|
||||||
|
added: () -> () -> (number, T),
|
||||||
|
removed: () -> () -> number,
|
||||||
|
changed: () -> () -> (number, T, T),
|
||||||
|
}
|
||||||
|
) -> ()
|
||||||
|
) -> (),
|
||||||
|
}
|
||||||
|
|
||||||
|
local function diff(a, b)
|
||||||
|
local size = 0
|
||||||
|
for k, v in a do
|
||||||
|
if b[k] ~= v then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
size += 1
|
||||||
|
end
|
||||||
|
for k, v in b do
|
||||||
|
size -= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if size ~= 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
type Entity<T> = number & { __nominal_type_dont_use: T }
|
||||||
|
|
||||||
|
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
||||||
|
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||||
|
local add = {}
|
||||||
|
local added
|
||||||
|
local removed
|
||||||
|
local is_trivial
|
||||||
|
|
||||||
|
local function changes_added()
|
||||||
|
added = true
|
||||||
|
local q = world:query(T):without(PreviousT):drain()
|
||||||
|
return function()
|
||||||
|
local id, data = q.next()
|
||||||
|
if not id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
is_trivial = typeof(data) ~= "table"
|
||||||
|
|
||||||
|
add[id] = data
|
||||||
|
|
||||||
|
return id, data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function changes_changed()
|
||||||
|
local q = world:query(T, PreviousT):drain()
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local id, new, old = q.next()
|
||||||
|
while true do
|
||||||
|
if not id then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not is_trivial then
|
||||||
|
if diff(new, old) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif new ~= old then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
id, new, old = q.next()
|
||||||
|
end
|
||||||
|
|
||||||
|
add[id] = new
|
||||||
|
|
||||||
|
return id, old, new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function changes_removed()
|
||||||
|
removed = true
|
||||||
|
|
||||||
|
local q = world:query(PreviousT):without(T):drain()
|
||||||
|
return function()
|
||||||
|
local id = q.next()
|
||||||
|
if id then
|
||||||
|
world:remove(id, PreviousT)
|
||||||
|
end
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local changes = {
|
||||||
|
added = changes_added,
|
||||||
|
changed = changes_changed,
|
||||||
|
removed = changes_removed,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function track(fn)
|
||||||
|
added = false
|
||||||
|
removed = false
|
||||||
|
|
||||||
|
fn(changes)
|
||||||
|
|
||||||
|
if not added then
|
||||||
|
for _ in changes_added() do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not removed then
|
||||||
|
for _ in changes_removed() do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for e, data in add do
|
||||||
|
world:set(e, PreviousT, if is_trivial then data else table.clone(data))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local tracker = { track = track }
|
||||||
|
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
|
||||||
|
local Vector3
|
||||||
|
do
|
||||||
|
Vector3 = {}
|
||||||
|
Vector3.__index = Vector3
|
||||||
|
|
||||||
|
function Vector3.new(x, y, z)
|
||||||
|
x = x or 0
|
||||||
|
y = y or 0
|
||||||
|
z = z or 0
|
||||||
|
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Vector3.__add(left, right)
|
||||||
|
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Vector3.__mul(left, right)
|
||||||
|
if typeof(right) == "number" then
|
||||||
|
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
|
||||||
|
end
|
||||||
|
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
|
||||||
|
end
|
||||||
|
|
||||||
|
Vector3.one = Vector3.new(1, 1, 1)
|
||||||
|
Vector3.zero = Vector3.new()
|
||||||
|
end
|
||||||
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.World.new()
|
||||||
local Name = world:component()
|
local Name = world:component()
|
||||||
|
|
||||||
|
@ -12,21 +171,10 @@ local function name(e)
|
||||||
return world:get(e, Name)
|
return world:get(e, Name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Position = named(world.component, "Position") :: jecs.Entity<Vector3>
|
local Position = named(world.component, "Position")
|
||||||
local Previous = jecs.Rest
|
|
||||||
local PreviousPosition = jecs.pair(Previous, Position)
|
|
||||||
|
|
||||||
local added = world
|
-- Create the ChangeTracker with the component type to track
|
||||||
:query(Position)
|
local PositionTracker = ChangeTracker(world, Position)
|
||||||
:without(PreviousPosition)
|
|
||||||
:cached()
|
|
||||||
local changed = world
|
|
||||||
:query(Position, PreviousPosition)
|
|
||||||
:cached()
|
|
||||||
local removed = world
|
|
||||||
:query(PreviousPosition)
|
|
||||||
:without(Position)
|
|
||||||
:cached()
|
|
||||||
|
|
||||||
local e1 = named(world.entity, "e1")
|
local e1 = named(world.entity, "e1")
|
||||||
world:set(e1, Position, Vector3.new(10, 20, 30))
|
world:set(e1, Position, Vector3.new(10, 20, 30))
|
||||||
|
@ -34,25 +182,52 @@ world:set(e1, Position, Vector3.new(10, 20, 30))
|
||||||
local e2 = named(world.entity, "e2")
|
local e2 = named(world.entity, "e2")
|
||||||
world:set(e2, Position, Vector3.new(10, 20, 30))
|
world:set(e2, Position, Vector3.new(10, 20, 30))
|
||||||
|
|
||||||
for e, p in added:iter() do
|
PositionTracker.track(function(changes)
|
||||||
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
|
-- You can iterate over different types of changes: Added, Changed, Removed
|
||||||
world:set(e, PreviousPosition, p)
|
|
||||||
end
|
|
||||||
|
|
||||||
world:set(e1, Position, "")
|
-- added queries for every entity with a new Position component
|
||||||
|
for e, p in changes.added() do
|
||||||
for e, new, old in changed:iter() do
|
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
|
||||||
if new ~= old then
|
|
||||||
print(`{name(new)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
|
|
||||||
world:set(e, PreviousPosition, new)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
-- changed queries for entities who's changed their data since
|
||||||
|
-- last was it tracked
|
||||||
|
for _ in changes.changed() do
|
||||||
|
print([[This won't print because it is the first time
|
||||||
|
we are tracking the Position component]])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- removed queries for entities who's removed their Position component
|
||||||
|
-- since last it was tracked
|
||||||
|
for _ in changes.removed() do
|
||||||
|
print([[This won't print because it is the first time
|
||||||
|
we are tracking the Position component]])
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:set(e1, Position, Vector3.new(1, 1, 2) * 999)
|
||||||
|
|
||||||
|
PositionTracker.track(function(changes)
|
||||||
|
for e, p in changes.added() do
|
||||||
|
print([[This won't never print no Position component was added
|
||||||
|
since last time we tracked]])
|
||||||
|
end
|
||||||
|
|
||||||
|
for e, old, new in changes.changed() do
|
||||||
|
print(`{name(e)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If you don't call e.g. changes.removed() then it will automatically drain its iterator and stage their changes.
|
||||||
|
-- This ensures you will not have any off-by-one frame errors.
|
||||||
|
end)
|
||||||
|
|
||||||
world:remove(e2, Position)
|
world:remove(e2, Position)
|
||||||
|
|
||||||
for e in removed:iter() do
|
PositionTracker.track(function(changes)
|
||||||
print(`Position was removed from {name(e)}`)
|
for e in changes.removed() do
|
||||||
end
|
print(`Position was removed from {name(e)}`)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- Output:
|
-- Output:
|
||||||
-- Added 265: {10, 20, 30}
|
-- Added 265: {10, 20, 30}
|
||||||
|
|
234
jecs.luau
234
jecs.luau
|
@ -222,7 +222,7 @@ local function entity_index_is_alive(entity_index: EntityIndex, entity: number)
|
||||||
return entity_index_try_get(entity_index, entity) ~= nil
|
return entity_index_try_get(entity_index, entity) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function entity_index_new_id(entity_index: EntityIndex): i53
|
local function entity_index_new_id(entity_index: EntityIndex, data): i53
|
||||||
local dense_array = entity_index.dense_array
|
local dense_array = entity_index.dense_array
|
||||||
local alive_count = entity_index.alive_count
|
local alive_count = entity_index.alive_count
|
||||||
if alive_count ~= #dense_array then
|
if alive_count ~= #dense_array then
|
||||||
|
@ -764,7 +764,7 @@ local function create_edge_for_remove(world: World, node: Archetype, edge: Graph
|
||||||
return to
|
return to
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetype_traverse_add(world: World, id: i53, from: Archetype): Archetype
|
local function archetype_traverse_add(world: World, id: i53, from: Archetype?): Archetype
|
||||||
from = from or world.ROOT_ARCHETYPE
|
from = from or world.ROOT_ARCHETYPE
|
||||||
local edge = archetype_ensure_edge(world, from.add, id)
|
local edge = archetype_ensure_edge(world, from.add, id)
|
||||||
|
|
||||||
|
@ -1116,7 +1116,7 @@ do
|
||||||
|
|
||||||
local delete = entity
|
local delete = entity
|
||||||
local component_index = world.componentIndex
|
local component_index = world.componentIndex
|
||||||
local archetypes: Archetypes = world.archetypes
|
local archetypes = world.archetypes
|
||||||
local tgt = ECS_PAIR(EcsWildcard, delete)
|
local tgt = ECS_PAIR(EcsWildcard, delete)
|
||||||
local idr_t = component_index[tgt]
|
local idr_t = component_index[tgt]
|
||||||
local idr = component_index[delete]
|
local idr = component_index[delete]
|
||||||
|
@ -1156,7 +1156,6 @@ do
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
|
|
||||||
local idr_t_types = idr_t_archetype.types
|
local idr_t_types = idr_t_archetype.types
|
||||||
local on_remove = idr_t.hooks.on_remove
|
|
||||||
|
|
||||||
for _, child in idr_t_archetype.entities do
|
for _, child in idr_t_archetype.entities do
|
||||||
table.insert(children, child)
|
table.insert(children, child)
|
||||||
|
@ -1178,18 +1177,8 @@ do
|
||||||
end
|
end
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
for _, child in children do
|
||||||
if on_remove then
|
world_remove(world, child, id)
|
||||||
for _, child in children do
|
|
||||||
on_remove(child)
|
|
||||||
local r = entity_index_try_get_fast(entity_index, child) :: Record
|
|
||||||
entity_move(entity_index, child, r, to)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for _, child in children do
|
|
||||||
local r = entity_index_try_get_fast(entity_index, child) :: Record
|
|
||||||
entity_move(entity_index, child, r, to)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1243,15 +1232,6 @@ local EMPTY_QUERY = {
|
||||||
|
|
||||||
setmetatable(EMPTY_QUERY, EMPTY_QUERY)
|
setmetatable(EMPTY_QUERY, EMPTY_QUERY)
|
||||||
|
|
||||||
type QueryInner = {
|
|
||||||
compatible_archetypes: { Archetype },
|
|
||||||
ids: { i53 },
|
|
||||||
filter_with: { i53 },
|
|
||||||
filter_without: { i53 },
|
|
||||||
next: () -> (number, ...any),
|
|
||||||
world: World,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
local world_query_iter_next
|
local world_query_iter_next
|
||||||
|
|
||||||
|
@ -1329,9 +1309,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1355,9 +1332,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1382,9 +1356,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1410,9 +1381,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1440,9 +1408,6 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1582,6 +1547,45 @@ local function query_archetypes(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function query_cached(query: QueryInner)
|
local function query_cached(query: QueryInner)
|
||||||
|
local archetypes = query.compatible_archetypes
|
||||||
|
local world = query.world :: World
|
||||||
|
-- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively
|
||||||
|
-- because the event will be emitted for all components of that Archetype.
|
||||||
|
local first = query.ids[1]
|
||||||
|
local observerable = world.observerable
|
||||||
|
local on_create_action = observerable[EcsOnArchetypeCreate]
|
||||||
|
if not on_create_action then
|
||||||
|
on_create_action = {}
|
||||||
|
observerable[EcsOnArchetypeCreate] = on_create_action
|
||||||
|
end
|
||||||
|
local query_cache_on_create = on_create_action[first]
|
||||||
|
if not query_cache_on_create then
|
||||||
|
query_cache_on_create = {}
|
||||||
|
on_create_action[first] = query_cache_on_create
|
||||||
|
end
|
||||||
|
|
||||||
|
local on_delete_action = observerable[EcsOnArchetypeDelete]
|
||||||
|
if not on_delete_action then
|
||||||
|
on_delete_action = {}
|
||||||
|
observerable[EcsOnArchetypeDelete] = on_delete_action
|
||||||
|
end
|
||||||
|
local query_cache_on_delete = on_delete_action[first]
|
||||||
|
if not query_cache_on_delete then
|
||||||
|
query_cache_on_delete = {}
|
||||||
|
on_delete_action[first] = query_cache_on_delete
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_create_callback(archetype)
|
||||||
|
table.insert(archetypes, archetype)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_delete_callback(archetype)
|
||||||
|
local i = table.find(archetypes, archetype) :: number
|
||||||
|
local n = #archetypes
|
||||||
|
archetypes[i] = archetypes[n]
|
||||||
|
archetypes[n] = nil
|
||||||
|
end
|
||||||
|
|
||||||
local with = query.filter_with
|
local with = query.filter_with
|
||||||
local ids = query.ids
|
local ids = query.ids
|
||||||
if with then
|
if with then
|
||||||
|
@ -1590,6 +1594,12 @@ local function query_cached(query: QueryInner)
|
||||||
query.filter_with = ids
|
query.filter_with = ids
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local observer_for_create = { query = query, callback = on_create_callback }
|
||||||
|
local observer_for_delete = { query = query, callback = on_delete_callback }
|
||||||
|
|
||||||
|
table.insert(query_cache_on_create, observer_for_create)
|
||||||
|
table.insert(query_cache_on_delete, observer_for_delete)
|
||||||
|
|
||||||
local compatible_archetypes = query.compatible_archetypes
|
local compatible_archetypes = query.compatible_archetypes
|
||||||
local lastArchetype = 1
|
local lastArchetype = 1
|
||||||
|
|
||||||
|
@ -1603,50 +1613,6 @@ local function query_cached(query: QueryInner)
|
||||||
local i: number
|
local i: number
|
||||||
local archetype: Archetype
|
local archetype: Archetype
|
||||||
local records: { ArchetypeRecord }
|
local records: { ArchetypeRecord }
|
||||||
local archetypes = query.compatible_archetypes
|
|
||||||
|
|
||||||
local world = query.world :: World
|
|
||||||
-- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively
|
|
||||||
-- because the event will be emitted for all components of that Archetype.
|
|
||||||
local observerable = world.observerable
|
|
||||||
local on_create_action = observerable[EcsOnArchetypeCreate]
|
|
||||||
if not on_create_action then
|
|
||||||
on_create_action = {}
|
|
||||||
observerable[EcsOnArchetypeCreate] = on_create_action
|
|
||||||
end
|
|
||||||
local query_cache_on_create = on_create_action[A]
|
|
||||||
if not query_cache_on_create then
|
|
||||||
query_cache_on_create = {}
|
|
||||||
on_create_action[A] = query_cache_on_create
|
|
||||||
end
|
|
||||||
|
|
||||||
local on_delete_action = observerable[EcsOnArchetypeDelete]
|
|
||||||
if not on_delete_action then
|
|
||||||
on_delete_action = {}
|
|
||||||
observerable[EcsOnArchetypeDelete] = on_delete_action
|
|
||||||
end
|
|
||||||
local query_cache_on_delete = on_delete_action[A]
|
|
||||||
if not query_cache_on_delete then
|
|
||||||
query_cache_on_delete = {}
|
|
||||||
on_delete_action[A] = query_cache_on_delete
|
|
||||||
end
|
|
||||||
|
|
||||||
local function on_create_callback(archetype)
|
|
||||||
table.insert(archetypes, archetype)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function on_delete_callback(archetype)
|
|
||||||
local i = table.find(archetypes, archetype) :: number
|
|
||||||
local n = #archetypes
|
|
||||||
archetypes[i] = archetypes[n]
|
|
||||||
archetypes[n] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local observer_for_create = { query = query, callback = on_create_callback }
|
|
||||||
local observer_for_delete = { query = query, callback = on_delete_callback }
|
|
||||||
|
|
||||||
table.insert(query_cache_on_create, observer_for_create)
|
|
||||||
table.insert(query_cache_on_delete, observer_for_delete)
|
|
||||||
|
|
||||||
local function cached_query_iter()
|
local function cached_query_iter()
|
||||||
lastArchetype = 1
|
lastArchetype = 1
|
||||||
|
@ -1719,9 +1685,6 @@ local function query_cached(query: QueryInner)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1745,9 +1708,6 @@ local function query_cached(query: QueryInner)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1772,12 +1732,9 @@ local function query_cached(query: QueryInner)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
a = columns[records[A].column]
|
a = columns[records[A].column]
|
||||||
b = columns[records[B].column]
|
b = columns[records[B].column]
|
||||||
c = columns[records[C].column]
|
c = columns[records[C].column]
|
||||||
|
@ -1800,9 +1757,6 @@ local function query_cached(query: QueryInner)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1830,9 +1784,6 @@ local function query_cached(query: QueryInner)
|
||||||
|
|
||||||
entities = archetype.entities
|
entities = archetype.entities
|
||||||
i = #entities
|
i = #entities
|
||||||
if i == 0 then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
entityId = entities[i]
|
entityId = entities[i]
|
||||||
columns = archetype.columns
|
columns = archetype.columns
|
||||||
records = archetype.records
|
records = archetype.records
|
||||||
|
@ -1988,7 +1939,7 @@ local function world_each(world: World, id): () -> ()
|
||||||
return function(): any
|
return function(): any
|
||||||
local entity = entities[row]
|
local entity = entities[row]
|
||||||
while not entity do
|
while not entity do
|
||||||
archetype_id = next(idr_cache, archetype_id) :: number
|
archetype_id = next(idr_cache, archetype_id)
|
||||||
if not archetype_id then
|
if not archetype_id then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -2190,37 +2141,35 @@ function World.new()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
export type Id<T = nil> =
|
type Id<T = unknown> =
|
||||||
| Entity<T>
|
| (number & { __jecs_pair_value: T })
|
||||||
| Pair<Entity<T>, Entity>
|
| (number & { __T: T })
|
||||||
| Pair<Entity, Entity<T>>
|
|
||||||
| Pair<Entity<T>, Entity<unknown>>
|
|
||||||
|
|
||||||
export type Pair<P, O> = number & {
|
export type Pair<P = Entity, O = Entity> = number & {
|
||||||
__P: P,
|
__jecs_pair_value: ecs_id_t<ecs_pair_t<P, O>>
|
||||||
__O: O,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- type function ecs_id_t(entity)
|
type function ecs_id_t(entity)
|
||||||
-- local ty = entity:components()[2]
|
local ty = entity:components()[2]
|
||||||
-- local __T = ty:readproperty(types.singleton("__T"))
|
local __T = ty:readproperty(types.singleton("__T"))
|
||||||
-- if not __T then
|
if not __T then
|
||||||
-- return ty:readproperty(types.singleton("__jecs_pair_value"))
|
return ty:readproperty(types.singleton("__jecs_pair_value"))
|
||||||
-- end
|
end
|
||||||
-- return __T
|
return __T
|
||||||
-- end
|
end
|
||||||
|
|
||||||
-- type function ecs_pair_t(first, second)
|
type function ecs_pair_t(first, second)
|
||||||
-- if ecs_id_t(first):is("nil") then
|
local ty = first:components()[2]
|
||||||
-- return second
|
if ty:readproperty(types.singleton("__T")):is("nil") then
|
||||||
-- else
|
return second
|
||||||
-- return first
|
else
|
||||||
-- end
|
return first
|
||||||
-- end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
type Item<T...> = (self: Query<T...>) -> (Entity, T...)
|
type Item<T...> = (self: Query<T...>) -> (Entity, T...)
|
||||||
|
|
||||||
export type Entity<T = nil> = number & { __T: T }
|
export type Entity<T = nil> = number & { __T: T }
|
||||||
|
|
||||||
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||||
|
|
||||||
|
@ -2234,6 +2183,15 @@ export type Query<T...> = typeof(setmetatable({}, {
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueryInner = {
|
||||||
|
compatible_archetypes: { Archetype },
|
||||||
|
filter_with: { i53 }?,
|
||||||
|
filter_without: { i53 }?,
|
||||||
|
ids: { i53 },
|
||||||
|
world: {}, -- Downcasted to be serializable by the analyzer
|
||||||
|
next: () -> Item<any>
|
||||||
|
}
|
||||||
|
|
||||||
type Observer = {
|
type Observer = {
|
||||||
callback: (archetype: Archetype) -> (),
|
callback: (archetype: Archetype) -> (),
|
||||||
query: QueryInner,
|
query: QueryInner,
|
||||||
|
@ -2250,13 +2208,7 @@ export type World = {
|
||||||
nextEntityId: number,
|
nextEntityId: number,
|
||||||
nextArchetypeId: number,
|
nextArchetypeId: number,
|
||||||
|
|
||||||
observerable: {
|
observerable: { [i53]: { [i53]: { { query: Query<i53> } } } },
|
||||||
[i53]: {
|
|
||||||
[i53]: {
|
|
||||||
{ query: QueryInner, callback: (Archetype) -> () }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
} & {
|
} & {
|
||||||
--- Creates a new entity
|
--- Creates a new entity
|
||||||
entity: (self: World) -> Entity,
|
entity: (self: World) -> Entity,
|
||||||
|
@ -2299,18 +2251,18 @@ export type World = {
|
||||||
children: (self: World, id: Id) -> () -> Entity,
|
children: (self: World, id: Id) -> () -> Entity,
|
||||||
|
|
||||||
--- Searches the world for entities that match a given query
|
--- Searches the world for entities that match a given query
|
||||||
query: (<A>(World, Id<A>) -> Query<A>)
|
query: (<A>(World, A) -> Query<ecs_id_t<A>>)
|
||||||
& (<A, B>(World, Id<A>, Id<B>) -> Query<A, B>)
|
& (<A, B>(World, A, B) -> Query<ecs_id_t<A>, ecs_id_t<B>>)
|
||||||
& (<A, B, C>(World, Id<A>, Id<B>, Id<C>) -> Query<A, B, C>)
|
& (<A, B, C>(World, A, B, C) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>>)
|
||||||
& (<A, B, C, D>(World, Id<A>, Id<B>, Id<C>, Id<D>) -> Query<A, B, C, D>)
|
& (<A, B, C, D>(World, A, B, C, D) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>>)
|
||||||
& (<A, B, C, D, E>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>) -> Query<A, B, C, D, E>)
|
& (<A, B, C, D, E>(World, A, B, C, D, E) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>>)
|
||||||
& (<A, B, C, D, E, F>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>) -> Query<A, B, C, D, E, F>)
|
& (<A, B, C, D, E, F>(World, A, B, C, D, E, F) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>>)
|
||||||
& (<A, B, C, D, E, F, G>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>, Id<G>) -> Query<A, B, C, D, E, F, G>)
|
& (<A, B, C, D, E, F, G>(World, A, B, C, D, E, F, G) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>, ecs_id_t<G>>)
|
||||||
& (<A, B, C, D, E, F, G, H>(World, Id<A>, Id<B>, Id<C>, Id<D>, Id<E>, Id<F>, Id<G>, Id<H>, ...Id<any>) -> Query<A, B, C, D, E, F, G, H>)
|
& (<A, B, C, D, E, F, G, H>(World, A, B, C, D, E, F, G, H) -> Query<ecs_id_t<A>, ecs_id_t<B>, ecs_id_t<C>, ecs_id_t<D>, ecs_id_t<E>, ecs_id_t<F>, ecs_id_t<G>, ecs_id_t<H>>)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
World = World,
|
World = World :: { new: () -> World },
|
||||||
|
|
||||||
OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>,
|
OnAdd = EcsOnAdd :: Entity<(entity: Entity) -> ()>,
|
||||||
OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>,
|
OnRemove = EcsOnRemove :: Entity<(entity: Entity) -> ()>,
|
||||||
|
|
|
@ -374,12 +374,9 @@ TEST("world:query()", function()
|
||||||
world:set(e, Bar, false)
|
world:set(e, Bar, false)
|
||||||
local i = 0
|
local i = 0
|
||||||
|
|
||||||
local iter = 0
|
|
||||||
for _, e in q:iter() do
|
for _, e in q:iter() do
|
||||||
iter += 1
|
|
||||||
i=1
|
i=1
|
||||||
end
|
end
|
||||||
CHECK (iter == 1)
|
|
||||||
CHECK(i == 1)
|
CHECK(i == 1)
|
||||||
for _, e in q:iter() do
|
for _, e in q:iter() do
|
||||||
i=2
|
i=2
|
||||||
|
@ -1367,65 +1364,6 @@ TEST("Hooks", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("change tracking", function()
|
|
||||||
CASE "#1" do
|
|
||||||
local world = world_new()
|
|
||||||
local Foo = world:component()
|
|
||||||
local Previous = jecs.Rest
|
|
||||||
|
|
||||||
local q1 = world
|
|
||||||
:query(Foo)
|
|
||||||
:without(pair(Previous, Foo))
|
|
||||||
:cached()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:set(e1, Foo, 1)
|
|
||||||
local e2 = world:entity()
|
|
||||||
world:set(e2, Foo, 2)
|
|
||||||
|
|
||||||
local i = 0
|
|
||||||
for e, new in q1 do
|
|
||||||
i += 1
|
|
||||||
world:set(e, pair(Previous, Foo), new)
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(i == 2)
|
|
||||||
local j = 0
|
|
||||||
for e, new in q1 do
|
|
||||||
j += 1
|
|
||||||
world:set(e, pair(Previous, Foo), new)
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(j == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
CASE "#2" do
|
|
||||||
local world = world_new()
|
|
||||||
local component = world:component()
|
|
||||||
local tag = world:entity()
|
|
||||||
local previous = jecs.Rest
|
|
||||||
|
|
||||||
local q1 = world:query(component):without(pair(previous, component), tag):cached()
|
|
||||||
|
|
||||||
local testEntity = world:entity()
|
|
||||||
|
|
||||||
world:set(testEntity, component, 10)
|
|
||||||
|
|
||||||
local i = 0
|
|
||||||
for entity, number in q1 do
|
|
||||||
i += 1
|
|
||||||
world:add(testEntity, tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
CHECK(i == 1)
|
|
||||||
|
|
||||||
for e, n in q1 do
|
|
||||||
world:set(e, pair(previous, component), n)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("repro", function()
|
TEST("repro", function()
|
||||||
do CASE "#1"
|
do CASE "#1"
|
||||||
local world = world_new()
|
local world = world_new()
|
||||||
|
|
Loading…
Reference in a new issue