More optimized speed updates

This commit is contained in:
reidbhuntley 2021-12-30 17:06:55 -05:00
parent cc0d7f402d
commit 1fed398e8f
2 changed files with 90 additions and 41 deletions

View file

@ -15,11 +15,15 @@ public class KineticNetwork {
private final Set<KineticNode> members = new HashSet<>();
private final Set<KineticNode> generators = new HashSet<>();
private final Set<Pair<KineticNode, KineticNode>> conflictingCycles = new HashSet<>();
private float rootSpeed;
private float rootTheoreticalSpeed;
private @Nullable KineticNode mainGenerator;
private boolean speedDirty;
private boolean rootSpeedDirty;
private boolean overstressed;
private float rootSpeedCur;
private float rootSpeedPrev;
private final Set<KineticNode> potentialNewBranches = new HashSet<>();
public KineticNetwork(KineticNode root) {
addMember(root);
}
@ -28,8 +32,10 @@ public class KineticNetwork {
members.add(node);
if (node.isGenerator() && !generators.contains(node)) {
generators.add(node);
speedDirty = true;
rootSpeedDirty = true;
}
potentialNewBranches.add(node);
}
public void updateMember(KineticNode node) {
@ -40,14 +46,14 @@ public class KineticNetwork {
} else {
generators.remove(node);
}
speedDirty = true;
rootSpeedDirty = true;
}
public void removeMember(KineticNode node) {
members.remove(node);
if (node.isGenerator() && generators.contains(node)) {
generators.remove(node);
speedDirty = true;
rootSpeedDirty = true;
}
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
}
@ -65,10 +71,14 @@ public class KineticNetwork {
* each other, and OK otherwise
*/
public SolveResult tryRecalculateSpeed() {
if (!conflictingCycles.isEmpty() && !isStopped()) return SolveResult.CONTRADICTION;
if (!speedDirty) return SolveResult.OK;
SolveResult result = tryRecalculateTheoreticalSpeed();
if (isStopped()) return SolveResult.OK;
return result;
}
SolveResult result = SolveResult.OK;
private SolveResult tryRecalculateTheoreticalSpeed() {
SolveResult result = conflictingCycles.isEmpty() ? SolveResult.OK : SolveResult.CONTRADICTION;
if (!rootSpeedDirty) return result;
float newSpeed = 0;
KineticNode newGenerator = null;
@ -93,11 +103,15 @@ public class KineticNetwork {
newGenerator = generator;
}
}
rootSpeed = newSpeed * sign;
mainGenerator = newGenerator;
speedDirty = false;
if (overstressed) return SolveResult.OK;
rootTheoreticalSpeed = newSpeed * sign;
if (!overstressed) {
rootSpeedCur = rootTheoreticalSpeed;
}
mainGenerator = newGenerator;
rootSpeedDirty = false;
return result;
}
@ -109,20 +123,24 @@ public class KineticNetwork {
if (generators.isEmpty()) {
overstressed = false;
members.forEach(KineticNode::stop);
members.forEach(KineticNode::flushChangedSpeed);
return newNetworks;
}
float stressImpact = (float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootSpeed)).sum();
float stressImpact = (float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootTheoreticalSpeed)).sum();
float stressCapacity = (float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum();
if (stressImpact > stressCapacity) {
if (!overstressed) {
overstressed = true;
rootSpeedCur = 0;
members.forEach(KineticNode::stop);
}
} else {
if (overstressed) {
overstressed = false;
rootSpeedCur = rootTheoreticalSpeed;
newNetworks.addAll(bulldozeContradictingMembers());
newNetworks.addAll(updateMemberSpeeds());
}
@ -132,7 +150,14 @@ public class KineticNetwork {
return newNetworks;
}
/**
* Update the speed of every member, starting from the main generator and popping off speeding nodes along the way
* @return a List of new networks created during this function call
*/
private List<KineticNetwork> updateMemberSpeeds() {
boolean rootSpeedChanged = rootSpeedPrev != rootSpeedCur;
rootSpeedPrev = rootSpeedCur;
// if we're stopped, then all members' speeds will be 0, so no need to check for speeding nodes
if (isStopped()) {
members.forEach(KineticNode::stop);
@ -143,45 +168,68 @@ public class KineticNetwork {
// generators should not be turning against each other or have conflicting cycles by now
assert(recalculateSpeedResult.isOk());
// update node speeds in a breadth-first order, checking for speeding nodes along the way
List<KineticNetwork> newNetworks = new LinkedList<>();
Set<KineticNode> visited = new HashSet<>();
List<KineticNode> frontier = new LinkedList<>();
frontier.add(mainGenerator);
while (!frontier.isEmpty()) {
KineticNode cur = frontier.remove(0);
visited.add(cur);
if (cur.tryUpdateSpeed(rootSpeed).isOk()) {
cur.getActiveConnections()
.map(Pair::getFirst)
.filter(n -> !visited.contains(n))
.forEach(frontier::add);
} else {
// stop searching on this branch once a speeding node is found
cur.onPopBlock();
newNetworks.add(cur.getNetwork());
}
if (rootSpeedChanged) {
// root speed changed, update all nodes starting from the main generator
bfs(mainGenerator, newNetworks, false);
} else if (!potentialNewBranches.isEmpty()) {
// new nodes added, update only the new network branches
potentialNewBranches.stream()
.filter(n -> !potentialNewBranches.contains(n.getSource()))
.forEach(n -> bfs(n, newNetworks, true));
potentialNewBranches.clear();
}
return newNetworks;
}
private void bfs(KineticNode root, List<KineticNetwork> newNetworks, boolean followSource) {
// update node speeds in a breadth-first order, checking for speeding nodes along the way
Set<KineticNode> visited = new HashSet<>();
List<KineticNode> frontier = new LinkedList<>();
frontier.add(root);
while (!frontier.isEmpty()) {
KineticNode cur = frontier.remove(0);
if (!members.contains(cur) || visited.contains(cur)) continue;
visited.add(cur);
if (cur.tryUpdateSpeed(rootSpeedCur).isOk()) {
cur.getActiveConnections()
.map(Pair::getFirst)
.filter(n -> !followSource || n.getSource() == cur)
.forEach(frontier::add);
} else {
// stop searching on this branch once a speeding node is found
cur.popBlock();
newNetworks.add(cur.getNetwork());
}
}
}
private List<KineticNetwork> bulldozeContradictingMembers() {
/*
This method is necessary to handle the edge case where contradicting nodes have been added to the network while
it was overstressed and now that it's moving again we need to pop them. Here we can't just stop following a
branch after popping a block though since there may be more contradictions further down that branch, so we'll
just pop all potentially contradicting nodes off and hope no one cares
*/
List<KineticNetwork> newNetworks = new LinkedList<>();
// generators running against network
float sign = Math.signum(rootSpeed);
float sign = Math.signum(rootSpeedCur);
List<KineticNode> runningAgainst = generators.stream()
.filter(n -> Math.signum(n.getGeneratedSpeedAtRoot()) != sign)
.collect(Collectors.toList());
runningAgainst.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); });
runningAgainst.forEach(n -> { n.popBlock(); newNetworks.add(n.getNetwork()); });
// conflicting cycles
List<KineticNode> cycles = conflictingCycles.stream()
.map(Pair::getFirst)
.collect(Collectors.toList());
cycles.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); });
cycles.forEach(n -> { n.popBlock(); newNetworks.add(n.getNetwork()); });
return newNetworks;
}

