/* * 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 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 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 getAvailablePeripherals() { if( !attached ) throw new NotAttachedException(); Map 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 getMethods( IPeripheral peripheral ) { String[] dynamicMethods = peripheral instanceof IDynamicPeripheral ? Objects.requireNonNull( ((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null" ) : LuaMethod.EMPTY_METHODS; List> methods = PeripheralMethod.GENERATOR.getMethods( peripheral.getClass() ); Map 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 method : methods ) { methodMap.put( method.getName(), method.getMethod() ); } return methodMap; } }