diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index b73ba910e..1ed1ac999 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -21,7 +21,6 @@ import dan200.computercraft.api.turtle.event.TurtleAction; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRule; import dan200.computercraft.core.apis.http.websocket.Websocket; -import dan200.computercraft.core.asm.GenericSource; import dan200.computercraft.shared.common.ColourableRecipe; import dan200.computercraft.shared.computer.core.ClientComputerRegistry; import dan200.computercraft.shared.computer.core.ServerComputerRegistry; @@ -39,7 +38,6 @@ import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.util.Config; import dan200.computercraft.shared.util.ImpostorRecipe; import dan200.computercraft.shared.util.ImpostorShapelessRecipe; -import dan200.computercraft.shared.util.ServiceUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -132,7 +130,6 @@ public final class ComputerCraft implements ModInitializer { Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "player_creative"), PlayerCreativeLootCondition.TYPE); Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "has_id"), HasComputerIdLootCondition.TYPE); init(); - GenericSource.setup( () -> ServiceUtil.loadServices( GenericSource.class )); FabricLoader.getInstance().getModContainer(MOD_ID).ifPresent(modContainer -> { ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MOD_ID, "classic"), modContainer, ResourcePackActivationType.NORMAL); ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MOD_ID, "overhaul"), modContainer, ResourcePackActivationType.NORMAL); diff --git a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java index 776fec7ca..4dcb559a9 100644 --- a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java +++ b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java @@ -16,6 +16,7 @@ import javax.annotation.Nullable; import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.GenericSource; import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.network.IPacketNetwork; @@ -26,6 +27,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.redstone.IBundledRedstoneProvider; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.core.apis.ApiFactories; +import dan200.computercraft.core.asm.GenericMethod; import dan200.computercraft.core.filesystem.FileMount; import dan200.computercraft.core.filesystem.ResourceMount; import dan200.computercraft.mixin.MinecraftServerAccess; @@ -143,6 +145,12 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI { PocketUpgrades.register(upgrade); } + @Override + public void registerGenericSource( @Nonnull GenericSource source ) + { + GenericMethod.register( source ); + } + @Nonnull @Override public IPacketNetwork getWirelessNetwork() { diff --git a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java index 4868de353..9699b6173 100644 --- a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java +++ b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java @@ -11,6 +11,7 @@ import javax.annotation.Nullable; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.GenericSource; import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMediaProvider; @@ -136,6 +137,17 @@ public final class ComputerCraftAPI { getInstance().registerPeripheralProvider(provider); } + /** + * Registers a method source for generic peripherals. + * + * @param source The method source to register. + * @see GenericSource + */ + public static void registerGenericSource( @Nonnull GenericSource source ) + { + getInstance().registerGenericSource( source ); + } + /** * Registers a new turtle turtle for use in ComputerCraft. After calling this, users should be able to craft Turtles with your new turtle. It is * recommended to call this during the load() method of your mod. @@ -238,6 +250,8 @@ public final class ComputerCraftAPI { void registerPeripheralProvider(@Nonnull IPeripheralProvider provider); + void registerGenericSource( @Nonnull GenericSource source ); + void registerTurtleUpgrade(@Nonnull ITurtleUpgrade upgrade); void registerBundledRedstoneProvider(@Nonnull IBundledRedstoneProvider provider); diff --git a/src/main/java/dan200/computercraft/api/lua/GenericSource.java b/src/main/java/dan200/computercraft/api/lua/GenericSource.java new file mode 100644 index 000000000..6b054ee9b --- /dev/null +++ b/src/main/java/dan200/computercraft/api/lua/GenericSource.java @@ -0,0 +1,58 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.api.lua; + +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralProvider; +import dan200.computercraft.core.asm.LuaMethod; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; + +/** + * A generic source of {@link LuaMethod} functions. + * + * Unlike normal objects ({@link IDynamicLuaObject} or {@link IPeripheral}), methods do not target this object but + * instead are defined as {@code static} and accept their target as the first parameter. This allows you to inject + * methods onto objects you do not own, as well as declaring methods for a specific "trait" (for instance, a + * {@link Capability}). + * + * Currently the "generic peripheral" system is incompatible with normal peripherals. Normal {@link IPeripheralProvider} + * or {@link IPeripheral} implementations take priority. Tile entities which use this system are given a peripheral name + * determined by their id, rather than any peripheral provider. This will hopefully change in the future, once a suitable + * design has been established. + * + * For example, the main CC: Tweaked mod defines a generic source for inventories, which works on {@link IItemHandler}s: + * + *
{@code
+ * public class InventoryMethods implements GenericSource {
+ *     @LuaFunction( mainThread = true )
+ *     public static int size(IItemHandler inventory) {
+ *         return inventory.getSlots();
+ *     }
+ *
+ *     // ...
+ * }
+ * }
+ * + * @see ComputerCraftAPI#registerGenericSource(GenericSource) + * @see ComputerCraftAPI#registerGenericCapability(Capability) New capabilities (those not built into Forge) must be + * explicitly given to the generic peripheral system, as there is no way to enumerate all capabilities. + */ +public interface GenericSource +{ + /** + * A unique identifier for this generic source. + * + * This is currently unused, but may be used in the future to allow disabling specific sources. It is recommended + * to return an identifier using your mod's ID. + * + * @return This source's identifier. + */ + @Nonnull + Identifier id(); +} diff --git a/src/main/java/dan200/computercraft/core/asm/Generator.java b/src/main/java/dan200/computercraft/core/asm/Generator.java index e600880c0..a3c028076 100644 --- a/src/main/java/dan200/computercraft/core/asm/Generator.java +++ b/src/main/java/dan200/computercraft/core/asm/Generator.java @@ -36,10 +36,7 @@ import com.google.common.cache.LoadingCache; import com.google.common.primitives.Primitives; import com.google.common.reflect.TypeToken; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.lua.IArguments; -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.api.lua.LuaFunction; -import dan200.computercraft.api.lua.MethodResult; +import dan200.computercraft.api.lua.*; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; @@ -120,10 +117,9 @@ public final class Generator { this.addMethod(methods, method, annotation, instance); } - for (GenericSource.GenericMethod method : GenericSource.GenericMethod.all()) { - if (!method.target.isAssignableFrom(klass)) { - continue; - } + for( GenericMethod method : GenericMethod.all() ) + { + if( !method.target.isAssignableFrom( klass ) ) continue; T instance = this.methodCache.getUnchecked(method.method) .orElse(null); diff --git a/src/main/java/dan200/computercraft/core/asm/GenericMethod.java b/src/main/java/dan200/computercraft/core/asm/GenericMethod.java new file mode 100644 index 000000000..ebd589112 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/asm/GenericMethod.java @@ -0,0 +1,85 @@ +package dan200.computercraft.core.asm; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.GenericSource; +import dan200.computercraft.api.lua.LuaFunction; + +import javax.annotation.Nonnull; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * A generic method is a method belonging to a {@link GenericSource} with a known target. + */ +public class GenericMethod +{ + final Method method; + final LuaFunction annotation; + final Class target; + + private static final List sources = new ArrayList<>(); + private static List cache; + + GenericMethod( Method method, LuaFunction annotation, Class target ) + { + this.method = method; + this.annotation = annotation; + this.target = target; + } + + /** + * Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}. + * + * @return All available generic methods. + */ + static List all() + { + if( cache != null ) return cache; + return cache = sources.stream() + .flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) ) + .map( method -> + { + LuaFunction annotation = method.getAnnotation( LuaFunction.class ); + if( annotation == null ) return null; + + if( !Modifier.isStatic( method.getModifiers() ) ) + { + ComputerCraft.log.error( "GenericSource method {}.{} should be static.", method.getDeclaringClass(), method.getName() ); + return null; + } + + Type[] types = method.getGenericParameterTypes(); + if( types.length == 0 ) + { + ComputerCraft.log.error( "GenericSource method {}.{} has no parameters.", method.getDeclaringClass(), method.getName() ); + return null; + } + + Class target = Reflect.getRawType( method, types[0], false ); + if( target == null ) return null; + + return new GenericMethod( method, annotation, target ); + } ) + .filter( Objects::nonNull ) + .collect( Collectors.toList() ); + } + + + public static synchronized void register( @Nonnull GenericSource source ) + { + Objects.requireNonNull( source, "Source cannot be null" ); + + if( cache != null ) + { + ComputerCraft.log.warn( "Registering a generic source {} after cache has been built. This source will be ignored.", cache ); + } + + sources.add( source ); + } +} diff --git a/src/main/java/dan200/computercraft/core/asm/GenericSource.java b/src/main/java/dan200/computercraft/core/asm/GenericSource.java deleted file mode 100644 index 9c870c814..000000000 --- a/src/main/java/dan200/computercraft/core/asm/GenericSource.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.core.asm; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.annotation.Nonnull; - -import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.lua.LuaFunction; - -import net.minecraft.util.Identifier; - -/** - * A generic source of {@link LuaMethod} functions. This allows for injecting methods onto objects you do not own. - * - * Unlike conventional Lua objects, the annotated methods should be {@code static}, with their target as the first parameter. - */ -public interface GenericSource { - /** - * Register a stream of generic sources. - * - * @param sources The source of generic methods. - */ - static void setup(Supplier> sources) { - GenericMethod.sources = sources; - } - - /** - * A unique identifier for this generic source. This may be used in the future to allow disabling specific sources. - * - * @return This source's identifier. - */ - @Nonnull - Identifier id(); - - /** - * A generic method is a method belonging to a {@link GenericSource} with a known target. - */ - class GenericMethod { - static Supplier> sources; - private static List cache; - final Method method; - final LuaFunction annotation; - final Class target; - - GenericMethod(Method method, LuaFunction annotation, Class target) { - this.method = method; - this.annotation = annotation; - this.target = target; - } - - /** - * Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}. - * - * @return All available generic methods. - */ - static List all() { - if (cache != null) { - return cache; - } - if (sources == null) { - ComputerCraft.log.warn("Getting GenericMethods without a provider"); - return cache = Collections.emptyList(); - } - - return cache = sources.get() - .flatMap(x -> Arrays.stream(x.getClass() - .getDeclaredMethods())) - .map(method -> { - LuaFunction annotation = method.getAnnotation(LuaFunction.class); - if (annotation == null) { - return null; - } - - if (!Modifier.isStatic(method.getModifiers())) { - ComputerCraft.log.error("GenericSource method {}.{} should be static.", - method.getDeclaringClass(), - method.getName()); - return null; - } - - Type[] types = method.getGenericParameterTypes(); - if (types.length == 0) { - ComputerCraft.log.error("GenericSource method {}.{} has no parameters.", - method.getDeclaringClass(), - method.getName()); - return null; - } - - Class target = Reflect.getRawType(method, types[0], false); - if (target == null) { - return null; - } - - return new GenericMethod(method, annotation, target); - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - } -} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java index c03576ee6..86267a3ba 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/methods/InventoryMethods.java @@ -5,13 +5,12 @@ */ package dan200.computercraft.shared.peripheral.generic.methods; -import com.google.auto.service.AutoService; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.GenericSource; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.core.asm.GenericSource; import dan200.computercraft.shared.peripheral.generic.data.ItemData; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.ItemStorage; @@ -34,14 +33,13 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel * * @cc.module inventory */ -@AutoService( GenericSource.class ) public class InventoryMethods implements GenericSource { @Nonnull @Override public Identifier id() { - return new Identifier(ComputerCraft.MOD_ID, "inventory" ); + return new Identifier( ComputerCraft.MOD_ID, "inventory" ); } /** diff --git a/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java b/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java deleted file mode 100644 index 9e4772450..000000000 --- a/src/main/java/dan200/computercraft/shared/util/ServiceUtil.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.shared.util; - -import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.ComputerCraftAPI; -import org.objectweb.asm.Type; - -import java.util.List; -import java.util.ServiceLoader; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -public final class ServiceUtil -{ - private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" ); - - private ServiceUtil() - { - } - - public static Stream loadServices( Class target ) - { - return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false ); - } -}