Skip to content

Commit 1933d7b

Browse files
committed
Simplify Affected Entity update
1 parent e623314 commit 1933d7b

4 files changed

Lines changed: 33 additions & 45 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/AffectedEntitiesUpdater.java

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package ai.timefold.solver.core.impl.domain.variable.declarative;
22

3-
import java.util.ArrayList;
43
import java.util.BitSet;
54
import java.util.List;
6-
import java.util.Map;
75
import java.util.Objects;
86
import java.util.PriorityQueue;
97
import java.util.Set;
10-
import java.util.function.BiConsumer;
118
import java.util.function.Consumer;
129
import java.util.function.Function;
1310

@@ -29,7 +26,6 @@ final class AffectedEntitiesUpdater<Solution_>
2926
private final BitSet visited;
3027
private final PriorityQueue<BaseTopologicalOrderGraph.NodeTopologicalOrder> changeQueue;
3128

32-
@SuppressWarnings("unchecked")
3329
AffectedEntitiesUpdater(BaseTopologicalOrderGraph graph, List<EntityVariablePair<Solution_>> instanceList,
3430
Function<Object, List<EntityVariablePair<Solution_>>> entityVariablePairFunction,
3531
ChangedVariableNotifier<Solution_> changedVariableNotifier) {
@@ -38,11 +34,7 @@ final class AffectedEntitiesUpdater<Solution_>
3834
this.entityVariablePairFunction = entityVariablePairFunction;
3935
this.changedVariableNotifier = changedVariableNotifier;
4036
var instanceCount = instanceList.size();
41-
this.affectedEntities = new AffectedEntities<>(this::updateLoopedStatusOfAffectedEntity, instanceList.stream()
42-
.map(e -> e.variableReference().shadowVariableLoopedDescriptor())
43-
.filter(Objects::nonNull)
44-
.distinct()
45-
.toArray(ShadowVariableLoopedVariableDescriptor[]::new));
37+
this.affectedEntities = new AffectedEntities<>(this::updateLoopedStatusOfAffectedEntity);
4638
this.loopedTracker = new LoopedTracker(instanceCount);
4739
this.visited = new BitSet(instanceCount);
4840
this.changeQueue = new PriorityQueue<>(instanceCount);
@@ -98,42 +90,48 @@ private void initializeChangeQueue(BitSet changed) {
9890
changed.clear();
9991
}
10092

101-
private void updateLoopedStatusOfAffectedEntity(Object affectedEntity,
102-
ShadowVariableLoopedVariableDescriptor<Solution_> shadowVariableLoopedDescriptor) {
93+
private void updateLoopedStatusOfAffectedEntity(Object affectedEntity) {
94+
ShadowVariableLoopedVariableDescriptor<Solution_> shadowVariableLoopedDescriptor = null;
10395
var isEntityLooped = false;
10496
for (var node : entityVariablePairFunction.apply(affectedEntity)) {
97+
// All variables come from the same entity,
98+
// therefore all have the same looped marker.
99+
shadowVariableLoopedDescriptor = node.variableReference().shadowVariableLoopedDescriptor();
105100
if (graph.isLooped(loopedTracker, node.graphNodeId())) {
106101
isEntityLooped = true;
107102
break;
108103
}
109104
}
105+
if (shadowVariableLoopedDescriptor == null) {
106+
// At this point, affectedEntity is guaranteed to have looped marker.
107+
// Otherwise AffectedEntities would not have sent it here.
108+
throw new IllegalStateException("Impossible state: loop marker descriptor does not exist.");
109+
}
110110
var oldValue = shadowVariableLoopedDescriptor.getValue(affectedEntity);
111111
if (!Objects.equals(oldValue, isEntityLooped)) {
112112
changeShadowVariableAndNotify(shadowVariableLoopedDescriptor, affectedEntity, isEntityLooped);
113113
}
114114

115115
}
116116

117-
private boolean updateShadowVariable(EntityVariablePair<Solution_> shadowVariable, boolean isLooped) {
118-
var entity = shadowVariable.entity();
119-
var shadowVariableReference = shadowVariable.variableReference();
120-
var shadowVariableLoopedDescriptor = shadowVariableReference.shadowVariableLoopedDescriptor();
117+
private boolean updateShadowVariable(EntityVariablePair<Solution_> entityVariable, boolean isLooped) {
118+
var entity = entityVariable.entity();
119+
var shadowVariableReference = entityVariable.variableReference();
121120
var oldValue = shadowVariableReference.memberAccessor().executeGetter(entity);
122121

123122
if (isLooped) {
124123
// null might be a valid value, and thus it could be the case
125124
// that is was not looped and null, then turned to looped and null,
126125
// which is still considered a change.
127-
affectedEntities.add(entity, shadowVariableLoopedDescriptor);
126+
affectedEntities.add(entityVariable);
128127
if (oldValue != null) {
129128
changeShadowVariableAndNotify(shadowVariableReference, entity, null);
130129
}
131130
return true;
132131
} else {
133132
var newValue = shadowVariableReference.calculator().apply(entity);
134-
135133
if (!Objects.equals(oldValue, newValue)) {
136-
affectedEntities.add(entity, shadowVariableLoopedDescriptor);
134+
affectedEntities.add(entityVariable);
137135
changeShadowVariableAndNotify(shadowVariableReference, entity, newValue);
138136
return true;
139137
}
@@ -156,36 +154,27 @@ private void changeShadowVariableAndNotify(VariableDescriptor<Solution_> variabl
156154

157155
private static final class AffectedEntities<Solution_> {
158156

159-
private final BiConsumer<Object, ShadowVariableLoopedVariableDescriptor<Solution_>> consumer;
160-
private final Map<ShadowVariableLoopedVariableDescriptor<Solution_>, Set<Object>> entitiesForLoopedVarUpdateSet;
157+
private final Consumer<Object> consumer;
158+
private final Set<Object> entitiesForLoopedVarUpdateSet;
161159

162-
@SuppressWarnings("unchecked")
163-
public AffectedEntities(BiConsumer<Object, ShadowVariableLoopedVariableDescriptor<Solution_>> consumer,
164-
ShadowVariableLoopedVariableDescriptor<Solution_>... shadowVariableLoopedDescriptors) {
160+
public AffectedEntities(Consumer<Object> consumer) {
165161
this.consumer = consumer;
166-
167-
var entryList = new ArrayList<Map.Entry<ShadowVariableLoopedVariableDescriptor<Solution_>, Set<Object>>>();
168-
for (var shadowVariableLoopedDescriptor : shadowVariableLoopedDescriptors) {
169-
entryList.add(Map.entry(shadowVariableLoopedDescriptor, new LinkedIdentityHashSet<>()));
170-
}
171-
this.entitiesForLoopedVarUpdateSet = Map.ofEntries(entryList.toArray(new Map.Entry[0]));
162+
this.entitiesForLoopedVarUpdateSet = new LinkedIdentityHashSet<>();
172163
}
173164

174-
public void add(Object entity, ShadowVariableLoopedVariableDescriptor<Solution_> shadowVariableLoopedDescriptor) {
165+
public void add(EntityVariablePair<Solution_> shadowVariable) {
166+
var shadowVariableLoopedDescriptor = shadowVariable.variableReference().shadowVariableLoopedDescriptor();
175167
if (shadowVariableLoopedDescriptor == null) {
176168
return;
177169
}
178-
entitiesForLoopedVarUpdateSet.get(shadowVariableLoopedDescriptor).add(entity);
170+
entitiesForLoopedVarUpdateSet.add(shadowVariable.entity());
179171
}
180172

181173
public void processAndClear() {
182-
for (var affectedEntitiesPerDescriptor : entitiesForLoopedVarUpdateSet.entrySet()) {
183-
var affectedEntitySet = affectedEntitiesPerDescriptor.getValue();
184-
for (var affectedEntity : affectedEntitySet) {
185-
consumer.accept(affectedEntity, affectedEntitiesPerDescriptor.getKey());
186-
}
187-
affectedEntitySet.clear(); // Keep the set, to not have to recreate and resize later.
174+
for (var entity : entitiesForLoopedVarUpdateSet) {
175+
consumer.accept(entity);
188176
}
177+
entitiesForLoopedVarUpdateSet.clear();
189178
}
190179

191180
}

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DefaultVariableReferenceGraph.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ public DefaultVariableReferenceGraph(VariableReferenceGraphBuilder<Solution_> ou
7777

7878
@Override
7979
public @Nullable EntityVariablePair<Solution_> lookupOrNull(VariableMetaModel<?, ?, ?> variableId, Object entity) {
80-
return variableReferenceToInstanceMap.getOrDefault(variableId, Collections.emptyMap()).get(entity);
80+
var map = variableReferenceToInstanceMap.get(variableId);
81+
if (map == null) {
82+
return null;
83+
}
84+
return map.get(entity);
8185
}
8286

8387
@Override

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/EntityVariablePair.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package ai.timefold.solver.core.impl.domain.variable.declarative;
22

3-
import java.util.Objects;
4-
53
import org.jspecify.annotations.NullMarked;
64

75
@NullMarked
@@ -15,7 +13,7 @@ public boolean equals(Object object) {
1513

1614
@Override
1715
public int hashCode() {
18-
return Objects.hashCode(graphNodeId);
16+
return Integer.hashCode(graphNodeId);
1917
}
2018

2119
@Override

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/LoopedTracker.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,8 @@ public void mark(int node, LoopedStatus status) {
3030
public LoopedStatus status(int node) {
3131
if (present.isEmpty() || !present.get(node)) {
3232
return LoopedStatus.UNKNOWN;
33-
} else if (looped.get(node)) {
34-
return LoopedStatus.LOOPED;
35-
} else {
36-
return LoopedStatus.NOT_LOOPED;
3733
}
34+
return looped.get(node) ? LoopedStatus.LOOPED : LoopedStatus.NOT_LOOPED;
3835
}
3936

4037
public void clear() {

0 commit comments

Comments
 (0)