mirror of
https://github.com/Ukendio/jecs.git
synced 2026-03-18 00:44:32 +00:00
v0.10
This commit is contained in:
parent
33f7c08025
commit
d4a7f1d86c
8 changed files with 304 additions and 66 deletions
|
|
@ -82,3 +82,23 @@ world:set(e1, Position, vector.create(10, 20, 30))
|
||||||
- [Position, Velocity] -> [Position] (for removing Velocity)
|
- [Position, Velocity] -> [Position] (for removing Velocity)
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local pair = jecs.pair
|
||||||
|
|
||||||
|
local Likes = world:component()
|
||||||
|
local alice = world:entity()
|
||||||
|
local bob = world:entity()
|
||||||
|
local charlie = world:entity()
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:add(e2, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
|
||||||
|
|
||||||
|
local e3 = world:entity()
|
||||||
|
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
|
||||||
|
|
||||||
|
local e4 = world:entity()
|
||||||
|
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
|
||||||
|
world:add(e3, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob), pair(Likes, charlie)]
|
||||||
|
|
||||||
|
-- Each different target creates a new archetype, leading to fragmentation
|
||||||
|
-- This is why relationships can increase archetype count significantly
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
local jecs = require("@jecs")
|
|
||||||
local pair = jecs.pair
|
|
||||||
local world = jecs.world()
|
|
||||||
|
|
||||||
local Likes = world:component()
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Fragmentation is a property of archetype-based ECS implementations where entities
|
Fragmentation is a property of archetype-based ECS implementations where entities
|
||||||
are spread out over more archetypes as the number of different component combinations
|
are spread out over more archetypes as the number of different component combinations
|
||||||
|
|
@ -30,19 +24,3 @@ local Likes = world:component()
|
||||||
pair(jecs.Wildcard, Apples) indices. For this reason, creating new archetypes
|
pair(jecs.Wildcard, Apples) indices. For this reason, creating new archetypes
|
||||||
with relationships has a higher overhead than an archetype without relationships.
|
with relationships has a higher overhead than an archetype without relationships.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local alice = world:entity()
|
|
||||||
local bob = world:entity()
|
|
||||||
local charlie = world:entity()
|
|
||||||
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e1, pair(Likes, alice)) -- Creates archetype [pair(Likes, alice)]
|
|
||||||
|
|
||||||
local e2 = world:entity()
|
|
||||||
world:add(e2, pair(Likes, bob)) -- Creates archetype [pair(Likes, bob)]
|
|
||||||
|
|
||||||
local e3 = world:entity()
|
|
||||||
world:add(e3, pair(Likes, charlie)) -- Creates archetype [pair(Likes, charlie)]
|
|
||||||
|
|
||||||
-- Each different target creates a new archetype, leading to fragmentation
|
|
||||||
-- This is why relationships can increase archetype count significantly
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ end)
|
||||||
local Health = world:component()
|
local Health = world:component()
|
||||||
local Dead = world:component()
|
local Dead = world:component()
|
||||||
|
|
||||||
world:set(Health, jecs.OnRemove, function(entity, id, delete)
|
world:set(Health, jecs.OnRemove, function(entity: jecs.Entity, id, delete)
|
||||||
if delete then
|
if delete then
|
||||||
-- Entity is being deleted, don't try to clean up
|
-- Entity is being deleted, don't try to clean up
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
--[[
|
||||||
|
Signals let you subscribe to component add, change, and remove events with
|
||||||
|
multiple listeners per component. Unlike hooks (see 110_hooks.luau), which
|
||||||
|
allow only one OnAdd, OnChange, and OnRemove per component, signals support
|
||||||
|
any number of subscribers and each subscription returns an unsubscribe
|
||||||
|
function so you can clean up when you no longer need to listen.
|
||||||
|
|
||||||
|
Use signals when you need several independent systems to react to the same
|
||||||
|
component lifecycle events, or when you want to subscribe and unsubscribe
|
||||||
|
dynamically (e.g. a UI that only cares while it's mounted).
|
||||||
|
]]
|
||||||
|
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
|
local Position = world:component() :: jecs.Id<{ x: number, y: number }>
|
||||||
|
|
||||||
|
--[[
|
||||||
|
world:added(component, fn)
|
||||||
|
|
||||||
|
Subscribe to "component added" events. Your callback is invoked with:
|
||||||
|
(entity, id, value, oldarchetype) whenever the component is added to an entity.
|
||||||
|
|
||||||
|
Returns a function; call it to unsubscribe.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local unsub_added = world:added(Position, function(entity, id, value, oldarchetype)
|
||||||
|
print(`Position added to entity {entity}: ({value.x}, {value.y})`)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
world:changed(component, fn)
|
||||||
|
|
||||||
|
Subscribe to "component changed" events. Your callback is invoked with:
|
||||||
|
(entity, id, value, oldarchetype) whenever the component's value is updated
|
||||||
|
on an entity (e.g. via world:set).
|
||||||
|
|
||||||
|
Returns a function; call it to unsubscribe.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local unsub_changed = world:changed(Position, function(entity, id, value, oldarchetype)
|
||||||
|
print(`Position changed on entity {entity}: ({value.x}, {value.y})`)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
world:removed(component, fn)
|
||||||
|
|
||||||
|
Subscribe to "component removed" events. Your callback is invoked with:
|
||||||
|
(entity, id, delete?) when the component is removed. The third argument
|
||||||
|
`delete` is true when the entity is being deleted, false or nil when
|
||||||
|
only the component was removed (same semantics as OnRemove in 110_hooks).
|
||||||
|
|
||||||
|
Returns a function; call it to unsubscribe.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local unsub_removed = world:removed(Position, function(entity, id, delete)
|
||||||
|
if delete then
|
||||||
|
print(`Entity {entity} deleted (had Position)`)
|
||||||
|
else
|
||||||
|
print(`Position removed from entity {entity}`)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, Position, { x = 10, y = 20 }) -- added
|
||||||
|
world:set(e, Position, { x = 30, y = 40 }) -- changed
|
||||||
|
world:remove(e, Position) -- removed
|
||||||
|
|
||||||
|
world:added(Position, function(entity)
|
||||||
|
print("Second listener: Position added")
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:set(e, Position, { x = 0, y = 0 }) -- Multiple listeners are all invoked
|
||||||
|
|
||||||
|
-- Unsubscribe when you no longer need to listen
|
||||||
|
unsub_added()
|
||||||
|
unsub_changed()
|
||||||
|
unsub_removed()
|
||||||
|
|
||||||
|
world:set(e, Position, { x = 1, y = 1 })
|
||||||
|
world:remove(e, Position)
|
||||||
54
how_to/999_temperance.luau
Normal file
54
how_to/999_temperance.luau
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
-- These notes are my thoughts jotted down from having experienced these
|
||||||
|
-- problems myself and gathered insights from many admired individuals such as
|
||||||
|
-- Sander Mertens, Ryan Fleury, Jonathon Blow, Benjamin Saunders and many more...
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
In 1993, the original source code for DOOM was about 50,000 lines of code. And
|
||||||
|
when your code gets into that neighbourhood, it should provide a large amount of
|
||||||
|
interesting and novel functionality. If it doesn't, perhaps it is time to ask questions.
|
||||||
|
|
||||||
|
Please, try to write code that is small, and that does a lot for its size.
|
||||||
|
|
||||||
|
Please, after you finish writing something, ask yourself whether you are
|
||||||
|
satisfied with how robust it is, and with how much it gets done for how much
|
||||||
|
code there is.
|
||||||
|
|
||||||
|
- Transfer of tacit knowledge is incredibly important. If tacit knowledge
|
||||||
|
about the code base is lost, ability to work on it at the same level of quality
|
||||||
|
is lost. Over time code quality will decline as code size grows.
|
||||||
|
|
||||||
|
- Tacit knowledge is very hard to recover by looking at a maze of code,
|
||||||
|
and it takes
|
||||||
|
|
||||||
|
- You will often hear that "every semantic distinction deserves its own
|
||||||
|
component or tag". Sometimes this is correct. A well chosen component boundary
|
||||||
|
can make queries clear and systems obvious. But sometimes this distinction would
|
||||||
|
be better served as a field, a bitset, or a local data structure. The
|
||||||
|
representation should match the problem.
|
||||||
|
|
||||||
|
Sub-Essay Here: Code Should Not Try To Meet Every Need Anyone May Ever Have.
|
||||||
|
|
||||||
|
Over-generalization leads to bloat and poor functionality. A common failure mode
|
||||||
|
is writing code "for everyone" while building configuration for every scenario,
|
||||||
|
abstractions for every future feature, and extension points for every imagined
|
||||||
|
consumer. It sounds responsible. It usually isn't.
|
||||||
|
Specialization is good in many cases. Think about the guy with the truck full of
|
||||||
|
automotive tools, who has a bunch of different wrenches. He doesn't carry one
|
||||||
|
wrench that transforms into every tool; he carries a few specialized tools that
|
||||||
|
are reliable and fast to use. One reason we have endless bloat is that we teach
|
||||||
|
that all code should expand until it meets all needs. This is wrong,
|
||||||
|
empirically, and we should stop teaching it.
|
||||||
|
|
||||||
|
- Relationships are very powerful however, with each pair being an unique
|
||||||
|
component, it can be an easy way to accidentally increase the number of archetypes which can cause
|
||||||
|
higher churn in systems.
|
||||||
|
|
||||||
|
- A hook is not a replacement for systems. They are for enforcing invariants when data changes during different lifecycles.
|
||||||
|
When gameplay logic that should run predictably each frame is instead scattered
|
||||||
|
across hooks, behaviour becomes implicit when it is triggered indirectly through
|
||||||
|
a cascade of changes that, logic split across many small
|
||||||
|
callbacks that fire in surprising order. Which also gets harder to reason about
|
||||||
|
and optimize.
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.9.0",
|
"version": "0.10.0",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "src/jecs.luau",
|
"main": "src/jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -37,10 +37,5 @@
|
||||||
"roblox-ts": "^3.0.0",
|
"roblox-ts": "^3.0.0",
|
||||||
"typescript": "^5.4.2",
|
"typescript": "^5.4.2",
|
||||||
"vitepress": "^1.3.0"
|
"vitepress": "^1.3.0"
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"docs:dev": "vitepress dev docs",
|
|
||||||
"docs:build": "vitepress build docs",
|
|
||||||
"docs:preview": "vitepress preview docs"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
182
src/jecs.luau
182
src/jecs.luau
|
|
@ -211,24 +211,6 @@ type world = {
|
||||||
removed: (world, i53, (e: i53, id: i53, delete: boolean?) -> ()) -> () -> (),
|
removed: (world, i53, (e: i53, id: i53, delete: boolean?) -> ()) -> () -> (),
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Type_Query<T...> = Query<T...>
|
|
||||||
|
|
||||||
type function QueryBundle(t1: type)
|
|
||||||
assert(t1:is("function"))
|
|
||||||
local params = t1:returns()
|
|
||||||
local head = params.head
|
|
||||||
assert(head)
|
|
||||||
local newt = {}
|
|
||||||
for i = 1, #head do
|
|
||||||
if head[i] == types.unknown then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
newt[i] = head[i]:readproperty(types.singleton("__T"))
|
|
||||||
end
|
|
||||||
|
|
||||||
return Type_Query(unpack(newt))
|
|
||||||
end
|
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
archetype_edges: Map<number, Map<Entity, Archetype>>,
|
archetype_edges: Map<number, Map<Entity, Archetype>>,
|
||||||
archetype_index: { [string]: Archetype },
|
archetype_index: { [string]: Archetype },
|
||||||
|
|
@ -313,7 +295,35 @@ export type World = {
|
||||||
children: <T>(self: World, id: Id<T>) -> () -> Entity,
|
children: <T>(self: World, id: Id<T>) -> () -> Entity,
|
||||||
|
|
||||||
--- Searches the world for entities that match a given query
|
--- Searches the world for entities that match a given query
|
||||||
query: (<A, B, C, D, E, F, G, H>(World, A?, B?, C?, D?, E?, F?, G?, H?, ...Component<any>) -> QueryBundle<() -> (A, B, C, D, E, F, G, H)>),
|
query: ((World) -> Query<nil>)
|
||||||
|
& (<A>(World, Component<A>) -> Query<A>)
|
||||||
|
& (<A, B>(World, Component<A>, Component<B>) -> Query<A, B>)
|
||||||
|
& (<A, B, C>(World, Component<A>, Component<B>, Component<C>) -> Query<A, B, C>)
|
||||||
|
& (<A, B, C, D>(World, Component<A>, Component<B>, Component<C>, Component<D>) -> Query<A, B, C, D>)
|
||||||
|
& (<A, B, C, D, E>(World, Component<A>, Component<B>, Component<C>, Component<D>, Component<E>) -> Query<A, B, C, D, E>)
|
||||||
|
& (<A, B, C, D, E, F>(World, Component<A>, Component<B>, Component<C>, Component<D>, Component<E>, Component<F>) -> Query<A, B, C, D, E, F>)
|
||||||
|
& (<A, B, C, D, E, F, G>(
|
||||||
|
World,
|
||||||
|
Component<A>,
|
||||||
|
Component<B>,
|
||||||
|
Component<C>,
|
||||||
|
Component<D>,
|
||||||
|
Component<E>,
|
||||||
|
Component<F>,
|
||||||
|
Component<G>
|
||||||
|
) -> Query<A, B, C, D, E, F, G>)
|
||||||
|
& (<A, B, C, D, E, F, G, H>(
|
||||||
|
World,
|
||||||
|
Component<A>,
|
||||||
|
Component<B>,
|
||||||
|
Component<C>,
|
||||||
|
Component<D>,
|
||||||
|
Component<E>,
|
||||||
|
Component<F>,
|
||||||
|
Component<G>,
|
||||||
|
Component<H>,
|
||||||
|
...Component<any>
|
||||||
|
) -> Query<A, B, C, D, E, F, G, H>),
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Record = {
|
export type Record = {
|
||||||
|
|
@ -3696,18 +3706,18 @@ local function world_new(DEBUG: boolean?)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function world_component(world: world): i53
|
local function world_component(world: world): i53
|
||||||
max_component_id += 1
|
if max_component_id + 1 > HI_COMPONENT_ID then
|
||||||
if max_component_id > HI_COMPONENT_ID then
|
|
||||||
-- IDs are partitioned into ranges because component IDs are not nominal,
|
-- IDs are partitioned into ranges because component IDs are not nominal,
|
||||||
-- so it needs to error when IDs intersect into the entity range.
|
-- so it needs to error when IDs intersect into the entity range.
|
||||||
error("Too many components, consider using world:entity() instead to create components.")
|
error("Too many components, consider using world:entity() instead to create components.")
|
||||||
end
|
end
|
||||||
|
max_component_id += 1
|
||||||
world.max_component_id = max_component_id
|
world.max_component_id = max_component_id
|
||||||
world_add(world, max_component_id, EcsComponent)
|
world_add(world, max_component_id, EcsComponent)
|
||||||
|
|
||||||
return max_component_id
|
return max_component_id
|
||||||
end
|
end
|
||||||
|
|
||||||
world.entity = world_entity
|
world.entity = world_entity
|
||||||
world.query = world_query :: any
|
world.query = world_query :: any
|
||||||
world.remove = world_remove
|
world.remove = world_remove
|
||||||
|
|
@ -3757,15 +3767,24 @@ local function world_new(DEBUG: boolean?)
|
||||||
]], 2)
|
]], 2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function DEBUG_ID_IS_INVALID_PAIR(id: i53)
|
local function DEBUG_ID_IS_INVALID(id: number)
|
||||||
if ECS_ID_IS_WILDCARD(id) then
|
if ECS_IS_PAIR(id) then
|
||||||
error([[
|
if ECS_ID_IS_WILDCARD(id) then
|
||||||
You tried to pass in a wildcard pair. This is strictly
|
error([[
|
||||||
forbidden. You probably want to iterate the targets and
|
You tried to pass in a wildcard pair. This is strictly
|
||||||
remove them one by one. You can also populate a list of
|
forbidden. You probably want to iterate the targets and
|
||||||
targets to remove and use jecs.bulk_remove.
|
remove them one by one. You can also populate a list of
|
||||||
]], 2)
|
targets to remove and use jecs.bulk_remove.
|
||||||
|
]], 2)
|
||||||
|
end
|
||||||
|
local first = ecs_pair_first(world, id)
|
||||||
|
local second = ecs_pair_second(world, id)
|
||||||
|
|
||||||
|
assert(world:contains(first), `The first element of the pair is invalid because it is not alive in the entity index. You might be holding onto an outdated handle or may have forward declared ids via jecs.component() and jecs.tag(). In the latter case, ensure that their calls precede jecs.world() or otherwise they will not register correctly`)
|
||||||
|
assert(world:contains(second), `The second element of the pair is invalid because it is not alive in the entity index. You might be holding onto an outdated handle or may have forward declared ids via jecs.component() and jecs.tag(). In the latter case, ensure that their calls precede jecs.world() or otherwise they will not register correctly`)
|
||||||
|
else
|
||||||
|
assert(world:contains(id), `The component in your parameters is invalid because it is not alive in the entity index. You might be holding onto an outdated handle or may have forward declared ids via jecs.component() and jecs.tag(). In the latter case, ensure that their calls precede jecs.world() or otherwise they will not register correctly`)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3784,21 +3803,21 @@ local function world_new(DEBUG: boolean?)
|
||||||
local function world_remove_checked(world: world, entity: i53, id: i53)
|
local function world_remove_checked(world: world, entity: i53, id: i53)
|
||||||
DEBUG_IS_DELETING_ENTITY(entity)
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
DEBUG_IS_INVALID_ENTITY(entity)
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
DEBUG_ID_IS_INVALID_PAIR(id)
|
DEBUG_ID_IS_INVALID(id)
|
||||||
|
|
||||||
world_remove(world, entity, id)
|
world_remove(world, entity, id)
|
||||||
end
|
end
|
||||||
local function world_add_checked(world: world, entity: i53, id: i53)
|
local function world_add_checked(world: world, entity: i53, id: i53)
|
||||||
DEBUG_IS_DELETING_ENTITY(entity)
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
DEBUG_IS_INVALID_ENTITY(entity)
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
DEBUG_ID_IS_INVALID_PAIR(id)
|
DEBUG_ID_IS_INVALID(id)
|
||||||
|
|
||||||
world_add(world, entity, id)
|
world_add(world, entity, id)
|
||||||
end
|
end
|
||||||
local function world_set_checked(world: world, entity: i53, id: i53, value: any)
|
local function world_set_checked(world: world, entity: i53, id: i53, value: any)
|
||||||
DEBUG_IS_DELETING_ENTITY(entity)
|
DEBUG_IS_DELETING_ENTITY(entity)
|
||||||
DEBUG_IS_INVALID_ENTITY(entity)
|
DEBUG_IS_INVALID_ENTITY(entity)
|
||||||
DEBUG_ID_IS_INVALID_PAIR(id)
|
DEBUG_ID_IS_INVALID(id)
|
||||||
|
|
||||||
world_set(world, entity, id, value)
|
world_set(world, entity, id, value)
|
||||||
end
|
end
|
||||||
|
|
@ -3875,7 +3894,98 @@ local function ecs_entity_record(world: world, entity: i53)
|
||||||
return entity_index_try_get(world.entity_index, entity)
|
return entity_index_try_get(world.entity_index, entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function entity_index_ensure(entity_index: entityindex, e: i53)
|
||||||
|
local eindex_sparse_array = entity_index.sparse_array
|
||||||
|
local eindex_dense_array = entity_index.dense_array
|
||||||
|
local index = ECS_ID(e)
|
||||||
|
local alive_count = entity_index.alive_count
|
||||||
|
local r = eindex_sparse_array[index]
|
||||||
|
if r then
|
||||||
|
local dense = r.dense
|
||||||
|
|
||||||
|
if dense == 0 then
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
r.dense = alive_count
|
||||||
|
eindex_dense_array[alive_count] = e
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If dense > 0, check if there's an existing entity at that position
|
||||||
|
local existing_entity = eindex_dense_array[dense]
|
||||||
|
if existing_entity and existing_entity ~= e then
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
r.dense = alive_count
|
||||||
|
eindex_dense_array[alive_count] = e
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
|
return e
|
||||||
|
else
|
||||||
|
local max_id = entity_index.max_id
|
||||||
|
|
||||||
|
if index > max_id then
|
||||||
|
for i = max_id + 1, index - 1 do
|
||||||
|
if not eindex_sparse_array[i] then
|
||||||
|
-- NOTE(marcus): We have to do this check to see if
|
||||||
|
-- they exist first because world:range() may have
|
||||||
|
-- pre-populated some slots already.
|
||||||
|
end
|
||||||
|
|
||||||
|
eindex_sparse_array[i] = { dense = 0 } :: record
|
||||||
|
end
|
||||||
|
entity_index.max_id = index
|
||||||
|
end
|
||||||
|
|
||||||
|
alive_count += 1
|
||||||
|
entity_index.alive_count = alive_count
|
||||||
|
eindex_dense_array[alive_count] = e
|
||||||
|
|
||||||
|
r = { dense = alive_count } :: record
|
||||||
|
eindex_sparse_array[index] = r
|
||||||
|
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new(world: world)
|
||||||
|
local e = ENTITY_INDEX_NEW_ID(world.entity_index)
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new_low_id(world: world)
|
||||||
|
local entity_index = world.entity_index
|
||||||
|
|
||||||
|
local e = 0
|
||||||
|
if world.max_component_id < HI_COMPONENT_ID then
|
||||||
|
while true do
|
||||||
|
world.max_component_id += 1
|
||||||
|
e = world.max_component_id
|
||||||
|
if not (entity_index_try_get_any(entity_index, e) ~= nil and e <= HI_COMPONENT_ID) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if e == 0 or e >= HI_COMPONENT_ID then
|
||||||
|
e = ENTITY_INDEX_NEW_ID(entity_index)
|
||||||
|
else
|
||||||
|
entity_index_ensure(entity_index, e)
|
||||||
|
end
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new_w_id(world: world, id: i53)
|
||||||
|
local e = ENTITY_INDEX_NEW_ID(world.entity_index)
|
||||||
|
world.add(world, e, id)
|
||||||
|
return e
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
new = new,
|
||||||
|
new_w_id = new_w_id,
|
||||||
|
new_low_id = new_low_id,
|
||||||
|
|
||||||
--- Create the world
|
--- Create the world
|
||||||
world = world_new :: (boolean?) -> World,
|
world = world_new :: (boolean?) -> World,
|
||||||
World = {
|
World = {
|
||||||
|
|
@ -3892,7 +4002,7 @@ return {
|
||||||
--- OnAdd Hook to detect added components (see more how_to/110_hooks.luau)
|
--- OnAdd Hook to detect added components (see more how_to/110_hooks.luau)
|
||||||
OnAdd = (EcsOnAdd :: any) :: Component<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
OnAdd = (EcsOnAdd :: any) :: Component<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||||
--- OnRemove Hook to detect removed components (see more how_to/110_hooks.luau)
|
--- OnRemove Hook to detect removed components (see more how_to/110_hooks.luau)
|
||||||
OnRemove = (EcsOnRemove :: any) :: Component<<T>(entity: Entity, id: Id<T>) -> ()>,
|
OnRemove = (EcsOnRemove :: any) :: Component<<T>(entity: Entity, id: Id<T>, delete: boolean?) -> ()>,
|
||||||
--- OnChange Hook to detect mutations (see more how_to/110_hooks.luau)
|
--- OnChange Hook to detect mutations (see more how_to/110_hooks.luau)
|
||||||
OnChange = (EcsOnChange :: any) :: Component<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
OnChange = (EcsOnChange :: any) :: Component<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||||
--- Relationship to define the parent of an entity
|
--- Relationship to define the parent of an entity
|
||||||
|
|
@ -3959,6 +4069,7 @@ return {
|
||||||
entity_index_try_get_any = entity_index_try_get_any :: (EntityIndex, Entity) -> Record,
|
entity_index_try_get_any = entity_index_try_get_any :: (EntityIndex, Entity) -> Record,
|
||||||
entity_index_is_alive = entity_index_is_alive :: (EntityIndex, Entity) -> boolean,
|
entity_index_is_alive = entity_index_is_alive :: (EntityIndex, Entity) -> boolean,
|
||||||
entity_index_new_id = ENTITY_INDEX_NEW_ID :: (EntityIndex) -> Entity,
|
entity_index_new_id = ENTITY_INDEX_NEW_ID :: (EntityIndex) -> Entity,
|
||||||
|
entity_index_ensure = entity_index_ensure,
|
||||||
|
|
||||||
Query = Query,
|
Query = Query,
|
||||||
|
|
||||||
|
|
@ -3971,7 +4082,6 @@ return {
|
||||||
|
|
||||||
find_observers = find_observers :: (World, Component, Component) -> { Observer },
|
find_observers = find_observers :: (World, Component, Component) -> { Observer },
|
||||||
|
|
||||||
-- Inwards facing API for testing
|
|
||||||
ECS_ID = ECS_ENTITY_T_LO :: (Entity) -> number,
|
ECS_ID = ECS_ENTITY_T_LO :: (Entity) -> number,
|
||||||
ECS_GENERATION_INC = ECS_GENERATION_INC :: (Entity) -> Entity,
|
ECS_GENERATION_INC = ECS_GENERATION_INC :: (Entity) -> Entity,
|
||||||
ECS_GENERATION = ECS_GENERATION :: (Entity) -> number,
|
ECS_GENERATION = ECS_GENERATION :: (Entity) -> number,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue