mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-23 16:39:17 +00:00
Compare commits
No commits in common. "5de842d144f39d8c60a2c40c1c5d6e6f1449f49e" and "0874e426afd29c73b97e95f3735c729c4de08fc6" have entirely different histories.
5de842d144
...
0874e426af
5 changed files with 655 additions and 844 deletions
|
@ -603,26 +603,12 @@ function World:exists(
|
|||
|
||||
## cleanup
|
||||
|
||||
Cleans up empty archetypes.
|
||||
Cleans up deleted entities and their associated data. This is automatically called by jecs, but can be called manually if needed.
|
||||
|
||||
```luau
|
||||
function World:cleanup(): void
|
||||
```
|
||||
|
||||
:::info
|
||||
It is recommended to profile the optimal interval you should cleanup because it varies completely from game to game.
|
||||
|
||||
Here are a couple of reasons from Sander Mertens:
|
||||
- some applications are memory constrained, so any wasted memory on empty
|
||||
archetypes has to get cleaned up
|
||||
- many archetypes can get created during game startup but aren't used later
|
||||
on, so it would be wasteful to keep them around
|
||||
- empty archetypes can slow queries down, especially if there are many more
|
||||
empty ones than non-empty ones
|
||||
- if the total number of component permutations (/relationships) is too
|
||||
high, you have no choice but to periodically cleanup empty archetypes
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
::: code-group
|
||||
|
|
264
jecs.luau
264
jecs.luau
|
@ -74,7 +74,7 @@ type query = {
|
|||
filter_with: { i53 },
|
||||
filter_without: { i53 },
|
||||
next: () -> (i53, ...any),
|
||||
world: world,
|
||||
world: World,
|
||||
}
|
||||
|
||||
export type observer = {
|
||||
|
@ -166,7 +166,7 @@ export type World = {
|
|||
|
||||
observable: Map<Id, Map<Id, { Observer }>>,
|
||||
|
||||
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T?) -> ()) -> () -> (),
|
||||
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
||||
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
||||
|
||||
|
@ -926,9 +926,9 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
|||
end
|
||||
end
|
||||
|
||||
world.archetype_index[ty] = archetype
|
||||
world.archetype_index[archetype.type] = archetype
|
||||
world.archetypes[archetype_id] = archetype
|
||||
world.archetype_edges[archetype_id] = {} :: Map<i53, archetype>
|
||||
world.archetype_edges[archetype.id] = {} :: Map<i53, archetype>
|
||||
|
||||
for id in columns_map do
|
||||
local observer_list = find_observers(world, EcsOnArchetypeCreate, id)
|
||||
|
@ -1175,86 +1175,11 @@ end
|
|||
|
||||
local function NOOP() end
|
||||
|
||||
local function query_archetypes(query: query)
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
if not compatible_archetypes then
|
||||
compatible_archetypes = {}
|
||||
query.compatible_archetypes = compatible_archetypes
|
||||
|
||||
local world = query.world
|
||||
local archetypes = world.archetypes
|
||||
|
||||
local component_index = world.component_index
|
||||
|
||||
local idr: componentrecord?
|
||||
local with = query.filter_with
|
||||
for _, id in with do
|
||||
local map = component_index[id]
|
||||
if not map then
|
||||
continue
|
||||
end
|
||||
|
||||
if idr == nil or (map.size :: number) < (idr.size :: number) then
|
||||
idr = map
|
||||
end
|
||||
end
|
||||
|
||||
if idr == nil then
|
||||
return compatible_archetypes
|
||||
end
|
||||
|
||||
local without = query.filter_without
|
||||
|
||||
for archetype_id in idr.records do
|
||||
local archetype = archetypes[archetype_id]
|
||||
local columns_map = archetype.columns_map
|
||||
local skip = false
|
||||
for _, component in with do
|
||||
if not columns_map[component] then
|
||||
skip = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if skip then
|
||||
continue
|
||||
end
|
||||
if without then
|
||||
for _, component in without do
|
||||
if columns_map[component] then
|
||||
skip = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if skip then
|
||||
continue
|
||||
end
|
||||
|
||||
table.insert(compatible_archetypes, archetype)
|
||||
end
|
||||
end
|
||||
return compatible_archetypes
|
||||
end
|
||||
|
||||
local function query_with(query: query, ...: i53)
|
||||
local ids = query.ids
|
||||
local with = { ... }
|
||||
table.move(ids, 1, #ids, #with + 1, with)
|
||||
query.filter_with = with
|
||||
return query
|
||||
end
|
||||
|
||||
local function query_without(query: query, ...: i53)
|
||||
local without = { ... }
|
||||
query.filter_without = without
|
||||
return query
|
||||
end
|
||||
|
||||
local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
||||
local world_query_iter_next
|
||||
|
||||
local compatible_archetypes = query_archetypes(query::any) :: { Archetype }
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
local lastArchetype = 1
|
||||
local archetype = compatible_archetypes[1]
|
||||
if not archetype then
|
||||
|
@ -1327,6 +1252,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1349,6 +1277,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1372,6 +1303,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1396,6 +1330,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1421,6 +1358,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1447,6 +1387,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1474,6 +1417,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1502,6 +1448,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1533,6 +1482,9 @@ local function query_iter_init(query: QueryInner): () -> (number, ...any)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1568,8 +1520,84 @@ local function query_iter(query): () -> (number, ...any)
|
|||
return query_next
|
||||
end
|
||||
|
||||
local function query_without(query: QueryInner, ...: Id)
|
||||
local without = { ... }
|
||||
query.filter_without = without :: any
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
for i = #compatible_archetypes, 1, -1 do
|
||||
local archetype = compatible_archetypes[i]
|
||||
local columns_map = archetype.columns_map
|
||||
local matches = true
|
||||
|
||||
for _, id in without do
|
||||
if columns_map[id] then
|
||||
matches = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if matches then
|
||||
continue
|
||||
end
|
||||
|
||||
local last = #compatible_archetypes
|
||||
if last ~= i then
|
||||
compatible_archetypes[i] = compatible_archetypes[last]
|
||||
end
|
||||
compatible_archetypes[last] = nil :: any
|
||||
end
|
||||
|
||||
return query :: any
|
||||
end
|
||||
|
||||
local function query_with(query: QueryInner, ...: Id)
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
local with = { ... } :: any
|
||||
query.filter_with = with
|
||||
|
||||
for i = #compatible_archetypes, 1, -1 do
|
||||
local archetype = compatible_archetypes[i]
|
||||
local columns_map = archetype.columns_map
|
||||
local matches = true
|
||||
|
||||
for _, id in with do
|
||||
if not columns_map[id] then
|
||||
matches = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if matches then
|
||||
continue
|
||||
end
|
||||
|
||||
local last = #compatible_archetypes
|
||||
if last ~= i then
|
||||
compatible_archetypes[i] = compatible_archetypes[last]
|
||||
end
|
||||
compatible_archetypes[last] = nil :: any
|
||||
end
|
||||
|
||||
return query :: any
|
||||
end
|
||||
|
||||
-- Meant for directly iterating over archetypes to minimize
|
||||
-- function call overhead. Should not be used unless iterating over
|
||||
-- hundreds of thousands of entities in bulk.
|
||||
local function query_archetypes(query)
|
||||
return query.compatible_archetypes
|
||||
end
|
||||
|
||||
local function query_cached(query: QueryInner)
|
||||
local with = query.filter_with
|
||||
local ids = query.ids
|
||||
if with then
|
||||
table.move(ids, 1, #ids, #with + 1, with)
|
||||
else
|
||||
query.filter_with = ids
|
||||
end
|
||||
|
||||
local compatible_archetypes = query.compatible_archetypes
|
||||
local lastArchetype = 1
|
||||
|
||||
local A, B, C, D, E, F, G, H, I = unpack(ids :: { Id })
|
||||
|
@ -1581,8 +1609,7 @@ local function query_cached(query: QueryInner)
|
|||
local i: number
|
||||
local archetype: Archetype
|
||||
local columns_map: { [Id]: Column }
|
||||
local archetypes = query_archetypes(query :: any)
|
||||
local compatible_archetypes = archetypes :: { Archetype }
|
||||
local archetypes = query.compatible_archetypes
|
||||
|
||||
local world = query.world
|
||||
-- Only need one observer for EcsArchetypeCreate and EcsArchetypeDelete respectively
|
||||
|
@ -1700,6 +1727,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1722,6 +1752,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1745,6 +1778,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1769,6 +1805,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1853,6 +1892,10 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
b = columns_map[B]
|
||||
|
@ -1880,6 +1923,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1911,6 +1957,9 @@ local function query_cached(query: QueryInner)
|
|||
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
if i == 0 then
|
||||
continue
|
||||
end
|
||||
entity = entities[i]
|
||||
columns_map = archetype.columns_map
|
||||
a = columns_map[A]
|
||||
|
@ -1952,16 +2001,65 @@ Query.archetypes = query_archetypes
|
|||
Query.cached = query_cached
|
||||
|
||||
local function world_query(world: World, ...)
|
||||
local compatible_archetypes = {}
|
||||
local length = 0
|
||||
|
||||
local ids = { ... }
|
||||
|
||||
local archetypes = world.archetypes
|
||||
|
||||
local idr: ComponentRecord?
|
||||
local component_index = world.component_index
|
||||
|
||||
local q = setmetatable({
|
||||
ids = ids,
|
||||
filter_with = ids,
|
||||
compatible_archetypes = compatible_archetypes,
|
||||
world = world,
|
||||
}, Query)
|
||||
|
||||
for _, id in ids do
|
||||
local map = component_index[id]
|
||||
if not map then
|
||||
return q
|
||||
end
|
||||
|
||||
if idr == nil or (map.size :: number) < (idr.size :: number) then
|
||||
idr = map
|
||||
end
|
||||
end
|
||||
|
||||
if idr == nil then
|
||||
return q
|
||||
end
|
||||
|
||||
for archetype_id in idr.records do
|
||||
local compatibleArchetype = archetypes[archetype_id]
|
||||
if #compatibleArchetype.entities == 0 then
|
||||
continue
|
||||
end
|
||||
local columns_map = compatibleArchetype.columns_map
|
||||
|
||||
local skip = false
|
||||
|
||||
for i, id in ids do
|
||||
local column = columns_map[id]
|
||||
if not column then
|
||||
skip = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if skip then
|
||||
continue
|
||||
end
|
||||
|
||||
length += 1
|
||||
compatible_archetypes[length] = compatibleArchetype
|
||||
end
|
||||
|
||||
return q
|
||||
end
|
||||
|
||||
local function world_each(world: world, id: i53): () -> i53
|
||||
local idr = world.component_index[id]
|
||||
if not idr then
|
||||
|
|
1177
mirror.luau
1177
mirror.luau
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@rbxts/jecs",
|
||||
"version": "0.9.0-rc.12",
|
||||
"version": "0.9.0-rc.11",
|
||||
"description": "Stupidly fast Entity Component System",
|
||||
"main": "jecs.luau",
|
||||
"repository": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ukendio/jecs"
|
||||
version = "0.9.0-rc.12"
|
||||
version = "0.9.0-rc.11"
|
||||
registry = "https://github.com/UpliftGames/wally-index"
|
||||
realm = "shared"
|
||||
license = "MIT"
|
||||
|
|
Loading…
Reference in a new issue