From 591a7eca231bd31ed39f8302c194fd2d7c09efbb Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 26 Jun 2023 19:32:09 +0100 Subject: [PATCH] Clean up how we enumerate Lua/peripheral methods - Move several interfaces out of `d00.computercraft.core.asm` into a new `aethods` package. It may make sense to expose this to the public API in a future commit (possibly part of #1462). - Add a new MethodSupplier interface, which provides methods to iterate over all methods exported by an object (either directly, or including those from ObjectSources). This interface's concrete implementation (asm.MethodSupplierImpl), uses Generators and IntCaches as before - we can now make that all package-private though, which is nice! - Make the LuaMethod and PeripheralMethod MethodSupplier local to the ComputerContext. This currently has no effect (the underlying Generator is still global), but eventually we'll make GenericMethods non-global, which unlocks the door for #1382. - Update everything to use this new interface. This is mostly pretty sensible, but is a little uglier on the MC side (especially in generic peripherals), as we need to access the global ServerContext. --- .../shared/computer/core/ServerContext.java | 12 +++ .../computer/upload/TransferredFile.java | 2 +- .../generic/GenericPeripheralBuilder.java | 38 +++++---- .../peripheral/generic/SaturatedMethod.java | 2 +- .../modem/wired/WiredModemPeripheral.java | 11 ++- .../computercraft/core/ComputerContext.java | 36 +++++++- .../core/apis/IAPIEnvironment.java | 2 +- .../core/apis/PeripheralAPI.java | 28 ++----- .../computercraft/core/apis/TableHelper.java | 2 +- .../apis/http/request/HttpResponseHandle.java | 2 +- .../computercraft/core/asm/Generator.java | 3 +- .../computercraft/core/asm/IntCache.java | 2 +- .../computercraft/core/asm/LuaMethod.java | 23 ----- .../core/asm/LuaMethodSupplier.java | 34 ++++++++ .../core/asm/MethodSupplierImpl.java | 61 ++++++++++++++ .../computercraft/core/asm/ObjectSource.java | 27 ------ .../core/asm/PeripheralMethod.java | 26 ------ .../core/asm/PeripheralMethodSupplier.java | 30 +++++++ .../core/computer/ComputerExecutor.java | 7 +- .../computercraft/core/lua/BasicFunction.java | 2 +- .../core/lua/CobaltLuaMachine.java | 31 ++----- .../core/lua/MachineEnvironment.java | 4 + .../core/lua/ResultInterpreterFunction.java | 2 +- .../computercraft/core/methods/LuaMethod.java | 31 +++++++ .../core/methods/MethodSupplier.java | 83 +++++++++++++++++++ .../core/{asm => methods}/NamedMethod.java | 2 +- .../core/methods/ObjectSource.java | 15 ++++ .../core/methods/PeripheralMethod.java | 35 ++++++++ .../core/apis/ObjectWrapper.java | 27 +++--- .../computercraft/core/asm/GeneratorTest.java | 32 +++---- .../computercraft/core/asm/MethodTest.java | 1 + .../generic/GenericPeripheralProvider.java | 12 ++- .../generic/GenericPeripheralProvider.java | 12 ++- 33 files changed, 449 insertions(+), 188 deletions(-) delete mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethod.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethodSupplier.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/MethodSupplierImpl.java delete mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/ObjectSource.java delete mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethodSupplier.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/methods/LuaMethod.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/methods/MethodSupplier.java rename projects/core/src/main/java/dan200/computercraft/core/{asm => methods}/NamedMethod.java (96%) create mode 100644 projects/core/src/main/java/dan200/computercraft/core/methods/ObjectSource.java create mode 100644 projects/core/src/main/java/dan200/computercraft/core/methods/PeripheralMethod.java diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java index 1ddff50d2..108c95483 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java @@ -14,6 +14,8 @@ import dan200.computercraft.core.computer.mainthread.MainThread; import dan200.computercraft.core.computer.mainthread.MainThreadConfig; import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.impl.AbstractComputerCraftAPI; import dan200.computercraft.impl.ApiFactories; import dan200.computercraft.shared.CommonHooks; @@ -134,6 +136,16 @@ public final class ServerContext { return context; } + /** + * Get the {@link MethodSupplier} used to find methods on peripherals. + * + * @return The {@link PeripheralMethod} method supplier. + * @see ComputerContext#peripheralMethods() + */ + public MethodSupplier peripheralMethods() { + return context.peripheralMethods(); + } + /** * Tick all components of this server context. This should NOT be called outside of {@link CommonHooks}. */ diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/upload/TransferredFile.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/upload/TransferredFile.java index 687ea6989..c059496f2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/upload/TransferredFile.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/upload/TransferredFile.java @@ -7,7 +7,7 @@ package dan200.computercraft.shared.computer.upload; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.ByteBufferChannel; -import dan200.computercraft.core.asm.ObjectSource; +import dan200.computercraft.core.methods.ObjectSource; import java.nio.ByteBuffer; import java.util.Collections; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java index 1d2c61134..e6b69f0cb 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralBuilder.java @@ -6,9 +6,12 @@ package dan200.computercraft.shared.peripheral.generic; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.PeripheralType; -import dan200.computercraft.core.asm.NamedMethod; -import dan200.computercraft.core.asm.PeripheralMethod; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.NamedMethod; +import dan200.computercraft.core.methods.PeripheralMethod; +import dan200.computercraft.shared.computer.core.ServerContext; import net.minecraft.core.Direction; +import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.block.entity.BlockEntity; import javax.annotation.Nullable; @@ -25,37 +28,36 @@ import java.util.Set; * See the platform-specific peripheral providers for the usage of this. */ final class GenericPeripheralBuilder { + private final MethodSupplier peripheralMethods; + private @Nullable String name; private final Set additionalTypes = new HashSet<>(0); - private final ArrayList methods = new ArrayList<>(0); + private final ArrayList methods = new ArrayList<>(); + + GenericPeripheralBuilder(MinecraftServer server) { + peripheralMethods = ServerContext.get(server).peripheralMethods(); + } @Nullable - IPeripheral toPeripheral(BlockEntity tile, Direction side) { + IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) { if (methods.isEmpty()) return null; methods.trimToSize(); - return new GenericPeripheral(tile, side, name, additionalTypes, methods); + return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods); } boolean addMethods(Object target) { - var methods = PeripheralMethod.GENERATOR.getMethods(target.getClass()); - if (methods.isEmpty()) return false; - - var saturatedMethods = this.methods; - saturatedMethods.ensureCapacity(saturatedMethods.size() + methods.size()); - for (var method : methods) { - saturatedMethods.add(new SaturatedMethod(target, method.name(), method.method())); + return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> { + methods.add(new SaturatedMethod(target, name, method)); // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods // don't change). - var type = method.genericType(); + var type = info == null ? null : info.genericType(); if (type != null && type.getPrimaryType() != null) { - var name = type.getPrimaryType(); - if (this.name == null || this.name.compareTo(name) > 0) this.name = name; + var primaryType = type.getPrimaryType(); + if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType; } if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); - } - - return true; + }); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java index 8d0c92920..ce978718c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/generic/SaturatedMethod.java @@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.peripheral.IComputerAccess; -import dan200.computercraft.core.asm.PeripheralMethod; +import dan200.computercraft.core.methods.PeripheralMethod; /** * A {@link PeripheralMethod} along with the method's target. diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java index 49eccfea8..a4956ac69 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java @@ -16,10 +16,12 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.core.apis.PeripheralAPI; -import dan200.computercraft.core.asm.PeripheralMethod; +import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.core.util.LuaUtil; +import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; import dan200.computercraft.shared.peripheral.modem.ModemState; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -284,7 +286,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap peripherals, String periphName, IPeripheral peripheral) { if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) { - var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName); + var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral); + var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods); peripherals.put(periphName, wrapper); wrapper.attach(); } @@ -314,7 +317,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi private volatile boolean attached; private final Set mounts = new HashSet<>(); - RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) { + RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map methods) { this.element = element; this.peripheral = peripheral; this.computer = computer; @@ -322,7 +325,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null"); additionalTypes = peripheral.getAdditionalTypes(); - methodMap = PeripheralAPI.getMethods(peripheral); + methodMap = methods; } public void attach() { diff --git a/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java b/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java index ce0270687..75a6cac6d 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java +++ b/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java @@ -5,12 +5,18 @@ package dan200.computercraft.core; import dan200.computercraft.api.lua.ILuaAPIFactory; +import dan200.computercraft.core.asm.LuaMethodSupplier; +import dan200.computercraft.core.asm.PeripheralMethodSupplier; import dan200.computercraft.core.computer.ComputerThread; import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.mainthread.MainThreadScheduler; import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler; import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineEnvironment; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.PeripheralMethod; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; @@ -28,17 +34,22 @@ public final class ComputerContext { private final MainThreadScheduler mainThreadScheduler; private final ILuaMachine.Factory luaFactory; private final List apiFactories; + private final MethodSupplier luaMethods; + private final MethodSupplier peripheralMethods; ComputerContext( GlobalEnvironment globalEnvironment, ComputerThread computerScheduler, MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory, - List apiFactories + List apiFactories, MethodSupplier luaMethods, + MethodSupplier peripheralMethods ) { this.globalEnvironment = globalEnvironment; this.computerScheduler = computerScheduler; this.mainThreadScheduler = mainThreadScheduler; this.luaFactory = luaFactory; this.apiFactories = apiFactories; + this.luaMethods = luaMethods; + this.peripheralMethods = peripheralMethods; } /** @@ -87,6 +98,25 @@ public final class ComputerContext { return apiFactories; } + /** + * Get the {@link MethodSupplier} used to find methods on Lua values. + * + * @return The {@link LuaMethod} method supplier. + * @see MachineEnvironment#luaMethods() + */ + public MethodSupplier luaMethods() { + return luaMethods; + } + + /** + * Get the {@link MethodSupplier} used to find methods on peripherals. + * + * @return The {@link PeripheralMethod} method supplier. + */ + public MethodSupplier peripheralMethods() { + return peripheralMethods; + } + /** * Close the current {@link ComputerContext}, disposing of any resources inside. * @@ -206,7 +236,9 @@ public final class ComputerContext { new ComputerThread(threads), mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler, luaFactory == null ? CobaltLuaMachine::new : luaFactory, - apiFactories == null ? List.of() : apiFactories + apiFactories == null ? List.of() : apiFactories, + LuaMethodSupplier.create(), + PeripheralMethodSupplier.create() ); } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/projects/core/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java index 9f19cf95f..a8b3e9112 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java @@ -10,9 +10,9 @@ import dan200.computercraft.core.computer.ComputerEnvironment; import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.filesystem.FileSystem; -import dan200.computercraft.core.metrics.OperationTimer; import dan200.computercraft.core.metrics.Metric; import dan200.computercraft.core.metrics.MetricsObserver; +import dan200.computercraft.core.metrics.OperationTimer; import dan200.computercraft.core.terminal.Terminal; import javax.annotation.Nullable; diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java b/projects/core/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java index cfdaea0e3..70fdb0b1c 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/PeripheralAPI.java @@ -7,13 +7,12 @@ package dan200.computercraft.core.apis; import dan200.computercraft.api.filesystem.Mount; import dan200.computercraft.api.filesystem.WritableMount; import dan200.computercraft.api.lua.*; -import dan200.computercraft.api.peripheral.IDynamicPeripheral; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.WorkMonitor; -import dan200.computercraft.core.asm.LuaMethod; -import dan200.computercraft.core.asm.PeripheralMethod; import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.util.LuaUtil; @@ -44,7 +43,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null"); additionalTypes = peripheral.getAdditionalTypes(); - methodMap = PeripheralAPI.getMethods(peripheral); + methodMap = peripheralMethods.getSelfMethods(peripheral); } public IPeripheral getPeripheral() { @@ -172,11 +171,13 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange } private final IAPIEnvironment environment; + private final MethodSupplier peripheralMethods; private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6]; private boolean running; - public PeripheralAPI(IAPIEnvironment environment) { + public PeripheralAPI(IAPIEnvironment environment, MethodSupplier peripheralMethods) { this.environment = environment; + this.peripheralMethods = peripheralMethods; this.environment.setPeripheralChangeListener(this); running = false; } @@ -315,21 +316,4 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange throw e; } } - - public static Map getMethods(IPeripheral peripheral) { - var dynamicMethods = peripheral instanceof IDynamicPeripheral - ? Objects.requireNonNull(((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null") - : LuaMethod.EMPTY_METHODS; - - var methods = PeripheralMethod.GENERATOR.getMethods(peripheral.getClass()); - - Map methodMap = new HashMap<>(methods.size() + dynamicMethods.length); - for (var i = 0; i < dynamicMethods.length; i++) { - methodMap.put(dynamicMethods[i], PeripheralMethod.DYNAMIC.get(i)); - } - for (var method : methods) { - methodMap.put(method.name(), method.method()); - } - return methodMap; - } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java b/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java index 9f3f505f0..d4ed5dc9f 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/TableHelper.java @@ -103,7 +103,7 @@ public final class TableHelper { public static Optional optRealField(Map table, String key) throws LuaException { var value = table.get(key); - if(value == null) { + if (value == null) { return Optional.empty(); } else { return Optional.of(getRealField(table, key)); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java index dba9683a6..e82e5c3ad 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java @@ -10,7 +10,7 @@ import dan200.computercraft.core.apis.HTTPAPI; import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.EncodedReadableHandle; import dan200.computercraft.core.apis.handles.HandleGeneric; -import dan200.computercraft.core.asm.ObjectSource; +import dan200.computercraft.core.methods.ObjectSource; import java.util.Collections; import java.util.Map; diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java b/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java index ea2870384..4b79e0060 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/Generator.java @@ -11,6 +11,7 @@ import com.google.common.primitives.Primitives; import com.google.common.reflect.TypeToken; import dan200.computercraft.api.lua.*; import dan200.computercraft.api.peripheral.PeripheralType; +import dan200.computercraft.core.methods.NamedMethod; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; @@ -30,7 +31,7 @@ import java.util.function.Function; import static org.objectweb.asm.Opcodes.*; -public final class Generator { +final class Generator { private static final Logger LOG = LoggerFactory.getLogger(Generator.class); private static final AtomicInteger METHOD_ID = new AtomicInteger(); diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/IntCache.java b/projects/core/src/main/java/dan200/computercraft/core/asm/IntCache.java index 850c6303a..3509dfc4a 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/IntCache.java +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/IntCache.java @@ -7,7 +7,7 @@ package dan200.computercraft.core.asm; import java.util.Arrays; import java.util.function.IntFunction; -public final class IntCache { +final class IntCache { private final IntFunction factory; private volatile Object[] cache = new Object[16]; diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethod.java b/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethod.java deleted file mode 100644 index 7de22ff1b..000000000 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethod.java +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.core.asm; - -import dan200.computercraft.api.lua.*; - -import java.util.Collections; - -public interface LuaMethod { - Generator GENERATOR = new Generator<>(LuaMethod.class, Collections.singletonList(ILuaContext.class), - m -> (target, context, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, args.escapes()))) - ); - - IntCache DYNAMIC = new IntCache<>( - method -> (instance, context, args) -> ((IDynamicLuaObject) instance).callMethod(context, method, args) - ); - - String[] EMPTY_METHODS = new String[0]; - - MethodResult apply(Object target, ILuaContext context, IArguments args) throws LuaException; -} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethodSupplier.java b/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethodSupplier.java new file mode 100644 index 000000000..626e9a329 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/LuaMethodSupplier.java @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.asm; + +import dan200.computercraft.api.lua.IDynamicLuaObject; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; +import org.jetbrains.annotations.VisibleForTesting; + +import java.util.List; +import java.util.Objects; + +public final class LuaMethodSupplier { + @VisibleForTesting + static final Generator GENERATOR = new Generator<>(LuaMethod.class, List.of(ILuaContext.class), + m -> (target, context, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, args.escapes()))) + ); + private static final IntCache DYNAMIC = new IntCache<>( + method -> (instance, context, args) -> ((IDynamicLuaObject) instance).callMethod(context, method, args) + ); + + private LuaMethodSupplier() { + } + + public static MethodSupplier create() { + return new MethodSupplierImpl<>(GENERATOR, DYNAMIC, x -> x instanceof IDynamicLuaObject dynamic + ? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null") + : null + ); + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/MethodSupplierImpl.java b/projects/core/src/main/java/dan200/computercraft/core/asm/MethodSupplierImpl.java new file mode 100644 index 000000000..1e532d713 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/MethodSupplierImpl.java @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.asm; + +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.ObjectSource; + +import java.util.function.Function; + +final class MethodSupplierImpl implements MethodSupplier { + private final Generator generator; + private final IntCache dynamic; + private final Function dynamicMethods; + + MethodSupplierImpl(Generator generator, IntCache dynamic, Function dynamicMethods) { + this.generator = generator; + this.dynamic = dynamic; + this.dynamicMethods = dynamicMethods; + } + + @Override + public boolean forEachSelfMethod(Object object, UntargetedConsumer consumer) { + var methods = generator.getMethods(object.getClass()); + for (var method : methods) consumer.accept(method.name(), method.method(), method); + + var dynamicMethods = this.dynamicMethods.apply(object); + if (dynamicMethods != null) { + for (var i = 0; i < dynamicMethods.length; i++) consumer.accept(dynamicMethods[i], dynamic.get(i), null); + } + + return !methods.isEmpty() || dynamicMethods != null; + } + + @Override + public boolean forEachMethod(Object object, TargetedConsumer consumer) { + var methods = generator.getMethods(object.getClass()); + for (var method : methods) consumer.accept(object, method.name(), method.method(), method); + + var hasMethods = !methods.isEmpty(); + + if (object instanceof ObjectSource source) { + for (var extra : source.getExtra()) { + var extraMethods = generator.getMethods(extra.getClass()); + if (!extraMethods.isEmpty()) hasMethods = true; + for (var method : extraMethods) consumer.accept(object, method.name(), method.method(), method); + } + } + + var dynamicMethods = this.dynamicMethods.apply(object); + if (dynamicMethods != null) { + hasMethods = true; + for (var i = 0; i < dynamicMethods.length; i++) { + consumer.accept(object, dynamicMethods[i], dynamic.get(i), null); + } + } + + return hasMethods; + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/ObjectSource.java b/projects/core/src/main/java/dan200/computercraft/core/asm/ObjectSource.java deleted file mode 100644 index 3328c9b89..000000000 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/ObjectSource.java +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.core.asm; - -import java.util.function.BiConsumer; - -/** - * A Lua object which exposes additional methods. - *

- * This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely - * happy with the interface - something I'd like to think about first. - */ -public interface ObjectSource { - Iterable getExtra(); - - static void allMethods(Generator generator, Object object, BiConsumer> accept) { - for (var method : generator.getMethods(object.getClass())) accept.accept(object, method); - - if (object instanceof ObjectSource source) { - for (var extra : source.getExtra()) { - for (var method : generator.getMethods(extra.getClass())) accept.accept(extra, method); - } - } - } -} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java b/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java deleted file mode 100644 index f20b85998..000000000 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethod.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.core.asm; - -import dan200.computercraft.api.lua.IArguments; -import dan200.computercraft.api.lua.ILuaContext; -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.api.lua.MethodResult; -import dan200.computercraft.api.peripheral.IComputerAccess; -import dan200.computercraft.api.peripheral.IDynamicPeripheral; - -import java.util.Arrays; - -public interface PeripheralMethod { - Generator GENERATOR = new Generator<>(PeripheralMethod.class, Arrays.asList(ILuaContext.class, IComputerAccess.class), - m -> (target, context, computer, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, computer, args.escapes()))) - ); - - IntCache DYNAMIC = new IntCache<>( - method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args) - ); - - MethodResult apply(Object target, ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException; -} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethodSupplier.java b/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethodSupplier.java new file mode 100644 index 000000000..5010449c9 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/asm/PeripheralMethodSupplier.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.asm; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IDynamicPeripheral; +import dan200.computercraft.core.methods.MethodSupplier; +import dan200.computercraft.core.methods.PeripheralMethod; + +import java.util.List; +import java.util.Objects; + +public class PeripheralMethodSupplier { + private static final Generator GENERATOR = new Generator<>(PeripheralMethod.class, List.of(ILuaContext.class, IComputerAccess.class), + m -> (target, context, computer, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, computer, args.escapes()))) + ); + private static final IntCache DYNAMIC = new IntCache<>( + method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args) + ); + + public static MethodSupplier create() { + return new MethodSupplierImpl<>(GENERATOR, DYNAMIC, x -> x instanceof IDynamicPeripheral dynamic + ? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null") + : null + ); + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index 4c7015502..3680ccba1 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -15,6 +15,8 @@ import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.MachineEnvironment; import dan200.computercraft.core.lua.MachineException; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.util.Colour; @@ -61,6 +63,7 @@ final class ComputerExecutor { private final MetricsObserver metrics; private final List apis = new ArrayList<>(); private final ComputerThread scheduler; + private final MethodSupplier luaMethods; final TimeoutState timeout; private @Nullable FileSystem fileSystem; @@ -168,6 +171,7 @@ final class ComputerExecutor { metrics = computerEnvironment.getMetrics(); luaFactory = context.luaFactory(); scheduler = context.computerScheduler(); + luaMethods = context.luaMethods(); timeout = new TimeoutState(scheduler); var environment = computer.getEnvironment(); @@ -176,7 +180,7 @@ final class ComputerExecutor { apis.add(new TermAPI(environment)); apis.add(new RedstoneAPI(environment)); apis.add(new FSAPI(environment)); - apis.add(new PeripheralAPI(environment)); + apis.add(new PeripheralAPI(environment, context.peripheralMethods())); apis.add(new OSAPI(environment)); if (CoreConfig.httpEnabled) apis.add(new HTTPAPI(environment)); @@ -382,6 +386,7 @@ final class ComputerExecutor { return luaFactory.create(new MachineEnvironment( new LuaContext(computer), metrics, timeout, () -> apis.stream().map(api -> api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api).iterator(), + luaMethods, computer.getGlobalEnvironment().getHostString() ), bios); } catch (IOException e) { diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/projects/core/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index dc9593fb7..44924be57 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -8,7 +8,7 @@ import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.core.Logging; -import dan200.computercraft.core.asm.LuaMethod; +import dan200.computercraft.core.methods.LuaMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.squiddev.cobalt.LuaError; diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index 7ce55baa1..7d181d5fb 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -10,9 +10,9 @@ import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaFunction; import dan200.computercraft.core.CoreConfig; import dan200.computercraft.core.Logging; -import dan200.computercraft.core.asm.LuaMethod; -import dan200.computercraft.core.asm.ObjectSource; import dan200.computercraft.core.computer.TimeoutState; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.util.Nullability; import dan200.computercraft.core.util.SanitisedError; import org.slf4j.Logger; @@ -39,6 +39,7 @@ public class CobaltLuaMachine implements ILuaMachine { private final TimeoutState timeout; private final Runnable timeoutListener = this::updateTimeout; private final ILuaContext context; + private final MethodSupplier luaMethods; private final LuaState state; private final LuaThread mainRoutine; @@ -51,6 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine { public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException { timeout = environment.timeout(); context = environment.context(); + luaMethods = environment.luaMethods(); // Create an environment to run in var state = this.state = LuaState.builder() @@ -161,28 +163,13 @@ public class CobaltLuaMachine implements ILuaMachine { @Nullable private LuaTable wrapLuaObject(Object object) { - var dynamicMethods = object instanceof IDynamicLuaObject dynamic - ? Objects.requireNonNull(dynamic.getMethodNames(), "Methods cannot be null") - : LuaMethod.EMPTY_METHODS; - var table = new LuaTable(); - for (var i = 0; i < dynamicMethods.length; i++) { - var method = dynamicMethods[i]; - table.rawset(method, new ResultInterpreterFunction(this, LuaMethod.DYNAMIC.get(i), object, context, method)); - } + var found = luaMethods.forEachMethod(object, (target, name, method, info) -> + table.rawset(name, info != null && info.nonYielding() + ? new BasicFunction(this, method, target, context, name) + : new ResultInterpreterFunction(this, method, target, context, name))); - ObjectSource.allMethods(LuaMethod.GENERATOR, object, (instance, method) -> - table.rawset(method.name(), method.nonYielding() - ? new BasicFunction(this, method.method(), instance, context, method.name()) - : new ResultInterpreterFunction(this, method.method(), instance, context, method.name()))); - - try { - if (table.next(Constants.NIL).first().isNil()) return null; - } catch (LuaError ignored) { - // next should never throw on nil. - } - - return table; + return found ? table : null; } private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap values) { diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/MachineEnvironment.java b/projects/core/src/main/java/dan200/computercraft/core/lua/MachineEnvironment.java index f3b7f51d5..2e9d5529c 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/MachineEnvironment.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/MachineEnvironment.java @@ -8,6 +8,8 @@ import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.TimeoutState; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.metrics.MetricsObserver; /** @@ -18,6 +20,7 @@ import dan200.computercraft.core.metrics.MetricsObserver; * @param timeout The current timeout state. This should be used by the machine to interrupt its execution. * @param apis APIs to inject into the global environment. Each API should be converted into a Lua object * (following the same rules as any other value), and then set to all names in {@link ILuaAPI#getNames()}. + * @param luaMethods A {@link MethodSupplier} to find methods on returned values. * @param hostString A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment. * @see ILuaMachine.Factory */ @@ -26,6 +29,7 @@ public record MachineEnvironment( MetricsObserver metrics, TimeoutState timeout, Iterable apis, + MethodSupplier luaMethods, String hostString ) { } diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/projects/core/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index 04ed846f0..57e09f561 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.core.Logging; -import dan200.computercraft.core.asm.LuaMethod; +import dan200.computercraft.core.methods.LuaMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.squiddev.cobalt.*; diff --git a/projects/core/src/main/java/dan200/computercraft/core/methods/LuaMethod.java b/projects/core/src/main/java/dan200/computercraft/core/methods/LuaMethod.java new file mode 100644 index 000000000..58052f23a --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/methods/LuaMethod.java @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.methods; + +import dan200.computercraft.api.lua.*; + +/** + * A basic Lua function (i.e. one not associated with a peripheral) on some object (such as a {@link IDynamicLuaObject} + * or {@link ILuaAPI}. + *

+ * This interface is not typically implemented yourself, but instead generated from a {@link LuaFunction}-annotated + * method. + * + * @see NamedMethod + */ +@FunctionalInterface +public interface LuaMethod { + /** + * Apply this method. + * + * @param target The object instance that this method targets. + * @param context The Lua context for this function call. + * @param args Arguments to this function. + * @return The return call of this function. + * @throws LuaException Thrown by the underlying method call. + * @see IDynamicLuaObject#callMethod(ILuaContext, int, IArguments) + */ + MethodResult apply(Object target, ILuaContext context, IArguments args) throws LuaException; +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/methods/MethodSupplier.java b/projects/core/src/main/java/dan200/computercraft/core/methods/MethodSupplier.java new file mode 100644 index 000000000..13f45b915 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/methods/MethodSupplier.java @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.methods; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +/** + * Finds methods available on an object and yields them. + * + * @param The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}. + */ +public interface MethodSupplier { + /** + * Iterate over methods available on an object, ignoring {@link ObjectSource}s. + * + * @param object The object to find methods for. + * @param consumer The consumer to call for each method. + * @return Whether any methods were found. + */ + boolean forEachSelfMethod(Object object, UntargetedConsumer consumer); + + /** + * Generate a map of all methods targeting the current object, ignoring {@link ObjectSource}s. + * + * @param object The object to find methods for. + * @return A map of all methods on the object. + */ + default Map getSelfMethods(Object object) { + var map = new HashMap(); + forEachSelfMethod(object, (n, m, i) -> map.put(n, m)); + return map; + } + + /** + * Iterate over all methods on an object, including {@link ObjectSource}s. + * + * @param object The object to find methods for. + * @param consumer The consumer to call for each method. + * @return Whether any methods were found. + */ + boolean forEachMethod(Object object, TargetedConsumer consumer); + + /** + * A function which is called for each method on an object. + * + * @param The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}. + * @see #forEachSelfMethod(Object, UntargetedConsumer) + */ + @FunctionalInterface + interface UntargetedConsumer { + /** + * Consume a method on an object. + * + * @param name The name of this method. + * @param method The actual method definition. + * @param info Additional information about the method, such as whether it will yield. May be {@code null}. + */ + void accept(String name, T method, @Nullable NamedMethod info); + } + + /** + * A function which is called for each method on an object and possibly nested objects. + * + * @param The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}. + * @see #forEachMethod(Object, TargetedConsumer) + */ + @FunctionalInterface + interface TargetedConsumer { + /** + * Consume a method on an object. + * + * @param object The object this method targets, should be passed to the method's {@code apply(...)} function. + * @param name The name of this method. + * @param method The actual method definition. + * @param info Additional information about the method, such as whether it will yield. May be {@code null}. + */ + void accept(Object object, String name, T method, @Nullable NamedMethod info); + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/asm/NamedMethod.java b/projects/core/src/main/java/dan200/computercraft/core/methods/NamedMethod.java similarity index 96% rename from projects/core/src/main/java/dan200/computercraft/core/asm/NamedMethod.java rename to projects/core/src/main/java/dan200/computercraft/core/methods/NamedMethod.java index 4c4cfbf8d..f4f5de441 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/asm/NamedMethod.java +++ b/projects/core/src/main/java/dan200/computercraft/core/methods/NamedMethod.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -package dan200.computercraft.core.asm; +package dan200.computercraft.core.methods; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.MethodResult; diff --git a/projects/core/src/main/java/dan200/computercraft/core/methods/ObjectSource.java b/projects/core/src/main/java/dan200/computercraft/core/methods/ObjectSource.java new file mode 100644 index 000000000..1ce62fe41 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/methods/ObjectSource.java @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.methods; + +/** + * A Lua object which exposes additional methods. + *

+ * This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely + * happy with the interface - something I'd like to think about first. + */ +public interface ObjectSource { + Iterable getExtra(); +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/methods/PeripheralMethod.java b/projects/core/src/main/java/dan200/computercraft/core/methods/PeripheralMethod.java new file mode 100644 index 000000000..0269146fe --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/methods/PeripheralMethod.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.methods; + +import dan200.computercraft.api.lua.*; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IDynamicPeripheral; +import dan200.computercraft.api.peripheral.IPeripheral; + +/** + * A Lua function associated with some peripheral. + *

+ * This interface is not typically implemented yourself, but instead generated from a {@link LuaFunction}-annotated + * method. + * + * @see NamedMethod + * @see IPeripheral + */ +@FunctionalInterface +public interface PeripheralMethod { + /** + * Apply this method. + * + * @param target The object instance that this method targets. + * @param context The Lua context for this function call. + * @param computer The interface to the computer that is making the call. + * @param args Arguments to this function. + * @return The return call of this function. + * @throws LuaException Thrown by the underlying method call. + * @see IDynamicPeripheral#callMethod(IComputerAccess, ILuaContext, int, IArguments) + */ + MethodResult apply(Object target, ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException; +} diff --git a/projects/core/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java b/projects/core/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java index 513848e7b..6b389a1e8 100644 --- a/projects/core/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java +++ b/projects/core/src/test/java/dan200/computercraft/core/apis/ObjectWrapper.java @@ -4,32 +4,25 @@ package dan200.computercraft.core.apis; -import dan200.computercraft.api.lua.*; -import dan200.computercraft.core.asm.LuaMethod; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaTask; +import dan200.computercraft.api.lua.ObjectArguments; +import dan200.computercraft.core.asm.LuaMethodSupplier; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.MethodSupplier; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; public class ObjectWrapper implements ILuaContext { + private static final MethodSupplier LUA_METHODS = LuaMethodSupplier.create(); + private final Object object; private final Map methodMap; public ObjectWrapper(Object object) { this.object = object; - var dynamicMethods = object instanceof IDynamicLuaObject dynamic - ? Objects.requireNonNull(dynamic.getMethodNames(), "Methods cannot be null") - : LuaMethod.EMPTY_METHODS; - - var methods = LuaMethod.GENERATOR.getMethods(object.getClass()); - - var methodMap = this.methodMap = new HashMap<>(methods.size() + dynamicMethods.length); - for (var i = 0; i < dynamicMethods.length; i++) { - methodMap.put(dynamicMethods[i], LuaMethod.DYNAMIC.get(i)); - } - for (var method : methods) { - methodMap.put(method.name(), method.method()); - } + methodMap = LUA_METHODS.getSelfMethods(object); } public Object[] call(String name, Object... args) throws LuaException { diff --git a/projects/core/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java b/projects/core/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java index 85de1ba28..cc504eb27 100644 --- a/projects/core/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java +++ b/projects/core/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java @@ -7,6 +7,8 @@ package dan200.computercraft.core.asm; import dan200.computercraft.api.lua.*; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.methods.LuaMethod; +import dan200.computercraft.core.methods.NamedMethod; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; @@ -21,9 +23,11 @@ import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; public class GeneratorTest { + private static final Generator GENERATOR = LuaMethodSupplier.GENERATOR; + @Test public void testBasic() { - var methods = LuaMethod.GENERATOR.getMethods(Basic.class); + var methods = GENERATOR.getMethods(Basic.class); assertThat(methods, contains( allOf( named("go"), @@ -34,48 +38,48 @@ public class GeneratorTest { @Test public void testIdentical() { - var methods = LuaMethod.GENERATOR.getMethods(Basic.class); - var methods2 = LuaMethod.GENERATOR.getMethods(Basic.class); + var methods = GENERATOR.getMethods(Basic.class); + var methods2 = GENERATOR.getMethods(Basic.class); assertThat(methods, sameInstance(methods2)); } @Test public void testIdenticalMethods() { - var methods = LuaMethod.GENERATOR.getMethods(Basic.class); - var methods2 = LuaMethod.GENERATOR.getMethods(Basic2.class); + var methods = GENERATOR.getMethods(Basic.class); + var methods2 = GENERATOR.getMethods(Basic2.class); assertThat(methods, contains(named("go"))); assertThat(methods.get(0).method(), sameInstance(methods2.get(0).method())); } @Test public void testEmptyClass() { - assertThat(LuaMethod.GENERATOR.getMethods(Empty.class), is(empty())); + assertThat(GENERATOR.getMethods(Empty.class), is(empty())); } @Test public void testNonPublicClass() { - assertThat(LuaMethod.GENERATOR.getMethods(NonPublic.class), is(empty())); + assertThat(GENERATOR.getMethods(NonPublic.class), is(empty())); } @Test public void testNonInstance() { - assertThat(LuaMethod.GENERATOR.getMethods(NonInstance.class), is(empty())); + assertThat(GENERATOR.getMethods(NonInstance.class), is(empty())); } @Test public void testIllegalThrows() { - assertThat(LuaMethod.GENERATOR.getMethods(IllegalThrows.class), is(empty())); + assertThat(GENERATOR.getMethods(IllegalThrows.class), is(empty())); } @Test public void testCustomNames() { - var methods = LuaMethod.GENERATOR.getMethods(CustomNames.class); + var methods = GENERATOR.getMethods(CustomNames.class); assertThat(methods, contains(named("go1"), named("go2"))); } @Test public void testArgKinds() { - var methods = LuaMethod.GENERATOR.getMethods(ArgKinds.class); + var methods = GENERATOR.getMethods(ArgKinds.class); assertThat(methods, containsInAnyOrder( named("objectArg"), named("intArg"), named("optIntArg"), named("context"), named("arguments") @@ -84,7 +88,7 @@ public class GeneratorTest { @Test public void testEnum() throws LuaException { - var methods = LuaMethod.GENERATOR.getMethods(EnumMethods.class); + var methods = GENERATOR.getMethods(EnumMethods.class); assertThat(methods, containsInAnyOrder(named("getEnum"), named("optEnum"))); assertThat(apply(methods, new EnumMethods(), "getEnum", "front"), one(is("FRONT"))); @@ -95,7 +99,7 @@ public class GeneratorTest { @Test public void testMainThread() throws LuaException { - var methods = LuaMethod.GENERATOR.getMethods(MainThread.class); + var methods = GENERATOR.getMethods(MainThread.class); assertThat(methods, contains(allOf( named("go"), contramap(is(false), "non-yielding", NamedMethod::nonYielding) @@ -107,7 +111,7 @@ public class GeneratorTest { @Test public void testUnsafe() { - var methods = LuaMethod.GENERATOR.getMethods(Unsafe.class); + var methods = GENERATOR.getMethods(Unsafe.class); assertThat(methods, contains(named("withUnsafe"))); } diff --git a/projects/core/src/test/java/dan200/computercraft/core/asm/MethodTest.java b/projects/core/src/test/java/dan200/computercraft/core/asm/MethodTest.java index bfff064e1..a61b15ec0 100644 --- a/projects/core/src/test/java/dan200/computercraft/core/asm/MethodTest.java +++ b/projects/core/src/test/java/dan200/computercraft/core/asm/MethodTest.java @@ -10,6 +10,7 @@ import dan200.computercraft.api.peripheral.IDynamicPeripheral; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.computer.ComputerBootstrap; import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.methods.ObjectSource; import org.junit.jupiter.api.Test; import javax.annotation.Nullable; diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java index a9eefd5bb..ac0d54e92 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java @@ -11,11 +11,15 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.List; public class GenericPeripheralProvider { + private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); + interface Lookup { @Nullable T find(Level world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, Direction context); @@ -29,7 +33,13 @@ public class GenericPeripheralProvider { public static IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) { if (blockEntity == null) return null; - var builder = new GenericPeripheralBuilder(); + var server = level.getServer(); + if (server == null) { + LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level.")); + return null; + } + + var builder = new GenericPeripheralBuilder(server); builder.addMethods(blockEntity); for (var lookup : lookups) { diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java index d8c275101..64c9333e1 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java @@ -11,12 +11,16 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.Level; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.NonNullConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Objects; public class GenericPeripheralProvider { + private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class); + private static final ArrayList> capabilities = new ArrayList<>(); public static synchronized void addCapability(Capability capability) { @@ -29,7 +33,13 @@ public class GenericPeripheralProvider { var blockEntity = level.getBlockEntity(pos); if (blockEntity == null) return null; - var builder = new GenericPeripheralBuilder(); + var server = level.getServer(); + if (server == null) { + LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level.")); + return null; + } + + var builder = new GenericPeripheralBuilder(server); builder.addMethods(blockEntity); for (var capability : capabilities) {