Package logisticspipes.routing

Source Code of logisticspipes.routing.ServerRouter

/**
* Copyright (c) Krapht, 2011
*
* "LogisticsPipes" is distributed under the terms of the Minecraft Mod Public
* License 1.0, or MMPL. Please check the contents of the license located in
* http://www.mod-buildcraft.com/MMPL-1.0.txt
*/

package logisticspipes.routing;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import logisticspipes.Configs;
import logisticspipes.api.ILogisticsPowerProvider;
import logisticspipes.interfaces.IRoutingDebugAdapter;
import logisticspipes.interfaces.ISubSystemPowerProvider;
import logisticspipes.interfaces.routing.IFilter;
import logisticspipes.modules.abstractmodules.LogisticsModule;
import logisticspipes.pipes.PipeItemsFirewall;
import logisticspipes.pipes.basic.CoreRoutedPipe;
import logisticspipes.pipes.basic.LogisticsTileGenericPipe;
import logisticspipes.proxy.MainProxy;
import logisticspipes.proxy.SimpleServiceLocator;
import logisticspipes.routing.pathfinder.PathFinder;
import logisticspipes.ticks.RoutingTableUpdateThread;
import logisticspipes.utils.OneList;
import logisticspipes.utils.item.ItemIdentifier;
import logisticspipes.utils.tuples.LPPosition;
import logisticspipes.utils.tuples.Pair;
import logisticspipes.utils.tuples.Quartet;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.util.ForgeDirection;

public class ServerRouter implements IRouter, Comparable<ServerRouter> {
 
  // things with specific interests -- providers (including crafters)
  static HashMap<ItemIdentifier,Set<IRouter>> _globalSpecificInterests = new HashMap<ItemIdentifier,Set<IRouter>>();
  // things potentially interested in every item (chassi with generic sinks)
  static Set<IRouter> _genericInterests = new TreeSet<IRouter>();
 
  // things this pipe is interested in (either providing or sinking)
  Set<ItemIdentifier> _hasInterestIn = new TreeSet<ItemIdentifier>();
  boolean _hasGenericInterest;
 
  static final int REFRESH_TIME=20;
  static int iterated=0;// used pseudp-random to spread items over the tick range
  int ticksUntillNextInventoryCheck=0;
  @Override
  public int hashCode(){
    return simpleID; // guaranteed to be unique, and uniform distribution over a range.
  }
 
  protected class LSA {
    public HashMap<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>> neighboursWithMetric;
    public List<Pair<ILogisticsPowerProvider,List<IFilter>>> power;
    public ArrayList<Pair<ISubSystemPowerProvider, List<IFilter>>>  subSystemPower;
  }
 
  private class UpdateRouterRunnable implements Comparable<UpdateRouterRunnable>, Runnable {
   
    int newVersion = 0;
    boolean run = false;
    IRouter target;
    UpdateRouterRunnable(IRouter target) {
      run = true;
      newVersion = _LSAVersion;
      this.target=target;
    }
   
    @Override
    public void run() {
      if(!run) return;
      try {
        CoreRoutedPipe p = target.getCachedPipe();
        if(p==null){
          run = false;
          return;
        }
        //spinlock during the first tick, we can't touch the routing table, untill Update() has been called on every pipe.
        for(int i=0;i<10 && p.stillNeedReplace();i++){Thread.sleep(10);}
        if(p.stillNeedReplace())
          return; // drop the pipe update if it still needs replace after 5 ticks.
        CreateRouteTable(newVersion);
      } catch(Exception e) {
        e.printStackTrace();
      }
      run = false;
    }

    @Override
    public int compareTo(UpdateRouterRunnable o) {
      int c=0;
      if(o.newVersion<=0)
        c = newVersion-o.newVersion; // negative numbers have priority, more negative first
      if(c!=0) return 0;
        c = this.target.getSimpleID()-o.target.getSimpleID(); // do things in order of router id, to minimize router recursion
      if(c!=0) return 0;
        c = o.newVersion - newVersion; // higher version first
      return c;
    }   
  }

  // these are maps, not hashMaps because they are unmodifiable Collections to avoid concurrentModification exceptions.
  public Map<CoreRoutedPipe, ExitRoute> _adjacent = new HashMap<CoreRoutedPipe, ExitRoute>();
  public Map<IRouter, ExitRoute> _adjacentRouter = new HashMap<IRouter, ExitRoute>();
  public List<Pair<ILogisticsPowerProvider,List<IFilter>>> _powerAdjacent = new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>();
  public List<Pair<ISubSystemPowerProvider,List<IFilter>>> _subSystemPowerAdjacent = new ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>>();
 
  public boolean[] sideDisconnected = new boolean[6];
 
  protected static int[] _lastLSAVersion = new int[0];
  protected int _LSAVersion = 0;
  protected final LSA _myLsa ;

  protected UpdateRouterRunnable updateThread = null;

  protected static final ReentrantReadWriteLock SharedLSADatabaseLock = new ReentrantReadWriteLock();
  protected static final Lock SharedLSADatabasereadLock = SharedLSADatabaseLock.readLock();
  protected static final Lock SharedLSADatabasewriteLock = SharedLSADatabaseLock.writeLock();
  protected final ReentrantReadWriteLock routingTableUpdateLock = new ReentrantReadWriteLock();
  protected final Lock routingTableUpdateReadLock = routingTableUpdateLock.readLock();
  protected final Lock routingTableUpdateWriteLock = routingTableUpdateLock.writeLock();
  public Object _externalRoutersByCostLock = new Object();
 
  protected static LSA[] SharedLSADatabase = new LSA[0];

