Version 1.9 1.9
Version 1.9

--- a/ARFlightController.cs
+++ b/ARFlightController.cs
@@ -2,7 +2,7 @@
 //
 // ARFlightController.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -40,7 +40,7 @@
 	public class ARFlightController : MonoBehaviour
 	{
 		#region Fields
-		private Dictionary<ConnectionStatus, string> connectionTextures;
+		private Dictionary<ConnectionStatus, string> toolbarTextures;
 		private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
 
 		private ARMapRenderer mapRenderer;
@@ -64,7 +64,7 @@
 		{
 			get
 			{
-				return this.connectionTextures[this.currentConnectionStatus];
+				return this.toolbarTextures[this.currentConnectionStatus];
 			}
 		}
 
@@ -126,11 +126,11 @@
 
 			this.updateTimer = new System.Diagnostics.Stopwatch();
 
-			this.connectionTextures = new Dictionary<ConnectionStatus, string>();
-
-			this.connectionTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
-			this.connectionTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
-			this.connectionTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
+			this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
+
+			this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
+			this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
+			this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
 
 			this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
 
@@ -145,7 +145,7 @@
 			{
 				this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
 
-				this.toolbarButton.TexturePath = this.connectionTextures[ConnectionStatus.None];
+				this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
 				this.toolbarButton.Text = "AntennaRange";
 				this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
 				this.toolbarButton.Enabled = false;
@@ -224,7 +224,7 @@
 
 		private void Update()
 		{
-			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 83L)
+			if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > 16L)
 			{
 				this.updateTimer.Restart();
 			}

--- a/ARMapRenderer.cs
+++ b/ARMapRenderer.cs
@@ -2,7 +2,7 @@
 //
 // ARMapRenderer.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -167,6 +167,7 @@
 
 						if (vesselRelay == null)
 						{
+							log.AppendFormat("\n\tGot null relay for vessel {0}", vessel.vesselName);
 							continue;
 						}
 
@@ -206,7 +207,7 @@
 		{
 			this.Cleanup();
 
-			print("ARMapRenderer: Destroyed.");
+			this.Log("Destroyed");
 		}
 		#endregion
 
@@ -273,18 +274,21 @@
 			if (relay.KerbinDirect)
 			{
 				nextPoint = ScaledSpace.LocalToScaledSpace(AntennaRelay.Kerbin.position);
-				relay = null;
 			}
 			else
 			{
-				if (relay.targetRelay == null)
-				{
+				if (relay.targetRelay == null || relay.targetRelay.vessel == null)
+				{
+					this.LogError(
+						"SetRelayVertices: relay {0} has null target relay or vessel when not KerbinDirect, bailing out!",
+						relay
+					);
+
 					renderer.enabled = false;
 					return;
 				}
 
 				nextPoint = ScaledSpace.LocalToScaledSpace(relay.targetRelay.vessel.GetWorldPos3D());
-				relay = relay.targetRelay;
 			}
 
 			renderer.SetColors(thisColor, thisColor);

--- a/AntennaRelay.cs
+++ b/AntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // AntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -198,6 +198,7 @@
 			this.KerbinDirect = true;
 
 			CelestialBody bodyOccludingBestOccludedRelay = null;
+			IAntennaRelay needle;
 
 			double nearestRelaySqrDistance = double.PositiveInfinity;
 			double bestOccludedSqrDistance = double.PositiveInfinity;
@@ -221,6 +222,7 @@
 				
 				if (potentialVessel == null)
 				{
+					Tools.PostErrorMessage("{0}: Skipping vessel at index {1} because it is null.", this, vIdx);
 					log.AppendFormat("\n\tSkipping vessel at index {0} because it is null.", vIdx);
 					log.Print();
 					return;
@@ -324,19 +326,49 @@
 
 				log.Append("\n\t\t...passed distance check");
 
-				if (
-					potentialBestRelay.CanTransmit() &&
-					(potentialBestRelay.targetRelay == null || potentialBestRelay.targetRelay.vessel != this.vessel))
-				{
-					// @TODO: Moved this here from outside the loop; why was it there?
-					nearestRelaySqrDistance = potentialSqrDistance;
-					this.nearestRelay = potentialBestRelay;
-
-					log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
-						this.ToString(),
-						this.nearestRelay.ToString(),
-						Math.Sqrt(nearestRelaySqrDistance)
-					);
+				if (potentialBestRelay.CanTransmit())
+				{
+					needle = potentialBestRelay;
+					bool isCircular = false;
+
+					while (needle != null)
+					{
+						if (needle.KerbinDirect)
+						{
+							break;
+						}
+
+						if (needle.targetRelay == null)
+						{
+							break;
+						}
+
+						if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef)
+						{
+							isCircular = true;
+							break;
+						}
+
+						needle = needle.targetRelay;
+					}
+
+					if (!isCircular)
+					{
+						nearestRelaySqrDistance = potentialSqrDistance;
+						this.nearestRelay = potentialBestRelay;
+
+						log.AppendFormat("\n\t{0}: found new nearest relay {1} ({2}m)",
+							this.ToString(),
+							this.nearestRelay.ToString(),
+							Math.Sqrt(nearestRelaySqrDistance)
+						);
+					}
+					else
+					{
+						log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping",
+							potentialBestRelay
+						);
+					}
 				}
 			}
 
