API for adding device providers and custom parameter serializers.
This commit is contained in:
@@ -1,8 +1,40 @@
|
||||
package li.cil.oc2.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
import li.cil.oc2.api.device.object.Callback;
|
||||
import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public final class API {
|
||||
public static final String MOD_ID = "oc2";
|
||||
|
||||
/**
|
||||
* IMC message for registering a {@link li.cil.oc2.api.device.provider.DeviceProvider}.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre>
|
||||
* InterModComms.sendTo(API.MOD_ID, API.IMC_ADD_DEVICE_PROVIDER, () -> new DeviceProvider() { ... });
|
||||
* </pre>
|
||||
*/
|
||||
public static final String IMC_ADD_DEVICE_PROVIDER = "addDeviceProvider";
|
||||
|
||||
/**
|
||||
* IMC message for registering Gson type adapters for method parameter serialization and
|
||||
* deserialization.
|
||||
* <p>
|
||||
* Must be called with a supplier that provides an instance of {@link DeviceMethodParameterTypeAdapter}.
|
||||
* <p>
|
||||
* It can be necessary to register additional serializers when implementing {@link DeviceMethod}s
|
||||
* that use custom parameter types.
|
||||
*
|
||||
* @see GsonBuilder#registerTypeAdapter(Type, Object)
|
||||
* @see DeviceMethod
|
||||
* @see Callback
|
||||
*/
|
||||
public static final String IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER = "addDeviceMethodParameterTypeAdapter";
|
||||
|
||||
private API() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import java.util.Optional;
|
||||
* <p>
|
||||
* The easiest and hence recommended way of implementing this interface is to use
|
||||
* the {@link ObjectDevice} class.
|
||||
* <p>
|
||||
* Method parameters are serialized and deserialized using Gson. When using custom
|
||||
* parameter types it may be necessary to register a custom type adapter for them
|
||||
* via {@link li.cil.oc2.api.API#IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER}.
|
||||
*
|
||||
* @see ObjectDevice
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,10 @@ import java.lang.annotation.Target;
|
||||
* <p>
|
||||
* Intended to be used in classes instances of which are used in combination with
|
||||
* {@link ObjectDevice} and subclasses of {@link ObjectDevice}.
|
||||
* <p>
|
||||
* Method parameters are serialized and deserialized using Gson. When using custom
|
||||
* parameter types it may be necessary to register a custom type adapter for them
|
||||
* via {@link li.cil.oc2.api.API#IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER}.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package li.cil.oc2.api.imc;
|
||||
|
||||
public final class DeviceMethodParameterTypeAdapter {
|
||||
public final Class<?> type;
|
||||
public final Object typeAdapter;
|
||||
|
||||
public DeviceMethodParameterTypeAdapter(final Class<?> type, final Object typeAdapter) {
|
||||
this.type = type;
|
||||
this.typeAdapter = typeAdapter;
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,26 @@ import li.cil.oc2.common.capabilities.DeviceBusElementCapability;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import li.cil.oc2.common.vm.Allocator;
|
||||
import li.cil.oc2.serialization.BlobStorage;
|
||||
import li.cil.oc2.serialization.serializers.ItemStackJsonSerializer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
|
||||
public final class CommonSetup {
|
||||
public static void run(final FMLCommonSetupEvent event) {
|
||||
DeviceBusElementCapability.register();
|
||||
|
||||
Providers.initialize();
|
||||
Network.setup();
|
||||
|
||||
FMLJavaModLoadingContext.get().getModEventBus().addListener(IMC::handleIMCMessages);
|
||||
MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerAboutToStart);
|
||||
MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerStoppedEvent);
|
||||
ServerScheduler.register();
|
||||
|
||||
addBuiltinDeviceMethodParameterTypeAdapters();
|
||||
}
|
||||
|
||||
public static void handleServerAboutToStart(final FMLServerAboutToStartEvent event) {
|
||||
@@ -27,4 +34,8 @@ public final class CommonSetup {
|
||||
BlobStorage.synchronize();
|
||||
Allocator.resetAndCheckLeaks();
|
||||
}
|
||||
|
||||
private static void addBuiltinDeviceMethodParameterTypeAdapters() {
|
||||
DeviceMethodParameterTypeAdapters.addTypeAdapter(ItemStack.class, new ItemStackJsonSerializer());
|
||||
}
|
||||
}
|
||||
|
||||
65
src/main/java/li/cil/oc2/common/IMC.java
Normal file
65
src/main/java/li/cil/oc2/common/IMC.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package li.cil.oc2.common;
|
||||
|
||||
import li.cil.oc2.api.API;
|
||||
import li.cil.oc2.api.device.provider.DeviceProvider;
|
||||
import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter;
|
||||
import li.cil.oc2.common.device.DeviceMethodParameterTypeAdapters;
|
||||
import li.cil.oc2.common.device.Providers;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraftforge.fml.InterModComms;
|
||||
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class IMC {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final HashMap<String, Consumer<InterModComms.IMCMessage>> METHODS = Util.make(() -> {
|
||||
HashMap<String, Consumer<InterModComms.IMCMessage>> map = new HashMap<>();
|
||||
|
||||
map.put(API.IMC_ADD_DEVICE_PROVIDER, IMC::addDeviceProvider);
|
||||
map.put(API.IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER, IMC::addDeviceMethodParameterTypeAdapter);
|
||||
|
||||
return map;
|
||||
});
|
||||
|
||||
public static void handleIMCMessages(final InterModProcessEvent event) {
|
||||
event.getIMCStream().forEach(message -> {
|
||||
final Consumer<InterModComms.IMCMessage> method = METHODS.get(message.getMethod());
|
||||
if (method != null) {
|
||||
method.accept(message);
|
||||
} else {
|
||||
LOGGER.error("Received unknown IMC message [{}] from mod [{}], ignoring.", message.getMethod(), message.getSenderModId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addDeviceProvider(final InterModComms.IMCMessage message) {
|
||||
getMessageParameter(message, DeviceProvider.class).ifPresent(Providers::addProvider);
|
||||
}
|
||||
|
||||
private static void addDeviceMethodParameterTypeAdapter(final InterModComms.IMCMessage message) {
|
||||
getMessageParameter(message, DeviceMethodParameterTypeAdapter.class).ifPresent(value -> {
|
||||
try {
|
||||
DeviceMethodParameterTypeAdapters.addTypeAdapter(value);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
LOGGER.error("Received invalid type adapter registration [{}] for type [{}] from mod [{}].", value.typeAdapter, value.type, message.getSenderModId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> Optional<T> getMessageParameter(final InterModComms.IMCMessage message, final Class<T> type) {
|
||||
final Object value = message.getMessageSupplier().get();
|
||||
if (type.isInstance(value)) {
|
||||
return Optional.of((T) value);
|
||||
} else {
|
||||
LOGGER.error("Received incompatible parameter [{}] for IMC message [{}] from mod [{}]. Expected type is [{}].", message.getMessageSupplier().get(), message.getMethod(), message.getSenderModId(), type);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/main/java/li/cil/oc2/common/ServerScheduler.java
Normal file
111
src/main/java/li/cil/oc2/common/ServerScheduler.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package li.cil.oc2.common;
|
||||
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class ServerScheduler {
|
||||
public static void register() {
|
||||
MinecraftForge.EVENT_BUS.register(EventHandler.class);
|
||||
}
|
||||
|
||||
private static final Scheduler serverScheduler = new Scheduler();
|
||||
private static final WeakHashMap<IWorld, Scheduler> worldSchedulers = new WeakHashMap<>();
|
||||
|
||||
public static void schedule(final Runnable runnable) {
|
||||
schedule(runnable, 0);
|
||||
}
|
||||
|
||||
public static void schedule(final Runnable runnable, final int afterTicks) {
|
||||
serverScheduler.schedule(runnable, afterTicks);
|
||||
}
|
||||
|
||||
public static void schedule(final IWorld world, final Runnable runnable) {
|
||||
schedule(world, runnable, 0);
|
||||
}
|
||||
|
||||
public static void schedule(final IWorld world, final Runnable runnable, final int afterTicks) {
|
||||
final Scheduler scheduler = worldSchedulers.computeIfAbsent(world, w -> new Scheduler());
|
||||
scheduler.schedule(runnable, afterTicks);
|
||||
}
|
||||
|
||||
private static final class EventHandler {
|
||||
@SubscribeEvent
|
||||
public static void handleServerStoppedEvent(final FMLServerStoppedEvent event) {
|
||||
serverScheduler.clear();
|
||||
worldSchedulers.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void handleWorldUnload(final WorldEvent.Unload event) {
|
||||
worldSchedulers.remove(event.getWorld());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void handleServerTick(final TickEvent.ServerTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.START) {
|
||||
for (final Scheduler scheduler : worldSchedulers.values()) {
|
||||
scheduler.tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void handleWorldTick(final TickEvent.WorldTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.START) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Scheduler scheduler = worldSchedulers.get(event.world);
|
||||
if (scheduler != null) {
|
||||
scheduler.processQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Scheduler {
|
||||
private final PriorityQueue<ScheduledRunnable> queue = new PriorityQueue<>();
|
||||
private int currentTick;
|
||||
|
||||
public void schedule(final Runnable runnable, final int afterTicks) {
|
||||
queue.add(new ScheduledRunnable(currentTick + afterTicks, runnable));
|
||||
}
|
||||
|
||||
public void processQueue() {
|
||||
while (!queue.isEmpty() && queue.peek().tick <= currentTick) {
|
||||
queue.poll().runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
currentTick++;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
currentTick = 0;
|
||||
queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ScheduledRunnable implements Comparable<ScheduledRunnable> {
|
||||
public final int tick;
|
||||
public final Runnable runnable;
|
||||
|
||||
private ScheduledRunnable(final int tick, final Runnable runnable) {
|
||||
this.tick = tick;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final ServerScheduler.ScheduledRunnable o) {
|
||||
return Integer.compare(tick, o.tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user