1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-12 10:20:28 +00:00

Merge pull request #5 from SquidDev-CC/feature/network-api

Well, how badly can this go?
This commit is contained in:
SquidDev 2018-02-21 15:47:15 +00:00 committed by GitHub
commit 9be61abd6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 3137 additions and 701 deletions

View File

@ -59,6 +59,8 @@ dependencies {
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api" deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
runtime "mezz.jei:jei_1.12:4.7.5.86" runtime "mezz.jei:jei_1.12:4.7.5.86"
shade 'org.squiddev:Cobalt:0.3.1' shade 'org.squiddev:Cobalt:0.3.1'
testCompile 'junit:junit:4.11'
} }
javadoc { javadoc {

View File

@ -14,6 +14,9 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork; import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredProvider;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.permissions.ITurtlePermissionProvider; import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
@ -44,6 +47,7 @@ import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.network.PacketHandler; import dan200.computercraft.shared.network.PacketHandler;
import dan200.computercraft.shared.peripheral.common.BlockCable; import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral; import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
import dan200.computercraft.shared.peripheral.common.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem; import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.WirelessNetwork; import dan200.computercraft.shared.peripheral.modem.WirelessNetwork;
@ -57,6 +61,7 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.*; import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@ -69,6 +74,7 @@ import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList; import net.minecraft.util.NonNullList;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Configuration;
@ -175,6 +181,7 @@ public class ComputerCraft
public static BlockTurtle turtleAdvanced; public static BlockTurtle turtleAdvanced;
public static BlockCommandComputer commandComputer; public static BlockCommandComputer commandComputer;
public static BlockAdvancedModem advancedModem; public static BlockAdvancedModem advancedModem;
public static BlockWiredModemFull wiredModemFull;
} }
public static class Items public static class Items
@ -259,6 +266,7 @@ public class ComputerCraft
private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>(); private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>();
private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>(); private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>();
private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>(); private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>();
private static final Set<IWiredProvider> wiredProviders = new LinkedHashSet<>();
// Implementation // Implementation
@Mod.Instance( value = ComputerCraft.MOD_ID ) @Mod.Instance( value = ComputerCraft.MOD_ID )
@ -730,6 +738,16 @@ public class ComputerCraft
} }
} }
public static void registerWiredProvider( IWiredProvider provider )
{
if( provider != null ) wiredProviders.add( provider );
}
public static IWiredNode createWiredNodeForElement( IWiredElement element )
{
return new WiredNode( element );
}
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side ) public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
{ {
// Try the handlers in order: // Try the handlers in order:
@ -751,6 +769,24 @@ public class ComputerCraft
return null; return null;
} }
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
{
// Try the handlers in order:
for( IWiredProvider provider : wiredProviders )
{
try
{
IWiredElement element = provider.getElement( world, pos, side );
if( element != null ) return element;
}
catch( Exception e )
{
ComputerCraft.log.error( "Wired element provider " + provider + " errored.", e );
}
}
return null;
}
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side ) public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
{ {
if( WorldUtil.isBlockInWorld( world, pos ) ) if( WorldUtil.isBlockInWorld( world, pos ) )

View File

@ -12,6 +12,9 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork; import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredProvider;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.peripheral.IPeripheralProvider;
@ -21,6 +24,7 @@ import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -328,6 +332,81 @@ public final class ComputerCraftAPI
} }
} }
/**
* Registers a peripheral handler to convert blocks into {@link IPeripheral} implementations.
*
* @param handler The peripheral provider to register.
* @see dan200.computercraft.api.peripheral.IPeripheral
* @see dan200.computercraft.api.peripheral.IPeripheralProvider
*/
public static void registerWiredProvider( @Nonnull IWiredProvider handler )
{
findCC();
if ( computerCraft_registerWiredProvider != null)
{
try {
computerCraft_registerWiredProvider.invoke( null, handler );
} catch (Exception e){
// It failed
}
}
}
/**
* Construct a new wired node for a given wired element
*
* @param element The element to construct it for
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nonnull
public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
{
findCC();
if( computerCraft_createWiredNodeForElement != null )
{
try
{
return (IWiredNode) computerCraft_createWiredNodeForElement.invoke( null, element );
}
catch( ReflectiveOperationException e )
{
throw new IllegalStateException( "Error creating wired node", e );
}
}
else
{
throw new IllegalStateException( "ComputerCraft cannot be found" );
}
}
/**
* Get the wired network element for a block in world
*
* @param world The world the block exists in
* @param pos The position the block exists in
* @param side The side to extract the network element from
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nullable
public static IWiredElement getWiredElementAt( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
findCC();
if( computerCraft_getWiredElementAt != null )
{
try
{
return (IWiredElement) computerCraft_getWiredElementAt.invoke( null, world, pos, side );
}
catch( ReflectiveOperationException ignored )
{
}
}
return null;
}
// The functions below here are private, and are used to interface with the non-API ComputerCraft classes. // The functions below here are private, and are used to interface with the non-API ComputerCraft classes.
// Reflection is used here so you can develop your mod without decompiling ComputerCraft and including // Reflection is used here so you can develop your mod without decompiling ComputerCraft and including
// it in your solution, and so your mod won't crash if ComputerCraft is installed. // it in your solution, and so your mod won't crash if ComputerCraft is installed.
@ -374,6 +453,15 @@ public final class ComputerCraftAPI
computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] { computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
ILuaAPIFactory.class ILuaAPIFactory.class
} ); } );
computerCraft_registerWiredProvider = findCCMethod( "registerWiredProvider", new Class<?>[] {
IWiredProvider.class
} );
computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
IWiredElement.class
} );
computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class<?>[]{
IBlockAccess.class, BlockPos.class, EnumFacing.class
} );
} catch( Exception e ) { } catch( Exception e ) {
System.out.println( "ComputerCraftAPI: ComputerCraft not found." ); System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
} finally { } finally {
@ -411,4 +499,7 @@ public final class ComputerCraftAPI
private static Method computerCraft_registerPocketUpgrade = null; private static Method computerCraft_registerPocketUpgrade = null;
private static Method computerCraft_getWirelessNetwork = null; private static Method computerCraft_getWirelessNetwork = null;
private static Method computerCraft_registerAPIFactory = null; private static Method computerCraft_registerAPIFactory = null;
private static Method computerCraft_registerWiredProvider = null;
private static Method computerCraft_createWiredNodeForElement = null;
private static Method computerCraft_getWiredElementAt = null;
} }

View File

@ -0,0 +1,51 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;
/**
* An object which may be part of a wired network.
*
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
*
* Elements are generally tied to a block or tile entity in world. One should either register an {@link IWiredProvider}
* or implement {@link IWiredElementTile} on your tile entity.
*
* @see IWiredProvider
* @see ComputerCraftAPI#registerWiredProvider(IWiredProvider)
* @see IWiredElementTile
*/
public interface IWiredElement extends IWiredSender
{
/**
* Fetch the peripherals this network element provides.
*
* This is only called when initially attaching to a network and after a call to {@link IWiredNode#invalidate()}}, so
* one does not <em>need</em> to cache the return value.
*
* @return The peripherals this node provides.
* @see IWiredNode#invalidate()
*/
@Nonnull
default Map<String, IPeripheral> getPeripherals()
{
return Collections.emptyMap();
}
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
*
* @param change The change which occurred.
* @see IWiredNetworkChange
*/
default void networkChanged( @Nonnull IWiredNetworkChange change )
{
}
}

View File

@ -0,0 +1,22 @@
package dan200.computercraft.api.network.wired;
import net.minecraft.util.EnumFacing;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link net.minecraft.tileentity.TileEntity} which provides a {@link IWiredElement}. This acts
* as a simpler alternative to a full-blown {@link IWiredProvider}.
*/
public interface IWiredElementTile
{
/**
* Get the wired element of this tile for a given side.
*
* @param side The side to get the network element from.
* @return A network element, or {@code null} if there is no element here.
*/
@Nullable
IWiredElement getWiredElement( @Nonnull EnumFacing side );
}

View File

@ -0,0 +1,74 @@
package dan200.computercraft.api.network.wired;
import javax.annotation.Nonnull;
/**
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
* of peripherals.
*
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
*
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link IWiredNode}.
*
* @see IWiredNode#getNetwork()
*/
public interface IWiredNetwork
{
/**
* Create a connection between two nodes.
*
* This should only be used on the server thread.
*
* @param left The first node to connect
* @param right The second node to connect
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#connectTo(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param left The first node in the connection.
* @param right The second node in the connection.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#disconnectFrom(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#remove()
*/
boolean remove( @Nonnull IWiredNode node );
/**
* Mark this node's peripherals as having changed.
*
* This should only be used on the server thread.
*
* @param node The node to mark as invalid.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredElement#getPeripherals()
*/
void invalidate( @Nonnull IWiredNode node );
}

View File

