diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java
index 7c806ca7..abe87828 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java
@@ -15,7 +15,7 @@ import li.cil.sedna.api.device.MemoryMappedDevice;
* in the guest system.
*
* To listen to lifecycle events of the VM and the device, register to the event
- * bus provided via {@link VMContext#getEventBus()} in {@link #load(VMContext)}.
+ * bus provided via {@link VMContext#getEventBus()} in {@link #mount(VMContext)}.
*
* @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider
* @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider
@@ -35,7 +35,7 @@ public interface VMDevice extends Device {
* @param context the virtual machine context.
* @return {@code true} if the device was loaded successfully; {@code false} otherwise.
*/
- VMDeviceLoadResult load(VMContext context);
+ VMDeviceLoadResult mount(VMContext context);
/**
* Called when the device is removed from the context it was loaded with.
@@ -43,7 +43,17 @@ public interface VMDevice extends Device {
* This can happen because the VM was stopped or the device was removed from
* the device bus that connected it to the VM, for example.
*
- * Intended for releasing resources acquired in {@link #load(VMContext)}.
+ * Intended for releasing resources acquired in {@link #mount(VMContext)}.
*/
- void unload();
+ void unmount();
+
+ /**
+ * Called when the device is suspended.
+ *
+ * This can happen when the world area containing the context the device was loaded in is unloaded,
+ * e.g. due to player moving too far away from the area.
+ *
+ * Intended for soft-releasing resources acquired in {@link #mount(VMContext)}.
+ */
+ void suspend();
}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java
index c8775e11..ab2775f2 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDeviceLoadResult.java
@@ -6,7 +6,7 @@ import net.minecraft.util.text.ITextComponent;
import javax.annotation.Nullable;
/**
- * {@link VMDevice}s may signal the result of their {@link VMDevice#load(VMContext)} operations.
+ * {@link VMDevice}s may signal the result of their {@link VMDevice#mount(VMContext)} operations.
*/
public final class VMDeviceLoadResult {
/**
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java
index 9d81f9c5..097b9daf 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/InterruptAllocator.java
@@ -7,7 +7,7 @@ import java.util.OptionalInt;
/**
* Allows reserving interrupts on the primary interrupt controller of a virtual machine
- * during a {@link VMDevice#load(VMContext)} call.
+ * during a {@link VMDevice#mount(VMContext)} call.
*
* Allocated interrupts should be persisted and used in {@link #claimInterrupt(int)}
* when restoring from a saved state to ensure correct behaviour of the loaded virtual
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java
index 53a6d44c..9fac0733 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/context/MemoryRangeAllocator.java
@@ -7,7 +7,7 @@ import java.util.OptionalLong;
/**
* Allows adding {@link MemoryMappedDevice}s to the memory map of a virtual machine
- * during a {@link VMDevice#load(VMContext)} call.
+ * during a {@link VMDevice#mount(VMContext)} call.
*
* Allocated addresses should be persisted and used in {@link #claimMemoryRange(long, MemoryMappedDevice)}
* when restoring from a saved state to ensure correct behaviour of the loaded virtual
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 4cc4655b..d562dbf5 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
@@ -40,7 +40,7 @@ public interface VMContext {
/**
* Allows adding {@link MemoryMappedDevice}s to the VM's {@link MemoryMap}.
*
- * {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#load(VMContext)}.
+ * {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#mount(VMContext)}.
* Trying to add devices after that method has returned will result in an exception.
*
* Added devices will be automatically removed when the {@link VMDevice} that added it
@@ -54,7 +54,7 @@ public interface VMContext {
/**
* Allows claiming interrupts for use with the VM's {@link InterruptController}.
*
- * Interrupts can only be claimed inside {@link VMDevice#load(VMContext)}.
+ * Interrupts can only be claimed inside {@link VMDevice#mount(VMContext)}.
* Trying to claim interrupts after that method has returned will result in an exception.
*
* Claimed interrupts will automatically be released when the {@link VMDevice} that
@@ -73,7 +73,7 @@ public interface VMContext {
* running VMs.
*
* Devices failing to reserve the memory they would use should fail their
- * {@link VMDevice#load(VMContext)}.
+ * {@link VMDevice#mount(VMContext)}.
*
* Memory will automatically be released when the {@link VMDevice} that claimed
* it is unloaded, e.g. because it is removed from the {@link DeviceBus} or the
@@ -97,7 +97,7 @@ public interface VMContext {
/**
* Waits for the executor thread of the virtual machine to finish running.
*
- * Events subscribers can only be registered inside {@link VMDevice#load(VMContext)}.
+ * Events subscribers can only be registered inside {@link VMDevice#mount(VMContext)}.
* Trying to register subscribers after that method has returned will result in an
* exception.
*
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 737d6d76..48e7b125 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
@@ -6,7 +6,7 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
/**
* Fired exactly once, when the VM first starts running.
*
- * Fired after all devices reported success from {@link VMDevice#load(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
@@ -14,7 +14,7 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
*
* Listeners of this event may throw a {@link VMInitializationException} in case
* initialization fails. For some devices it may be too costly to perform a full
- * validity check in {@link VMDevice#load(VMContext)}. These devices may still cause
+ * validity check in {@link VMDevice#mount(VMContext)}. These devices may still cause
* a startup to fail this way.
*
* This is invoked from the worker thread running the VM.
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
index 970d251d..061bef2f 100644
--- 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
@@ -6,7 +6,7 @@ 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#load(VMContext)}.
+ * 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
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 2130f054..29f36944 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
@@ -55,7 +55,7 @@ public abstract class AbstractBlockDeviceVMDevice
///////////////////////////////////////////////////////////////
@Override
- public VMDeviceLoadResult load(final VMContext context) {
+ public VMDeviceLoadResult mount(final VMContext context) {
memoryMap = context.getMemoryMap();
context.getEventBus().register(this);
@@ -39,7 +39,12 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy
}
@Override
- public void unload() {
+ public void unmount() {
+ suspend();
+ }
+
+ @Override
+ public void suspend() {
memoryMap = null;
}
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 198b2e15..52fb7bca 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
@@ -45,7 +45,7 @@ public final class MemoryDevice extends IdentityProxy implements VMDe
///////////////////////////////////////////////////////////////////
@Override
- public VMDeviceLoadResult load(final VMContext context) {
+ public VMDeviceLoadResult mount(final VMContext context) {
if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
@@ -62,16 +62,22 @@ public final class MemoryDevice extends IdentityProxy implements VMDe
}
@Override
- public void unload() {
+ public void unmount() {
+ suspend();
+
// Memory is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
jobHandle = null;
- device = null;
address.clear();
}
+ @Override
+ public void suspend() {
+ device = null;
+ }
+
@Subscribe
public void handleResumingRunningEvent(final VMResumingRunningEvent event) {
awaitStorageOperation();
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java
index 25932b8d..dce1aaed 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java
@@ -60,7 +60,7 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy {
- if (context != null) {
- context.invalidate();
- }
- });
+ for (final VMDevice device : deviceContexts.keySet()) {
+ device.suspend();
+ }
- incompleteLoads.clear();
- incompleteLoads.addAll(deviceContexts.keySet());
+ unload();
}
public void addDevices(final Collection devices) {
@@ -100,7 +97,7 @@ public final class VMDeviceBusAdapter {
if (device instanceof VMDevice) {
final VMDevice vmDevice = (VMDevice) device;
- vmDevice.unload();
+ vmDevice.unmount();
final ManagedVMContext context = deviceContexts.remove(vmDevice);
if (context != null) {
@@ -111,4 +108,17 @@ public final class VMDeviceBusAdapter {
}
}
}
+
+ ///////////////////////////////////////////////////////////////////
+
+ private void unload() {
+ deviceContexts.forEach((device, context) -> {
+ if (context != null) {
+ context.invalidate();
+ }
+ });
+
+ incompleteLoads.clear();
+ incompleteLoads.addAll(deviceContexts.keySet());
+ }
}
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 b2552230..5c73a9f2 100644
--- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java
+++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java
@@ -63,64 +63,64 @@ public final class VMDeviceTests {
public void addedDevicesHaveLoadCalled() {
final VMDevice device1 = mock(VMDevice.class);
final VMDevice device2 = mock(VMDevice.class);
- when(device1.load(any())).thenReturn(VMDeviceLoadResult.success());
- when(device2.load(any())).thenReturn(VMDeviceLoadResult.success());
+ when(device1.mount(any())).thenReturn(VMDeviceLoadResult.success());
+ when(device2.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device1));
- assertTrue(adapter.load().wasSuccessful());
- verify(device1).load(any());
+ assertTrue(adapter.mount().wasSuccessful());
+ verify(device1).mount(any());
adapter.addDevices(Collections.singleton(device2));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
verifyNoMoreInteractions(device1);
- verify(device2).load(any());
+ verify(device2).mount(any());
}
@Test
public void removedDevicesHaveUnloadCalled() {
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
+ when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
adapter.removeDevices(Collections.singleton(device));
- verify(device).unload();
+ verify(device).unmount();
}
@Test
public void devicesHaveUnloadCalledOnGlobalUnload() {
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
+ when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
- adapter.unload();
- verify(device).unload();
+ adapter.unmount();
+ verify(device).unmount();
}
@Test
public void devicesHaveLoadCalledAfterGlobalUnload() {
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
+ when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
- verify(device).load(any());
+ assertTrue(adapter.mount().wasSuccessful());
+ verify(device).mount(any());
- adapter.unload();
- verify(device).unload();
+ adapter.unmount();
+ verify(device).unmount();
- assertTrue(adapter.load().wasSuccessful());
- verify(device, times(2)).load(any());
+ assertTrue(adapter.mount().wasSuccessful());
+ verify(device, times(2)).mount(any());
}
@Test
public void deviceCanClaimInterrupts() {
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -128,9 +128,9 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
- verify(device).load(any());
+ verify(device).mount(any());
}
@Test
@@ -138,7 +138,7 @@ public final class VMDeviceTests {
final int claimedInterrupt = 1;
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final boolean result = context.getInterruptAllocator().claimInterrupt(claimedInterrupt);
assertFalse(result);
@@ -148,14 +148,14 @@ public final class VMDeviceTests {
context.getInterruptAllocator().claimInterrupt(claimedInterrupt);
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
}
@Test
public void deviceCanRaiseClaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -167,7 +167,7 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
final int claimedInterruptMask = 1 << deviceData.interrupt;
deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask);
@@ -179,13 +179,13 @@ public final class VMDeviceTests {
public void devicesCannotRaiseUnclaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
deviceData.context = invocation.getArgument(0);
return VMDeviceLoadResult.success();
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
final int someInterruptMask = 0x1;
assertThrows(IllegalArgumentException.class, () ->
@@ -196,7 +196,7 @@ public final class VMDeviceTests {
public void unloadLowersClaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -208,14 +208,14 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
final int claimedInterruptMask = 1 << deviceData.interrupt;
deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask);
assertTrue((interruptController.getRaisedInterrupts() & claimedInterruptMask) != 0);
- adapter.unload();
+ adapter.unmount();
assertFalse((interruptController.getRaisedInterrupts() & claimedInterruptMask) != 0);
}
@@ -223,7 +223,7 @@ public final class VMDeviceTests {
@Test
public void devicesCannotAddToMemoryMapDirectly() {
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
assertThrows(UnsupportedOperationException.class, () ->
@@ -233,14 +233,14 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- adapter.load();
+ adapter.mount();
}
@Test
public void devicesCanAddMemoryMappedDevices() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
deviceData.context = context;
@@ -253,7 +253,7 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
}
@@ -262,7 +262,7 @@ public final class VMDeviceTests {
public void addedDevicesGetRemovedOnUnload() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
- when(device.load(any())).thenAnswer(invocation -> {
+ when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
deviceData.context = context;
@@ -275,11 +275,11 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
- assertTrue(adapter.load().wasSuccessful());
+ assertTrue(adapter.mount().wasSuccessful());
assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
- adapter.unload();
+ adapter.unmount();
assertFalse(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
}