diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java
index ace2fec2..fe3c864a 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMContext.java
@@ -93,13 +93,4 @@ public interface VMContext {
* @return the event bus.
*/
VMLifecycleEventBus getEventBus();
-
- /**
- * Waits for the executor thread of the virtual machine to finish running.
- *
- * Note that this may trigger a {@link li.cil.oc2.api.bus.device.vm.event.VMPausingEvent}
- * if the virtual machine has not been paused before. Calling this on a paused virtual
- * machine is a no-op.
- */
- void joinWorkerThread();
}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java
index 1baf042e..28db8c66 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/VMLifecycleEventBus.java
@@ -4,9 +4,8 @@ package li.cil.oc2.api.bus.device.vm.context;
* Allows registering for VM lifecycle events.
*
* @see li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent
- * @see li.cil.oc2.api.bus.device.vm.event.VMResumingRunningEvent
+ * @see li.cil.oc2.api.bus.device.vm.event.VMSynchronizeEvent
* @see li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent
- * @see li.cil.oc2.api.bus.device.vm.event.VMPausingEvent
*/
public interface VMLifecycleEventBus {
/**
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java
index e06c8185..dcc22637 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMInitializingEvent.java
@@ -8,8 +8,8 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
*
* Fired after all devices reported success from {@link VMDevice#mount(VMContext)}.
*
- * If a running VM is restored from a saved state, this event will not be fired. It is
- * intended for initializing the VM state on boot, e.g. by loading initial executable
+ * If a running VM is restored from a saved state, this event will not be fired.
+ * It is intended for initializing the VM state on boot, e.g. by loading initial executable
* code into memory.
*
* Listeners of this event may throw a {@link VMInitializationException} in case
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java
index d2d5549c..e1c9cf59 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumedRunningEvent.java
@@ -1,13 +1,16 @@
package li.cil.oc2.api.bus.device.vm.event;
+import li.cil.oc2.api.bus.device.vm.VMDevice;
+import li.cil.oc2.api.bus.device.vm.context.VMContext;
+
/**
- * Fired when the VM resumed running.
+ * Fired when the VM resumed running, either when first starting up, when resuming after
+ * being loaded, or after a {@link VMSynchronizeEvent}.
*
- * Fired after {@link VMResumingRunningEvent} has been fired and handled by all devices.
+ * May be used to ensure asynchronous work started in {@link VMDevice#mount(VMContext)}
+ * completes before regular execution of the virtual machine begins.
*
- * Allows device initialization that relies on all other devices having fully loaded.
- *
- * Typically, this is used in combination with {@link VMPausingEvent}, to re-enable external
+ * May also be used in combination with {@link VMSynchronizeEvent}, to re-enable external
* interactions after VM state is guaranteed to be safe to modify again.
*/
public final class VMResumedRunningEvent { }
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java
deleted file mode 100644
index 46d136ab..00000000
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMResumingRunningEvent.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package li.cil.oc2.api.bus.device.vm.event;
-
-import li.cil.oc2.api.bus.device.vm.VMDevice;
-import li.cil.oc2.api.bus.device.vm.context.VMContext;
-
-/**
- * Fired when the VM resumes running.
- *
- * Fired after all devices reported success from {@link VMDevice#mount(VMContext)}.
- *
- * Fired on initial boot-up as well as when the VM resumes after being restored
- * from a saved state as well as when continuing to run after being paused for
- * a save. It is intended for awaiting asynchronous load and store operations.
- */
-public final class VMResumingRunningEvent { }
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java
similarity index 56%
rename from src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java
rename to src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java
index 476cf1ae..df2da01a 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMPausingEvent.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/event/VMSynchronizeEvent.java
@@ -7,5 +7,10 @@ package li.cil.oc2.api.bus.device.vm.event;
* such interactions until {@link VMResumedRunningEvent} is fired. This is required
* if such interactions may modify VM state, to prevent corrupting data being
* serialized asynchronously.
+ *
+ * Note that this is called right before the worker thread used to execute the
+ * virtual machine is joined to the main thread. As such, only devices that run
+ * their own threads modifying observable state will need to synchronize these
+ * threads here.
*/
-public final class VMPausingEvent { }
+public final class VMSynchronizeEvent { }
diff --git a/src/main/java/li/cil/oc2/common/blockentity/DiskDriveBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/DiskDriveBlockEntity.java
index 5c869151..a4943765 100644
--- a/src/main/java/li/cil/oc2/common/blockentity/DiskDriveBlockEntity.java
+++ b/src/main/java/li/cil/oc2/common/blockentity/DiskDriveBlockEntity.java
@@ -28,8 +28,6 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -37,10 +35,9 @@ import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
public final class DiskDriveBlockEntity extends ModBlockEntity {
- private static final Logger LOGGER = LogManager.getLogger();
-
private static final String DATA_TAG_NAME = "data";
private static final ByteBufferBlockDevice EMPTY_BLOCK_DEVICE = ByteBufferBlockDevice.create(0, false);
@@ -232,10 +229,18 @@ public final class DiskDriveBlockEntity extends ModBlockEntity {
}
public void updateBlockDevice(final CompoundTag tag) {
+ joinOpenJob();
+
if (device == null) {
return;
}
+ try {
+ device.setBlock(EMPTY_BLOCK_DEVICE);
+ } catch (final IOException e) {
+ LOGGER.error(e);
+ }
+
if (blobHandle != null) {
BlobStorage.close(blobHandle);
blobHandle = null;
@@ -243,46 +248,56 @@ public final class DiskDriveBlockEntity extends ModBlockEntity {
importFromItemStack(tag);
- try {
- device.setBlock(createBlockDevice());
- } catch (final IOException e) {
- LOGGER.error(e);
- }
+ setOpenJob(createBlockDevice().thenAcceptAsync(blockDevice -> {
+ try {
+ device.setBlock(blockDevice);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, WORKERS));
}
public void removeBlockDevice() {
+ joinOpenJob();
+
if (device == null) {
return;
}
- if (blobHandle != null) {
- BlobStorage.close(blobHandle);
- blobHandle = null;
- }
-
try {
device.setBlock(EMPTY_BLOCK_DEVICE);
} catch (final IOException e) {
LOGGER.error(e);
}
+
+ if (blobHandle != null) {
+ BlobStorage.close(blobHandle);
+ blobHandle = null;
+ }
}
@Override
- protected BlockDevice createBlockDevice() throws IOException {
+ protected CompletableFuture createBlockDevice() {
final ItemStack stack = itemHandler.getStackInSlotRaw(0);
if (stack.isEmpty() || !(stack.getItem() instanceof final FloppyItem floppy)) {
- return EMPTY_BLOCK_DEVICE;
+ return CompletableFuture.completedFuture(EMPTY_BLOCK_DEVICE);
}
final int capacity = Mth.clamp(floppy.getCapacity(stack), 0, Config.maxFloppySize);
if (capacity <= 0) {
- return EMPTY_BLOCK_DEVICE;
+ return CompletableFuture.completedFuture(EMPTY_BLOCK_DEVICE);
}
blobHandle = BlobStorage.validateHandle(blobHandle);
- final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
- final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, capacity);
- return ByteBufferBlockDevice.wrap(buffer, false);
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
+ final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, capacity);
+ return ByteBufferBlockDevice.wrap(buffer, false);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, WORKERS);
}
@Override
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java
index 2f44b96c..10028467 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java
@@ -1,9 +1,11 @@
package li.cil.oc2.common.bus.device.item;
+import com.google.common.eventbus.Subscribe;
import li.cil.oc2.api.bus.device.ItemDevice;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.api.bus.device.vm.context.VMContext;
+import li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
@@ -23,18 +25,29 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
public abstract class AbstractBlockDeviceVMDevice extends IdentityProxy implements VMDevice, ItemDevice {
- private static final Logger LOGGER = LogManager.getLogger();
+ protected static final Logger LOGGER = LogManager.getLogger();
private static final String DEVICE_TAG_NAME = "device";
private static final String ADDRESS_TAG_NAME = "address";
private static final String INTERRUPT_TAG_NAME = "interrupt";
private static final String BLOB_HANDLE_TAG_NAME = "blob";
+ protected static final ExecutorService WORKERS = Executors.newCachedThreadPool(r -> {
+ final Thread thread = new Thread(r, "Block Device Initializer");
+ thread.setDaemon(false);
+ return thread;
+ });
+
///////////////////////////////////////////////////////////////
protected VirtIOBlockDevice device;
+ private CompletableFuture openJob;
///////////////////////////////////////////////////////////////
@@ -81,7 +94,7 @@ public abstract class AbstractBlockDeviceVMDevice job) {
+ joinOpenJob();
+ openJob = job;
}
+ protected void joinOpenJob() {
+ if (openJob != null) {
+ try {
+ openJob.join();
+ } catch (final CompletionException e) {
+ LOGGER.error(e);
+ } finally {
+ openJob = null;
+ }
+ }
+ }
+
+ protected abstract CompletableFuture createBlockDevice();
+
protected void handleDataAccess() {
}
@@ -185,18 +206,40 @@ public abstract class AbstractBlockDeviceVMDevice {
+ try {
+ final ListenableBlockDevice listenableData = new ListenableBlockDevice(blockDevice);
+ listenableData.onAccess.add(this::handleDataAccess);
+ device.setBlock(listenableData);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, WORKERS));
return true;
}
+ private void closeDevice() {
+ // Join the init job before releasing the device to avoid writes from thread to closed device.
+ // Since we use memory mapped memory, closing the device leads to it holding a dead pointer,
+ // meaning further access to it will hard-crash the JVM.
+ joinOpenJob();
+
+ if (device == null) {
+ return;
+ }
+
+ try {
+ device.close();
+ } catch (final IOException e) {
+ LOGGER.error(e);
+ }
+
+ device = null;
+ }
+
///////////////////////////////////////////////////////////////
private static final class ListenableBlockDevice implements BlockDevice {
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDevice.java
index f11331cc..9673943d 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDevice.java
@@ -12,6 +12,7 @@ import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.time.Duration;
import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public class HardDriveVMDevice extends AbstractBlockDeviceVMDevice {
@@ -32,11 +33,18 @@ public class HardDriveVMDevice extends AbstractBlockDeviceVMDevice createBlockDevice() {
blobHandle = BlobStorage.validateHandle(blobHandle);
- final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
- final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
- return ByteBufferBlockDevice.wrap(buffer, readonly);
+
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
+ final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
+ return ByteBufferBlockDevice.wrap(buffer, readonly);
+ } catch (final IOException e) {
+ throw new RuntimeException(e);
+ }
+ }, WORKERS);
}
@Override
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java
index 34c8a8d9..e79a333a 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java
@@ -1,8 +1,6 @@
package li.cil.oc2.common.bus.device.item;
-import com.google.common.eventbus.Subscribe;
import com.google.common.io.ByteStreams;
-import li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent;
import li.cil.oc2.common.util.Location;
import li.cil.sedna.api.device.BlockDevice;
import li.cil.sedna.device.block.ByteBufferBlockDevice;
@@ -15,21 +13,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.function.Supplier;
public final class HardDriveVMDeviceWithInitialData extends HardDriveVMDevice {
private static final Logger LOGGER = LogManager.getLogger();
- private static final ExecutorService WORKERS = Executors.newCachedThreadPool(r -> {
- final Thread thread = new Thread(r, "Hard Drive Initializer");
- thread.setDaemon(false);
- return thread;
- });
private final BlockDevice base;
- private Future copyJob;
///////////////////////////////////////////////////////////////////
@@ -38,53 +27,23 @@ public final class HardDriveVMDeviceWithInitialData extends HardDriveVMDevice {
this.base = base;
}
- @Subscribe
- public void handleResumedRunningEvent(final VMResumedRunningEvent event) {
- joinCopyJob();
- }
-
///////////////////////////////////////////////////////////////////
@Override
- protected ByteBufferBlockDevice createBlockDevice() throws IOException {
+ protected CompletableFuture createBlockDevice() {
final boolean isInitializing = blobHandle == null;
- final ByteBufferBlockDevice device = super.createBlockDevice();
- if (isInitializing) {
- copyJob = CompletableFuture.runAsync(() -> {
+ return super.createBlockDevice().thenApplyAsync(device -> {
+ if (isInitializing) {
try {
try (final InputStream input = base.getInputStream(0);
final OutputStream output = device.getOutputStream(0)) {
ByteStreams.copy(input, output);
}
} catch (final IOException e) {
- LOGGER.error(e);
+ throw new RuntimeException(e);
}
- }, WORKERS);
- }
- return device;
- }
-
- @Override
- protected void closeBlockDevice() {
- // Join the copy job before releasing the device to avoid writes from thread to closed device.
- // Since we use memory mapped memory, closing the device leads to it holding a dead pointer,
- // meaning further access to it will hard-crash the JVM.
- joinCopyJob();
-
- super.closeBlockDevice();
- }
-
- ///////////////////////////////////////////////////////////////////
-
- private void joinCopyJob() {
- if (copyJob != null) {
- try {
- copyJob.get();
- } catch (final Throwable e) {
- LOGGER.error(e);
- } finally {
- copyJob = null;
}
- }
+ return device;
+ }, WORKERS);
}
}
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java
index 9717f8ae..5b0dbbd0 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java
@@ -56,22 +56,12 @@ public final class MemoryDevice extends IdentityProxy implements VMDe
return VMDeviceLoadResult.fail();
}
- context.getEventBus().register(this);
-
return VMDeviceLoadResult.success();
}
@Override
public void unmount() {
- if (device != null) {
- try {
- device.close();
- } catch (final Exception e) {
- LOGGER.error(e);
- }
-
- device = null;
- }
+ closeDevice();
if (blobHandle != null) {
BlobStorage.close(blobHandle);
@@ -126,9 +116,24 @@ public final class MemoryDevice extends IdentityProxy implements VMDe
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
device = new ByteBufferMemory(size, buffer);
} catch (final IOException e) {
+ LOGGER.error(e);
return false;
}
return true;
}
+
+ private void closeDevice() {
+ if (device == null) {
+ return;
+ }
+
+ try {
+ device.close();
+ } catch (final Exception e) {
+ LOGGER.error(e);
+ }
+
+ device = null;
+ }
}
diff --git a/src/main/java/li/cil/oc2/common/serialization/BlobStorage.java b/src/main/java/li/cil/oc2/common/serialization/BlobStorage.java
index 2deaaff9..2868e6e3 100644
--- a/src/main/java/li/cil/oc2/common/serialization/BlobStorage.java
+++ b/src/main/java/li/cil/oc2/common/serialization/BlobStorage.java
@@ -15,6 +15,7 @@ import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
/**
* This class facilitates storing binary chunks of data in an efficient, parallelized fashion.
@@ -52,7 +53,7 @@ public final class BlobStorage {
/**
* Closes all currently open blobs.
*/
- public static void close() {
+ public static synchronized void close() {
for (final FileChannel blob : BLOBS.values()) {
try {
blob.close();
@@ -98,7 +99,7 @@ public final class BlobStorage {
* @return the file channel for the requested blob.
* @throws IOException if opening the blob fails.
*/
- public static FileChannel getOrOpen(final UUID handle) throws IOException {
+ public static synchronized FileChannel getOrOpen(final UUID handle) throws IOException {
FileChannel blob = BLOBS.get(handle);
if (blob != null && blob.isOpen()) {
return blob;
@@ -115,7 +116,7 @@ public final class BlobStorage {
*
* @param handle the handle of the blob to close.
*/
- public static void close(final UUID handle) {
+ public static synchronized void close(final UUID handle) {
try {
final FileChannel blob = BLOBS.remove(handle);
if (blob != null) {
@@ -134,11 +135,13 @@ public final class BlobStorage {
public static void delete(final UUID handle) {
close(handle);
- try {
- final Path path = dataDirectory.resolve(handle.toString());
- Files.deleteIfExists(path);
- } catch (final Throwable e) {
- LOGGER.error(e);
- }
+ final Path path = dataDirectory.resolve(handle.toString());
+ CompletableFuture.runAsync(() -> {
+ try {
+ Files.deleteIfExists(path);
+ } catch (final Throwable e) {
+ LOGGER.error(e);
+ }
+ });
}
}
diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java
index 1fa210c8..584b3fc9 100644
--- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java
+++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java
@@ -70,7 +70,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
busController.onDevicesRemoved.add(this::handleDevicesRemoved);
state.board = new R5Board();
- state.context = new GlobalVMContext(state.board, this::joinWorkerThread);
+ state.context = new GlobalVMContext(state.board);
state.builtinDevices = new BuiltinDevices(state.context);
state.rpcAdapter = new RPCDeviceBusAdapter(state.builtinDevices.rpcSerialDevice);
state.vmAdapter = new VMDeviceBusAdapter(state.context);
@@ -171,13 +171,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
private void joinWorkerThread() {
if (runner != null) {
- try {
- runner.join();
- } catch (final Throwable e) {
- LOGGER.error(e);
- runner = null;
- setRunState(VMRunState.STOPPED);
- }
+ runner.join();
}
}
@@ -309,6 +303,8 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
return;
}
+ assert runner == null : "Runner active while still in load phase.";
+
final VMDeviceLoadResult loadResult = state.vmAdapter.mountDevices();
if (!loadResult.wasSuccessful()) {
if (loadResult.getErrorMessage() != null) {
@@ -410,10 +406,12 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
}
private void handleDevicesAdded(final CommonDeviceBusController.DevicesChangedEvent event) {
+ joinWorkerThread();
state.vmAdapter.addDevices(event.devices());
}
private void handleDevicesRemoved(final CommonDeviceBusController.DevicesChangedEvent event) {
+ joinWorkerThread();
state.vmAdapter.removeDevices(event.devices());
}
}
diff --git a/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java
index fdcfd43c..e0aa0ef4 100644
--- a/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java
+++ b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java
@@ -33,8 +33,6 @@ public final class VMDeviceBusAdapter {
}
public VMDeviceLoadResult mountDevices() {
- globalContext.joinWorkerThread();
-
for (final VMDevice device : unmountedDevices) {
final ManagedVMContext context = new ManagedVMContext(globalContext, globalContext,
() -> baseAddressProvider.getBaseAddress(device));
@@ -64,8 +62,6 @@ public final class VMDeviceBusAdapter {
}
public void unmountDevices() {
- globalContext.joinWorkerThread();
-
mountedDevices.forEach((device, context) -> {
device.unmount();
context.invalidate();
@@ -82,8 +78,6 @@ public final class VMDeviceBusAdapter {
}
public void addDevices(final Collection devices) {
- globalContext.joinWorkerThread();
-
for (final Device device : devices) {
if (device instanceof final VMDevice vmDevice) {
// Add to set of unmounted devices if we don't already track it. It's a set, so
@@ -96,8 +90,6 @@ public final class VMDeviceBusAdapter {
}
public void removeDevices(final Collection devices) {
- globalContext.joinWorkerThread();
-
for (final Device device : devices) {
if (device instanceof final VMDevice vmDevice) {
final ManagedVMContext context = mountedDevices.remove(vmDevice);
diff --git a/src/main/java/li/cil/oc2/common/vm/VMRunner.java b/src/main/java/li/cil/oc2/common/vm/VMRunner.java
index 0c2e3bc3..4f09b043 100644
--- a/src/main/java/li/cil/oc2/common/vm/VMRunner.java
+++ b/src/main/java/li/cil/oc2/common/vm/VMRunner.java
@@ -1,7 +1,10 @@
package li.cil.oc2.common.vm;
import li.cil.ceres.api.Serialized;
-import li.cil.oc2.api.bus.device.vm.event.*;
+import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
+import li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent;
+import li.cil.oc2.api.bus.device.vm.event.VMResumedRunningEvent;
+import li.cil.oc2.api.bus.device.vm.event.VMSynchronizeEvent;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.RPCDeviceBusAdapter;
import li.cil.oc2.common.vm.context.global.GlobalVMContext;
@@ -71,21 +74,17 @@ public class VMRunner implements Runnable {
}
}
- public void join() throws Throwable {
- context.postEvent(new VMPausingEvent());
- try {
- if (lastSchedule != null) {
- try {
- lastSchedule.get();
- } catch (final InterruptedException e) {
- // We do not mind this.
- } catch (final ExecutionException e) {
- throw e.getCause();
- }
+ public void join() {
+ context.postEvent(new VMSynchronizeEvent());
+ firedResumedRunningEvent = false;
+ if (lastSchedule != null) {
+ try {
+ lastSchedule.get();
+ } catch (final InterruptedException e) {
+ // We do not mind this.
+ } catch (final ExecutionException e) {
+ throw new RuntimeException(e.getCause());
}
- } finally {
- context.postEvent(new VMResumingRunningEvent());
- firedResumedRunningEvent = false;
}
}
diff --git a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java
index 73bee47b..aa58c0e7 100644
--- a/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java
+++ b/src/main/java/li/cil/oc2/common/vm/context/global/GlobalVMContext.java
@@ -19,7 +19,6 @@ public final class GlobalVMContext implements VMContext, VMContextManagerCollect
private final GlobalInterruptController interruptController;
private final GlobalMemoryAllocator memoryAllocator;
private final GlobalEventBus eventBus;
- private final Runnable joinWorkerThread;
///////////////////////////////////////////////////////////////////
@@ -38,14 +37,13 @@ public final class GlobalVMContext implements VMContext, VMContextManagerCollect
///////////////////////////////////////////////////////////////////
- public GlobalVMContext(final Board board, final Runnable joinWorkerThread) {
+ public GlobalVMContext(final Board board) {
this.memoryMap = new GlobalMemoryMap(board.getMemoryMap());
this.memoryRangeAllocator = new GlobalMemoryRangeAllocator(board, reservedMemoryRanges);
this.interruptAllocator = new GlobalInterruptAllocator(board.getInterruptCount(), reservedInterrupts);
this.interruptController = new GlobalInterruptController(board.getInterruptController(), interruptAllocator);
this.memoryAllocator = new GlobalMemoryAllocator();
this.eventBus = new GlobalEventBus();
- this.joinWorkerThread = joinWorkerThread;
}
///////////////////////////////////////////////////////////////////
@@ -98,11 +96,6 @@ public final class GlobalVMContext implements VMContext, VMContextManagerCollect
return eventBus;
}
- @Override
- public void joinWorkerThread() {
- joinWorkerThread.run();
- }
-
@Override
public InterruptManager getInterruptManager() {
return interruptAllocator;
diff --git a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java
index 27c1c926..174ac1d8 100644
--- a/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java
+++ b/src/main/java/li/cil/oc2/common/vm/context/managed/ManagedVMContext.java
@@ -9,7 +9,6 @@ import java.util.OptionalLong;
import java.util.function.Supplier;
public final class ManagedVMContext implements VMContext {
- private final VMContext parent;
private final ManagedMemoryMap memoryMap;
private final ManagedInterruptController interruptController;
private final ManagedMemoryRangeAllocator memoryRangeAllocator;
@@ -20,7 +19,6 @@ public final class ManagedVMContext implements VMContext {
///////////////////////////////////////////////////////////////////
public ManagedVMContext(final VMContext parent, final VMContextManagerCollection managers, final Supplier baseAddressSupplier) {
- this.parent = parent;
this.memoryRangeAllocator = new ManagedMemoryRangeAllocator(parent.getMemoryRangeAllocator(), managers.getMemoryRangeManager(), baseAddressSupplier);
this.interruptAllocator = new ManagedInterruptAllocator(parent.getInterruptAllocator(), managers.getInterruptManager());
this.memoryMap = new ManagedMemoryMap(parent.getMemoryMap());
@@ -76,9 +74,4 @@ public final class ManagedVMContext implements VMContext {
public VMLifecycleEventBus getEventBus() {
return eventBus;
}
-
- @Override
- public void joinWorkerThread() {
- parent.joinWorkerThread();
- }
}
diff --git a/src/test/java/li/cil/oc2/common/vm/AbstractTestMethod.java b/src/test/java/li/cil/oc2/common/bus/AbstractTestMethod.java
similarity index 97%
rename from src/test/java/li/cil/oc2/common/vm/AbstractTestMethod.java
rename to src/test/java/li/cil/oc2/common/bus/AbstractTestMethod.java
index e8262f99..81a5795a 100644
--- a/src/test/java/li/cil/oc2/common/vm/AbstractTestMethod.java
+++ b/src/test/java/li/cil/oc2/common/bus/AbstractTestMethod.java
@@ -1,4 +1,4 @@
-package li.cil.oc2.common.vm;
+package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.rpc.AbstractRPCMethod;
import li.cil.oc2.api.bus.device.rpc.RPCParameter;
diff --git a/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java b/src/test/java/li/cil/oc2/common/bus/RPCMethodTests.java
similarity index 98%
rename from src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java
rename to src/test/java/li/cil/oc2/common/bus/RPCMethodTests.java
index cb694648..d2133f24 100644
--- a/src/test/java/li/cil/oc2/common/vm/RPCAdapterTests.java
+++ b/src/test/java/li/cil/oc2/common/bus/RPCMethodTests.java
@@ -1,4 +1,4 @@
-package li.cil.oc2.common.vm;
+package li.cil.oc2.common.bus;
import com.google.gson.*;
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
@@ -7,7 +7,6 @@ import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.ObjectDevice;
import li.cil.oc2.api.bus.device.object.Parameter;
import li.cil.oc2.api.bus.device.rpc.*;
-import li.cil.oc2.common.bus.RPCDeviceBusAdapter;
import li.cil.sedna.api.device.serial.SerialDevice;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -25,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-public class RPCAdapterTests {
+public class RPCMethodTests {
private static final UUID DEVICE_UUID = java.util.UUID.randomUUID();
private TestSerialDevice serialDevice;
diff --git a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java
index 0d7c735e..cd9bd1e0 100644
--- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java
+++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java
@@ -54,8 +54,7 @@ public final class VMDeviceTests {
return null;
}).when(board).removeDevice(any());
- context = new GlobalVMContext(board, () -> {
- });
+ context = new GlobalVMContext(board);
adapter = new VMDeviceBusAdapter(context);
}
diff --git a/src/test/java/li/cil/oc2/common/vm/package-info.java b/src/test/java/li/cil/oc2/common/bus/package-info.java
similarity index 84%
rename from src/test/java/li/cil/oc2/common/vm/package-info.java
rename to src/test/java/li/cil/oc2/common/bus/package-info.java
index f415ab90..44244ffe 100644
--- a/src/test/java/li/cil/oc2/common/vm/package-info.java
+++ b/src/test/java/li/cil/oc2/common/bus/package-info.java
@@ -1,6 +1,6 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
-package li.cil.oc2.common.vm;
+package li.cil.oc2.common.bus;
import net.minecraft.MethodsReturnNonnullByDefault;