diff --git a/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java index aec4c1be..21635d68 100644 --- a/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/block/entity/ComputerTileEntity.java @@ -11,6 +11,7 @@ import li.cil.oc2.api.bus.device.vm.VMDevice; import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType; import li.cil.oc2.common.block.ComputerBlock; import li.cil.oc2.common.bus.AbstractDeviceBusController; +import li.cil.oc2.common.bus.TileEntityDeviceBusController; import li.cil.oc2.common.bus.TileEntityDeviceBusElement; import li.cil.oc2.common.bus.device.ItemDeviceInfo; import li.cil.oc2.common.capabilities.Capabilities; @@ -113,14 +114,14 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic virtualMachine.vmAdapter.setDefaultAddressProvider(this::getDefaultDeviceAddress); } - public Terminal getTerminal() { - return terminal; - } - public IItemHandler getItemHandler() { return itemHandler; } + public Terminal getTerminal() { + return terminal; + } + public void start() { if (runState == RunState.RUNNING) { return; @@ -157,7 +158,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic } public void handleNeighborChanged(final BlockPos pos) { - busElement.handleNeighborChanged(pos); + busController.scheduleBusScan(); } @OnlyIn(Dist.CLIENT) @@ -198,11 +199,6 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic @Override protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) { - if (direction != getBlockState().get(ComputerBlock.HORIZONTAL_FACING)) { - collector.offer(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement); - collector.offer(Capabilities.DEVICE_BUS_CONTROLLER_CAPABILITY, busController); - } - collector.offer(Capabilities.ITEM_HANDLER_CAPABILITY, itemHandler); } @@ -241,7 +237,15 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic // bus setup and devices to load. So we can keep using it. if (runner == null) { virtualMachine.board.reset(); - virtualMachine.board.initialize(); + + try { + virtualMachine.board.initialize(); + } catch (final Throwable e) { + LOGGER.error(e); + setRunState(RunState.STOPPED); + return; + } + virtualMachine.board.setRunning(true); runner = new ComputerVirtualMachineRunner(virtualMachine); @@ -451,9 +455,9 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic /////////////////////////////////////////////////////////////////// - private final class BusController extends AbstractDeviceBusController { - private BusController() { - super(busElement); + private final class BusController extends TileEntityDeviceBusController { + private BusController(final DeviceBusElement root) { + super(root, ComputerTileEntity.this); } @Override diff --git a/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusController.java b/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusController.java new file mode 100644 index 00000000..44e87bf5 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/TileEntityDeviceBusController.java @@ -0,0 +1,90 @@ +package li.cil.oc2.common.bus; + +import li.cil.oc2.api.bus.BlockDeviceBusElement; +import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.common.util.ServerScheduler; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; + +import java.util.Collection; +import java.util.HashSet; + +public class TileEntityDeviceBusController extends AbstractDeviceBusController { + private final Runnable onBusChunkLoadedStateChanged = this::scheduleBusScan; + private final HashSet trackedChunks = new HashSet<>(); + private final TileEntity tileEntity; + + /////////////////////////////////////////////////////////////////// + + public TileEntityDeviceBusController(final DeviceBusElement root, final TileEntity tileEntity) { + super(root); + this.tileEntity = tileEntity; + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void dispose() { + super.dispose(); + + removeListeners(trackedChunks); + trackedChunks.clear(); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void onAfterBusScan() { + final World world = tileEntity.getWorld(); + if (world == null) { + return; + } + + final HashSet newTrackedChunks = new HashSet<>(); + for (final DeviceBusElement element : getElements()) { + if (element instanceof BlockDeviceBusElement) { + final BlockPos position = ((BlockDeviceBusElement) element).getPosition(); + newTrackedChunks.add(new ChunkPos(position)); + newTrackedChunks.add(new ChunkPos(position.offset(Direction.NORTH))); + newTrackedChunks.add(new ChunkPos(position.offset(Direction.EAST))); + newTrackedChunks.add(new ChunkPos(position.offset(Direction.SOUTH))); + newTrackedChunks.add(new ChunkPos(position.offset(Direction.WEST))); + } + } + + // Do not track the chunk the controller itself is in -- this is unneeded because + // we expect the controller to be disposed if its chunk is unloaded. + newTrackedChunks.remove(new ChunkPos(tileEntity.getPos())); + + final HashSet removedChunks = new HashSet<>(trackedChunks); + removedChunks.removeAll(newTrackedChunks); + removeListeners(removedChunks); + + final HashSet addedChunks = new HashSet<>(newTrackedChunks); + newTrackedChunks.removeAll(trackedChunks); + addListeners(world, addedChunks); + + trackedChunks.removeAll(removedChunks); + trackedChunks.addAll(newTrackedChunks); + } + + /////////////////////////////////////////////////////////////////// + + private void addListeners(final World world, final Collection chunks) { + for (final ChunkPos chunkPos : chunks) { + ServerScheduler.scheduleOnLoad(world, chunkPos, onBusChunkLoadedStateChanged); + ServerScheduler.scheduleOnUnload(world, chunkPos, onBusChunkLoadedStateChanged); + } + } + + private void removeListeners(final Collection chunks) { + final World world = tileEntity.getWorld(); + for (final ChunkPos chunkPos : chunks) { + ServerScheduler.cancelOnLoad(world, chunkPos, onBusChunkLoadedStateChanged); + ServerScheduler.cancelOnUnload(world, chunkPos, onBusChunkLoadedStateChanged); + } + } +}