From ab4d0ad3c33fae085ad4cd805d91df48ac892dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 19 Jul 2021 23:38:29 +0200 Subject: [PATCH] Rename load/unload to mount/unmount, add suspend for temporary unload (e.g. containing chunk unload). --- .../cil/oc2/api/bus/device/vm/VMDevice.java | 18 ++++- .../api/bus/device/vm/VMDeviceLoadResult.java | 2 +- .../device/vm/context/InterruptAllocator.java | 2 +- .../vm/context/MemoryRangeAllocator.java | 2 +- .../api/bus/device/vm/context/VMContext.java | 8 +- .../device/vm/event/VMInitializingEvent.java | 4 +- .../vm/event/VMResumingRunningEvent.java | 2 +- .../item/AbstractBlockDeviceVMDevice.java | 15 ++-- .../item/ByteBufferFlashMemoryVMDevice.java | 13 +++- .../item/FirmwareFlashMemoryVMDevice.java | 9 ++- .../common/bus/device/item/MemoryDevice.java | 12 ++- .../item/NetworkInterfaceCardItemDevice.java | 11 ++- .../li/cil/oc2/common/entity/RobotEntity.java | 12 +-- .../common/tileentity/ComputerTileEntity.java | 4 +- .../oc2/common/vm/AbstractVirtualMachine.java | 6 +- .../cil/oc2/common/vm/VMDeviceBusAdapter.java | 36 +++++---- .../li/cil/oc2/common/bus/VMDeviceTests.java | 78 +++++++++---------- 17 files changed, 138 insertions(+), 96 deletions(-) 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()); }