AntennaRelay: Major surgery on FindNearestRelay to improve performance and reliability. Hopefully.
[AntennaRange.git] / AntennaRelay.cs
blob:a/AntennaRelay.cs -> blob:b/AntennaRelay.cs
// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// AntennaRange provides incentive and requirements for the use of the various antenna parts. // AntennaRange provides incentive and requirements for the use of the various antenna parts.
// Nominally, the breakdown is as follows: // Nominally, the breakdown is as follows:
// //
// Communotron 16 - Suitable up to Kerbalsynchronous Orbit // Communotron 16 - Suitable up to Kerbalsynchronous Orbit
// Comms DTS-M1 - Suitable throughout the Kerbin subsystem // Comms DTS-M1 - Suitable throughout the Kerbin subsystem
// Communotron 88-88 - Suitable throughout the Kerbol system. // Communotron 88-88 - Suitable throughout the Kerbol system.
// //
// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a // This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ // copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
// //
// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike // This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
// 3.0 Uported License. // 3.0 Uported License.
// //
// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3. // This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
   
namespace AntennaRange namespace AntennaRange
{ {
public class AntennaRelay public class AntennaRelay
{ {
// We don't have a Bard, so we'll hide Kerbin here. // We don't have a Bard, so we'll hide Kerbin here.
protected CelestialBody Kerbin; protected CelestialBody Kerbin;
   
  protected IAntennaRelay _nearestRelayCache;
   
protected System.Diagnostics.Stopwatch searchTimer; protected System.Diagnostics.Stopwatch searchTimer;
protected long millisecondsBetweenSearches; protected long millisecondsBetweenSearches;
   
/// <summary> /// <summary>
/// Gets the parent Vessel. /// Gets the parent Vessel.
/// </summary> /// </summary>
/// <value>The parent Vessel.</value> /// <value>The parent Vessel.</value>
public Vessel vessel public Vessel vessel
{ {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets or sets the nearest relay. /// Gets or sets the nearest relay.
/// </summary> /// </summary>
/// <value>The nearest relay</value> /// <value>The nearest relay</value>
public IAntennaRelay nearestRelay public IAntennaRelay nearestRelay
{ {
  get
  {
  if (this.searchTimer.IsRunning &&
  this.searchTimer.ElapsedMilliseconds > this.millisecondsBetweenSearches)
  {
  this._nearestRelayCache = this.FindNearestRelay();
  this.searchTimer.Restart();
  }
   
  return this._nearestRelayCache;
  }
  protected set
  {
  this._nearestRelayCache = value;
  }
  }
   
  /// <summary>
  /// Gets the transmit distance.
  /// </summary>
  /// <value>The transmit distance.</value>
  public double transmitDistance
  {
  get
  {
  this.nearestRelay = this.FindNearestRelay();
   
  // If there is no available relay nearby...
  if (this.nearestRelay == null)
  {
  // .. return the distance to Kerbin
  return this.DistanceTo(this.Kerbin);
  }
  else
  {
  /// ...otherwise, return the distance to the nearest available relay.
  return this.DistanceTo(nearestRelay);
  }
  }
  }
   
  /// <summary>
  /// The maximum distance at which this relay can operate.
  /// </summary>
  /// <value>The max transmit distance.</value>
  public virtual float maxTransmitDistance
  {
  get;
  set;
  }
   
  /// <summary>
  /// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during
  /// the current relay attempt.
  /// </summary>
  /// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>
  public virtual bool relayChecked
  {
get; get;
protected set; protected set;
} }
   
/// <summary> /// <summary>
/// Gets the transmit distance.  
/// </summary>  
/// <value>The transmit distance.</value>  
public double transmitDistance  
{  
get  
{  
this.nearestRelay = this.FindNearestRelay();  
   
// If there is no available relay nearby...  
if (nearestRelay == null)  
{  
// .. return the distance to Kerbin  
return this.DistanceTo(this.Kerbin);  
}  
else  
{  
/// ...otherwise, return the distance to the nearest available relay.  
return this.DistanceTo(nearestRelay);  
}  
}  
}  
   
/// <summary>  
/// The maximum distance at which this relay can operate.  
/// </summary>  
/// <value>The max transmit distance.</value>  
public virtual float maxTransmitDistance  
{  
get;  
set;  
}  
   
/// <summary>  
/// Gets a value indicating whether this <see cref="AntennaRange.ProtoDataTransmitter"/> has been checked during  
/// the current relay attempt.  
/// </summary>  
/// <value><c>true</c> if relay checked; otherwise, <c>false</c>.</value>  
public virtual bool relayChecked  
{  
get;  
protected set;  
}  
   
/// <summary>  
/// Determines whether this instance can transmit. /// Determines whether this instance can transmit.
/// </summary> /// </summary>
/// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if this instance can transmit; otherwise, <c>false</c>.</returns>
public virtual bool CanTransmit() public virtual bool CanTransmit()
{ {
if (this.transmitDistance > this.maxTransmitDistance) if (this.transmitDistance > this.maxTransmitDistance)
{ {
return false; return false;
} }
else else
{ {
return true; return true;
} }
} }
   
/// <summary> /// <summary>
/// Finds the nearest relay. /// Finds the nearest relay.
/// </summary> /// </summary>
/// <returns>The nearest relay or null, if no relays in range.</returns> /// <returns>The nearest relay or null, if no relays in range.</returns>
public IAntennaRelay FindNearestRelay() public IAntennaRelay FindNearestRelay()
{ {
if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches) if (this.searchTimer.IsRunning && this.searchTimer.ElapsedMilliseconds < this.millisecondsBetweenSearches)
{ {
return this.nearestRelay; return this.nearestRelay;
} }
   
if (this.searchTimer.IsRunning) if (this.searchTimer.IsRunning)
{ {
this.searchTimer.Stop(); this.searchTimer.Stop();
this.searchTimer.Reset(); this.searchTimer.Reset();
} }
   
this.searchTimer.Start(); this.searchTimer.Start();
   
// Set this relay as checked, so that we don't check it again. // Set this vessel as checked, so that we don't check it again.
this.relayChecked = true; RelayDatabase.Instance.CheckedVesselsTable[vessel.id] = true;
   
// Get a list of vessels within transmission range. double nearestDistance = double.PositiveInfinity;
List<Vessel> nearbyVessels = FlightGlobals.Vessels IAntennaRelay _nearestRelay = null;
.Where(v => (v.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude < this.maxTransmitDistance)  
.ToList(); /*
  * Loop through all the vessels and exclude this vessel, vessels of the wrong type, and vessels that are too
nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Debris); * far away. When we find a candidate, get through its antennae for relays which have not been checked yet
nearbyVessels.RemoveAll(v => v.vesselType == VesselType.Flag); * and that can transmit. Once we find a suitable candidate, assign it to _nearestRelay for comparison
  * against future finds.
Tools.PostDebugMessage(string.Format( * */
"{0}: Non-debris, non-flag vessels in range: {1}", foreach (Vessel potentialVessel in FlightGlobals.Vessels)
this.GetType().Name, {
nearbyVessels.Count // Skip vessels that have already been checked for a nearest relay this pass.
)); try
  {
// Remove this vessel. if (RelayDatabase.Instance.CheckedVesselsTable[potentialVessel.id])
nearbyVessels.RemoveAll(v => v.id == vessel.id); {
  continue;
Tools.PostDebugMessage(string.Format( }
"{0}: Vessels in range excluding self: {1}", }
this.GetType().Name, catch (KeyNotFoundException) { /* If the key doesn't exist, do nothing. */}
nearbyVessels.Count  
)); // Skip vessels of the wrong type.
  switch (potentialVessel.vesselType)
// Get a flattened list of all IAntennaRelay modules and protomodules in transmission range. {
List<IAntennaRelay> nearbyRelays = nearbyVessels.SelectMany(v => v.GetAntennaRelays()).ToList(); case VesselType.Debris:
  case VesselType.Flag:
Tools.PostDebugMessage(string.Format( case VesselType.EVA:
"{0}: Found {1} nearby relays.", case VesselType.SpaceObject:
this.GetType().Name, case VesselType.Unknown:
nearbyRelays.Count continue;
)); default:
  break;
// Remove all relays already checked this time. }
nearbyRelays.RemoveAll(r => r.relayChecked);  
  // Skip vessels with the wrong ID
Tools.PostDebugMessage(string.Format( if (potentialVessel.id == vessel.id)
"{0}: Found {1} nearby relays not already checked.", {
this.GetType().Name, continue;
nearbyRelays.Count }
));  
  // Find the distance from here to the vessel...
// Remove all relays that cannot transmit. double potentialDistance = (potentialVessel.GetWorldPos3D() - vessel.GetWorldPos3D()).magnitude;
// This call to r.CanTransmit() starts a depth-first recursive search for relays with a path back to Kerbin.  
nearbyRelays.RemoveAll(r => !r.CanTransmit()); /*
  * ...so that we can skip the vessel if it is further away than Kerbin, our transmit distance, or a
Tools.PostDebugMessage(string.Format( * vessel we've already checked.
"{0}: Found {1} nearby relays not already checked that can transmit.", * */
this.GetType().Name, if (potentialDistance > Tools.Min(this.maxTransmitDistance, nearestDistance, vessel.DistanceTo(Kerbin)))
nearbyRelays.Count {
)); continue;
  }
// Sort the available relays by distance.  
nearbyRelays.Sort(new RelayComparer(this.vessel)); nearestDistance = potentialDistance;
   
// Get the nearest available relay, or null if there are no available relays nearby. foreach (IAntennaRelay potentialRelay in potentialVessel.GetAntennaRelays())
IAntennaRelay _nearestRelay = nearbyRelays.FirstOrDefault(); {
  if (potentialRelay.CanTransmit())
// If we have a nearby relay... {
if (_nearestRelay != null) _nearestRelay = potentialRelay;
{ break;
// ...but that relay is farther than Kerbin... }
if (this.DistanceTo(_nearestRelay) > this.DistanceTo(Kerbin))  
{  
// ...just use Kerbin.  
_nearestRelay = null;  
} }
} }
   
// Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be // Now that we're done with our recursive CanTransmit checks, flag this relay as not checked so it can be
// used next time. // used next time.
this.relayChecked = false; RelayDatabase.Instance.CheckedVesselsTable.Remove(vessel.id);
   
// Return the nearest available relay, or null if there are no available relays nearby. // Return the nearest available relay, or null if there are no available relays nearby.
return _nearestRelay; return _nearestRelay;
} }
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class. /// Initializes a new instance of the <see cref="AntennaRange.ProtoDataTransmitter"/> class.
/// </summary> /// </summary>
/// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param> /// <param name="ms"><see cref="ProtoPartModuleSnapshot"/></param>
public AntennaRelay(Vessel v) public AntennaRelay(Vessel v)
{ {
this.vessel = v; this.vessel = v;
   
this.searchTimer = new System.Diagnostics.Stopwatch(); this.searchTimer = new System.Diagnostics.Stopwatch();
this.millisecondsBetweenSearches = 5000; this.millisecondsBetweenSearches = 5000;
   
// HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start, // HACK: This might not be safe in all circumstances, but since AntennaRelays are not built until Start,
// we hope it is safe enough. // we hope it is safe enough.
this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin"); this.Kerbin = FlightGlobals.Bodies.FirstOrDefault(b => b.name == "Kerbin");
} }
   
/* /*
* Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance. * Class implementing IComparer<IAntennaRelay> for use in sorting relays by distance.
* */ * */
internal class RelayComparer : IComparer<IAntennaRelay> internal class RelayComparer : IComparer<IAntennaRelay>
{ {
/// <summary> /// <summary>
/// The reference Vessel (usually the active vessel). /// The reference Vessel (usually the active vessel).
/// </summary> /// </summary>
protected Vessel referenceVessel; protected Vessel referenceVessel;
   
// We don't want no stinking public parameterless constructors. // We don't want no stinking public parameterless constructors.
private RelayComparer() {} private RelayComparer() {}
   
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use /// Initializes a new instance of the <see cref="AntennaRange.AntennaRelay+RelayComparer"/> class for use
/// in sorting relays by distance. /// in sorting relays by distance.
/// </summary> /// </summary>
/// <param name="reference">The reference Vessel</param> /// <param name="reference">The reference Vessel</param>
public RelayComparer(Vessel reference) public RelayComparer(Vessel reference)
{ {
this.referenceVessel = reference; this.referenceVessel = reference;
} }
   
/// <summary> /// <summary>
/// Compare the <see cref="IAntennaRelay"/>s "one" and "two". /// Compare the <see cref="IAntennaRelay"/>s "one" and "two".
/// </summary> /// </summary>
/// <param name="one">The first IAntennaRelay in the comparison</param> /// <param name="one">The first IAntennaRelay in the comparison</param>
/// <param name="two">The second IAntennaRelay in the comparison</param> /// <param name="two">The second IAntennaRelay in the comparison</param>
public int Compare(IAntennaRelay one, IAntennaRelay two) public int Compare(IAntennaRelay one, IAntennaRelay two)
{ {
double distanceOne; double distanceOne;
double distanceTwo; double distanceTwo;
   
distanceOne = one.vessel.DistanceTo(referenceVessel); distanceOne = one.vessel.DistanceTo(referenceVessel);
distanceTwo = two.vessel.DistanceTo(referenceVessel); distanceTwo = two.vessel.DistanceTo(referenceVessel);
   
return distanceOne.CompareTo(distanceTwo); return distanceOne.CompareTo(distanceTwo);
} }
} }
} }
} }