1
0
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:
Jonathan Coates 2021-11-22 18:05:13 +00:00
parent 070479d901
commit f33f57ea35
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
7 changed files with 191 additions and 37 deletions

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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