New approach

This commit is contained in:
reidbhuntley 2021-12-24 18:36:57 -05:00
parent 69d33525f6
commit 8f5a885bb0
17 changed files with 456 additions and 216 deletions

View file

@ -346,16 +346,24 @@ public class KineticTileEntity extends SmartTileEntity
public void attachKinetics() {
updateSpeed = false;
KineticSolver solver = KineticSolver.getSolver(level);
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);
}

View file

@ -3,7 +3,8 @@ 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.GeneratorGoal;
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;
@ -76,6 +77,11 @@ public class CreativeMotorBlock extends DirectionalKineticBlock implements ITE<C
@Override
public void created(KineticSolver solver, Level level, BlockPos pos) {
solver.addGoal(new GeneratorGoal(pos, 16));
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));
}
}

View file

@ -7,8 +7,8 @@ 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.Connection;
import com.simibubi.create.content.contraptions.solver.ConnectionGoal;
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.foundation.advancement.AllTriggers;
import com.simibubi.create.foundation.utility.placement.IPlacementHelper;
@ -90,16 +90,13 @@ public class ShaftBlock extends AbstractShaftBlock implements SolverBlock {
@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();
Connection.Shaft c1 = new Connection.Shaft(pos, positive);
Connection.Shaft c2 = new Connection.Shaft(pos, positive.getOpposite());
solver.addGoal(new ConnectionGoal.EqualSpeed(c1));
solver.addGoal(new ConnectionGoal.EqualSpeed(c2));
solver.addRule(pos, new ShaftConnectionRule(axis));
solver.addRule(pos, new ShaftEqualSpeedRule(positive));
solver.addRule(pos, new ShaftEqualSpeedRule(negative));
}
@MethodsReturnNonnullByDefault

View file

@ -0,0 +1,10 @@
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<>();
}

View file

@ -1,28 +0,0 @@
package com.simibubi.create.content.contraptions.solver;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
public abstract class Connection {
public final BlockPos from;
public final BlockPos to;
public Connection(BlockPos from, BlockPos to) {
this.from = from;
this.to = to;
}
public abstract boolean isCompatible(Connection that);
public static final class Shaft extends Connection {
public Shaft(BlockPos pos, Direction face) {
super(pos, pos.relative(face));
}
@Override
public boolean isCompatible(Connection that) {
return that instanceof Shaft;
}
}
}

View file

@ -1,43 +0,0 @@
package com.simibubi.create.content.contraptions.solver;
import net.minecraft.core.BlockPos;
public abstract class ConnectionGoal implements Goal {
public final Connection connection;
public ConnectionGoal(Connection connection) {
this.connection = connection;
}
@Override
public void onAdded(KineticSolver.PropertyMap solver) {
solver.addConnection(connection);
}
public static class EqualSpeed extends ConnectionGoal {
public EqualSpeed(Connection connection) {
super(connection);
}
@Override
public BlockPos getPos() {
return connection.from;
}
@Override
public SolveResult solve(KineticSolver.PropertyMap solver) {
if (solver.isComplete(connection)) {
Value toSpeed = solver.getOrCreateProperty(connection.to, "speed");
Value fromSpeed = solver.getOrCreateProperty(connection.from, "speed");
if (toSpeed instanceof Value.Known toValue && fromSpeed instanceof Value.Known fromValue) {
if (toValue.value != fromValue.value) {
return Goal.SolveResult.CONTRADICTION;
}
} else {
solver.setProperty(connection.to, "speed", fromSpeed);
}
}
return Goal.SolveResult.OK;
}
}
}

View file

@ -0,0 +1,21 @@
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);
}
}

View file

@ -1,24 +0,0 @@
package com.simibubi.create.content.contraptions.solver;
import net.minecraft.core.BlockPos;
public final class GeneratorGoal implements Goal {
public final BlockPos me;
public final float speed;
public GeneratorGoal(BlockPos me, float speed) {
this.me = me;
this.speed = speed;
}
@Override
public BlockPos getPos() {
return me;
}
@Override
public SolveResult solve(KineticSolver.PropertyMap solver) {
solver.setProperty(me, "speed", new Value.Known(speed));
return Goal.SolveResult.OK;
}
}

