mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 17:40:02 +00:00
Compare commits
1 commit
cb9e8f4468
...
79ad25b547
Author | SHA1 | Date | |
---|---|---|---|
|
79ad25b547 |
2 changed files with 197 additions and 56 deletions
247
jecs.luau
247
jecs.luau
|
@ -13,6 +13,22 @@ type Column = { any }
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
type Map<K, V> = { [K]: V }
|
||||||
|
|
||||||
|
type ecs_graph_edge_t = {
|
||||||
|
from: ecs_archetype_t,
|
||||||
|
to: ecs_archetype_t?,
|
||||||
|
id: number,
|
||||||
|
prev: ecs_graph_edge_t?,
|
||||||
|
next: ecs_graph_edge_t?,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ecs_graph_edges_t = Map<i53, ecs_graph_edge_t>
|
||||||
|
|
||||||
|
type ecs_graph_node_t = {
|
||||||
|
add: ecs_graph_edges_t,
|
||||||
|
remove: ecs_graph_edges_t,
|
||||||
|
refs: ecs_graph_edge_t,
|
||||||
|
}
|
||||||
|
|
||||||
type ecs_archetype_t = {
|
type ecs_archetype_t = {
|
||||||
id: number,
|
id: number,
|
||||||
types: Ty,
|
types: Ty,
|
||||||
|
@ -21,7 +37,7 @@ type ecs_archetype_t = {
|
||||||
columns: { Column },
|
columns: { Column },
|
||||||
records: { [i53]: number },
|
records: { [i53]: number },
|
||||||
counts: { [i53]: number },
|
counts: { [i53]: number },
|
||||||
}
|
} & ecs_graph_node_t
|
||||||
|
|
||||||
export type Archetype = {
|
export type Archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -81,7 +97,6 @@ type ecs_observer_t = {
|
||||||
type ecs_observable_t = Map<i53, Map<i53, { ecs_observer_t }>>
|
type ecs_observable_t = Map<i53, Map<i53, { ecs_observer_t }>>
|
||||||
|
|
||||||
type ecs_world_t = {
|
type ecs_world_t = {
|
||||||
archetype_edges: Map<i53, Map<i53, ecs_archetype_t>>,
|
|
||||||
entity_index: ecs_entity_index_t,
|
entity_index: ecs_entity_index_t,
|
||||||
component_index: ecs_id_index_t,
|
component_index: ecs_id_index_t,
|
||||||
archetypes: ecs_archetypes_t,
|
archetypes: ecs_archetypes_t,
|
||||||
|
@ -631,6 +646,10 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
|
||||||
counts = counts,
|
counts = counts,
|
||||||
type = ty,
|
type = ty,
|
||||||
types = id_types,
|
types = id_types,
|
||||||
|
|
||||||
|
add = {},
|
||||||
|
remove = {},
|
||||||
|
refs = {} :: ecs_graph_edge_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, component_id in id_types do
|
for i, component_id in id_types do
|
||||||
|
@ -670,7 +689,6 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
|
||||||
|
|
||||||
world.archetype_index[ty] = archetype
|
world.archetype_index[ty] = archetype
|
||||||
world.archetypes[archetype_id] = archetype
|
world.archetypes[archetype_id] = archetype
|
||||||
world.archetype_edges[archetype.id] = {}
|
|
||||||
|
|
||||||
return archetype
|
return archetype
|
||||||
end
|
end
|
||||||
|
@ -700,7 +718,6 @@ end
|
||||||
local function find_insert(id_types: { i53 }, toAdd: i53): number
|
local function find_insert(id_types: { i53 }, toAdd: i53): number
|
||||||
for i, id in id_types do
|
for i, id in id_types do
|
||||||
if id == toAdd then
|
if id == toAdd then
|
||||||
error("Duplicate component id")
|
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
if id > toAdd then
|
if id > toAdd then
|
||||||
|
@ -710,6 +727,25 @@ local function find_insert(id_types: { i53 }, toAdd: i53): number
|
||||||
return #id_types + 1
|
return #id_types + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function find_archetype_with(world: ecs_world_t, node: ecs_archetype_t, id: i53): ecs_archetype_t
|
||||||
|
local id_types = node.types
|
||||||
|
-- Component IDs are added incrementally, so inserting and sorting
|
||||||
|
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
||||||
|
-- point in the types array.
|
||||||
|
|
||||||
|
local at = find_insert(id_types, id)
|
||||||
|
if at == -1 then
|
||||||
|
-- If it finds a duplicate, it just means it is the same archetype so it can return it
|
||||||
|
-- directly instead of needing to hash types for a lookup to the archetype.
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
|
||||||
|
local dst = table.clone(node.types) :: { i53 }
|
||||||
|
table.insert(dst, at, id)
|
||||||
|
|
||||||
|
return archetype_ensure(world, dst)
|
||||||
|
end
|
||||||
|
|
||||||
local function find_archetype_without(
|
local function find_archetype_without(
|
||||||
world: ecs_world_t,
|
world: ecs_world_t,
|
||||||
node: ecs_archetype_t,
|
node: ecs_archetype_t,
|
||||||
|
@ -717,6 +753,9 @@ local function find_archetype_without(
|
||||||
): ecs_archetype_t
|
): ecs_archetype_t
|
||||||
local id_types = node.types
|
local id_types = node.types
|
||||||
local at = table.find(id_types, id)
|
local at = table.find(id_types, id)
|
||||||
|
if at == nil then
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
|
||||||
local dst = table.clone(id_types)
|
local dst = table.clone(id_types)
|
||||||
table.remove(dst, at)
|
table.remove(dst, at)
|
||||||
|
@ -724,67 +763,126 @@ local function find_archetype_without(
|
||||||
return archetype_ensure(world, dst)
|
return archetype_ensure(world, dst)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function archetype_init_edge(
|
||||||
|
archetype: ecs_archetype_t,
|
||||||
|
edge: ecs_graph_edge_t,
|
||||||
|
id: i53,
|
||||||
|
to: ecs_archetype_t
|
||||||
|
)
|
||||||
|
edge.from = archetype
|
||||||
|
edge.to = to
|
||||||
|
edge.id = id
|
||||||
|
end
|
||||||
|
|
||||||
|
local function archetype_ensure_edge(
|
||||||
|
world: ecs_world_t,
|
||||||
|
edges: ecs_graph_edges_t,
|
||||||
|
id: i53
|
||||||
|
): ecs_graph_edge_t
|
||||||
|
local edge = edges[id]
|
||||||
|
if not edge then
|
||||||
|
edge = {} :: ecs_graph_edge_t
|
||||||
|
edges[id] = edge
|
||||||
|
end
|
||||||
|
|
||||||
|
return edge
|
||||||
|
end
|
||||||
|
|
||||||
|
local function init_edge_for_add(world, archetype: ecs_archetype_t, edge: ecs_graph_edge_t, id, to: ecs_archetype_t)
|
||||||
|
archetype_init_edge(archetype, edge, id, to)
|
||||||
|
archetype_ensure_edge(world, archetype.add, id)
|
||||||
|
if archetype ~= to then
|
||||||
|
local to_refs = to.refs
|
||||||
|
local next_edge = to_refs.next
|
||||||
|
|
||||||
|
to_refs.next = edge
|
||||||
|
edge.prev = to_refs
|
||||||
|
edge.next = next_edge
|
||||||
|
|
||||||
|
if next_edge then
|
||||||
|
next_edge.prev = edge
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function init_edge_for_remove(
|
||||||
|
world: ecs_world_t,
|
||||||
|
archetype: ecs_archetype_t,
|
||||||
|
edge: ecs_graph_edge_t,
|
||||||
|
id: number,
|
||||||
|
to: ecs_archetype_t
|
||||||
|
)
|
||||||
|
archetype_init_edge(archetype, edge, id, to)
|
||||||
|
archetype_ensure_edge(world, archetype.remove, id)
|
||||||
|
if archetype ~= to then
|
||||||
|
local to_refs = to.refs
|
||||||
|
local prev_edge = to_refs.prev
|
||||||
|
|
||||||
|
to_refs.prev = edge
|
||||||
|
edge.next = to_refs
|
||||||
|
edge.prev = prev_edge
|
||||||
|
|
||||||
|
if prev_edge then
|
||||||
|
prev_edge.next = edge
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_edge_for_add(
|
||||||
|
world: ecs_world_t,
|
||||||
|
node: ecs_archetype_t,
|
||||||
|
edge: ecs_graph_edge_t,
|
||||||
|
id: i53
|
||||||
|
): ecs_archetype_t
|
||||||
|
local to = find_archetype_with(world, node, id)
|
||||||
|
init_edge_for_add(world, node, edge, id, to)
|
||||||
|
return to
|
||||||
|
end
|
||||||
|
|
||||||
local function create_edge_for_remove(
|
local function create_edge_for_remove(
|
||||||
world: ecs_world_t,
|
world: ecs_world_t,
|
||||||
node: ecs_archetype_t,
|
node: ecs_archetype_t,
|
||||||
edge: Map<number, ecs_archetype_t>,
|
edge: ecs_graph_edge_t,
|
||||||
id: i53
|
id: i53
|
||||||
): ecs_archetype_t
|
): ecs_archetype_t
|
||||||
local to = find_archetype_without(world, node, id)
|
local to = find_archetype_without(world, node, id)
|
||||||
local edges = world.archetype_edges
|
init_edge_for_remove(world, node, edge, id, to)
|
||||||
local archetype_id = node.id
|
|
||||||
edges[archetype_id][id] = to
|
|
||||||
edges[to.id][id] = node
|
|
||||||
return to
|
return to
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function archetype_traverse_add(
|
||||||
|
world: ecs_world_t,
|
||||||
|
id: i53,
|
||||||
|
from: ecs_archetype_t
|
||||||
|
): ecs_archetype_t
|
||||||
|
from = from or world.ROOT_ARCHETYPE
|
||||||
|
local edge = archetype_ensure_edge(world, from.add, id)
|
||||||
|
|
||||||
|
local to = edge.to
|
||||||
|
if not to then
|
||||||
|
to = create_edge_for_add(world, from, edge, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
return to :: ecs_archetype_t
|
||||||
|
end
|
||||||
|
|
||||||
local function archetype_traverse_remove(
|
local function archetype_traverse_remove(
|
||||||
world: ecs_world_t,
|
world: ecs_world_t,
|
||||||
id: i53,
|
id: i53,
|
||||||
from: ecs_archetype_t
|
from: ecs_archetype_t
|
||||||
): ecs_archetype_t
|
): ecs_archetype_t
|
||||||
local edges = world.archetype_edges
|
from = from or world.ROOT_ARCHETYPE
|
||||||
local edge = edges[from.id]
|
|
||||||
|
|
||||||
local to = edge[id]
|
local edge = archetype_ensure_edge(world, from.remove, id)
|
||||||
|
|
||||||
|
local to = edge.to
|
||||||
if not to then
|
if not to then
|
||||||
to = find_archetype_without(world, from, id)
|
to = create_edge_for_remove(world, from, edge, id)
|
||||||
edge[id] = to
|
|
||||||
edges[to.id][id] = from
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return to :: ecs_archetype_t
|
return to :: ecs_archetype_t
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_archetype_with(world, id, from)
|
|
||||||
local id_types = from.types
|
|
||||||
|
|
||||||
local at = find_insert(id_types, id)
|
|
||||||
local dst = table.clone(id_types) :: { i53 }
|
|
||||||
table.insert(dst, at, id)
|
|
||||||
|
|
||||||
return archetype_ensure(world, dst)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function archetype_traverse_add(world, id, from: ecs_archetype_t)
|
|
||||||
from = from or world.ROOT_ARCHETYPE
|
|
||||||
if from.records[id] then
|
|
||||||
return from
|
|
||||||
end
|
|
||||||
local edges = world.archetype_edges
|
|
||||||
local edge = edges[from.id]
|
|
||||||
|
|
||||||
local to = edge[id]
|
|
||||||
if not to then
|
|
||||||
to = find_archetype_with(world, id, from)
|
|
||||||
edge[id] = to
|
|
||||||
edges[to.id][id] = from
|
|
||||||
end
|
|
||||||
|
|
||||||
return to
|
|
||||||
end
|
|
||||||
|
|
||||||
local function world_add(
|
local function world_add(
|
||||||
world: ecs_world_t,
|
world: ecs_world_t,
|
||||||
entity: i53,
|
entity: i53,
|
||||||
|
@ -1058,18 +1156,62 @@ local function world_clear(world: ecs_world_t, entity: i53)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function archetype_disconnect_edge(edge: ecs_graph_edge_t)
|
||||||
|
local edge_next = edge.next
|
||||||
|
local edge_prev = edge.prev
|
||||||
|
if edge_next then
|
||||||
|
edge_next.prev = edge_prev
|
||||||
|
end
|
||||||
|
if edge_prev then
|
||||||
|
edge_prev.next = edge_next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function archetype_remove_edge(edges: ecs_graph_edges_t, id: i53, edge: ecs_graph_edge_t)
|
||||||
|
archetype_disconnect_edge(edge)
|
||||||
|
edges[id] = nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
local function archetype_clear_edges(archetype: ecs_archetype_t)
|
||||||
|
local add: ecs_graph_edges_t = archetype.add
|
||||||
|
local remove: ecs_graph_edges_t = archetype.remove
|
||||||
|
local node_refs = archetype.refs
|
||||||
|
for id, edge in add do
|
||||||
|
archetype_disconnect_edge(edge)
|
||||||
|
add[id] = nil :: any
|
||||||
|
end
|
||||||
|
for id, edge in remove do
|
||||||
|
archetype_disconnect_edge(edge)
|
||||||
|
remove[id] = nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
local cur = node_refs.next
|
||||||
|
while cur do
|
||||||
|
local edge = cur :: ecs_graph_edge_t
|
||||||
|
local next_edge = edge.next
|
||||||
|
archetype_remove_edge(edge.from.add, edge.id, edge)
|
||||||
|
cur = next_edge
|
||||||
|
end
|
||||||
|
|
||||||
|
cur = node_refs.prev
|
||||||
|
while cur do
|
||||||
|
local edge: ecs_graph_edge_t = cur
|
||||||
|
local next_edge = edge.prev
|
||||||
|
archetype_remove_edge(edge.from.remove, edge.id, edge)
|
||||||
|
cur = next_edge
|
||||||
|
end
|
||||||
|
|
||||||
|
node_refs.next = nil
|
||||||
|
node_refs.prev = nil
|
||||||
|
end
|
||||||
|
|
||||||
local function archetype_destroy(world: ecs_world_t, archetype: ecs_archetype_t)
|
local function archetype_destroy(world: ecs_world_t, archetype: ecs_archetype_t)
|
||||||
if archetype == world.ROOT_ARCHETYPE then
|
if archetype == world.ROOT_ARCHETYPE then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
local archetype_edges = world.archetype_edges
|
archetype_clear_edges(archetype)
|
||||||
|
|
||||||
for id, edge in archetype_edges[archetype.id] do
|
|
||||||
archetype_edges[edge.id][id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local archetype_id = archetype.id
|
local archetype_id = archetype.id
|
||||||
world.archetypes[archetype_id] = nil :: any
|
world.archetypes[archetype_id] = nil :: any
|
||||||
world.archetype_index[archetype.type] = nil :: any
|
world.archetype_index[archetype.type] = nil :: any
|
||||||
|
@ -2297,8 +2439,6 @@ local function world_new()
|
||||||
max_id = 0,
|
max_id = 0,
|
||||||
} :: ecs_entity_index_t
|
} :: ecs_entity_index_t
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
archetype_edges = {},
|
|
||||||
|
|
||||||
archetype_index = {} :: { [string]: Archetype },
|
archetype_index = {} :: { [string]: Archetype },
|
||||||
archetypes = {} :: Archetypes,
|
archetypes = {} :: Archetypes,
|
||||||
component_index = {} :: ComponentIndex,
|
component_index = {} :: ComponentIndex,
|
||||||
|
@ -2499,6 +2639,11 @@ return {
|
||||||
find_insert = find_insert,
|
find_insert = find_insert,
|
||||||
find_archetype_with = find_archetype_with,
|
find_archetype_with = find_archetype_with,
|
||||||
find_archetype_without = find_archetype_without,
|
find_archetype_without = find_archetype_without,
|
||||||
|
archetype_init_edge = archetype_init_edge,
|
||||||
|
archetype_ensure_edge = archetype_ensure_edge,
|
||||||
|
init_edge_for_add = init_edge_for_add,
|
||||||
|
init_edge_for_remove = init_edge_for_remove,
|
||||||
|
create_edge_for_add = create_edge_for_add,
|
||||||
create_edge_for_remove = create_edge_for_remove,
|
create_edge_for_remove = create_edge_for_remove,
|
||||||
archetype_traverse_add = archetype_traverse_add,
|
archetype_traverse_add = archetype_traverse_add,
|
||||||
archetype_traverse_remove = archetype_traverse_remove,
|
archetype_traverse_remove = archetype_traverse_remove,
|
||||||
|
|
|
@ -347,7 +347,7 @@ TEST("archetype", function()
|
||||||
|
|
||||||
local a1 = archetype_traverse_add(world, c1, nil :: any)
|
local a1 = archetype_traverse_add(world, c1, nil :: any)
|
||||||
local a2 = archetype_traverse_remove(world, c1, a1)
|
local a2 = archetype_traverse_remove(world, c1, a1)
|
||||||
-- CHECK(root.add[c1].to == a1)
|
CHECK(root.add[c1].to == a1)
|
||||||
CHECK(root == a2)
|
CHECK(root == a2)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -580,12 +580,8 @@ TEST("world:add()", function()
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
world:add(e, _1)
|
world:add(e, _1)
|
||||||
world:add(e, _2)
|
world:add(e, _2)
|
||||||
|
|
||||||
print("----idempotent")
|
|
||||||
print(d.archetype(e))
|
|
||||||
world:add(e, _2) -- should have 0 effects
|
world:add(e, _2) -- should have 0 effects
|
||||||
CHECK(d.archetype(e) == "1_2")
|
CHECK(d.archetype(e) == "1_2")
|
||||||
print(d.archetype(e))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|
Loading…
Reference in a new issue