diff --git a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java index d340e363..94a3a801 100644 --- a/src/main/java/li/cil/oc2/common/entity/RobotEntity.java +++ b/src/main/java/li/cil/oc2/common/entity/RobotEntity.java @@ -19,6 +19,7 @@ import li.cil.oc2.common.network.Network; import li.cil.oc2.common.network.message.*; import li.cil.oc2.common.serialization.NBTSerialization; import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.util.NBTUtils; import li.cil.oc2.common.util.WorldUtils; import li.cil.oc2.common.vm.*; import net.minecraft.block.SoundType; @@ -67,7 +68,8 @@ public final class RobotEntity extends Entity { private static final String BUS_ELEMENT_TAG_NAME = "bus_element"; private static final String COMMAND_PROCESSOR_TAG_NAME = "commands"; - private static final int MAX_QUEUED_ACTIONS = 15; + private static final int MAX_QUEUED_ACTIONS = 16; + private static final int MAX_QUEUED_RESULTS = 16; private static final int MEMORY_SLOTS = 4; private static final int HARD_DRIVE_SLOTS = 2; @@ -80,7 +82,7 @@ public final class RobotEntity extends Entity { private final Consumer worldUnloadListener = this::handleWorldUnload; private final AnimationState animationState = new AnimationState(); - private final CommandProcessor commandProcessor = new CommandProcessor(); + private final RobotActionProcessor actionProcessor = new RobotActionProcessor(); private final Terminal terminal = new Terminal(); private final RobotVirtualMachineState state; private final RobotItemStackHandlers items = new RobotItemStackHandlers(); @@ -145,8 +147,8 @@ public final class RobotEntity extends Entity { } else { registerListeners(); RobotActions.initializeData(this); - if (commandProcessor.action != null) { - commandProcessor.action.initialize(this); + if (actionProcessor.action != null) { + actionProcessor.action.initialize(this); } } } @@ -157,7 +159,7 @@ public final class RobotEntity extends Entity { state.tick(); } - commandProcessor.tick(); + actionProcessor.tick(); } @Override @@ -235,7 +237,7 @@ public final class RobotEntity extends Entity { protected void writeAdditional(final CompoundNBT tag) { tag.put(STATE_TAG_NAME, state.serialize()); tag.put(TERMINAL_TAG_NAME, NBTSerialization.serialize(terminal)); - tag.put(COMMAND_PROCESSOR_TAG_NAME, commandProcessor.serialize()); + tag.put(COMMAND_PROCESSOR_TAG_NAME, actionProcessor.serialize()); tag.put(BUS_ELEMENT_TAG_NAME, busElement.serialize()); tag.put(Constants.INVENTORY_TAG_NAME, items.serialize()); } @@ -244,7 +246,7 @@ public final class RobotEntity extends Entity { protected void readAdditional(final CompoundNBT tag) { state.deserialize(tag.getCompound(STATE_TAG_NAME)); NBTSerialization.deserialize(tag.getCompound(TERMINAL_TAG_NAME), terminal); - commandProcessor.deserialize(tag.getCompound(COMMAND_PROCESSOR_TAG_NAME)); + actionProcessor.deserialize(tag.getCompound(COMMAND_PROCESSOR_TAG_NAME)); busElement.deserialize(tag.getCompound(BUS_ELEMENT_TAG_NAME)); if (tag.contains(Constants.INVENTORY_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { @@ -365,7 +367,7 @@ public final class RobotEntity extends Entity { public float topRenderHover = -(hashCode() & 0xFFFF); // init to "random" to avoid synchronous hovering public void update(final float deltaTime, final Random random) { - if (getState().isRunning() || commandProcessor.hasQueuedActions()) { + if (getState().isRunning() || actionProcessor.hasQueuedActions()) { topRenderHover = topRenderHover + deltaTime * HOVER_ANIMATION_SPEED; final float topOffsetY = MathHelper.sin(topRenderHover) / 32f; @@ -386,13 +388,49 @@ public final class RobotEntity extends Entity { } } - private final class CommandProcessor { + private static final class RobotActionProcessorResult { + private static final String ACTION_ID_TAG_NAME = "action_id"; + private static final String RESULT_TAG_NAME = "result"; + + public int actionId; + public RobotActionResult result; + + public RobotActionProcessorResult(final int actionId, final RobotActionResult result) { + this.actionId = actionId; + this.result = result; + } + + public RobotActionProcessorResult(final CompoundNBT tag) { + deserialize(tag); + } + + public CompoundNBT serialize() { + final CompoundNBT tag = new CompoundNBT(); + + tag.putInt(ACTION_ID_TAG_NAME, actionId); + NBTUtils.putEnum(tag, RESULT_TAG_NAME, result); + + return tag; + } + + public void deserialize(final CompoundNBT tag) { + actionId = tag.getInt(ACTION_ID_TAG_NAME); + result = NBTUtils.getEnum(tag, RESULT_TAG_NAME, RobotActionResult.class); + } + } + + private final class RobotActionProcessor { private static final String QUEUE_TAG_NAME = "queue"; private static final String ACTION_TAG_NAME = "action"; + private static final String RESULTS_TAG_NAME = "results"; + private static final String LAST_ACTION_ID_TAG_NAME = "last_action_id"; - private final Queue queue = new ArrayDeque<>(MAX_QUEUED_ACTIONS); + private final Queue queue = new ArrayDeque<>(MAX_QUEUED_ACTIONS - 1); @Nullable private AbstractRobotAction action; + private final Queue results = new ArrayDeque<>(MAX_QUEUED_RESULTS); + private int lastActionId; + public boolean hasQueuedActions() { return action != null || !queue.isEmpty(); } @@ -414,7 +452,16 @@ public final class RobotEntity extends Entity { RobotActions.performClient(RobotEntity.this); } else { if (action != null) { - if (action.perform(RobotEntity.this)) { + final RobotActionResult result = action.perform(RobotEntity.this); + if (result != RobotActionResult.INCOMPLETE) { + synchronized (results) { + if (results.size() == MAX_QUEUED_RESULTS) { + results.remove(); + } + + results.add(new RobotActionProcessorResult(action.getId(), result)); + } + action = null; } } @@ -429,6 +476,8 @@ public final class RobotEntity extends Entity { public void clear() { queue.clear(); + results.clear(); + lastActionId = 0; } public CompoundNBT serialize() { @@ -444,24 +493,40 @@ public final class RobotEntity extends Entity { tag.put(ACTION_TAG_NAME, RobotActions.serialize(action)); } + final ListNBT resultsTag = new ListNBT(); + for (final RobotActionProcessorResult result : results) { + resultsTag.add(result.serialize()); + } + tag.put(RESULTS_TAG_NAME, resultsTag); + + tag.putInt(LAST_ACTION_ID_TAG_NAME, lastActionId); + return tag; } public void deserialize(final CompoundNBT tag) { queue.clear(); - action = null; + results.clear(); final ListNBT queueTag = tag.getList(QUEUE_TAG_NAME, NBTTagIds.TAG_COMPOUND); - for (int i = 0; i < queueTag.size(); i++) { + for (int i = 0; i < Math.min(queueTag.size(), MAX_QUEUED_ACTIONS - 1); i++) { final AbstractRobotAction action = RobotActions.deserialize(queueTag.getCompound(i)); if (action != null) { queue.add(action); } } - if (tag.contains(ACTION_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - action = RobotActions.deserialize(tag.getCompound(ACTION_TAG_NAME)); + action = RobotActions.deserialize(tag.getCompound(ACTION_TAG_NAME)); + + final ListNBT resultsTag = tag.getList(RESULTS_TAG_NAME, NBTTagIds.TAG_COMPOUND); + for (int i = 0; i < Math.min(resultsTag.size(), MAX_QUEUED_RESULTS); i++) { + final RobotActionProcessorResult result = new RobotActionProcessorResult(resultsTag.getCompound(i)); + if (result.actionId != 0) { + results.add(result); + } } + + lastActionId = tag.getInt(LAST_ACTION_ID_TAG_NAME); } private boolean addAction(final AbstractRobotAction action) { @@ -473,8 +538,12 @@ public final class RobotEntity extends Entity { return false; } - if (queue.size() < MAX_QUEUED_ACTIONS) { - queue.add(action); + if (queue.size() < MAX_QUEUED_ACTIONS - 1) { // -1 for current action + lastActionId = (lastActionId + 1) & 0x7FFFFFFF; // only positive ids; unlikely to ever wrap, but eh. + action.setId(lastActionId); + synchronized (queue) { + queue.add(action); + } return true; } else { return false; @@ -589,7 +658,7 @@ public final class RobotEntity extends Entity { public void stopRunnerAndReset() { super.stopRunnerAndReset(); - commandProcessor.clear(); + actionProcessor.clear(); } @Override @@ -609,21 +678,51 @@ public final class RobotEntity extends Entity { } public final class RobotDevice { - @Callback + @Callback(synchronize = false) public boolean move(@Parameter("direction") @Nullable final MovementDirection direction) { if (direction == null) throw new IllegalArgumentException(); - return commandProcessor.move(direction); + return actionProcessor.move(direction); } - @Callback + @Callback(synchronize = false) public boolean turn(@Parameter("direction") @Nullable final RotationDirection direction) { if (direction == null) throw new IllegalArgumentException(); - return commandProcessor.rotate(direction); + return actionProcessor.rotate(direction); } - @Callback + @Callback(synchronize = false) + public int getLastActionId() { + return actionProcessor.lastActionId; + } + + @Callback(synchronize = false) public int getQueuedActionCount() { - return commandProcessor.getQueuedActionCount(); + return actionProcessor.getQueuedActionCount(); + } + + @Nullable + @Callback(synchronize = false) + public RobotActionResult getActionResult(@Parameter("actionId") final int actionId) { + final AbstractRobotAction currentAction = actionProcessor.action; + if (currentAction != null && currentAction.getId() == actionId) { + return RobotActionResult.INCOMPLETE; + } + synchronized (actionProcessor.queue) { + for (final AbstractRobotAction action : actionProcessor.queue) { + if (action.getId() == actionId) { + return RobotActionResult.INCOMPLETE; + } + } + } + synchronized (actionProcessor.results) { + for (final RobotActionProcessorResult result : actionProcessor.results) { + if (result.actionId == actionId) { + return result.result; + } + } + } + + return null; } private RobotDevice() { diff --git a/src/main/java/li/cil/oc2/common/entity/robot/AbstractRobotAction.java b/src/main/java/li/cil/oc2/common/entity/robot/AbstractRobotAction.java index bd2a6186..f08237a9 100644 --- a/src/main/java/li/cil/oc2/common/entity/robot/AbstractRobotAction.java +++ b/src/main/java/li/cil/oc2/common/entity/robot/AbstractRobotAction.java @@ -4,7 +4,12 @@ import li.cil.oc2.common.entity.RobotEntity; import net.minecraft.nbt.CompoundNBT; public abstract class AbstractRobotAction { + private static final String ID_TAG_NAME = "id"; + + /////////////////////////////////////////////////////////////////// + private final AbstractRobotActionType type; + private int id; /////////////////////////////////////////////////////////////////// @@ -23,17 +28,28 @@ public abstract class AbstractRobotAction { return type; } + public int getId() { + return id; + } + + public void setId(final int value) { + id = value; + } + public void initialize(final RobotEntity robot) { } - public abstract boolean perform(RobotEntity robot); + public abstract RobotActionResult perform(RobotEntity robot); public CompoundNBT serialize() { - return new CompoundNBT(); + final CompoundNBT tag = new CompoundNBT(); + + tag.putInt(ID_TAG_NAME, id); + + return tag; } - /////////////////////////////////////////////////////////////////// - - protected void deserialize(final CompoundNBT tag) { + public void deserialize(final CompoundNBT tag) { + id = tag.getInt(ID_TAG_NAME); } } diff --git a/src/main/java/li/cil/oc2/common/entity/robot/RobotActionResult.java b/src/main/java/li/cil/oc2/common/entity/robot/RobotActionResult.java new file mode 100644 index 00000000..80fb1627 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/entity/robot/RobotActionResult.java @@ -0,0 +1,7 @@ +package li.cil.oc2.common.entity.robot; + +public enum RobotActionResult { + INCOMPLETE, + SUCCESS, + FAILURE +} diff --git a/src/main/java/li/cil/oc2/common/entity/robot/RobotMovementAction.java b/src/main/java/li/cil/oc2/common/entity/robot/RobotMovementAction.java index 01b4d4bb..c722ee3a 100644 --- a/src/main/java/li/cil/oc2/common/entity/robot/RobotMovementAction.java +++ b/src/main/java/li/cil/oc2/common/entity/robot/RobotMovementAction.java @@ -12,6 +12,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import javax.annotation.Nullable; +import java.util.Objects; public final class RobotMovementAction extends AbstractRobotAction { public static final double TARGET_EPSILON = 0.0001; @@ -21,14 +22,17 @@ public final class RobotMovementAction extends AbstractRobotAction { private static final float MOVEMENT_SPEED = 1f / Constants.TICK_SECONDS; // In blocks per second. private static final String DIRECTION_TAG_NAME = "direction"; + private static final String ORIGIN_TAG_NAME = "origin"; private static final String START_TAG_NAME = "start"; - private static final String TARGET_TAG_NAME = "start"; + private static final String TARGET_TAG_NAME = "target"; /////////////////////////////////////////////////////////////////// private MovementDirection direction; + @Nullable private BlockPos origin; @Nullable private BlockPos start; - @Nullable private Vector3d target; + @Nullable private BlockPos target; + @Nullable private Vector3d targetPos; /////////////////////////////////////////////////////////////////// @@ -57,82 +61,94 @@ public final class RobotMovementAction extends AbstractRobotAction { robot.move(MoverType.SELF, delta); } + /////////////////////////////////////////////////////////////////// + @Override public void initialize(final RobotEntity robot) { - if (target == null) { - start = robot.getPosition(); - BlockPos targetPosition = start; + if (origin == null || start == null || target == null) { + origin = robot.getPosition(); + start = origin; + target = start; switch (direction) { case UP: - targetPosition = targetPosition.offset(Direction.UP); + target = target.offset(Direction.UP); break; case DOWN: - targetPosition = targetPosition.offset(Direction.DOWN); + target = target.offset(Direction.DOWN); break; case FORWARD: - targetPosition = targetPosition.offset(robot.getHorizontalFacing()); + target = target.offset(robot.getHorizontalFacing()); break; case BACKWARD: - targetPosition = targetPosition.offset(robot.getHorizontalFacing().getOpposite()); + target = target.offset(robot.getHorizontalFacing().getOpposite()); break; } - - target = getTargetPositionInBlock(targetPosition); } - robot.getDataManager().set(RobotEntity.TARGET_POSITION, new BlockPos(target)); + targetPos = getTargetPositionInBlock(target); + robot.getDataManager().set(RobotEntity.TARGET_POSITION, target); } @Override - public boolean perform(final RobotEntity robot) { - if (target == null) { - return true; + public RobotActionResult perform(final RobotEntity robot) { + if (targetPos == null) { + throw new IllegalStateException(); } - moveTowards(robot, target); + moveTowards(robot, targetPos); final boolean didCollide = robot.collidedHorizontally || robot.collidedVertically; if (didCollide && !robot.getEntityWorld().isRemote()) { - if (start != null) { - target = getTargetPositionInBlock(start); - robot.getDataManager().set(RobotEntity.TARGET_POSITION, start); + final BlockPos newStart = target; + target = start; + start = newStart; + targetPos = getTargetPositionInBlock(target); + robot.getDataManager().set(RobotEntity.TARGET_POSITION, target); + } - start = null; + if (robot.getPositionVec().squareDistanceTo(targetPos) < TARGET_EPSILON) { + if (Objects.equals(target, origin)) { + return RobotActionResult.FAILURE; // Collided and returned. } else { - // todo if it's a block, try to break it. or just drop ourselves? + return RobotActionResult.SUCCESS; // Made it to new location. } } - return robot.getPositionVec().squareDistanceTo(target) < TARGET_EPSILON; + return RobotActionResult.INCOMPLETE; } - /////////////////////////////////////////////////////////////////// - @Override public CompoundNBT serialize() { final CompoundNBT tag = super.serialize(); NBTUtils.putEnum(tag, DIRECTION_TAG_NAME, direction); + if (origin != null) { + NBTUtils.putBlockPos(tag, ORIGIN_TAG_NAME, origin); + } if (start != null) { NBTUtils.putBlockPos(tag, START_TAG_NAME, start); } if (target != null) { - NBTUtils.putVector3d(tag, TARGET_TAG_NAME, target); + NBTUtils.putBlockPos(tag, TARGET_TAG_NAME, target); } return tag; } @Override - protected void deserialize(final CompoundNBT tag) { + public void deserialize(final CompoundNBT tag) { super.deserialize(tag); direction = NBTUtils.getEnum(tag, DIRECTION_TAG_NAME, MovementDirection.class); + if (tag.contains(ORIGIN_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + origin = NBTUtils.getBlockPos(tag, ORIGIN_TAG_NAME); + } if (tag.contains(START_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { start = NBTUtils.getBlockPos(tag, START_TAG_NAME); } if (tag.contains(TARGET_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { - target = NBTUtils.getVector3d(tag, TARGET_TAG_NAME); + target = NBTUtils.getBlockPos(tag, TARGET_TAG_NAME); + targetPos = getTargetPositionInBlock(target); } } } diff --git a/src/main/java/li/cil/oc2/common/entity/robot/RobotRotationAction.java b/src/main/java/li/cil/oc2/common/entity/robot/RobotRotationAction.java index c02040ef..217e330e 100644 --- a/src/main/java/li/cil/oc2/common/entity/robot/RobotRotationAction.java +++ b/src/main/java/li/cil/oc2/common/entity/robot/RobotRotationAction.java @@ -43,6 +43,8 @@ public final class RobotRotationAction extends AbstractRobotAction { robot.rotationYaw = MathHelper.approachDegrees(robot.rotationYaw, targetRotation.getHorizontalAngle(), ROTATION_SPEED); } + /////////////////////////////////////////////////////////////////// + @Override public void initialize(final RobotEntity robot) { if (target == null) { @@ -61,17 +63,19 @@ public final class RobotRotationAction extends AbstractRobotAction { } @Override - public boolean perform(final RobotEntity robot) { + public RobotActionResult perform(final RobotEntity robot) { if (target == null) { - return true; + throw new IllegalStateException(); } rotateTowards(robot, target); - return MathHelper.degreesDifferenceAbs(robot.rotationYaw, target.getHorizontalAngle()) < TARGET_EPSILON; - } + if (MathHelper.degreesDifferenceAbs(robot.rotationYaw, target.getHorizontalAngle()) < TARGET_EPSILON) { + return RobotActionResult.SUCCESS; + } - /////////////////////////////////////////////////////////////////// + return RobotActionResult.INCOMPLETE; + } @Override public CompoundNBT serialize() { @@ -86,7 +90,7 @@ public final class RobotRotationAction extends AbstractRobotAction { } @Override - protected void deserialize(final CompoundNBT tag) { + public void deserialize(final CompoundNBT tag) { super.deserialize(tag); direction = NBTUtils.getEnum(tag, DIRECTION_TAG_NAME, RotationDirection.class);