@@ -346,6 +378,15 @@
 			kerbinSqrDistance *= kerbinSqrDistance;
 
 			log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name);
+			log.AppendFormat(
+				"\n{0}: nearestRelay={1} ({2}m²)), bestOccludedRelay={3} ({4}m²), kerbinSqrDistance={5}m²)",
+				this,
+				this.nearestRelay == null ? "null" : this.nearestRelay.ToString(),
+				nearestRelaySqrDistance,
+				this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(),
+				bestOccludedSqrDistance,
+				kerbinSqrDistance
+			);
 
 			// If we don't have LOS to Kerbin, focus on relays
 			if (!this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio))
@@ -389,7 +430,7 @@
 							log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
 								nearestRelaySqrDistance, bestOccludedSqrDistance);
 							
-							this.targetRelay = nearestRelay;
+							this.targetRelay = this.nearestRelay;
 							this.firstOccludingBody = null;
 						}
 						// Otherwise, target the best occluded relay.
@@ -419,7 +460,7 @@
 								nearestRelaySqrDistance, kerbinSqrDistance);
 							
 							this.KerbinDirect = false;
-							this.targetRelay = nearestRelay;
+							this.targetRelay = this.nearestRelay;
 						}
 						// Otherwise, pick Kerbin.
 						else
@@ -510,7 +551,7 @@
 								log.AppendFormat("\n\t\t\t\t...but the nearest relay is closer ({0} < {1}), so picking it.",
 									nearestRelaySqrDistance, bestOccludedSqrDistance);
 								
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 								this.firstOccludingBody = null;
 							}
 							// Otherwise, target the best occluded relay.
@@ -540,7 +581,7 @@
 									nearestRelaySqrDistance, kerbinSqrDistance);
 								
 								this.KerbinDirect = false;
-								this.targetRelay = nearestRelay;
+								this.targetRelay = this.nearestRelay;
 							}
 							// Otherwise, pick Kerbin.
 							else

--- a/GameData/AntennaRange/AntennaRange.cfg
+++ b/GameData/AntennaRange/AntennaRange.cfg
@@ -2,7 +2,7 @@
 //
 // AntennaRange.cfg
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,

--- a/IAntennaRelay.cs
+++ b/IAntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // IAntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,

--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -2,7 +2,7 @@
 //
 // ModuleLimitedDataTransmitter.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -145,13 +145,13 @@
 				{
 					return base.vessel;
 				}
-				else if (this.part != null)
+				else if (this.part != null && this.part.vessel != null)
 				{
 					return this.part.vessel;
 				}
-
 				else
 				{
+					this.LogError("Vessel and/or part reference are null, returning null vessel.");
 					return null;
 				}
 			}
@@ -205,11 +205,8 @@
 		/// </summary>
 		public double maxTransmitDistance
 		{
-			get
-			{
-				// TODO: Cache this in a way that doesn't break everything.
-				return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
-			}
+			get;
+			protected set;
 		}
 
 		/// <summary>
@@ -370,6 +367,8 @@
 
 			if (state >= StartState.PreLaunch)
 			{
+				this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
+
 				this.relay = new AntennaRelay(this);
 				this.relay.maxTransmitDistance = this.maxTransmitDistance;
 				this.relay.nominalTransmitDistance = this.nominalRange;
@@ -393,6 +392,8 @@
 			base.Fields.Load(node);
 
 			base.OnLoad (node);
+
+			this.maxTransmitDistance = Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
 		}
 
 		/// <summary>
@@ -643,7 +644,6 @@
 					{
 						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
 					}
-					this.UImaxTransmitDistance = "N/A";
 					this.UIpacketSize = "N/A";
 					this.UIpacketCost = "N/A";
 				}

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -2,7 +2,7 @@
 //
 // AssemblyInfo.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("1.8.*")]
