1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-06-08 17:44:11 +00:00

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<T> 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.
This commit is contained in:
Jonathan Coates 2023-06-26 19:32:09 +01:00
parent a29a516a3f
commit 591a7eca23
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
33 changed files with 449 additions and 188 deletions

View File

@ -14,6 +14,8 @@ import dan200.computercraft.core.computer.mainthread.MainThread;
import dan200.computercraft.core.computer.mainthread.MainThreadConfig; import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine; 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.AbstractComputerCraftAPI;
import dan200.computercraft.impl.ApiFactories; import dan200.computercraft.impl.ApiFactories;
import dan200.computercraft.shared.CommonHooks; import dan200.computercraft.shared.CommonHooks;
@ -134,6 +136,16 @@ public final class ServerContext {
return context; return context;
} }
/**
* Get the {@link MethodSupplier} used to find methods on peripherals.
*
* @return The {@link PeripheralMethod} method supplier.
* @see ComputerContext#peripheralMethods()
*/
public MethodSupplier<PeripheralMethod> peripheralMethods() {
return context.peripheralMethods();
}
/** /**
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}. * Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
*/ */

View File

@ -7,7 +7,7 @@ package dan200.computercraft.shared.computer.upload;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.ByteBufferChannel; 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.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;

View File

@ -6,9 +6,12 @@ package dan200.computercraft.shared.peripheral.generic;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType; import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.core.asm.NamedMethod; import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.asm.PeripheralMethod; 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.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -25,37 +28,36 @@ import java.util.Set;
* See the platform-specific peripheral providers for the usage of this. * See the platform-specific peripheral providers for the usage of this.
*/ */
final class GenericPeripheralBuilder { final class GenericPeripheralBuilder {
private final MethodSupplier<PeripheralMethod> peripheralMethods;
private @Nullable String name; private @Nullable String name;
private final Set<String> additionalTypes = new HashSet<>(0); private final Set<String> additionalTypes = new HashSet<>(0);
private final ArrayList<SaturatedMethod> methods = new ArrayList<>(0); private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
GenericPeripheralBuilder(MinecraftServer server) {
peripheralMethods = ServerContext.get(server).peripheralMethods();
}
@Nullable @Nullable
IPeripheral toPeripheral(BlockEntity tile, Direction side) { IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
if (methods.isEmpty()) return null; if (methods.isEmpty()) return null;
methods.trimToSize(); methods.trimToSize();
return new GenericPeripheral(tile, side, name, additionalTypes, methods); return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
} }
boolean addMethods(Object target) { boolean addMethods(Object target) {
var methods = PeripheralMethod.GENERATOR.getMethods(target.getClass()); return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
if (methods.isEmpty()) return false; methods.add(new SaturatedMethod(target, name, method));
var saturatedMethods = this.methods;
saturatedMethods.ensureCapacity(saturatedMethods.size() + methods.size());
for (var method : methods) {
saturatedMethods.add(new SaturatedMethod(target, method.name(), method.method()));
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
// don't change). // don't change).
var type = method.genericType(); var type = info == null ? null : info.genericType();
if (type != null && type.getPrimaryType() != null) { if (type != null && type.getPrimaryType() != null) {
var name = type.getPrimaryType(); var primaryType = type.getPrimaryType();
if (this.name == null || this.name.compareTo(name) > 0) this.name = name; if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
} }
if (type != null) additionalTypes.addAll(type.getAdditionalTypes()); if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
} });
return true;
} }
} }

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; 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. * A {@link PeripheralMethod} along with the method's target.

View File

