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) {