diff --git a/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java index 96b3240c2..6b8af4910 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/base/KineticTileEntity.java @@ -18,8 +18,9 @@ import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation; 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.KineticConnections; +import com.simibubi.create.content.contraptions.solver.KineticNodeState; import com.simibubi.create.content.contraptions.solver.KineticSolver; -import com.simibubi.create.content.contraptions.solver.SolverBlock; import com.simibubi.create.foundation.block.BlockStressValues; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.item.TooltipHelper; @@ -72,6 +73,26 @@ public class KineticTileEntity extends SmartTileEntity protected float lastStressApplied; protected float lastCapacityProvided; + private KineticNodeState kineticNodeState; + + public KineticNodeState getKineticNodeState() { + return kineticNodeState; + } + + public KineticNodeState getInitialKineticNodeState() { + return new KineticNodeState(new KineticConnections(), 0); + } + + private void addToSolver() { + kineticNodeState = getInitialKineticNodeState(); + KineticSolver.getSolver(level).addNode(this); + } + + private void removeFromSolver() { + KineticSolver.getSolver(level).removeNode(this); + } + + public KineticTileEntity(BlockEntityType typeIn, BlockPos pos, BlockState state) { super(typeIn, pos, state); effects = new KineticEffectHandler(this); @@ -87,6 +108,10 @@ public class KineticTileEntity extends SmartTileEntity network.addSilently(this, lastCapacityProvided, lastStressApplied); } + if (!level.isClientSide) { + addToSolver(); + } + super.initialize(); } @@ -98,6 +123,10 @@ public class KineticTileEntity extends SmartTileEntity super.tick(); effects.tick(); + if (!level.isClientSide && !isRemoved()) { + KineticSolver.getSolver(level).updateNode(this); + } + if (level.isClientSide) { cachedBoundingBox = null; // cache the bounding box for every frame between ticks DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.tickAudio()); @@ -196,6 +225,7 @@ public class KineticTileEntity extends SmartTileEntity if (hasNetwork()) getOrCreateNetwork().remove(this); detachKinetics(); + removeFromSolver(); } super.setRemovedNotDueToChunkUnload(); } @@ -346,24 +376,10 @@ public class KineticTileEntity extends SmartTileEntity public void attachKinetics() { updateSpeed = false; - - KineticSolver solver = KineticSolver.getSolver(level); - BlockState state = getBlockState(); - if (state.getBlock() instanceof SolverBlock sb) { - solver.removeAllRules(worldPosition); - sb.created(solver, level, worldPosition); - } - RotationPropagator.handleAdded(level, worldPosition, this); } public void detachKinetics() { - KineticSolver solver = KineticSolver.getSolver(level); - BlockState state = getBlockState(); - if (state.getBlock() instanceof SolverBlock) { - solver.removeAllRules(worldPosition); - } - RotationPropagator.handleRemoved(level, worldPosition, this); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java index 8e611ff95..3bc5df474 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorBlock.java @@ -3,10 +3,7 @@ package com.simibubi.create.content.contraptions.components.motor; import com.simibubi.create.AllShapes; import com.simibubi.create.AllTileEntities; import com.simibubi.create.content.contraptions.base.DirectionalKineticBlock; -import com.simibubi.create.content.contraptions.solver.ConstantSpeedRule; -import com.simibubi.create.content.contraptions.solver.HalfShaftConnectionRule; import com.simibubi.create.content.contraptions.solver.KineticSolver; -import com.simibubi.create.content.contraptions.solver.SolverBlock; import com.simibubi.create.foundation.block.ITE; import net.minecraft.core.BlockPos; @@ -22,7 +19,7 @@ import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE, SolverBlock { +public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE { public CreativeMotorBlock(Properties properties) { super(properties); @@ -75,13 +72,4 @@ public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE te.generatedSpeed.getValue()).orElse(0); - - solver.addRule(pos, new HalfShaftConnectionRule(to)); - solver.addRule(pos, new ConstantSpeedRule(speed)); - } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorTileEntity.java index 864aba99d..2d9e681dd 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/motor/CreativeMotorTileEntity.java @@ -4,6 +4,8 @@ import java.util.List; import com.simibubi.create.AllBlocks; import com.simibubi.create.content.contraptions.base.GeneratingKineticTileEntity; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticNodeState; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.behaviour.CenteredSideValueBoxTransform; @@ -37,11 +39,19 @@ public class CreativeMotorTileEntity extends GeneratingKineticTileEntity { generatedSpeed.value = DEFAULT_SPEED; generatedSpeed.scrollableValue = DEFAULT_SPEED; generatedSpeed.withUnit(i -> Lang.translate("generic.unit.rpm")); - generatedSpeed.withCallback(i -> this.updateGeneratedRotation()); + generatedSpeed.withCallback(i -> this.getKineticNodeState().setGeneratedSpeed(getGeneratedSpeed())); generatedSpeed.withStepFunction(CreativeMotorTileEntity::step); behaviours.add(generatedSpeed); } + @Override + public KineticNodeState getInitialKineticNodeState() { + return new KineticNodeState( + AllConnections.HALF_SHAFT.apply(getBlockState().getValue(CreativeMotorBlock.FACING)), + getGeneratedSpeed() + ); + } + @Override public void initialize() { super.initialize(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java index 19962113a..85345a7d8 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/CogWheelBlock.java @@ -8,6 +8,8 @@ import com.simibubi.create.content.contraptions.base.IRotate; import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.content.contraptions.relays.advanced.SpeedControllerBlock; import com.simibubi.create.content.contraptions.relays.encased.EncasedCogwheelBlock; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.foundation.utility.Iterate; import net.minecraft.MethodsReturnNonnullByDefault; @@ -33,7 +35,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel { +public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel, ISimpleConnectable { boolean isLarge; @@ -60,6 +62,12 @@ public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel { return !isLarge; } + @Override + public KineticConnections getConnections(BlockState state) { + return (isLargeCog() ? AllConnections.LARGE_COG_FULL_SHAFT : AllConnections.SMALL_COG_FULL_SHAFT) + .apply(state.getValue(AXIS)); + } + @Override public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { return (isLarge ? AllShapes.LARGE_GEAR : AllShapes.SMALL_GEAR).get(state.getValue(AXIS)); @@ -105,7 +113,7 @@ public class CogWheelBlock extends AbstractShaftBlock implements ICogWheel { encasedState.cycle(d.getAxisDirection() == AxisDirection.POSITIVE ? EncasedCogwheelBlock.TOP_SHAFT : EncasedCogwheelBlock.BOTTOM_SHAFT); } - + KineticTileEntity.switchToBlockState(world, pos, encasedState); return InteractionResult.SUCCESS; } diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java new file mode 100644 index 000000000..f927ac670 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ISimpleConnectable.java @@ -0,0 +1,9 @@ +package com.simibubi.create.content.contraptions.relays.elementary; + +import com.simibubi.create.content.contraptions.solver.KineticConnections; + +import net.minecraft.world.level.block.state.BlockState; + +public interface ISimpleConnectable { + KineticConnections getConnections(BlockState state); +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java index 1080d78b2..706dc0789 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/ShaftBlock.java @@ -4,12 +4,10 @@ import java.util.function.Predicate; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllShapes; -import com.simibubi.create.content.contraptions.solver.KineticSolver; import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.content.contraptions.relays.encased.EncasedShaftBlock; -import com.simibubi.create.content.contraptions.solver.ShaftConnectionRule; -import com.simibubi.create.content.contraptions.solver.ShaftEqualSpeedRule; -import com.simibubi.create.content.contraptions.solver.SolverBlock; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; import com.simibubi.create.foundation.advancement.AllTriggers; import com.simibubi.create.foundation.utility.placement.IPlacementHelper; import com.simibubi.create.foundation.utility.placement.PlacementHelpers; @@ -30,7 +28,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class ShaftBlock extends AbstractShaftBlock implements SolverBlock { +public class ShaftBlock extends AbstractShaftBlock implements ISimpleConnectable { private static final int placementHelperId = PlacementHelpers.register(new PlacementHelper()); @@ -42,6 +40,11 @@ public class ShaftBlock extends AbstractShaftBlock implements SolverBlock { return AllBlocks.SHAFT.has(state); } + @Override + public KineticConnections getConnections(BlockState state) { + return AllConnections.FULL_SHAFT.apply(state.getValue(AXIS)); + } + @Override public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { return AllShapes.SIX_VOXEL_POLE.get(state.getValue(AXIS)); @@ -87,18 +90,6 @@ public class ShaftBlock extends AbstractShaftBlock implements SolverBlock { return InteractionResult.PASS; } - @Override - public void created(KineticSolver solver, Level level, BlockPos pos) { - BlockState state = level.getBlockState(pos); - Direction.Axis axis = state.getValue(AXIS); - Direction positive = Direction.fromAxisAndDirection(axis, Direction.AxisDirection.POSITIVE); - Direction negative = positive.getOpposite(); - - solver.addRule(pos, new ShaftConnectionRule(axis)); - solver.addRule(pos, new ShaftEqualSpeedRule(positive)); - solver.addRule(pos, new ShaftEqualSpeedRule(negative)); - } - @MethodsReturnNonnullByDefault private static class PlacementHelper extends PoleHelper { //used for extending a shaft in its axis, like the piston poles. works with shafts and cogs diff --git a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java index 647b60bb0..414886ef9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/relays/elementary/SimpleKineticTileEntity.java @@ -1,10 +1,12 @@ package com.simibubi.create.content.contraptions.relays.elementary; -import java.util.List; - -import com.simibubi.create.content.contraptions.base.IRotate; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.content.contraptions.base.RotatedPillarKineticBlock; +import com.simibubi.create.content.contraptions.solver.AllConnections; +import com.simibubi.create.content.contraptions.solver.KineticConnections; +import com.simibubi.create.content.contraptions.solver.KineticNodeState; + import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -21,22 +23,19 @@ public class SimpleKineticTileEntity extends KineticTileEntity { return new AABB(worldPosition).inflate(1); } - @Override - public List addPropagationLocations(IRotate block, BlockState state, List neighbours) { - if (!ICogWheel.isLargeCog(state)) - return super.addPropagationLocations(block, state, neighbours); - - BlockPos.betweenClosedStream(new BlockPos(-1, -1, -1), new BlockPos(1, 1, 1)) - .forEach(offset -> { - if (offset.distSqr(0, 0, 0, false) == BlockPos.ZERO.distSqr(1, 1, 0, false)) - neighbours.add(worldPosition.offset(offset)); - }); - return neighbours; - } - @Override protected boolean isNoisy() { return false; } + @Override + public KineticNodeState getInitialKineticNodeState() { + KineticConnections connections = AllConnections.EMPTY; + BlockState state = getBlockState(); + if (state.getBlock() instanceof ISimpleConnectable connectable) { + connections = connectable.getConnections(state); + } + return new KineticNodeState(connections, 0); + } + } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java b/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java new file mode 100644 index 000000000..5b5473ed6 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java @@ -0,0 +1,65 @@ +package com.simibubi.create.content.contraptions.solver; + +import com.simibubi.create.foundation.utility.DirectionHelper; +import com.simibubi.create.foundation.utility.LazyMap; +import com.simibubi.create.content.contraptions.solver.KineticConnections.Type; +import com.simibubi.create.content.contraptions.solver.KineticConnections.Entry; + +import net.minecraft.core.Direction; + +import java.util.LinkedList; +import java.util.List; + +public class AllConnections { + + private static Direction pos(Direction.Axis axis) { + return Direction.get(Direction.AxisDirection.POSITIVE, axis); + } + + private static Direction neg(Direction.Axis axis) { + return Direction.get(Direction.AxisDirection.NEGATIVE, axis); + } + + public static final KineticConnections EMPTY = new KineticConnections(); + + public static final LazyMap HALF_SHAFT + = new LazyMap<>(dir -> new KineticConnections(new Entry(dir.getNormal(), Type.SHAFT))); + + public static final LazyMap FULL_SHAFT + = new LazyMap<>(axis -> HALF_SHAFT.apply(pos(axis)).merge(HALF_SHAFT.apply(neg(axis)))); + + public static final LazyMap LARGE_COG + = new LazyMap<>(axis -> { + List out = new LinkedList<>(); + Direction cur = DirectionHelper.getPositivePerpendicular(axis); + for (int i = 0; i < 4; i++) { + Direction next = DirectionHelper.rotateAround(cur, axis); + out.add(new Entry(cur.getNormal().multiply(2), Type.LARGE_COG, -1)); + out.add(new Entry(cur.getNormal().relative(pos(axis)), Type.LARGE_COG, -1)); + out.add(new Entry(cur.getNormal().relative(neg(axis)), Type.LARGE_COG, -1)); + out.add(new Entry(cur.getNormal().relative(next), Type.LARGE_COG, Type.SMALL_COG, -2)); + cur = next; + } + return new KineticConnections(out); + }); + + public static final LazyMap SMALL_COG + = new LazyMap<>(axis -> { + List out = new LinkedList<>(); + Direction cur = DirectionHelper.getPositivePerpendicular(axis); + for (int i = 0; i < 4; i++) { + Direction next = DirectionHelper.rotateAround(cur, axis); + out.add(new Entry(cur.getNormal(), Type.SMALL_COG, -1)); + out.add(new Entry(cur.getNormal().relative(next), Type.SMALL_COG, Type.LARGE_COG, -0.5f)); + cur = next; + } + return new KineticConnections(out); + }); + + public static final LazyMap LARGE_COG_FULL_SHAFT + = new LazyMap<>(axis -> LARGE_COG.apply(axis).merge(FULL_SHAFT.apply(axis))); + + public static final LazyMap SMALL_COG_FULL_SHAFT + = new LazyMap<>(axis -> SMALL_COG.apply(axis).merge(FULL_SHAFT.apply(axis))); + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/AllPropertyTypes.java b/src/main/java/com/simibubi/create/content/contraptions/solver/AllPropertyTypes.java deleted file mode 100644 index 23e7f1d91..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/AllPropertyTypes.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.Vec3i; - -import java.util.Set; - -public class AllPropertyTypes { - public static final Property.Type SPEED = new Property.Type<>(); - public static final Property.Type> SHAFT_CONNECTIONS = new Property.Type<>(); -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/ConstantSpeedRule.java b/src/main/java/com/simibubi/create/content/contraptions/solver/ConstantSpeedRule.java deleted file mode 100644 index b863e6b5d..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/ConstantSpeedRule.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import java.util.Optional; - -public class ConstantSpeedRule implements RewriteRule.Descriptor { - private final float speed; - - public ConstantSpeedRule(float speed) { - this.speed = speed; - } - - @Override - public Property.Type getWrittenProperty() { - return AllPropertyTypes.SPEED; - } - - @Override - public Optional getRewrittenValue(RewriteRule.PropertyReader reader) { - return Optional.of(speed); - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/HalfShaftConnectionRule.java b/src/main/java/com/simibubi/create/content/contraptions/solver/HalfShaftConnectionRule.java deleted file mode 100644 index 52d34f1e8..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/HalfShaftConnectionRule.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; - -import java.util.Optional; -import java.util.Set; - -public class HalfShaftConnectionRule implements RewriteRule.Descriptor> { - private final Set connections; - - public HalfShaftConnectionRule(Direction dir) { - connections = Set.of(dir.getNormal()); - } - - @Override - public Property.Type> getWrittenProperty() { - return AllPropertyTypes.SHAFT_CONNECTIONS; - } - - @Override - public Optional> getRewrittenValue(RewriteRule.PropertyReader reader) { - return Optional.of(connections); - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java new file mode 100644 index 000000000..947c17848 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java @@ -0,0 +1,90 @@ +package com.simibubi.create.content.contraptions.solver; + +import net.minecraft.core.Vec3i; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class KineticConnections { + + public enum Type { + SHAFT, SMALL_COG, LARGE_COG, BELT + } + + public static record Entry(Vec3i offset, Value value) { + public Entry(Vec3i offset, Type from, Type to, float ratio) { + this(offset, new Value(from, to, ratio)); + } + public Entry(Vec3i offset, Type type, float ratio) { + this(offset, type, type, ratio); + } + public Entry(Vec3i offset, Type type) { + this(offset, type, 1); + } + } + + private static record Value(Type from, Type to, float ratio) { } + + private final Map connections; + + private KineticConnections(Map connections) { + this.connections = connections; + } + + public KineticConnections(Stream entries) { + this(entries.collect(Collectors.toMap(Entry::offset, Entry::value))); + } + + public KineticConnections(Collection entries) { + this(entries.stream()); + } + + public KineticConnections(Entry... entries) { + this(Arrays.stream(entries)); + } + + public Set getDirections() { + return connections.keySet(); + } + + public Optional checkConnection(KineticConnections to, Vec3i offset) { + Value fromValue = connections.get(offset); + if (fromValue == null) return Optional.empty(); + + Value toValue = to.connections.get(offset.multiply(-1)); + if (toValue == null) return Optional.empty(); + + if (fromValue.from.equals(toValue.to) && fromValue.to.equals(toValue.from)) + return Optional.of(fromValue.ratio); + return Optional.empty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KineticConnections that = (KineticConnections) o; + return Objects.equals(connections, that.connections); + } + + @Override + public int hashCode() { + return Objects.hash(connections); + } + + public KineticConnections merge(KineticConnections other) { + Map out = new HashMap<>(); + out.putAll(other.connections); + out.putAll(connections); + return new KineticConnections(out); + } + +} + diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java new file mode 100644 index 000000000..43ee75207 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java @@ -0,0 +1,88 @@ +package com.simibubi.create.content.contraptions.solver; + +import com.simibubi.create.foundation.utility.Pair; + +import java.util.HashSet; +import java.util.Set; + +public class KineticNetwork { + + private final Set members = new HashSet<>(); + private final Set generators = new HashSet<>(); + private final Set> 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)) { + 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() && generators.contains(node)) { + generators.remove(node); + speedDirty = true; + } + conflictingCycles.removeIf(p -> p.getFirst() == node || p.getSecond() == node); + } + + public void markConflictingCycle(KineticNode from, KineticNode to) { + if (!members.contains(from) || !members.contains(to)) throw new IllegalArgumentException(); + conflictingCycles.add(Pair.of(from, to)); + } + + public float getRootSpeed() { + return rootSpeed; + } + + public SolveResult recalculateSpeed() { + if (!conflictingCycles.isEmpty() && !generators.isEmpty()) return SolveResult.CONTRADICTION; + if (!speedDirty) return SolveResult.OK; + + 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); + } + } + newSpeed *= sign; + + if (rootSpeed != newSpeed) { + rootSpeed = newSpeed; + for (KineticNode member : members) { + member.onSpeedUpdated(); + } + } + + speedDirty = false; + return SolveResult.OK; + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java new file mode 100644 index 000000000..de79369fe --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java @@ -0,0 +1,158 @@ +package com.simibubi.create.content.contraptions.solver; + +import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.foundation.utility.Pair; + +import net.minecraft.core.BlockPos; + +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; + +public class KineticNode { + + private final Function> nodeAccessor; + private final KineticTileEntity entity; + + private @Nullable KineticNode source; + private KineticNetwork network; + private float speedRatio = 1; + + private final KineticConnections connections; + private float generatedSpeed; + + private float speedCur; + private float speedNext; + + public KineticNode(KineticTileEntity entity, Function> nodeAccessor) { + this.nodeAccessor = nodeAccessor; + this.entity = entity; + + KineticNodeState state = entity.getKineticNodeState(); + this.connections = state.getConnections(); + this.generatedSpeed = state.getGeneratedSpeed(); + + this.network = new KineticNetwork(this); + onSpeedUpdated(); + } + + public KineticConnections getConnections() { + return connections; + } + + public Map 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)); + } + + public float getGeneratedSpeed() { + return generatedSpeed; + } + + public float getGeneratedSpeedAtRoot() { + return generatedSpeed / speedRatio; + } + + public boolean isGenerator() { + return generatedSpeed != 0; + } + + public void setGeneratedSpeed(float newSpeed) { + generatedSpeed = newSpeed; + network.updateMember(this); + if (network.recalculateSpeed().isContradiction()) + onPopBlock(); + } + + private void setNetwork(KineticNetwork network) { + this.network.removeMember(this); + this.network = network; + network.addMember(this); + onSpeedUpdated(); + } + + private void setSource(KineticNode from, float ratio) { + source = from; + speedRatio = from.speedRatio * ratio; + setNetwork(from.network); + } + + public void onAdded() { + getActiveConnections() + .keySet() + .stream() + .findAny() + .ifPresent(n -> { + if (n.propagateSource().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() { + List frontier = new LinkedList<>(); + frontier.add(this); + + while (!frontier.isEmpty()) { + KineticNode cur = frontier.remove(0); + for (Map.Entry 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) { + network.markConflictingCycle(cur, next); + } + continue; + } + next.setSource(cur, ratio); + frontier.add(next); + } + } + + return network.recalculateSpeed(); + } + + private void rerootHere() { + source = null; + speedRatio = 1; + setNetwork(new KineticNetwork(this)); + propagateSource(); + } + + public void onSpeedUpdated() { + speedNext = network.getRootSpeed() * speedRatio; + } + + public void flushChangedSpeed() { + if (speedCur != speedNext) { + speedCur = speedNext; + // TODO: update entity's speed + System.out.printf("Set speed of %s to %f\n", this, speedNext); + } + } + + public void onPopBlock() { + // this should cause the node to get removed from the solver and lead to onRemoved() being called + entity.getLevel().destroyBlock(entity.getBlockPos(), true); + } + +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java new file mode 100644 index 000000000..2aa195b1d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNodeState.java @@ -0,0 +1,27 @@ +package com.simibubi.create.content.contraptions.solver; + +public class KineticNodeState { + private KineticConnections connections; + private float generatedSpeed; + + public KineticNodeState(KineticConnections connections, float generatedSpeed) { + this.connections = connections; + this.generatedSpeed = generatedSpeed; + } + + public KineticConnections getConnections() { + return connections; + } + + public float getGeneratedSpeed() { + return generatedSpeed; + } + + public void setConnections(KineticConnections connections) { + this.connections = connections; + } + + public void setGeneratedSpeed(float generatedSpeed) { + this.generatedSpeed = generatedSpeed; + } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java index 03b3dfafd..adf9bebfd 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticSolver.java @@ -1,12 +1,10 @@ package com.simibubi.create.content.contraptions.solver; -import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Optional; +import com.simibubi.create.content.contraptions.base.KineticTileEntity; import com.simibubi.create.foundation.utility.WorldAttached; import net.minecraft.core.BlockPos; @@ -20,71 +18,39 @@ public class KineticSolver { return SOLVERS.get(level); } - private final PropertyMap properties = new PropertyMap(); - private final Map>> rules = new HashMap<>(); - private final HashSet> allRules = new HashSet<>(); + private final Map nodes = new HashMap<>(); - private Set> rulesFrontier = new HashSet<>(); - - public RewriteRule addRule(BlockPos pos, RewriteRule.Descriptor ruleDesc) { - RewriteRule rule = new RewriteRule<>(ruleDesc); - RewriteRule.Tracker tracker = new RewriteRule.Tracker<>(rule, pos, properties::trackReader); - rules.computeIfAbsent(pos, $ -> new HashSet<>()).add(tracker); - allRules.add(tracker); - rulesFrontier.add(tracker); - return rule; + public void addNode(KineticTileEntity entity) { + removeNode(entity); + KineticNode node = new KineticNode(entity, this::getNode); + nodes.put(entity.getBlockPos(), node); + node.onAdded(); } - public void removeRule(BlockPos pos, RewriteRule rule) { - Set> trackers = rules.get(pos); - if (trackers == null) return; - trackers.stream() - .filter(t -> t.rule == rule) - .findAny() - .ifPresent(tracker -> { - allRules.remove(tracker); - trackers.remove(tracker); - if (trackers.isEmpty()) { - rules.remove(pos); - } - properties.untrackReader(tracker); - rulesFrontier.addAll(properties.unwrite(tracker.writes)); - }); - } + public void updateNode(KineticTileEntity entity) { + KineticNode node = nodes.get(entity.getBlockPos()); + KineticNodeState state = entity.getKineticNodeState(); - public void removeAllRules(BlockPos pos) { - Set> trackers = rules.remove(pos); - if (trackers == null) return; - for (RewriteRule.Tracker tracker: trackers) { - allRules.remove(tracker); - properties.untrackReader(tracker); - } - for (RewriteRule.Tracker tracker: trackers) { - rulesFrontier.addAll(properties.unwrite(tracker.writes)); + if (!node.getConnections().equals(state.getConnections())) { + // connections changed, so things could've been disconnected + removeNode(entity); + addNode(entity); + } else { + // connections are the same, so just set speed in case it changed + node.setGeneratedSpeed(state.getGeneratedSpeed()); } } - public Set solve() { - Set contradictions = new HashSet<>(); + protected Optional getNode(BlockPos pos) { + return Optional.ofNullable(nodes.get(pos)); + } - while (!rulesFrontier.isEmpty()) { - Set> next = new HashSet<>(); + public void removeNode(KineticTileEntity entity) { + KineticNode node = nodes.remove(entity.getBlockPos()); + if (node != null) node.onRemoved(); + } - for (RewriteRule.Tracker rule : rulesFrontier) { - if (!allRules.contains(rule) || !rule.canRewrite()) continue; - - PropertyMap.WriteResult res = rule.rewrite(properties); - if (res instanceof PropertyMap.WriteResult.Ok ok) { - next.addAll(ok.readyToRewrite); - } else if (res instanceof PropertyMap.WriteResult.Contradiction) { - removeAllRules(rule.pos); - contradictions.add(rule.pos); - } - } - - rulesFrontier = next; - } - - return contradictions; + public void flushChangedSpeeds() { + nodes.values().forEach(KineticNode::flushChangedSpeed); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/Property.java b/src/main/java/com/simibubi/create/content/contraptions/solver/Property.java deleted file mode 100644 index 692daebdb..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/Property.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; - -import java.util.Objects; - -public record Property(BlockPos pos, Type type) { - public static class Type { } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Property property = (Property) o; - return Objects.equals(pos, property.pos) && Objects.equals(type, property.type); - } - - @Override - public int hashCode() { - return Objects.hash(pos, type); - } - - public static record Relative(Vec3i offset, Type type) { - public Relative(Type type) { - this(Vec3i.ZERO, type); - } - - public Property toAbsolute(BlockPos pos) { - return new Property<>(pos.offset(offset), type); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Relative relative = (Relative) o; - return Objects.equals(offset, relative.offset) && Objects.equals(type, relative.type); - } - - @Override - public int hashCode() { - return Objects.hash(offset, type); - } - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/PropertyMap.java b/src/main/java/com/simibubi/create/content/contraptions/solver/PropertyMap.java deleted file mode 100644 index 07823307c..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/PropertyMap.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.BlockPos; - -import javax.annotation.Nonnull; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -public class PropertyMap { - public static sealed class WriteResult { - public static final class Ok extends WriteResult { - public static final Ok V = new Ok(Set.of()); - public final Set> readyToRewrite; - public Ok(Set> readyToRewrite) { this.readyToRewrite = readyToRewrite; } - } - - public static final class Contradiction extends WriteResult { - public static final Contradiction V = new Contradiction(); - } - } - - private static class Counter { - public final Property key; - private T value; - private final Set> readers = new HashSet<>(); - - public Counter(Property key) { - this.key = key; - } - - public boolean isEmpty() { - return value == null; - } - - public boolean canDrop() { - return isEmpty() && readers.isEmpty(); - } - - public Optional read() { - return Optional.ofNullable(value); - } - - public WriteResult write(@Nonnull T newValue) { - WriteResult result = WriteResult.Ok.V; - if (isEmpty()) { - value = newValue; - // notify readers - readers.forEach(RewriteRule.Tracker::dependencyRemoved); - result = new WriteResult.Ok(readers.stream() - .filter(RewriteRule.Tracker::canRewrite) - .collect(Collectors.toSet())); - } else if (!value.equals(newValue)) { - return WriteResult.Contradiction.V; - } - return result; - } - - public void unwrite() { - if (isEmpty()) throw new IllegalStateException(); - value = null; - // notify readers - readers.forEach(RewriteRule.Tracker::dependencyAdded); - } - - public boolean trackReader(RewriteRule.Tracker reader) { - readers.add(reader); - return isEmpty(); - } - - public void untrackReader(RewriteRule.Tracker reader) { - readers.remove(reader); - } - } - - private final Map, Counter> properties = new HashMap<>(); - - public Optional read(Property property) { - Counter counter = (Counter) properties.get(property); - if (counter == null) return Optional.empty(); - return counter.read(); - } - - public WriteResult write(Property property, T value) { - Counter counter = (Counter) properties.computeIfAbsent(property, $ -> new Counter<>(property)); - return counter.write(value); - } - - public Set> unwrite(Property property) { - Counter init = properties.get(property); - if (init == null) return Set.of(); - - Set> toVisit = new HashSet<>(); - toVisit.add(init); - Set> visited = new HashSet<>(); - - while (!toVisit.isEmpty()) { - Set> next = new HashSet<>(); - - for (Counter c : toVisit) { - if (c.isEmpty() || visited.contains(c)) continue; - visited.add(c); - - c.unwrite(); - if (c.canDrop()) { - properties.put(c.key, null); - } - - c.readers.stream() - .map(r -> properties.get(r.writes)) - .filter(Objects::nonNull) - .forEachOrdered(toVisit::add); - } - - toVisit = next; - } - - return visited.stream().flatMap(c -> c.readers.stream()).collect(Collectors.toSet()); - } - - public int trackReader(RewriteRule.Tracker reader) { - int dependencies = 0; - for (Property p : reader.reads) { - dependencies += properties.computeIfAbsent(p, $ -> new Counter<>(p)).trackReader(reader) ? 1 : 0; - } - return dependencies; - } - - public void untrackReader(RewriteRule.Tracker reader) { - for (Property property : reader.reads) { - properties.get(property).untrackReader(reader); - } - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/RewriteRule.java b/src/main/java/com/simibubi/create/content/contraptions/solver/RewriteRule.java deleted file mode 100644 index e5b8104c1..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/RewriteRule.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.BlockPos; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class RewriteRule { - private final Property.Type writes; - private final Set> reads; - private final Descriptor descriptor; - - public interface Descriptor { - Property.Type getWrittenProperty(); - - default Set> getReadProperties() { return Set.of(); } - - Optional getRewrittenValue(PropertyReader reader); - } - - public RewriteRule(Descriptor descriptor) { - this.descriptor = descriptor; - this.writes = descriptor.getWrittenProperty(); - this.reads = descriptor.getReadProperties(); - } - - record PropertyReader(BlockPos pos, PropertyMap map) { - public U read(Property.Relative property) { - return map.read(property.toAbsolute(pos)).get(); - } - } - - private PropertyMap.WriteResult rewrite(BlockPos pos, PropertyMap properties) { - return descriptor - .getRewrittenValue(new PropertyReader(pos, properties)) - .map(v -> properties.write(new Property<>(pos, writes), v)) - .orElse(PropertyMap.WriteResult.Ok.V); - } - - public static class Tracker { - public final RewriteRule rule; - public final BlockPos pos; - public final Property writes; - public final Set> reads; - private int dependencies; - - public Tracker(RewriteRule rule, BlockPos pos, Function, Integer> dependencies) { - this.rule = rule; - this.pos = pos; - this.writes = new Property<>(pos, rule.writes); - this.reads = rule.reads.stream().map(p -> p.toAbsolute(pos)).collect(Collectors.toSet()); - this.dependencies = dependencies.apply(this); - } - - public PropertyMap.WriteResult rewrite(PropertyMap properties) { - if (!canRewrite()) throw new IllegalStateException(); - return rule.rewrite(pos, properties); - } - - public void dependencyAdded() { - dependencies += 1; - } - - public void dependencyRemoved() { - if (dependencies == 0) throw new IllegalStateException(); - dependencies -= 1; - } - - public boolean canRewrite() { - return dependencies == 0; - } - } - -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftConnectionRule.java b/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftConnectionRule.java deleted file mode 100644 index e6aa6b261..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftConnectionRule.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; - -import java.util.Optional; -import java.util.Set; - -public class ShaftConnectionRule implements RewriteRule.Descriptor> { - private final Set connections; - - public ShaftConnectionRule(Direction.Axis axis) { - Direction positive = Direction.fromAxisAndDirection(axis, Direction.AxisDirection.POSITIVE); - connections = Set.of(positive.getNormal(), positive.getOpposite().getNormal()); - } - - @Override - public Property.Type> getWrittenProperty() { - return AllPropertyTypes.SHAFT_CONNECTIONS; - } - - @Override - public Optional> getRewrittenValue(RewriteRule.PropertyReader reader) { - return Optional.of(connections); - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftEqualSpeedRule.java b/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftEqualSpeedRule.java deleted file mode 100644 index a54689d4e..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/ShaftEqualSpeedRule.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.Direction; -import net.minecraft.core.Vec3i; - -import java.util.Optional; -import java.util.Set; - -public class ShaftEqualSpeedRule implements RewriteRule.Descriptor { - private final Vec3i to, from; - private final Property.Relative> otherConnections; - private final Property.Relative otherSpeed; - - public ShaftEqualSpeedRule(Direction dir) { - to = dir.getNormal(); - from = dir.getOpposite().getNormal(); - otherConnections = new Property.Relative<>(to, AllPropertyTypes.SHAFT_CONNECTIONS); - otherSpeed = new Property.Relative<>(to, AllPropertyTypes.SPEED); - } - - @Override - public Property.Type getWrittenProperty() { - return AllPropertyTypes.SPEED; - } - - @Override - public Set> getReadProperties() { - return Set.of(otherConnections, otherSpeed); - } - - @Override - public Optional getRewrittenValue(RewriteRule.PropertyReader reader) { - Set otherConnections = reader.read(this.otherConnections); - float otherSpeed = reader.read(this.otherSpeed); - - if (otherConnections.contains(from)) - return Optional.of(otherSpeed); - return Optional.empty(); - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/SolveResult.java b/src/main/java/com/simibubi/create/content/contraptions/solver/SolveResult.java new file mode 100644 index 000000000..4dccffafb --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/SolveResult.java @@ -0,0 +1,8 @@ +package com.simibubi.create.content.contraptions.solver; + +public enum SolveResult { + OK, CONTRADICTION; + + public boolean isOk() { return this == OK; } + public boolean isContradiction() { return this == CONTRADICTION; } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/solver/SolverBlock.java b/src/main/java/com/simibubi/create/content/contraptions/solver/SolverBlock.java deleted file mode 100644 index 489900ea6..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/SolverBlock.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.simibubi.create.content.contraptions.solver; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; - -public interface SolverBlock { - void created(KineticSolver solver, Level level, BlockPos pos); -} diff --git a/src/main/java/com/simibubi/create/events/CommonEvents.java b/src/main/java/com/simibubi/create/events/CommonEvents.java index 8547d4a05..ff1562c96 100644 --- a/src/main/java/com/simibubi/create/events/CommonEvents.java +++ b/src/main/java/com/simibubi/create/events/CommonEvents.java @@ -112,7 +112,7 @@ public class CommonEvents { CouplingPhysics.tick(world); LinkedControllerServerHandler.tick(world); - KineticSolver.getSolver(world).solve(); + KineticSolver.getSolver(world).flushChangedSpeeds(); } @SubscribeEvent diff --git a/src/main/java/com/simibubi/create/foundation/utility/LazyMap.java b/src/main/java/com/simibubi/create/foundation/utility/LazyMap.java new file mode 100644 index 000000000..996049004 --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/utility/LazyMap.java @@ -0,0 +1,30 @@ +package com.simibubi.create.foundation.utility; + +import net.minecraftforge.common.util.NonNullFunction; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class LazyMap implements Function { + private final NonNullFunction function; + private final Map map; + + public LazyMap(NonNullFunction function, Map map) { + this.function = function; + this.map = map; + } + + public LazyMap(NonNullFunction function) { + this(function, new HashMap<>()); + } + + @Override + public V apply(K k) { + return map.computeIfAbsent(k, function::apply); + } + + public static LazyMap of(NonNullFunction function) { + return new LazyMap<>(function); + } +}