/* * This file is part of ComputerCraft - http://www.computercraft.info * Copyright Daniel Ratcliffe, 2011-2019. 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.ILuaContext; import dan200.computercraft.api.lua.LuaException; 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.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.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static dan200.computercraft.core.apis.ArgumentHelper.getString; public abstract class WiredModemPeripheral extends ModemPeripheral implements IWiredSender { private final WiredModemElement modem; private final Map> 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 IPeripheral @Nonnull @Override public String[] getMethodNames() { String[] methods = super.getMethodNames(); String[] newMethods = new String[methods.length + 6]; System.arraycopy( methods, 0, newMethods, 0, methods.length ); newMethods[methods.length] = "getNamesRemote"; newMethods[methods.length + 1] = "isPresentRemote"; newMethods[methods.length + 2] = "getTypeRemote"; newMethods[methods.length + 3] = "getMethodsRemote"; newMethods[methods.length + 4] = "callRemote"; newMethods[methods.length + 5] = "getNameLocal"; return newMethods; } @Override public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException { String[] methods = super.getMethodNames(); switch( method - methods.length ) { case 0: { // getNamesRemote Map wrappers = getWrappers( computer ); Map table = new HashMap<>(); if( wrappers != null ) { int idx = 1; for( String name : wrappers.keySet() ) table.put( idx++, name ); } return new Object[] { table }; } case 1: { // isPresentRemote String name = getString( arguments, 0 ); return new Object[] { getWrapper( computer, name ) != null }; } case 2: { // getTypeRemote String name = getString( arguments, 0 ); RemotePeripheralWrapper wrapper = getWrapper( computer, name ); return wrapper != null ? new Object[] { wrapper.getType() } : null; } case 3: { // getMethodsRemote String name = getString( arguments, 0 ); RemotePeripheralWrapper wrapper = getWrapper( computer, name ); if( wrapper == null ) return null; String[] methodNames = wrapper.getMethodNames(); Map table = new HashMap<>(); for( int i = 0; i < methodNames.length; i++ ) { table.put( i + 1, methodNames[i] ); } return new Object[] { table }; } case 4: { // callRemote String remoteName = getString( arguments, 0 ); String methodName = getString( arguments, 1 ); RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName ); if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName ); Object[] methodArgs = new Object[arguments.length - 2]; System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 ); return wrapper.callMethod( context, methodName, methodArgs ); } case 5: { // getNameLocal String local = getLocalPeripheral().getConnectedName(); return local == null ? null : new Object[] { local }; } default: { // The regular modem methods return super.callMethod( computer, context, method, arguments ); } } } @Override public void attach( @Nonnull IComputerAccess computer ) { super.attach( computer ); ConcurrentMap wrappers; synchronized( peripheralWrappers ) { wrappers = peripheralWrappers.get( computer ); if( wrappers == null ) peripheralWrappers.put( computer, wrappers = new ConcurrentHashMap<>() ); } synchronized( modem.getRemotePeripherals() ) { for( Map.Entry entry : modem.getRemotePeripherals().entrySet() ) { attachPeripheralImpl( computer, wrappers, entry.getKey(), entry.getValue() ); } } } @Override public void detach( @Nonnull IComputerAccess computer ) { Map 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> entry : peripheralWrappers.entrySet() ) { attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral ); } } } public void detachPeripheral( String name ) { synchronized( peripheralWrappers ) { for( ConcurrentMap wrappers : peripheralWrappers.values() ) { RemotePeripheralWrapper wrapper = wrappers.remove( name ); if( wrapper != null ) wrapper.detach(); } } } private void attachPeripheralImpl( IComputerAccess computer, ConcurrentMap 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 getWrappers( IComputerAccess computer ) { synchronized( peripheralWrappers ) { return peripheralWrappers.get( computer ); } } private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String remoteName ) { ConcurrentMap wrappers = getWrappers( computer ); return wrappers == null ? null : wrappers.get( remoteName ); } private static class RemotePeripheralWrapper implements IComputerAccess { private final WiredModemElement m_element; private final IPeripheral m_peripheral; private final IComputerAccess m_computer; private final String m_name; private final String m_type; private final String[] m_methods; private final Map m_methodMap; RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name ) { m_element = element; m_peripheral = peripheral; m_computer = computer; m_name = name; m_type = peripheral.getType(); m_methods = peripheral.getMethodNames(); assert m_type != null; assert m_methods != null; m_methodMap = new HashMap<>(); for( int i = 0; i < m_methods.length; i++ ) { if( m_methods[i] != null ) { m_methodMap.put( m_methods[i], i ); } } } public void attach() { m_peripheral.attach( this ); m_computer.queueEvent( "peripheral", new Object[] { getAttachmentName() } ); } public void detach() { m_peripheral.detach( this ); m_computer.queueEvent( "peripheral_detach", new Object[] { getAttachmentName() } ); } public String getType() { return m_type; } public String[] getMethodNames() { return m_methods; } public Object[] callMethod( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException { if( m_methodMap.containsKey( methodName ) ) { int method = m_methodMap.get( methodName ); return m_peripheral.callMethod( this, context, method, arguments ); } throw new LuaException( "No such method " + methodName ); } // IComputerAccess implementation @Override public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount ) { return m_computer.mount( desiredLocation, mount, m_name ); } @Override public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName ) { return m_computer.mount( desiredLocation, mount, driveName ); } @Override public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount ) { return m_computer.mountWritable( desiredLocation, mount, m_name ); } @Override public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName ) { return m_computer.mountWritable( desiredLocation, mount, driveName ); } @Override public void unmount( String location ) { m_computer.unmount( location ); } @Override public int getID() { return m_computer.getID(); } @Override public void queueEvent( @Nonnull String event, Object[] arguments ) { m_computer.queueEvent( event, arguments ); } @Nullable @Override public IWorkMonitor getMainThreadMonitor() { return m_computer.getMainThreadMonitor(); } @Nonnull @Override public String getAttachmentName() { return m_name; } @Nonnull @Override public Map getAvailablePeripherals() { synchronized( m_element.getRemotePeripherals() ) { return ImmutableMap.copyOf( m_element.getRemotePeripherals() ); } } @Nullable @Override public IPeripheral getAvailablePeripheral( @Nonnull String name ) { synchronized( m_element.getRemotePeripherals() ) { return m_element.getRemotePeripherals().get( name ); } } } }