Skip to content

Commit 0fe23e1

Browse files
authored
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
1 parent b73d7e1 commit 0fe23e1

File tree

5 files changed

+120
-53
lines changed

5 files changed

+120
-53
lines changed

.luaurc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"aliases": {
3-
"jecs": "C:/Users/Marcus/Documents/packages/jecs/src",
4-
"testkit": "C:/Users/Marcus/Documents/packages/jecs/testkit"
3+
"jecs": "src",
4+
"testkit": "testkit"
55
}
66
}

CHANGELOG.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
1010

1111
## [Unreleased]
1212

13+
### Added
14+
15+
- Added `query:replace(function(...T) return ...U end)` for replacing components in place
16+
- Method is fast pathed to replacing the data to the components for each corresponding entity
17+
1318
### Changed
1419

1520
- 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
2530

2631
### Added
2732

28-
- Added `world:parent(entity)` and `jecs.ChildOf` respectively as first class citizen for building parent-child relationships.
33+
- Added `world:parent(entity)` and `jecs.ChildOf` respectively as first class citizen for building parent-child relationships.
2934
- Give a parent to an entity with `world:add($source, pair(ChildOf, $target))`
3035
- Use `world:parent(entity)` to find the target of the relationship
3136
- Added user-facing Luau types
3237

3338
### Changed
34-
- Improved iteration speeds 20-40% by manually indexing rather than using `next()` :scream:
39+
- Improved iteration speeds 20-40% by manually indexing rather than using `next()` :scream:
3540

3641

3742
## [0.1.1] - 2024-05-19
@@ -48,19 +53,19 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
4853

4954
## [0.1.0-rc.6] - 2024-05-13
5055

51-
### Added
56+
### Added
5257

5358
- Added a `jecs.Wildcard` term
5459
- it lets you query any partially matched pairs
5560

5661
## [0.1.0-rc.5] - 2024-05-10
5762

58-
### Added
63+
### Added
5964

6065
- Added Entity relationships for creating logical connections between entities
6166
- Added `world:__iter method` which allows for iteration over the whole world to get every entity
6267
- used for reconciling whole worlds such as via replication, saving/loading, etc
63-
- Added `world:add(entity, component)` which adds a component to the entity
68+
- Added `world:add(entity, component)` which adds a component to the entity
6469
- it is an idempotent function, so calling it twice and in any order should be fine
6570

6671
### Fixed
@@ -89,7 +94,7 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
8994
## [0.0.0-prototype.rc.2] - 2024-04-26
9095

9196
### Changed
92-
- Optimized the creation of the query
97+
- Optimized the creation of the query
9398
- It will now finds the smallest archetype map to iterate over
9499
- Optimized the query iterator
95100
- 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
109114
[0.0.0-prototype-rc.3]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.3
110115
[0.0.0-prototype.rc.2]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.2
111116
[0.0.0-prototype-rc.1]: https://github.com/ukendio/jecs/releases/tag/v0.0.0-prototype.rc.1
112-
113-
114-
115-
116-
117-
118-
119-
120-

benches/query.luau

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ local function TITLE(title: string)
77
print()
88
print(testkit.color.white(title))
99
end
10+
1011
local jecs = require("@jecs")
1112
local mirror = require("../mirror/init")
1213

src/init.luau

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ TODO:
4242
index: number,
4343
count: number,
4444
column: number
45-
}
45+
}
4646
4747
]]
4848

@@ -131,7 +131,7 @@ local function ECS_ENTITY_T_LO(e: i53): i24
131131
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
132132
end
133133

134-
local function STRIP_GENERATION(e: i53): i24
134+
local function STRIP_GENERATION(e: i53): i24
135135
return ECS_ENTITY_T_LO(e)
136136
end
137137

@@ -149,7 +149,7 @@ local function getAlive(index: EntityIndex, e: i24): i53
149149
if id then
150150
local currentGeneration = ECS_GENERATION(id)
151151
local gen = ECS_GENERATION(e)
152-
if gen == currentGeneration then
152+
if gen == currentGeneration then
153153
return id
154154
end
155155

