mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 05:33:00 +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:
		| @@ -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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates