Made vm lifecycle events use an event bus and event classes.

Allows passing along program start so that firmware doesn't have to hardcode this.
Also made unload a method instead of an event, as it needs to be called on individual objects anyway; previous implementation was a little awkward.
This commit is contained in:
Florian Nücke
2021-01-22 21:05:49 +01:00
parent f92e94797e
commit c101a7ab9a
27 changed files with 358 additions and 229 deletions

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.api.bus.device.data;
import li.cil.oc2.api.API;
import li.cil.oc2.api.bus.device.vm.event.VMInitializationException;
import li.cil.sedna.api.memory.MemoryMap;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
@@ -34,7 +35,7 @@ public interface Firmware extends IForgeRegistryEntry<Firmware> {
* @param memory access to the memory map of the machine.
* @param startAddress the memory address where execution will commence.
*/
void run(final MemoryMap memory, final long startAddress);
boolean run(final MemoryMap memory, final long startAddress);
/**
* The display name of this firmware. May be shown in the tooltip of item devices

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.api.bus.device.vm;
import li.cil.oc2.api.bus.DeviceBus;
import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus;
import li.cil.sedna.api.device.InterruptController;
import li.cil.sedna.api.device.MemoryMappedDevice;
import li.cil.sedna.api.memory.MemoryMap;
@@ -81,4 +82,15 @@ public interface VMContext {
* @return the memory allocator.
*/
MemoryAllocator getMemoryAllocator();
/**
* Allows registering to VM lifecycle events.
* <p>
* Registered subscribers will automatically be unsubscribed when the {@link VMDevice}
* that registered them is unloaded, e.g. because it is removed from the {@link DeviceBus}
* of the VM stopped.
*
* @return the event bus.
*/
VMLifecycleEventBus getEventBus();
}

View File

@@ -12,11 +12,9 @@ import li.cil.sedna.api.device.MemoryMappedDevice;
* implemented through this interface will require explicit driver support
* in the guest system.
* <p>
* To listen to lifecycle events of the VM and the device, implement the
* {@link VMDeviceLifecycleListener} interface. This is particularly useful
* for releasing unmanaged resources acquired in {@link #load(VMContext)}.
* 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)}.
*
* @see VMDeviceLifecycleListener
* @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider
* @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider
*/
@@ -36,4 +34,14 @@ public interface VMDevice extends Device {
* @return {@code true} if the device was loaded successfully; {@code false} otherwise.
*/
VMDeviceLoadResult load(VMContext context);
/**
* Called when the device is removed from the context it was loaded with.
* <p>
* 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.
* <p>
* Intended for releasing resources acquired in {@link #load(VMContext)}.
*/
void unload();
}

View File

@@ -1,57 +0,0 @@
package li.cil.oc2.api.bus.device.vm;
public enum VMDeviceLifecycleEventType {
/**
* Fired exactly once, when the VM first starts running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* <p>
* 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.
* <p>
* <em>This is invoked from the worker thread running the VM.</em>
*/
INITIALIZING,
/**
* Fired when the VM is paused, typically before state is persisted.
* <p>
* Allows devices that offer interaction to external code-flow to suspend
* such interactions until {@link #RESUMED_RUNNING} is fired. This is required
* if such interactions may modify VM state, to prevent corrupting data being
* serialized asynchronously.
*/
PAUSING,
/**
* Fired when the VM resumes running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* <p>
* 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.
*/
RESUME_RUNNING,
/**
* Fired when the VM resumed running.
* <p>
* Fired after {@link #RESUME_RUNNING} has been fired and handled by all devices.
* <p>
* Allows device initialization that relies on all other devices having fully loaded.
* <p>
* Typically this is used in combination with {@link #PAUSING}, to re-enable external
* interactions after VM state is guaranteed to be safe to modify again.
*/
RESUMED_RUNNING,
/**
* Fired when the device is disposed, either because the VM is disposed or the source
* of the device is disconnected / removed from the current VM.
* <p>
* Intended for releasing resources acquired in {@link VMDevice#load(VMContext)}.
*/
UNLOAD,
}

View File