@ -0,0 +1,32 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Represents a change to the objects on a wired network.
*
* @see IWiredElement#networkChanged(IWiredNetworkChange)
*/
public interface IWiredNetworkChange
{
/**
* A set of peripherals which have been removed. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of removed peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsRemoved();
/**
* A set of peripherals which have been added. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of added peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsAdded();
}

View File

@ -0,0 +1,98 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketNetwork;
import javax.annotation.Nonnull;
/**
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
*
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
*
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
* element's position. Note that packet senders and receivers can have different locations from their associated
* element: the distance between the two will be added to the total packet's distance.
*
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
public interface IWiredNode extends IPacketNetwork
{
/**
* The associated element for this network node.
*
* @return This node's element.
*/
@Nonnull
IWiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
* after any network operation, so it should not be cached.
*
* This should only be used on the server thread.
*
* @return This node's network.
*/
@Nonnull
IWiredNetwork getNetwork();
/**
* Create a connection from this node to another.
*
* This should only be used on the server thread.
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see IWiredNode#disconnectFrom(IWiredNode)
*/
default boolean connectTo( @Nonnull IWiredNode node )
{
return getNetwork().connect( this, node );
}
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
* @see IWiredNode#connectTo(IWiredNode)
*/
default boolean disconnectFrom( @Nonnull IWiredNode node )
{
return getNetwork().disconnect( this, node );
}
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread.
*
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNetwork#remove(IWiredNode)
*/
default boolean remove()
{
return getNetwork().remove( this );
}
/**
* Mark this node's peripherals as having changed.
*
* This should only be used on the server thread.
*
* @see IWiredElement#getPeripherals()
*/
default void invalidate()
{
getNetwork().invalidate( this );
}
}

View File

@ -0,0 +1,29 @@
package dan200.computercraft.api.network.wired;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Fetch or create an {@link IWiredElement} for a block at a given position.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerWiredProvider(IWiredProvider)
* @see IWiredElementTile
*/
@FunctionalInterface
public interface IWiredProvider
{
/**
* Extract a wired network element from a block location.
*
* @param world The world the block is in.
* @param pos The position the block is at.
* @param side The side to get the network element from.
* @return A network element, or {@code null} if there is not an element here you'd like to handle.
*/
@Nullable
IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side );
}

View File

@ -0,0 +1,25 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketSender;
import javax.annotation.Nonnull;
/**
* An object on a {@link IWiredNetwork} capable of sending packets.
*
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*/
public interface IWiredSender extends IPacketSender
{
/**
* The node in the network representing this object.
*
* This should be used as a proxy for the main network. One should send packets
* and register receivers through this object.
*
* @return The node for this element.
*/
@Nonnull
IWiredNode getNode();
}

View File

@ -0,0 +1,10 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. 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.
*/
@API( owner="ComputerCraft", provides="ComputerCraft|API|Network|Wired", apiVersion="${version}" )
package dan200.computercraft.api.network.wired;
import net.minecraftforge.fml.common.API;

View File

@ -13,6 +13,8 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
/** /**
* The interface passed to peripherals by computers or turtles, providing methods * The interface passed to peripherals by computers or turtles, providing methods
@ -154,4 +156,33 @@ public interface IComputerAccess
*/ */
@Nonnull @Nonnull
String getAttachmentName(); String getAttachmentName();
/**
* Get a set of peripherals that this computer access can "see", along with their attachment name.
*
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
*
* @return All reachable peripherals
* @see #getAttachmentName()
* @see #getAvailablePeripheral(String)
*/
@Nonnull
default Map<String, IPeripheral> getAvailablePeripherals()
{
return Collections.emptyMap();
}
/**
* Get a reachable peripheral with the given attachement name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
*
* @param name The peripheral's attached name
* @return The reachable peripheral, or {@code null} if none can be found.
* @see #getAvailablePeripherals()
*/
@Nullable
default IPeripheral getAvailablePeripheral( @Nonnull String name )
{
return null;
}
} }

View File

@ -114,6 +114,18 @@ public interface IPeripheral
{ {
} }
/**
* Get the object that this peripheral provides methods for. This will generally be the tile entity
* or block, but may be an inventory, entity, etc...
*
* @return The object this peripheral targets
*/
@Nonnull
default Object getTarget()
{
return this;
}
/** /**
* Determine whether this peripheral is equivalent to another one. * Determine whether this peripheral is equivalent to another one.
* *

View File

@ -115,6 +115,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" ); registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" );
registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" ); registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" );
registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" ); registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" );
registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" );
registerItemModel( ComputerCraft.Items.disk, "disk" ); registerItemModel( ComputerCraft.Items.disk, "disk" );
registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" ); registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" );

View File

@ -54,8 +54,6 @@ public class RenderOverlayCable
GlStateManager.depthMask( false ); GlStateManager.depthMask( false );
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
EnumFacing direction = type != PeripheralType.Cable ? cable.getDirection() : null;
{ {
EntityPlayer player = event.getPlayer(); EntityPlayer player = event.getPlayer();
double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks(); double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
@ -78,7 +76,7 @@ public class RenderOverlayCable
for( EnumFacing facing : EnumFacing.VALUES ) for( EnumFacing facing : EnumFacing.VALUES )
{ {
if( direction == facing || BlockCable.isCable( world, pos.offset( facing ) ) ) if( BlockCable.doesConnectVisually( state, world, pos, facing ) )
{ {
flags |= 1 << facing.ordinal(); flags |= 1 << facing.ordinal();

View File

@ -15,10 +15,11 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread; import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.ITask; import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.filesystem.FileSystem;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -180,10 +181,49 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
return m_side; return m_side;
} }
@Nonnull
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
Map<String, IPeripheral> peripherals = new HashMap<>();
for( PeripheralWrapper wrapper : m_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( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
for( PeripheralWrapper wrapper : m_peripherals )
{
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
{
return wrapper.getPeripheral();
}
}
return null;
}
} }
private final IAPIEnvironment m_environment; private final IAPIEnvironment m_environment;
private FileSystem m_fileSystem;
private final PeripheralWrapper[] m_peripherals; private final PeripheralWrapper[] m_peripherals;
private boolean m_running; private boolean m_running;
@ -285,7 +325,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
synchronized( m_peripherals ) synchronized( m_peripherals )
{ {
m_fileSystem = m_environment.getFileSystem();
m_running = true; m_running = true;
for( int i=0; i<6; ++i ) for( int i=0; i<6; ++i )
{ {
@ -312,7 +351,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
wrapper.detach(); wrapper.detach();
} }
} }
m_fileSystem = null;
} }
} }

View File

@ -97,4 +97,11 @@ public class ComputerPeripheral
{ {
return (other != null && other.getClass() == this.getClass()); return (other != null && other.getClass() == this.getClass());
} }
@Nonnull
@Override
public Object getTarget()
{
return m_computer.getTile();
}
} }

View File

@ -21,7 +21,8 @@ public enum PeripheralType implements IStringSerializable
Cable( "cable" ), Cable( "cable" ),
WiredModemWithCable( "wired_modem_with_cable" ), WiredModemWithCable( "wired_modem_with_cable" ),
AdvancedModem( "advanced_modem" ), AdvancedModem( "advanced_modem" ),
Speaker( "speaker" ); Speaker( "speaker" ),
WiredModemFull( "wired_modem_full" );
private String m_name; private String m_name;

View File

@ -11,7 +11,6 @@ import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.PeripheralType; import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.modem.TileCable; import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block;
import net.minecraft.block.properties.PropertyBool; import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyEnum; import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockFaceShape; import net.minecraft.block.state.BlockFaceShape;
@ -51,23 +50,6 @@ public class BlockCable extends BlockPeripheralBase
public static final PropertyBool DOWN = PropertyBool.create( "down" ); public static final PropertyBool DOWN = PropertyBool.create( "down" );
} }
public static boolean isCable( IBlockAccess world, BlockPos pos )
{
Block block = world.getBlockState( pos ).getBlock();
if( block == ComputerCraft.Blocks.cable )
{
switch( ComputerCraft.Blocks.cable.getPeripheralType( world, pos ) )
{
case Cable:
case WiredModemWithCable:
{
return true;
}
}
}
return false;
}
// Members // Members
public BlockCable() public BlockCable()
@ -175,20 +157,17 @@ public class BlockCable extends BlockPeripheralBase
} }
} }
private boolean doesConnect( IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing dir ) public static boolean canConnectIn( IBlockState state, EnumFacing direction )
{ {
if( state.getValue( Properties.CABLE ) == BlockCableCableVariant.NONE ) return state.getValue( BlockCable.Properties.CABLE ) != BlockCableCableVariant.NONE
{ && state.getValue( BlockCable.Properties.MODEM ).getFacing() != direction;
return false; }
}
else if( state.getValue( Properties.MODEM ).getFacing() == dir ) public static boolean doesConnectVisually( IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing direction )
{ {
return true; if( state.getValue( Properties.CABLE ) == BlockCableCableVariant.NONE ) return false;
} if( state.getValue( Properties.MODEM ).getFacing() == direction ) return true;
else return ComputerCraft.getWiredElementAt( world, pos.offset( direction ), direction.getOpposite() ) != null;
{
return isCable( world, pos.offset( dir ) );
}
} }
@Nonnull @Nonnull
@ -196,12 +175,12 @@ public class BlockCable extends BlockPeripheralBase
@Deprecated @Deprecated
public IBlockState getActualState( @Nonnull IBlockState state, IBlockAccess world, BlockPos pos ) public IBlockState getActualState( @Nonnull IBlockState state, IBlockAccess world, BlockPos pos )
{ {
state = state.withProperty( Properties.NORTH, doesConnect( state, world, pos, EnumFacing.NORTH ) ); state = state.withProperty( Properties.NORTH, doesConnectVisually( state, world, pos, EnumFacing.NORTH ) );
state = state.withProperty( Properties.SOUTH, doesConnect( state, world, pos, EnumFacing.SOUTH ) ); state = state.withProperty( Properties.SOUTH, doesConnectVisually( state, world, pos, EnumFacing.SOUTH ) );
state = state.withProperty( Properties.EAST, doesConnect( state, world, pos, EnumFacing.EAST ) ); state = state.withProperty( Properties.EAST, doesConnectVisually( state, world, pos, EnumFacing.EAST ) );
state = state.withProperty( Properties.WEST, doesConnect( state, world, pos, EnumFacing.WEST ) ); state = state.withProperty( Properties.WEST, doesConnectVisually( state, world, pos, EnumFacing.WEST ) );
state = state.withProperty( Properties.UP, doesConnect( state, world, pos, EnumFacing.UP ) ); state = state.withProperty( Properties.UP, doesConnectVisually( state, world, pos, EnumFacing.UP ) );
state = state.withProperty( Properties.DOWN, doesConnect( state, world, pos, EnumFacing.DOWN ) ); state = state.withProperty( Properties.DOWN, doesConnectVisually( state, world, pos, EnumFacing.DOWN ) );
if( state.getValue( Properties.CABLE ) != BlockCableCableVariant.NONE ) if( state.getValue( Properties.CABLE ) != BlockCableCableVariant.NONE )
{ {
@ -345,7 +324,6 @@ public class BlockCable extends BlockPeripheralBase
if( WorldUtil.isVecInsideInclusive( bb, hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) if( WorldUtil.isVecInsideInclusive( bb, hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
{ {
world.setBlockState( pos, state.withProperty( Properties.MODEM, BlockCableModemVariant.None ), 3 ); world.setBlockState( pos, state.withProperty( Properties.MODEM, BlockCableModemVariant.None ), 3 );
cable.modemChanged();
item = PeripheralItemFactory.create( PeripheralType.WiredModem, null, 1 ); item = PeripheralItemFactory.create( PeripheralType.WiredModem, null, 1 );
} }
else else
@ -365,6 +343,7 @@ public class BlockCable extends BlockPeripheralBase
return super.removedByPlayer( state, world, pos, player, willHarvest ); return super.removedByPlayer( state, world, pos, player, willHarvest );
} }
@Nonnull
@Override @Override
public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult hit, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player ) public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult hit, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player )
{ {
@ -373,7 +352,7 @@ public class BlockCable extends BlockPeripheralBase
{ {
TileCable cable = (TileCable) tile; TileCable cable = (TileCable) tile;
PeripheralType type = getPeripheralType( state ); PeripheralType type = getPeripheralType( state );
if( type == PeripheralType.WiredModemWithCable ) if( type == PeripheralType.WiredModemWithCable )
{ {
if( hit == null || WorldUtil.isVecInsideInclusive( cable.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) if( hit == null || WorldUtil.isVecInsideInclusive( cable.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )

View File

@ -0,0 +1,102 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.common;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.modem.TileWiredModemFull;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import javax.annotation.Nonnull;
public class BlockWiredModemFull extends BlockPeripheralBase
{
// Statics
public static class Properties
{
public static final PropertyBool MODEM_ON = PropertyBool.create( "modem" );
public static final PropertyBool PERIPHERAL_ON = PropertyBool.create( "peripheral" );
}
// Members
public BlockWiredModemFull()
{
setHardness( 1.5f );
setUnlocalizedName( "computercraft:wired_modem_full" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( blockState.getBaseState()
.withProperty( Properties.MODEM_ON, false )
.withProperty( Properties.PERIPHERAL_ON, false )
);
}
@Override
protected IBlockState getDefaultBlockState( PeripheralType type, EnumFacing placedSide )
{
return getDefaultState();
}
@Nonnull
@Override
protected BlockStateContainer createBlockState()
{
return new BlockStateContainer( this,
Properties.MODEM_ON,
Properties.PERIPHERAL_ON
);
}
@Override
public int getMetaFromState( IBlockState state )
{
return 0;
}
@Nonnull
@Override
@Deprecated
public IBlockState getActualState( @Nonnull IBlockState state, IBlockAccess world, BlockPos pos )
{
TileEntity te = world.getTileEntity( pos );
if( te instanceof TileWiredModemFull )
{
TileWiredModemFull modem = (TileWiredModemFull) te;
int anim = modem.getAnim();
state = state
.withProperty( Properties.MODEM_ON, (anim & 1) != 0 )
.withProperty( Properties.PERIPHERAL_ON, (anim & 2) != 0 );
}
return state;
}
@Override
public PeripheralType getPeripheralType( int damage )
{
return PeripheralType.WiredModemFull;
}
@Override
public PeripheralType getPeripheralType( IBlockState state )
{
return PeripheralType.WiredModemFull;
}
@Override
public TilePeripheralBase createTile( PeripheralType type )
{
return new TileWiredModemFull();
}
}

View File

@ -11,8 +11,8 @@ import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -102,6 +102,8 @@ public abstract class ItemPeripheralBase extends ItemBlock implements IPeriphera
{ {
return "tile.computercraft:speaker"; return "tile.computercraft:speaker";
} }
case WiredModemFull:
return "tile.computercraft:wired_modem";
} }
} }

View File

@ -0,0 +1,18 @@
package dan200.computercraft.shared.peripheral.common;
import dan200.computercraft.shared.peripheral.PeripheralType;
import net.minecraft.block.Block;
public class ItemWiredModemFull extends ItemPeripheralBase
{
public ItemWiredModemFull( Block block )
{
super( block );
}
@Override
public PeripheralType getPeripheralType( int damage )
{
return PeripheralType.WiredModemFull;
}
}

View File

@ -47,6 +47,8 @@ public class PeripheralItemFactory
{ {
return advancedModem.create( type, label, quantity ); return advancedModem.create( type, label, quantity );
} }
case WiredModemFull:
return new ItemStack( ComputerCraft.Blocks.wiredModemFull, quantity );
} }
return ItemStack.EMPTY; return ItemStack.EMPTY;
} }

View File

@ -196,4 +196,11 @@ public class DiskDrivePeripheral implements IPeripheral
} }
return false; return false;
} }
@Nonnull
@Override
public Object getTarget()
{
return m_diskDrive;
}
} }

View File

@ -0,0 +1,416 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.peripheral.modem;
import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredElementTile;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.TilePeripheralBase;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import javax.annotation.Nonnull;
import java.io.File;
import java.util.*;
public class TileWiredModemFull extends TilePeripheralBase implements IWiredElementTile
{
private static class FullElement extends WiredModemElement
{
private final TileWiredModemFull m_entity;
private FullElement( TileWiredModemFull m_entity )
{
this.m_entity = m_entity;
}
@Override
protected void attachPeripheral( String name, IPeripheral peripheral )
{
for( int i = 0; i < 6; i++ )
{
WiredModemPeripheral modem = m_entity.m_modems[i];
if( modem != null && !name.equals( m_entity.getCachedPeripheralName( EnumFacing.VALUES[i] ) ) )
{
modem.attachPeripheral( name, peripheral );
}
}
}
@Override
protected void detachPeripheral( String name )
{
for( int i = 0; i < 6; i++ )
{
WiredModemPeripheral modem = m_entity.m_modems[i];
if( modem != null ) modem.detachPeripheral( name );
}
}
@Nonnull
@Override
public World getWorld()
{
return m_entity.getWorld();
}
@Nonnull
@Override
public Vec3d getPosition()
{
BlockPos pos = m_entity.getPos();
return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
}
@Nonnull
@Override
public Map<String, IPeripheral> getPeripherals()
{
return m_entity.getPeripherals();
}
}
private WiredModemPeripheral[] m_modems = new WiredModemPeripheral[6];
private boolean m_peripheralAccessAllowed = false;
private int[] m_attachedPeripheralIDs = new int[6];
private String[] m_attachedPeripheralTypes = new String[6];
private boolean m_destroyed = false;
private boolean m_connectionsFormed = false;
private final WiredModemElement m_element = new FullElement( this );
private final IWiredNode node = m_element.getNode();
public TileWiredModemFull()
{
Arrays.fill( m_attachedPeripheralIDs, -1 );
}
private void remove()
{
if( world == null || !world.isRemote )
{
node.remove();
m_connectionsFormed = false;
}
}
@Override
public void destroy()
{
if( !m_destroyed )
{
m_destroyed = true;
remove();
}
super.destroy();
}
@Override
public void onChunkUnload()
{
super.onChunkUnload();
remove();
}
@Override
public void invalidate()
{
super.invalidate();
remove();
}
@Override
public EnumFacing getDirection()
{
return EnumFacing.NORTH;
}
@Override
public void setDirection( EnumFacing dir )
{
}
@Override
public void onNeighbourChange()
{
if( !world.isRemote && m_peripheralAccessAllowed )
{
Map<String, IPeripheral> updated = getPeripherals();
if( updated.isEmpty() )
{
// If there are no peripherals then disable access and update the display state.
m_peripheralAccessAllowed = false;
updateAnim();
}
// Always invalidate the node: it's more accurate than checking if the peripherals
// have changed
node.invalidate();
}
}
@Nonnull
@Override
public AxisAlignedBB getBounds()
{
return BlockCable.FULL_BLOCK_AABB;
}
@Override
public boolean onActivate( EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ )
{
if( !getWorld().isRemote )
{
// On server, we interacted if a peripheral was found
Set<String> oldPeriphName = getPeripherals().keySet();
togglePeripheralAccess();
Set<String> periphName = getPeripherals().keySet();
if( !Objects.equal( periphName, oldPeriphName ) )
{
if( !oldPeriphName.isEmpty() )
{
List<String> names = new ArrayList<>( oldPeriphName );
names.sort( Comparator.naturalOrder() );
player.sendMessage(
new TextComponentTranslation( "gui.computercraft:wired_modem.peripheral_disconnected", String.join( ", ", names ) )
);
}
if( !periphName.isEmpty() )
{
List<String> names = new ArrayList<>( periphName );
names.sort( Comparator.naturalOrder() );
player.sendMessage(
new TextComponentTranslation( "gui.computercraft:wired_modem.peripheral_connected", String.join( ", ", names ) )
);
}
}
return true;
}
else
{
// On client, we can't know this, so we assume so to be safe
// The server will correct us if we're wrong
return true;
}
}
@Override
public void readFromNBT( NBTTagCompound tag )
{
super.readFromNBT( tag );
m_peripheralAccessAllowed = tag.getBoolean( "peripheralAccess" );
for( int i = 0; i < m_attachedPeripheralIDs.length; i++ )
{
if( tag.hasKey( "peripheralID_" + i, Constants.NBT.TAG_ANY_NUMERIC ) )
{
m_attachedPeripheralIDs[i] = tag.getInteger( "peripheralID_" + i );
}
if( tag.hasKey( "peripheralType_" + i, Constants.NBT.TAG_STRING ) )
{
m_attachedPeripheralTypes[i] = tag.getString( "peripheralType_" + i );
}
}
}
@Nonnull
@Override
public NBTTagCompound writeToNBT( NBTTagCompound tag )
{
tag = super.writeToNBT( tag );
tag.setBoolean( "peripheralAccess", m_peripheralAccessAllowed );
for( int i = 0; i < m_attachedPeripheralIDs.length; i++ )
{
if( m_attachedPeripheralIDs[i] >= 0 )
{
tag.setInteger( "peripheralID_" + i, m_attachedPeripheralIDs[i] );
}
if( m_attachedPeripheralTypes[i] != null )
{
tag.setString( "peripheralType_" + i, m_attachedPeripheralTypes[i] );
}
}
return tag;
}
protected void updateAnim()
{
int anim = 0;
for( WiredModemPeripheral modem : m_modems )
{
if( modem != null && modem.isActive() )
{
anim += 1;
break;
}
}
if( m_peripheralAccessAllowed )
{
anim += 2;
}
setAnim( anim );
}
@Override
public final void readDescription( @Nonnull NBTTagCompound tag )
{
super.readDescription( tag );
updateBlock();
}
@Override
public void update()
{
if( !getWorld().isRemote )
{
boolean changed = false;
for( WiredModemPeripheral peripheral : m_modems )
{
if( peripheral != null && peripheral.pollChanged() ) changed = true;
}
if( changed ) updateAnim();
if( !m_connectionsFormed )
{
networkChanged();
m_connectionsFormed = true;
}
}
super.update();
}
private void networkChanged()
{
if( getWorld().isRemote ) return;
World world = getWorld();
BlockPos current = getPos();
for( EnumFacing facing : EnumFacing.VALUES )
{
if( !world.isBlockLoaded( pos ) ) continue;
IWiredElement element = ComputerCraft.getWiredElementAt( world, current.offset( facing ), facing.getOpposite() );
if( element == null ) continue;
// If we can connect to it then do so
node.connectTo( element.getNode() );
}
node.invalidate();
}
// private stuff
private void togglePeripheralAccess()
{
if( !m_peripheralAccessAllowed )
{
m_peripheralAccessAllowed = true;
if( getPeripherals().isEmpty() )
{
m_peripheralAccessAllowed = false;
return;
}
}
else
{
m_peripheralAccessAllowed = false;
}
updateAnim();
node.invalidate();
}
@Nonnull
private Map<String, IPeripheral> getPeripherals()
{
if( !m_peripheralAccessAllowed ) return Collections.emptyMap();
Map<String, IPeripheral> peripherals = new HashMap<>( 6 );
for( EnumFacing facing : EnumFacing.VALUES )
{
BlockPos neighbour = getPos().offset( facing );
IPeripheral peripheral = TileCable.getPeripheral( getWorld(), neighbour, facing.getOpposite() );
if( peripheral != null && !(peripheral instanceof WiredModemPeripheral) )
{
String type = peripheral.getType();
int id = m_attachedPeripheralIDs[facing.ordinal()];
String oldType = m_attachedPeripheralTypes[facing.ordinal()];
if( id < 0 || !type.equals( oldType ) )
{
m_attachedPeripheralTypes[facing.ordinal()] = type;
id = m_attachedPeripheralIDs[facing.ordinal()] = IDAssigner.getNextIDFromFile( new File(
ComputerCraft.getWorldDir( getWorld() ),
"computer/lastid_" + type + ".txt"
) );
}
peripherals.put( type + "_" + id, peripheral );
}
}
return peripherals;
}
private String getCachedPeripheralName( EnumFacing facing )
{
if( !m_peripheralAccessAllowed ) return null;
int id = m_attachedPeripheralIDs[facing.ordinal()];
String type = m_attachedPeripheralTypes[facing.ordinal()];
return id < 0 || type == null ? null : type + "_" + id;
}
// IWiredElementTile
@Nonnull
@Override
public IWiredElement getWiredElement( @Nonnull EnumFacing side )
{
return m_element;
}
// IPeripheralTile
@Override
public IPeripheral getPeripheral( EnumFacing side )
{
WiredModemPeripheral peripheral = m_modems[side.ordinal()];
if( peripheral == null )
{
peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_element )
{
@Nonnull
@Override
public Vec3d getPosition()
{
BlockPos pos = getPos().offset( side );
return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
}
};
}
return peripheral;
}
}

View File

@ -0,0 +1,59 @@
package dan200.computercraft.shared.peripheral.modem;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.wired.WiredNode;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
public abstract class WiredModemElement implements IWiredElement
{
private final IWiredNode node = new WiredNode( this );
private final Map<String, IPeripheral> remotePeripherals = new HashMap<>();
@Nonnull
@Override
public IWiredNode getNode()
{
return node;
}
@Nonnull
@Override
public String getSenderID()
{
return "modem";
}
@Override
public void networkChanged( @Nonnull IWiredNetworkChange change )
{
synchronized( remotePeripherals )
{
remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );
for( String name : change.peripheralsRemoved().keySet() )
{
detachPeripheral( name );
}
for( Map.Entry<String, IPeripheral> peripheral : change.peripheralsAdded().entrySet() )
{
attachPeripheral( peripheral.getKey(), peripheral.getValue() );
}
remotePeripherals.putAll( change.peripheralsAdded() );
}
}
public Map<String, IPeripheral> getRemotePeripherals()
{
return remotePeripherals;
}
protected abstract void attachPeripheral( String name, IPeripheral peripheral );
protected abstract void detachPeripheral( String name );
}

View File

@ -0,0 +1,410 @@
package dan200.computercraft.shared.peripheral.modem;
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 net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
public class WiredModemPeripheral extends ModemPeripheral implements IWiredSender
{
private final WiredModemElement modem;
private final Map<String, RemotePeripheralWrapper> peripheralWrappers = new HashMap<>();
public WiredModemPeripheral( WiredModemElement modem )
{
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
@Override
public Vec3d getPosition()
{
return modem.getPosition();
}
//endregion
//region IPeripheral
@Nonnull
@Override
public String[] getMethodNames()
{
String[] methods = super.getMethodNames();
String[] newMethods = new String[methods.length + 5];
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";
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
synchronized( peripheralWrappers )
{
int idx = 1;
Map<Object, Object> table = new HashMap<>();
for( String name : peripheralWrappers.keySet() )
{
table.put( idx++, name );
}
return new Object[]{ table };
}
}
case 1:
{
// isPresentRemote
String type = getTypeRemote( getString( arguments, 0 ) );
return new Object[]{ type != null };
}
case 2:
{
// getTypeRemote
String type = getTypeRemote( getString( arguments, 0 ) );
if( type != null )
{
return new Object[]{ type };
}
return null;
}
case 3:
{
// getMethodsRemote
String[] methodNames = getMethodNamesRemote( getString( arguments, 0 ) );
if( methodNames != null )
{
Map<Object, Object> table = new HashMap<>();
for( int i = 0; i < methodNames.length; ++i )
{
table.put( i + 1, methodNames[i] );
}
return new Object[]{ table };
}
return null;
}
case 4:
{
// callRemote
String remoteName = getString( arguments, 0 );
String methodName = getString( arguments, 1 );
Object[] methodArgs = new Object[arguments.length - 2];
System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 );
return callMethodRemote( remoteName, context, methodName, methodArgs );
}
default:
{
// The regular modem methods
return super.callMethod( computer, context, method, arguments );
}
}
}
@Override
public void attach( @Nonnull IComputerAccess computer )
{
super.attach( computer );
synchronized( modem.getRemotePeripherals() )
{
synchronized( peripheralWrappers )
{
for( Map.Entry<String, IPeripheral> entry : modem.getRemotePeripherals().entrySet() )
{
attachPeripheralImpl( entry.getKey(), entry.getValue() );
}
}
}
}
@Override
public synchronized void detach( @Nonnull IComputerAccess computer )
{
synchronized( peripheralWrappers )
{
for( RemotePeripheralWrapper wrapper : peripheralWrappers.values() )
{
wrapper.detach();
}
peripheralWrappers.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 )
{
if( getComputer() == null ) return;
synchronized( peripheralWrappers )
{
attachPeripheralImpl( name, peripheral );
}
}
public void detachPeripheral( String name )
{
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( name );
if( wrapper != null )
{
peripheralWrappers.remove( name );
wrapper.detach();
}
}
}
private void attachPeripheralImpl( String periphName, IPeripheral peripheral )
{
if( !peripheralWrappers.containsKey( periphName ) )
{
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
peripheralWrappers.put( periphName, wrapper );
wrapper.attach();
}
}
private String getTypeRemote( String remoteName )
{
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getType();
}
}
return null;
}
private String[] getMethodNamesRemote( String remoteName )
{
synchronized( peripheralWrappers )
{
RemotePeripheralWrapper wrapper = peripheralWrappers.get( remoteName );
if( wrapper != null )
{
return wrapper.getMethodNames();
}
}
return null;
}
private Object[] callMethodRemote( String remoteName, ILuaContext context, String method, Object[] arguments ) throws LuaException, InterruptedException
{
RemotePeripheralWrapper wrapper;
synchronized( peripheralWrappers )
{
wrapper = peripheralWrappers.get( remoteName );
}
if( wrapper != null )
{
return wrapper.callMethod( context, method, arguments );
}
throw new LuaException( "No peripheral: " + 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<String, Integer> m_methodMap;
public 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 );
}
@Nonnull
@Override
public String getAttachmentName()
{
return m_name;
}
@Nonnull
@Override
public Map<String, IPeripheral> 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 );
}
}
}
}

View File

@ -145,6 +145,13 @@ public class PrinterPeripheral implements IPeripheral
return false; return false;
} }
@Nonnull
@Override
public Object getTarget()
{
return m_printer;
}
private Terminal getCurrentPage() throws LuaException private Terminal getCurrentPage() throws LuaException
{ {
Terminal currentPage = m_printer.getCurrentPage(); Terminal currentPage = m_printer.getCurrentPage();

View File

@ -35,10 +35,7 @@ import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeriphera
import dan200.computercraft.shared.peripheral.common.*; import dan200.computercraft.shared.peripheral.common.*;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem; import dan200.computercraft.shared.peripheral.modem.*;
import dan200.computercraft.shared.peripheral.modem.TileAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.peripheral.modem.TileWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor; import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
import dan200.computercraft.shared.peripheral.printer.TilePrinter; import dan200.computercraft.shared.peripheral.printer.TilePrinter;
@ -52,6 +49,7 @@ import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import dan200.computercraft.shared.util.*; import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.DefaultWiredProvider;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.creativetab.CreativeTabs; import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@ -272,6 +270,10 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
// Command Computer // Command Computer
ComputerCraft.Blocks.advancedModem = new BlockAdvancedModem(); ComputerCraft.Blocks.advancedModem = new BlockAdvancedModem();
registry.register( ComputerCraft.Blocks.advancedModem.setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) ); registry.register( ComputerCraft.Blocks.advancedModem.setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) );
// Full block modem
ComputerCraft.Blocks.wiredModemFull = new BlockWiredModemFull();
registry.register( ComputerCraft.Blocks.wiredModemFull.setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) ) );
} }
@SubscribeEvent @SubscribeEvent
@ -291,9 +293,12 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
// Command Computer // Command Computer
registry.register( new ItemCommandComputer( ComputerCraft.Blocks.commandComputer ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "command_computer" ) ) ); registry.register( new ItemCommandComputer( ComputerCraft.Blocks.commandComputer ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "command_computer" ) ) );
// Command Computer // Advanced modem
registry.register( new ItemAdvancedModem( ComputerCraft.Blocks.advancedModem ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) ); registry.register( new ItemAdvancedModem( ComputerCraft.Blocks.advancedModem ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "advanced_modem" ) ) );
// Full block modem
registry.register( new ItemWiredModemFull( ComputerCraft.Blocks.wiredModemFull ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full" ) ) );
// Items // Items
// Floppy Disk // Floppy Disk
ComputerCraft.Items.disk = new ItemDiskLegacy(); ComputerCraft.Items.disk = new ItemDiskLegacy();
@ -469,6 +474,7 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
GameRegistry.registerTileEntity( TileCommandComputer.class, ComputerCraft.LOWER_ID + " : " + "command_computer" ); GameRegistry.registerTileEntity( TileCommandComputer.class, ComputerCraft.LOWER_ID + " : " + "command_computer" );
GameRegistry.registerTileEntity( TileAdvancedModem.class, ComputerCraft.LOWER_ID + " : " + "advanced_modem" ); GameRegistry.registerTileEntity( TileAdvancedModem.class, ComputerCraft.LOWER_ID + " : " + "advanced_modem" );
GameRegistry.registerTileEntity( TileSpeaker.class, ComputerCraft.LOWER_ID + " : " + "speaker" ); GameRegistry.registerTileEntity( TileSpeaker.class, ComputerCraft.LOWER_ID + " : " + "speaker" );
GameRegistry.registerTileEntity( TileWiredModemFull.class, ComputerCraft.LOWER_ID + " : " + "wired_modem_full" );
// Register peripheral providers // Register peripheral providers
ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() ); ComputerCraftAPI.registerPeripheralProvider( new DefaultPeripheralProvider() );
@ -482,6 +488,9 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
// Register media providers // Register media providers
ComputerCraftAPI.registerMediaProvider( new DefaultMediaProvider() ); ComputerCraftAPI.registerMediaProvider( new DefaultMediaProvider() );
// Register network providers
ComputerCraftAPI.registerWiredProvider( new DefaultWiredProvider() );
} }
private void registerForgeHandlers() private void registerForgeHandlers()

View File

@ -0,0 +1,23 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredElementTile;
import dan200.computercraft.api.network.wired.IWiredProvider;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class DefaultWiredProvider implements IWiredProvider
{
@Nullable
@Override
public IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
TileEntity te = world.getTileEntity( pos );
return te instanceof IWiredElementTile ? ((IWiredElementTile) te).getWiredElement( side ) : null;
}
}

View File

@ -0,0 +1,46 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.ComputerCraft;
/**
* Verifies certain elements of a network are "well formed".
*
* This adds substantial overhead to network modification, and so should only be enabled
* in a development environment.
*/
public class InvariantChecker
{
private static final boolean ENABLED = false;
public static void checkNode( WiredNode node )
{
if( !ENABLED ) return;
WiredNetwork network = node.network;
if( network == null )
{
ComputerCraft.log.error( "Node's network is null", new Exception() );
return;
}
if( network.nodes == null || !network.nodes.contains( node ) )
{
ComputerCraft.log.error( "Node's network does not contain node", new Exception() );
}
for( WiredNode neighbour : node.neighbours )
{
if( !neighbour.neighbours.contains( node ) )
{
ComputerCraft.log.error( "Neighbour is missing node", new Exception() );
}
}
}
public static void checkNetwork( WiredNetwork network )
{
if( !ENABLED ) return;
for( WiredNode node : network.nodes ) checkNode( node );
}
}

