Deserialization magic

This commit is contained in:
reidbhuntley 2022-01-02 12:16:16 -05:00
parent 44d59fe793
commit 6d3c3e0d1f
14 changed files with 365 additions and 521 deletions

View file

@ -9,7 +9,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.simibubi.create.api.behaviour.BlockSpoutingBehaviour;
import com.simibubi.create.content.CreateItemGroup;
import com.simibubi.create.content.contraptions.TorquePropagator;
import com.simibubi.create.content.contraptions.components.flywheel.engine.FurnaceEngineModifiers;
import com.simibubi.create.content.curiosities.weapons.BuiltinPotatoProjectileTypes;
import com.simibubi.create.content.logistics.RedstoneLinkNetworkHandler;
@ -71,7 +70,6 @@ public class Create {
public static final ServerSchematicLoader SCHEMATIC_RECEIVER = new ServerSchematicLoader();
public static final RedstoneLinkNetworkHandler REDSTONE_LINK_NETWORK_HANDLER = new RedstoneLinkNetworkHandler();
public static final TorquePropagator TORQUE_PROPAGATOR = new TorquePropagator();
public static final ServerLagger LAGGER = new ServerLagger();
public static final Random RANDOM = new Random();

View file

@ -1,194 +0,0 @@
package com.simibubi.create.content.contraptions;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.contraptions.components.flywheel.FlywheelTileEntity;
import com.simibubi.create.foundation.advancement.AllTriggers;
public class KineticNetwork {
public Long id;
public boolean initialized;
public boolean containsFlywheel;
public Map<KineticTileEntity, Float> sources;
public Map<KineticTileEntity, Float> members;
private float currentCapacity;
private float currentStress;
private float unloadedCapacity;
private float unloadedStress;
private int unloadedMembers;
public KineticNetwork() {
sources = new HashMap<>();
members = new HashMap<>();
containsFlywheel = false;
}
public void initFromTE(float maxStress, float currentStress, int members) {
unloadedCapacity = maxStress;
unloadedStress = currentStress;
unloadedMembers = members;
initialized = true;
updateStress();
updateCapacity();
}
public void addSilently(KineticTileEntity te, float lastCapacity, float lastStress) {
if (members.containsKey(te))
return;
if (te.isSource()) {
unloadedCapacity -= lastCapacity * getStressMultiplierForSpeed(te.getGeneratedSpeed());
float addedStressCapacity = te.calculateAddedStressCapacity();
sources.put(te, addedStressCapacity);
containsFlywheel |= te instanceof FlywheelTileEntity;
}
unloadedStress -= lastStress * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
float stressApplied = te.calculateStressApplied();
members.put(te, stressApplied);
unloadedMembers--;
if (unloadedMembers < 0)
unloadedMembers = 0;
if (unloadedCapacity < 0)
unloadedCapacity = 0;
if (unloadedStress < 0)
unloadedStress = 0;
}
public void add(KineticTileEntity te) {
if (members.containsKey(te))
return;
if (te.isSource())
sources.put(te, te.calculateAddedStressCapacity());
members.put(te, te.calculateStressApplied());
updateFromNetwork(te);
//te.networkDirty = true;
}
public void updateCapacityFor(KineticTileEntity te, float capacity) {
sources.put(te, capacity);
updateCapacity();
}
public void updateStressFor(KineticTileEntity te, float stress) {
members.put(te, stress);
updateStress();
}
public void remove(KineticTileEntity te) {
if (!members.containsKey(te))
return;
if (te.isSource())
sources.remove(te);
members.remove(te);
te.updateFromNetwork(0, 0, 0);
if (members.isEmpty()) {
TorquePropagator.networks.get(te.getLevel())
.remove(this.id);
return;
}
// members.keySet()
// .stream()
// .findFirst()
// .map(member -> member.networkDirty = true);
}
public void sync() {
for (KineticTileEntity te : members.keySet())
updateFromNetwork(te);
}
private void updateFromNetwork(KineticTileEntity te) {
boolean wasOverStressed = te.isOverStressed();
te.updateFromNetwork(currentCapacity, currentStress, getSize());
if (!wasOverStressed && te.isOverStressed() && te.getTheoreticalSpeed() != 0) {
AllTriggers.triggerForNearbyPlayers(AllTriggers.OVERSTRESSED, te.getLevel(), te.getBlockPos(), 4);
if (containsFlywheel)
AllTriggers.triggerForNearbyPlayers(AllTriggers.OVERSTRESS_FLYWHEEL, te.getLevel(), te.getBlockPos(), 4);
}
}
public void updateCapacity() {
float newMaxStress = calculateCapacity();
if (currentCapacity != newMaxStress) {
currentCapacity = newMaxStress;
sync();
}
}
public void updateStress() {
float newStress = calculateStress();
if (currentStress != newStress) {
currentStress = newStress;
sync();
}
}
public void updateNetwork() {
float newStress = calculateStress();
float newMaxStress = calculateCapacity();
if (currentStress != newStress || currentCapacity != newMaxStress) {
currentStress = newStress;
currentCapacity = newMaxStress;
sync();
}
}
public float calculateCapacity() {
float presentCapacity = 0;
containsFlywheel = false;
for (Iterator<KineticTileEntity> iterator = sources.keySet()
.iterator(); iterator.hasNext();) {
KineticTileEntity te = iterator.next();
if (te.getLevel()
.getBlockEntity(te.getBlockPos()) != te) {
iterator.remove();
continue;
}
containsFlywheel |= te instanceof FlywheelTileEntity;
presentCapacity += getActualCapacityOf(te);
}
float newMaxStress = presentCapacity + unloadedCapacity;
return newMaxStress;
}
public float calculateStress() {
float presentStress = 0;
for (Iterator<KineticTileEntity> iterator = members.keySet()
.iterator(); iterator.hasNext();) {
KineticTileEntity te = iterator.next();
if (te.getLevel()
.getBlockEntity(te.getBlockPos()) != te) {
iterator.remove();
continue;
}
presentStress += getActualStressOf(te);
}
float newStress = presentStress + unloadedStress;
return newStress;
}
public float getActualCapacityOf(KineticTileEntity te) {
return sources.get(te) * getStressMultiplierForSpeed(te.getGeneratedSpeed());
}
public float getActualStressOf(KineticTileEntity te) {
return members.get(te) * getStressMultiplierForSpeed(te.getTheoreticalSpeed());
}
private static float getStressMultiplierForSpeed(float speed) {
return Math.abs(speed);
}
public int getSize() {
return unloadedMembers + members.size();
}
}

View file

@ -1,42 +0,0 @@
package com.simibubi.create.content.contraptions;
import java.util.HashMap;
import java.util.Map;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.foundation.utility.WorldHelper;
import net.minecraft.world.level.LevelAccessor;
public class TorquePropagator {
static Map<LevelAccessor, Map<Long, KineticNetwork>> networks = new HashMap<>();
public void onLoadWorld(LevelAccessor world) {
networks.put(world, new HashMap<>());
Create.LOGGER.debug("Prepared Kinetic Network Space for " + WorldHelper.getDimensionID(world));
}
public void onUnloadWorld(LevelAccessor world) {
networks.remove(world);
Create.LOGGER.debug("Removed Kinetic Network Space for " + WorldHelper.getDimensionID(world));
}
public KineticNetwork getOrCreateNetworkFor(KineticTileEntity te) {
Long id = te.network;
KineticNetwork network;
Map<Long, KineticNetwork> map = networks.get(te.getLevel());
if (id == null)
return null;
if (!map.containsKey(id)) {
network = new KineticNetwork();
network.id = te.network;
map.put(id, network);
}
network = map.get(id);
return network;
}
}

View file

@ -2,16 +2,8 @@ package com.simibubi.create.content.contraptions.base;
import java.util.List;
import com.simibubi.create.content.contraptions.KineticNetwork;
import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel;
import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation;
import com.simibubi.create.foundation.utility.Lang;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@ -23,28 +15,6 @@ public abstract class GeneratingKineticTileEntity extends KineticTileEntity {
super(typeIn, pos, state);
}
protected void notifyStressCapacityChange(float capacity) {
getOrCreateNetwork().updateCapacityFor(this, capacity);
}
@Override
public void removeSource() {
if (hasSource() && isSource())
reActivateSource = true;
super.removeSource();
}
@Override
public void setSource(BlockPos source) {
super.setSource(source);
BlockEntity tileEntity = level.getBlockEntity(source);
if (!(tileEntity instanceof KineticTileEntity))
return;
KineticTileEntity sourceTe = (KineticTileEntity) tileEntity;
if (reActivateSource && Math.abs(sourceTe.getSpeed()) >= Math.abs(getGeneratedSpeed()))
reActivateSource = false;
}
@Override
public void tick() {
super.tick();
@ -58,107 +28,59 @@ public abstract class GeneratingKineticTileEntity extends KineticTileEntity {
public boolean addToGoggleTooltip(List<Component> tooltip, boolean isPlayerSneaking) {
boolean added = super.addToGoggleTooltip(tooltip, isPlayerSneaking);
float stressBase = calculateAddedStressCapacity();
if (stressBase != 0 && IRotate.StressImpact.isEnabled()) {
tooltip.add(componentSpacing.plainCopy().append(Lang.translate("gui.goggles.generator_stats")));
tooltip.add(componentSpacing.plainCopy().append(Lang.translate("tooltip.capacityProvided").withStyle(ChatFormatting.GRAY)));
float speed = getTheoreticalSpeed();
if (speed != getGeneratedSpeed() && speed != 0)
stressBase *= getGeneratedSpeed() / speed;
speed = Math.abs(speed);
float stressTotal = stressBase * speed;
tooltip.add(
componentSpacing.plainCopy()
.append(new TextComponent(" " + IHaveGoggleInformation.format(stressTotal))
.append(Lang.translate("generic.unit.stress"))
.withStyle(ChatFormatting.AQUA))
.append(" ")
.append(Lang.translate("gui.goggles.at_current_speed").withStyle(ChatFormatting.DARK_GRAY)));
added = true;
}
// float stressBase = calculateAddedStressCapacity();
// if (stressBase != 0 && IRotate.StressImpact.isEnabled()) {
// tooltip.add(componentSpacing.plainCopy().append(Lang.translate("gui.goggles.generator_stats")));
// tooltip.add(componentSpacing.plainCopy().append(Lang.translate("tooltip.capacityProvided").withStyle(ChatFormatting.GRAY)));
//
// float speed = getTheoreticalSpeed();
// if (speed != getGeneratedSpeed() && speed != 0)
// stressBase *= getGeneratedSpeed() / speed;
//
// speed = Math.abs(speed);
// float stressTotal = stressBase * speed;
//
// tooltip.add(
// componentSpacing.plainCopy()
// .append(new TextComponent(" " + IHaveGoggleInformation.format(stressTotal))
// .append(Lang.translate("generic.unit.stress"))
// .withStyle(ChatFormatting.AQUA))
// .append(" ")
// .append(Lang.translate("gui.goggles.at_current_speed").withStyle(ChatFormatting.DARK_GRAY)));
//
// added = true;
// }
return added;
}
public void updateGeneratedRotation() {
float speed = getGeneratedSpeed();
float prevSpeed = this.speed;
if (level.isClientSide)
return;
if (prevSpeed != speed) {
if (!hasSource()) {
SpeedLevel levelBefore = SpeedLevel.of(this.speed);
SpeedLevel levelafter = SpeedLevel.of(speed);
if (levelBefore != levelafter)
effects.queueRotationIndicators();
}
applyNewSpeed(prevSpeed, speed);
}
if (hasNetwork() && speed != 0) {
KineticNetwork network = getOrCreateNetwork();
notifyStressCapacityChange(calculateAddedStressCapacity());
getOrCreateNetwork().updateStressFor(this, calculateStressApplied());
network.updateStress();
}
onSpeedChanged(prevSpeed);
sendData();
}
public void applyNewSpeed(float prevSpeed, float speed) {
// Speed changed to 0
if (speed == 0) {
if (hasSource()) {
notifyStressCapacityChange(0);
getOrCreateNetwork().updateStressFor(this, calculateStressApplied());
return;
}
detachKinetics();
setSpeed(0);
setNetwork(null);
return;
}
// Now turning - create a new Network
if (prevSpeed == 0) {
setSpeed(speed);
setNetwork(createNetworkId());
attachKinetics();
return;
}
// Change speed when overpowered by other generator
if (hasSource()) {
// Staying below Overpowered speed
if (Math.abs(prevSpeed) >= Math.abs(speed)) {
if (Math.signum(prevSpeed) != Math.signum(speed))
level.destroyBlock(worldPosition, true);
return;
}
// Faster than attached network -> become the new source
detachKinetics();
setSpeed(speed);
source = null;
setNetwork(createNetworkId());
attachKinetics();
return;
}
// Reapply source
detachKinetics();
setSpeed(speed);
attachKinetics();
// float speed = getGeneratedSpeed();
// float prevSpeed = this.speed;
//
// if (level.isClientSide)
// return;
//
// if (prevSpeed != speed) {
// if (!hasSource()) {
// SpeedLevel levelBefore = SpeedLevel.of(this.speed);
// SpeedLevel levelafter = SpeedLevel.of(speed);
// if (levelBefore != levelafter)
// effects.queueRotationIndicators();
// }
//
// applyNewSpeed(prevSpeed, speed);
// }
//
// if (hasNetwork() && speed != 0) {
// KineticNetwork network = getOrCreateNetwork();
// notifyStressCapacityChange(calculateAddedStressCapacity());
// getOrCreateNetwork().updateStressFor(this, calculateStressApplied());
// network.updateStress();
// }
//
// onSpeedChanged(prevSpeed);
// sendData();
}
public Long createNetworkId() {

View file

@ -10,8 +10,6 @@ import javax.annotation.Nullable;
import com.jozufozu.flywheel.api.FlywheelRendered;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.KineticNetwork;
import com.simibubi.create.content.contraptions.base.IRotate.SpeedLevel;
import com.simibubi.create.content.contraptions.base.IRotate.StressImpact;
import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation;
@ -19,6 +17,7 @@ import com.simibubi.create.content.contraptions.goggles.IHaveHoveringInformation
import com.simibubi.create.content.contraptions.relays.elementary.ICogWheel;
import com.simibubi.create.content.contraptions.relays.gearbox.GearboxBlock;
import com.simibubi.create.content.contraptions.solver.AllConnections;
import com.simibubi.create.content.contraptions.solver.IKineticController;
import com.simibubi.create.content.contraptions.solver.KineticConnections;
import com.simibubi.create.content.contraptions.solver.KineticSolver;
import com.simibubi.create.foundation.block.BlockStressValues;
@ -51,7 +50,7 @@ import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.DistExecutor;
public class KineticTileEntity extends SmartTileEntity
implements IHaveGoggleInformation, IHaveHoveringInformation, FlywheelRendered {
implements IHaveGoggleInformation, IHaveHoveringInformation, FlywheelRendered, IKineticController {
public @Nullable Long network = null;
public @Nullable BlockPos source = null;
@ -69,35 +68,25 @@ public class KineticTileEntity extends SmartTileEntity
protected float lastStressApplied;
protected float lastCapacityProvided;
private KineticConnections connections = AllConnections.EMPTY;
private final KineticConnections connections;
public KineticTileEntity(BlockEntityType<?> typeIn, BlockPos pos, BlockState state) {
super(typeIn, pos, state);
effects = new KineticEffectHandler(this);
if (state.getBlock() instanceof IRotate rotate) {
connections = rotate.getInitialConnections(state);
} else {
connections = AllConnections.EMPTY;
}
}
public KineticConnections getConnections() {
return connections;
}
@Override public KineticConnections getConnections() { return connections; }
public float getGeneratedSpeed() {
return 0;
}
@Override public float getStressImpact() { return getDefaultStressImpact(); }
public float getStressImpact() {
return getDefaultStressImpact();
}
public float getStressCapacity() {
return getDefaultStressCapacity();
}
public boolean isStressConstant() {
return false;
}
@Override public float getStressCapacity() { return getDefaultStressCapacity(); }
public float getDefaultStressImpact() {
return (float) BlockStressValues.getImpact(getStressConfigKey());
@ -129,9 +118,9 @@ public class KineticTileEntity extends SmartTileEntity
super.tick();
effects.tick();
if (!level.isClientSide) {
KineticSolver.getSolver(level).updateNode(this);
}
// if (!level.isClientSide) {
// KineticSolver.getSolver(level).updateNode(this);
// }
if (level.isClientSide) {
cachedBoundingBox = null; // cache the bounding box for every frame between ticks
@ -161,9 +150,7 @@ public class KineticTileEntity extends SmartTileEntity
super.onChunkUnloaded();
if (!level.isClientSide) {
preKineticsUnloaded();
KineticSolver solver = KineticSolver.getSolver(level);
solver.updateNode(this);
solver.unloadNode(this);
KineticSolver.getSolver(level).unloadNode(this);
}
}
@ -312,7 +299,8 @@ public class KineticTileEntity extends SmartTileEntity
}
public boolean isSource() {
return getGeneratedSpeed() != 0;
//return getGeneratedSpeed() != 0;
return false;
}
public float getSpeed() {
@ -378,10 +366,6 @@ public class KineticTileEntity extends SmartTileEntity
// network.add(this);
}
public KineticNetwork getOrCreateNetwork() {
return Create.TORQUE_PROPAGATOR.getOrCreateNetworkFor(this);
}
public boolean hasNetwork() {
return network != null;
}

View file

@ -7,15 +7,20 @@ import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.contraptions.components.motor.CreativeMotorTileEntity;
import com.simibubi.create.content.contraptions.relays.elementary.CogWheelBlock;
import com.simibubi.create.content.contraptions.relays.elementary.ICogWheel;
import com.simibubi.create.content.contraptions.solver.KineticControllerSerial;
import com.simibubi.create.content.contraptions.solver.KineticNode;
import com.simibubi.create.content.contraptions.solver.KineticSolver;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.tileEntity.behaviour.ValueBoxTransform;
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
@ -57,16 +62,15 @@ public class SpeedControllerTileEntity extends KineticTileEntity {
return targetSpeed.getValue();
}
private void updateTargetRotation() {
if (hasNetwork())
getOrCreateNetwork().remove(this);
RotationPropagator.handleRemoved(level, worldPosition, this);
removeSource();
attachKinetics();
@Override
public void onUpdate(Level level, KineticSolver solver, KineticNode node) {
solver.getNode(node.getPos().above())
.filter(n -> node.getActiveStressOnlyConnections().anyMatch(m -> m == n))
.ifPresent(n -> n.setController(node, KineticControllerSerial.SPEED_CONTROLLER_COG));
}
public static float getConveyedSpeed(KineticTileEntity cogWheel, KineticTileEntity speedControllerIn,
boolean targetingController) {
boolean targetingController) {
if (!(speedControllerIn instanceof SpeedControllerTileEntity))
return 0;

View file

@ -2,11 +2,7 @@ package com.simibubi.create.content.contraptions.relays.elementary;
import com.simibubi.create.content.contraptions.base.KineticTileEntity;
import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
@ -27,13 +23,4 @@ public class SimpleKineticTileEntity extends KineticTileEntity {
return false;
}
@Override
public float getGeneratedSpeed() {
BlockPos belowPos = getBlockPos().below();
if (isStressOnlyConnected(belowPos)
&& level.getBlockEntity(belowPos) instanceof SpeedControllerTileEntity controller
&& controller.getSpeed() != 0)
return controller.getTargetSpeed();
return 0;
}
}

View file

@ -57,7 +57,7 @@ public class StressGaugeTileEntity extends GaugeTileEntity {
return;
}
updateFromNetwork(capacity, stress, getOrCreateNetwork().getSize());
updateFromNetwork(capacity, stress, 0);
}
@Override

View file

@ -0,0 +1,30 @@
package com.simibubi.create.content.contraptions.solver;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.Level;
public interface IKineticController {
default void onUpdate(Level level, KineticSolver solver, KineticNode node) { }
default KineticConnections getConnections() {
return AllConnections.EMPTY;
}
default float getGeneratedSpeed() {
return 0;
}
default float getStressImpact() {
return 0;
}
default float getStressCapacity() {
return 0;
}
default boolean isStressConstant() {
return false;
}
default CompoundTag save(CompoundTag tag) { return tag; }
}

View file

@ -0,0 +1,73 @@
package com.simibubi.create.content.contraptions.solver;
import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerTileEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.Level;
import java.util.Optional;
public enum KineticControllerSerial {
SPEED_CONTROLLER_COG {
class Controller implements IKineticController {
private final KineticConnections connections;
private float targetSpeed;
private float generatedSpeed;
public Controller(KineticConnections connections, float targetSpeed) {
this.connections = connections;
this.targetSpeed = targetSpeed;
}
@Override
public KineticConnections getConnections() {
return connections;
}
@Override
public void onUpdate(Level level, KineticSolver solver, KineticNode node) {
BlockPos below = node.getPos().below();
if (level.getBlockEntity(below) instanceof SpeedControllerTileEntity se) {
targetSpeed = se.getTargetSpeed();
}
Optional<KineticNode> seNode = solver.getNode(below);
if (seNode.isPresent() && seNode.get().getTheoreticalSpeed() != 0) {
generatedSpeed = targetSpeed;
} else {
generatedSpeed = 0;
}
}
@Override
public float getGeneratedSpeed() {
return generatedSpeed;
}
@Override
public CompoundTag save(CompoundTag tag) {
tag.put("Connections", connections.save(new CompoundTag()));
tag.putFloat("Target", targetSpeed);
return tag;
}
}
@Override
public IKineticController init(KineticNode prev) {
return new Controller(prev.getConnections(), 0);
}
@Override
public IKineticController load(CompoundTag tag) {
KineticConnections connections = KineticConnections.load(tag.getCompound("Connections"));
float target = tag.getFloat("Target");
return new Controller(connections, target);
}
};
public abstract IKineticController init(KineticNode prev);
public abstract IKineticController load(CompoundTag tag);
}

View file

@ -10,6 +10,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class KineticNetwork {
@ -28,18 +29,18 @@ public class KineticNetwork {
private final Set<KineticNode> potentialNewBranches = new HashSet<>();
private final ResetableLazy<Float> totalStressImpact = ResetableLazy.of(() ->
(float) members.stream().mapToDouble(n -> n.getTotalStressImpact(rootTheoreticalSpeed)).sum());
(float) members.stream().mapToDouble(KineticNode::getTotalStressImpact).sum());
private final ResetableLazy<Float> totalStressCapacity = ResetableLazy.of(() ->
(float) members.stream().mapToDouble(KineticNode::getStressCapacity).sum());
private boolean ticked;
private final Set<KineticNode> stressConnectors = new HashSet<>();
public KineticNetwork(KineticNode root) {
protected KineticNetwork(KineticNode root) {
addMember(root);
}
public void addMember(KineticNode node) {
protected void addMember(KineticNode node) {
members.add(node);
potentialNewBranches.add(node);
if (node.getConnections().hasStressOnlyConnections()) stressConnectors.add(node);
@ -52,11 +53,11 @@ public class KineticNetwork {
if (node.hasStressCapacity()) onMemberStressCapacityUpdated();
}
public void onMemberLoaded(KineticNode node) {
protected void onMemberLoaded(KineticNode node) {
potentialNewBranches.add(node);
}
public void onMemberGeneratedSpeedUpdated(KineticNode node) {
protected void onMemberGeneratedSpeedUpdated(KineticNode node) {
if (node.isGenerator()) {
generators.add(node);
} else {
@ -66,15 +67,15 @@ public class KineticNetwork {
onMemberStressCapacityUpdated();
}
public void onMemberStressImpactUpdated() {
protected void onMemberStressImpactUpdated() {
totalStressImpact.reset();
}
public void onMemberStressCapacityUpdated() {
protected void onMemberStressCapacityUpdated() {
totalStressCapacity.reset();
}
public void removeMember(KineticNode node) {
protected void removeMember(KineticNode node) {
if (node.isGenerator() && generators.contains(node)) {
generators.remove(node);
rootSpeedDirty = true;
@ -87,25 +88,25 @@ public class KineticNetwork {
conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node);
}
public void markConflictingCycle(KineticNode from, KineticNode to) {
protected void markConflictingCycle(KineticNode from, KineticNode to) {
if (!members.contains(from) || !members.contains(to)) throw new IllegalArgumentException();
conflictingCycles.add(Pair.of(from, to));
}
public boolean isStopped() { return generators.isEmpty() || overstressed; }
protected boolean isStopped() { return generators.isEmpty() || overstressed; }
/**
* Recalculates the speed at the root node of this network.
* @return CONTRADICTION if the network has cycles with conflicting speed ratios or generators turning against
* each other, and OK otherwise
*/
public SolveResult tryRecalculateSpeed() {
protected SolveResult tryRecalculateSpeed() {
SolveResult result = tryRecalculateTheoreticalSpeed();
if (isStopped()) return SolveResult.OK;
return result;
}
private SolveResult tryRecalculateTheoreticalSpeed() {
protected SolveResult tryRecalculateTheoreticalSpeed() {
SolveResult result = conflictingCycles.isEmpty() ? SolveResult.OK : SolveResult.CONTRADICTION;
if (!rootSpeedDirty) return result;
@ -152,7 +153,11 @@ public class KineticNetwork {
return totalStressCapacity.get();
}
private float getRootSpeed() {
public float getRootTheoreticalSpeed() {
return rootTheoreticalSpeed;
}
public float getRootSpeed() {
return isStopped() ? 0 : rootTheoreticalSpeed;
}
@ -161,45 +166,52 @@ public class KineticNetwork {
onMemberStressImpactUpdated();
}
public void untick() {
protected void untick() {
ticked = false;
}
public void tick(List<KineticNetwork> newNetworks) {
protected void tick(List<KineticNetwork> newNetworks) {
if (ticked) return;
Set<KineticNetwork> stressConnected = stressConnectors.stream()
.flatMap(KineticNode::getActiveStressOnlyConnections)
.map(KineticNode::getNetwork)
.collect(Collectors.toSet());
stressConnected.add(this);
float stressImpact = 0;
float stressCapacity = 0;
Set<KineticNode> popQueue = new HashSet<>();
Consumer<KineticNode> pop = n -> { n.popBlock(); newNetworks.add(n.getNetwork()); };
for (KineticNetwork cur : stressConnected) {
cur.ticked = true;
cur.updateMemberSpeeds(newNetworks);
cur.updateMemberSpeeds(popQueue::add);
stressImpact += cur.getTotalStressImpact();
stressCapacity += cur.getTotalStressCapacity();
}
boolean nowOverstressed = stressImpact > stressCapacity;
if (!nowOverstressed) {
// we should only pop speeding nodes if the network isn't actually overstressed now
popQueue.forEach(pop);
}
for (KineticNetwork cur : stressConnected) {
if (cur.generators.isEmpty()) {
cur.overstressed = false;
} else if (nowOverstressed) {
if (nowOverstressed) {
if (!cur.overstressed) {
// just became overstressed
cur.overstressed = true;
cur.onRootSpeedChanged();
cur.members.forEach(KineticNode::stop);
}
} else {
if (cur.overstressed) {
// just became non-overstressed
cur.overstressed = false;
cur.onRootSpeedChanged();
cur.bulldozeContradictingMembers(newNetworks);
cur.updateMemberSpeeds(newNetworks);
cur.updateMemberSpeeds(pop);
}
}
@ -208,10 +220,10 @@ public class KineticNetwork {
}
/**
* Update the speed of every member, starting from the main generator and popping off speeding nodes along the way
* @param newNetworks a List that any new networks created during this call will be added to
* Update the speed of every member, starting from the main generator and checking for speeding nodes along the way
* @param onSpeeding a function to call whenever a speeding node is found and should be popped
*/
private void updateMemberSpeeds(List<KineticNetwork> newNetworks) {
private void updateMemberSpeeds(Consumer<KineticNode> onSpeeding) {
SolveResult recalculateSpeedResult = tryRecalculateSpeed();
// generators should not be turning against each other or have conflicting cycles by now
assert(recalculateSpeedResult.isOk());
@ -225,17 +237,17 @@ public class KineticNetwork {
if (rootSpeedChanged) {
// root speed changed, update all nodes starting from the main generator
rootSpeedChanged = false;
bfs(mainGenerator, newNetworks, false);
bfs(mainGenerator, onSpeeding, 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));
.forEach(n -> bfs(n, onSpeeding, true));
}
potentialNewBranches.clear();
}
private void bfs(KineticNode root, List<KineticNetwork> newNetworks, boolean followSource) {
private void bfs(KineticNode root, Consumer<KineticNode> onSpeeding, 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<>();
@ -246,15 +258,14 @@ public class KineticNetwork {
if (!members.contains(cur) || visited.contains(cur)) continue;
visited.add(cur);
if (cur.tryUpdateSpeed(getRootSpeed()).isOk()) {
if (cur.tryUpdateSpeed().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());
onSpeeding.accept(cur);
}
}
}

View file

@ -2,6 +2,7 @@ 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.NBTHelper;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
@ -11,9 +12,11 @@ import net.minecraft.util.Mth;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -22,6 +25,10 @@ public class KineticNode {
private final KineticSolver solver;
private @Nullable KineticTileEntity entity;
private @Nullable IKineticController controller;
private @Nullable KineticControllerSerial controllerType;
private @Nullable Set<BlockPos> controlling;
private @Nullable KineticNode source;
private KineticNetwork network;
private float speedRatio = 1;
@ -35,18 +42,22 @@ public class KineticNode {
private float stressImpact;
private final boolean constantStress;
protected KineticNode regen() {
return new KineticNode(solver, pos, entity, getController().get(), controllerType);
}
public KineticNode(KineticSolver solver, KineticTileEntity entity) {
this.solver = solver;
this(solver, entity.getBlockPos(), entity, entity, null);
this.controller = null;
}
private KineticNode(KineticSolver solver, BlockPos pos, @Nullable KineticTileEntity entity,
IKineticController controller, @Nullable KineticControllerSerial controllerType) {
this(solver, pos, controller.getConnections(), controller.getGeneratedSpeed(), controller.getStressCapacity(),
controller.getStressImpact(), controller.isStressConstant());
this.entity = entity;
this.pos = entity.getBlockPos();
this.connections = entity.getConnections();
this.generatedSpeed = entity.getGeneratedSpeed();
this.stressImpact = entity.getStressImpact();
this.stressCapacity = entity.getStressCapacity();
this.constantStress = entity.isStressConstant();
this.network = new KineticNetwork(this);
this.controller = controller;
this.controllerType = controllerType;
}
private KineticNode(KineticSolver solver, BlockPos pos, KineticConnections connections, float generatedSpeed,
@ -65,17 +76,29 @@ public class KineticNode {
public CompoundTag save(CompoundTag tag) {
tag.put("Pos", NbtUtils.writeBlockPos(pos));
if (controller != null && controllerType != null) {
NBTHelper.writeEnum(tag, "ControllerType", controllerType);
tag.put("Controller", controller.save(new CompoundTag()));
return tag;
}
tag.put("Connections", connections.save(new CompoundTag()));
tag.putFloat("Generated", generatedSpeed);
tag.putFloat("Capacity", stressCapacity);
tag.putFloat("Impact", stressImpact);
if (constantStress)
if (constantStress) {
tag.putBoolean("ConstantStress", true);
}
return tag;
}
public static KineticNode load(KineticSolver solver, CompoundTag tag) {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos"));
if (tag.contains("Controller") && tag.contains("ControllerType")) {
KineticControllerSerial type = NBTHelper.readEnum(tag, "ControllerType", KineticControllerSerial.class);
return new KineticNode(solver, pos, null, type.load(tag.getCompound("Controller")), type);
}
KineticConnections connections = KineticConnections.load(tag.getCompound("Connections"));
float generatedSpeed = tag.getFloat("Generated");
float stressCapacity = tag.getFloat("Capacity");
@ -88,16 +111,38 @@ public class KineticNode {
return entity != null;
}
public void onLoaded(KineticTileEntity entity) {
protected void onLoaded(KineticTileEntity entity) {
this.entity = entity;
network.onMemberLoaded(this);
if (speedCur != 0) entity.setSpeed(speedCur);
if (speedCur != 0)
entity.setSpeed(speedCur);
}
public void onUnloaded() {
protected void onUnloaded() {
this.entity = null;
}
public Optional<IKineticController> getController() {
if (controller != null) return Optional.of(controller);
if (entity != null) return Optional.of(entity);
return Optional.empty();
}
public boolean setController(KineticNode source, KineticControllerSerial controller) {
if (this.controller != null) return false;
this.controller = controller.init(this);
this.controllerType = controller;
if (source.controlling == null)
source.controlling = new HashSet<>();
source.controlling.add(pos);
return true;
}
protected void removeController() {
controller = null;
controllerType = null;
}
public KineticConnections getConnections() {
return connections;
}
@ -127,12 +172,15 @@ public class KineticNode {
return getActiveConnections().collect(Collectors.toList());
}
public Stream<KineticNetwork> getActiveStressOnlyConnections() {
public Stream<KineticNode> getActiveStressOnlyConnections() {
return connections.getDirections().stream()
.map(d -> solver.getNode(pos.offset(d))
.filter(n -> connections.checkStressOnlyConnection(n.connections, d)))
.flatMap(Optional::stream)
.map(KineticNode::getNetwork);
.flatMap(Optional::stream);
}
public float getGeneratedSpeed() {
return generatedSpeed;
}
public float getGeneratedSpeedAtRoot() {
@ -143,36 +191,42 @@ public class KineticNode {
return generatedSpeed != 0;
}
public boolean onUpdated() {
if (entity == null) return false;
protected enum UpdateResult {
UNCHANGED, CHANGED, NEEDS_REGEN, NEEDS_POP
}
protected UpdateResult onUpdated() {
return getController().map(ctl -> {
if (!getConnections().equals(ctl.getConnections()) || constantStress != ctl.isStressConstant())
return UpdateResult.NEEDS_REGEN;
boolean changed = false;
UpdateResult result = UpdateResult.UNCHANGED;
float generatedSpeedNew = entity.getGeneratedSpeed();
if (this.generatedSpeed != generatedSpeedNew) {
this.generatedSpeed = generatedSpeedNew;
changed = true;
network.onMemberGeneratedSpeedUpdated(this);
if (network.tryRecalculateSpeed().isContradiction()) {
popBlock();
float generatedSpeedNew = ctl.getGeneratedSpeed();
if (this.generatedSpeed != generatedSpeedNew) {
this.generatedSpeed = generatedSpeedNew;
network.onMemberGeneratedSpeedUpdated(this);
if (network.tryRecalculateSpeed().isContradiction()) {
return UpdateResult.NEEDS_POP;
}
result = UpdateResult.CHANGED;
}
}
float stressImpactNew = entity.getStressImpact();
if (this.stressImpact != stressImpactNew) {
this.stressImpact = stressImpactNew;
changed = true;
network.onMemberStressImpactUpdated();
}
float stressImpactNew = ctl.getStressImpact();
if (this.stressImpact != stressImpactNew) {
this.stressImpact = stressImpactNew;
network.onMemberStressImpactUpdated();
result = UpdateResult.CHANGED;
}
float stressCapacityNew = entity.getStressCapacity();
if (this.stressCapacity != stressCapacityNew) {
this.stressCapacity = stressCapacityNew;
changed = true;
network.onMemberStressCapacityUpdated();
}
float stressCapacityNew = ctl.getStressCapacity();
if (this.stressCapacity != stressCapacityNew) {
this.stressCapacity = stressCapacityNew;
network.onMemberStressCapacityUpdated();
result = UpdateResult.CHANGED;
}
return changed;
return result;
}).orElse(UpdateResult.UNCHANGED);
}
public boolean hasStressCapacity() {
@ -183,16 +237,20 @@ public class KineticNode {
return stressImpact != 0;
}
public float getTheoreticalSpeed(float speedAtRoot) {
return speedAtRoot * speedRatio;
public float getSpeed() {
return network.getRootSpeed() * speedRatio;
}
public float getTheoreticalSpeed() {
return network.getRootTheoreticalSpeed() * speedRatio;
}
public float getStressCapacity() {
return constantStress ? stressCapacity : stressCapacity * Math.abs(generatedSpeed);
}
public float getTotalStressImpact(float speedAtRoot) {
return constantStress ? stressImpact : stressImpact * Math.abs(getTheoreticalSpeed(speedAtRoot));
public float getTotalStressImpact() {
return constantStress ? stressImpact : stressImpact * Math.abs(getTheoreticalSpeed());
}
private SolveResult setNetwork(KineticNetwork network) {
@ -202,7 +260,7 @@ public class KineticNode {
return network.tryRecalculateSpeed();
}
public @Nullable KineticNode getSource() {
protected @Nullable KineticNode getSource() {
return source;
}
@ -212,7 +270,7 @@ public class KineticNode {
return setNetwork(from.network);
}
public void onAdded() {
protected void onAdded() {
getActiveConnections()
.findAny()
.ifPresent(e -> {
@ -264,12 +322,18 @@ public class KineticNode {
}
}
public void onRemoved() {
protected void onRemoved() {
network.removeMember(this);
getActiveConnections()
.map(Pair::getFirst)
.filter(n -> n.source == this)
.forEach(KineticNode::rerootHere);
if (controlling != null) {
controlling.stream()
.map(solver::getNode)
.flatMap(Optional::stream)
.forEach(KineticNode::removeController);
}
}
private void rerootHere() {
@ -282,11 +346,10 @@ public class KineticNode {
/**
* Updates the speed of this node based on its network's root speed and its own speed ratio.
* @param speedAtRoot Current speed at the root of this node's network
* @return CONTRADICTION if the node's new speed exceeds the maximum value, and OK otherwise
* @return CONTRADICTION if the node's new speed exceeds the maximum value, and OK otherwise
*/
protected SolveResult tryUpdateSpeed(float speedAtRoot) {
speedNext = getTheoreticalSpeed(speedAtRoot);
protected SolveResult tryUpdateSpeed() {
speedNext = getSpeed();
if (Math.abs(speedNext) > AllConfigs.SERVER.kinetics.maxRotationSpeed.get())
return SolveResult.CONTRADICTION;
return SolveResult.OK;
@ -296,7 +359,7 @@ public class KineticNode {
speedNext = 0;
}
public void flushChangedSpeed() {
protected void flushChangedSpeed() {
if (speedCur != speedNext) {
speedCur = speedNext;
if (entity != null) {
@ -305,7 +368,7 @@ public class KineticNode {
}
}
public void popBlock() {
protected void popBlock() {
if (entity != null) {
solver.removeAndPopNow(entity);
} else {

View file

@ -109,20 +109,14 @@ public class KineticSolver extends SavedData {
setDirty();
}
public void updateNode(KineticTileEntity entity) {
KineticNode node = nodes.get(entity.getBlockPos());
if (node == null) return;
if (!node.getConnections().equals(entity.getConnections())) {
// connections changed, so things could've been disconnected
removeNode(entity);
addNode(entity);
} else {
// connections are the same, so just update in case other properties changed
if (node.onUpdated()) {
setDirty();
}
}
private void regenNode(KineticNode node) {
BlockPos pos = node.getPos();
nodes.remove(pos);
node.onRemoved();
KineticNode newNode = node.regen();
nodes.put(pos, newNode);
newNode.onAdded();
setDirty();
}
public void unloadNode(KineticTileEntity entity) {
@ -130,7 +124,7 @@ public class KineticSolver extends SavedData {
if (node != null) node.onUnloaded();
}
protected Optional<KineticNode> getNode(BlockPos pos) {
public Optional<KineticNode> getNode(BlockPos pos) {
return Optional.ofNullable(nodes.get(pos));
}
@ -164,9 +158,25 @@ public class KineticSolver extends SavedData {
level.destroyBlock(pos, true);
}
public void tick() {
Set<KineticNetwork> networks = nodes.values().stream().map(KineticNode::getNetwork).collect(Collectors.toSet());
networks.forEach(KineticNetwork::untick);
public void tick(Level level) {
Set<KineticNode> popQueue = new HashSet<>();
Set<KineticNode> regenQueue = new HashSet<>();
for (KineticNode node : nodes.values()) {
node.getController().ifPresent(c -> c.onUpdate(level, this, node));
switch (node.onUpdated()) {
case NEEDS_POP -> popQueue.add(node);
case NEEDS_REGEN -> regenQueue.add(node);
case CHANGED -> setDirty();
}
}
popQueue.forEach(KineticNode::popBlock);
regenQueue.forEach(this::regenNode);
Set<KineticNetwork> networks = new HashSet<>();
for (KineticNode node : nodes.values()) {
networks.add(node.getNetwork());
node.getNetwork().untick();
}
List<KineticNetwork> frontier = new LinkedList<>();

View file

@ -112,7 +112,7 @@ public class CommonEvents {
CouplingPhysics.tick(world);
LinkedControllerServerHandler.tick(world);
KineticSolver.getSolver(world).tick();
KineticSolver.getSolver(world).tick(world);
}
@SubscribeEvent
@ -169,14 +169,12 @@ public class CommonEvents {
public static void onLoadWorld(WorldEvent.Load event) {
LevelAccessor world = event.getWorld();
Create.REDSTONE_LINK_NETWORK_HANDLER.onLoadWorld(world);
Create.TORQUE_PROPAGATOR.onLoadWorld(world);
}
@SubscribeEvent
public static void onUnloadWorld(WorldEvent.Unload event) {
LevelAccessor world = event.getWorld();
Create.REDSTONE_LINK_NETWORK_HANDLER.onUnloadWorld(world);
Create.TORQUE_PROPAGATOR.onUnloadWorld(world);
WorldAttached.invalidateWorld(world);
}