Handle speeding nodes

This commit is contained in:
reidbhuntley 2021-12-28 20:44:09 -05:00
parent 1c8f9232b7
commit 28a8358b22
6 changed files with 238 additions and 193 deletions

View file

@ -67,7 +67,7 @@ public class KineticNetwork {
sources.put(te, te.calculateAddedStressCapacity());
members.put(te, te.calculateStressApplied());
updateFromNetwork(te);
te.networkDirty = true;
//te.networkDirty = true;
}
public void updateCapacityFor(KineticTileEntity te, float capacity) {
@ -94,10 +94,10 @@ public class KineticNetwork {
return;
}
members.keySet()
.stream()
.findFirst()
.map(member -> member.networkDirty = true);
// members.keySet()
// .stream()
// .findFirst()
// .map(member -> member.networkDirty = true);
}
public void sync() {

View file

@ -31,7 +31,7 @@ public abstract class KineticBlock extends Block implements IRotate {
BlockEntity tileEntity = worldIn.getBlockEntity(pos);
if (tileEntity instanceof KineticTileEntity) {
KineticTileEntity kineticTileEntity = (KineticTileEntity) tileEntity;
kineticTileEntity.preventSpeedUpdate = 0;
//kineticTileEntity.preventSpeedUpdate = 0;
if (oldState.getBlock() != state.getBlock())
return;
@ -40,7 +40,7 @@ public abstract class KineticBlock extends Block implements IRotate {
if (!areStatesKineticallyEquivalent(oldState, state))
return;
kineticTileEntity.preventSpeedUpdate = 2;
//kineticTileEntity.preventSpeedUpdate = 2;
}
}
@ -65,15 +65,15 @@ public abstract class KineticBlock extends Block implements IRotate {
return;
KineticTileEntity kte = (KineticTileEntity) tileEntity;
if (kte.preventSpeedUpdate > 0) {
kte.preventSpeedUpdate--;
return;
}
// if (kte.preventSpeedUpdate > 0) {
// kte.preventSpeedUpdate--;
// return;
// }
// Remove previous information when block is added
kte.warnOfMovement();
kte.clearKineticInformation();
kte.updateSpeed = true;
// kte.updateSpeed = true;
}
@Override

View file

@ -54,11 +54,8 @@ import net.minecraftforge.fml.DistExecutor;
public class KineticTileEntity extends SmartTileEntity
implements IHaveGoggleInformation, IHaveHoveringInformation, FlywheelRendered {
public @Nullable Long network;
public @Nullable BlockPos source;
public boolean networkDirty;
public boolean updateSpeed;
public int preventSpeedUpdate;
public @Nullable Long network = null;
public @Nullable BlockPos source = null;
protected KineticEffectHandler effects;
protected float speed;
@ -96,18 +93,10 @@ public class KineticTileEntity extends SmartTileEntity
public KineticTileEntity(BlockEntityType<?> typeIn, BlockPos pos, BlockState state) {
super(typeIn, pos, state);
effects = new KineticEffectHandler(this);
updateSpeed = true;
}
@Override
public void initialize() {
if (hasNetwork() && !level.isClientSide) {
KineticNetwork network = getOrCreateNetwork();
if (!network.initialized)
network.initFromTE(capacity, stress, networkSize);
network.addSilently(this, lastCapacityProvided, lastStressApplied);
}
if (!level.isClientSide) {
addToSolver();
}
@ -117,9 +106,6 @@ public class KineticTileEntity extends SmartTileEntity
@Override
public void tick() {
if (!level.isClientSide && needsSpeedUpdate())
attachKinetics();
super.tick();
effects.tick();
@ -140,55 +126,49 @@ public class KineticTileEntity extends SmartTileEntity
if (getFlickerScore() > 0)
flickerTally = getFlickerScore() - 1;
if (networkDirty) {
if (hasNetwork())
getOrCreateNetwork().updateNetwork();
networkDirty = false;
}
}
private void validateKinetics() {
if (hasSource()) {
if (!hasNetwork()) {
removeSource();
return;
}
if (!level.isLoaded(source))
return;
BlockEntity tileEntity = level.getBlockEntity(source);
KineticTileEntity sourceTe =
tileEntity instanceof KineticTileEntity ? (KineticTileEntity) tileEntity : null;
if (sourceTe == null || sourceTe.speed == 0) {
removeSource();
detachKinetics();
return;
}
return;
}
if (speed != 0) {
if (getGeneratedSpeed() == 0)
speed = 0;
}
// if (hasSource()) {
// if (!hasNetwork()) {
// removeSource();
// return;
// }
//
// if (!level.isLoaded(source))
// return;
//
// BlockEntity tileEntity = level.getBlockEntity(source);
// KineticTileEntity sourceTe =
// tileEntity instanceof KineticTileEntity ? (KineticTileEntity) tileEntity : null;
// if (sourceTe == null || sourceTe.speed == 0) {
// removeSource();
// detachKinetics();
// return;
// }
//
// return;
// }
//
// if (speed != 0) {
// if (getGeneratedSpeed() == 0)
// speed = 0;
// }
}
public void updateFromNetwork(float maxStress, float currentStress, int networkSize) {
networkDirty = false;
this.capacity = maxStress;
this.stress = currentStress;
this.networkSize = networkSize;
boolean overStressed = maxStress < currentStress && StressImpact.isEnabled();
if (overStressed != this.overStressed) {
float prevSpeed = getSpeed();
this.overStressed = overStressed;
onSpeedChanged(prevSpeed);
sendData();
}
// networkDirty = false;
// this.capacity = maxStress;
// this.stress = currentStress;
// this.networkSize = networkSize;
// boolean overStressed = maxStress < currentStress && StressImpact.isEnabled();
//
// if (overStressed != this.overStressed) {
// float prevSpeed = getSpeed();
// this.overStressed = overStressed;
// onSpeedChanged(prevSpeed);
// sendData();
// }
}
protected Block getStressConfigKey() {
@ -222,9 +202,6 @@ public class KineticTileEntity extends SmartTileEntity
@Override
protected void setRemovedNotDueToChunkUnload() {
if (!level.isClientSide) {
if (hasNetwork())
getOrCreateNetwork().remove(this);
detachKinetics();
removeFromSolver();
}
super.setRemovedNotDueToChunkUnload();
@ -234,33 +211,33 @@ public class KineticTileEntity extends SmartTileEntity
protected void write(CompoundTag compound, boolean clientPacket) {
compound.putFloat("Speed", speed);
if (needsSpeedUpdate())
compound.putBoolean("NeedsSpeedUpdate", true);
// if (needsSpeedUpdate())
// compound.putBoolean("NeedsSpeedUpdate", true);
//
// if (hasSource())
// compound.put("Source", NbtUtils.writeBlockPos(source));
if (hasSource())
compound.put("Source", NbtUtils.writeBlockPos(source));
if (hasNetwork()) {
CompoundTag networkTag = new CompoundTag();
networkTag.putLong("Id", this.network);
networkTag.putFloat("Stress", stress);
networkTag.putFloat("Capacity", capacity);
networkTag.putInt("Size", networkSize);
if (lastStressApplied != 0)
networkTag.putFloat("AddedStress", lastStressApplied);
if (lastCapacityProvided != 0)
networkTag.putFloat("AddedCapacity", lastCapacityProvided);
compound.put("Network", networkTag);
}
// if (hasNetwork()) {
// CompoundTag networkTag = new CompoundTag();
// //networkTag.putLong("Id", this.network);
// networkTag.putFloat("Stress", stress);
// networkTag.putFloat("Capacity", capacity);
// networkTag.putInt("Size", networkSize);
//
// if (lastStressApplied != 0)
// networkTag.putFloat("AddedStress", lastStressApplied);
// if (lastCapacityProvided != 0)
// networkTag.putFloat("AddedCapacity", lastCapacityProvided);
//
// compound.put("Network", networkTag);
// }
super.write(compound, clientPacket);
}
public boolean needsSpeedUpdate() {
return updateSpeed;
}
// public boolean needsSpeedUpdate() {
// return updateSpeed;
// }
@Override
protected void read(CompoundTag compound, boolean clientPacket) {
@ -275,19 +252,19 @@ public class KineticTileEntity extends SmartTileEntity
speed = compound.getFloat("Speed");
if (compound.contains("Source"))
source = NbtUtils.readBlockPos(compound.getCompound("Source"));
// if (compound.contains("Source"))
// source = NbtUtils.readBlockPos(compound.getCompound("Source"));
if (compound.contains("Network")) {
CompoundTag networkTag = compound.getCompound("Network");
network = networkTag.getLong("Id");
stress = networkTag.getFloat("Stress");
capacity = networkTag.getFloat("Capacity");
networkSize = networkTag.getInt("Size");
lastStressApplied = networkTag.getFloat("AddedStress");
lastCapacityProvided = networkTag.getFloat("AddedCapacity");
overStressed = capacity < stress && StressImpact.isEnabled();
}
// if (compound.contains("Network")) {
// CompoundTag networkTag = compound.getCompound("Network");
// network = networkTag.getLong("Id");
// stress = networkTag.getFloat("Stress");
// capacity = networkTag.getFloat("Capacity");
// networkSize = networkTag.getInt("Size");
// lastStressApplied = networkTag.getFloat("AddedStress");
// lastCapacityProvided = networkTag.getFloat("AddedCapacity");
// overStressed = capacity < stress && StressImpact.isEnabled();
// }
super.read(compound, clientPacket);
@ -325,45 +302,45 @@ public class KineticTileEntity extends SmartTileEntity
}
public void setSource(BlockPos source) {
this.source = source;
if (level == null || level.isClientSide)
return;
BlockEntity tileEntity = level.getBlockEntity(source);
if (!(tileEntity instanceof KineticTileEntity)) {
removeSource();
return;
}
KineticTileEntity sourceTe = (KineticTileEntity) tileEntity;
setNetwork(sourceTe.network);
// this.source = source;
// if (level == null || level.isClientSide)
// return;
//
// BlockEntity tileEntity = level.getBlockEntity(source);
// if (!(tileEntity instanceof KineticTileEntity)) {
// removeSource();
// return;
// }
//
// KineticTileEntity sourceTe = (KineticTileEntity) tileEntity;
// setNetwork(sourceTe.network);
}
public void removeSource() {
float prevSpeed = getSpeed();
speed = 0;
source = null;
setNetwork(null);
onSpeedChanged(prevSpeed);
// float prevSpeed = getSpeed();
//
// speed = 0;
// source = null;
// setNetwork(null);
//
// onSpeedChanged(prevSpeed);
}
public void setNetwork(@Nullable Long networkIn) {
if (network == networkIn)
return;
if (network != null)
getOrCreateNetwork().remove(this);
network = networkIn;
if (networkIn == null)
return;
network = networkIn;
KineticNetwork network = getOrCreateNetwork();
network.initialized = true;
network.add(this);
// if (network == networkIn)
// return;
// if (network != null)
// getOrCreateNetwork().remove(this);
//
// network = networkIn;
//
// if (networkIn == null)
// return;
//
// network = networkIn;
// KineticNetwork network = getOrCreateNetwork();
// network.initialized = true;
// network.add(this);
}
public KineticNetwork getOrCreateNetwork() {
@ -375,12 +352,12 @@ public class KineticTileEntity extends SmartTileEntity
}
public void attachKinetics() {
updateSpeed = false;
RotationPropagator.handleAdded(level, worldPosition, this);
//updateSpeed = false;
//RotationPropagator.handleAdded(level, worldPosition, this);
}
public void detachKinetics() {
RotationPropagator.handleRemoved(level, worldPosition, this);
//RotationPropagator.handleRemoved(level, worldPosition, this);
}
public boolean isSpeedRequirementFulfilled() {
@ -413,15 +390,15 @@ public class KineticTileEntity extends SmartTileEntity
return;
}
KineticTileEntity tileEntity = (KineticTileEntity) tileEntityIn;
if (state.getBlock() instanceof KineticBlock
&& !((KineticBlock) state.getBlock()).areStatesKineticallyEquivalent(currentState, state)) {
if (tileEntity.hasNetwork())
tileEntity.getOrCreateNetwork()
.remove(tileEntity);
tileEntity.detachKinetics();
tileEntity.removeSource();
}
// KineticTileEntity tileEntity = (KineticTileEntity) tileEntityIn;
// if (state.getBlock() instanceof KineticBlock
// && !((KineticBlock) state.getBlock()).areStatesKineticallyEquivalent(currentState, state)) {
// if (tileEntity.hasNetwork())
// tileEntity.getOrCreateNetwork()
// .remove(tileEntity);
// tileEntity.detachKinetics();
// tileEntity.removeSource();
// }
world.setBlock(pos, state, 3);
}
@ -492,8 +469,6 @@ public class KineticTileEntity extends SmartTileEntity
public void clearKineticInformation() {
speed = 0;
source = null;
network = null;
overStressed = false;
stress = 0;
capacity = 0;

View file

@ -2,7 +2,13 @@ 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;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class KineticNetwork {
@ -11,19 +17,16 @@ public class KineticNetwork {
private final Set<KineticNode> generators = new HashSet<>();
private final Set<Pair<KineticNode, KineticNode>> conflictingCycles = new HashSet<>();
private float rootSpeed;
private boolean speedDirty;
public KineticNetwork(KineticNode root) {
addMember(root);
rootSpeed = root.getGeneratedSpeed();
speedDirty = false;
}
public void addMember(KineticNode node) {
members.add(node);
if (node.isGenerator() && !generators.contains(node)) {
if (node.isGenerator()) {
generators.add(node);
speedDirty = true;
}
}
@ -34,14 +37,12 @@ public class KineticNetwork {
} else {
generators.remove(node);
}
speedDirty = true;
}
public void removeMember(KineticNode node) {
members.remove(node);
if (node.isGenerator() && generators.contains(node)) {
if (node.isGenerator()) {
generators.remove(node);
speedDirty = true;
}
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
}
@ -55,34 +56,70 @@ public class KineticNetwork {
return rootSpeed;
}
public SolveResult recalculateSpeed() {
if (!conflictingCycles.isEmpty() && !generators.isEmpty()) return SolveResult.CONTRADICTION;
if (!speedDirty) return SolveResult.OK;
/**
* 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.
*/
public SolveResult recalculateSpeed(@Nullable KineticNode checkRoot, boolean forced) {
if (!conflictingCycles.isEmpty() && !generators.isEmpty()) {
// cycle with conflicting speed ratios is present
return SolveResult.CONTRADICTION;
}
// find the generator that would 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);
newSpeed = sign * speedAtRoot;
} else {
if (Math.signum(speedAtRoot) != sign)
return SolveResult.CONTRADICTION;
newSpeed = Math.max(newSpeed, sign * speedAtRoot);
} else if (Math.signum(speedAtRoot) != sign) {
// generators are turning against each other
return SolveResult.CONTRADICTION;
}
newSpeed = Math.max(newSpeed, sign * speedAtRoot);
}
newSpeed *= sign;
if (rootSpeed != newSpeed) {
if (!Mth.equal(rootSpeed, newSpeed)) {
rootSpeed = newSpeed;
for (KineticNode member : members) {
member.onSpeedUpdated();
if (checkRoot == null) {
members.forEach(KineticNode::tryUpdateSpeed);
} else {
updateNodeSpeeds(checkRoot, false);
}
} else if (forced) {
updateNodeSpeeds(checkRoot, true);
}
speedDirty = false;
return SolveResult.OK;
}
private void updateNodeSpeeds(KineticNode root, boolean followSources) {
Set<KineticNode> visited = new HashSet<>();
List<KineticNode> frontier = new LinkedList<>();
frontier.add(root);
// 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);
}
} else {
// stop searching on this branch once a speeding node is found
cur.onPopBlock();
}
}
}
}

View file

@ -1,9 +1,11 @@
package com.simibubi.create.content.contraptions.solver;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import javax.annotation.Nullable;
@ -38,13 +40,16 @@ public class KineticNode {
this.generatedSpeed = state.getGeneratedSpeed();
this.network = new KineticNetwork(this);
onSpeedUpdated();
}
public KineticConnections getConnections() {
return connections;
}
/**
* @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
*/
public Map<KineticNode, Float> getActiveConnections() {
return connections.getDirections().stream()
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d))
@ -68,9 +73,10 @@ public class KineticNode {
}
public void setGeneratedSpeed(float newSpeed) {
if (Mth.equal(generatedSpeed, newSpeed)) return;
generatedSpeed = newSpeed;
network.updateMember(this);
if (network.recalculateSpeed().isContradiction())
if (network.recalculateSpeed(this, false).isContradiction())
onPopBlock();
}
@ -78,7 +84,6 @@ public class KineticNode {
this.network.removeMember(this);
this.network = network;
network.addMember(this);
onSpeedUpdated();
}
private void setSource(KineticNode from, float ratio) {
@ -93,21 +98,18 @@ public class KineticNode {
.stream()
.findAny()
.ifPresent(n -> {
if (n.propagateSource().isContradiction())
if (n.propagateSource(this).isContradiction())
onPopBlock();
});
}
public void onRemoved() {
network.removeMember(this);
for (KineticNode neighbor : getActiveConnections().keySet()) {
if (neighbor.source != this) continue;
neighbor.rerootHere();
}
network.recalculateSpeed();
}
private SolveResult propagateSource() {
/**
* 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) {
List<KineticNode> frontier = new LinkedList<>();
frontier.add(this);
@ -116,30 +118,53 @@ public class KineticNode {
for (Map.Entry<KineticNode, Float> entry : cur.getActiveConnections().entrySet()) {
KineticNode next = entry.getKey();
float ratio = entry.getValue();
if (next == cur.source) continue;
if (next.network == network) {
if (next.speedRatio != cur.speedRatio * ratio) {
if (!Mth.equal(next.speedRatio, cur.speedRatio * ratio)) {
// we found a cycle with conflicting speed ratios
network.markConflictingCycle(cur, next);
}
continue;
}
next.setSource(cur, ratio);
frontier.add(next);
}
}
return network.recalculateSpeed();
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);
}
private void rerootHere() {
source = null;
speedRatio = 1;
setNetwork(new KineticNetwork(this));
propagateSource();
if (tryUpdateSpeed().isOk()) {
propagateSource(this);
} else {
onPopBlock();
}
}
public void onSpeedUpdated() {
/**
* Updates the speed of this node based on its network's root speed and its own speed ratio.
* @return CONTRADICTION if the node's new speed exceeds the maximum value, and OK otherwise
*/
protected SolveResult tryUpdateSpeed() {
speedNext = network.getRootSpeed() * speedRatio;
if (Math.abs(speedNext) > AllConfigs.SERVER.kinetics.maxRotationSpeed.get())
return SolveResult.CONTRADICTION;
return SolveResult.OK;
}
public void flushChangedSpeed() {
@ -155,4 +180,8 @@ public class KineticNode {
entity.getLevel().destroyBlock(entity.getBlockPos(), true);
}
public boolean isSourceOf(KineticNode other) {
return other.source == this;
}
}

View file

@ -24,7 +24,11 @@ public class KineticSolver {
removeNode(entity);
KineticNode node = new KineticNode(entity, this::getNode);
nodes.put(entity.getBlockPos(), node);
node.onAdded();
if (node.tryUpdateSpeed().isOk()) {
node.onAdded();
} else {
node.onPopBlock();
}
}
public void updateNode(KineticTileEntity entity) {