View File

@ -0,0 +1,458 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.api.network.Packet;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class WiredNetwork implements IWiredNetwork
{
final ReadWriteLock lock = new ReentrantReadWriteLock();
HashSet<WiredNode> nodes;
private HashMap<String, IPeripheral> peripherals = new HashMap<>();
public WiredNetwork( WiredNode node )
{
nodes = new HashSet<>( 1 );
nodes.add( node );
}
private WiredNetwork( HashSet<WiredNode> nodes )
{
this.nodes = nodes;
}
@Override
public boolean connect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV )
{
WiredNode wiredU = checkNode( nodeU );
WiredNode wiredV = checkNode( nodeV );
if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot add a connection to oneself." );
lock.writeLock().lock();
try
{
if( nodes == null ) throw new IllegalStateException( "Cannot add a connection to an empty network." );
boolean hasU = wiredU.network == this;
boolean hasV = wiredV.network == this;
if( !hasU && !hasV ) throw new IllegalArgumentException( "Neither node is in the network." );
// We're going to assimilate a node. Copy across all edges and vertices.
if( !hasU || !hasV )
{
WiredNetwork other = hasU ? wiredV.network : wiredU.network;
other.lock.writeLock().lock();
try
{
// Cache several properties for iterating over later
Map<String, IPeripheral> otherPeripherals = other.peripherals;
Map<String, IPeripheral> thisPeripherals = otherPeripherals.isEmpty() ? peripherals : new HashMap<>( peripherals );
Collection<WiredNode> thisNodes = otherPeripherals.isEmpty() ? nodes : new ArrayList<>( this.nodes );
Collection<WiredNode> otherNodes = other.nodes;
// Move all nodes across into this network, destroying the original nodes.
nodes.addAll( otherNodes );
for( WiredNode node : otherNodes ) node.network = this;
other.nodes = null;
// Move all peripherals across,
other.peripherals = null;
peripherals.putAll( otherPeripherals );
if( !thisPeripherals.isEmpty() )
{
WiredNetworkChange.added( thisPeripherals ).broadcast( otherNodes );
}
if( !otherPeripherals.isEmpty() )
{
WiredNetworkChange.added( otherPeripherals ).broadcast( thisNodes );
}
}
finally
{
other.lock.writeLock().unlock();
}
}
boolean added = wiredU.neighbours.add( wiredV );
if( added ) wiredV.neighbours.add( wiredU );
InvariantChecker.checkNetwork( this );
InvariantChecker.checkNode( wiredU );
InvariantChecker.checkNode( wiredV );
return added;
}
finally
{
lock.writeLock().unlock();
}
}
@Override
public boolean disconnect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV )
{
WiredNode wiredU = checkNode( nodeU );
WiredNode wiredV = checkNode( nodeV );
if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot remove a connection to oneself." );
lock.writeLock().lock();
try
{
boolean hasU = wiredU.network == this;
boolean hasV = wiredV.network == this;
if( !hasU || !hasV ) throw new IllegalArgumentException( "One node is not in the network." );
// If there was no connection to remove then split.
if( !wiredU.neighbours.remove( wiredV ) ) return false;
wiredV.neighbours.remove( wiredU );
// Determine if there is still some connection from u to v.
// Note this is an inlining of reachableNodes which short-circuits
// if all nodes are reachable.
Queue<WiredNode> enqueued = new ArrayDeque<>();
HashSet<WiredNode> reachableU = new HashSet<>();
reachableU.add( wiredU );
enqueued.add( wiredU );
while( !enqueued.isEmpty() )
{
WiredNode node = enqueued.remove();
for( WiredNode neighbour : node.neighbours )
{
// If we can reach wiredV from wiredU then abort.
if( neighbour == wiredV ) return true;
// Otherwise attempt to enqueue this neighbour as well.
if( reachableU.add( neighbour ) ) enqueued.add( neighbour );
}
}
// Create a new network with all U-reachable nodes/edges and remove them
// from the existing graph.
WiredNetwork networkU = new WiredNetwork( reachableU );
networkU.lock.writeLock().lock();
try
{
// Remove nodes from this network
nodes.removeAll( reachableU );
// Set network and transfer peripherals
for( WiredNode node : reachableU )
{
node.network = networkU;
networkU.peripherals.putAll( node.peripherals );
peripherals.keySet().removeAll( node.peripherals.keySet() );
}
// Broadcast changes
if( peripherals.size() != 0 ) WiredNetworkChange.removed( peripherals ).broadcast( networkU.nodes );
if( networkU.peripherals.size() != 0 )
{
WiredNetworkChange.removed( networkU.peripherals ).broadcast( nodes );
}
InvariantChecker.checkNetwork( this );
InvariantChecker.checkNetwork( networkU );
InvariantChecker.checkNode( wiredU );
InvariantChecker.checkNode( wiredV );
return true;
}
finally
{
networkU.lock.writeLock().unlock();
}
}
finally
{
lock.writeLock().unlock();
}
}
@Override
public boolean remove( @Nonnull IWiredNode node )
{
WiredNode wired = checkNode( node );
lock.writeLock().lock();
try
{
// If we're the empty graph then just abort: nodes must have _some_ network.
if( nodes == null ) return false;
if( nodes.size() <= 1 ) return false;
if( wired.network != this ) return false;
HashSet<WiredNode> neighbours = wired.neighbours;
// Remove this node and move into a separate network.
nodes.remove( wired );
for( WiredNode neighbour : neighbours ) neighbour.neighbours.remove( wired );
WiredNetwork wiredNetwork = new WiredNetwork( wired );
// If we're a leaf node in the graph (only one neighbour) then we don't need to
// check for network splitting
if( neighbours.size() == 1 )
{
// Broadcast our simple peripheral changes
removeSingleNode( wired, wiredNetwork );
InvariantChecker.checkNode( wired );
InvariantChecker.checkNetwork( wiredNetwork );
return true;
}
HashSet<WiredNode> reachable = reachableNodes( neighbours.iterator().next() );
// If all nodes are reachable then exit.
if( reachable.size() == nodes.size() )
{
// Broadcast our simple peripheral changes
removeSingleNode( wired, wiredNetwork );
InvariantChecker.checkNode( wired );
InvariantChecker.checkNetwork( wiredNetwork );
return true;
}
// A split may cause 2..neighbours.size() separate networks, so we
// iterate through our neighbour list, generating child networks.
neighbours.removeAll( reachable );
ArrayList<WiredNetwork> maximals = new ArrayList<>( neighbours.size() + 1 );
maximals.add( wiredNetwork );
maximals.add( new WiredNetwork( reachable ) );
while( neighbours.size() > 0 )
{
reachable = reachableNodes( neighbours.iterator().next() );
neighbours.removeAll( reachable );
maximals.add( new WiredNetwork( reachable ) );
}
for( WiredNetwork network : maximals ) network.lock.writeLock().lock();
try
{
// We special case the original node: detaching all peripherals when needed.
wired.network = wiredNetwork;
wired.peripherals = Collections.emptyMap();
// Ensure every network is finalised
for( WiredNetwork network : maximals )
{
for( WiredNode child : network.nodes )
{
child.network = network;
network.peripherals.putAll( child.peripherals );
}
}
for( WiredNetwork network : maximals ) InvariantChecker.checkNetwork( network );
InvariantChecker.checkNode( wired );
// Then broadcast network changes once all nodes are finalised
for( WiredNetwork network : maximals )
{
WiredNetworkChange.changeOf( peripherals, network.peripherals ).broadcast( network.nodes );
}
}
finally
{
for( WiredNetwork network : maximals ) network.lock.writeLock().unlock();
}
nodes.clear();
peripherals.clear();
return true;
}
finally
{
lock.writeLock().unlock();
}
}
@Override
public void invalidate( @Nonnull IWiredNode node )
{
WiredNode wired = checkNode( node );
lock.writeLock().lock();
try
{
if( wired.network != this ) throw new IllegalStateException( "Node is not on this network" );
Map<String, IPeripheral> oldPeripherals = wired.peripherals;
Map<String, IPeripheral> newPeripherals = wired.element.getPeripherals();
WiredNetworkChange change = WiredNetworkChange.changeOf( oldPeripherals, newPeripherals );
if( change.isEmpty() ) return;
wired.peripherals = newPeripherals;
// Detach the old peripherals then remove them.
peripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );
// Add the new peripherals and attach them
peripherals.putAll( change.peripheralsAdded() );
change.broadcast( nodes );
}
finally
{
lock.writeLock().unlock();
}
}
void transmitPacket( WiredNode start, Packet packet, double range, boolean interdimensional )
{
Map<WiredNode, TransmitPoint> points = new HashMap<>();
TreeSet<TransmitPoint> transmitTo = new TreeSet<>();
{
TransmitPoint startEntry = start.element.getWorld() != packet.getSender().getWorld()
? new TransmitPoint( start, Double.POSITIVE_INFINITY, true )
: new TransmitPoint( start, start.element.getPosition().distanceTo( packet.getSender().getPosition() ), false );
points.put( start, startEntry );
transmitTo.add( startEntry );
}
{
TransmitPoint point;
while( (point = transmitTo.pollFirst()) != null )
{
World world = point.node.element.getWorld();
Vec3d position = point.node.element.getPosition();
for( WiredNode neighbour : point.node.neighbours )
{
TransmitPoint neighbourPoint = points.get( neighbour );
boolean newInterdimensional;
double newDistance;
if( world != neighbour.element.getWorld() )
{
newInterdimensional = true;
newDistance = Double.POSITIVE_INFINITY;
}
else
{
newInterdimensional = false;
newDistance = point.distance + position.distanceTo( neighbour.element.getPosition() );
}
if( neighbourPoint == null )
{
neighbourPoint = new TransmitPoint( neighbour, newDistance, newInterdimensional );
points.put( neighbour, neighbourPoint );
transmitTo.add( neighbourPoint );
}
else if( newDistance < neighbourPoint.distance )
{
transmitTo.remove( neighbourPoint );
neighbourPoint.distance = newDistance;
neighbourPoint.interdimensional = newInterdimensional;
transmitTo.add( neighbourPoint );
}
}
}
}
for( TransmitPoint point : points.values() )
{
point.node.tryTransmit( packet, point.distance, point.interdimensional, range, interdimensional );
}
}
private void removeSingleNode( WiredNode wired, WiredNetwork wiredNetwork )
{
wiredNetwork.lock.writeLock().lock();
try
{
// Cache all the old nodes.
Map<String, IPeripheral> wiredPeripherals = new HashMap<>( wired.peripherals );
// Setup the new node's network
// Detach the old peripherals then remove them from the old network
wired.network = wiredNetwork;
wired.neighbours.clear();
wired.peripherals = Collections.emptyMap();
// Broadcast the change
if( !peripherals.isEmpty() ) WiredNetworkChange.removed( peripherals ).broadcast( wired );
// Now remove all peripherals from this network and broadcast the change.
peripherals.keySet().removeAll( wiredPeripherals.keySet() );
if( !wiredPeripherals.isEmpty() ) WiredNetworkChange.removed( wiredPeripherals ).broadcast( nodes );
}
finally
{
wiredNetwork.lock.writeLock().unlock();
}
}
private static class TransmitPoint implements Comparable<TransmitPoint>
{
final WiredNode node;
double distance;
boolean interdimensional;
TransmitPoint( WiredNode node, double distance, boolean interdimensional )
{
this.node = node;
this.distance = distance;
this.interdimensional = interdimensional;
}
@Override
public int compareTo( @Nonnull TransmitPoint o )
{
// Objects with the same distance are not the same object, so we must add an additional layer of ordering.
return distance == o.distance
? Integer.compare( node.hashCode(), o.node.hashCode() )
: Double.compare( distance, o.distance );
}
}
private static WiredNode checkNode( IWiredNode node )
{
if( node instanceof WiredNode )
{
return (WiredNode) node;
}
else
{
throw new IllegalArgumentException( "Unknown implementation of IWiredNode: " + node );
}
}
private static HashSet<WiredNode> reachableNodes( WiredNode start )
{
Queue<WiredNode> enqueued = new ArrayDeque<>();
HashSet<WiredNode> reachable = new HashSet<>();
reachable.add( start );
enqueued.add( start );
WiredNode node;
while( (node = enqueued.poll()) != null )
{
for( WiredNode neighbour : node.neighbours )
{
// Otherwise attempt to enqueue this neighbour as well.
if( reachable.add( neighbour ) ) enqueued.add( neighbour );
}
}
return reachable;
}
}

