diff --git a/build.gradle b/build.gradle
index 1a6d0f66..c7b8588e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -97,8 +97,6 @@ minecraft {
source sourceSets.main
}
}
-
- arg "-mixin.config=oc2.mixins.json"
}
client {
@@ -119,6 +117,10 @@ minecraft {
mixin {
add sourceSets.main, "oc2.refmap.json"
+
+ config 'oc2.mixins.json'
+
+ quiet
}
task copyGeneratedResources(type: Copy) {
diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java
index 2badd061..5c6fdfc7 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/object/LifecycleAwareDevice.java
@@ -11,23 +11,20 @@ import net.minecraft.world.level.block.entity.BlockEntity;
*/
public interface LifecycleAwareDevice {
/**
- * This method corresponds to {@link RPCDevice#mount()}. It is called when the device is initialized, either
- * because its virtual machine starts running, or because it is added to a running virtual machine.
+ * This method corresponds to {@link RPCDevice#mount()}.
*/
default void onDeviceMounted() {
}
/**
- * This method corresponds to {@link RPCDevice#unmount()}. It is called when the device is disposed, either
- * because its virtual machine stops running, or because it is removed from a running virtual machine.
+ * This method corresponds to {@link RPCDevice#unmount()}.
*/
default void onDeviceUnmounted() {
}
/**
- * This method corresponds to {@link RPCDevice#suspend()}. It is called when its virtual machine is suspended,
- * either due to the containing chunk being unloaded, or the containing world being unloaded.
+ * This method corresponds to {@link RPCDevice#dispose()}.
*/
- default void onDeviceSuspended() {
+ default void onDeviceDisposed() {
}
}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java
index e5f1885e..afda5cd5 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/object/ObjectDevice.java
@@ -102,9 +102,9 @@ public final class ObjectDevice implements RPCDevice {
}
@Override
- public void suspend() {
+ public void dispose() {
if (object instanceof LifecycleAwareDevice device) {
- device.onDeviceSuspended();
+ device.onDeviceDisposed();
}
}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java
index f650d71c..f35f45d4 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java
@@ -20,35 +20,27 @@ import java.util.List;
*
getMethodGroups();
/**
- * Called to initialize this device.
+ * Called to start this device.
*
* This is called when the connected virtual machine starts, or when the device is added to an already running
* virtual machine.
@@ -87,22 +79,28 @@ public interface RPCDevice extends Device {
}
/**
- * Called to dispose this device.
+ * Called to stop this device.
*
- * Called when the connected virtual machine stops, or when the device is removed from a currently running
- * virtual machine.
+ * Called when the connected virtual machine stops, the device is removed from a currently running
+ * virtual machine, or the connected virtual machine is suspended (chunk unload/server stopped/...).
+ *
+ * If {@link #mount()} was called, this is guaranteed to be called.
*/
default void unmount() {
}
/**
- * Called when the device is suspended.
+ * Called to dispose this device.
*
- * This can happen when the level area containing the context the device was loaded in is unloaded,
- * e.g. due to player moving too far away from the area.
+ * Called when the connected virtual machine stops or the device is removed from a currently running
+ * virtual machine.
*
- * Intended for soft-releasing unmanaged resource, i.e. non-persisted unmanaged resources.
+ * Will only be called on unmounted devices (i.e. will always be called after {@link #unmount()} if
+ * {@link #mount()} was called before). May be called without intermediary {@link #mount()} calls, e.g.
+ * virtual machine stops, then device is disconnected from the virtual machine.
+ *
+ * Intended for releasing persistent unmanaged resources.
*/
- default void suspend() {
+ default void dispose() {
}
}
diff --git a/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java
index bd72d899..2b537f88 100644
--- a/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java
+++ b/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java
@@ -36,7 +36,7 @@ public final class RPCDeviceBusAdapter implements Steppable {
private final SerialDevice serialDevice;
private final Gson gson;
- private final ArrayList devices = new ArrayList<>();
+ private final ArrayList devicesWithId = new ArrayList<>();
private final HashMap devicesById = new HashMap<>();
private final Set unmountedDevices = new HashSet<>();
private final Set mountedDevices = new HashSet<>();
@@ -82,14 +82,18 @@ public final class RPCDeviceBusAdapter implements Steppable {
for (final RPCDevice device : mountedDevices) {
device.unmount();
}
- unmountedDevices.addAll(mountedDevices);
- mountedDevices.clear();
}
- public void suspend() {
- for (final RPCDeviceWithIdentifier info : devices) {
- info.device.suspend();
+ public void dispose() {
+ for (final RPCDevice device : mountedDevices) {
+ device.unmount();
+ device.dispose();
}
+ for (final RPCDeviceList device : unmountedDevices) {
+ device.dispose();
+ }
+ unmountedDevices.addAll(mountedDevices);
+ mountedDevices.clear();
}
public void reset() {
@@ -115,10 +119,6 @@ public final class RPCDeviceBusAdapter implements Steppable {
return;
}
- devices.clear();
- devicesById.clear();
- unmountedDevices.clear();
-
// How device grouping works:
// Each device can have multiple UUIDs due to being attached to multiple bus elements.
// There is no guarantee that for each device D1 present on bus elements E1 and E2,
@@ -162,31 +162,38 @@ public final class RPCDeviceBusAdapter implements Steppable {
.add(identifier);
});
- final Set newDevices = new HashSet<>();
+ // Rebuild devices lists.
+ devicesWithId.clear();
+ devicesById.clear();
+
+ final Set devices = new HashSet<>();
identifiersByDevice.forEach((device, identifiers) -> {
final UUID identifier = selectIdentifierDeterministically(identifiers);
- devices.add(new RPCDeviceWithIdentifier(identifier, device));
+ devicesWithId.add(new RPCDeviceWithIdentifier(identifier, device));
devicesById.put(identifier, device);
- newDevices.add(device);
+ devices.add(device);
+
+ // Add to set of unmounted devices if we don't already track it. It's a set, so
+ // there won't be duplicates in the unmounted set due to this.
+ if (!mountedDevices.contains(device)) {
+ unmountedDevices.add(device);
+ }
});
- // Add new devices to list of unmounted devices. List was cleared, so removed devices previously in
- // list of unmounted devices are already gone.
- for (final RPCDeviceList newDevice : newDevices) {
- if (!mountedDevices.contains(newDevice)) {
- unmountedDevices.add(newDevice);
- }
- }
+ // Remove devices from mounted set, call appropriate callbacks.
+ final HashSet removedMountedDevices = new HashSet<>(mountedDevices);
+ removedMountedDevices.removeAll(devices);
+ mountedDevices.removeAll(removedMountedDevices);
+ removedMountedDevices.forEach(device -> {
+ device.unmount();
+ device.dispose();
+ });
- // Remove removed devices from list of mounted devices.
- final Iterator mountedDeviceIterator = mountedDevices.iterator();
- while (mountedDeviceIterator.hasNext()) {
- final RPCDeviceList device = mountedDeviceIterator.next();
- if (!newDevices.contains(device)) {
- device.unmount();
- mountedDeviceIterator.remove();
- }
- }
+ // Remove devices from unmounted set, call appropriate callbacks.
+ final HashSet removedUnmountedDevices = new HashSet<>(unmountedDevices);
+ removedUnmountedDevices.removeAll(devices);
+ unmountedDevices.removeAll(removedUnmountedDevices);
+ removedUnmountedDevices.forEach(RPCDeviceList::dispose);
}
public void tick() {
@@ -355,7 +362,7 @@ public final class RPCDeviceBusAdapter implements Steppable {
}
private void writeDeviceList() {
- writeMessage(Message.MESSAGE_TYPE_LIST, devices);
+ writeMessage(Message.MESSAGE_TYPE_LIST, devicesWithId);
}
private void writeDeviceMethods(final UUID deviceId) {
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractItemRPCDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractItemRPCDevice.java
index 9293f412..17ef1558 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractItemRPCDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractItemRPCDevice.java
@@ -42,7 +42,7 @@ public abstract class AbstractItemRPCDevice extends IdentityProxy imp
}
@Override
- public void suspend() {
- device.suspend();
+ public void dispose() {
+ device.dispose();
}
}
diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/FileImportExportCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/FileImportExportCardItemDevice.java
index 666f15ed..0a59c03f 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/item/FileImportExportCardItemDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/item/FileImportExportCardItemDevice.java
@@ -134,7 +134,7 @@ public final class FileImportExportCardItemDevice extends AbstractItemRPCDevice
///////////////////////////////////////////////////////////////////
@Override
- public void suspend() {
+ public void unmount() {
reset();
}
diff --git a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCDeviceList.java b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCDeviceList.java
index d0e9cd16..087680b9 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCDeviceList.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCDeviceList.java
@@ -42,9 +42,9 @@ public record RPCDeviceList(ArrayList devices) implements RPCDevice {
}
@Override
- public void suspend() {
+ public void dispose() {
for (final RPCDevice device : devices) {
- device.suspend();
+ device.dispose();
}
}
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 a874f452..0e37586c 100644
--- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java
+++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java
@@ -91,7 +91,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
public void suspend() {
joinWorkerThread();
state.vmAdapter.suspend();
- state.rpcAdapter.suspend();
+ state.rpcAdapter.unmount();
}
@Override
@@ -203,7 +203,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
state.board.setRunning(false);
state.board.reset();
state.rpcAdapter.reset();
- state.rpcAdapter.unmount();
+ state.rpcAdapter.dispose();
state.vmAdapter.unmount();
runner = null;
diff --git a/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java
index 9ad35b03..c3be390d 100644
--- a/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java
+++ b/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java
@@ -30,134 +30,94 @@ public final class RPCDeviceTests {
}
@Test
- public void emptyDevicesAreNotMounted() {
- final RPCDevice device1 = mock(RPCDevice.class);
- addDevice(device1);
+ public void resumeDoesNotMountDirectly() {
+ final RPCDevice device1 = addDevice();
adapter.resume(controller, true);
- adapter.mount();
verify(device1, never()).mount();
}
+ @Test
+ public void emptyDevicesAreNotMounted() {
+ final RPCDevice device = addEmptyDevice();
+ adapter.resume(controller, true);
+
+ adapter.mount();
+ verify(device, never()).mount();
+ }
+
@Test
public void addedDevicesHaveMountCalled() {
- final RPCDevice device1 = mock(RPCDevice.class);
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(device1);
-
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
adapter.mount();
- verify(device1).mount();
+ verify(device).mount();
}
@Test
- public void mountedDevicesAreUnmountedWhenRemoved() {
- final RPCDevice device1 = mock(RPCDevice.class);
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(device1);
-
+ public void mountedDevicesAreUnmountedAndDisposedWhenRemoved() {
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
-
adapter.mount();
- verify(device1).mount();
- removeDevice(device1);
+ removeDevice(device);
adapter.resume(controller, true);
- verify(device1).unmount();
+ verify(device).unmount();
+ verify(device).dispose();
}
@Test
- public void mountedDevicesAreUnmountedOnGlobalUnmount() {
- final RPCDevice device1 = mock(RPCDevice.class);
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(device1);
-
+ public void unmountedDevicesAreDisposedWhenRemoved() {
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
+ removeDevice(device);
+ adapter.resume(controller, true);
+ verify(device, never()).unmount();
+ verify(device).dispose();
+ }
+
+ @Test
+ public void mountedDevicesAreUnmountedButNotDisposedOnGlobalUnmount() {
+ final RPCDevice device = addDevice();
+ adapter.resume(controller, true);
adapter.mount();
- verify(device1).mount();
adapter.unmount();
- verify(device1).unmount();
+ verify(device).unmount();
+ verify(device, never()).dispose();
}
@Test
- public void unmountedDevicesAreNotUnmountedOnGlobalUnmount() {
- final RPCDevice device1 = mock(RPCDevice.class);
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(device1);
-
+ public void unmountedDevicesAreNotUnmountedAndNotDisposedOnGlobalUnmount() {
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
adapter.unmount();
- verify(device1, never()).unmount();
+ verify(device, never()).unmount();
+ verify(device, never()).dispose();
}
@Test
- public void deviceListForwardsMount() {
- final RPCDevice device1 = mock(RPCDevice.class);
- final RPCDevice device2 = mock(RPCDevice.class);
- final RPCDevice listDevice = new RPCDeviceList(new ArrayList<>(Arrays.asList(device1, device2)));
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- when(device2.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(listDevice);
-
+ public void mountedDevicesAreUnmountedAndDisposedOnGlobalDispose() {
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
- verify(device2, never()).mount();
-
adapter.mount();
- verify(device1).mount();
- verify(device2).mount();
+
+ adapter.dispose();
+ verify(device).unmount();
+ verify(device).dispose();
}
@Test
- public void deviceListForwardsUnmount() {
- final RPCDevice device1 = mock(RPCDevice.class);
- final RPCDevice device2 = mock(RPCDevice.class);
- final RPCDevice listDevice = new RPCDeviceList(new ArrayList<>(Arrays.asList(device1, device2)));
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- when(device2.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(listDevice);
-
+ public void unmountedDevicesAreNotUnmountedButDisposedOnGlobalDispose() {
+ final RPCDevice device = addDevice();
adapter.resume(controller, true);
- verify(device1, never()).mount();
- verify(device2, never()).mount();
- adapter.mount();
- verify(device1).mount();
- verify(device2).mount();
-
- adapter.unmount();
- verify(device1).unmount();
- verify(device2).unmount();
- }
-
- @Test
- public void deviceListForwardsSuspend() {
- final RPCDevice device1 = mock(RPCDevice.class);
- final RPCDevice device2 = mock(RPCDevice.class);
- final RPCDevice listDevice = new RPCDeviceList(new ArrayList<>(Arrays.asList(device1, device2)));
- when(device1.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- when(device2.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
- addDevice(listDevice);
-
- adapter.resume(controller, true);
- verify(device1, never()).mount();
- verify(device2, never()).mount();
-
- adapter.mount();
- verify(device1).mount();
- verify(device2).mount();
-
- adapter.suspend();
- verify(device1).suspend();
- verify(device2).suspend();
+ adapter.dispose();
+ verify(device, never()).unmount();
+ verify(device).dispose();
}
@Test
@@ -187,6 +147,19 @@ public final class RPCDeviceTests {
verify(device2, atMostOnce()).mount();
}
+ private RPCDevice addEmptyDevice() {
+ final RPCDevice device = mock(RPCDevice.class);
+ addDevice(device);
+ return device;
+ }
+
+ private RPCDevice addDevice() {
+ final RPCDevice device = mock(RPCDevice.class);
+ when(device.getMethodGroups()).thenReturn(Collections.singletonList(mock(RPCMethod.class)));
+ addDevice(device);
+ return device;
+ }
+
private void addDevice(final Device device, UUID... identifiers) {
if (identifiers.length == 0) {
identifiers = new UUID[]{UUID.randomUUID()};