  /** Map of router -> orientation for all known destinations **/
  public List<List<ExitRoute>> _routeTable = Collections.unmodifiableList(new ArrayList<List<ExitRoute>>());
  public List<ExitRoute> _routeCosts = Collections.unmodifiableList(new ArrayList<ExitRoute>());
  public List<Pair<ILogisticsPowerProvider,List<IFilter>>> _LPPowerTable = Collections.unmodifiableList(new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>());
  public List<Pair<ISubSystemPowerProvider,List<IFilter>>> _SubSystemPowerTable = Collections.unmodifiableList(new ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>>());
 
  private EnumSet<ForgeDirection> _routedExits = EnumSet.noneOf(ForgeDirection.class);
  private EnumMap<ForgeDirection, Integer> _subPowerExits = new EnumMap<ForgeDirection, Integer>(ForgeDirection.class);

  private static int firstFreeId = 1;
  private static BitSet simpleIdUsedSet = new BitSet();

  protected final int simpleID;
  public final UUID id;
  private int _dimension;
  @Getter
  private final int _xCoord;
  @Getter
  private final int _yCoord;
  @Getter
  private final int _zCoord;
 
  @Getter
  @Setter(value=AccessLevel.PRIVATE)
  private boolean destroied = false;
 
  private WeakReference<CoreRoutedPipe> _myPipeCache=null;
  private LinkedList<Pair<Integer, IRouterQueuedTask>> queue = new LinkedList<Pair<Integer,IRouterQueuedTask>>();
  @Override
  public void clearPipeCache(){_myPipeCache=null;}
 
  // called on server shutdown only
  public static void cleanup() {
    _globalSpecificInterests.clear();
    _genericInterests.clear();
    SharedLSADatabasewriteLock.lock();
    SharedLSADatabase = new LSA[0];
    _lastLSAVersion = new int[0];
    SharedLSADatabasewriteLock.unlock();
    simpleIdUsedSet.clear();
    firstFreeId = 1;
  }
 
  private static int claimSimpleID() {
    int idx = simpleIdUsedSet.nextClearBit(firstFreeId);
    firstFreeId = idx + 1;
    simpleIdUsedSet.set(idx);
    return idx;
  }
 
  private static void releaseSimpleID(int idx) {
    simpleIdUsedSet.clear(idx);
    if(idx < firstFreeId)
      firstFreeId = idx;
  }
 
  public static int getBiggestSimpleID() {
    return simpleIdUsedSet.size();
  }
 
  public ServerRouter(UUID globalID, int dimension, int xCoord, int yCoord, int zCoord){
    if(globalID!=null)
      this.id = globalID;
    else
      this.id = UUID.randomUUID();
    this._dimension = dimension;
    this._xCoord = xCoord;
    this._yCoord = yCoord;
    this._zCoord = zCoord;
    clearPipeCache();
    _myLsa = new LSA();
    _myLsa.neighboursWithMetric = new HashMap<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>>();
    _myLsa.power = new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>();
    SharedLSADatabasewriteLock.lock(); // any time after we claim the SimpleID, the database could be accessed at that index
    simpleID = claimSimpleID();
    if(SharedLSADatabase.length<=simpleID){
      int newlength = ((int) (simpleID*1.5))+1;
      LSA[] new_SharedLSADatabase = new LSA[newlength];
      System.arraycopy(SharedLSADatabase, 0, new_SharedLSADatabase, 0, SharedLSADatabase.length);
      SharedLSADatabase = new_SharedLSADatabase;
      int[] new_lastLSAVersion = new int[newlength];
      System.arraycopy(_lastLSAVersion, 0, new_lastLSAVersion, 0, _lastLSAVersion.length);
      _lastLSAVersion = new_lastLSAVersion;
    }
    _lastLSAVersion[simpleID] = 0;
    SharedLSADatabase[simpleID] = _myLsa; // make non-structural change (threadsafe)
    SharedLSADatabasewriteLock.unlock();
  }
 
  @Override
  public int getSimpleID() {
    return this.simpleID;
  }

  @Override
  public boolean isInDim(int dimension) {
    return _dimension == dimension;
  }

  @Override
  public boolean isAt(int dimension, int xCoord, int yCoord, int zCoord){
    return _dimension == dimension && _xCoord == xCoord && _yCoord == yCoord && _zCoord == zCoord;
  }
 
  @Override
  public LPPosition getLPPosition() {
    return new LPPosition(_xCoord, _yCoord, _zCoord);
  }

  @Override
  public CoreRoutedPipe getPipe(){
    CoreRoutedPipe crp = getCachedPipe();
    if(crp != null)
      return crp;
    World worldObj = DimensionManager.getWorld(_dimension);
    if(worldObj == null) {
      return null;
    }
    TileEntity tile = worldObj.getTileEntity(_xCoord, _yCoord, _zCoord);
   
    if (!(tile instanceof LogisticsTileGenericPipe)) return null;
    LogisticsTileGenericPipe pipe = (LogisticsTileGenericPipe) tile;
    if (!(pipe.pipe instanceof CoreRoutedPipe)) return null;
    _myPipeCache=new WeakReference<CoreRoutedPipe>((CoreRoutedPipe) pipe.pipe);

    return (CoreRoutedPipe) pipe.pipe;
  }

  @Override
  public CoreRoutedPipe getCachedPipe(){
    if(_myPipeCache!=null)
      return _myPipeCache.get();
    return null;
  }

  @Override
  public boolean isValidCache() {
    return getPipe() != null;
  }

  private void ensureRouteTableIsUpToDate(boolean force){
    if (_LSAVersion > _lastLSAVersion[simpleID]) {
      if(Configs.MULTI_THREAD_NUMBER > 0 && !force) {
        RoutingTableUpdateThread.add(new UpdateRouterRunnable(this));
      } else {
        CreateRouteTable(_LSAVersion);
      }
    }
  }