View File

@ -0,0 +1,101 @@
package dan200.computercraft.shared.wired;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class WiredNetworkChange implements IWiredNetworkChange
{
private final Map<String, IPeripheral> removed;
private final Map<String, IPeripheral> added;
private WiredNetworkChange( Map<String, IPeripheral> removed, Map<String, IPeripheral> added )
{
this.removed = removed;
this.added = added;
}
public static WiredNetworkChange changed( Map<String, IPeripheral> removed, Map<String, IPeripheral> added )
{
return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.unmodifiableMap( added ) );
}
public static WiredNetworkChange added( Map<String, IPeripheral> added )
{
return new WiredNetworkChange( Collections.emptyMap(), Collections.unmodifiableMap( added ) );
}
public static WiredNetworkChange removed( Map<String, IPeripheral> removed )
{
return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.emptyMap() );
}
public static WiredNetworkChange changeOf( Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals )
{
Map<String, IPeripheral> added = new HashMap<>( newPeripherals );
Map<String, IPeripheral> removed = new HashMap<>();
for( Map.Entry<String, IPeripheral> entry : oldPeripherals.entrySet() )
{
String oldKey = entry.getKey();
IPeripheral oldValue = entry.getValue();
if( newPeripherals.containsKey( oldKey ) )
{
IPeripheral rightValue = added.get( oldKey );
if( oldValue.equals( rightValue ) )
{
added.remove( oldKey );
}
else
{
removed.put( oldKey, oldValue );
}
}
else
{
removed.put( oldKey, oldValue );
}
}
return changed( removed, added );
}
@Nonnull
@Override
public Map<String, IPeripheral> peripheralsAdded()
{
return added;
}
@Nonnull
@Override
public Map<String, IPeripheral> peripheralsRemoved()
{
return removed;
}
public boolean isEmpty()
{
return added.isEmpty() && removed.isEmpty();
}
void broadcast( Iterable<WiredNode> nodes )
{
if( !isEmpty() )
{
for( WiredNode node : nodes ) node.element.networkChanged( this );
}
}
void broadcast( WiredNode node )
{
if( !isEmpty() )
{
node.element.networkChanged( this );
}
}
}

View File

@ -0,0 +1,151 @@
package dan200.computercraft.shared.wired;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.network.IPacketReceiver;
import dan200.computercraft.api.network.Packet;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
public final class WiredNode implements IWiredNode
{
private Set<IPacketReceiver> receivers;
final IWiredElement element;
Map<String, IPeripheral> peripherals = Collections.emptyMap();
final HashSet<WiredNode> neighbours = new HashSet<>();
volatile WiredNetwork network;
public WiredNode( IWiredElement element )
{
this.element = element;
this.network = new WiredNetwork( this );
}
@Override
public synchronized void addReceiver( @Nonnull IPacketReceiver receiver )
{
if( receivers == null ) receivers = new HashSet<>();
receivers.add( receiver );
}
@Override
public synchronized void removeReceiver( @Nonnull IPacketReceiver receiver )
{
if( receivers != null ) receivers.remove( receiver );
}
synchronized void tryTransmit( Packet packet, double packetDistance, boolean packetInterdimensional, double range, boolean interdimensional )
{
if( receivers == null ) return;
for( IPacketReceiver receiver : receivers )
{
if( !packetInterdimensional )
{
double receiveRange = Math.max( range, receiver.getRange() ); // Ensure range is symmetrical
if( interdimensional || receiver.isInterdimensional() || packetDistance < receiveRange )
{
receiver.receiveSameDimension( packet, packetDistance + element.getPosition().distanceTo( receiver.getPosition() ) );
}
}
else
{
if( interdimensional || receiver.isInterdimensional() )
{
receiver.receiveDifferentDimension( packet );
}
}
}
}
@Override
public boolean isWireless()
{
return false;
}
@Override
public void transmitSameDimension( @Nonnull Packet packet, double range )
{
Preconditions.checkNotNull( packet, "packet cannot be null" );
if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this )
{
throw new IllegalArgumentException( "Sender is not in the network" );
}
acquireReadLock();
try
{
network.transmitPacket( this, packet, range, false );
}
finally
{
network.lock.readLock().unlock();
}
}
@Override
public void transmitInterdimensional( @Nonnull Packet packet )
{
Preconditions.checkNotNull( packet, "packet cannot be null" );
if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this )
{
throw new IllegalArgumentException( "Sender is not in the network" );
}
acquireReadLock();
try
{
network.transmitPacket( this, packet, 0, true );
}
finally
{
network.lock.readLock().unlock();
}
}
@Nonnull
@Override
public IWiredElement getElement()
{
return element;
}
@Nonnull
@Override
public IWiredNetwork getNetwork()
{
return network;
}
@Override
public String toString()
{
return "WiredNode{@" + element.getPosition() + " (" + element.getClass().getSimpleName() + ")}";
}
private void acquireReadLock()
{
WiredNetwork currentNetwork = network;
while( true )
{
Lock lock = currentNetwork.lock.readLock();
lock.lock();
if( currentNetwork == network ) return;
lock.unlock();
}
}
}