@@ -1,18 +0,0 @@
package li.cil.oc2.api.bus.device.vm;
/**
* Specialization of {@link VMDevice} for devices that require additional
* lifecycle events to function correctly.
* <p>
* A typical use case is to add custom synchronization points for asynchronous
* operations, such as loading blobs, and for disposing unmanaged resources
* acquired in {@link VMDevice#load(VMContext)}.
*/
public interface VMDeviceLifecycleListener extends VMDevice {
/**
* Called to notify the device of a lifecycle event.
*
* @param event the type of the event.
*/
void handleLifecycleEvent(VMDeviceLifecycleEventType event);
}

View File

@@ -0,0 +1,21 @@
package li.cil.oc2.api.bus.device.vm.event;
import net.minecraft.util.text.ITextComponent;
import java.util.Optional;
public final class VMInitializationException extends Exception {
private final ITextComponent message;
public VMInitializationException(final ITextComponent message) {
this.message = message;
}
public VMInitializationException() {
this.message = null;
}
public Optional<ITextComponent> getErrorMessage() {
return Optional.ofNullable(message);
}
}

View File

@@ -0,0 +1,44 @@
package li.cil.oc2.api.bus.device.vm.event;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.VMDevice;
/**
* Fired exactly once, when the VM first starts running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* <p>
* 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.
* <p>
* 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
* a startup to fail this way.
* <p>
* <em>This is invoked from the worker thread running the VM.</em>
*/
public final class VMInitializingEvent extends VMLifecycleEvent {
private final long programStartAddress;
///////////////////////////////////////////////////////////////
public VMInitializingEvent(final long programStartAddress) {
this.programStartAddress = programStartAddress;
}
///////////////////////////////////////////////////////////////
/**
* The address where code execution will begin.
* <p>
* Some VM implementations may perform some early setup before jumping to this
* memory address.
*
* @return the memory address where code execution begins.
*/
public long getProgramStartAddress() {
return programStartAddress;
}
}

View File

@@ -0,0 +1,4 @@
package li.cil.oc2.api.bus.device.vm.event;
public class VMLifecycleEvent {
}

View File

@@ -0,0 +1,7 @@
package li.cil.oc2.api.bus.device.vm.event;
public interface VMLifecycleEventBus {
void register(Object object);
void unregister(Object object);
}

View File

@@ -0,0 +1,12 @@
package li.cil.oc2.api.bus.device.vm.event;
/**
* Fired when the VM is paused, typically before state is persisted.
* <p>
* Allows devices that offer interaction to external code-flow to suspend
* such interactions until {@link VMResumedRunningEvent} is fired. This is required
* if such interactions may modify VM state, to prevent corrupting data being
* serialized asynchronously.
*/
public final class VMPausingEvent extends VMLifecycleEvent {
}

View File

@@ -0,0 +1,14 @@
package li.cil.oc2.api.bus.device.vm.event;
/**
* Fired when the VM resumed running.
* <p>
* Fired after {@link VMResumingRunningEvent} has been fired and handled by all devices.
* <p>
* Allows device initialization that relies on all other devices having fully loaded.
* <p>
* Typically this is used in combination with {@link VMPausingEvent}, to re-enable external
* interactions after VM state is guaranteed to be safe to modify again.
*/
public final class VMResumedRunningEvent extends VMLifecycleEvent {
}

View File

