mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-01-11 23:07:13 +01:00
Defer per-node speed calculation until end of tick
This commit is contained in:
parent
d58d303d28
commit
5c5e535551
4 changed files with 132 additions and 86 deletions
|
@ -2,8 +2,6 @@ package com.simibubi.create.content.contraptions.solver;
|
|||
|
||||
import com.simibubi.create.foundation.utility.Pair;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
@ -17,32 +15,37 @@ public class KineticNetwork {
|
|||
private final Set<KineticNode> generators = new HashSet<>();
|
||||
private final Set<Pair<KineticNode, KineticNode>> conflictingCycles = new HashSet<>();
|
||||
private float rootSpeed;
|
||||
private @Nullable KineticNode mainGenerator;
|
||||
private boolean speedDirty;
|
||||
|
||||
public KineticNetwork(KineticNode root) {
|
||||
addMember(root);
|
||||
rootSpeed = root.getGeneratedSpeed();
|
||||
}
|
||||
|
||||
public void addMember(KineticNode node) {
|
||||
members.add(node);
|
||||
if (node.isGenerator()) {
|
||||
if (node.isGenerator() && !generators.contains(node)) {
|
||||
generators.add(node);
|
||||
speedDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateMember(KineticNode node) {
|
||||
if (!members.contains(node)) throw new IllegalArgumentException();
|
||||
|
||||
if (node.isGenerator()) {
|
||||
generators.add(node);
|
||||
} else {
|
||||
generators.remove(node);
|
||||
}
|
||||
speedDirty = true;
|
||||
}
|
||||
|
||||
public void removeMember(KineticNode node) {
|
||||
members.remove(node);
|
||||
if (node.isGenerator()) {
|
||||
if (node.isGenerator() && generators.contains(node)) {
|
||||
generators.remove(node);
|
||||
speedDirty = true;
|
||||
}
|
||||
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
|
||||
}
|
||||
|
@ -56,70 +59,88 @@ public class KineticNetwork {
|
|||
return rootSpeed;
|
||||
}
|
||||
|
||||
public boolean isStopped() { return generators.isEmpty(); }
|
||||
|
||||
/**
|
||||
* Recalculates the speed at the root of this network, and if it has changed, recalculates the speed of all
|
||||
* KineticNodes in the network and pops any nodes whose speed has increased above the speed limit.
|
||||
* @param checkRoot Node to start performing a breadth-first from in order to find and pop speeding nodes. If null,
|
||||
* speeding nodes are ignored.
|
||||
* @param forced If true, will check for speeding nodes from checkRoot even if the root speed has not changed.
|
||||
* @return CONTRADICTION if the network currently has a cycle with conflicting speed ratios or
|
||||
* has generators turning against each other, and OK otherwise.
|
||||
* Recalculates the speed at the root node of this network.
|
||||
* @return CONTRADICTION if the network has generators turning against each other, and OK otherwise
|
||||
*/
|
||||
public SolveResult recalculateSpeed(@Nullable KineticNode checkRoot, boolean forced) {
|
||||
if (!conflictingCycles.isEmpty() && !generators.isEmpty()) {
|
||||
// cycle with conflicting speed ratios is present
|
||||
return SolveResult.CONTRADICTION;
|
||||
}
|
||||
public SolveResult recalculateSpeed() {
|
||||
if (!speedDirty) return SolveResult.OK;
|
||||
|
||||
float newSpeed = 0;
|
||||
KineticNode newGenerator = null;
|
||||
float sign = 0;
|
||||
|
||||
// search over all generators to maximize the root speed
|
||||
float newSpeed = 0;
|
||||
float sign = 0;
|
||||
for (KineticNode generator : generators) {
|
||||
float speedAtRoot = generator.getGeneratedSpeedAtRoot();
|
||||
|
||||
if (newSpeed == 0) {
|
||||
sign = Math.signum(speedAtRoot);
|
||||
} else if (Math.signum(speedAtRoot) != sign) {
|
||||
}
|
||||
|
||||
if (Math.signum(speedAtRoot) != sign) {
|
||||
// generators are turning against each other
|
||||
return SolveResult.CONTRADICTION;
|
||||
}
|
||||
newSpeed = Math.max(newSpeed, sign * speedAtRoot);
|
||||
}
|
||||
newSpeed *= sign;
|
||||
|
||||
if (!Mth.equal(rootSpeed, newSpeed)) {
|
||||
rootSpeed = newSpeed;
|
||||
|
||||
if (checkRoot == null) {
|
||||
members.forEach(KineticNode::tryUpdateSpeed);
|
||||
} else {
|
||||
updateNodeSpeeds(checkRoot, false);
|
||||
if (newSpeed < speedAtRoot * sign) {
|
||||
newSpeed = speedAtRoot * sign;
|
||||
newGenerator = generator;
|
||||
}
|
||||
} else if (forced) {
|
||||
updateNodeSpeeds(checkRoot, true);
|
||||
}
|
||||
|
||||
rootSpeed = newSpeed * sign;
|
||||
mainGenerator = newGenerator;
|
||||
speedDirty = false;
|
||||
return SolveResult.OK;
|
||||
}
|
||||
|
||||
private void updateNodeSpeeds(KineticNode root, boolean followSources) {
|
||||
/**
|
||||
* @return a List of new networks created during this function call
|
||||
*/
|
||||
public List<KineticNetwork> tick() {
|
||||
List<KineticNetwork> newNetworks = updateMemberSpeeds();
|
||||
members.forEach(KineticNode::flushChangedSpeed);
|
||||
return newNetworks;
|
||||
}
|
||||
|
||||
private List<KineticNetwork> updateMemberSpeeds() {
|
||||
SolveResult recalculateSpeedResult = recalculateSpeed();
|
||||
// generators should not be turning against each other by now
|
||||
assert(recalculateSpeedResult.isOk());
|
||||
|
||||
// if we're stopped then all members' speeds will be 0, so no need to check for speeding nodes
|
||||
if (isStopped()) {
|
||||
members.forEach(KineticNode::tryUpdateSpeed);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// there should be no cycles with conflicting speed ratios by now
|
||||
assert(conflictingCycles.isEmpty());
|
||||
|
||||
// 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(root);
|
||||
frontier.add(mainGenerator);
|
||||
|
||||
// update node speeds in a breadth-first order, starting at root
|
||||
while (!frontier.isEmpty()) {
|
||||
KineticNode cur = frontier.remove(0);
|
||||
visited.add(cur);
|
||||
if (cur.tryUpdateSpeed().isOk()) {
|
||||
for (KineticNode next : cur.getActiveConnections().keySet()) {
|
||||
if (!(visited.contains(next) || (followSources && !cur.isSourceOf(next))))
|
||||
frontier.add(next);
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
return newNetworks;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ import javax.annotation.Nullable;
|
|||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class KineticNode {
|
||||
|
||||
|
@ -46,18 +46,25 @@ public class KineticNode {
|
|||
return connections;
|
||||
}
|
||||
|
||||
public KineticNetwork getNetwork() {
|
||||
return network;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map where the keys are every node with a compatible connection to this node, and the values are the
|
||||
* speed ratios of those connections
|
||||
* @return a Stream containing a pair for each compatible connection with this node, where the first value is
|
||||
* the connecting node and the second value is the speed ratio of the connection
|
||||
*/
|
||||
public Map<KineticNode, Float> getActiveConnections() {
|
||||
public Stream<Pair<KineticNode, Float>> getActiveConnections() {
|
||||
return connections.getDirections().stream()
|
||||
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d))
|
||||
.map(n -> connections.checkConnection(n.connections, d)
|
||||
.map(r -> Pair.of(n, r))))
|
||||
.flatMap(Optional::stream)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
|
||||
.flatMap(Optional::stream);
|
||||
}
|
||||
|
||||
public Iterable<Pair<KineticNode, Float>> getActiveConnectionsList() {
|
||||
return getActiveConnections().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public float getGeneratedSpeed() {
|
||||
|
@ -73,87 +80,92 @@ public class KineticNode {
|
|||
}
|
||||
|
||||
public void setGeneratedSpeed(float newSpeed) {
|
||||
if (Mth.equal(generatedSpeed, newSpeed)) return;
|
||||
if (generatedSpeed == newSpeed) return;
|
||||
generatedSpeed = newSpeed;
|
||||
network.updateMember(this);
|
||||
if (network.recalculateSpeed(this, false).isContradiction())
|
||||
if (network.recalculateSpeed().isContradiction()) {
|
||||
onPopBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void setNetwork(KineticNetwork network) {
|
||||
private SolveResult setNetwork(KineticNetwork network) {
|
||||
this.network.removeMember(this);
|
||||
this.network = network;
|
||||
network.addMember(this);
|
||||
return network.recalculateSpeed();
|
||||
}
|
||||
|
||||
private void setSource(KineticNode from, float ratio) {
|
||||
private SolveResult setSource(KineticNode from, float ratio) {
|
||||
source = from;
|
||||
speedRatio = from.speedRatio * ratio;
|
||||
setNetwork(from.network);
|
||||
return setNetwork(from.network);
|
||||
}
|
||||
|
||||
public void onAdded() {
|
||||
getActiveConnections()
|
||||
.keySet()
|
||||
.stream()
|
||||
.findAny()
|
||||
.ifPresent(n -> {
|
||||
if (n.propagateSource(this).isContradiction())
|
||||
.ifPresent(e -> {
|
||||
if (setSource(e.getFirst(), 1/e.getSecond()).isOk()) {
|
||||
propagateSource();
|
||||
} else {
|
||||
onPopBlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagates this node's source and network to any connected nodes that aren't yet part of the same network, then
|
||||
* repeats this recursively with the connected nodes in a breadth-first order.
|
||||
* @param checkRoot Node to start searching from when looking for nodes that started speeding because of this call
|
||||
* @return whether or not this propagation caused a contradiction in the kinetic network
|
||||
*/
|
||||
private SolveResult propagateSource(KineticNode checkRoot) {
|
||||
private void propagateSource() {
|
||||
List<KineticNode> frontier = new LinkedList<>();
|
||||
frontier.add(this);
|
||||
|
||||
while (!frontier.isEmpty()) {
|
||||
KineticNode cur = frontier.remove(0);
|
||||
for (Map.Entry<KineticNode, Float> entry : cur.getActiveConnections().entrySet()) {
|
||||
KineticNode next = entry.getKey();
|
||||
float ratio = entry.getValue();
|
||||
for (Pair<KineticNode, Float> pair : cur.getActiveConnectionsList()) {
|
||||
KineticNode next = pair.getFirst();
|
||||
float ratio = pair.getSecond();
|
||||
|
||||
if (next == cur.source) continue;
|
||||
|
||||
if (next.network == network) {
|
||||
if (!Mth.equal(next.speedRatio, cur.speedRatio * ratio)) {
|
||||
// we found a cycle with conflicting speed ratios
|
||||
network.markConflictingCycle(cur, next);
|
||||
// this node will cause a cycle with conflicting speed ratios
|
||||
if (network.isStopped()) {
|
||||
network.markConflictingCycle(cur, next);
|
||||
} else {
|
||||
onPopBlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
next.setSource(cur, ratio);
|
||||
frontier.add(next);
|
||||
if (next.setSource(cur, ratio).isOk()) {
|
||||
frontier.add(next);
|
||||
} else {
|
||||
// this node will run against the network
|
||||
onPopBlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return network.recalculateSpeed(checkRoot, true);
|
||||
}
|
||||
|
||||
public void onRemoved() {
|
||||
network.removeMember(this);
|
||||
for (KineticNode neighbor : getActiveConnections().keySet()) {
|
||||
if (neighbor.source != this) continue;
|
||||
neighbor.rerootHere();
|
||||
}
|
||||
network.recalculateSpeed(null, false);
|
||||
getActiveConnections()
|
||||
.map(Pair::getFirst)
|
||||
.filter(n -> n.source == this)
|
||||
.forEach(KineticNode::rerootHere);
|
||||
}
|
||||
|
||||
private void rerootHere() {
|
||||
source = null;
|
||||
speedRatio = 1;
|
||||
setNetwork(new KineticNetwork(this));
|
||||
if (tryUpdateSpeed().isOk()) {
|
||||
propagateSource(this);
|
||||
} else {
|
||||
onPopBlock();
|
||||
}
|
||||
propagateSource();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -182,5 +194,4 @@ public class KineticNode {
|
|||
public boolean isSourceOf(KineticNode other) {
|
||||
return other.source == this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package com.simibubi.create.content.contraptions.solver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
|
||||
import com.simibubi.create.foundation.utility.WorldAttached;
|
||||
|
@ -24,11 +29,7 @@ public class KineticSolver {
|
|||
removeNode(entity);
|
||||
KineticNode node = new KineticNode(entity, this::getNode);
|
||||
nodes.put(entity.getBlockPos(), node);
|
||||
if (node.tryUpdateSpeed().isOk()) {
|
||||
node.onAdded();
|
||||
} else {
|
||||
node.onPopBlock();
|
||||
}
|
||||
node.onAdded();
|
||||
}
|
||||
|
||||
public void updateNode(KineticTileEntity entity) {
|
||||
|
@ -54,7 +55,20 @@ public class KineticSolver {
|
|||
if (node != null) node.onRemoved();
|
||||
}
|
||||
|
||||
public void flushChangedSpeeds() {
|
||||
nodes.values().forEach(KineticNode::flushChangedSpeed);
|
||||
public void tick() {
|
||||
Set<KineticNetwork> visited = new HashSet<>();
|
||||
List<KineticNetwork> frontier = new LinkedList<>();
|
||||
|
||||
Set<KineticNetwork> networks = nodes.values().stream().map(KineticNode::getNetwork).collect(Collectors.toSet());
|
||||
for (KineticNetwork network : networks) {
|
||||
frontier.add(network);
|
||||
while (!frontier.isEmpty()) {
|
||||
KineticNetwork cur = frontier.remove(0);
|
||||
if (visited.contains(cur)) continue;
|
||||
visited.add(cur);
|
||||
frontier.addAll(cur.tick());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ public class CommonEvents {
|
|||
CouplingPhysics.tick(world);
|
||||
LinkedControllerServerHandler.tick(world);
|
||||
|
||||
KineticSolver.getSolver(world).flushChangedSpeeds();
|
||||
KineticSolver.getSolver(world).tick();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
|
Loading…
Reference in a new issue