mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-12 11:10:29 +00:00
Add the default implementation of wired networks
This commit is contained in:
parent
4651e362c9
commit
74f5093d2a
@ -59,6 +59,8 @@ dependencies {
|
||||
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
|
||||
runtime "mezz.jei:jei_1.12:4.7.5.86"
|
||||
shade 'org.squiddev:Cobalt:0.3.1'
|
||||
|
||||
testCompile 'junit:junit:4.11'
|
||||
}
|
||||
|
||||
javadoc {
|
||||
|
@ -14,6 +14,9 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.IMediaProvider;
|
||||
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.IPeripheralProvider;
|
||||
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
|
||||
@ -57,6 +60,7 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import dan200.computercraft.shared.wired.WiredNode;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@ -69,6 +73,7 @@ import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.config.ConfigCategory;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
@ -259,6 +264,7 @@ public class ComputerCraft
|
||||
private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>();
|
||||
private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>();
|
||||
private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>();
|
||||
private static final Set<IWiredProvider> wiredProviders = new LinkedHashSet<>();
|
||||
|
||||
// Implementation
|
||||
@Mod.Instance( value = ComputerCraft.MOD_ID )
|
||||
@ -730,6 +736,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 )
|
||||
{
|
||||
// Try the handlers in order:
|
||||
@ -751,6 +767,24 @@ public class ComputerCraft
|
||||
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 )
|
||||
{
|
||||
if( WorldUtil.isBlockInWorld( world, pos ) )
|
||||
|
@ -52,6 +52,7 @@ import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import dan200.computercraft.shared.wired.DefaultWiredProvider;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@ -291,7 +292,7 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
|
||||
// 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" ) ) );
|
||||
|
||||
// Items
|
||||
@ -482,6 +483,9 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
|
||||
|
||||
// Register media providers
|
||||
ComputerCraftAPI.registerMediaProvider( new DefaultMediaProvider() );
|
||||
|
||||
// Register network providers
|
||||
ComputerCraftAPI.registerWiredProvider( new DefaultWiredProvider() );
|
||||
}
|
||||
|
||||
private void registerForgeHandlers()
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
151
src/main/java/dan200/computercraft/shared/wired/WiredNode.java
Normal file
151
src/main/java/dan200/computercraft/shared/wired/WiredNode.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
506
src/test/java/dan200/computercraft/shared/wired/NetworkTest.java
Normal file
506
src/test/java/dan200/computercraft/shared/wired/NetworkTest.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user