@@ -0,0 +1,16 @@
package li.cil.oc2.api.bus.device.vm.event;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.VMDevice;
/**
* Fired when the VM resumes running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* <p>
* 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 extends VMLifecycleEvent {
}

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.api.bus.device.vm.event;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -67,7 +67,8 @@ public final class Constants {
public static final String COMPUTER_SCREEN_POWER_CAPTION = "gui.oc2.computer.power.capt";
public static final String COMPUTER_SCREEN_POWER_DESCRIPTION = "gui.oc2.computer.power.desc";
public static final String COMPUTER_BOOT_ERROR_UNKNOWN = "gui.oc2.computer.boot_error.unknown";
public static final String COMPUTER_BOOT_ERROR_NO_MEMORY = "gui.oc2.computer.boot_error.no_memory";
public static final String COMPUTER_BOOT_ERROR_MISSING_FIRMWARE = "gui.oc2.computer.boot_error.missing_firmware";
public static final String COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY = "gui.oc2.computer.boot_error.insufficient_memory";
public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete";
public static final String COMPUTER_BUS_STATE_TOO_COMPLEX = "gui.oc2.computer.bus_state.too_complex";
public static final String COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS = "gui.oc2.computer.bus_state.multiple_controllers";

View File

@@ -7,21 +7,18 @@ import li.cil.sedna.memory.MemoryMaps;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.registries.ForgeRegistryEntry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public final class BuildrootFirmware extends ForgeRegistryEntry<Firmware> implements Firmware {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public void run(final MemoryMap memory, final long startAddress) {
public boolean run(final MemoryMap memory, final long startAddress) {
try {
MemoryMaps.store(memory, startAddress, Buildroot.getFirmware());
MemoryMaps.store(memory, startAddress + 0x200000, Buildroot.getLinuxImage());
return true;
} catch (final IOException e) {
LOGGER.error(e);
return false;
}
}

View File

@@ -1,7 +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.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMResumingRunningEvent;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
import li.cil.oc2.common.bus.device.util.OptionalInterrupt;
@@ -18,7 +22,8 @@ import java.io.OutputStream;
import java.util.Optional;
import java.util.UUID;
public abstract class AbstractHardDriveVMDevice<T extends BlockDevice> extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice {
@SuppressWarnings("UnstableApiUsage")
public abstract class AbstractHardDriveVMDevice<T extends BlockDevice> extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice {
private static final String DEVICE_TAG_NAME = "device";
private static final String ADDRESS_TAG_NAME = "address";
private static final String INTERRUPT_TAG_NAME = "interrupt";
@@ -64,21 +69,33 @@ public abstract class AbstractHardDriveVMDevice<T extends BlockDevice> extends I
return VMDeviceLoadResult.fail();
}
context.getEventBus().register(this);
loadPersistedState();
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case RESUME_RUNNING:
awaitStorageOperation();
break;
case UNLOAD:
unload();
break;
}
public void unload() {
// Since we cannot serialize the data in a regular serialize call due to the
// actual data being unloaded at that point, but want to permanently persist
// it (it's the contents of the block device) we need to serialize it in the
// unload, too. Don't need to wait for the job, though.
serializeData();
data = null;
jobHandle = null;
device = null;
deviceNbt = null;
address.clear();
interrupt.clear();
}
@Subscribe
public void handleResumingRunningEvent(final VMResumingRunningEvent event) {
awaitStorageOperation();
}
@Override
@@ -179,22 +196,6 @@ public abstract class AbstractHardDriveVMDevice<T extends BlockDevice> extends I
}
}
private void unload() {
// Since we cannot serialize the data in a regular serialize call due to the
// actual data being unloaded at that point, but want to permanently persist
// it (it's the contents of the block device) we need to serialize it in the
// unload, too. Don't need to wait for the job, though.
serializeData();
data = null;
jobHandle = null;
device = null;
deviceNbt = null;
address.clear();
interrupt.clear();
}
private void serializeData() {
if (data != null) {
final Optional<InputStream> optional = getSerializationStream(data);

View File

@@ -1,7 +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.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMInitializingEvent;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.sedna.api.memory.MemoryAccessException;
import li.cil.sedna.api.memory.MemoryMap;
@@ -15,7 +19,8 @@ import org.apache.logging.log4j.Logger;
import java.nio.ByteBuffer;
import java.util.OptionalLong;
public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice {
@SuppressWarnings("UnstableApiUsage")
public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice {
private static final Logger LOGGER = LogManager.getLogger();
///////////////////////////////////////////////////////////////
@@ -58,36 +63,42 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack
memoryMap = context.getMemoryMap();
context.getEventBus().register(this);
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case INITIALIZING:
// TODO Have start address passed with event?
copyDataToMemory(0x80000000L);
break;
case UNLOAD:
unload();
break;
}
public void unload() {
memoryMap = null;
data = null;
device = null;
deviceNbt = null;
address = null;
}
@Subscribe
public void handleInitializingEvent(final VMInitializingEvent event) {
copyDataToMemory(event.getProgramStartAddress());
}
@Override
public CompoundNBT serializeNBT() {
final CompoundNBT nbt = new CompoundNBT();
final CompoundNBT tag = new CompoundNBT();
if (device != null) {
nbt.putByteArray(DATA_TAG_NAME, device.getData().array());
tag.putByteArray(DATA_TAG_NAME, device.getData().array());
}
return nbt;
return tag;
}
@Override
public void deserializeNBT(final CompoundNBT tag) {
final byte[] data = tag.getByteArray(DATA_TAG_NAME);
final ByteBuffer bufferData = device.getData();
bufferData.clear();
bufferData.put(data, 0, Math.min(bufferData.limit(), data.length));
}
///////////////////////////////////////////////////////////////
@@ -139,12 +150,4 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack
LOGGER.error(e);
}
}
private void unload() {
memoryMap = null;
data = null;
device = null;
deviceNbt = null;
address = null;
}
}

View File

@@ -1,13 +1,21 @@
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.data.Firmware;
import li.cil.oc2.api.bus.device.vm.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMInitializationException;
import li.cil.oc2.api.bus.device.vm.event.VMInitializingEvent;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.sedna.api.memory.MemoryMap;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.TranslationTextComponent;
public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice {
@SuppressWarnings("UnstableApiUsage")
public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice {
private final Firmware firmware;
private MemoryMap memoryMap;
@@ -24,19 +32,20 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack>
public VMDeviceLoadResult load(final VMContext context) {
memoryMap = context.getMemoryMap();
context.getEventBus().register(this);
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case INITIALIZING:
// TODO Have start address passed with event?
firmware.run(memoryMap, 0x80000000L);
break;
case UNLOAD:
memoryMap = null;
break;
public void unload() {
memoryMap = null;
}
@Subscribe
public void handleInitializingEvent(final VMInitializingEvent event) throws VMInitializationException {
if (!firmware.run(memoryMap, event.getProgramStartAddress())) {
throw new VMInitializationException(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY));
}
}
}

View File

@@ -1,7 +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.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMResumingRunningEvent;
import li.cil.oc2.common.Config;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
@@ -18,7 +22,8 @@ import net.minecraft.util.math.MathHelper;
import java.util.UUID;
public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice {
@SuppressWarnings("UnstableApiUsage")
public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice {
private static final String BLOB_HANDLE_TAG_NAME = "blob";
private static final String ADDRESS_TAG_NAME = "address";
@@ -54,19 +59,25 @@ public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDe
loadPersistedState();
context.getEventBus().register(this);
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case RESUME_RUNNING:
awaitStorageOperation();
break;
case UNLOAD:
unload();
break;
}
public void unload() {
// Memory is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
jobHandle = null;
device = null;
address.clear();
}
@Subscribe
public void handleResumingRunningEvent(final VMResumingRunningEvent event) {
awaitStorageOperation();
}
@Override
@@ -120,14 +131,4 @@ public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDe
jobHandle = null;
}
}
private void unload() {
// Memory is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
jobHandle = null;
device = null;
address.clear();
}
}

View File

@@ -1,7 +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.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMResumingRunningEvent;
import li.cil.oc2.api.capabilities.NetworkInterface;
import li.cil.oc2.common.bus.device.util.IdentityProxy;
import li.cil.oc2.common.bus.device.util.OptionalAddress;
@@ -19,7 +23,8 @@ import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nullable;
public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStack> implements VMDevice, VMDeviceLifecycleListener, ItemDevice, ICapabilityProvider {
@SuppressWarnings("UnstableApiUsage")
public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStack> implements VMDevice, ItemDevice, ICapabilityProvider {
private static final String DEVICE_TAG_NAME = "device";
private static final String ADDRESS_TAG_NAME = "address";
private static final String INTERRUPT_TAG_NAME = "interrupt";
@@ -69,19 +74,22 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStac
NBTSerialization.deserialize(deviceNbt, device);
}
context.getEventBus().register(this);
return VMDeviceLoadResult.success();
}
@Override
public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) {
switch (event) {
case RESUMED_RUNNING:
isRunning = true;
break;
case UNLOAD:
unload();
break;
}
public void unload() {
device = null;
isRunning = false;
address.clear();
interrupt.clear();
}
@Subscribe
public void handleResumingRunningEvent(final VMResumingRunningEvent event) {
isRunning = true;
}
@Override
@@ -119,15 +127,6 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStac
///////////////////////////////////////////////////////////////
private void unload() {
device = null;
isRunning = false;
address.clear();
interrupt.clear();
}
///////////////////////////////////////////////////////////////
private final class NetworkInterfaceImpl implements NetworkInterface {
@Override
public byte[] readEthernetFrame() {

View File

@@ -2,7 +2,9 @@ package li.cil.oc2.common.vm;
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
import li.cil.ceres.api.Serialized;
import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType;
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.VMResumingRunningEvent;
import java.nio.ByteBuffer;
@@ -73,13 +75,13 @@ public abstract class AbstractTerminalVirtualMachineRunner extends VirtualMachin
protected void handleBeforeRun() {
if (!firedInitializationEvent) {
firedInitializationEvent = true;
virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.INITIALIZING);
virtualMachine.vmAdapter.postLifecycleEvent(new VMInitializingEvent(0x80000000L));
}
if (!firedResumeEvent) {
firedResumeEvent = true;
virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.RESUME_RUNNING);
virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.RESUMED_RUNNING);
virtualMachine.vmAdapter.postLifecycleEvent(new VMResumingRunningEvent());
virtualMachine.vmAdapter.postLifecycleEvent(new VMResumedRunningEvent());
}
int value;

View File

@@ -1,7 +1,7 @@
package li.cil.oc2.common.vm;
import li.cil.oc2.api.bus.device.vm.VMDeviceLifecycleEventType;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.api.bus.device.vm.event.VMPausingEvent;
import li.cil.oc2.common.Constants;
import li.cil.oc2.common.bus.AbstractDeviceBusController;
import li.cil.oc2.common.serialization.NBTSerialization;
@@ -51,7 +51,6 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
public AbstractVirtualMachineState(final TBusController busController, final TVirtualMachine virtualMachine) {
this.busController = busController;
this.virtualMachine = virtualMachine;
}
///////////////////////////////////////////////////////////////////
@@ -191,6 +190,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
break;
}
// May have a valid runner after load. In which case we just had to wait for
// bus setup and devices to load. So we can keep using it.
if (runner == null) {
@@ -203,7 +203,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
// a program that only uses registers. But not supporting that esoteric
// use-case loses out against avoiding people getting confused for having
// forgotten to add some RAM modules.
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_NO_MEMORY));
setBootError(new TranslationTextComponent(Constants.COMPUTER_BOOT_ERROR_INSUFFICIENT_MEMORY));
setRunState(RunState.STOPPED);
return;
} catch (final MemoryAccessException e) {
@@ -239,7 +239,7 @@ public abstract class AbstractVirtualMachineState<TBusController extends Abstrac
if (runner != null) {
tag.put(RUNNER_TAG_NAME, NBTSerialization.serialize(runner));
virtualMachine.vmAdapter.fireLifecycleEvent(VMDeviceLifecycleEventType.PAUSING);
virtualMachine.vmAdapter.postLifecycleEvent(new VMPausingEvent());
runner.scheduleResumeEvent(); // Allow synchronizing to async device saves.
} else {
NBTUtils.putEnum(tag, RUN_STATE_TAG_NAME, runState);

View File

@@ -0,0 +1,35 @@
package li.cil.oc2.common.vm;
import com.google.common.eventbus.EventBus;
import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus;
import java.util.ArrayList;
@SuppressWarnings("UnstableApiUsage")
public final class ManagedEventBus implements VMLifecycleEventBus {
private final EventBus eventBus;
private final ArrayList<Object> subscribers = new ArrayList<>();
public ManagedEventBus(final EventBus eventBus) {
this.eventBus = eventBus;
}
public void invalidate() {
for (final Object subscriber : subscribers) {
eventBus.unregister(subscriber);
}
subscribers.clear();
}
@Override
public void register(final Object object) {
eventBus.register(object);
subscribers.add(object);
}
@Override
public void unregister(final Object object) {
eventBus.unregister(object);
subscribers.remove(object);
}
}

View File

@@ -1,9 +1,11 @@
package li.cil.oc2.common.vm;
import com.google.common.eventbus.EventBus;
import li.cil.oc2.api.bus.device.vm.InterruptAllocator;
import li.cil.oc2.api.bus.device.vm.MemoryAllocator;
import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.event.VMLifecycleEventBus;
import li.cil.sedna.api.Board;
import li.cil.sedna.api.device.InterruptController;
import li.cil.sedna.api.device.MemoryMappedDevice;
@@ -13,26 +15,29 @@ import java.util.BitSet;
import java.util.OptionalLong;
import java.util.function.Function;
@SuppressWarnings("UnstableApiUsage")
public final class ManagedVMContext implements VMContext {
private final ManagedMemoryMap memoryMap;
private final ManagedInterruptController interruptController;
private final ManagedMemoryRangeAllocator memoryRangeAllocator;
private final ManagedInterruptAllocator interruptAllocator;
private final ManagedMemoryAllocator memoryAllocator;
private final ManagedEventBus eventBus;
///////////////////////////////////////////////////////////////////
public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts,
final Function<MemoryMappedDevice, OptionalLong> defaultAddress) {
final EventBus eventBus, final Function<MemoryMappedDevice, OptionalLong> defaultAddress) {
this.memoryRangeAllocator = new ManagedMemoryRangeAllocator(board, defaultAddress);
this.interruptAllocator = new ManagedInterruptAllocator(claimedInterrupts, reservedInterrupts, board.getInterruptCount());
this.memoryMap = new ManagedMemoryMap(board.getMemoryMap());
this.interruptController = new ManagedInterruptController(board.getInterruptController(), interruptAllocator);
this.memoryAllocator = new ManagedMemoryAllocator();
this.eventBus = new ManagedEventBus(eventBus);
}
public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts) {
this(board, claimedInterrupts, reservedInterrupts, (memoryMappedDevice) -> OptionalLong.empty());
public ManagedVMContext(final Board board, final BitSet claimedInterrupts, final BitSet reservedInterrupts, final EventBus eventBus) {
this(board, claimedInterrupts, reservedInterrupts, eventBus, (memoryMappedDevice) -> OptionalLong.empty());
}
///////////////////////////////////////////////////////////////////
@@ -48,6 +53,7 @@ public final class ManagedVMContext implements VMContext {
interruptAllocator.invalidate();
interruptController.invalidate();
memoryAllocator.invalidate();
eventBus.invalidate();
}
@Override
@@ -74,4 +80,9 @@ public final class ManagedVMContext implements VMContext {
public MemoryAllocator getMemoryAllocator() {
return memoryAllocator;
}
@Override
public VMLifecycleEventBus getEventBus() {
return eventBus;
}
}

View File

@@ -1,21 +1,26 @@
package li.cil.oc2.common.vm;
import com.google.common.eventbus.EventBus;
import li.cil.ceres.api.Serialized;
import li.cil.oc2.api.bus.device.Device;
import li.cil.oc2.api.bus.device.vm.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
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.event.VMLifecycleEvent;
import li.cil.sedna.api.Board;
import java.util.*;
@SuppressWarnings("UnstableApiUsage")
public final class VirtualMachineDeviceBusAdapter {
private final Board board;
private final EventBus eventBus = new EventBus();
private final ManagedVMContext globalContext;
private final BitSet claimedInterrupts = new BitSet();
private final HashMap<VMDevice, ManagedVMContext> deviceContexts = new HashMap<>();
private final ArrayList<VMDevice> incompleteLoads = new ArrayList<>();
private final HashSet<VMDeviceLifecycleListener> lifecycleEventListeners = new HashSet<>();
private DefaultAddressProvider defaultAddressProvider = unused -> OptionalLong.empty();
///////////////////////////////////////////////////////////////////
@@ -30,8 +35,7 @@ public final class VirtualMachineDeviceBusAdapter {
public VirtualMachineDeviceBusAdapter(final Board board) {
this.board = board;
this.globalContext = new ManagedVMContext(
board, claimedInterrupts, reservedInterrupts);
this.globalContext = new ManagedVMContext(board, claimedInterrupts, reservedInterrupts, eventBus);
this.claimedInterrupts.set(0);
}
@@ -50,7 +54,7 @@ public final class VirtualMachineDeviceBusAdapter {
final VMDevice device = incompleteLoads.get(i);
final ManagedVMContext context = new ManagedVMContext(
board, claimedInterrupts, reservedInterrupts,
board, claimedInterrupts, reservedInterrupts, eventBus,
(memoryMappedDevice) -> defaultAddressProvider.getDefaultAddress(device));
deviceContexts.put(device, context);
@@ -75,7 +79,9 @@ public final class VirtualMachineDeviceBusAdapter {
}
public void unload() {
fireLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD);
for (final VMDevice device : deviceContexts.keySet()) {
device.unload();
}
suspend();
}
@@ -91,10 +97,8 @@ public final class VirtualMachineDeviceBusAdapter {
incompleteLoads.addAll(deviceContexts.keySet());
}
public void fireLifecycleEvent(final VMDeviceLifecycleEventType event) {
for (final VMDeviceLifecycleListener tickListener : lifecycleEventListeners) {
tickListener.handleLifecycleEvent(event);
}
public void postLifecycleEvent(final VMLifecycleEvent event) {
eventBus.post(event);
}
public void addDevices(final Collection<Device> devices) {
@@ -108,10 +112,6 @@ public final class VirtualMachineDeviceBusAdapter {
}
incompleteLoads.add(vmDevice);
if (vmDevice instanceof VMDeviceLifecycleListener) {
lifecycleEventListeners.add((VMDeviceLifecycleListener) vmDevice);
}
}
}
}
@@ -121,18 +121,14 @@ public final class VirtualMachineDeviceBusAdapter {
if (device instanceof VMDevice) {
final VMDevice vmDevice = (VMDevice) device;
vmDevice.unload();
final ManagedVMContext context = deviceContexts.remove(vmDevice);
if (context != null) {
context.invalidate();
}
incompleteLoads.remove(vmDevice);
if (vmDevice instanceof VMDeviceLifecycleListener) {
final VMDeviceLifecycleListener eventListener = (VMDeviceLifecycleListener) vmDevice;
lifecycleEventListeners.remove(eventListener);
eventListener.handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD);
}
}
}
}

View File

@@ -37,7 +37,8 @@
"config.oc2.admin.fakePlayerUUID": "Fake Player UUID",
"gui.oc2.computer.boot_error.unknown": "Unknown Error",
"gui.oc2.computer.boot_error.no_memory": "Insufficient Memory",
"gui.oc2.computer.boot_error.missing_firmware": "Missing Flash Memory",
"gui.oc2.computer.boot_error.insufficient_memory": "Insufficient Memory",
"gui.oc2.computer.bus_state.incomplete": "Bus Incomplete",
"gui.oc2.computer.bus_state.too_complex": "Bus Too Complex",
"gui.oc2.computer.bus_state.multiple_controllers": "Multiple Bus Controllers",

View File

@@ -1,6 +1,8 @@
package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.device.vm.*;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.oc2.api.bus.device.vm.VMDevice;
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
import li.cil.oc2.common.vm.VirtualMachineDeviceBusAdapter;
import li.cil.sedna.api.Board;
import li.cil.sedna.api.device.InterruptController;
@@ -71,31 +73,31 @@ public final class VMDeviceTests {
@Test
public void removedDevicesHaveUnloadCalled() {
final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class);
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
adapter.removeDevices(Collections.singleton(device));
verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD);
verify(device).unload();
}
@Test
public void devicesHaveUnloadCalledOnGlobalUnload() {
final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class);
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
adapter.unload();
verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD);
verify(device).unload();
}
@Test
public void devicesHaveLoadCalledAfterGlobalUnload() {
final VMDeviceLifecycleListener device = mock(VMDeviceLifecycleListener.class);
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
@@ -103,7 +105,7 @@ public final class VMDeviceTests {
verify(device).load(any());
adapter.unload();
verify(device).handleLifecycleEvent(VMDeviceLifecycleEventType.UNLOAD);
verify(device).unload();
assertTrue(adapter.load().wasSuccessful());
verify(device, times(2)).load(any());