mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-01-26 04:48:22 +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 com.simibubi.create.foundation.utility.Pair;
|
||||||
|
|
||||||
import net.minecraft.util.Mth;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -17,32 +15,37 @@ public class KineticNetwork {
|
||||||
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 rootSpeed;
|
||||||
|
private @Nullable KineticNode mainGenerator;
|
||||||
|
private boolean speedDirty;
|
||||||
|
|
||||||
public KineticNetwork(KineticNode root) {
|
public KineticNetwork(KineticNode root) {
|
||||||
addMember(root);
|
addMember(root);
|
||||||
rootSpeed = root.getGeneratedSpeed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMember(KineticNode node) {
|
public void addMember(KineticNode node) {
|
||||||
members.add(node);
|
members.add(node);
|
||||||
if (node.isGenerator()) {
|
if (node.isGenerator() && !generators.contains(node)) {
|
||||||
generators.add(node);
|
generators.add(node);
|
||||||
|
speedDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMember(KineticNode node) {
|
public void updateMember(KineticNode node) {
|
||||||
if (!members.contains(node)) throw new IllegalArgumentException();
|
if (!members.contains(node)) throw new IllegalArgumentException();
|
||||||
|
|
||||||
if (node.isGenerator()) {
|
if (node.isGenerator()) {
|
||||||
generators.add(node);
|
generators.add(node);
|
||||||
} else {
|
} else {
|
||||||
generators.remove(node);
|
generators.remove(node);
|
||||||
}
|
}
|
||||||
|
speedDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeMember(KineticNode node) {
|
public void removeMember(KineticNode node) {
|
||||||
members.remove(node);
|
members.remove(node);
|
||||||
if (node.isGenerator()) {
|
if (node.isGenerator() && generators.contains(node)) {
|
||||||
generators.remove(node);
|
generators.remove(node);
|
||||||
|
speedDirty = true;
|
||||||
}
|
}
|
||||||
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
|
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
|
||||||
}
|
}
|
||||||
|
@ -56,70 +59,88 @@ public class KineticNetwork {
|
||||||
return rootSpeed;
|
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
|
* Recalculates the speed at the root node of this network.
|
||||||
* KineticNodes in the network and pops any nodes whose speed has increased above the speed limit.
|
* @return CONTRADICTION if the network has generators turning against each other, and OK otherwise
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
public SolveResult recalculateSpeed(@Nullable KineticNode checkRoot, boolean forced) {
|
public SolveResult recalculateSpeed() {
|
||||||
if (!conflictingCycles.isEmpty() && !generators.isEmpty()) {
|
if (!speedDirty) return SolveResult.OK;
|
||||||
// cycle with conflicting speed ratios is present
|
|
||||||
return SolveResult.CONTRADICTION;
|
float newSpeed = 0;
|
||||||
}
|
KineticNode newGenerator = null;
|
||||||
|
float sign = 0;
|
||||||
|
|
||||||
// search over all generators to maximize the root speed
|
// search over all generators to maximize the root speed
|
||||||
float newSpeed = 0;
|
|
||||||
float sign = 0;
|
|
||||||
for (KineticNode generator : generators) {
|
for (KineticNode generator : generators) {
|
||||||
float speedAtRoot = generator.getGeneratedSpeedAtRoot();
|
float speedAtRoot = generator.getGeneratedSpeedAtRoot();
|
||||||
|
|
||||||
if (newSpeed == 0) {
|
if (newSpeed == 0) {
|
||||||
sign = Math.signum(speedAtRoot);
|
sign = Math.signum(speedAtRoot);
|
||||||
} else if (Math.signum(speedAtRoot) != sign) {
|
}
|
||||||
|
|
||||||
|
if (Math.signum(speedAtRoot) != sign) {
|
||||||
// generators are turning against each other
|
// generators are turning against each other
|
||||||
return SolveResult.CONTRADICTION;
|
return SolveResult.CONTRADICTION;
|
||||||
}
|
}
|
||||||
newSpeed = Math.max(newSpeed, sign * speedAtRoot);
|
|
||||||
}
|
|
||||||
newSpeed *= sign;
|
|
||||||
|
|
||||||
if (!Mth.equal(rootSpeed, newSpeed)) {
|
if (newSpeed < speedAtRoot * sign) {
|
||||||
rootSpeed = newSpeed;
|
newSpeed = speedAtRoot * sign;
|
||||||
|
newGenerator = generator;
|
||||||
if (checkRoot == null) {
|
|
||||||
members.forEach(KineticNode::tryUpdateSpeed);
|
|
||||||
} else {
|
|
||||||
updateNodeSpeeds(checkRoot, false);
|
|
||||||
}
|
}
|
||||||
} else if (forced) {
|
|
||||||
updateNodeSpeeds(checkRoot, true);
|
|
||||||
}
|
}
|
||||||
|
rootSpeed = newSpeed * sign;
|
||||||
|
mainGenerator = newGenerator;
|
||||||
|
speedDirty = false;
|
||||||
return SolveResult.OK;
|
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<>();
|
Set<KineticNode> visited = new HashSet<>();
|
||||||
List<KineticNode> frontier = new LinkedList<>();
|
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()) {
|
while (!frontier.isEmpty()) {
|
||||||
KineticNode cur = frontier.remove(0);
|
KineticNode cur = frontier.remove(0);
|
||||||
visited.add(cur);
|
visited.add(cur);
|
||||||
if (cur.tryUpdateSpeed().isOk()) {
|
if (cur.tryUpdateSpeed().isOk()) {
|
||||||
for (KineticNode next : cur.getActiveConnections().keySet()) {
|
cur.getActiveConnections()
|
||||||
if (!(visited.contains(next) || (followSources && !cur.isSourceOf(next))))
|
.map(Pair::getFirst)
|
||||||
frontier.add(next);
|
.filter(n -> !visited.contains(n))
|
||||||
}
|
.forEach(frontier::add);
|
||||||
} else {
|
} else {
|
||||||
// stop searching on this branch once a speeding node is found
|
// stop searching on this branch once a speeding node is found
|
||||||
cur.onPopBlock();
|
cur.onPopBlock();
|
||||||
|
newNetworks.add(cur.getNetwork());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newNetworks;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class KineticNode {
|
public class KineticNode {
|
||||||
|
|
||||||
|
@ -46,18 +46,25 @@ public class KineticNode {
|
||||||
return connections;
|
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
|
* @return a Stream containing a pair for each compatible connection with this node, where the first value is
|
||||||
* speed ratios of those connections
|
* 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()
|
return connections.getDirections().stream()
|
||||||
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d))
|
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d))
|
||||||
.map(n -> connections.checkConnection(n.connections, d)
|
.map(n -> connections.checkConnection(n.connections, d)
|
||||||
.map(r -> Pair.of(n, r))))
|
.map(r -> Pair.of(n, r))))
|
||||||
.flatMap(Optional::stream)
|
.flatMap(Optional::stream)
|
||||||
.flatMap(Optional::stream)
|
.flatMap(Optional::stream);
|
||||||
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
|
}
|
||||||
|
|
||||||
|
public Iterable<Pair<KineticNode, Float>> getActiveConnectionsList() {
|
||||||
|
return getActiveConnections().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getGeneratedSpeed() {
|
public float getGeneratedSpeed() {
|
||||||
|
@ -73,87 +80,92 @@ public class KineticNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGeneratedSpeed(float newSpeed) {
|
public void setGeneratedSpeed(float newSpeed) {
|
||||||
if (Mth.equal(generatedSpeed, newSpeed)) return;
|
if (generatedSpeed == newSpeed) return;
|
||||||
generatedSpeed = newSpeed;
|
generatedSpeed = newSpeed;
|
||||||
network.updateMember(this);
|
network.updateMember(this);
|
||||||
if (network.recalculateSpeed(this, false).isContradiction())
|
if (network.recalculateSpeed().isContradiction()) {
|
||||||
onPopBlock();
|
onPopBlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setNetwork(KineticNetwork network) {
|
private SolveResult setNetwork(KineticNetwork network) {
|
||||||
this.network.removeMember(this);
|
this.network.removeMember(this);
|
||||||
this.network = network;
|
this.network = network;
|
||||||
network.addMember(this);
|
network.addMember(this);
|
||||||
|
return network.recalculateSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSource(KineticNode from, float ratio) {
|
private SolveResult setSource(KineticNode from, float ratio) {
|
||||||
source = from;
|
source = from;
|
||||||
speedRatio = from.speedRatio * ratio;
|
speedRatio = from.speedRatio * ratio;
|
||||||
setNetwork(from.network);
|
return setNetwork(from.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAdded() {
|
public void onAdded() {
|
||||||
getActiveConnections()
|
getActiveConnections()
|
||||||
.keySet()
|
|
||||||
.stream()
|
|
||||||
.findAny()
|
.findAny()
|
||||||
.ifPresent(n -> {
|
.ifPresent(e -> {
|
||||||
if (n.propagateSource(this).isContradiction())
|
if (setSource(e.getFirst(), 1/e.getSecond()).isOk()) {
|
||||||
|
propagateSource();
|
||||||
|
} else {
|
||||||
onPopBlock();
|
onPopBlock();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Propagates this node's source and network to any connected nodes that aren't yet part of the same network, then
|
* 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.
|
* 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<>();
|
List<KineticNode> frontier = new LinkedList<>();
|
||||||
frontier.add(this);
|
frontier.add(this);
|
||||||
|
|
||||||
while (!frontier.isEmpty()) {
|
while (!frontier.isEmpty()) {
|
||||||
KineticNode cur = frontier.remove(0);
|
KineticNode cur = frontier.remove(0);
|
||||||
for (Map.Entry<KineticNode, Float> entry : cur.getActiveConnections().entrySet()) {
|
for (Pair<KineticNode, Float> pair : cur.getActiveConnectionsList()) {
|
||||||
KineticNode next = entry.getKey();
|
KineticNode next = pair.getFirst();
|
||||||
float ratio = entry.getValue();
|
float ratio = pair.getSecond();
|
||||||
|
|
||||||
if (next == cur.source) continue;
|
if (next == cur.source) continue;
|
||||||
|
|
||||||
if (next.network == network) {
|
if (next.network == network) {
|
||||||
if (!Mth.equal(next.speedRatio, cur.speedRatio * ratio)) {
|
if (!Mth.equal(next.speedRatio, cur.speedRatio * ratio)) {
|
||||||
// we found a cycle with conflicting speed ratios
|
// this node will cause a cycle with conflicting speed ratios
|
||||||
network.markConflictingCycle(cur, next);
|
if (network.isStopped()) {
|
||||||
|
network.markConflictingCycle(cur, next);
|
||||||
|
} else {
|
||||||
|
onPopBlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
next.setSource(cur, ratio);
|
if (next.setSource(cur, ratio).isOk()) {
|
||||||
frontier.add(next);
|
frontier.add(next);
|
||||||
|
} else {
|
||||||
|
// this node will run against the network
|
||||||
|
onPopBlock();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return network.recalculateSpeed(checkRoot, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRemoved() {
|
public void onRemoved() {
|
||||||
network.removeMember(this);
|
network.removeMember(this);
|
||||||
for (KineticNode neighbor : getActiveConnections().keySet()) {
|
getActiveConnections()
|
||||||
if (neighbor.source != this) continue;
|
.map(Pair::getFirst)
|
||||||
neighbor.rerootHere();
|
.filter(n -> n.source == this)
|
||||||
}
|
.forEach(KineticNode::rerootHere);
|
||||||
network.recalculateSpeed(null, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rerootHere() {
|
private void rerootHere() {
|
||||||
source = null;
|
source = null;
|
||||||
speedRatio = 1;
|
speedRatio = 1;
|
||||||
setNetwork(new KineticNetwork(this));
|
setNetwork(new KineticNetwork(this));
|
||||||
if (tryUpdateSpeed().isOk()) {
|
propagateSource();
|
||||||
propagateSource(this);
|
|
||||||
} else {
|
|
||||||
onPopBlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,5 +194,4 @@ public class KineticNode {
|
||||||
public boolean isSourceOf(KineticNode other) {
|
public boolean isSourceOf(KineticNode other) {
|
||||||
return other.source == this;
|
return other.source == this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package com.simibubi.create.content.contraptions.solver;
|
package com.simibubi.create.content.contraptions.solver;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
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.content.contraptions.base.KineticTileEntity;
|
||||||
import com.simibubi.create.foundation.utility.WorldAttached;
|
import com.simibubi.create.foundation.utility.WorldAttached;
|
||||||
|
@ -24,11 +29,7 @@ public class KineticSolver {
|
||||||
removeNode(entity);
|
removeNode(entity);
|
||||||
KineticNode node = new KineticNode(entity, this::getNode);
|
KineticNode node = new KineticNode(entity, this::getNode);
|
||||||
nodes.put(entity.getBlockPos(), node);
|
nodes.put(entity.getBlockPos(), node);
|
||||||
if (node.tryUpdateSpeed().isOk()) {
|
node.onAdded();
|
||||||
node.onAdded();
|
|
||||||
} else {
|
|
||||||
node.onPopBlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNode(KineticTileEntity entity) {
|
public void updateNode(KineticTileEntity entity) {
|
||||||
|
@ -54,7 +55,20 @@ public class KineticSolver {
|
||||||
if (node != null) node.onRemoved();
|
if (node != null) node.onRemoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushChangedSpeeds() {
|
public void tick() {
|
||||||
nodes.values().forEach(KineticNode::flushChangedSpeed);
|
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);
|
CouplingPhysics.tick(world);
|
||||||
LinkedControllerServerHandler.tick(world);
|
LinkedControllerServerHandler.tick(world);
|
||||||
|
|
||||||
KineticSolver.getSolver(world).flushChangedSpeeds();
|
KineticSolver.getSolver(world).tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|
Loading…
Reference in a new issue