@ -16,10 +16,12 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.api.peripheral.WorkMonitor;
import dan200.computercraft.core.apis.PeripheralAPI; 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.core.util.LuaUtil;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -284,7 +286,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) { private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) { 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); peripherals.put(periphName, wrapper);
wrapper.attach(); wrapper.attach();
} }
@ -314,7 +317,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
private volatile boolean attached; private volatile boolean attached;
private final Set<String> mounts = new HashSet<>(); private final Set<String> mounts = new HashSet<>();
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) { RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) {
this.element = element; this.element = element;
this.peripheral = peripheral; this.peripheral = peripheral;
this.computer = computer; 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"); type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
additionalTypes = peripheral.getAdditionalTypes(); additionalTypes = peripheral.getAdditionalTypes();
methodMap = PeripheralAPI.getMethods(peripheral); methodMap = methods;
} }
public void attach() { public void attach() {

View File

@ -5,12 +5,18 @@
package dan200.computercraft.core; package dan200.computercraft.core;
import dan200.computercraft.api.lua.ILuaAPIFactory; 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.ComputerThread;
import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler; import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler; import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler;
import dan200.computercraft.core.lua.CobaltLuaMachine; import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine; 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.CheckReturnValue;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -28,17 +34,22 @@ public final class ComputerContext {
private final MainThreadScheduler mainThreadScheduler; private final MainThreadScheduler mainThreadScheduler;
private final ILuaMachine.Factory luaFactory; private final ILuaMachine.Factory luaFactory;
private final List<ILuaAPIFactory> apiFactories; private final List<ILuaAPIFactory> apiFactories;
private final MethodSupplier<LuaMethod> luaMethods;
private final MethodSupplier<PeripheralMethod> peripheralMethods;
ComputerContext( ComputerContext(
GlobalEnvironment globalEnvironment, ComputerThread computerScheduler, GlobalEnvironment globalEnvironment, ComputerThread computerScheduler,
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory, MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
List<ILuaAPIFactory> apiFactories List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
MethodSupplier<PeripheralMethod> peripheralMethods
) { ) {
this.globalEnvironment = globalEnvironment; this.globalEnvironment = globalEnvironment;
this.computerScheduler = computerScheduler; this.computerScheduler = computerScheduler;
this.mainThreadScheduler = mainThreadScheduler; this.mainThreadScheduler = mainThreadScheduler;
this.luaFactory = luaFactory; this.luaFactory = luaFactory;
this.apiFactories = apiFactories; this.apiFactories = apiFactories;
this.luaMethods = luaMethods;
this.peripheralMethods = peripheralMethods;
} }
/** /**
@ -87,6 +98,25 @@ public final class ComputerContext {
return apiFactories; return apiFactories;
} }
/**
* Get the {@link MethodSupplier} used to find methods on Lua values.
*
* @return The {@link LuaMethod} method supplier.
* @see MachineEnvironment#luaMethods()
*/
public MethodSupplier<LuaMethod> luaMethods() {
return luaMethods;
}
/**
* Get the {@link MethodSupplier} used to find methods on peripherals.
*
* @return The {@link PeripheralMethod} method supplier.
*/
public MethodSupplier<PeripheralMethod> peripheralMethods() {
return peripheralMethods;
}
/** /**
* Close the current {@link ComputerContext}, disposing of any resources inside. * Close the current {@link ComputerContext}, disposing of any resources inside.
* *
@ -206,7 +236,9 @@ public final class ComputerContext {
new ComputerThread(threads), new ComputerThread(threads),
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler, mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
luaFactory == null ? CobaltLuaMachine::new : luaFactory, luaFactory == null ? CobaltLuaMachine::new : luaFactory,
apiFactories == null ? List.of() : apiFactories apiFactories == null ? List.of() : apiFactories,
LuaMethodSupplier.create(),
PeripheralMethodSupplier.create()
); );
} }
} }

View File

@ -10,9 +10,9 @@ import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.metrics.OperationTimer;
import dan200.computercraft.core.metrics.Metric; import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.metrics.OperationTimer;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@ -7,13 +7,12 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.Mount; import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount; import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.api.peripheral.WorkMonitor; 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.computer.ComputerSide;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.util.LuaUtil; 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"); type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
additionalTypes = peripheral.getAdditionalTypes(); additionalTypes = peripheral.getAdditionalTypes();
methodMap = PeripheralAPI.getMethods(peripheral); methodMap = peripheralMethods.getSelfMethods(peripheral);
} }
public IPeripheral getPeripheral() { public IPeripheral getPeripheral() {
@ -172,11 +171,13 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
private final MethodSupplier<PeripheralMethod> peripheralMethods;
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6]; private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
private boolean running; private boolean running;
public PeripheralAPI(IAPIEnvironment environment) { public PeripheralAPI(IAPIEnvironment environment, MethodSupplier<PeripheralMethod> peripheralMethods) {
this.environment = environment; this.environment = environment;
this.peripheralMethods = peripheralMethods;
this.environment.setPeripheralChangeListener(this); this.environment.setPeripheralChangeListener(this);
running = false; running = false;
} }
@ -315,21 +316,4 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
throw e; throw e;
} }
} }
public static Map<String, PeripheralMethod> 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<String, PeripheralMethod> 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;
}
} }

View File

@ -10,7 +10,7 @@ import dan200.computercraft.core.apis.HTTPAPI;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle; import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle; import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.apis.handles.HandleGeneric; 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.Collections;
import java.util.Map; import java.util.Map;

View File

@ -11,6 +11,7 @@ import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.peripheral.PeripheralType; import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.core.methods.NamedMethod;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
@ -30,7 +31,7 @@ import java.util.function.Function;
import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Opcodes.*;
public final class Generator<T> { final class Generator<T> {
private static final Logger LOG = LoggerFactory.getLogger(Generator.class); private static final Logger LOG = LoggerFactory.getLogger(Generator.class);
private static final AtomicInteger METHOD_ID = new AtomicInteger(); private static final AtomicInteger METHOD_ID = new AtomicInteger();

View File

@ -7,7 +7,7 @@ package dan200.computercraft.core.asm;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.IntFunction; import java.util.function.IntFunction;
public final class IntCache<T> { final class IntCache<T> {
private final IntFunction<T> factory; private final IntFunction<T> factory;
private volatile Object[] cache = new Object[16]; private volatile Object[] cache = new Object[16];

View File

@ -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<LuaMethod> GENERATOR = new Generator<>(LuaMethod.class, Collections.singletonList(ILuaContext.class),
m -> (target, context, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, args.escapes())))
);
IntCache<LuaMethod> 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;
}

View File

@ -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<LuaMethod> 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<LuaMethod> DYNAMIC = new IntCache<>(
method -> (instance, context, args) -> ((IDynamicLuaObject) instance).callMethod(context, method, args)
);
private LuaMethodSupplier() {
}
public static MethodSupplier<LuaMethod> create() {
return new MethodSupplierImpl<>(GENERATOR, DYNAMIC, x -> x instanceof IDynamicLuaObject dynamic
? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null")
: null
);
}
}

View File

@ -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<T> implements MethodSupplier<T> {
private final Generator<T> generator;
private final IntCache<T> dynamic;
private final Function<Object, String[]> dynamicMethods;
MethodSupplierImpl(Generator<T> generator, IntCache<T> dynamic, Function<Object, String[]> dynamicMethods) {
this.generator = generator;
this.dynamic = dynamic;
this.dynamicMethods = dynamicMethods;
}
@Override
public boolean forEachSelfMethod(Object object, UntargetedConsumer<T> 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<T> 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;
}
}

View File

@ -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.
* <p>
* 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<Object> getExtra();
static <T> void allMethods(Generator<T> generator, Object object, BiConsumer<Object, NamedMethod<T>> 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);
}
}
}
}

View File

@ -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<PeripheralMethod> 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<PeripheralMethod> 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;
}

View File

@ -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<PeripheralMethod> 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<PeripheralMethod> DYNAMIC = new IntCache<>(
method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args)
);
public static MethodSupplier<PeripheralMethod> create() {
return new MethodSupplierImpl<>(GENERATOR, DYNAMIC, x -> x instanceof IDynamicPeripheral dynamic
? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null")
: null
);
}
}

View File

@ -15,6 +15,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineEnvironment; import dan200.computercraft.core.lua.MachineEnvironment;
import dan200.computercraft.core.lua.MachineException; 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.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
@ -61,6 +63,7 @@ final class ComputerExecutor {
private final MetricsObserver metrics; private final MetricsObserver metrics;
private final List<ILuaAPI> apis = new ArrayList<>(); private final List<ILuaAPI> apis = new ArrayList<>();
private final ComputerThread scheduler; private final ComputerThread scheduler;
private final MethodSupplier<LuaMethod> luaMethods;
final TimeoutState timeout; final TimeoutState timeout;
private @Nullable FileSystem fileSystem; private @Nullable FileSystem fileSystem;
@ -168,6 +171,7 @@ final class ComputerExecutor {
metrics = computerEnvironment.getMetrics(); metrics = computerEnvironment.getMetrics();
luaFactory = context.luaFactory(); luaFactory = context.luaFactory();
scheduler = context.computerScheduler(); scheduler = context.computerScheduler();
luaMethods = context.luaMethods();
timeout = new TimeoutState(scheduler); timeout = new TimeoutState(scheduler);
var environment = computer.getEnvironment(); var environment = computer.getEnvironment();
@ -176,7 +180,7 @@ final class ComputerExecutor {
apis.add(new TermAPI(environment)); apis.add(new TermAPI(environment));
apis.add(new RedstoneAPI(environment)); apis.add(new RedstoneAPI(environment));
apis.add(new FSAPI(environment)); apis.add(new FSAPI(environment));
apis.add(new PeripheralAPI(environment)); apis.add(new PeripheralAPI(environment, context.peripheralMethods()));
apis.add(new OSAPI(environment)); apis.add(new OSAPI(environment));
if (CoreConfig.httpEnabled) apis.add(new HTTPAPI(environment)); if (CoreConfig.httpEnabled) apis.add(new HTTPAPI(environment));
@ -382,6 +386,7 @@ final class ComputerExecutor {
return luaFactory.create(new MachineEnvironment( return luaFactory.create(new MachineEnvironment(
new LuaContext(computer), metrics, timeout, new LuaContext(computer), metrics, timeout,
() -> apis.stream().map(api -> api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api).iterator(), () -> apis.stream().map(api -> api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api).iterator(),
luaMethods,
computer.getGlobalEnvironment().getHostString() computer.getGlobalEnvironment().getHostString()
), bios); ), bios);
} catch (IOException e) { } catch (IOException e) {

View File

@ -8,7 +8,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.Logging; import dan200.computercraft.core.Logging;
import dan200.computercraft.core.asm.LuaMethod; import dan200.computercraft.core.methods.LuaMethod;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.squiddev.cobalt.LuaError; import org.squiddev.cobalt.LuaError;

View File

@ -10,9 +10,9 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaFunction; import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.core.CoreConfig; import dan200.computercraft.core.CoreConfig;
import dan200.computercraft.core.Logging; 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.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.Nullability;
import dan200.computercraft.core.util.SanitisedError; import dan200.computercraft.core.util.SanitisedError;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -39,6 +39,7 @@ public class CobaltLuaMachine implements ILuaMachine {
private final TimeoutState timeout; private final TimeoutState timeout;
private final Runnable timeoutListener = this::updateTimeout; private final Runnable timeoutListener = this::updateTimeout;
private final ILuaContext context; private final ILuaContext context;
private final MethodSupplier<LuaMethod> luaMethods;
private final LuaState state; private final LuaState state;
private final LuaThread mainRoutine; private final LuaThread mainRoutine;
@ -51,6 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine {
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException { public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
timeout = environment.timeout(); timeout = environment.timeout();
context = environment.context(); context = environment.context();
luaMethods = environment.luaMethods();
// Create an environment to run in // Create an environment to run in
var state = this.state = LuaState.builder() var state = this.state = LuaState.builder()
@ -161,28 +163,13 @@ public class CobaltLuaMachine implements ILuaMachine {
@Nullable @Nullable
private LuaTable wrapLuaObject(Object object) { 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(); var table = new LuaTable();
for (var i = 0; i < dynamicMethods.length; i++) { var found = luaMethods.forEachMethod(object, (target, name, method, info) ->
var method = dynamicMethods[i]; table.rawset(name, info != null && info.nonYielding()
table.rawset(method, new ResultInterpreterFunction(this, LuaMethod.DYNAMIC.get(i), object, context, method)); ? new BasicFunction(this, method, target, context, name)
} : new ResultInterpreterFunction(this, method, target, context, name)));
ObjectSource.allMethods(LuaMethod.GENERATOR, object, (instance, method) -> return found ? table : null;
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;
} }
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) { private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) {

View File

@ -8,6 +8,8 @@ import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.core.computer.GlobalEnvironment; import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.TimeoutState; import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.metrics.MetricsObserver; 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 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 * @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()}. * (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. * @param hostString A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment.
* @see ILuaMachine.Factory * @see ILuaMachine.Factory
*/ */
@ -26,6 +29,7 @@ public record MachineEnvironment(
MetricsObserver metrics, MetricsObserver metrics,
TimeoutState timeout, TimeoutState timeout,
Iterable<ILuaAPI> apis, Iterable<ILuaAPI> apis,
MethodSupplier<LuaMethod> luaMethods,
String hostString String hostString
) { ) {
} }

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.Logging; import dan200.computercraft.core.Logging;
import dan200.computercraft.core.asm.LuaMethod; import dan200.computercraft.core.methods.LuaMethod;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.squiddev.cobalt.*; import org.squiddev.cobalt.*;

View File

@ -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}.
* <p>
* 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;
}