+[assembly: AssemblyVersion("1.9.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/ProtoAntennaRelay.cs
+++ b/ProtoAntennaRelay.cs
@@ -2,7 +2,7 @@
 //
 // ProtoAntennaRelay.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -48,17 +48,24 @@
 		{
 			get
 			{
-				if (this.protoPart != null && this.protoPart.pVesselRef != null)
+				if (
+					this.protoPart != null &&
+					this.protoPart.pVesselRef != null &&
+					this.protoPart.pVesselRef.vesselRef != null
+				)
 				{
 					return this.protoPart.pVesselRef.vesselRef;
 				}
 				else
 				{
-					Tools.PostLogMessage("{0}: Could not fetch vessel!  {1}{2}{3}",
+					Tools.PostErrorMessage("{0}: Could not fetch vessel!  {1}{2}{3}",
 						this.ToString(),
-						this.protoPart == null ? "\n\tprotoPart=Null" : string.Empty,
-						this.protoPart != null && this.protoPart.pVesselRef == null ? "\n\tthis.protoPart.pVesselRef=Null" : string.Empty,
-						this.protoPart != null && this.protoPart.pVesselRef != null && this.protoPart.pVesselRef.vesselRef == null ? "\n\tthis.protoPart.pVesselRef.vesselRef=Null" : string.Empty
+						this.protoPart == null ? "\n\tprotoPart=null" : string.Empty,
+						this.protoPart != null && this.protoPart.pVesselRef == null ?
+							"\n\tthis.protoPart.pVesselRef=null" : string.Empty,
+						this.protoPart != null && this.protoPart.pVesselRef != null &&
+							this.protoPart.pVesselRef.vesselRef == null ?
+							"\n\tthis.protoPart.pVesselRef.vesselRef=null" : string.Empty
 					);
 					return null;
 				}

--- a/RelayDatabase.cs
+++ b/RelayDatabase.cs
@@ -2,7 +2,7 @@
 //
 // RelayDatabase.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,
@@ -37,27 +37,8 @@
 
 namespace AntennaRange
 {
-	public class RelayDatabase
+	public class RelayDatabase : Singleton<RelayDatabase>
 	{
-		/*
-		 * Static members
-		 * */
-		// Singleton storage
-		private static RelayDatabase _instance;
-		// Gets the singleton
-		public static RelayDatabase Instance
-		{
-			get
-			{	
-				if (_instance == null)
-				{
-					_instance = new RelayDatabase();
-				}
-
-				return _instance;
-			}
-		}
-
 		/*
 		 * Instance members
 		 * */
@@ -72,9 +53,6 @@
 		// Vessel.id-keyed hash table of part counts, used for caching
 		private Dictionary<Guid, int> vesselPartCountTable;
 
-		// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time.
-		public Dictionary<Guid, bool> CheckedVesselsTable;
-
 		private int cacheHits;
 		private int cacheMisses;
 
@@ -117,9 +95,25 @@
 		// Remove a vessel from the cache, if it exists.
 		public void DirtyVessel(Vessel vessel)
 		{
+			#if DEBUG
+			Tools.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
+				vessel, new System.Diagnostics.StackTrace().ToString());
+			#else
+			Tools.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
+			#endif
+
 			this.relayDatabase.Remove(vessel.id);
 			this.vesselPartCountTable.Remove(vessel.id);
-			this.relayDatabase.Remove(vessel.id);
+			this.bestRelayTable.Remove(vessel.id);
+		}
+
+		public void ClearCache()
+		{
+			Tools.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
+
+			this.relayDatabase.Clear();
+			this.bestRelayTable.Clear();
+			this.vesselPartCountTable.Clear();
 		}
 
 		// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
@@ -228,12 +222,35 @@
 		// Runs when the player requests a scene change, such as when changing vessels or leaving flight.
 		private void onSceneChange(GameScenes scene)
 		{
-			// If the active vessel is a real thing...
-			if (FlightGlobals.ActiveVessel != null)
-			{
-				// ... dirty its cache
-				this.onVesselEvent(FlightGlobals.ActiveVessel);
-			}
+			Tools.PostDebugMessage(
+				"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}.  ActiveVessel is {2}",
+				HighLogic.LoadedScene,
+				scene,
+				FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
+			);
+
+			if (scene == GameScenes.FLIGHT)
+			{
+				if (scene == HighLogic.LoadedScene)
+				{
+					if (FlightGlobals.ActiveVessel != null)
+					{
+						Tools.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
+							FlightGlobals.ActiveVessel.vesselName);
+
+						this.onVesselEvent(FlightGlobals.ActiveVessel);
+					}
+				}
+				else
+				{
+					this.ClearCache();
+				}
+			}
+		}
+
+		private void onGameLoaded(object data)
+		{
+			this.ClearCache();
 		}
 
 		// Runs when parts are undocked
@@ -394,7 +411,6 @@
 			this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
 			this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
 			this.vesselPartCountTable = new Dictionary<Guid, int>();
-			this.CheckedVesselsTable = new Dictionary<Guid, bool>();
 
 			this.cacheHits = 0;
 			this.cacheMisses = 0;
@@ -406,6 +422,7 @@
 			GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
 			GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Add(this.onPartEvent);
+			GameEvents.onGameStateLoad.Add(this.onGameLoaded);
 		}
 
 		~RelayDatabase()
@@ -417,6 +434,7 @@
 			GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
 			GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
 			GameEvents.onPartUndock.Remove(this.onPartEvent);
+			GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
 
 			Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
 

--- a/RelayExtensions.cs
+++ b/RelayExtensions.cs
@@ -2,7 +2,7 @@
 //
 // Extensions.cs
 //
-// Copyright © 2014, toadicus
+// Copyright © 2014-2015, toadicus
 // All rights reserved.
 //
 // Redistribution and use in source and binary forms, with or without modification,