View file

@ -84,7 +84,7 @@ public class KineticNode {
generatedSpeed = newSpeed;
network.updateMember(this);
if (network.tryRecalculateSpeed().isContradiction()) {
onPopBlock();
popBlock();
}
}
@ -111,6 +111,10 @@ public class KineticNode {
return network.tryRecalculateSpeed();
}
public KineticNode getSource() {
return source;
}
private SolveResult setSource(KineticNode from, float ratio) {
source = from;
speedRatio = from.speedRatio * ratio;
@ -124,7 +128,7 @@ public class KineticNode {
if (setSource(e.getFirst(), 1/e.getSecond()).isOk()) {
propagateSource();
} else {
onPopBlock();
popBlock();
}
});
}
@ -151,7 +155,7 @@ public class KineticNode {
if (network.isStopped()) {
network.markConflictingCycle(cur, next);
} else {
onPopBlock();
popBlock();
return;
}
}
@ -162,7 +166,7 @@ public class KineticNode {
frontier.add(next);
} else {
// this node will run against the network or activate a conflicting cycle
onPopBlock();
popBlock();
return;
}
}
@ -208,12 +212,9 @@ public class KineticNode {
}
}
public void onPopBlock() {
public void popBlock() {
// this should cause the node to get removed from the solver and lead to onRemoved() being called
entity.getLevel().destroyBlock(entity.getBlockPos(), true);
}
public boolean isSourceOf(KineticNode other) {
return other.source == this;
}
}