View File

@ -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 <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
*/
public interface MethodSupplier<T> {
/**
* 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<T> 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<String, T> getSelfMethods(Object object) {
var map = new HashMap<String, T>();
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<T> consumer);
/**
* A function which is called for each method on an object.
*
* @param <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
* @see #forEachSelfMethod(Object, UntargetedConsumer)
*/
@FunctionalInterface
interface UntargetedConsumer<T> {
/**
* 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<T> info);
}
/**
* A function which is called for each method on an object and possibly nested objects.
*
* @param <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
* @see #forEachMethod(Object, TargetedConsumer)
*/
@FunctionalInterface
interface TargetedConsumer<T> {
/**
* 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<T> info);
}
}

View File

@ -2,7 +2,7 @@
// //
// SPDX-License-Identifier: MPL-2.0 // 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.LuaFunction;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;

View File

@ -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.
* <p>
* 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<Object> getExtra();
}

View File

@ -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.
* <p>
* 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;
}

View File

@ -4,32 +4,25 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.core.asm.LuaMethod; 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.Map;
import java.util.Objects;
public class ObjectWrapper implements ILuaContext { public class ObjectWrapper implements ILuaContext {
private static final MethodSupplier<LuaMethod> LUA_METHODS = LuaMethodSupplier.create();
private final Object object; private final Object object;
private final Map<String, LuaMethod> methodMap; private final Map<String, LuaMethod> methodMap;
public ObjectWrapper(Object object) { public ObjectWrapper(Object object) {
this.object = object; this.object = object;
var dynamicMethods = object instanceof IDynamicLuaObject dynamic methodMap = LUA_METHODS.getSelfMethods(object);
? 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());
}
} }
public Object[] call(String name, Object... args) throws LuaException { public Object[] call(String name, Object... args) throws LuaException {

View File

@ -7,6 +7,8 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.methods.NamedMethod;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -21,9 +23,11 @@ import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
public class GeneratorTest { public class GeneratorTest {
private static final Generator<LuaMethod> GENERATOR = LuaMethodSupplier.GENERATOR;
@Test @Test
public void testBasic() { public void testBasic() {
var methods = LuaMethod.GENERATOR.getMethods(Basic.class); var methods = GENERATOR.getMethods(Basic.class);
assertThat(methods, contains( assertThat(methods, contains(
allOf( allOf(
named("go"), named("go"),
@ -34,48 +38,48 @@ public class GeneratorTest {
@Test @Test
public void testIdentical() { public void testIdentical() {
var methods = LuaMethod.GENERATOR.getMethods(Basic.class); var methods = GENERATOR.getMethods(Basic.class);
var methods2 = LuaMethod.GENERATOR.getMethods(Basic.class); var methods2 = GENERATOR.getMethods(Basic.class);
assertThat(methods, sameInstance(methods2)); assertThat(methods, sameInstance(methods2));
} }
@Test @Test
public void testIdenticalMethods() { public void testIdenticalMethods() {
var methods = LuaMethod.GENERATOR.getMethods(Basic.class); var methods = GENERATOR.getMethods(Basic.class);
var methods2 = LuaMethod.GENERATOR.getMethods(Basic2.class); var methods2 = GENERATOR.getMethods(Basic2.class);
assertThat(methods, contains(named("go"))); assertThat(methods, contains(named("go")));
assertThat(methods.get(0).method(), sameInstance(methods2.get(0).method())); assertThat(methods.get(0).method(), sameInstance(methods2.get(0).method()));
} }
@Test @Test
public void testEmptyClass() { public void testEmptyClass() {
assertThat(LuaMethod.GENERATOR.getMethods(Empty.class), is(empty())); assertThat(GENERATOR.getMethods(Empty.class), is(empty()));
} }
@Test @Test
public void testNonPublicClass() { public void testNonPublicClass() {
assertThat(LuaMethod.GENERATOR.getMethods(NonPublic.class), is(empty())); assertThat(GENERATOR.getMethods(NonPublic.class), is(empty()));
} }
@Test @Test
public void testNonInstance() { public void testNonInstance() {
assertThat(LuaMethod.GENERATOR.getMethods(NonInstance.class), is(empty())); assertThat(GENERATOR.getMethods(NonInstance.class), is(empty()));
} }
@Test @Test
public void testIllegalThrows() { public void testIllegalThrows() {
assertThat(LuaMethod.GENERATOR.getMethods(IllegalThrows.class), is(empty())); assertThat(GENERATOR.getMethods(IllegalThrows.class), is(empty()));
} }
@Test @Test
public void testCustomNames() { public void testCustomNames() {
var methods = LuaMethod.GENERATOR.getMethods(CustomNames.class); var methods = GENERATOR.getMethods(CustomNames.class);
assertThat(methods, contains(named("go1"), named("go2"))); assertThat(methods, contains(named("go1"), named("go2")));
} }
@Test @Test
public void testArgKinds() { public void testArgKinds() {
var methods = LuaMethod.GENERATOR.getMethods(ArgKinds.class); var methods = GENERATOR.getMethods(ArgKinds.class);
assertThat(methods, containsInAnyOrder( assertThat(methods, containsInAnyOrder(
named("objectArg"), named("intArg"), named("optIntArg"), named("objectArg"), named("intArg"), named("optIntArg"),
named("context"), named("arguments") named("context"), named("arguments")
@ -84,7 +88,7 @@ public class GeneratorTest {
@Test @Test
public void testEnum() throws LuaException { 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(methods, containsInAnyOrder(named("getEnum"), named("optEnum")));
assertThat(apply(methods, new EnumMethods(), "getEnum", "front"), one(is("FRONT"))); assertThat(apply(methods, new EnumMethods(), "getEnum", "front"), one(is("FRONT")));
@ -95,7 +99,7 @@ public class GeneratorTest {
@Test @Test
public void testMainThread() throws LuaException { public void testMainThread() throws LuaException {
var methods = LuaMethod.GENERATOR.getMethods(MainThread.class); var methods = GENERATOR.getMethods(MainThread.class);
assertThat(methods, contains(allOf( assertThat(methods, contains(allOf(
named("go"), named("go"),
contramap(is(false), "non-yielding", NamedMethod::nonYielding) contramap(is(false), "non-yielding", NamedMethod::nonYielding)
@ -107,7 +111,7 @@ public class GeneratorTest {
@Test @Test
public void testUnsafe() { public void testUnsafe() {
var methods = LuaMethod.GENERATOR.getMethods(Unsafe.class); var methods = GENERATOR.getMethods(Unsafe.class);
assertThat(methods, contains(named("withUnsafe"))); assertThat(methods, contains(named("withUnsafe")));
} }

View File

@ -10,6 +10,7 @@ import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerBootstrap; import dan200.computercraft.core.computer.ComputerBootstrap;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.methods.ObjectSource;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@ -11,11 +11,15 @@ import net.minecraft.core.Direction;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
public class GenericPeripheralProvider { public class GenericPeripheralProvider {
private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class);
interface Lookup<T> { interface Lookup<T> {
@Nullable @Nullable
T find(Level world, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, Direction context); 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) { public static IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) {
if (blockEntity == null) return null; 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); builder.addMethods(blockEntity);
for (var lookup : lookups) { for (var lookup : lookups) {

View File

@ -11,12 +11,16 @@ import net.minecraft.core.Direction;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.NonNullConsumer; import net.minecraftforge.common.util.NonNullConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
public class GenericPeripheralProvider { public class GenericPeripheralProvider {
private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class);
private static final ArrayList<Capability<?>> capabilities = new ArrayList<>(); private static final ArrayList<Capability<?>> capabilities = new ArrayList<>();
public static synchronized void addCapability(Capability<?> capability) { public static synchronized void addCapability(Capability<?> capability) {
@ -29,7 +33,13 @@ public class GenericPeripheralProvider {
var blockEntity = level.getBlockEntity(pos); var blockEntity = level.getBlockEntity(pos);
if (blockEntity == null) return null; 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); builder.addMethods(blockEntity);
for (var capability : capabilities) { for (var capability : capabilities) {