From 44d59fe793b889b3b759f56b3409460ce94a14a4 Mon Sep 17 00:00:00 2001 From: reidbhuntley Date: Sat, 1 Jan 2022 00:19:10 -0500 Subject: [PATCH] Persistence is key --- .../contraptions/base/KineticTileEntity.java | 55 ++++--- .../elementary/SimpleKineticTileEntity.java | 8 +- .../contraptions/solver/AllConnections.java | 42 ++---- .../solver/KineticConnections.java | 86 ++++++++++- .../contraptions/solver/KineticNetwork.java | 33 +++-- .../contraptions/solver/KineticNode.java | 103 +++++++++++-- .../contraptions/solver/KineticSolver.java | 138 +++++++++++++++++- 7 files changed, 376 insertions(+), 89 deletions(-) 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 0fe7c74a0..cad96a0c7 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 @@ -4,6 +4,7 @@ import static net.minecraft.ChatFormatting.GOLD; import static net.minecraft.ChatFormatting.GRAY; import java.util.List; +import java.util.Optional; import javax.annotation.Nullable; @@ -78,14 +79,6 @@ public class KineticTileEntity extends SmartTileEntity } } - private void addToSolver() { - KineticSolver.getSolver(level).addNode(this); - } - - private void removeFromSolver() { - KineticSolver.getSolver(level).removeNode(this); - } - public KineticConnections getConnections() { return connections; } @@ -102,6 +95,10 @@ public class KineticTileEntity extends SmartTileEntity return getDefaultStressCapacity(); } + public boolean isStressConstant() { + return false; + } + public float getDefaultStressImpact() { return (float) BlockStressValues.getImpact(getStressConfigKey()); } @@ -110,11 +107,18 @@ public class KineticTileEntity extends SmartTileEntity return (float) BlockStressValues.getCapacity(getStressConfigKey()); } + public Optional isConnected(BlockPos to) { + return KineticSolver.getSolver(level).isConnected(this.getBlockPos(), to); + } + + public boolean isStressOnlyConnected(BlockPos to) { + return KineticSolver.getSolver(level).isStressOnlyConnected(this.getBlockPos(), to); + } @Override public void initialize() { if (!level.isClientSide) { - addToSolver(); + KineticSolver.getSolver(level).addNode(this); } super.initialize(); @@ -125,7 +129,7 @@ public class KineticTileEntity extends SmartTileEntity super.tick(); effects.tick(); - if (!level.isClientSide && !isRemoved()) { + if (!level.isClientSide) { KineticSolver.getSolver(level).updateNode(this); } @@ -144,6 +148,29 @@ public class KineticTileEntity extends SmartTileEntity flickerTally = getFlickerScore() - 1; } + @Override + protected void setRemovedNotDueToChunkUnload() { + if (!level.isClientSide) { + KineticSolver.getSolver(level).removeNode(this); + } + super.setRemovedNotDueToChunkUnload(); + } + + @Override + public void onChunkUnloaded() { + super.onChunkUnloaded(); + if (!level.isClientSide) { + preKineticsUnloaded(); + KineticSolver solver = KineticSolver.getSolver(level); + solver.updateNode(this); + solver.unloadNode(this); + } + } + + public void preKineticsUnloaded() {} + + + private void validateKinetics() { // if (hasSource()) { // if (!hasNetwork()) { @@ -215,14 +242,6 @@ public class KineticTileEntity extends SmartTileEntity super.setRemoved(); } - @Override - protected void setRemovedNotDueToChunkUnload() { - if (!level.isClientSide) { - removeFromSolver(); - } - super.setRemovedNotDueToChunkUnload(); - } - @Override protected void write(CompoundTag compound, boolean clientPacket) { compound.putFloat("Speed", speed); 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 5d6575675..6f7d05e9f 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 @@ -29,10 +29,10 @@ public class SimpleKineticTileEntity extends KineticTileEntity { @Override public float getGeneratedSpeed() { - Block block = getBlockState().getBlock(); - BlockEntity below = level.getBlockEntity(getBlockPos().below()); - if (block instanceof ICogWheel cog && cog.isLargeCog() - && below instanceof SpeedControllerTileEntity controller && controller.getSpeed() != 0) + BlockPos belowPos = getBlockPos().below(); + if (isStressOnlyConnected(belowPos) + && level.getBlockEntity(belowPos) instanceof SpeedControllerTileEntity controller + && controller.getSpeed() != 0) return controller.getTargetSpeed(); return 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 index a3e0833eb..ac8fc0898 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/AllConnections.java @@ -1,40 +1,24 @@ package com.simibubi.create.content.contraptions.solver; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; 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.Types; import com.simibubi.create.content.contraptions.solver.KineticConnections.Entry; import net.minecraft.core.Direction; import net.minecraft.core.Direction.Axis; import net.minecraft.core.Vec3i; +import net.minecraft.util.StringRepresentable; import java.util.LinkedList; import java.util.List; import java.util.Optional; -import static net.minecraft.world.level.block.state.properties.BlockStateProperties.AXIS; - public class AllConnections { - public static record ValueType(Object value) implements Type { - @Override - public boolean compatible(Type other) { - return this == other; - } - - public static LazyMap map() { - return new LazyMap<>(ValueType::new); - } - } - - public static final LazyMap - TYPE_SHAFT = ValueType.map(), - TYPE_LARGE_COG = ValueType.map(), - TYPE_SMALL_COG = ValueType.map(), - TYPE_SPEED_CONTROLLER_TOP = ValueType.map(); - - private static Direction pos(Axis axis) { return Direction.get(Direction.AxisDirection.POSITIVE, axis); } @@ -47,7 +31,7 @@ public class AllConnections { int fromDiff = from.choose(diff.getX(), diff.getY(), diff.getZ()); int toDiff = to.choose(diff.getX(), diff.getY(), diff.getZ()); float ratio = fromDiff > 0 ^ toDiff > 0 ? -1 : 1; - return new Entry(diff, TYPE_LARGE_COG.apply(from), TYPE_LARGE_COG.apply(to), ratio); + return new Entry(diff, Type.of(Types.LARGE_COG, from), Type.of(Types.LARGE_COG, to), ratio); } private static Optional oppAxis(Axis axis) { @@ -63,14 +47,14 @@ public class AllConnections { public static final LazyMap HALF_SHAFT = new LazyMap<>(dir -> - new KineticConnections(new Entry(dir.getNormal(), TYPE_SHAFT.apply(dir.getAxis())))); + new KineticConnections(new Entry(dir.getNormal(), Type.of(Types.SHAFT, dir.getAxis())))); public static final LazyMap FULL_SHAFT = new LazyMap<>(axis -> HALF_SHAFT.apply(pos(axis)).merge(HALF_SHAFT.apply(neg(axis)))), LARGE_COG = new LazyMap<>(axis -> { - Type large = TYPE_LARGE_COG.apply(axis); - Type small = TYPE_SMALL_COG.apply(axis); + Type large = Type.of(Types.LARGE_COG, axis); + Type small = Type.of(Types.SMALL_COG, axis); List out = new LinkedList<>(); Direction cur = DirectionHelper.getPositivePerpendicular(axis); @@ -83,15 +67,15 @@ public class AllConnections { } oppAxis(axis).ifPresent(opp -> { - Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(opp); + Type sc = Type.of(Types.SPEED_CONTROLLER_TOP, opp); out.add(new Entry(Direction.DOWN.getNormal(), large, sc).stressOnly()); }); return new KineticConnections(out); }), SMALL_COG = new LazyMap<>(axis -> { - Type large = TYPE_LARGE_COG.apply(axis); - Type small = TYPE_SMALL_COG.apply(axis); + Type large = Type.of(Types.LARGE_COG, axis); + Type small = Type.of(Types.SMALL_COG, axis); List out = new LinkedList<>(); Direction cur = DirectionHelper.getPositivePerpendicular(axis); @@ -110,8 +94,8 @@ public class AllConnections { SMALL_COG_FULL_SHAFT = new LazyMap<>(axis -> SMALL_COG.apply(axis).merge(FULL_SHAFT.apply(axis))), SPEED_CONTROLLER = new LazyMap<>(axis -> { - Type sc = TYPE_SPEED_CONTROLLER_TOP.apply(axis); - Type large = TYPE_LARGE_COG.apply(oppAxis(axis).get()); + Type sc = Type.of(Types.SPEED_CONTROLLER_TOP, axis); + Type large = Type.of(Types.LARGE_COG, oppAxis(axis).get()); Vec3i up = Direction.UP.getNormal(); return new KineticConnections(new Entry(up, sc, large).stressOnly()).merge(FULL_SHAFT.apply(axis)); }); 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 index 2174094a5..c541b8628 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticConnections.java @@ -1,7 +1,15 @@ package com.simibubi.create.content.contraptions.solver; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import com.simibubi.create.foundation.utility.NBTHelper; + import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; import net.minecraft.util.Mth; +import net.minecraft.util.StringRepresentable; import java.util.Arrays; import java.util.Collection; @@ -15,8 +23,51 @@ import java.util.stream.Stream; public class KineticConnections { - public interface Type { - boolean compatible(Type other); + public enum Types { + SHAFT, LARGE_COG, SMALL_COG, SPEED_CONTROLLER_TOP + } + + public static class Type { + private final Types name; + private final String value; + + private Type(Types name, String value) { + this.name = name; + this.value = value; + } + + private final static Interner cachedTypes = Interners.newStrongInterner(); + + public static Type of(Types name, String value) { + return cachedTypes.intern(new Type(name, value)); + } + + public static Type of(Types name, StringRepresentable value) { + return of(name, value.getSerializedName()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Type type1 = (Type) o; + return name == type1.name && Objects.equals(value, type1.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + public CompoundTag save(CompoundTag tag) { + NBTHelper.writeEnum(tag, "Name", name); + tag.putString("Value", value); + return tag; + } + + public static Type load(CompoundTag tag) { + return Type.of(NBTHelper.readEnum(tag, "Name", Types.class), tag.getString("Value")); + } } public static record Entry(Vec3i offset, Value value) { @@ -74,7 +125,7 @@ public class KineticConnections { if (fromValue.isStressOnly() || toValue.isStressOnly()) return Optional.empty(); - if (fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from) + if (fromValue.from.equals(toValue.to) && fromValue.to.equals(toValue.from) && (Mth.equal(fromValue.ratio, 1/toValue.ratio) || (Mth.equal(toValue.ratio, 1/fromValue.ratio)))) return Optional.of(fromValue.ratio); return Optional.empty(); @@ -89,7 +140,7 @@ public class KineticConnections { if (!fromValue.isStressOnly() || !toValue.isStressOnly()) return false; - return fromValue.from.compatible(toValue.to) && fromValue.to.compatible(toValue.from); + return fromValue.from.equals(toValue.to) && fromValue.to.equals(toValue.from); } @Override @@ -116,5 +167,32 @@ public class KineticConnections { return connections.values().stream().anyMatch(Value::isStressOnly); } + public CompoundTag save(CompoundTag tag) { + ListTag connectionsTags = new ListTag(); + for (Map.Entry entry : connections.entrySet()) { + CompoundTag entryTag = new CompoundTag(); + entryTag.put("Off", NBTHelper.writeVec3i(entry.getKey())); + entryTag.put("From", entry.getValue().from().save(new CompoundTag())); + entryTag.put("To", entry.getValue().to().save(new CompoundTag())); + entryTag.putFloat("Ratio", entry.getValue().ratio()); + connectionsTags.add(entryTag); + } + tag.put("Connections", connectionsTags); + return tag; + } + + public static KineticConnections load(CompoundTag tag) { + Map connections = new HashMap<>(); + tag.getList("Connections", Tag.TAG_COMPOUND).forEach(c -> { + CompoundTag comp = (CompoundTag) c; + Vec3i offset = NBTHelper.readVec3i(comp.getList("Off", Tag.TAG_INT)); + Type from = Type.load(comp.getCompound("From")); + Type to = Type.load(comp.getCompound("To")); + float ratio = comp.getFloat("Ratio"); + connections.put(offset, new Value(from, to, ratio)); + }); + return new KineticConnections(connections); + } + } 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 index a4c16398e..db7e6a386 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNetwork.java @@ -47,12 +47,15 @@ public class KineticNetwork { if (node.isGenerator() && !generators.contains(node)) { generators.add(node); rootSpeedDirty = true; - rootSpeedChanged = true; } if (node.hasStressImpact()) onMemberStressImpactUpdated(); if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); } + public void onMemberLoaded(KineticNode node) { + potentialNewBranches.add(node); + } + public void onMemberGeneratedSpeedUpdated(KineticNode node) { if (node.isGenerator()) { generators.add(node); @@ -60,7 +63,7 @@ public class KineticNetwork { generators.remove(node); } rootSpeedDirty = true; - rootSpeedChanged = true; + onMemberStressCapacityUpdated(); } public void onMemberStressImpactUpdated() { @@ -75,7 +78,6 @@ public class KineticNetwork { if (node.isGenerator() && generators.contains(node)) { generators.remove(node); rootSpeedDirty = true; - rootSpeedChanged = true; } if (node.hasStressImpact()) onMemberStressImpactUpdated(); if (node.hasStressCapacity()) onMemberStressCapacityUpdated(); @@ -131,7 +133,11 @@ public class KineticNetwork { } } - rootTheoreticalSpeed = newSpeed * sign; + if (rootTheoreticalSpeed != newSpeed * sign) { + rootTheoreticalSpeed = newSpeed * sign; + onRootSpeedChanged(); + } + mainGenerator = newGenerator; rootSpeedDirty = false; @@ -150,6 +156,11 @@ public class KineticNetwork { return isStopped() ? 0 : rootTheoreticalSpeed; } + private void onRootSpeedChanged() { + rootSpeedChanged = true; + onMemberStressImpactUpdated(); + } + public void untick() { ticked = false; } @@ -180,13 +191,13 @@ public class KineticNetwork { } else if (nowOverstressed) { if (!cur.overstressed) { cur.overstressed = true; - rootSpeedChanged = true; + cur.onRootSpeedChanged(); cur.members.forEach(KineticNode::stop); } } else { if (cur.overstressed) { cur.overstressed = false; - rootSpeedChanged = true; + cur.onRootSpeedChanged(); cur.bulldozeContradictingMembers(newNetworks); cur.updateMemberSpeeds(newNetworks); } @@ -201,16 +212,16 @@ public class KineticNetwork { * @param newNetworks a List that any new networks created during this call will be added to */ private void updateMemberSpeeds(List newNetworks) { + SolveResult recalculateSpeedResult = tryRecalculateSpeed(); + // generators should not be turning against each other or have conflicting cycles by now + assert(recalculateSpeedResult.isOk()); + // if we're stopped, then all members' speeds will be 0, so no need to check for speeding nodes if (isStopped()) { members.forEach(KineticNode::stop); return; } - SolveResult recalculateSpeedResult = tryRecalculateSpeed(); - // generators should not be turning against each other or have conflicting cycles by now - assert(recalculateSpeedResult.isOk()); - if (rootSpeedChanged) { // root speed changed, update all nodes starting from the main generator rootSpeedChanged = false; @@ -220,8 +231,8 @@ public class KineticNetwork { potentialNewBranches.stream() .filter(n -> !potentialNewBranches.contains(n.getSource())) .forEach(n -> bfs(n, newNetworks, true)); - potentialNewBranches.clear(); } + potentialNewBranches.clear(); } private void bfs(KineticNode root, List newNetworks, boolean followSource) { 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 index 64da7c544..62213d689 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java +++ b/src/main/java/com/simibubi/create/content/contraptions/solver/KineticNode.java @@ -5,6 +5,8 @@ import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.utility.Pair; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; import net.minecraft.util.Mth; import javax.annotation.Nullable; @@ -12,39 +14,90 @@ import javax.annotation.Nullable; import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; public class KineticNode { - private final Function> nodeAccessor; - private final KineticTileEntity entity; + private final KineticSolver solver; + private @Nullable KineticTileEntity entity; private @Nullable KineticNode source; private KineticNetwork network; private float speedRatio = 1; + private float speedCur; + private float speedNext; + private final BlockPos pos; private final KineticConnections connections; private float generatedSpeed; private float stressCapacity; private float stressImpact; + private final boolean constantStress; - private float speedCur; - private float speedNext; - - public KineticNode(KineticTileEntity entity, Function> nodeAccessor) { - this.nodeAccessor = nodeAccessor; + public KineticNode(KineticSolver solver, KineticTileEntity entity) { + this.solver = solver; this.entity = entity; + this.pos = entity.getBlockPos(); this.connections = entity.getConnections(); this.generatedSpeed = entity.getGeneratedSpeed(); this.stressImpact = entity.getStressImpact(); this.stressCapacity = entity.getStressCapacity(); + this.constantStress = entity.isStressConstant(); this.network = new KineticNetwork(this); } + private KineticNode(KineticSolver solver, BlockPos pos, KineticConnections connections, float generatedSpeed, + float stressCapacity, float stressImpact, boolean constantStress) { + this.solver = solver; + + this.pos = pos; + this.connections = connections; + this.generatedSpeed = generatedSpeed; + this.stressImpact = stressImpact; + this.stressCapacity = stressCapacity; + this.constantStress = constantStress; + + this.network = new KineticNetwork(this); + } + + public CompoundTag save(CompoundTag tag) { + tag.put("Pos", NbtUtils.writeBlockPos(pos)); + tag.put("Connections", connections.save(new CompoundTag())); + tag.putFloat("Generated", generatedSpeed); + tag.putFloat("Capacity", stressCapacity); + tag.putFloat("Impact", stressImpact); + if (constantStress) + tag.putBoolean("ConstantStress", true); + return tag; + } + + public static KineticNode load(KineticSolver solver, CompoundTag tag) { + BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos")); + KineticConnections connections = KineticConnections.load(tag.getCompound("Connections")); + float generatedSpeed = tag.getFloat("Generated"); + float stressCapacity = tag.getFloat("Capacity"); + float stressImpact = tag.getFloat("Impact"); + boolean constantStress = tag.getBoolean("ConstantStress"); + return new KineticNode(solver, pos, connections, generatedSpeed, stressCapacity, stressImpact, constantStress); + } + + public boolean isLoaded() { + return entity != null; + } + + public void onLoaded(KineticTileEntity entity) { + this.entity = entity; + network.onMemberLoaded(this); + if (speedCur != 0) entity.setSpeed(speedCur); + } + + public void onUnloaded() { + this.entity = null; + } + public KineticConnections getConnections() { return connections; } @@ -53,13 +106,17 @@ public class KineticNode { return network; } + public BlockPos getPos() { + return pos; + } + /** * @return a Stream containing a pair for each compatible connection with this node, where the first value is * the connecting node and the second value is the speed ratio of the connection */ public Stream> getActiveConnections() { return connections.getDirections().stream() - .map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) + .map(d -> solver.getNode(pos.offset(d)) .map(n -> connections.checkConnection(n.connections, d) .map(r -> Pair.of(n, r)))) .flatMap(Optional::stream) @@ -72,7 +129,7 @@ public class KineticNode { public Stream getActiveStressOnlyConnections() { return connections.getDirections().stream() - .map(d -> nodeAccessor.apply(entity.getBlockPos().offset(d)) + .map(d -> solver.getNode(pos.offset(d)) .filter(n -> connections.checkStressOnlyConnection(n.connections, d))) .flatMap(Optional::stream) .map(KineticNode::getNetwork); @@ -86,10 +143,15 @@ public class KineticNode { return generatedSpeed != 0; } - public void onUpdated() { + public boolean onUpdated() { + if (entity == null) return false; + + boolean changed = false; + float generatedSpeedNew = entity.getGeneratedSpeed(); if (this.generatedSpeed != generatedSpeedNew) { this.generatedSpeed = generatedSpeedNew; + changed = true; network.onMemberGeneratedSpeedUpdated(this); if (network.tryRecalculateSpeed().isContradiction()) { popBlock(); @@ -99,14 +161,18 @@ public class KineticNode { float stressImpactNew = entity.getStressImpact(); if (this.stressImpact != stressImpactNew) { this.stressImpact = stressImpactNew; + changed = true; network.onMemberStressImpactUpdated(); } float stressCapacityNew = entity.getStressCapacity(); if (this.stressCapacity != stressCapacityNew) { this.stressCapacity = stressCapacityNew; + changed = true; network.onMemberStressCapacityUpdated(); } + + return changed; } public boolean hasStressCapacity() { @@ -122,11 +188,11 @@ public class KineticNode { } public float getStressCapacity() { - return Math.abs(stressCapacity * generatedSpeed); + return constantStress ? stressCapacity : stressCapacity * Math.abs(generatedSpeed); } public float getTotalStressImpact(float speedAtRoot) { - return Math.abs(stressImpact * getTheoreticalSpeed(speedAtRoot)); + return constantStress ? stressImpact : stressImpact * Math.abs(getTheoreticalSpeed(speedAtRoot)); } private SolveResult setNetwork(KineticNetwork network) { @@ -233,13 +299,18 @@ public class KineticNode { public void flushChangedSpeed() { if (speedCur != speedNext) { speedCur = speedNext; - entity.setSpeed(speedCur); + if (entity != null) { + entity.setSpeed(speedCur); + } } } public void popBlock() { - // this should cause the node to get removed from the solver and lead to onRemoved() being called - entity.getLevel().destroyBlock(entity.getBlockPos(), true); + if (entity != null) { + solver.removeAndPopNow(entity); + } else { + solver.removeAndQueuePop(pos); + } } } 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 814285be7..64f6ed50b 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 @@ -10,30 +10,108 @@ import java.util.Set; import java.util.stream.Collectors; import com.simibubi.create.content.contraptions.base.KineticTileEntity; +import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.WorldAttached; import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.saveddata.SavedData; -public class KineticSolver { +import org.checkerframework.checker.units.qual.C; +import org.jetbrains.annotations.NotNull; - private static final WorldAttached SOLVERS = new WorldAttached<>($ -> new KineticSolver()); +public class KineticSolver extends SavedData { + + public static final String DATA_FILE_NAME = "kinetics"; + + private static final WorldAttached SOLVERS = new WorldAttached<>(levelAccessor -> { + if (levelAccessor instanceof ServerLevel level) + return level.getDataStorage().computeIfAbsent(KineticSolver::load, KineticSolver::new, DATA_FILE_NAME); + return new KineticSolver(); + }); public static KineticSolver getSolver(Level level) { return SOLVERS.get(level); } private final Map nodes = new HashMap<>(); + private final Set popQueue = new HashSet<>(); + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag tag) { + ListTag popQueueTag = new ListTag(); + for (BlockPos pos : popQueue) { + popQueueTag.add(NbtUtils.writeBlockPos(pos)); + } + tag.put("PopQueue", popQueueTag); + + ListTag nodesTag = new ListTag(); + for (KineticNode node : nodes.values()) { + nodesTag.add(node.save(new CompoundTag())); + } + tag.put("Nodes", nodesTag); + + return tag; + } + + private static KineticSolver load(CompoundTag tag) { + KineticSolver out = new KineticSolver(); + + tag.getList("PopQueue", Tag.TAG_COMPOUND).forEach(c -> + out.popQueue.add(NbtUtils.readBlockPos((CompoundTag) c))); + + tag.getList("Nodes", Tag.TAG_COMPOUND).forEach(c -> + out.addUnloadedNode(KineticNode.load(out, (CompoundTag) c))); + + return out; + } + + private void addUnloadedNode(KineticNode node) { + KineticNode nodePrev = nodes.remove(node.getPos()); + if (nodePrev != null) nodePrev.onRemoved(); + + nodes.put(node.getPos(), node); + node.onAdded(); + } public void addNode(KineticTileEntity entity) { - removeNode(entity); - KineticNode node = new KineticNode(entity, this::getNode); - nodes.put(entity.getBlockPos(), node); + BlockPos pos = entity.getBlockPos(); + if (popQueue.contains(pos)) { + popQueue.remove(pos); + popBlock(entity.getLevel(), pos); + setDirty(); + return; + } + + KineticNode nodePrev = nodes.get(pos); + if (nodePrev != null) { + if (nodePrev.isLoaded()) { + nodes.remove(pos); + nodePrev.onRemoved(); + } else { + // a node exists here but is unloaded, so just load it with this entity instead of replacing + nodePrev.onLoaded(entity); + return; + } + } + + KineticNode node = new KineticNode(this, entity); + nodes.put(pos, node); node.onAdded(); + setDirty(); } public void updateNode(KineticTileEntity entity) { KineticNode node = nodes.get(entity.getBlockPos()); + if (node == null) return; if (!node.getConnections().equals(entity.getConnections())) { // connections changed, so things could've been disconnected @@ -41,17 +119,49 @@ public class KineticSolver { addNode(entity); } else { // connections are the same, so just update in case other properties changed - node.onUpdated(); + if (node.onUpdated()) { + setDirty(); + } } } + public void unloadNode(KineticTileEntity entity) { + KineticNode node = nodes.get(entity.getBlockPos()); + if (node != null) node.onUnloaded(); + } + protected Optional getNode(BlockPos pos) { return Optional.ofNullable(nodes.get(pos)); } public void removeNode(KineticTileEntity entity) { KineticNode node = nodes.remove(entity.getBlockPos()); - if (node != null) node.onRemoved(); + if (node != null) { + node.onRemoved(); + setDirty(); + } + } + + protected void removeAndQueuePop(BlockPos pos) { + KineticNode node = nodes.remove(pos); + if (node != null) { + popQueue.add(pos); + node.onRemoved(); + setDirty(); + } + } + + protected void removeAndPopNow(KineticTileEntity entity) { + KineticNode node = nodes.remove(entity.getBlockPos()); + if (node != null) { + popBlock(entity.getLevel(), entity.getBlockPos()); + node.onRemoved(); + setDirty(); + } + } + + private void popBlock(Level level, BlockPos pos) { + level.destroyBlock(pos, true); } public void tick() { @@ -69,4 +179,18 @@ public class KineticSolver { } } + public Optional isConnected(BlockPos from, BlockPos to) { + return getNode(from).flatMap(fromNode -> + getNode(to).flatMap(toNode -> + fromNode.getConnections() + .checkConnection(toNode.getConnections(), to.subtract(from)))); + } + + public boolean isStressOnlyConnected(BlockPos from, BlockPos to) { + return getNode(from).flatMap(fromNode -> + getNode(to).map(toNode -> + fromNode.getConnections() + .checkStressOnlyConnection(toNode.getConnections(), to.subtract(from))) + ).orElse(false); + } }