mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-01-27 13:28:00 +01:00
More optimized speed updates
This commit is contained in:
parent
cc0d7f402d
commit
1fed398e8f
2 changed files with 90 additions and 41 deletions
|
@ -15,11 +15,15 @@ public class KineticNetwork {
|
||||||
private final Set<KineticNode> members = new HashSet<>();
|
private final Set<KineticNode> members = new HashSet<>();
|
||||||
private final Set<KineticNode> generators = new HashSet<>();
|
private final Set<KineticNode> generators = new HashSet<>();
|
||||||
private final Set<Pair<KineticNode, KineticNode>> conflictingCycles = new HashSet<>();
|
private final Set<Pair<KineticNode, KineticNode>> conflictingCycles = new HashSet<>();
|
||||||
private float rootSpeed;
|
private float rootTheoreticalSpeed;
|
||||||
private @Nullable KineticNode mainGenerator;
|
private @Nullable KineticNode mainGenerator;
|
||||||
private boolean speedDirty;
|
private boolean rootSpeedDirty;
|
||||||
private boolean overstressed;
|
private boolean overstressed;
|
||||||
|
|
||||||
|
private float rootSpeedCur;
|
||||||
|
private float rootSpeedPrev;
|
||||||
|
private final Set<KineticNode> potentialNewBranches = new HashSet<>();
|
||||||
|
|
||||||
public KineticNetwork(KineticNode root) {
|
public KineticNetwork(KineticNode root) {
|
||||||
addMember(root);
|
addMember(root);
|
||||||
}
|
}
|
||||||
|
@ -28,8 +32,10 @@ public class KineticNetwork {
|
||||||
members.add(node);
|
members.add(node);
|
||||||
if (node.isGenerator() && !generators.contains(node)) {
|
if (node.isGenerator() && !generators.contains(node)) {
|
||||||
generators.add(node);
|
generators.add(node);
|
||||||
speedDirty = true;
|
rootSpeedDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
potentialNewBranches.add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMember(KineticNode node) {
|
public void updateMember(KineticNode node) {
|
||||||
|
@ -40,14 +46,14 @@ public class KineticNetwork {
|
||||||
} else {
|
} else {
|
||||||
generators.remove(node);
|
generators.remove(node);
|
||||||
}
|
}
|
||||||
speedDirty = true;
|
rootSpeedDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeMember(KineticNode node) {
|
public void removeMember(KineticNode node) {
|
||||||
members.remove(node);
|
members.remove(node);
|
||||||
if (node.isGenerator() && generators.contains(node)) {
|
if (node.isGenerator() && generators.contains(node)) {
|
||||||
generators.remove(node);
|
generators.remove(node);
|
||||||
speedDirty = true;
|
rootSpeedDirty = true;
|
||||||
}
|
}
|
||||||
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
|
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
|
||||||
}
|
}
|
||||||
|
@ -65,10 +71,14 @@ public class KineticNetwork {
|
||||||
* each other, and OK otherwise
|
* each other, and OK otherwise
|
||||||
*/
|
*/
|
||||||
public SolveResult tryRecalculateSpeed() {
|
public SolveResult tryRecalculateSpeed() {
|
||||||
if (!conflictingCycles.isEmpty() && !isStopped()) return SolveResult.CONTRADICTION;
|
SolveResult result = tryRecalculateTheoreticalSpeed();
|
||||||
if (!speedDirty) return SolveResult.OK;
|
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;
|
float newSpeed = 0;
|
||||||
KineticNode newGenerator = null;
|
KineticNode newGenerator = null;
|
||||||
|
@ -93,11 +103,15 @@ public class KineticNetwork {
|
||||||
newGenerator = generator;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,20 +123,24 @@ public class KineticNetwork {
|
||||||
|
|
||||||
if (generators.isEmpty()) {
|
if (generators.isEmpty()) {
|
||||||
overstressed = false;
|
overstressed = false;
|
||||||
|
members.forEach(KineticNode::stop);
|
||||||
|
members.forEach(KineticNode::flushChangedSpeed);
|
||||||
return newNetworks;
|
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();
|
float stressCapacity = (float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum();
|
||||||
|
|
||||||
if (stressImpact > stressCapacity) {
|
if (stressImpact > stressCapacity) {
|
||||||
if (!overstressed) {
|
if (!overstressed) {
|
||||||
overstressed = true;
|
overstressed = true;
|
||||||
|
rootSpeedCur = 0;
|
||||||
members.forEach(KineticNode::stop);
|
members.forEach(KineticNode::stop);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (overstressed) {
|
if (overstressed) {
|
||||||
overstressed = false;
|
overstressed = false;
|
||||||
|
rootSpeedCur = rootTheoreticalSpeed;
|
||||||
newNetworks.addAll(bulldozeContradictingMembers());
|
newNetworks.addAll(bulldozeContradictingMembers());
|
||||||
newNetworks.addAll(updateMemberSpeeds());
|
newNetworks.addAll(updateMemberSpeeds());
|
||||||
}
|
}
|
||||||
|
@ -132,7 +150,14 @@ public class KineticNetwork {
|
||||||
return newNetworks;
|
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() {
|
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 we're stopped, then all members' speeds will be 0, so no need to check for speeding nodes
|
||||||
if (isStopped()) {
|
if (isStopped()) {
|
||||||
members.forEach(KineticNode::stop);
|
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
|
// generators should not be turning against each other or have conflicting cycles by now
|
||||||
assert(recalculateSpeedResult.isOk());
|
assert(recalculateSpeedResult.isOk());
|
||||||
|
|
||||||
// update node speeds in a breadth-first order, checking for speeding nodes along the way
|
|
||||||
List<KineticNetwork> newNetworks = new LinkedList<>();
|
List<KineticNetwork> newNetworks = new LinkedList<>();
|
||||||
Set<KineticNode> visited = new HashSet<>();
|
|
||||||
List<KineticNode> frontier = new LinkedList<>();
|
|
||||||
frontier.add(mainGenerator);
|
|
||||||
|
|
||||||
while (!frontier.isEmpty()) {
|
if (rootSpeedChanged) {
|
||||||
KineticNode cur = frontier.remove(0);
|
// root speed changed, update all nodes starting from the main generator
|
||||||
visited.add(cur);
|
bfs(mainGenerator, newNetworks, false);
|
||||||
if (cur.tryUpdateSpeed(rootSpeed).isOk()) {
|
} else if (!potentialNewBranches.isEmpty()) {
|
||||||
cur.getActiveConnections()
|
// new nodes added, update only the new network branches
|
||||||
.map(Pair::getFirst)
|
potentialNewBranches.stream()
|
||||||
.filter(n -> !visited.contains(n))
|
.filter(n -> !potentialNewBranches.contains(n.getSource()))
|
||||||
.forEach(frontier::add);
|
.forEach(n -> bfs(n, newNetworks, true));
|
||||||
} else {
|
potentialNewBranches.clear();
|
||||||
// stop searching on this branch once a speeding node is found
|
|
||||||
cur.onPopBlock();
|
|
||||||
newNetworks.add(cur.getNetwork());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNetworks;
|
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() {
|
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<>();
|
List<KineticNetwork> newNetworks = new LinkedList<>();
|
||||||
|
|
||||||
// generators running against network
|
// generators running against network
|
||||||
float sign = Math.signum(rootSpeed);
|
float sign = Math.signum(rootSpeedCur);
|
||||||
List<KineticNode> runningAgainst = generators.stream()
|
List<KineticNode> runningAgainst = generators.stream()
|
||||||
.filter(n -> Math.signum(n.getGeneratedSpeedAtRoot()) != sign)
|
.filter(n -> Math.signum(n.getGeneratedSpeedAtRoot()) != sign)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
runningAgainst.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); });
|
runningAgainst.forEach(n -> { n.popBlock(); newNetworks.add(n.getNetwork()); });
|
||||||
|
|
||||||
// conflicting cycles
|
// conflicting cycles
|
||||||
List<KineticNode> cycles = conflictingCycles.stream()
|
List<KineticNode> cycles = conflictingCycles.stream()
|
||||||
.map(Pair::getFirst)
|
.map(Pair::getFirst)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
cycles.forEach(n -> { n.onPopBlock(); newNetworks.add(n.getNetwork()); });
|
cycles.forEach(n -> { n.popBlock(); newNetworks.add(n.getNetwork()); });
|
||||||
|
|
||||||
return newNetworks;
|
return newNetworks;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class KineticNode {
|
||||||
generatedSpeed = newSpeed;
|
generatedSpeed = newSpeed;
|
||||||
network.updateMember(this);
|
network.updateMember(this);
|
||||||
if (network.tryRecalculateSpeed().isContradiction()) {
|
if (network.tryRecalculateSpeed().isContradiction()) {
|
||||||
onPopBlock();
|
popBlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +111,10 @@ public class KineticNode {
|
||||||
return network.tryRecalculateSpeed();
|
return network.tryRecalculateSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KineticNode getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
private SolveResult setSource(KineticNode from, float ratio) {
|
private SolveResult setSource(KineticNode from, float ratio) {
|
||||||
source = from;
|
source = from;
|
||||||
speedRatio = from.speedRatio * ratio;
|
speedRatio = from.speedRatio * ratio;
|
||||||
|
@ -124,7 +128,7 @@ public class KineticNode {
|
||||||
if (setSource(e.getFirst(), 1/e.getSecond()).isOk()) {
|
if (setSource(e.getFirst(), 1/e.getSecond()).isOk()) {
|
||||||
propagateSource();
|
propagateSource();
|
||||||
} else {
|
} else {
|
||||||
onPopBlock();
|
popBlock();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -151,7 +155,7 @@ public class KineticNode {
|
||||||
if (network.isStopped()) {
|
if (network.isStopped()) {
|
||||||
network.markConflictingCycle(cur, next);
|
network.markConflictingCycle(cur, next);
|
||||||
} else {
|
} else {
|
||||||
onPopBlock();
|
popBlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +166,7 @@ public class KineticNode {
|
||||||
frontier.add(next);
|
frontier.add(next);
|
||||||
} else {
|
} else {
|
||||||
// this node will run against the network or activate a conflicting cycle
|
// this node will run against the network or activate a conflicting cycle
|
||||||
onPopBlock();
|
popBlock();
|
||||||
return;
|
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
|
// this should cause the node to get removed from the solver and lead to onRemoved() being called
|
||||||
entity.getLevel().destroyBlock(entity.getBlockPos(), true);
|
entity.getLevel().destroyBlock(entity.getBlockPos(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSourceOf(KineticNode other) {
|
|
||||||
return other.source == this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue