1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-07-06 03:52:53 +00:00
Jonathan Coates d5f82fa458
Replace getMethodNames/callMethod with annotations (#447)
When creating a peripheral or custom Lua object, one must implement two
methods:

 - getMethodNames(): String[] - Returns the name of the methods
 - callMethod(int, ...): Object[] - Invokes the method using an index in
   the above array.

This has a couple of problems:
 - It's somewhat unwieldy to use - you need to keep track of array
   indices, which leads to ugly code.
 - Functions which yield (for instance, those which run on the main
   thread) are blocking. This means we need to spawn new threads for
   each CC-side yield.

We replace this system with a few changes:

 - @LuaFunction annotation: One may annotate a public instance method
   with this annotation. This then exposes a peripheral/lua object
   method.

   Furthermore, this method can accept and return a variety of types,
   which often makes functions cleaner (e.g. can return an int rather
   than an Object[], and specify and int argument rather than
   Object[]).

 - MethodResult: Instead of returning an Object[] and having blocking
   yields, functions return a MethodResult. This either contains an
   immediate return, or an instruction to yield with some continuation
   to resume with.

   MethodResult is then interpreted by the Lua runtime (i.e. Cobalt),
   rather than our weird bodgey hacks before. This means we no longer
   spawn new threads when yielding within CC.

 - Methods accept IArguments instead of a raw Object array. This has a
   few benefits:
   - Consistent argument handling - people no longer need to use
     ArgumentHelper (as it doesn't exist!), or even be aware of its
     existence - you're rather forced into using it.
   - More efficient code in some cases. We provide a Cobalt-specific
     implementation of IArguments, which avoids the boxing/unboxing when
     handling numbers and binary strings.
2020-05-15 13:21:16 +01:00

356 lines
11 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.PeripheralAPI;
import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public abstract class WiredModemPeripheral extends ModemPeripheral implements IWiredSender
{
private final WiredModemElement modem;
private final Map<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> peripheralWrappers = new HashMap<>( 1 );
public WiredModemPeripheral( ModemState state, WiredModemElement modem )
{
super( state );
this.modem = modem;
}
//region IPacketSender implementation
@Override
public boolean isInterdimensional()
{
return false;
}
@Override
public double getRange()
{
return 256.0;
}
@Override
protected IPacketNetwork getNetwork()
{
return modem.getNode();
}
@Nonnull
@Override
public World getWorld()
{
return modem.getWorld();
}
@Nonnull
protected abstract WiredModemLocalPeripheral getLocalPeripheral();
//endregion
//region Peripheral methods
@LuaFunction
public final Collection<String> getNamesRemote( IComputerAccess computer )
{
return getWrappers( computer ).keySet();
}
@LuaFunction
public final boolean isPresentRemote( IComputerAccess computer, String name )
{
return getWrapper( computer, name ) != null;
}
@LuaFunction
public final Object[] getTypeRemote( IComputerAccess computer, String name )
{
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
return wrapper != null ? new Object[] { wrapper.getType() } : null;
}
@LuaFunction
public final Object[] getMethodsRemote( IComputerAccess computer, String name )
{
RemotePeripheralWrapper wrapper = getWrapper( computer, name );
if( wrapper == null ) return null;
return new Object[] { wrapper.getMethodNames() };
}
@LuaFunction
public final MethodResult callRemote( IComputerAccess computer, ILuaContext context, IArguments arguments ) throws LuaException
{
String remoteName = arguments.getString( 0 );
String methodName = arguments.getString( 1 );
RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName );
if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName );
return wrapper.callMethod( context, methodName, arguments.drop( 2 ) );
}
@LuaFunction
public final Object[] getNameLocal()
{
String local = getLocalPeripheral().getConnectedName();
return local == null ? null : new Object[] { local };
}
@Override
public void attach( @Nonnull IComputerAccess computer )
{
super.attach( computer );
ConcurrentMap<String, RemotePeripheralWrapper> wrappers;
synchronized( peripheralWrappers )
{
wrappers = peripheralWrappers.get( computer );
if( wrappers == null ) peripheralWrappers.put( computer, wrappers = new ConcurrentHashMap<>() );
}
synchronized( modem.getRemotePeripherals() )
{
for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() )
{
attachPeripheralImpl( computer, wrappers, entry.getKey(), entry.getValue() );
}
}
}
@Override
public void detach( @Nonnull IComputerAccess computer )
{
Map<String, RemotePeripheralWrapper> wrappers;
synchronized( peripheralWrappers )
{
wrappers = peripheralWrappers.remove( computer );
}
if( wrappers != null )
{
for( RemotePeripheralWrapper wrapper : wrappers.values() ) wrapper.detach();
wrappers.clear();
}
super.detach( computer );
}
@Override
public boolean equals( IPeripheral other )
{
if( other instanceof WiredModemPeripheral )
{
WiredModemPeripheral otherModem = (WiredModemPeripheral) other;
return otherModem.modem == modem;
}
return false;
}
//endregion
@Nonnull
@Override
public IWiredNode getNode()
{
return modem.getNode();
}
public void attachPeripheral( String name, IPeripheral peripheral )
{
synchronized( peripheralWrappers )
{
for( Map.Entry<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> entry : peripheralWrappers.entrySet() )
{
attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral );
}
}
}
public void detachPeripheral( String name )
{
synchronized( peripheralWrappers )
{
for( ConcurrentMap<String, RemotePeripheralWrapper> wrappers : peripheralWrappers.values() )
{
RemotePeripheralWrapper wrapper = wrappers.remove( name );
if( wrapper != null ) wrapper.detach();
}
}
}
private void attachPeripheralImpl( IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral )
{
if( !peripherals.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) )
{
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, computer, periphName );
peripherals.put( periphName, wrapper );
wrapper.attach();
}
}
private ConcurrentMap<String, RemotePeripheralWrapper> getWrappers( IComputerAccess computer )
{
synchronized( peripheralWrappers )
{
return peripheralWrappers.get( computer );
}
}
private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String remoteName )
{
ConcurrentMap<String, RemotePeripheralWrapper> wrappers = getWrappers( computer );
return wrappers == null ? null : wrappers.get( remoteName );
}
private static class RemotePeripheralWrapper implements IComputerAccess
{
private final WiredModemElement element;
private final IPeripheral peripheral;
private final IComputerAccess computer;
private final String name;
private final String type;
private final Map<String, PeripheralMethod> methodMap;
RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name )
{
this.element = element;
this.peripheral = peripheral;
this.computer = computer;
this.name = name;
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
methodMap = PeripheralAPI.getMethods( peripheral );
}
public void attach()
{
peripheral.attach( this );
computer.queueEvent( "peripheral", getAttachmentName() );
}
public void detach()
{
peripheral.detach( this );
computer.queueEvent( "peripheral_detach", getAttachmentName() );
}
public String getType()
{
return type;
}
public Collection<String> getMethodNames()
{
return methodMap.keySet();
}
public MethodResult callMethod( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
{
PeripheralMethod method = methodMap.get( methodName );
if( method == null ) throw new LuaException( "No such method " + methodName );
return method.apply( peripheral, context, this, arguments );
}
// IComputerAccess implementation
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount )
{
return computer.mount( desiredLocation, mount, name );
}
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName )
{
return computer.mount( desiredLocation, mount, driveName );
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount )
{
return computer.mountWritable( desiredLocation, mount, name );
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
return computer.mountWritable( desiredLocation, mount, driveName );
}
@Override
public void unmount( String location )
{
computer.unmount( location );
}
@Override
public int getID()
{
return computer.getID();
}
@Override
public void queueEvent( @Nonnull String event, Object... arguments )
{
computer.queueEvent( event, arguments );
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{
return computer.getMainThreadMonitor();
}
@Nonnull
@Override
public String getAttachmentName()
{
return name;
}
@Nonnull
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
synchronized( element.getRemotePeripherals() )
{
return ImmutableMap.copyOf( element.getRemotePeripherals() );
}
}
@Nullable
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
synchronized( element.getRemotePeripherals() )
{
return element.getRemotePeripherals().get( name );
}
}
}
}