mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-08 01:12:59 +00:00
568 lines
19 KiB
Java
568 lines
19 KiB
Java
/*
|
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
|
* Send enquiries to dratcliffe@gmail.com
|
|
*/
|
|
|
|
package dan200.computercraft.shared.wired;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
|
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<>();
|
|
|
|
WiredNetwork( WiredNode node )
|
|
{
|
|
nodes = new HashSet<>( 1 );
|
|
nodes.add( node );
|
|
}
|
|
|
|
private WiredNetwork( HashSet<WiredNode> nodes )
|
|
{
|
|
this.nodes = nodes;
|
|
}
|
|
|
|
static 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 )
|
|
{
|
|
TransmitPoint nextPoint = new TransmitPoint( neighbour, newDistance, newInterdimensional );
|
|
points.put( neighbour, nextPoint );
|
|
transmitTo.add( nextPoint );
|
|
}
|
|
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 );
|
|
}
|
|
}
|
|
|
|
@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<>( 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.isEmpty() )
|
|
{
|
|
WiredNetworkChange.removed( peripherals )
|
|
.broadcast( networkU.nodes );
|
|
}
|
|
if( !networkU.peripherals.isEmpty() )
|
|
{
|
|
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.isEmpty() )
|
|
{
|
|
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 updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> newPeripherals )
|
|
{
|
|
WiredNode wired = checkNode( node );
|
|
Objects.requireNonNull( peripherals, "peripherals cannot be null" );
|
|
|
|
lock.writeLock()
|
|
.lock();
|
|
try
|
|
{
|
|
if( wired.network != this )
|
|
{
|
|
throw new IllegalStateException( "Node is not on this network" );
|
|
}
|
|
|
|
Map<String, IPeripheral> oldPeripherals = wired.peripherals;
|
|
WiredNetworkChange change = WiredNetworkChange.changeOf( oldPeripherals, newPeripherals );
|
|
if( change.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
wired.peripherals = ImmutableMap.copyOf( 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();
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
private static WiredNode checkNode( IWiredNode node )
|
|
{
|
|
if( node instanceof WiredNode )
|
|
{
|
|
return (WiredNode) node;
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalArgumentException( "Unknown implementation of IWiredNode: " + node );
|
|
}
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|