mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-15 12:40:30 +00:00
Allow generic peripherals to specify a custom source
- Add a new GenericPeripheral interface. We don't strictly speaking need this - could put this on GenericSource - but the separation seems cleaner. - GenericPeripheral.getType() returns a new PeripheralType class, which can either be untyped() or specify a type name. This is a little over-engineered (could just be a nullable string), but I'm planning to allow multiple types in the future, so want some level of future-proofing. - Thread this PeripheralType through the method gathering code and expose it to the GenericPeripheralProvider, which then chooses an appropriate name. This is a little ugly (we're leaking information about peripherals everywhere), but I think is fine for now. It's all private internals after all! Closes #830
This commit is contained in:
parent
070479d901
commit
f33f57ea35
@ -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 <strong>should</strong> 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 <strong>AND</strong> it's likely that you're the
|
||||||
|
* only mod to do so. Similarly this should <strong>NOT</strong> 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import dan200.computercraft.api.lua.IArguments;
|
|||||||
import dan200.computercraft.api.lua.LuaException;
|
import dan200.computercraft.api.lua.LuaException;
|
||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import dan200.computercraft.api.lua.MethodResult;
|
import dan200.computercraft.api.lua.MethodResult;
|
||||||
|
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||||
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;
|
||||||
@ -108,7 +109,7 @@ public final class Generator<T>
|
|||||||
if( instance == null ) continue;
|
if( instance == null ) continue;
|
||||||
|
|
||||||
if( methods == null ) methods = new ArrayList<>();
|
if( methods == null ) methods = new ArrayList<>();
|
||||||
addMethod( methods, method, annotation, instance );
|
addMethod( methods, method, annotation, null, instance );
|
||||||
}
|
}
|
||||||
|
|
||||||
for( GenericMethod method : GenericMethod.all() )
|
for( GenericMethod method : GenericMethod.all() )
|
||||||
@ -119,7 +120,7 @@ public final class Generator<T>
|
|||||||
if( instance == null ) continue;
|
if( instance == null ) continue;
|
||||||
|
|
||||||
if( methods == null ) methods = new ArrayList<>();
|
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();
|
if( methods == null ) return Collections.emptyList();
|
||||||
@ -127,7 +128,7 @@ public final class Generator<T>
|
|||||||
return Collections.unmodifiableList( methods );
|
return Collections.unmodifiableList( methods );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMethod( List<NamedMethod<T>> methods, Method method, LuaFunction annotation, T instance )
|
private void addMethod( List<NamedMethod<T>> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance )
|
||||||
{
|
{
|
||||||
if( annotation.mainThread() ) instance = wrap.apply( instance );
|
if( annotation.mainThread() ) instance = wrap.apply( instance );
|
||||||
|
|
||||||
@ -135,13 +136,13 @@ public final class Generator<T>
|
|||||||
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
boolean isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||||
if( names.length == 0 )
|
if( names.length == 0 )
|
||||||
{
|
{
|
||||||
methods.add( new NamedMethod<>( method.getName(), instance, isSimple ) );
|
methods.add( new NamedMethod<>( method.getName(), instance, isSimple, genericType ) );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for( String name : names )
|
for( String name : names )
|
||||||
{
|
{
|
||||||
methods.add( new NamedMethod<>( name, instance, isSimple ) );
|
methods.add( new NamedMethod<>( name, instance, isSimple, genericType ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ package dan200.computercraft.core.asm;
|
|||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.api.lua.GenericSource;
|
import dan200.computercraft.api.lua.GenericSource;
|
||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
|
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||||
|
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@ -18,6 +20,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
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.
|
* 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 Method method;
|
||||||
final LuaFunction annotation;
|
final LuaFunction annotation;
|
||||||
final Class<?> target;
|
final Class<?> target;
|
||||||
|
final PeripheralType peripheralType;
|
||||||
|
|
||||||
private static final List<GenericSource> sources = new ArrayList<>();
|
private static final List<GenericSource> sources = new ArrayList<>();
|
||||||
private static List<GenericMethod> cache;
|
private static List<GenericMethod> cache;
|
||||||
|
|
||||||
GenericMethod( Method method, LuaFunction annotation, Class<?> target )
|
GenericMethod( Method method, LuaFunction annotation, Class<?> target, PeripheralType peripheralType )
|
||||||
{
|
{
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
this.peripheralType = peripheralType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,10 +51,28 @@ public class GenericMethod
|
|||||||
static List<GenericMethod> all()
|
static List<GenericMethod> all()
|
||||||
{
|
{
|
||||||
if( cache != null ) return cache;
|
if( cache != null ) return cache;
|
||||||
return cache = sources.stream()
|
return cache = sources.stream().flatMap( GenericMethod::getMethods ).collect( Collectors.toList() );
|
||||||
.flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) )
|
}
|
||||||
.map( method ->
|
|
||||||
|
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<GenericMethod> 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 );
|
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||||
if( annotation == null ) return null;
|
if( annotation == null ) return null;
|
||||||
|
|
||||||
@ -69,22 +92,8 @@ public class GenericMethod
|
|||||||
Class<?> target = Reflect.getRawType( method, types[0], false );
|
Class<?> target = Reflect.getRawType( method, types[0], false );
|
||||||
if( target == null ) return null;
|
if( target == null ) return null;
|
||||||
|
|
||||||
return new GenericMethod( method, annotation, target );
|
return new GenericMethod( method, annotation, target, type );
|
||||||
} )
|
} )
|
||||||
.filter( Objects::nonNull )
|
.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 );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
package dan200.computercraft.core.asm;
|
package dan200.computercraft.core.asm;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public final class NamedMethod<T>
|
public final class NamedMethod<T>
|
||||||
{
|
{
|
||||||
@ -13,11 +16,14 @@ public final class NamedMethod<T>
|
|||||||
private final T method;
|
private final T method;
|
||||||
private final boolean nonYielding;
|
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.name = name;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.nonYielding = nonYielding;
|
this.nonYielding = nonYielding;
|
||||||
|
this.genericType = genericType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -36,4 +42,10 @@ public final class NamedMethod<T>
|
|||||||
{
|
{
|
||||||
return nonYielding;
|
return nonYielding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public PeripheralType getGenericType()
|
||||||
|
{
|
||||||
|
return genericType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,11 @@ class GenericPeripheral implements IDynamicPeripheral
|
|||||||
private final TileEntity tile;
|
private final TileEntity tile;
|
||||||
private final List<SaturatedMethod> methods;
|
private final List<SaturatedMethod> methods;
|
||||||
|
|
||||||
GenericPeripheral( TileEntity tile, List<SaturatedMethod> methods )
|
GenericPeripheral( TileEntity tile, String name, List<SaturatedMethod> methods )
|
||||||
{
|
{
|
||||||
ResourceLocation type = tile.getType().getRegistryName();
|
ResourceLocation type = tile.getType().getRegistryName();
|
||||||
this.tile = tile;
|
this.tile = tile;
|
||||||
this.type = type == null ? "unknown" : type.toString();
|
this.type = name != null ? name : (type != null ? type.toString() : "unknown");
|
||||||
this.methods = methods;
|
this.methods = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package dan200.computercraft.shared.peripheral.generic;
|
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.core.asm.NamedMethod;
|
import dan200.computercraft.core.asm.NamedMethod;
|
||||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||||
import net.minecraft.tileentity.TileEntity;
|
import net.minecraft.tileentity.TileEntity;
|
||||||
@ -38,10 +39,10 @@ public class GenericPeripheralProvider
|
|||||||
TileEntity tile = world.getBlockEntity( pos );
|
TileEntity tile = world.getBlockEntity( pos );
|
||||||
if( tile == null ) return null;
|
if( tile == null ) return null;
|
||||||
|
|
||||||
ArrayList<SaturatedMethod> saturated = new ArrayList<>( 0 );
|
GenericPeripheralBuilder saturated = new GenericPeripheralBuilder();
|
||||||
|
|
||||||
List<NamedMethod<PeripheralMethod>> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() );
|
List<NamedMethod<PeripheralMethod>> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() );
|
||||||
if( !tileMethods.isEmpty() ) addSaturated( saturated, tile, tileMethods );
|
if( !tileMethods.isEmpty() ) saturated.addMethods( tile, tileMethods );
|
||||||
|
|
||||||
for( Capability<?> capability : capabilities )
|
for( Capability<?> capability : capabilities )
|
||||||
{
|
{
|
||||||
@ -50,20 +51,44 @@ public class GenericPeripheralProvider
|
|||||||
List<NamedMethod<PeripheralMethod>> capabilityMethods = PeripheralMethod.GENERATOR.getMethods( contents.getClass() );
|
List<NamedMethod<PeripheralMethod>> capabilityMethods = PeripheralMethod.GENERATOR.getMethods( contents.getClass() );
|
||||||
if( capabilityMethods.isEmpty() ) return;
|
if( capabilityMethods.isEmpty() ) return;
|
||||||
|
|
||||||
addSaturated( saturated, contents, capabilityMethods );
|
saturated.addMethods( contents, capabilityMethods );
|
||||||
wrapper.addListener( cast( invalidate ) );
|
wrapper.addListener( cast( invalidate ) );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
return saturated.isEmpty() ? null : new GenericPeripheral( tile, saturated );
|
return saturated.toPeripheral( tile );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addSaturated( ArrayList<SaturatedMethod> saturated, Object target, List<NamedMethod<PeripheralMethod>> methods )
|
private static class GenericPeripheralBuilder
|
||||||
{
|
{
|
||||||
saturated.ensureCapacity( saturated.size() + methods.size() );
|
String name;
|
||||||
|
final ArrayList<SaturatedMethod> methods = new ArrayList<>( 0 );
|
||||||
|
|
||||||
|
IPeripheral toPeripheral( TileEntity tile )
|
||||||
|
{
|
||||||
|
if( methods.isEmpty() ) return null;
|
||||||
|
|
||||||
|
methods.trimToSize();
|
||||||
|
return new GenericPeripheral( tile, name, methods );
|
||||||
|
}
|
||||||
|
|
||||||
|
void addMethods( Object target, List<NamedMethod<PeripheralMethod>> methods )
|
||||||
|
{
|
||||||
|
ArrayList<SaturatedMethod> saturatedMethods = this.methods;
|
||||||
|
saturatedMethods.ensureCapacity( saturatedMethods.size() + methods.size() );
|
||||||
for( NamedMethod<PeripheralMethod> method : methods )
|
for( NamedMethod<PeripheralMethod> method : methods )
|
||||||
{
|
{
|
||||||
saturated.add( new SaturatedMethod( target, method ) );
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user