diff --git a/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java b/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java
new file mode 100644
index 000000000..02e13fd7a
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/peripheral/GenericPeripheral.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.peripheral;
+
+import dan200.computercraft.api.lua.GenericSource;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A {@link GenericSource} which provides methods for a peripheral.
+ *
+ * Unlike a {@link GenericSource}, all methods should target the same type, for instance a
+ * {@link TileEntity} subclass or a capability interface. This is not currently enforced.
+ */
+public interface GenericPeripheral extends GenericSource
+{
+ /**
+ * Get the type of the exposed peripheral.
+ *
+ * Unlike normal {@link IPeripheral}s, {@link GenericPeripheral} do not have to have a type. By default, the
+ * resulting peripheral uses the resource name of the wrapped {@link TileEntity} (for instance {@literal minecraft:chest}).
+ *
+ * However, in some cases it may be more appropriate to specify a more readable name. Overriding this method allows
+ * you to do so.
+ *
+ * When multiple {@link GenericPeripheral}s return a non-empty peripheral type for a single tile entity, the
+ * lexicographically smallest will be chosen. In order to avoid this conflict, this method should only be
+ * implemented when your peripheral targets a single tile entity AND it's likely that you're the
+ * only mod to do so. Similarly this should NOT be implemented when your methods target a
+ * capability or other interface (i.e. {@link IItemHandler}).
+ *
+ * @return The type of this peripheral or {@link PeripheralType#untyped()}.
+ * @see IPeripheral#getType()
+ */
+ @Nonnull
+ default PeripheralType getType()
+ {
+ return PeripheralType.untyped();
+ }
+}
diff --git a/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java b/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java
new file mode 100644
index 000000000..2780534c2
--- /dev/null
+++ b/src/main/java/dan200/computercraft/api/peripheral/PeripheralType.java
@@ -0,0 +1,62 @@
+/*
+ * This file is part of the public ComputerCraft API - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only.
+ * For help using the API, and posting your mods, visit the forums at computercraft.info.
+ */
+package dan200.computercraft.api.peripheral;
+
+import com.google.common.base.Strings;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * The type of a {@link GenericPeripheral}.
+ *
+ * When determining the final type of the resulting peripheral, the union of all types is taken, with the
+ * lexicographically smallest non-empty name being chosen.
+ */
+public final class PeripheralType
+{
+ private static final PeripheralType UNTYPED = new PeripheralType( null );
+
+ private final String type;
+
+ public PeripheralType( String type )
+ {
+ this.type = type;
+ }
+
+ /**
+ * An empty peripheral type, used when a {@link GenericPeripheral} does not have an explicit type.
+ *
+ * @return The empty peripheral type.
+ */
+ public static PeripheralType untyped()
+ {
+ return UNTYPED;
+ }
+
+ /**
+ * Create a new non-empty peripheral type.
+ *
+ * @param type The name of the type.
+ * @return The constructed peripheral type.
+ */
+ public static PeripheralType ofType( @Nonnull String type )
+ {
+ if( Strings.isNullOrEmpty( type ) ) throw new IllegalArgumentException( "type cannot be null or empty" );
+ return new PeripheralType( type );
+ }
+
+ /**
+ * Get the name of this peripheral type. This may be {@literal null}.
+ *
+ * @return The type of this peripheral.
+ */
+ @Nullable
+ public String getPrimaryType()
+ {
+ return type;
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/asm/Generator.java b/src/main/java/dan200/computercraft/core/asm/Generator.java
index e68732f95..4cd2d66cf 100644
--- a/src/main/java/dan200/computercraft/core/asm/Generator.java
+++ b/src/main/java/dan200/computercraft/core/asm/Generator.java
@@ -15,6 +15,7 @@ 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.peripheral.PeripheralType;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -108,7 +109,7 @@ public final class Generator
if( instance == null ) continue;
if( methods == null ) methods = new ArrayList<>();
- addMethod( methods, method, annotation, instance );
+ addMethod( methods, method, annotation, null, instance );
}
for( GenericMethod method : GenericMethod.all() )
@@ -119,7 +120,7 @@ public final class Generator
if( instance == null ) continue;
if( methods == null ) methods = new ArrayList<>();
- addMethod( methods, method.method, method.annotation, instance );
+ addMethod( methods, method.method, method.annotation, method.peripheralType, instance );
}
if( methods == null ) return Collections.emptyList();
@@ -127,7 +128,7 @@ public final class Generator
return Collections.unmodifiableList( methods );
}
- private void addMethod( List> methods, Method method, LuaFunction annotation, T instance )
+ private void addMethod( List> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance )
{
if( annotation.mainThread() ) instance = wrap.apply( instance );
@@ -135,13 +136,13 @@ public final class Generator
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
if( names.length == 0 )
{
- methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
+ methods.add( new NamedMethod<>( method.getName(), instance, isSimple, genericType ) );
}
else
{
for( String name : names )
{
- methods.add( new NamedMethod<>( name, instance, isSimple ) );
+ methods.add( new NamedMethod<>( name, instance, isSimple, genericType ) );
}
}
}
diff --git a/src/main/java/dan200/computercraft/core/asm/GenericMethod.java b/src/main/java/dan200/computercraft/core/asm/GenericMethod.java
index e0c916c2e..691d9b742 100644
--- a/src/main/java/dan200/computercraft/core/asm/GenericMethod.java
+++ b/src/main/java/dan200/computercraft/core/asm/GenericMethod.java
@@ -8,6 +8,8 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaFunction;
+import dan200.computercraft.api.peripheral.GenericPeripheral;
+import dan200.computercraft.api.peripheral.PeripheralType;
import javax.annotation.Nonnull;
import java.lang.reflect.Method;
@@ -18,6 +20,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* A generic method is a method belonging to a {@link GenericSource} with a known target.
@@ -27,15 +30,17 @@ public class GenericMethod
final Method method;
final LuaFunction annotation;
final Class> target;
+ final PeripheralType peripheralType;
private static final List sources = new ArrayList<>();
private static List cache;
- GenericMethod( Method method, LuaFunction annotation, Class> target )
+ GenericMethod( Method method, LuaFunction annotation, Class> target, PeripheralType peripheralType )
{
this.method = method;
this.annotation = annotation;
this.target = target;
+ this.peripheralType = peripheralType;
}
/**
@@ -46,10 +51,28 @@ public class GenericMethod
static List all()
{
if( cache != null ) return cache;
- return cache = sources.stream()
- .flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) )
- .map( method ->
- {
+ return cache = sources.stream().flatMap( GenericMethod::getMethods ).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 );
+ }
+
+ private static Stream getMethods( GenericSource source )
+ {
+ Class> klass = source.getClass();
+ PeripheralType type = source instanceof GenericPeripheral ? ((GenericPeripheral) source).getType() : null;
+
+ return Arrays.stream( klass.getDeclaredMethods() )
+ .map( method -> {
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
if( annotation == null ) return null;
@@ -69,22 +92,8 @@ public class GenericMethod
Class> target = Reflect.getRawType( method, types[0], false );
if( target == null ) return null;
- return new GenericMethod( method, annotation, target );
+ return new GenericMethod( method, annotation, target, type );
} )
- .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 );
+ .filter( Objects::nonNull );
}
}
diff --git a/src/main/java/dan200/computercraft/core/asm/NamedMethod.java b/src/main/java/dan200/computercraft/core/asm/NamedMethod.java
index ea72bb7a4..35d9c0a77 100644
--- a/src/main/java/dan200/computercraft/core/asm/NamedMethod.java
+++ b/src/main/java/dan200/computercraft/core/asm/NamedMethod.java
@@ -5,7 +5,10 @@
*/
package dan200.computercraft.core.asm;
+import dan200.computercraft.api.peripheral.PeripheralType;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
public final class NamedMethod
{
@@ -13,11 +16,14 @@ public final class NamedMethod
private final T method;
private final boolean nonYielding;
- NamedMethod( String name, T method, boolean nonYielding )
+ private final PeripheralType genericType;
+
+ NamedMethod( String name, T method, boolean nonYielding, PeripheralType genericType )
{
this.name = name;
this.method = method;
this.nonYielding = nonYielding;
+ this.genericType = genericType;
}
@Nonnull
@@ -36,4 +42,10 @@ public final class NamedMethod
{
return nonYielding;
}
+
+ @Nullable
+ public PeripheralType getGenericType()
+ {
+ return genericType;
+ }
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java
index ebfa94a6f..8703f687e 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheral.java
@@ -25,11 +25,11 @@ class GenericPeripheral implements IDynamicPeripheral
private final TileEntity tile;
private final List methods;
- GenericPeripheral( TileEntity tile, List methods )
+ GenericPeripheral( TileEntity tile, String name, List methods )
{
ResourceLocation type = tile.getType().getRegistryName();
this.tile = tile;
- this.type = type == null ? "unknown" : type.toString();
+ this.type = name != null ? name : (type != null ? type.toString() : "unknown");
this.methods = methods;
}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java
index 8eab46f11..d6918a1fd 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/GenericPeripheralProvider.java
@@ -6,6 +6,7 @@
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 net.minecraft.tileentity.TileEntity;
@@ -38,10 +39,10 @@ public class GenericPeripheralProvider
TileEntity tile = world.getBlockEntity( pos );
if( tile == null ) return null;
- ArrayList saturated = new ArrayList<>( 0 );
+ GenericPeripheralBuilder saturated = new GenericPeripheralBuilder();
List> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() );
- if( !tileMethods.isEmpty() ) addSaturated( saturated, tile, tileMethods );
+ if( !tileMethods.isEmpty() ) saturated.addMethods( tile, tileMethods );
for( Capability> capability : capabilities )
{
@@ -50,20 +51,44 @@ public class GenericPeripheralProvider
List> capabilityMethods = PeripheralMethod.GENERATOR.getMethods( contents.getClass() );
if( capabilityMethods.isEmpty() ) return;
- addSaturated( saturated, contents, capabilityMethods );
+ saturated.addMethods( contents, capabilityMethods );
wrapper.addListener( cast( invalidate ) );
} );
}
- return saturated.isEmpty() ? null : new GenericPeripheral( tile, saturated );
+ return saturated.toPeripheral( tile );
}
- private static void addSaturated( ArrayList saturated, Object target, List> methods )
+ private static class GenericPeripheralBuilder
{
- saturated.ensureCapacity( saturated.size() + methods.size() );
- for( NamedMethod method : methods )
+ String name;
+ final ArrayList methods = new ArrayList<>( 0 );
+
+ IPeripheral toPeripheral( TileEntity tile )
{
- saturated.add( new SaturatedMethod( target, method ) );
+ if( methods.isEmpty() ) return null;
+
+ methods.trimToSize();
+ return new GenericPeripheral( tile, name, methods );
+ }
+
+ void addMethods( Object target, List> methods )
+ {
+ ArrayList saturatedMethods = this.methods;
+ saturatedMethods.ensureCapacity( saturatedMethods.size() + methods.size() );
+ for( NamedMethod method : methods )
+ {
+ saturatedMethods.add( new SaturatedMethod( target, method ) );
+
+ // If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
+ // don't change).
+ PeripheralType type = method.getGenericType();
+ if( type != null && type.getPrimaryType() != null )
+ {
+ String name = type.getPrimaryType();
+ if( this.name == null || this.name.compareTo( name ) > 0 ) this.name = name;
+ }
+ }
}
}