@@ -159,7 +159,7 @@ local function getAlive(index: EntityIndex, e: i24): i53
159159
error(ERROR_ENTITY_NOT_ALIVE)
160160
end
161161

162-
local function sparseGet(entityIndex, id)
162+
local function sparseGet(entityIndex, id)
163163
return entityIndex.sparse[getAlive(entityIndex, id)]
164164
end
165165

@@ -353,7 +353,7 @@ end
353353
-- TODO:
354354
-- should have an additional `nth` parameter which selects the nth target
355355
-- this is important when an entity can have multiple relationships with the same target
356-
local function target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
356+
local function target(world: World, entity: i53, relation: i24--[[, nth: number]]): i24?
357357
local entityIndex = world.entityIndex
358358
local record = entityIndex.sparse[entity]
359359
local archetype = record.archetype
@@ -374,7 +374,7 @@ local function target(world: World, entity: i53, relation: i24--[[, nth: number]
374374
return ECS_PAIR_OBJECT(entityIndex, archetype.types[archetypeRecord])
375375
end
376376

377-
local function parent(world: World, entity: i53)
377+
local function parent(world: World, entity: i53)
378378
return target(world, entity, EcsChildOf)
379379
end
380380

@@ -462,7 +462,7 @@ local function add(world: World, entityId: i53, componentId: i53)
462462
end
463463

464464
-- Symmetric like `World.add` but idempotent
465-
local function set(world: World, entityId: i53, componentId: i53, data: unknown)
465+
local function set(world: World, entityId: i53, componentId: i53, data: unknown)
466466
local record = world.entityIndex.sparse[entityId]
467467
local from = record.archetype
468468
local to = archetypeTraverseAdd(world, componentId, from)
@@ -600,17 +600,17 @@ local function delete(world: World, entityId: i53)
600600

601601
end
602602

603-
local function clear(world: World, entityId: i53)
603+
local function clear(world: World, entityId: i53)
604604
--TODO: use sparse_get (stashed)
605605
local record = world.entityIndex.sparse[entityId]
606-
if not record then
606+
if not record then
607607
return
608608
end
609609

610610
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
611611
local archetype = record.archetype
612612

613-
if archetype == nil or archetype == ROOT_ARCHETYPE then
613+
if archetype == nil or archetype == ROOT_ARCHETYPE then
614614
return
615615
end
616616

@@ -662,21 +662,29 @@ local function noop(_self: Query, ...): () -> ()
662662
end
663663

664664
local EmptyQuery = {
665-
__iter = noop,
666-
without = noop,
665+
__iter = iterNoop,
666+
next = noop,
667+
replace = noop,
668+
without = function(self)
669+
return self
670+
end
667671
}
668-
EmptyQuery.__index = EmptyQuery
669-
setmetatable(EmptyQuery, EmptyQuery)
670672

671673
export type Query = typeof(EmptyQuery)
672674

673675
type CompatibleArchetype = { archetype: Archetype, indices: { number } }
674676

675-
local function preparedQuery(compatibleArchetypes: { Archetype },
676-
components: { i53? }, indices: { { number } })
677+
local function replaceMult(row, columns, ...)
678+
for i, column in columns do
679+
column[row] = select(i, ...)
680+
end
681+
end
682+
683+
local function preparedQuery(compatibleArchetypes: { Archetype },
684+
components: { i53? }, indices: { { number } })
677685

678686
local queryLength = #components
679-
687+
680688
local lastArchetype = 1
681689
local archetype: Archetype = compatibleArchetypes[lastArchetype]
682690

@@ -686,16 +694,16 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
686694

687695
local queryOutput = {}
688696

689-
local entities = archetype.entities
697+
local entities = archetype.entities
690698
local i = #entities
691699

692700
local function queryNext(): ...any
693701
local entityId = entities[i]
694-
while entityId == nil do
702+
while entityId == nil do
695703
lastArchetype += 1
696704
archetype = compatibleArchetypes[lastArchetype]
697-
698-
if not archetype then
705+
706+
if not archetype then
699707
return
700708
end
701709

@@ -782,34 +790,77 @@ local function preparedQuery(compatibleArchetypes: { Archetype },
782790

783791
return self
784792
end
785-
786-
local it = {
787-
__iter = function()
788-
lastArchetype = 1
789-
archetype = compatibleArchetypes[1]
790-
entities = archetype.entities
791-
i = #entities
792793

793-
return queryNext
794-
end,
794+
local function iter()
795+
lastArchetype = 1
796+
archetype = compatibleArchetypes[1]
797+
entities = archetype.entities
798+
i = #entities
799+
800+
return queryNext
801+
end
802+
803+
local function replace(_, fn)
804+
for i, archetype in compatibleArchetypes do
805+
local tr = indices[i]
806+
local columns = archetype.columns
807+
808+
for row in archetype.entities do
809+
if queryLength == 1 then
810+
local a = columns[tr[1]]
811+
local pa = fn(a[row])
812+
813+
a[row] = pa
814+
elseif queryLength == 2 then
815+
local a = columns[tr[1]]
816+
local b = columns[tr[2]]
817+
818+
a[row], b[row] = fn(a[row], b[row])
819+
elseif queryLength == 3 then
820+
local a = columns[tr[1]]
821+
local b = columns[tr[2]]
822+
local c = columns[tr[3]]
823+
824+
a[row], b[row], c[row] = fn(a[row], b[row], c[row])
825+
elseif queryLength == 4 then
826+
local a = columns[tr[1]]
827+
local b = columns[tr[2]]
828+
local c = columns[tr[3]]
829+
local d = columns[tr[4]]
830+
831+
a[row], b[row], c[row], d[row] = fn(
832+
a[row], b[row], c[row], d[row])
833+
else
834+
for i = 1, queryLength do
835+
queryOutput[i] = columns[tr[i]][row]
836+
end
837+
replaceMult(row, columns, fn(unpack(queryOutput)))
838+
end
839+
end
840+
end
841+
end
842+
843+
local it = {
844+
__iter = iter,
795845
next = queryNext,
796-
without = without
846+
without = without,
847+
replace = replace
797848
}
798849

799850
return setmetatable(it, it) :: any
800851
end
801852

802-
local function query(world: World, ...: number): Query
853+
local function query(world: World, ...: number): Query
803854
-- breaking?
804855
if (...) == nil then
805856
error("Missing components")
806857
end
807858

808859
local indices: { { number } } = {}
809-
local compatibleArchetypes: { Archetype } = {}
860+
local compatibleArchetypes: { Archetype } = {}
810861
local length = 0
811862

812-
local components: { number } = { ... }
863+
local components: { number } = { ... }
813864
local archetypes: { Archetype } = world.archetypes :: any
814865

815866
local firstArchetypeMap: ArchetypeMap
@@ -989,7 +1040,7 @@ export type WorldShim = typeof(setmetatable(
9891040
local World = {}
9901041
World.__index = World
9911042

992-
function World.new()
1043+
function World.new()
9931044
local self = setmetatable({
9941045
archetypeIndex = {} :: { [string]: Archetype },
9951046
archetypes = {} :: Archetypes,

tests/world.luau

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,28 @@ TEST("world", function()
449449
count += 1
450450
end
451451

452-
print(count)
453452
CHECK(count == 2)
454453
end
454+
455+
do CASE "should replace component data"
456+
local world = jecs.World.new()
457+
local A = world:component()
458+
local B = world:component()
459+
local C = world:component()
460+
461+
local e = world:entity()
462+
world:set(e, A, 1)
463+
world:set(e, B, true)
464+
world:set(e, C, "hello ")
465+
466+
world:query(A, B, C):replace(function(a, b, c)
467+
return a * 2, not b, c.."world"
468+
end)
469+
470+
CHECK(world:get(e, A) == 2)
471+
CHECK(world:get(e, B) == false)
472+
CHECK(world:get(e, C) == "hello world")
473+
end
455474
end)
456475

457476
FINISH()

0 commit comments

Comments
 (0)