-
Notifications
You must be signed in to change notification settings - Fork 210
chore: simplify DSV's affected entity update #1619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,37 +4,29 @@ | |
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.PriorityQueue; | ||
| import java.util.Set; | ||
| import java.util.function.Consumer; | ||
| import java.util.function.Function; | ||
|
|
||
| import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; | ||
| import ai.timefold.solver.core.impl.util.LinkedIdentityHashSet; | ||
|
|
||
| final class AffectedEntitiesUpdater<Solution_> | ||
| implements Consumer<BitSet> { | ||
|
|
||
| // From WorkingReferenceGraph. | ||
| private final BaseTopologicalOrderGraph graph; | ||
| private final List<EntityVariablePair<Solution_>> instanceList; // Immutable. | ||
| private final Function<Object, List<EntityVariablePair<Solution_>>> entityVariablePairFunction; | ||
| private final ChangedVariableNotifier<Solution_> changedVariableNotifier; | ||
|
|
||
| // Internal state; expensive to create, therefore we reuse. | ||
| private final AffectedEntities<Solution_> affectedEntities; | ||
| private final LoopedTracker loopedTracker; | ||
| private final BitSet visited; | ||
| private final PriorityQueue<BaseTopologicalOrderGraph.NodeTopologicalOrder> changeQueue; | ||
|
|
||
| AffectedEntitiesUpdater(BaseTopologicalOrderGraph graph, List<EntityVariablePair<Solution_>> instanceList, | ||
| Function<Object, List<EntityVariablePair<Solution_>>> entityVariablePairFunction, | ||
| ChangedVariableNotifier<Solution_> changedVariableNotifier) { | ||
| this.graph = graph; | ||
| this.instanceList = instanceList; | ||
| this.entityVariablePairFunction = entityVariablePairFunction; | ||
| this.changedVariableNotifier = changedVariableNotifier; | ||
| var instanceCount = instanceList.size(); | ||
| this.affectedEntities = new AffectedEntities<>(this::updateLoopedStatusOfAffectedEntity); | ||
| this.loopedTracker = new LoopedTracker(instanceCount); | ||
| this.visited = new BitSet(instanceCount); | ||
| this.changeQueue = new PriorityQueue<>(instanceCount); | ||
|
|
@@ -64,7 +56,6 @@ public void accept(BitSet changed) { | |
| } | ||
| } | ||
|
|
||
| affectedEntities.processAndClear(); | ||
| // Prepare for the next time updateChanged() is called. | ||
| // No need to clear changeQueue, as that already finishes empty. | ||
| loopedTracker.clear(); | ||
|
|
@@ -90,30 +81,6 @@ private void initializeChangeQueue(BitSet changed) { | |
| changed.clear(); | ||
| } | ||
|
|
||
| private void updateLoopedStatusOfAffectedEntity(Object affectedEntity) { | ||
| ShadowVariableLoopedVariableDescriptor<Solution_> shadowVariableLoopedDescriptor = null; | ||
| var isEntityLooped = false; | ||
| for (var node : entityVariablePairFunction.apply(affectedEntity)) { | ||
| // All variables come from the same entity, | ||
| // therefore all have the same looped marker. | ||
| shadowVariableLoopedDescriptor = node.variableReference().shadowVariableLoopedDescriptor(); | ||
| if (graph.isLooped(loopedTracker, node.graphNodeId())) { | ||
| isEntityLooped = true; | ||
| break; | ||
| } | ||
| } | ||
| if (shadowVariableLoopedDescriptor == null) { | ||
| // At this point, affectedEntity is guaranteed to have looped marker. | ||
| // Otherwise AffectedEntities would not have sent it here. | ||
| throw new IllegalStateException("Impossible state: loop marker descriptor does not exist."); | ||
| } | ||
| var oldValue = shadowVariableLoopedDescriptor.getValue(affectedEntity); | ||
| if (!Objects.equals(oldValue, isEntityLooped)) { | ||
| changeShadowVariableAndNotify(shadowVariableLoopedDescriptor, affectedEntity, isEntityLooped); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| private boolean updateShadowVariable(EntityVariablePair<Solution_> entityVariable, boolean isLooped) { | ||
| var entity = entityVariable.entity(); | ||
| var shadowVariableReference = entityVariable.variableReference(); | ||
|
|
@@ -122,21 +89,19 @@ private boolean updateShadowVariable(EntityVariablePair<Solution_> entityVariabl | |
| if (loopDescriptor != null) { | ||
| var oldLooped = (boolean) loopDescriptor.getValue(entity); | ||
| if (oldLooped != isLooped) { | ||
| // Loop status change; add to affected entities | ||
| affectedEntities.add(entityVariable); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was indirection. Put into a set, affect later. Seemingly unnecessary, since the invariant is that the loop status changed from previous, and therefore we need to execute the update even if we had already executed it on this entity before. (The set wouldn't save any work.)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is incorrect; the reason why entities are added to a set is becauses shadow variables can have different @ShadowVariable(supplierName="distanceSupplier")
Long distance;
@ShadowVariable(supplierName="arrivalTimeSupplier")
LocalDateTime arrivalTime;
@ShadowVariable(supplierName="serviceStartTimeSupplier")
LocalDateTime serviceStartTime;
@ShadowVariable(supplierName="departureTime")
LocalDateTime departureTime;
@PreviousElementShadowVariable(sourceVariableName="visits")
Visit previous;
List<Visit> visitGroup;
@ShadowSources("previous")
public Long distanceSupplier() {
if (previous == null) {
return 0L;
}
return previous.distance + 10L;
}
// the normal code for arrivalTime, serviceStartTime, departureTime goes hereIn this case, distance can never be looped, but serviceStartTime might be. So if serviceStartTime is looped, and is updated before distance, the distance update would incorrectly set the loop status of the entity to false. |
||
| // Loop status change; mark it | ||
| changeShadowVariableAndNotify(loopDescriptor, entity, isLooped); | ||
| } | ||
| } | ||
|
|
||
| if (isLooped) { | ||
| if (oldValue != null) { | ||
| affectedEntities.add(entityVariable); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is effectively dead code. The |
||
| changeShadowVariableAndNotify(shadowVariableReference, entity, null); | ||
| } | ||
| return true; | ||
| } else { | ||
| var newValue = shadowVariableReference.calculator().apply(entity); | ||
| if (!Objects.equals(oldValue, newValue)) { | ||
| affectedEntities.add(entityVariable); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same code, same logic. |
||
| changeShadowVariableAndNotify(shadowVariableReference, entity, newValue); | ||
| return true; | ||
| } | ||
|
|
@@ -157,31 +122,4 @@ private void changeShadowVariableAndNotify(VariableDescriptor<Solution_> variabl | |
| changedVariableNotifier.afterVariableChanged().accept(variableDescriptor, entity); | ||
| } | ||
|
|
||
| private static final class AffectedEntities<Solution_> { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we no longer defer the updates, we no longer need this. |
||
|
|
||
| private final Consumer<Object> consumer; | ||
| private final Set<Object> entitiesForLoopedVarUpdateSet; | ||
|
|
||
| public AffectedEntities(Consumer<Object> consumer) { | ||
| this.consumer = consumer; | ||
| this.entitiesForLoopedVarUpdateSet = new LinkedIdentityHashSet<>(); | ||
| } | ||
|
|
||
| public void add(EntityVariablePair<Solution_> shadowVariable) { | ||
| var shadowVariableLoopedDescriptor = shadowVariable.variableReference().shadowVariableLoopedDescriptor(); | ||
| if (shadowVariableLoopedDescriptor == null) { | ||
| return; | ||
| } | ||
| entitiesForLoopedVarUpdateSet.add(shadowVariable.entity()); | ||
| } | ||
|
|
||
| public void processAndClear() { | ||
| for (var entity : entitiesForLoopedVarUpdateSet) { | ||
| consumer.accept(entity); | ||
| } | ||
| entitiesForLoopedVarUpdateSet.clear(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we no longer defer the updates (see below), this is no longer necessary.