  @Override
  public List<List<ExitRoute>> getRouteTable(){
    ensureRouteTableIsUpToDate(true);
    return _routeTable;
  }
 
  @Override
  public List<ExitRoute> getIRoutersByCost() {
    ensureRouteTableIsUpToDate(true);
    return _routeCosts;
  }
 
  @Override
  public UUID getId() {
    return this.id;
  }
 

  /**
   * Rechecks the piped connection to all adjacent routers as well as discover new ones.
   */
  private boolean recheckAdjacent() {
    boolean adjacentChanged = false;
    CoreRoutedPipe thisPipe = getPipe();
    if (thisPipe == null) return false;
    HashMap<CoreRoutedPipe, ExitRoute> adjacent;
    List<Pair<ILogisticsPowerProvider,List<IFilter>>> power;
    List<Pair<ISubSystemPowerProvider,List<IFilter>>> subSystemPower;
    PathFinder finder = new PathFinder(thisPipe.container, Configs.LOGISTICS_DETECTION_COUNT, Configs.LOGISTICS_DETECTION_LENGTH);
    power = finder.powerNodes;
    subSystemPower = finder.subPowerProvider;
    adjacent = finder.result;
   
    for(CoreRoutedPipe pipe : adjacent.keySet()) {
      if(pipe.stillNeedReplace()) {
        return false;
      }
    }
   
    boolean[] oldSideDisconnected = sideDisconnected;
    sideDisconnected = new boolean[6];
    checkSecurity(adjacent);
   
    boolean changed = false;
   
    for(int i=0;i<6;i++) {
      changed |= sideDisconnected[i] != oldSideDisconnected[i];
    }
    if(changed) {
      CoreRoutedPipe pipe = getPipe();
      if (pipe != null) {
        pipe.getWorld().notifyBlocksOfNeighborChange(pipe.getX(), pipe.getY(), pipe.getZ(), pipe.getWorld().getBlock(pipe.getX(), pipe.getY(), pipe.getZ()));
        pipe.refreshConnectionAndRender(false);
      }
      adjacentChanged = true;
    }
   
    if(_adjacent.size() != adjacent.size()) {
      adjacentChanged = true;
    }
   
    for (CoreRoutedPipe pipe : _adjacent.keySet()) {
      if(!adjacent.containsKey(pipe)) {
        adjacentChanged = true;
      }
    }
    if(_powerAdjacent!=null) {
      if(power==null){
        adjacentChanged = true;
      } else {
        for (Pair<ILogisticsPowerProvider,List<IFilter>> provider : _powerAdjacent){
          if(!power.contains(provider))
            adjacentChanged = true;
        }
      }
    }
    if(power!=null){
      if(_powerAdjacent==null) {
        adjacentChanged = true;
      } else {
        for (Pair<ILogisticsPowerProvider,List<IFilter>> provider : power){
          if(!_powerAdjacent.contains(provider))
            adjacentChanged = true;
        }
      }
    }
    if(_subSystemPowerAdjacent!=null) {
      if(subSystemPower==null){
        adjacentChanged = true;
      } else {
        for (Pair<ISubSystemPowerProvider, List<IFilter>> provider : _subSystemPowerAdjacent){
          if(!subSystemPower.contains(provider))
            adjacentChanged = true;
        }
      }
    }
    if(subSystemPower!=null){
      if(_subSystemPowerAdjacent==null) {
        adjacentChanged = true;
      } else {
        for (Pair<ISubSystemPowerProvider, List<IFilter>> provider : subSystemPower){
          if(!_subSystemPowerAdjacent.contains(provider))
            adjacentChanged = true;
        }
      }
    }
    for (Entry<CoreRoutedPipe, ExitRoute> pipe : adjacent.entrySet())  {
      ExitRoute oldExit = _adjacent.get(pipe.getKey());
      if (oldExit==null){
        adjacentChanged = true;
        break;
      }
      ExitRoute newExit = pipe.getValue();
     
      if (!newExit.equals(oldExit))  {
        adjacentChanged = true;
        break;
      }
    }   
    if (adjacentChanged) {
      HashMap<IRouter, ExitRoute> adjacentRouter = new HashMap<IRouter, ExitRoute>();
      EnumSet<ForgeDirection> routedexits = EnumSet.noneOf(ForgeDirection.class);
      EnumMap<ForgeDirection, Integer> subpowerexits = new EnumMap<ForgeDirection, Integer>(ForgeDirection.class);
      for(Entry<CoreRoutedPipe,ExitRoute> pipe:adjacent.entrySet()) {
        adjacentRouter.put(pipe.getKey().getRouter(), pipe.getValue());
        if((pipe.getValue().connectionDetails.contains(PipeRoutingConnectionType.canRouteTo) || pipe.getValue().connectionDetails.contains(PipeRoutingConnectionType.canRequestFrom) && !routedexits.contains(pipe.getValue().exitOrientation))) {
          routedexits.add(pipe.getValue().exitOrientation);
        }
        if(!subpowerexits.containsKey(pipe.getValue().exitOrientation) && pipe.getValue().connectionDetails.contains(PipeRoutingConnectionType.canPowerSubSystemFrom)) {
          subpowerexits.put(pipe.getValue().exitOrientation, PathFinder.messureDistanceToNextRoutedPipe(this.getLPPosition(), pipe.getValue().exitOrientation, pipe.getKey().getWorld()));
        }
      }
      _adjacent = Collections.unmodifiableMap(adjacent);
      _adjacentRouter = Collections.unmodifiableMap(adjacentRouter);
      if(power != null){
        _powerAdjacent = Collections.unmodifiableList(power);
      } else {
        _powerAdjacent = null;
      }
      if(subSystemPower != null){
        _subSystemPowerAdjacent = Collections.unmodifiableList(subSystemPower);
      } else {
        _subSystemPowerAdjacent = null;
      }
      _routedExits = routedexits;
      _subPowerExits = subpowerexits;
      SendNewLSA();
    }
    return adjacentChanged;
  }
 
