369 lines
11 KiB
Java
369 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.core.apis;
|
|
|
|
import dan200.computercraft.api.filesystem.IMount;
|
|
import dan200.computercraft.api.filesystem.IWritableMount;
|
|
import dan200.computercraft.api.lua.*;
|
|
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
|
import dan200.computercraft.api.peripheral.NotAttachedException;
|
|
import dan200.computercraft.core.asm.LuaMethod;
|
|
import dan200.computercraft.core.asm.NamedMethod;
|
|
import dan200.computercraft.core.asm.PeripheralMethod;
|
|
import dan200.computercraft.core.computer.ComputerSide;
|
|
import dan200.computercraft.core.tracking.TrackingField;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* CC's "native" peripheral API. This is wrapped within CraftOS to provide a version which works with modems.
|
|
*
|
|
* @cc.module peripheral
|
|
* @hidden
|
|
*/
|
|
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
|
|
{
|
|
private class PeripheralWrapper extends ComputerAccess
|
|
{
|
|
private final String side;
|
|
private final IPeripheral peripheral;
|
|
|
|
private final String type;
|
|
private final Map<String, PeripheralMethod> methodMap;
|
|
private boolean attached;
|
|
|
|
PeripheralWrapper( IPeripheral peripheral, String side )
|
|
{
|
|
super( environment );
|
|
this.side = side;
|
|
this.peripheral = peripheral;
|
|
attached = false;
|
|
|
|
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
|
|
|
|
methodMap = PeripheralAPI.getMethods( peripheral );
|
|
}
|
|
|
|
public IPeripheral getPeripheral()
|
|
{
|
|
return peripheral;
|
|
}
|
|
|
|
public String getType()
|
|
{
|
|
return type;
|
|
}
|
|
|
|
public Collection<String> getMethods()
|
|
{
|
|
return methodMap.keySet();
|
|
}
|
|
|
|
public synchronized boolean isAttached()
|
|
{
|
|
return attached;
|
|
}
|
|
|
|
public synchronized void attach()
|
|
{
|
|
attached = true;
|
|
peripheral.attach( this );
|
|
}
|
|
|
|
public void detach()
|
|
{
|
|
// Call detach
|
|
peripheral.detach( this );
|
|
|
|
synchronized( this )
|
|
{
|
|
// Unmount everything the detach function forgot to do
|
|
unmountAll();
|
|
}
|
|
|
|
attached = false;
|
|
}
|
|
|
|
public MethodResult call( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
|
|
{
|
|
PeripheralMethod method;
|
|
synchronized( this )
|
|
{
|
|
method = methodMap.get( methodName );
|
|
}
|
|
|
|
if( method == null ) throw new LuaException( "No such method " + methodName );
|
|
|
|
environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
|
|
return method.apply( peripheral, context, this, arguments );
|
|
}
|
|
|
|
// IComputerAccess implementation
|
|
@Override
|
|
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
return super.mount( desiredLoc, mount, driveName );
|
|
}
|
|
|
|
@Override
|
|
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
return super.mountWritable( desiredLoc, mount, driveName );
|
|
}
|
|
|
|
@Override
|
|
public synchronized void unmount( String location )
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
super.unmount( location );
|
|
}
|
|
|
|
@Override
|
|
public int getID()
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
return super.getID();
|
|
}
|
|
|
|
@Override
|
|
public void queueEvent( @Nonnull String event, Object... arguments )
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
super.queueEvent( event, arguments );
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public String getAttachmentName()
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
return side;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Map<String, IPeripheral> getAvailablePeripherals()
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
|
|
Map<String, IPeripheral> peripherals = new HashMap<>();
|
|
for( PeripheralWrapper wrapper : PeripheralAPI.this.peripherals )
|
|
{
|
|
if( wrapper != null && wrapper.isAttached() )
|
|
{
|
|
peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() );
|
|
}
|
|
}
|
|
|
|
return Collections.unmodifiableMap( peripherals );
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public IPeripheral getAvailablePeripheral( @Nonnull String name )
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
|
|
for( PeripheralWrapper wrapper : peripherals )
|
|
{
|
|
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
|
|
{
|
|
return wrapper.getPeripheral();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IWorkMonitor getMainThreadMonitor()
|
|
{
|
|
if( !attached ) throw new NotAttachedException();
|
|
return super.getMainThreadMonitor();
|
|
}
|
|
}
|
|
|
|
private final IAPIEnvironment environment;
|
|
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
|
|
private boolean running;
|
|
|
|
public PeripheralAPI( IAPIEnvironment environment )
|
|
{
|
|
this.environment = environment;
|
|
this.environment.setPeripheralChangeListener( this );
|
|
running = false;
|
|
}
|
|
|
|
// IPeripheralChangeListener
|
|
|
|
@Override
|
|
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
int index = side.ordinal();
|
|
if( peripherals[index] != null )
|
|
{
|
|
// Queue a detachment
|
|
final PeripheralWrapper wrapper = peripherals[index];
|
|
if( wrapper.isAttached() ) wrapper.detach();
|
|
|
|
// Queue a detachment event
|
|
environment.queueEvent( "peripheral_detach", side.getName() );
|
|
}
|
|
|
|
// Assign the new peripheral
|
|
peripherals[index] = newPeripheral == null ? null
|
|
: new PeripheralWrapper( newPeripheral, side.getName() );
|
|
|
|
if( peripherals[index] != null )
|
|
{
|
|
// Queue an attachment
|
|
final PeripheralWrapper wrapper = peripherals[index];
|
|
if( running && !wrapper.isAttached() ) wrapper.attach();
|
|
|
|
// Queue an attachment event
|
|
environment.queueEvent( "peripheral", side.getName() );
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String[] getNames()
|
|
{
|
|
return new String[] { "peripheral" };
|
|
}
|
|
|
|
@Override
|
|
public void startup()
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
running = true;
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
PeripheralWrapper wrapper = peripherals[i];
|
|
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void shutdown()
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
running = false;
|
|
for( int i = 0; i < 6; i++ )
|
|
{
|
|
PeripheralWrapper wrapper = peripherals[i];
|
|
if( wrapper != null && wrapper.isAttached() )
|
|
{
|
|
wrapper.detach();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@LuaFunction
|
|
public final boolean isPresent( String sideName )
|
|
{
|
|
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
|
if( side != null )
|
|
{
|
|
synchronized( peripherals )
|
|
{
|
|
PeripheralWrapper p = peripherals[side.ordinal()];
|
|
if( p != null ) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@LuaFunction
|
|
public final Object[] getType( String sideName )
|
|
{
|
|
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
|
if( side == null ) return null;
|
|
|
|
synchronized( peripherals )
|
|
{
|
|
PeripheralWrapper p = peripherals[side.ordinal()];
|
|
if( p != null ) return new Object[] { p.getType() };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@LuaFunction
|
|
public final Object[] getMethods( String sideName )
|
|
{
|
|
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
|
|
if( side == null ) return null;
|
|
|
|
synchronized( peripherals )
|
|
{
|
|
PeripheralWrapper p = peripherals[side.ordinal()];
|
|
if( p != null ) return new Object[] { p.getMethods() };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@LuaFunction
|
|
public final MethodResult call( ILuaContext context, IArguments args ) throws LuaException
|
|
{
|
|
ComputerSide side = ComputerSide.valueOfInsensitive( args.getString( 0 ) );
|
|
String methodName = args.getString( 1 );
|
|
IArguments methodArgs = args.drop( 2 );
|
|
|
|
if( side == null ) throw new LuaException( "No peripheral attached" );
|
|
|
|
PeripheralWrapper p;
|
|
synchronized( peripherals )
|
|
{
|
|
p = peripherals[side.ordinal()];
|
|
}
|
|
if( p == null ) throw new LuaException( "No peripheral attached" );
|
|
|
|
try
|
|
{
|
|
return p.call( context, methodName, methodArgs ).adjustError( 1 );
|
|
}
|
|
catch( LuaException e )
|
|
{
|
|
// We increase the error level by one in order to shift the error level to where peripheral.call was
|
|
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
|
|
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public static Map<String, PeripheralMethod> getMethods( IPeripheral peripheral )
|
|
{
|
|
String[] dynamicMethods = peripheral instanceof IDynamicPeripheral
|
|
? Objects.requireNonNull( ((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null" )
|
|
: LuaMethod.EMPTY_METHODS;
|
|
|
|
List<NamedMethod<PeripheralMethod>> methods = PeripheralMethod.GENERATOR.getMethods( peripheral.getClass() );
|
|
|
|
Map<String, PeripheralMethod> methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
|
|
for( int i = 0; i < dynamicMethods.length; i++ )
|
|
{
|
|
methodMap.put( dynamicMethods[i], PeripheralMethod.DYNAMIC.get( i ) );
|
|
}
|
|
for( NamedMethod<PeripheralMethod> method : methods )
|
|
{
|
|
methodMap.put( method.getName(), method.getMethod() );
|
|
}
|
|
return methodMap;
|
|
}
|
|
}
|