View File

@ -1,7 +1,11 @@
{ {
"parent": "minecraft:recipes/root", "parent": "minecraft:recipes/root",
"rewards": { "rewards": {
"recipes": [ "computercraft:wired_modem" ] "recipes": [
"computercraft:wired_modem",
"computercraft:wired_modem_full_to",
"computercraft:wired_modem_full_from"
]
}, },
"criteria": { "criteria": {
"has_normal": { "has_normal": {
@ -22,6 +26,12 @@
"items": [ { "item": "computercraft:cable", "data": 0 } ] "items": [ { "item": "computercraft:cable", "data": 0 } ]
} }
}, },
"has_modem_full": {
"trigger": "minecraft:inventory_changed",
"conditions": {
"items": [ { "item": "computercraft:wired_modem_full", "data": 0 } ]
}
},
"has_the_recipe": { "has_the_recipe": {
"trigger": "minecraft:recipe_unlocked", "trigger": "minecraft:recipe_unlocked",
"conditions": { "recipe": "computercraft:wired_modem" } "conditions": { "recipe": "computercraft:wired_modem" }
@ -32,6 +42,7 @@
"has_normal", "has_normal",
"has_advanced", "has_advanced",
"has_cable", "has_cable",
"has_modem_full",
"has_the_recipe" "has_the_recipe"
] ]
] ]

View File

@ -0,0 +1,9 @@
{
"variants": {
"modem=false,peripheral=false": { "model": "computercraft:wired_modem_full_off" },
"modem=false,peripheral=true": { "model": "computercraft:wired_modem_full_off_peripheral" },
"modem=true,peripheral=false": { "model": "computercraft:wired_modem_full_on" },
"modem=true,peripheral=true": { "model": "computercraft:wired_modem_full_on_peripheral" }
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "computercraft:blocks/wired_modem_face"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "computercraft:blocks/wired_modem_face_peripheral"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "computercraft:blocks/wired_modem_face_on"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "computercraft:blocks/wired_modem_face_peripheral_on"
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "computercraft:block/wired_modem_full_off"
}

View File

@ -0,0 +1,7 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{ "item": "computercraft:wired_modem_full", "data": 0 }
],
"result": { "item": "computercraft:cable", "data": 1 }
}

View File

