1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-08 01:12:59 +00:00
Files
CC-Tweaked/remappedSrc/dan200/computercraft/shared/wired/WiredNetwork.java
2021-07-11 01:33:21 +00:00

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 );
}
}
}