  private void checkSecurity(HashMap<CoreRoutedPipe, ExitRoute> adjacent) {
    CoreRoutedPipe pipe = getPipe();
    if(pipe == null) return;
    UUID id = pipe.getSecurityID();
    List<CoreRoutedPipe> toRemove = new ArrayList<CoreRoutedPipe>();
    if(id != null) {
      for(Entry<CoreRoutedPipe, ExitRoute> entry:adjacent.entrySet()) {
        if(!entry.getValue().connectionDetails.contains(PipeRoutingConnectionType.canRouteTo) && !entry.getValue().connectionDetails.contains(PipeRoutingConnectionType.canRequestFrom)) continue;
        UUID thatId = entry.getKey().getSecurityID();
        if(!(pipe instanceof PipeItemsFirewall)) {
          if(thatId == null) {
            entry.getKey().insetSecurityID(id);
          } else if(!id.equals(thatId)) {
            sideDisconnected[entry.getValue().exitOrientation.ordinal()] = true;
          }
        } else {
          if(!(entry.getKey() instanceof PipeItemsFirewall)) {
            if(thatId != null && !id.equals(thatId)) {
              sideDisconnected[entry.getValue().exitOrientation.ordinal()] = true;
            }
          }
        }
      }
      for(Entry<CoreRoutedPipe, ExitRoute> entry:adjacent.entrySet()) {
        if(sideDisconnected[entry.getValue().exitOrientation.ordinal()]) {
          toRemove.add(entry.getKey());
        }
      }
      for(CoreRoutedPipe remove:toRemove) {
        adjacent.remove(remove);
      }
    }
  }

  private void SendNewLSA() {
    HashMap<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>> neighboursWithMetric = new HashMap<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>>();
    for (Entry<IRouter, ExitRoute> adjacent : _adjacentRouter.entrySet()){
      neighboursWithMetric.put(adjacent.getKey(), new Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>(adjacent.getValue().distanceToDestination, adjacent.getValue().connectionDetails, adjacent.getValue().filters, adjacent.getValue().blockDistance));
    }
    ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>> power = null;
    if(_powerAdjacent != null){
      power = new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>();
      for (Pair<ILogisticsPowerProvider,List<IFilter>> provider : _powerAdjacent){
        power.add(provider);
      }
    }
    ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>> subSystemPower = null;
    if(_subSystemPowerAdjacent != null){
      subSystemPower = new ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>>();
      for (Pair<ISubSystemPowerProvider, List<IFilter>> provider : _subSystemPowerAdjacent){
        subSystemPower.add(provider);
      }
    }
    SharedLSADatabasewriteLock.lock();
    _myLsa.neighboursWithMetric = neighboursWithMetric;
    _myLsa.power = power;
    _myLsa.subSystemPower = subSystemPower;
    SharedLSADatabasewriteLock.unlock();
  }
 
  public void CreateRouteTable(int version_to_update_to) {
    CreateRouteTable(version_to_update_to, new DummyRoutingDebugAdapter());
  }
 
  /**
   * Create a route table from the link state database
   */
  public void CreateRouteTable(int version_to_update_to, IRoutingDebugAdapter debug) {
   
    if(_lastLSAVersion[simpleID] >= version_to_update_to && !debug.independent())
      return; // this update is already done.

    //Dijkstra!
   
    debug.init();
   
    int routingTableSize = ServerRouter.getBiggestSimpleID();
    if(routingTableSize == 0) {
      routingTableSize = SharedLSADatabase.length; // deliberatly ignoring concurrent access, either the old or the version of the size will work, this is just an approximate number.
    }

    /** same info as above, but sorted by distance -- sorting is implicit, because Dijkstra finds the closest routes first.**/
    List<ExitRoute> routeCosts = new ArrayList<ExitRoute>(routingTableSize);
   
    //Add the current Router
    routeCosts.add(new ExitRoute(this, this, ForgeDirection.UNKNOWN, ForgeDirection.UNKNOWN,0,EnumSet.allOf(PipeRoutingConnectionType.class), 0));
   
    ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>> powerTable;
    if(_powerAdjacent != null)
      powerTable = new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>(_powerAdjacent);
    else
      powerTable = new ArrayList<Pair<ILogisticsPowerProvider,List<IFilter>>>(5);
    ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>> subSystemPower;
    if(_subSystemPowerAdjacent != null)
      subSystemPower = new ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>>(_subSystemPowerAdjacent);
    else
      subSystemPower = new ArrayList<Pair<ISubSystemPowerProvider,List<IFilter>>>(5);
   
    //space and time inefficient, a bitset with 3 bits per node would save a lot but makes the main iteration look like a complete mess
    ArrayList<EnumSet<PipeRoutingConnectionType>> closedSet = new ArrayList<EnumSet<PipeRoutingConnectionType>>(getBiggestSimpleID());
    for(int i=0;i<getBiggestSimpleID();i++)
      closedSet.add(null);
   
    ArrayList<EnumMap<PipeRoutingConnectionType, List<List<IFilter>>>> filterList = new ArrayList<EnumMap<PipeRoutingConnectionType, List<List<IFilter>>>>(getBiggestSimpleID());
    for(int i=0;i<getBiggestSimpleID();i++)
      filterList.add(null);

    /** The total cost for the candidate route **/
    PriorityQueue<ExitRoute> candidatesCost = new PriorityQueue<ExitRoute>((int) Math.sqrt(routingTableSize)); // sqrt nodes is a good guess for the total number of candidate nodes at once.
   
    //Init candidates
    // the shortest way to go to an adjacent item is the adjacent item.
    for (Entry<IRouter, ExitRoute> pipe :  _adjacentRouter.entrySet()){
      ExitRoute currentE = pipe.getValue();
      IRouter newRouter = pipe.getKey();
      if(newRouter != null) {
        ExitRoute newER = new ExitRoute(newRouter, newRouter, currentE.distanceToDestination, currentE.connectionDetails, currentE.filters, new ArrayList<IFilter>(0), currentE.blockDistance);
        candidatesCost.add(newER);
        debug.newCanidate(newER);
      }
    }

    debug.start(candidatesCost, closedSet, filterList);
   
    SharedLSADatabasereadLock.lock(); // readlock, not inside the while - too costly to aquire, then release.
    ExitRoute lowestCostNode;
    while ((lowestCostNode = candidatesCost.poll()) != null){
      if(!lowestCostNode.hasActivePipe())
        continue;
     
      if(debug.isDebug()) SharedLSADatabasereadLock.unlock();
      debug.nextPipe(lowestCostNode);
      if(debug.isDebug()) SharedLSADatabasereadLock.lock();
     
     
      for(ExitRoute e:candidatesCost) {
        e.debug.isNewlyAddedCanidate = false;
      }
     
      //if the node does not have any flags not in the closed set, check it
      EnumSet<PipeRoutingConnectionType> lowestCostClosedFlags = closedSet.get(lowestCostNode.destination.getSimpleID());
      if(lowestCostClosedFlags == null)
        lowestCostClosedFlags = EnumSet.noneOf(PipeRoutingConnectionType.class);
      if(lowestCostClosedFlags.containsAll(lowestCostNode.getFlags())) continue;
     
      EnumSet<PipeRoutingConnectionType> newFlags = lowestCostNode.getFlags();
      newFlags.removeAll(lowestCostClosedFlags);
     
      debug.newFlagsForPipe(newFlags);
     
      EnumMap<PipeRoutingConnectionType, List<List<IFilter>>> filters = filterList.get(lowestCostNode.destination.getSimpleID());
     
      debug.filterList(filters);
     
      if(filters != null) {
        boolean containsNewInfo = false;
        for(PipeRoutingConnectionType type: newFlags) {
          if(!filters.containsKey(type)) {
            containsNewInfo = true;
            break;
          }
          boolean matches = false;
          List<List<IFilter>> list = filters.get(type);
          for(List<IFilter> filter:list) {
            if(lowestCostNode.filters.containsAll(filter)) {
              matches = true;
              break;
            }
          }
          if(!matches) {
            containsNewInfo = true;
            break;
         
        }
        if(!containsNewInfo) {
          continue;
        }
      }

      //Add new candidates from the newly approved route
      LSA lsa = null;
      if(lowestCostNode.destination.getSimpleID() < SharedLSADatabase.length) {
        lsa = SharedLSADatabase[lowestCostNode.destination.getSimpleID()];
      }
      if(lsa == null) {
        lowestCostNode.removeFlags(lowestCostClosedFlags);
        lowestCostClosedFlags.addAll(lowestCostNode.getFlags());
        if(lowestCostNode.containsFlag(PipeRoutingConnectionType.canRouteTo) || lowestCostNode.containsFlag(PipeRoutingConnectionType.canRequestFrom))
          routeCosts.add(lowestCostNode);
        closedSet.set(lowestCostNode.destination.getSimpleID(),lowestCostClosedFlags);
        continue;
      }
      if(lowestCostNode.containsFlag(PipeRoutingConnectionType.canPowerFrom)) {
        if(lsa.power!=null && (lsa.power.isEmpty() == false)) {
          for(Pair<ILogisticsPowerProvider, List<IFilter>> p: lsa.power) {
            Pair<ILogisticsPowerProvider, List<IFilter>> entry = p.copy();
            List<IFilter> list = new ArrayList<IFilter>();
            list.addAll(p.getValue2());
            list.addAll(lowestCostNode.filters);
            entry.setValue2(Collections.unmodifiableList(list));
            if(!powerTable.contains(entry)) {
              powerTable.add(entry);
            }
          }
        }
      }
      if(lowestCostNode.containsFlag(PipeRoutingConnectionType.canPowerSubSystemFrom)) {
        if(lsa.subSystemPower!=null && (lsa.subSystemPower.isEmpty() == false)) {
          for(Pair<ISubSystemPowerProvider, List<IFilter>> p: lsa.subSystemPower) {
            Pair<ISubSystemPowerProvider, List<IFilter>> entry = p.copy();
            List<IFilter> list = new ArrayList<IFilter>();
            list.addAll(p.getValue2());
            list.addAll(lowestCostNode.filters);
            entry.setValue2(Collections.unmodifiableList(list));
            if(!subSystemPower.contains(entry)) {
              subSystemPower.add(entry);
            }
          }
        }
      }
        Iterator<Entry<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>>> it = lsa.neighboursWithMetric.entrySet().iterator();
        while (it.hasNext()) {
          Entry<IRouter, Quartet<Integer, EnumSet<PipeRoutingConnectionType>, List<IFilter>, Integer>> newCandidate = it.next();
        int candidateCost = lowestCostNode.distanceToDestination + newCandidate.getValue().getValue1();
        int blockDistance = lowestCostNode.blockDistance + newCandidate.getValue().getValue4();
        EnumSet<PipeRoutingConnectionType> newCT = lowestCostNode.getFlags();
        newCT.retainAll(newCandidate.getValue().getValue2());
        if(!newCT.isEmpty()) {
          ExitRoute next = new ExitRoute(lowestCostNode.root, newCandidate.getKey(), candidateCost, newCT, lowestCostNode.filters, newCandidate.getValue().getValue3(), blockDistance);
          next.debug.isTraced = lowestCostNode.debug.isTraced;
          candidatesCost.add(next);
          debug.newCanidate(next);
        }
      }

        lowestCostClosedFlags = lowestCostClosedFlags.clone();
       
      lowestCostNode.removeFlags(lowestCostClosedFlags);
      lowestCostClosedFlags.addAll(lowestCostNode.getFlags());
      if(lowestCostNode.containsFlag(PipeRoutingConnectionType.canRouteTo) || lowestCostNode.containsFlag(PipeRoutingConnectionType.canRequestFrom) || lowestCostNode.containsFlag(PipeRoutingConnectionType.canPowerSubSystemFrom))
        routeCosts.add(lowestCostNode);
      EnumMap<PipeRoutingConnectionType, List<List<IFilter>>> map = filterList.get(lowestCostNode.destination.getSimpleID());
      if(map == null) {
        map = new EnumMap<PipeRoutingConnectionType, List<List<IFilter>>>(PipeRoutingConnectionType.class);
        filterList.set(lowestCostNode.destination.getSimpleID(), map);
      }
      for(PipeRoutingConnectionType type :lowestCostNode.getFlags()) {
        if(!map.containsKey(type)) {
          map.put(type, new ArrayList<List<IFilter>>());
        }
        map.get(type).add(Collections.unmodifiableList(new ArrayList<IFilter>(lowestCostNode.filters)));
      }
      if(lowestCostNode.filters.isEmpty()) {
        closedSet.set(lowestCostNode.destination.getSimpleID(),lowestCostClosedFlags);
      }
     
      if(debug.isDebug()) SharedLSADatabasereadLock.unlock();
      debug.handledPipe();
      if(debug.isDebug()) SharedLSADatabasereadLock.lock();
    }
    SharedLSADatabasereadLock.unlock();
   
    debug.stepOneDone();
   
    //Build route table
    ArrayList<List<ExitRoute>> routeTable = new ArrayList<List<ExitRoute>>(ServerRouter.getBiggestSimpleID()+1);
    while (simpleID >= routeTable.size())
      routeTable.add(null);
    routeTable.set(simpleID, new OneList<ExitRoute>(new ExitRoute(this, this, ForgeDirection.UNKNOWN, ForgeDirection.UNKNOWN,0,EnumSet.allOf(PipeRoutingConnectionType.class), 0)));

    Iterator<ExitRoute> itr = routeCosts.iterator();
    while (itr.hasNext()) {
      ExitRoute node = itr.next();
      IRouter firstHop = node.root;
      ExitRoute hop = _adjacentRouter.get(firstHop);
      if (hop == null) {
        continue;
      }
      node.root = this; // replace the root with this, rather than the first hop.
      node.exitOrientation = hop.exitOrientation;
      while (node.destination.getSimpleID() >= routeTable.size()) { // the array will not expand, as it is init'd to contain enough elements
        routeTable.add(null);
      }
     
      List<ExitRoute> current = routeTable.get(node.destination.getSimpleID());
      if(current != null && !current.isEmpty()) {
        List<ExitRoute> list = new ArrayList<ExitRoute>();
        list.addAll(current);
        list.add(node);
        routeTable.set(node.destination.getSimpleID(), Collections.unmodifiableList(list));
      } else {
        routeTable.set(node.destination.getSimpleID(), new OneList<ExitRoute>(node));
      }
    }
    debug.stepTwoDone();
    if(!debug.independent()) {
      routingTableUpdateWriteLock.lock();
      if(version_to_update_to == this._LSAVersion){
        SharedLSADatabasereadLock.lock();
 
        if(_lastLSAVersion[simpleID] < version_to_update_to){
          _lastLSAVersion[simpleID] = version_to_update_to;
          _LPPowerTable = Collections.unmodifiableList(powerTable);
          _SubSystemPowerTable = Collections.unmodifiableList(subSystemPower);
          _routeTable = Collections.unmodifiableList(routeTable);
          _routeCosts = Collections.unmodifiableList(routeCosts);
        }
        SharedLSADatabasereadLock.unlock();
      }
      routingTableUpdateWriteLock.unlock();
    }
    debug.done();
  }
 
  @Override
  public void act(BitSet hasBeenProcessed,IRAction actor){
    if(hasBeenProcessed.get(this.simpleID))
      return;
    hasBeenProcessed.set(this.simpleID);
    if(!actor.isInteresting(this))
      return;
   
    actor.doTo(this);
    for(IRouter r : _adjacentRouter.keySet()) {
      r.act(hasBeenProcessed, actor);
    }
    return;
  }
 
  /**
   * Flags the last sent LSA as expired. Each router will be responsible of purging it from its database.
   */
  @Override
  public void destroy() {
    SharedLSADatabasewriteLock.lock(); // take a write lock so that we don't overlap with any ongoing route updates
    if (simpleID < SharedLSADatabase.length) {
      SharedLSADatabase[simpleID] = null;
    }
    SharedLSADatabasewriteLock.unlock();
    this.removeAllInterests();
   
    clearPipeCache();
    setDestroied(true);
    SimpleServiceLocator.routerManager.removeRouter(this.simpleID);
    updateAdjacentAndLsa();
    releaseSimpleID(simpleID);
  }


  private void removeAllInterests() {
    this.removeGenericInterest();
    for(ItemIdentifier i : _hasInterestIn) {
      this.removeInterest(i);
    }
    _hasInterestIn.clear();
  }

  /**
   * Floodfill recheckAdjacent, leave _prevAdjacentRouter around for LSA updating
   */
  class floodCheckAdjacent implements IRAction{
    @Override
    public boolean isInteresting(IRouter that) {
      return that.checkAdjacentUpdate();
    }
   
    @Override
    public void doTo(IRouter that) {
     
    }
  }

  @Override
  public boolean checkAdjacentUpdate() {
    boolean blockNeedsUpdate = recheckAdjacent();
    if(!blockNeedsUpdate) return false;

    CoreRoutedPipe pipe = getPipe();
    if (pipe == null) return true;
    pipe.refreshRender(true);
    return true;
  }

  /**
   * Floodfill LSA increment and clean up the _prevAdjacentRouter list left by floodCheckAdjacent
   */
  class flagForLSAUpdate implements IRAction{
    @Override
    public boolean isInteresting(IRouter that) {
      return true;
    }
    @Override
    public void doTo(IRouter that) {
      that.flagForRoutingUpdate();
    }
  }

  @Override
  public void flagForRoutingUpdate() {
    _LSAVersion++;
    //if(LogisticsPipes.DEBUG)
      //System.out.println("[LogisticsPipes] flag for routing update to "+_LSAVersion+" for Node" +  simpleID);
  }

  private void updateAdjacentAndLsa() {
    //this already got a checkAdjacentUpdate, so start the recursion with neighbors
    BitSet visited = new BitSet(ServerRouter.getBiggestSimpleID());
    IRAction flood = new floodCheckAdjacent();
    visited.set(simpleID);
    // for all connected updatecurrent and previous
    for(IRouter r : _adjacentRouter.keySet()) {
      r.act(visited, flood);
    }
    //now increment LSA version in the network
    visited.clear();
    this.act(visited, new flagForLSAUpdate());
  }
 
  @Override
  public void update(boolean doFullRefresh, CoreRoutedPipe pipe){ 
    handleQueuedTasks(pipe);
    updateInterests();
    if (doFullRefresh) {
      boolean blockNeedsUpdate = checkAdjacentUpdate();
      if (blockNeedsUpdate) {
        updateAdjacentAndLsa(); // also calls checkAdjacentUpdate() by default;
      }
      ensureRouteTableIsUpToDate(false);
      if (pipe != null) {
        pipe.refreshRender(false);
      }
      return;
    }
    if (Configs.MULTI_THREAD_NUMBER > 0) {
      ensureRouteTableIsUpToDate(false);
    }
  }
 
  private void handleQueuedTasks(CoreRoutedPipe pipe) {
    while(!queue.isEmpty()) {
      Pair<Integer, IRouterQueuedTask> element = queue.poll();
      if(element.getValue1() > MainProxy.getGlobalTick()) {
        element.getValue2().call(pipe, this);
      }
    }
  }

  /************* IROUTER *******************/
 
  @Override
  public boolean isRoutedExit(ForgeDirection o){
    return _routedExits.contains(o);
  }
 
  @Override
  public boolean isSubPoweredExit(ForgeDirection o){
    return _subPowerExits.containsKey(o);
  }

  @Override
  public int getDistanceToNextPowerPipe(ForgeDirection dir) {
    return _subPowerExits.get(dir);
  }

  @Override
  public ExitRoute getExitFor(int id, boolean active, ItemIdentifier type) {
    ensureRouteTableIsUpToDate(true);
    if(this.getRouteTable().size() <= id || this.getRouteTable().get(id) == null) return null;
outer:
    for(ExitRoute exit: this.getRouteTable().get(id)) {
      if(exit.containsFlag(PipeRoutingConnectionType.canRouteTo)) {
        for(IFilter filter:exit.filters) {
          if(!active) {
            if(filter.blockRouting() || filter.isBlocked() == filter.isFilteredItem(type)) continue outer;
          } else  {
            if((filter.blockProvider() && filter.blockCrafting()) || filter.isBlocked() == filter.isFilteredItem(type)) continue outer;
          }
        }
        return exit;
      }
    }
    return null;
  }
 
  @Override
  public boolean hasRoute(int id, boolean active, ItemIdentifier type) {
    if (!SimpleServiceLocator.routerManager.isRouterUnsafe(id,false)) return false;
    ensureRouteTableIsUpToDate(true);
    if(getRouteTable().size() <= id)
      return false;
    List<ExitRoute> source = this.getRouteTable().get(id);
    if(source == null) return false;
outer:
    for(ExitRoute exit: source) {
      if(exit.containsFlag(PipeRoutingConnectionType.canRouteTo)) {
        for(IFilter filter:exit.filters) {
          if(!active) {
            if(filter.blockRouting() || filter.isBlocked() == filter.isFilteredItem(type)) continue outer;
          } else  {
            if((filter.blockProvider() && filter.blockCrafting()) || filter.isBlocked() == filter.isFilteredItem(type)) continue outer;
          }
        }
        return true;
      }
    }
    return false;
  }
 
  @Override
  public LogisticsModule getLogisticsModule() {
    CoreRoutedPipe pipe = this.getPipe();
    if (pipe == null) return null;
    return pipe.getLogisticsModule();
  }

  @Override
  public List<Pair<ILogisticsPowerProvider,List<IFilter>>> getPowerProvider() {
    return _LPPowerTable;
  }

  @Override
  public List<Pair<ISubSystemPowerProvider,List<IFilter>>> getSubSystemPowerProvider() {
    return _SubSystemPowerTable;
  }

  @Override
  public boolean isSideDisconneceted(ForgeDirection dir) {
    return ForgeDirection.UNKNOWN != dir && sideDisconnected[dir.ordinal()];
  }

  @Override
  public void updateInterests() {
    if(--ticksUntillNextInventoryCheck>0)
      return;
    ticksUntillNextInventoryCheck=REFRESH_TIME;
    if(iterated++%this.simpleID==0)
      ticksUntillNextInventoryCheck++; // randomly wait 1 extra tick - just so that every router doesn't tick at the same time
    if(iterated >= ServerRouter.getBiggestSimpleID())
      iterated = 0;
    CoreRoutedPipe pipe = getPipe();
    if(pipe==null)
      return;
    if(pipe.hasGenericInterests())
      this.declareGenericInterest();
    else
      this.removeGenericInterest();
    Set<ItemIdentifier> newInterests = pipe.getSpecificInterests();
    if(newInterests == null) {
      newInterests = new TreeSet<ItemIdentifier>();
    }
    if(!newInterests.equals(_hasInterestIn)) {
      for(ItemIdentifier i : _hasInterestIn) {
        if(!newInterests.contains(i)) {
          this.removeInterest(i);
        }
      }
      for(ItemIdentifier i : newInterests) {
        if(!_hasInterestIn.contains(i)) {
          this.addInterest(i);
        }
      }
      _hasInterestIn=newInterests;
    }
  }

  private void removeGenericInterest() {
    this._hasGenericInterest=false;
    _genericInterests.remove(this);
  }

  private void declareGenericInterest() {
    this._hasGenericInterest=true;
    _genericInterests.add(this);
  }

  private void addInterest(ItemIdentifier items) {
    Set<IRouter> interests = _globalSpecificInterests.get(items);
    if(interests==null) {
      interests = new TreeSet<IRouter>();
      _globalSpecificInterests.put(items, interests);
    }
    interests.add(this);   
  }

  private void removeInterest(ItemIdentifier p2) {
    Set<IRouter> interests = _globalSpecificInterests.get(p2);
    if(interests==null) {
      return;
    }
    interests.remove(this);
    if(interests.isEmpty())
      _globalSpecificInterests.remove(p2);
   
  }

  public boolean hasGenericInterest() {
    return this._hasGenericInterest;
  }
 
  public boolean hasInterestIn(ItemIdentifier item) {
    return this._hasInterestIn.contains(item);
  }
 
  public static BitSet getRoutersInterestedIn(ItemIdentifier item) {
    BitSet s = new BitSet(getBiggestSimpleID()+1);
    if(_genericInterests != null){
      for(IRouter r:_genericInterests){
        s.set(r.getSimpleID());
      }
    }
    if(item == null) return s;
    Set<IRouter> specifics = _globalSpecificInterests.get(item);
    if(specifics != null){
      for(IRouter r:specifics){
        s.set(r.getSimpleID());
      }
    }
    specifics = _globalSpecificInterests.get(item.getUndamaged());
    if(specifics != null){
      for(IRouter r:specifics){
        s.set(r.getSimpleID());
      }
    }
    specifics = _globalSpecificInterests.get(item.getIgnoringNBT());
    if(specifics != null){
      for(IRouter r:specifics){
        s.set(r.getSimpleID());
      }
    }
    specifics = _globalSpecificInterests.get(item.getUndamaged().getIgnoringNBT());
    if(specifics != null){
      for(IRouter r:specifics){
        s.set(r.getSimpleID());
      }
    }
    return s;
  }

  @Override
  public int compareTo(ServerRouter o) {
    return this.simpleID-o.simpleID;
  }

  @Override
  public List<ExitRoute> getDistanceTo(IRouter r) {
    ensureRouteTableIsUpToDate(true);
    int id = r.getSimpleID();
    if (_routeTable.size()<=id) return new ArrayList<ExitRoute>(0);
    List<ExitRoute> result = _routeTable.get(id);
    return result != null ? result : new ArrayList<ExitRoute>(0);
  }

  public static Map<ItemIdentifier,Set<IRouter>> getInterestedInSpecifics() {
    return _globalSpecificInterests;   
  }

  public static Set<IRouter> getInterestedInGeneral() {
    return _genericInterests;
  }

  @Override
  public void clearInterests() {
    this.removeAllInterests();   
  }

  @Override
  public String toString() {
    StringBuilder string = new StringBuilder("ServerRouter: {ID: ");
    string.append(simpleID);
    string.append(", UUID: ");
    string.append(this.getId());
    string.append(", AT: (");
    string.append(this._dimension);
    string.append(", ");
    string.append(this._xCoord);
    string.append(", ");
    string.append(this._yCoord);
    string.append(", ");
    string.append(this._zCoord);
    string.append("), Version: ");
    string.append(_LSAVersion);
    string.append("), Destroied: ");
    string.append(isDestroied());
    return string.append("}").toString();
  }

  @Override
  public void forceLsaUpdate() {
    BitSet visited = new BitSet(ServerRouter.getBiggestSimpleID());
    this.act(visited, new flagForLSAUpdate());
  }

  @Override
  public List<ExitRoute> getRoutersOnSide(ForgeDirection direction) {
    List<ExitRoute> routers = new ArrayList<ExitRoute>();
    for(ExitRoute exit:_adjacentRouter.values()) {
      if(exit.exitOrientation == direction) {
        routers.add(exit);
      }
    }
    return routers;
  }

  @Override
  public int getDimension() {
    return _dimension;
  }

  @Override
  public void queueTask(int i, IRouterQueuedTask callable) {
    this.queue.add(new Pair<Integer, IRouterQueuedTask> (i + MainProxy.getGlobalTick(), callable));
  }
}


TOP

Related Classes of logisticspipes.routing.ServerRouter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.