-
Notifications
You must be signed in to change notification settings - Fork 26
Open
Description
Problem
Mutating arrays/objects inside a trait “in place” can fail to trigger updates; developers must remember to reassign or call entity.changed(trait) manually. This is easy to miss and leads to confusing “UI did not update” bugs. There is an open issue reporting cases where useTraitEffect updates but useTrait does not, highlighting change‑detection pain in real projects.
References:
- Issue: useTraitEffect updates but not useTrait #150
- README: https://github.com/pmndrs/koota#use-trait and https://github.com/pmndrs/koota#use-traiteffect.
Proposal
Provide a small, ergonomic mutation primitive that guarantees change signaling:
// Always marks the trait as changed if anything inside is modified.
entity.update(Inventory, inv => {
// Nested object + array mutations should be safe:
const stack = inv.stacks.find(s => s.itemId === newItemId)
if (stack) stack.count += 1
else inv.stacks.push({ itemId: newItemId, count: 1 })
})Principles
- The updater receives a draft (or equivalent safe handle) to mutate.
- After the updater returns, the engine calls
changed(trait)exactly once if anything meaningful changed. - Works for nested objects/arrays; trivial scalar writes continue to behave as they do today.
Prior art (authoritative references)
- Flecs:
ecs_set/ecs_set_idtreat writes as explicit “set” operations that update state and notify systems.- Getting & Setting docs: https://www.flecs.dev/flecs/group__flecs__c__getting__setting.html
- Repo (overview): https://github.com/SanderMertens/flecs
- Bevy: change detection (
Changed<T>,DetectChanges) builds on clear “you wrote to it” signals; a single update primitive makes this consistent and hard to forget.- Query/filters index: https://docs.rs/bevy/latest/bevy/ecs/query/index.html
Performance considerations
- No heavy deep proxies required: the contract should not mandate Immer‑style deep proxies; implementations can fast‑path scalars and use lightweight bookkeeping for arrays/objects.
- One signal per call: even if the updater makes many writes, emit at most one
changed(trait)to avoid redundant downstream work. - SoA / typed arrays: for traits backed by typed arrays, direct index writes must remain fast;
updateshould not add overhead in these cases.
Edge cases to define
- Exceptions inside the updater: decide whether partial mutations should mark changed; safest is to mark changed only if the updater completes successfully.
- Re‑entrancy: prevent calling
updateon the same trait recursively. - Interop with future
Changed<T>filters: ensure this path triggers those reliably when richer query filters land.
Related Koota discussion
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels