mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-24 17:10:03 +00:00
Add replace method to query (#46)
* Add replace function * Add next method * Remove tostring * Fix EmptyQuery * add replace method * merge conflicts * return self in without * Make aliases relative * Add test * add to changelog
This commit is contained in:
parent
b73d7e12b7
commit
0fe23e151c
5 changed files with 120 additions and 53 deletions
4
.luaurc
4
.luaurc
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"aliases": {
|
||||
"jecs": "C:/Users/Marcus/Documents/packages/jecs/src",
|
||||
"testkit": "C:/Users/Marcus/Documents/packages/jecs/testkit"
|
||||
"jecs": "src",
|
||||
"testkit": "testkit"
|
||||
}
|
||||
}
|
||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -10,6 +10,11 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `query:replace(function(...T) return ...U end)` for replacing components in place
|
||||
- Method is fast pathed to replacing the data to the components for each corresponding entity
|
||||
|
||||
### Changed
|
||||
|
||||
- Iterator now goes backwards instead to prevent common cases of iterator invalidation
|
||||
|
@ -25,13 +30,13 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
|
|||
|
||||
### Added
|
||||
|
||||
- Added `world:parent(entity)` and `jecs.ChildOf` respectively as first class citizen for building parent-child relationships.
|
||||
- Added `world:parent(entity)` and `jecs.ChildOf` respectively as first class citizen for building parent-child relationships.
|
||||
- Give a parent to an entity with `world:add($source, pair(ChildOf, $target))`
|
||||
- Use `world:parent(entity)` to find the target of the relationship
|
||||
- Added user-facing Luau types
|
||||
|
||||
### Changed
|
||||
- Improved iteration speeds 20-40% by manually indexing rather than using `next()` :scream:
|
||||
- Improved iteration speeds 20-40% by manually indexing rather than using `next()` :scream:
|
||||
|
||||
|
||||
## [0.1.1] - 2024-05-19
|
||||
|
@ -48,19 +53,19 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
|
|||
|
||||
## [0.1.0-rc.6] - 2024-05-13
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
- Added a `jecs.Wildcard` term
|
||||
- it lets you query any partially matched pairs
|
||||
|
||||
## [0.1.0-rc.5] - 2024-05-10
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
- Added Entity relationships for creating logical connections between entities
|
||||
- Added `world:__iter method` which allows for iteration over the whole world to get every entity
|
||||
- used for reconciling whole worlds such as via replication, saving/loading, etc
|
||||
- Added `world:add(entity, component)` which adds a component to the entity
|
||||
- Added `world:add(entity, component)` which adds a component to the entity
|
||||
- it is an idempotent function, so calling it twice and in any order should be fine
|
||||
|
||||
### Fixed
|
||||
|
@ -89,7 +94,7 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
|
|||
## [0.0.0-prototype.rc.2] - 2024-04-26
|
||||
|
||||
### Changed
|
||||
- Optimized the creation of the query
|
||||
- Optimized the creation of the query
|
||||
- It will now finds the smallest archetype map to iterate over
|
||||
- Optimized the query iterator
|
||||
- It will now populates iterator with columns for faster indexing
|
||||
|
@ -109,12 +114,3 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
|
|||
[0.0.0-prototype-rc.3]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.3
|
||||
[0.0.0-prototype.rc.2]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.2
|
||||
[0.0.0-prototype-rc.1]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ local function TITLE(title: string)
|
|||
print()
|
||||
print(testkit.color.white(title))
|
||||
end
|
||||
|
||||
local jecs = require("@jecs")
|
||||
local mirror = require("../mirror/init")
|
||||
|
||||
|
|
121
src/init.luau
121
src/init.luau
|
@ -42,7 +42,7 @@ TODO:
|
|||
index: number,
|
||||
count: number,
|
||||
column: number
|
||||
}
|
||||
}
|
||||
|
||||
]]
|
||||
|
||||
|
@ -131,7 +131,7 @@ local function ECS_ENTITY_T_LO(e: i53): i24
|
|||
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
|
||||
end
|
||||
|
||||
local function STRIP_GENERATION(e: i53): i24
|
||||
local function STRIP_GENERATION(e: i53): i24
|
||||
return ECS_ENTITY_T_LO(e)
|
||||
end
|
||||
|
||||
|
@ -149,7 +149,7 @@ local function getAlive(index: EntityIndex, e: i24): i53
|
|||
if id then
|
||||
local currentGeneration = ECS_GENERATION(id)
|
||||
local gen = ECS_GENERATION(e)
|
||||
if gen == currentGeneration then
|
||||
if gen == currentGeneration then
|
||||
return id
|
||||
end
|
||||
|
||||
|
@ -159,7 +159,7 @@ local function getAlive(index: EntityIndex, e: i24): i53
|
|||
error(ERROR_ENTITY_NOT_ALIVE)
|
||||
end
|
||||
|
||||
local function sparseGet(entityIndex, id)
|
||||
local function sparseGet(entityIndex, id)
|
||||
return entityIndex.sparse[getAlive(entityIndex, id)]
|
||||
end
|
||||
|
||||
|
@ -353,7 +353,7 @@ end
|
|||
-- TODO:
|
||||
-- should have an additional `nth` parameter which selects the nth target
|
||||
-- this is important when an entity can have multiple relationships with the same target
|
||||
local function target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
|
||||
local function target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
|
||||
local entityIndex = world.entityIndex
|
||||
local record = entityIndex.sparse[entity]
|
||||
local archetype = record.archetype
|
||||
|
@ -374,7 +374,7 @@ local function target(world: World, entity: i53, relation: i24--[[, nth: number]
|
|||
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord])
|
||||
end
|
||||
|
||||
local function parent(world: World, entity: i53)
|
||||
local function parent(world: World, entity: i53)
|
||||
return target(world, entity, EcsChildOf)
|
||||
end
|
||||
|
||||
|
@ -462,7 +462,7 @@ local function add(world: World, entityId: i53, componentId: i53)
|
|||
end
|
||||
|
||||
-- Symmetric like `World.add` but idempotent
|
||||
local function set(world: World, entityId: i53, componentId: i53, data: unknown)
|
||||
local function set(world: World, entityId: i53, componentId: i53, data: unknown)
|
||||
local record = world.entityIndex.sparse[entityId]
|
||||
local from = record.archetype
|
||||
local to = archetypeTraverseAdd(world, componentId, from)
|
||||
|
@ -600,17 +600,17 @@ local function delete(world: World, entityId: i53)
|
|||
|
||||
end
|
||||
|
||||
local function clear(world: World, entityId: i53)
|
||||
local function clear(world: World, entityId: i53)
|
||||
--TODO: use sparse_get (stashed)
|
||||
local record = world.entityIndex.sparse[entityId]
|
||||
if not record then
|
||||
if not record then
|
||||
return
|
||||
end
|
||||
|
||||
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
||||
local archetype = record.archetype
|
||||
|
||||
if archetype == nil or archetype == ROOT_ARCHETYPE then
|
||||
if archetype == nil or archetype == ROOT_ARCHETYPE then
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -662,21 +662,29 @@ local function noop(_self: Query, ...): () -> ()
|
|||
end
|
||||
|
||||
local EmptyQuery = {
|
||||
__iter = noop,
|
||||
without = noop,
|
||||
__iter = iterNoop,
|
||||
next = noop,
|
||||
replace = noop,
|
||||
without = function(self)
|
||||
return self
|
||||
end
|
||||
}
|
||||
EmptyQuery.__index = EmptyQuery
|
||||
setmetatable(EmptyQuery, EmptyQuery)
|
||||
|
||||
export type Query = typeof(EmptyQuery)
|
||||
|
||||
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
|
||||
|
||||
local function preparedQuery(compatibleArchetypes: { Archetype },
|
||||
components: { i53? }, indices: { { number } })
|
||||
local function replaceMult(row, columns, ...)
|
||||
for i, column in columns do
|
||||
column[row] = select(i, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local function preparedQuery(compatibleArchetypes: { Archetype },
|
||||
components: { i53? }, indices: { { number } })
|
||||
|
||||
local queryLength = #components
|
||||
|
||||
|
||||
local lastArchetype = 1
|
||||
local archetype: Archetype = compatibleArchetypes[lastArchetype]
|
||||
|
||||
|
@ -686,16 +694,16 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
|
|||
|
||||
local queryOutput = {}
|
||||
|
||||
local entities = archetype.entities
|
||||
local entities = archetype.entities
|
||||
local i = #entities
|
||||
|
||||
local function queryNext(): ...any
|
||||
local entityId = entities[i]
|
||||
while entityId == nil do
|
||||
while entityId == nil do
|
||||
lastArchetype += 1
|
||||
archetype = compatibleArchetypes[lastArchetype]
|
||||
|
||||
if not archetype then
|
||||
|
||||
if not archetype then
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -782,34 +790,77 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
|
|||
|
||||
return self
|
||||
end
|
||||
|
||||
local it = {
|
||||
__iter = function()
|
||||
lastArchetype = 1
|
||||
archetype = compatibleArchetypes[1]
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
|
||||
return queryNext
|
||||
end,
|
||||
local function iter()
|
||||
lastArchetype = 1
|
||||
archetype = compatibleArchetypes[1]
|
||||
entities = archetype.entities
|
||||
i = #entities
|
||||
|
||||
return queryNext
|
||||
end
|
||||
|
||||
local function replace(_, fn)
|
||||
for i, archetype in compatibleArchetypes do
|
||||
local tr = indices[i]
|
||||
local columns = archetype.columns
|
||||
|
||||
for row in archetype.entities do
|
||||
if queryLength == 1 then
|
||||
local a = columns[tr[1]]
|
||||
local pa = fn(a[row])
|
||||
|
||||
a[row] = pa
|
||||
elseif queryLength == 2 then
|
||||
local a = columns[tr[1]]
|
||||
local b = columns[tr[2]]
|
||||
|
||||
a[row], b[row] = fn(a[row], b[row])
|
||||
elseif queryLength == 3 then
|
||||
local a = columns[tr[1]]
|
||||
local b = columns[tr[2]]
|
||||
local c = columns[tr[3]]
|
||||
|
||||
a[row], b[row], c[row] = fn(a[row], b[row], c[row])
|
||||
elseif queryLength == 4 then
|
||||
local a = columns[tr[1]]
|
||||
local b = columns[tr[2]]
|
||||
local c = columns[tr[3]]
|
||||
local d = columns[tr[4]]
|
||||
|
||||
a[row], b[row], c[row], d[row] = fn(
|
||||
a[row], b[row], c[row], d[row])
|
||||
else
|
||||
for i = 1, queryLength do
|
||||
queryOutput[i] = columns[tr[i]][row]
|
||||
end
|
||||
replaceMult(row, columns, fn(unpack(queryOutput)))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local it = {
|
||||
__iter = iter,
|
||||
next = queryNext,
|
||||
without = without
|
||||
without = without,
|
||||
replace = replace
|
||||
}
|
||||
|
||||
return setmetatable(it, it) :: any
|
||||
end
|
||||
|
||||
local function query(world: World, ...: number): Query
|
||||
local function query(world: World, ...: number): Query
|
||||
-- breaking?
|
||||
if (...) == nil then
|
||||
error("Missing components")
|
||||
end
|
||||
|
||||
local indices: { { number } } = {}
|
||||
local compatibleArchetypes: { Archetype } = {}
|
||||
local compatibleArchetypes: { Archetype } = {}
|
||||
local length = 0
|
||||
|
||||
local components: { number } = { ... }
|
||||
local components: { number } = { ... }
|
||||
local archetypes: { Archetype } = world.archetypes :: any
|
||||
|
||||
local firstArchetypeMap: ArchetypeMap
|
||||
|
@ -989,7 +1040,7 @@ export type WorldShim = typeof(setmetatable(
|
|||
local World = {}
|
||||
World.__index = World
|
||||
|
||||
function World.new()
|
||||
function World.new()
|
||||
local self = setmetatable({
|
||||
archetypeIndex = {} :: { [string]: Archetype },
|
||||
archetypes = {} :: Archetypes,
|
||||
|
|
|
@ -449,9 +449,28 @@ TEST("world", function()
|
|||
count += 1
|
||||
end
|
||||
|
||||
print(count)
|
||||
CHECK(count == 2)
|
||||
end
|
||||
|
||||
do CASE "should replace component data"
|
||||
local world = jecs.World.new()
|
||||
local A = world:component()
|
||||
local B = world:component()
|
||||
local C = world:component()
|
||||
|
||||
local e = world:entity()
|
||||
world:set(e, A, 1)
|
||||
world:set(e, B, true)
|
||||
world:set(e, C, "hello ")
|
||||
|
||||
world:query(A, B, C):replace(function(a, b, c)
|
||||
return a * 2, not b, c.."world"
|
||||
end)
|
||||
|
||||
CHECK(world:get(e, A) == 2)
|
||||
CHECK(world:get(e, B) == false)
|
||||
CHECK(world:get(e, C) == "hello world")
|
||||
end
|
||||
end)
|
||||
|
||||
FINISH()
|
||||
|
|
Loading…
Reference in a new issue