mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-02-11 12:55:00 +01:00
Now with 100% less solver
This commit is contained in:
parent
8f5a885bb0
commit
1c8f9232b7
25 changed files with 579 additions and 517 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<CreativeMotorTileEntity>, SolverBlock {
|
||||
public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE<CreativeMotorTileEntity> {
|
||||
|
||||
public CreativeMotorBlock(Properties properties) {
|
||||
super(properties);
|
||||
|
@ -75,13 +72,4 @@ public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE<C
|
|||
return AllTileEntities.MOTOR.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(KineticSolver solver, Level level, BlockPos pos) {
|
||||
BlockState state = level.getBlockState(pos);
|
||||
Direction to = state.getValue(FACING);
|
||||
int speed = getTileEntityOptional(level, pos).map(te -> te.generatedSpeed.getValue()).orElse(0);
|
||||
|
||||
solver.addRule(pos, new HalfShaftConnectionRule(to));
|
||||
solver.addRule(pos, new ConstantSpeedRule(speed));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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<Direction.Axis> {
|
||||
//used for extending a shaft in its axis, like the piston poles. works with shafts and cogs
|
||||
|
|
|
@ -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<BlockPos> addPropagationLocations(IRotate block, BlockState state, List<BlockPos> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Direction, KineticConnections> HALF_SHAFT
|
||||
= new LazyMap<>(dir -> new KineticConnections(new Entry(dir.getNormal(), Type.SHAFT)));
|
||||
|
||||
public static final LazyMap<Direction.Axis, KineticConnections> FULL_SHAFT
|
||||
= new LazyMap<>(axis -> HALF_SHAFT.apply(pos(axis)).merge(HALF_SHAFT.apply(neg(axis))));
|
||||
|
||||
public static final LazyMap<Direction.Axis, KineticConnections> LARGE_COG
|
||||
= new LazyMap<>(axis -> {
|
||||
List<Entry> 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<Direction.Axis, KineticConnections> SMALL_COG
|
||||
= new LazyMap<>(axis -> {
|
||||
List<Entry> 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<Direction.Axis, KineticConnections> LARGE_COG_FULL_SHAFT
|
||||
= new LazyMap<>(axis -> LARGE_COG.apply(axis).merge(FULL_SHAFT.apply(axis)));
|
||||
|
||||
public static final LazyMap<Direction.Axis, KineticConnections> SMALL_COG_FULL_SHAFT
|
||||
= new LazyMap<>(axis -> SMALL_COG.apply(axis).merge(FULL_SHAFT.apply(axis)));
|
||||
|
||||
}
|
|
@ -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<Float> SPEED = new Property.Type<>();
|
||||
public static final Property.Type<Set<Vec3i>> SHAFT_CONNECTIONS = new Property.Type<>();
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package com.simibubi.create.content.contraptions.solver;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ConstantSpeedRule implements RewriteRule.Descriptor<Float> {
|
||||
private final float speed;
|
||||
|
||||
public ConstantSpeedRule(float speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property.Type<Float> getWrittenProperty() {
|
||||
return AllPropertyTypes.SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Float> getRewrittenValue(RewriteRule.PropertyReader reader) {
|
||||
return Optional.of(speed);
|
||||
}
|
||||
}
|
|
@ -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<Set<Vec3i>> {
|
||||
private final Set<Vec3i> connections;
|
||||
|
||||
public HalfShaftConnectionRule(Direction dir) {
|
||||
connections = Set.of(dir.getNormal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property.Type<Set<Vec3i>> getWrittenProperty() {
|
||||
return AllPropertyTypes.SHAFT_CONNECTIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Set<Vec3i>> getRewrittenValue(RewriteRule.PropertyReader reader) {
|
||||
return Optional.of(connections);
|
||||
}
|
||||
}
|
|
@ -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<Vec3i, Value> connections;
|
||||
|
||||
private KineticConnections(Map<Vec3i, Value> connections) {
|
||||
this.connections = connections;
|
||||
}
|
||||
|
||||
public KineticConnections(Stream<Entry> entries) {
|
||||
this(entries.collect(Collectors.toMap(Entry::offset, Entry::value)));
|
||||
}
|
||||
|
||||
public KineticConnections(Collection<Entry> entries) {
|
||||
this(entries.stream());
|
||||
}
|
||||
|
||||
public KineticConnections(Entry... entries) {
|
||||
this(Arrays.stream(entries));
|
||||
}
|
||||
|
||||
public Set<Vec3i> getDirections() {
|
||||
return connections.keySet();
|
||||
}
|
||||
|
||||
public Optional<Float> 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<Vec3i, Value> out = new HashMap<>();
|
||||
out.putAll(other.connections);
|
||||
out.putAll(connections);
|
||||
return new KineticConnections(out);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<KineticNode> members = new HashSet<>();
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<BlockPos, Optional<KineticNode>> 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<BlockPos, Optional<KineticNode>> 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<KineticNode, Float> getActiveConnections() {
|
||||
return connections.getDirections().stream()
|
||||
.map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d))
|
||||
.map(n -> connections.checkConnection(n.connections, d)
|
||||
.map(r -> Pair.of(n, r))))
|
||||
.flatMap(Optional::stream)
|
||||
.flatMap(Optional::stream)
|
||||
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
|
||||
}
|
||||
|
||||
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<KineticNode> frontier = new LinkedList<>();
|
||||
frontier.add(this);
|
||||
|
||||
while (!frontier.isEmpty()) {
|
||||
KineticNode cur = frontier.remove(0);
|
||||
for (Map.Entry<KineticNode, Float> entry : cur.getActiveConnections().entrySet()) {
|
||||
KineticNode next = entry.getKey();
|
||||
float ratio = entry.getValue();
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<BlockPos, Set<RewriteRule.Tracker<?>>> rules = new HashMap<>();
|
||||
private final HashSet<RewriteRule.Tracker<?>> allRules = new HashSet<>();
|
||||
private final Map<BlockPos, KineticNode> nodes = new HashMap<>();
|
||||
|
||||
private Set<RewriteRule.Tracker<?>> rulesFrontier = new HashSet<>();
|
||||
|
||||
public <T> RewriteRule<T> addRule(BlockPos pos, RewriteRule.Descriptor<T> ruleDesc) {
|
||||
RewriteRule<T> 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<RewriteRule.Tracker<?>> 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<RewriteRule.Tracker<?>> 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<BlockPos> solve() {
|
||||
Set<BlockPos> contradictions = new HashSet<>();
|
||||
protected Optional<KineticNode> getNode(BlockPos pos) {
|
||||
return Optional.ofNullable(nodes.get(pos));
|
||||
}
|
||||
|
||||
while (!rulesFrontier.isEmpty()) {
|
||||
Set<RewriteRule.Tracker<?>> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T>(BlockPos pos, Type<T> type) {
|
||||
public static class Type<T> { }
|
||||
|
||||
@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<T>(Vec3i offset, Type<T> type) {
|
||||
public Relative(Type<T> type) {
|
||||
this(Vec3i.ZERO, type);
|
||||
}
|
||||
|
||||
public Property<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<RewriteRule.Tracker<?>> readyToRewrite;
|
||||
public Ok(Set<RewriteRule.Tracker<?>> readyToRewrite) { this.readyToRewrite = readyToRewrite; }
|
||||
}
|
||||
|
||||
public static final class Contradiction extends WriteResult {
|
||||
public static final Contradiction V = new Contradiction();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Counter<T> {
|
||||
public final Property<T> key;
|
||||
private T value;
|
||||
private final Set<RewriteRule.Tracker<?>> readers = new HashSet<>();
|
||||
|
||||
public Counter(Property<T> key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return value == null;
|
||||
}
|
||||
|
||||
public boolean canDrop() {
|
||||
return isEmpty() && readers.isEmpty();
|
||||
}
|
||||
|
||||
public Optional<T> 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<Property<?>, Counter<?>> properties = new HashMap<>();
|
||||
|
||||
public <T> Optional<T> read(Property<T> property) {
|
||||
Counter<T> counter = (Counter<T>) properties.get(property);
|
||||
if (counter == null) return Optional.empty();
|
||||
return counter.read();
|
||||
}
|
||||
|
||||
public <T> WriteResult write(Property<T> property, T value) {
|
||||
Counter<T> counter = (Counter<T>) properties.computeIfAbsent(property, $ -> new Counter<>(property));
|
||||
return counter.write(value);
|
||||
}
|
||||
|
||||
public Set<RewriteRule.Tracker<?>> unwrite(Property<?> property) {
|
||||
Counter<?> init = properties.get(property);
|
||||
if (init == null) return Set.of();
|
||||
|
||||
Set<Counter<?>> toVisit = new HashSet<>();
|
||||
toVisit.add(init);
|
||||
Set<Counter<?>> visited = new HashSet<>();
|
||||
|
||||
while (!toVisit.isEmpty()) {
|
||||
Set<Counter<?>> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> {
|
||||
private final Property.Type<T> writes;
|
||||
private final Set<Property.Relative<?>> reads;
|
||||
private final Descriptor<T> descriptor;
|
||||
|
||||
public interface Descriptor<T> {
|
||||
Property.Type<T> getWrittenProperty();
|
||||
|
||||
default Set<Property.Relative<?>> getReadProperties() { return Set.of(); }
|
||||
|
||||
Optional<T> getRewrittenValue(PropertyReader reader);
|
||||
}
|
||||
|
||||
public RewriteRule(Descriptor<T> descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
this.writes = descriptor.getWrittenProperty();
|
||||
this.reads = descriptor.getReadProperties();
|
||||
}
|
||||
|
||||
record PropertyReader(BlockPos pos, PropertyMap map) {
|
||||
public <U> U read(Property.Relative<U> 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<T> {
|
||||
public final RewriteRule<T> rule;
|
||||
public final BlockPos pos;
|
||||
public final Property<T> writes;
|
||||
public final Set<Property<?>> reads;
|
||||
private int dependencies;
|
||||
|
||||
public Tracker(RewriteRule<T> rule, BlockPos pos, Function<Tracker<?>, 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Set<Vec3i>> {
|
||||
private final Set<Vec3i> 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<Set<Vec3i>> getWrittenProperty() {
|
||||
return AllPropertyTypes.SHAFT_CONNECTIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Set<Vec3i>> getRewrittenValue(RewriteRule.PropertyReader reader) {
|
||||
return Optional.of(connections);
|
||||
}
|
||||
}
|
|
@ -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<Float> {
|
||||
private final Vec3i to, from;
|
||||
private final Property.Relative<Set<Vec3i>> otherConnections;
|
||||
private final Property.Relative<Float> 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<Float> getWrittenProperty() {
|
||||
return AllPropertyTypes.SPEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Property.Relative<?>> getReadProperties() {
|
||||
return Set.of(otherConnections, otherSpeed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Float> getRewrittenValue(RewriteRule.PropertyReader reader) {
|
||||
Set<Vec3i> otherConnections = reader.read(this.otherConnections);
|
||||
float otherSpeed = reader.read(this.otherSpeed);
|
||||
|
||||
if (otherConnections.contains(from))
|
||||
return Optional.of(otherSpeed);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -112,7 +112,7 @@ public class CommonEvents {
|
|||
CouplingPhysics.tick(world);
|
||||
LinkedControllerServerHandler.tick(world);
|
||||
|
||||
KineticSolver.getSolver(world).solve();
|
||||
KineticSolver.getSolver(world).flushChangedSpeeds();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
|
|
@ -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<K, V> implements Function<K, V> {
|
||||
private final NonNullFunction<K, V> function;
|
||||
private final Map<K, V> map;
|
||||
|
||||
public LazyMap(NonNullFunction<K, V> function, Map<K, V> map) {
|
||||
this.function = function;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public LazyMap(NonNullFunction<K, V> function) {
|
||||
this(function, new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public V apply(K k) {
|
||||
return map.computeIfAbsent(k, function::apply);
|
||||
}
|
||||
|
||||
public static <K, V> LazyMap<K, V> of(NonNullFunction<K, V> function) {
|
||||
return new LazyMap<>(function);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue