2828 * @param <Tuple_>
2929 */
3030@ NullMarked
31- public final class RecordAndReplayPropagator <Tuple_ extends AbstractTuple >
32- implements Propagator {
31+ public final class RecordAndReplayPropagator <Tuple_ extends AbstractTuple > implements Propagator {
3332
3433 private final Set <Object > retractQueue ;
3534 private final Set <Object > insertQueue ;
@@ -42,32 +41,29 @@ public final class RecordAndReplayPropagator<Tuple_ extends AbstractTuple>
4241 private final Supplier <BavetPrecomputeBuildHelper <Tuple_ >> precomputeBuildHelperSupplier ;
4342 private final UnaryOperator <Tuple_ > internalTupleToOutputTupleMapper ;
4443 private final Map <Object , List <Tuple_ >> objectToOutputTuplesMap ;
45- private final Map <Class <?>, Boolean > objectClassToIsEntitySourceClass ;
44+ private final Set <Object > alreadyUpdatingSet = Collections .newSetFromMap (new IdentityHashMap <>());
45+ private final Map <Class <?>, Boolean > objectClassToIsEntitySourceClassMap ;
4646
4747 private final StaticPropagationQueue <Tuple_ > propagationQueue ;
4848
49- public RecordAndReplayPropagator (
50- Supplier <BavetPrecomputeBuildHelper <Tuple_ >> precomputeBuildHelperSupplier ,
51- UnaryOperator <Tuple_ > internalTupleToOutputTupleMapper ,
52- TupleLifecycle <Tuple_ > nextNodesTupleLifecycle , int size ) {
49+ public RecordAndReplayPropagator (Supplier <BavetPrecomputeBuildHelper <Tuple_ >> precomputeBuildHelperSupplier ,
50+ UnaryOperator <Tuple_ > internalTupleToOutputTupleMapper , TupleLifecycle <Tuple_ > nextNodesTupleLifecycle , int size ) {
5351 this .precomputeBuildHelperSupplier = precomputeBuildHelperSupplier ;
5452 this .internalTupleToOutputTupleMapper = internalTupleToOutputTupleMapper ;
5553 this .objectToOutputTuplesMap = CollectionUtils .newIdentityHashMap (size );
5654
5755 // Guesstimate that updates are dominant.
5856 this .retractQueue = CollectionUtils .newIdentityHashSet (size / 20 );
5957 this .insertQueue = CollectionUtils .newIdentityHashSet (size / 20 );
60- this .objectClassToIsEntitySourceClass = new HashMap <>();
58+ this .objectClassToIsEntitySourceClassMap = new HashMap <>();
6159 this .seenEntitySet = CollectionUtils .newIdentityHashSet (size );
6260 this .seenFactSet = CollectionUtils .newIdentityHashSet (size );
6361
6462 this .propagationQueue = new StaticPropagationQueue <>(nextNodesTupleLifecycle );
6563 }
6664
67- public RecordAndReplayPropagator (
68- Supplier <BavetPrecomputeBuildHelper <Tuple_ >> precomputeBuildHelperSupplier ,
69- UnaryOperator <Tuple_ > internalTupleToOutputTupleMapper ,
70- TupleLifecycle <Tuple_ > nextNodesTupleLifecycle ) {
65+ public RecordAndReplayPropagator (Supplier <BavetPrecomputeBuildHelper <Tuple_ >> precomputeBuildHelperSupplier ,
66+ UnaryOperator <Tuple_ > internalTupleToOutputTupleMapper , TupleLifecycle <Tuple_ > nextNodesTupleLifecycle ) {
7167 this (precomputeBuildHelperSupplier , internalTupleToOutputTupleMapper , nextNodesTupleLifecycle , 1000 );
7268 }
7369
@@ -77,14 +73,18 @@ public void insert(Object object) {
7773 }
7874
7975 public void update (Object object ) {
76+ if (!alreadyUpdatingSet .add (object )) {
77+ // The list was already sent to the propagation queue.
78+ // Don't iterate over it again, even though the queue would deduplicate its contents.
79+ return ;
80+ }
8081 // Updates happen very frequently, so we optimize by avoiding the update queue
8182 // and going straight to the propagation queue.
8283 // The propagation queue deduplicates updates internally.
8384 var outTupleList = objectToOutputTuplesMap .get (object );
84- if (outTupleList = = null ) {
85- return ;
85+ if (outTupleList ! = null ) {
86+ outTupleList . forEach ( propagationQueue :: update ) ;
8687 }
87- outTupleList .forEach (propagationQueue ::update );
8888 }
8989
9090 public void retract (Object object ) {
@@ -122,7 +122,7 @@ public void propagateRetracts() {
122122 // Do not remove queued retracts from inserts; if a fact property
123123 // change, there will be both a retract and insert for that fact
124124 for (var object : insertQueue ) {
125- if (objectClassToIsEntitySourceClass .computeIfAbsent (object .getClass (),
125+ if (objectClassToIsEntitySourceClassMap .computeIfAbsent (object .getClass (),
126126 precomputeBuildHelper ::isSourceEntityClass )) {
127127 seenEntitySet .add (object );
128128 } else {
@@ -158,6 +158,7 @@ private static <A> List<BavetRootNode<A>> getRootNodes(Object object, NodeNetwor
158158 @ Override
159159 public void propagateUpdates () {
160160 propagationQueue .propagateUpdates ();
161+ alreadyUpdatingSet .clear ();
161162 }
162163
163164 @ Override
@@ -192,8 +193,7 @@ private void invalidateCache() {
192193 objectToOutputTuplesMap .clear ();
193194 }
194195
195- private void recalculateTuples (NodeNetwork internalNodeNetwork ,
196- Map <Class <?>, List <BavetRootNode <?>>> classToRootNodeList ,
196+ private void recalculateTuples (NodeNetwork internalNodeNetwork , Map <Class <?>, List <BavetRootNode <?>>> classToRootNodeList ,
197197 RecordingTupleLifecycle <Tuple_ > recordingTupleLifecycle ) {
198198 var internalTupleToOutputTupleMap = new IdentityHashMap <Tuple_ , Tuple_ >(seenEntitySet .size ());
199199 for (var invalidated : seenEntitySet ) {
@@ -207,12 +207,12 @@ private void recalculateTuples(NodeNetwork internalNodeNetwork,
207207 internalNodeNetwork .settle ();
208208 }
209209 if (mappedTuples .isEmpty ()) {
210- objectToOutputTuplesMap .put (invalidated , Collections . emptyList () );
210+ objectToOutputTuplesMap .remove (invalidated );
211211 } else {
212212 objectToOutputTuplesMap .put (invalidated , mappedTuples );
213213 }
214214 }
215215 objectToOutputTuplesMap .values ().stream ().flatMap (List ::stream ).forEach (this ::insertIfAbsent );
216216 }
217217
218- }
218+ }
0 commit comments