@ -0,0 +1,7 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{ "item": "computercraft:cable", "data": 1 }
],
"result": { "item": "computercraft:wired_modem_full", "data": 0 }
}

View File

@ -0,0 +1,506 @@
package dan200.computercraft.shared.wired;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.*;
public class NetworkTest
{
@Before
public void setup()
{
ComputerCraft.log = LogManager.getLogger();
}
@Test
public void testConnect()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
assertNotEquals( "A's and B's network must be different", aN.getNetwork(), bN.getNetwork() );
assertNotEquals( "A's and C's network must be different", aN.getNetwork(), cN.getNetwork() );
assertNotEquals( "B's and C's network must be different", bN.getNetwork(), cN.getNetwork() );
assertTrue( "Must be able to add connection", aN.getNetwork().connect( aN, bN ) );
assertFalse( "Cannot add connection twice", aN.getNetwork().connect( aN, bN ) );
assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's network should be A and B", Sets.newHashSet( aN, bN ), nodes( aN.getNetwork() ) );
assertEquals( "A's peripheral set should be A, B", Sets.newHashSet( "a", "b" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be A, B", Sets.newHashSet( "a", "b" ), bE.allPeripherals().keySet() );
aN.getNetwork().connect( aN, cN );
assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() );
assertEquals( "A's network should be A, B and C", Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ) );
assertEquals( "A's neighbour set should be B, C", Sets.newHashSet( bN, cN ), neighbours( aN ) );
assertEquals( "B's neighbour set should be A", Sets.newHashSet( aN ), neighbours( bN ) );
assertEquals( "C's neighbour set should be A", Sets.newHashSet( aN ), neighbours( cN ) );
assertEquals( "A's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet() );
assertEquals( "C's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet() );
}
@Test
public void testDisconnectNoChange()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().connect( bN, cN );
aN.getNetwork().disconnect( aN, bN );
assertEquals( "A's and B's network must be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() );
assertEquals( "A's network should be A, B and C", Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ) );
assertEquals( "A's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet() );
assertEquals( "C's peripheral set should be A, B, C", Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet() );
}
@Test
public void testDisconnectLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() );
assertEquals( "A's network should be A and C", Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ) );
assertEquals( "B's network should be B", Sets.newHashSet( bN ), nodes( bN.getNetwork() ) );
assertEquals( "A's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be B", Sets.newHashSet( "b" ), bE.allPeripherals().keySet() );
assertEquals( "C's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet() );
}
@Test
public void testDisconnectSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
aN.getNetwork().connect( aN, bN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and A_'s network must be equal", aN.getNetwork(), aaN.getNetwork() );
assertEquals( "B's and B_'s network must be equal", bN.getNetwork(), bbN.getNetwork() );
assertEquals( "A's network should be A and A_", Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ) );
assertEquals( "B's network should be B and B_", Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ) );
assertEquals( "A's peripheral set should be A and A_", Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be B and B_", Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet() );
}
@Test
public void testRemoveSingle()
{
NetworkElement aE = new NetworkElement( null, null, "a" );
IWiredNode aN = aE.getNode();
IWiredNetwork network = aN.getNetwork();
assertFalse( "Cannot remove node from an empty network", aN.remove() );
assertEquals( "Networks are same before and after", network, aN.getNetwork() );
}
@Test
public void testRemoveLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
assertTrue( "Must be able to remove node", aN.getNetwork().remove( bN ) );
assertFalse( "Cannot remove a second time", aN.getNetwork().remove( bN ) );
assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and C's network must be equal", aN.getNetwork(), cN.getNetwork() );
assertEquals( "A's network should be A and C", Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ) );
assertEquals( "B's network should be B", Sets.newHashSet( bN ), nodes( bN.getNetwork() ) );
assertEquals( "A's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be empty", Sets.newHashSet(), bE.allPeripherals().keySet() );
assertEquals( "C's peripheral set should be A, C", Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet() );
}
@Test
public void testRemoveSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
cN.getNetwork().connect( aN, cN );
cN.getNetwork().connect( bN, cN );
cN.getNetwork().remove( cN );
assertNotEquals( "A's and B's network must not be equal", aN.getNetwork(), bN.getNetwork() );
assertEquals( "A's and A_'s network must be equal", aN.getNetwork(), aaN.getNetwork() );
assertEquals( "B's and B_'s network must be equal", bN.getNetwork(), bbN.getNetwork() );
assertEquals( "A's network should be A and A_", Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ) );
assertEquals( "B's network should be B and B_", Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ) );
assertEquals( "C's network should be C", Sets.newHashSet( cN ), nodes( cN.getNetwork() ) );
assertEquals( "A's peripheral set should be A and A_", Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet() );
assertEquals( "B's peripheral set should be B and B_", Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet() );
assertEquals( "C's peripheral set should be empty", Sets.newHashSet(), cE.allPeripherals().keySet() );
}
@Test
@Ignore("Takes a long time to run, mostly for stress testing")
public void testLarge()
{
final int BRUTE_SIZE = 16;
final int TOGGLE_CONNECTION_TIMES = 5;
final int TOGGLE_NODE_TIMES = 5;
Grid<IWiredNode> grid = new Grid<>( BRUTE_SIZE );
grid.map( ( existing, pos ) -> new NetworkElement( null, null, "n_" + pos ).getNode() );
// Test connecting
{
long start = System.nanoTime();
grid.forEach( ( existing, pos ) -> {
for( EnumFacing facing : EnumFacing.VALUES )
{
BlockPos offset = pos.offset( facing );
if( (offset.getX() > BRUTE_SIZE / 2) == (pos.getX() > BRUTE_SIZE / 2) )
{
IWiredNode other = grid.get( offset );
if( other != null ) existing.getNetwork().connect( existing, other );
}
}
} );
long end = System.nanoTime();
System.out.printf( "Connecting %s³ nodes took %s seconds\n", BRUTE_SIZE, (end - start) * 1e-9 );
}
// Test toggling
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_CONNECTION_TIMES; i++ )
{
left.getNetwork().connect( left, right );
left.getNetwork().disconnect( left, right );
}
long end = System.nanoTime();
System.out.printf( "Toggling connection %s times took %s seconds\n", TOGGLE_CONNECTION_TIMES, (end - start) * 1e-9 );
}
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
IWiredNode centre = new NetworkElement( null, null, "c" ).getNode();
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_NODE_TIMES; i++ )
{
left.getNetwork().connect( left, centre );
right.getNetwork().connect( right, centre );
left.getNetwork().remove( centre );
}
long end = System.nanoTime();
System.out.printf( "Toggling node %s times took %s seconds\n", TOGGLE_NODE_TIMES, (end - start) * 1e-9 );
}
}
private static class NetworkElement implements IWiredElement
{
private final World world;
private final Vec3d position;
private final String id;
private final IWiredNode node;
private final Map<String, IPeripheral> localPeripherals = Maps.newHashMap();
private final Map<String, IPeripheral> remotePeripherals = Maps.newHashMap();
private NetworkElement( World world, Vec3d position, String id )
{
this.world = world;
this.position = position;
this.id = id;
this.node = ComputerCraftAPI.createWiredNodeForElement( this );
this.addPeripheral( id );
}
@Nonnull
@Override
public World getWorld()
{
return world;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return position;
}
@Nonnull
@Override
public String getSenderID()
{
return id;
}
@Override
public String toString()
{
return "NetworkElement{" + id + "}";
}
@Nonnull
@Override
public IWiredNode getNode()
{
return node;
}
@Override
public void networkChanged( @Nonnull IWiredNetworkChange change )
{
remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );
remotePeripherals.putAll( change.peripheralsAdded() );
}
@Nonnull
@Override
public Map<String, IPeripheral> getPeripherals()
{
return Collections.unmodifiableMap( localPeripherals );
}
public NetworkElement addPeripheral( String name )
{
localPeripherals.put( name, new NetworkPeripheral() );
getNode().invalidate();
return this;
}
@Nonnull
public Map<String, IPeripheral> allPeripherals()
{
return remotePeripherals;
}
}
private static class NetworkPeripheral implements IPeripheral
{
@Nonnull
@Override
public String getType()
{
return "test";
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[0];
}
@Nullable
@Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return new Object[0];
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
private static class Grid<T>
{
private final int size;
private final T[] box;
@SuppressWarnings("unchecked")
public Grid( int size )
{
this.size = size;
this.box = (T[]) new Object[size * size * size];
}
public void set( BlockPos pos, T elem )
{
int x = pos.getX(), y = pos.getY(), z = pos.getZ();
if( x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size )
{
box[x * size * size + y * size + z] = elem;
}
else
{
throw new IndexOutOfBoundsException( pos.toString() );
}
}
public T get( BlockPos pos )
{
int x = pos.getX(), y = pos.getY(), z = pos.getZ();
return x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size
? box[x * size * size + y * size + z]
: null;
}
public void forEach( BiConsumer<T, BlockPos> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
transform.accept( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
public void map( BiFunction<T, BlockPos, T> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
box[x * size * size + y * size + z] = transform.apply( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
}
private static Set<WiredNode> nodes( IWiredNetwork network )
{
return ((WiredNetwork) network).nodes;
}
private static Set<WiredNode> neighbours( IWiredNode node )
{
return ((WiredNode) node).neighbours;
}
}