mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-15 04:30:29 +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.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<T>
|
||||
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<T>
|
||||
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<T>
|
||||
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 );
|
||||
|
||||
@ -135,13 +136,13 @@ public final class Generator<T>
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<GenericSource> sources = new ArrayList<>();
|
||||
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.annotation = annotation;
|
||||
this.target = target;
|
||||
this.peripheralType = peripheralType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,10 +51,28 @@ public class GenericMethod
|
||||
static List<GenericMethod> 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<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 );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -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<T>
|
||||
{
|
||||
@ -13,11 +16,14 @@ public final class NamedMethod<T>
|
||||
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<T>
|
||||
{
|
||||
return nonYielding;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PeripheralType getGenericType()
|
||||
{
|
||||
return genericType;
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ class GenericPeripheral implements IDynamicPeripheral
|
||||
private final TileEntity tile;
|
||||
private final List<SaturatedMethod> methods;
|
||||
|
||||
GenericPeripheral( TileEntity tile, List<SaturatedMethod> methods )
|
||||
GenericPeripheral( TileEntity tile, String name, List<SaturatedMethod> 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;
|
||||
}
|
||||
|
||||
|
@ -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<SaturatedMethod> saturated = new ArrayList<>( 0 );
|
||||
GenericPeripheralBuilder saturated = new GenericPeripheralBuilder();
|
||||
|
||||
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 )
|
||||
{
|
||||
@ -50,20 +51,44 @@ public class GenericPeripheralProvider
|
||||
List<NamedMethod<PeripheralMethod>> 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<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 )
|
||||
{
|
||||
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