View file

@ -1,17 +0,0 @@
package com.simibubi.create.content.contraptions.solver;
import net.minecraft.core.BlockPos;
public interface Goal {
BlockPos getPos();
SolveResult solve(KineticSolver.PropertyMap solver);
default void onAdded(KineticSolver.PropertyMap solver) {
}
enum SolveResult {
CONTRADICTION,
OK,
}
}

View file

@ -0,0 +1,25 @@
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);
}
}

View file

@ -1,12 +1,10 @@
package com.simibubi.create.content.contraptions.solver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.simibubi.create.foundation.utility.WorldAttached;
@ -23,89 +21,70 @@ public class KineticSolver {
}
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 List<Goal> goals = new ArrayList<>();
private Set<RewriteRule.Tracker<?>> rulesFrontier = new HashSet<>();
public static class PropertyMap {
private final Map<BlockPos, Map<String, Value>> properties = new HashMap<>();
private final Map<BlockPos, Set<Connection>> connections = new HashMap<>();
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 Optional<Value> getProperty(BlockPos pos, String property) {
Map<String, Value> map = properties.get(pos);
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));
});
}
if (map != null) {
return Optional.of(map.computeIfAbsent(property, $ -> new Value.Unknown()));
} else {
return Optional.empty();
}
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);
}
public Value getOrCreateProperty(BlockPos pos, String property) {
return properties.computeIfAbsent(pos, $ -> new HashMap<>())
.computeIfAbsent(property, $ -> new Value.Unknown());
}
public void setProperty(BlockPos pos, String property, Value value) {
properties.computeIfAbsent(pos, $ -> new HashMap<>())
.put(property, value);
}
public boolean isComplete(Connection connection) {
Set<Connection> connections = this.connections.get(connection.to);
if (connections == null) return false;
for (Connection other : connections) {
if (connection.isCompatible(other) && other.to.equals(connection.from)) {
return true;
}
}
return false;
}
public void addConnection(Connection connection) {
properties.computeIfAbsent(connection.from, $ -> new HashMap<>());
connections.computeIfAbsent(connection.from, $ -> new HashSet<>()).add(connection);
}
public void clear() {
properties.clear();
for (RewriteRule.Tracker<?> tracker: trackers) {
rulesFrontier.addAll(properties.unwrite(tracker.writes));
}
}
private boolean needsUpdate;
public Set<BlockPos> solve() {
Set<BlockPos> contradictions = new HashSet<>();
public List<BlockPos> solve() {
if (!needsUpdate) return Collections.emptyList();
needsUpdate = false;
while (!rulesFrontier.isEmpty()) {
Set<RewriteRule.Tracker<?>> next = new HashSet<>();
List<BlockPos> troublemakers = new ArrayList<>();
for (RewriteRule.Tracker<?> rule : rulesFrontier) {
if (!allRules.contains(rule) || !rule.canRewrite()) continue;
outer:
while (true) {
properties.clear();
for (Goal goal : goals) {
Goal.SolveResult result = goal.solve(properties);
switch (result) {
case CONTRADICTION -> {
troublemakers.add(goal.getPos());
continue outer;
}
case OK -> {}
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);
}
}
break;
rulesFrontier = next;
}
return troublemakers;
}
public void addGoal(Goal goal) {
goals.add(goal);
needsUpdate = true;
goal.onAdded(properties);
return contradictions;
}
}

View file

@ -0,0 +1,46 @@
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);
}
}
}

View file

@ -0,0 +1,139 @@
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);
}
}
}

View file

@ -0,0 +1,76 @@
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;
}
}
}

View file

@ -0,0 +1,26 @@
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);
}
}

View file

@ -0,0 +1,40 @@
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();
}
}

View file

@ -1,21 +0,0 @@
package com.simibubi.create.content.contraptions.solver;
public sealed class Value {
public static final class Unknown extends Value {
private static int nextID = 0;
public final int id;
public Unknown() {
this.id = nextID++;
}
}
public static final class Known extends Value {
public final float value;
public Known(float value) {
this.value = value;
}
}
}