Open
Conversation
k88hudson-cfa
approved these changes
Mar 3, 2026
Collaborator
k88hudson-cfa
left a comment
There was a problem hiding this comment.
This looks OK to me / passes under tree borrows, I'd prefer if we could find a way to get rid of the unsafe or isolate it in data structures that are stable and well-tested
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch reworks entity-property indexing by removing interior mutability from index storage and switching from lazy (query-time) to eager (write-time) index maintenance.
The advantages
Performance
It turns out that lazy indexing wasn't the performance advantage we thought it was. In fact, this refactor shows a consistent but very small performance improvement by indexing eagerly. This alone is probably not good enough justification for the change. I am posting my local benchmark results separately.
Simpler code
Removing
RefCellfromPropertyIndexmakes indexing explicit: reads take shared references, writes take mutable references, and the compiler enforces the distinction — no more runtime borrow guards or interior-mutable control flow. For example, there is less API noise from lazy-era plumbing: we can remove now-unusedcontextpassthrough parameters. Index maintenance is also consolidated: old/new value updates now happen in one place rather than being split across partial-event creation and emission. The result is code that's simpler to follow, review, and maintain.EntitySetandEntitySetIteratorcan beCloneIn the current (lazy/
RefCell) implementation ofEntitySetandEntitySetIterator, the two types cannot implementClone, because some source set types wrap aRefholding a reference to an index set, which cannot beClone. (See #786.) Removing indexes fromRefCellallow these types to hold a&IndexSetinstead, which is triviallyCopy.The Price: Unfortunate use of
unsafeThe cost of removing
RefCellis the introduction of two uses ofunsafe:ContextEntitiesExt::add_entityContextEntitiesExt::index_propertyIn both cases,
unsafeis used to callproperty_store.index_unindexed_entities_for_all_properties(&*context_ptr). The problem boils down to the fact that we want to mutate the index in the property value store while also supplying an immutable reference tocontextso that we can compute derived properties withP::compute_derived(context, entity_id). Rust actually allows a version of this, "partial borrows," for the simple case of a multi-field struct:This doesn't work across method boundaries: methods borrow the entire struct, even though in our case we are mutating a different part of
contextthan what is being read.Implementation
FullIndexandValueCountIndexare removed fromRefCellinPropertyIndex, so index mutation now requires&mut selfand index access returns plain references instead ofRefguards. This eliminates runtime borrow checks and simplifies reference types through query iteration.Index consistency is now maintained at write time rather than via lazy catch-up during queries. Entity creation writes initial values and immediately syncs all enabled indexes for that type. Property updates use a two-phase flow: snapshot previous values, write the new value, then update indexes (remove old / add new) during partial-event emission.
Cleanup:
set_propertyalgorithm notescontext: &Contextparameters from index set/count lookup helpers and updated all call sitesproperty_store.create_partial_property_changeandpartial_property_change.emit_in_context