ARFlightController: Trying to tweak the ordering of things to help networks resolve more neatly.
ARFlightController: Trying to tweak the ordering of things to help networks resolve more neatly.

// AntennaRange // AntennaRange
// //
// ARFlightController.cs // ARFlightController.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
#pragma warning disable 1591 #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools.Extensions; using ToadicusTools.Extensions;
using ToadicusTools.Text; using ToadicusTools.Text;
using ToadicusTools.DebugTools; using ToadicusTools.DebugTools;
using ToadicusTools.Wrappers; using ToadicusTools.Wrappers;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
[KSPAddon(KSPAddon.Startup.Flight, false)] [KSPAddon(KSPAddon.Startup.Flight, false)]
public class ARFlightController : MonoBehaviour public class ARFlightController : MonoBehaviour
{ {
#region Static #region Static
private static List<IAntennaRelay> usefulRelays; private static List<IAntennaRelay> usefulRelays;
public static IList<IAntennaRelay> UsefulRelays; public static IList<IAntennaRelay> UsefulRelays;
#endregion #endregion
   
#region Fields #region Fields
private Dictionary<ConnectionStatus, string> toolbarTextures; private Dictionary<ConnectionStatus, string> toolbarTextures;
private Dictionary<ConnectionStatus, Texture> appLauncherTextures; private Dictionary<ConnectionStatus, Texture> appLauncherTextures;
   
private ARMapRenderer mapRenderer; private ARMapRenderer mapRenderer;
   
private IButton toolbarButton; private IButton toolbarButton;
   
private ApplicationLauncherButton appLauncherButton; private ApplicationLauncherButton appLauncherButton;
private PooledDebugLogger log; private PooledDebugLogger log;
   
private System.Diagnostics.Stopwatch updateTimer; private System.Diagnostics.Stopwatch updateTimer;
#endregion #endregion
   
#region Properties #region Properties
public ConnectionStatus currentConnectionStatus public ConnectionStatus currentConnectionStatus
{ {
get; get;
private set; private set;
} }
   
private string currentConnectionTexture private string currentConnectionTexture
{ {
get get
{ {
return this.toolbarTextures[this.currentConnectionStatus]; return this.toolbarTextures[this.currentConnectionStatus];
} }
} }
   
private Texture currentAppLauncherTexture private Texture currentAppLauncherTexture
{ {
get get
{ {
return this.appLauncherTextures[this.currentConnectionStatus]; return this.appLauncherTextures[this.currentConnectionStatus];
} }
} }
   
public ControlTypes currentControlLock public ControlTypes currentControlLock
{ {
get get
{ {
if (this.lockID == string.Empty) if (this.lockID == string.Empty)
{ {
return ControlTypes.None; return ControlTypes.None;
} }
   
return InputLockManager.GetControlLock(this.lockID); return InputLockManager.GetControlLock(this.lockID);
} }
} }
   
public string lockID public string lockID
{ {
get; get;
private set; private set;
} }
   
public ControlTypes lockSet public ControlTypes lockSet
{ {
get get
{ {
return ControlTypes.ALL_SHIP_CONTROLS; return ControlTypes.ALL_SHIP_CONTROLS;
} }
} }
   
public Vessel vessel public Vessel vessel
{ {
get get
{ {
if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
return FlightGlobals.ActiveVessel; return FlightGlobals.ActiveVessel;
} }
   
return null; return null;
} }
} }
#endregion #endregion
   
#region MonoBehaviour LifeCycle #region MonoBehaviour LifeCycle
private void Awake() private void Awake()
{ {
this.lockID = "ARConnectionRequired"; this.lockID = "ARConnectionRequired";
   
this.log = PooledDebugLogger.New(this); this.log = PooledDebugLogger.New(this);
   
this.updateTimer = new System.Diagnostics.Stopwatch(); this.updateTimer = new System.Diagnostics.Stopwatch();
   
this.toolbarTextures = new Dictionary<ConnectionStatus, string>(); this.toolbarTextures = new Dictionary<ConnectionStatus, string>();
   
this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection"; this.toolbarTextures[ConnectionStatus.None] = "AntennaRange/Textures/toolbarIconNoConnection";
this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal"; this.toolbarTextures[ConnectionStatus.Suboptimal] = "AntennaRange/Textures/toolbarIconSubOptimal";
this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon"; this.toolbarTextures[ConnectionStatus.Optimal] = "AntennaRange/Textures/toolbarIcon";
   
this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>(); this.appLauncherTextures = new Dictionary<ConnectionStatus, Texture>();
   
this.appLauncherTextures[ConnectionStatus.None] = this.appLauncherTextures[ConnectionStatus.None] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconNoConnection", false);
this.appLauncherTextures[ConnectionStatus.Suboptimal] = this.appLauncherTextures[ConnectionStatus.Suboptimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIconSubOptimal", false);
this.appLauncherTextures[ConnectionStatus.Optimal] = this.appLauncherTextures[ConnectionStatus.Optimal] =
GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false); GameDatabase.Instance.GetTexture("AntennaRange/Textures/appLauncherIcon", false);
   
if (ToolbarManager.ToolbarAvailable) if (ToolbarManager.ToolbarAvailable)
{ {
this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus"); this.toolbarButton = ToolbarManager.Instance.add("AntennaRange", "ARConnectionStatus");
   
this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None]; this.toolbarButton.TexturePath = this.toolbarTextures[ConnectionStatus.None];
this.toolbarButton.Text = "AntennaRange"; this.toolbarButton.Text = "AntennaRange";
this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT); this.toolbarButton.Visibility = new GameScenesVisibility(GameScenes.FLIGHT);
this.toolbarButton.OnClick += (e) => (this.buttonToggle()); this.toolbarButton.OnClick += (e) => (this.buttonToggle());
} }
   
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChangeRequested);
GameEvents.onVesselChange.Add(this.onVesselChange); GameEvents.onVesselChange.Add(this.onVesselChange);
   
usefulRelays = new List<IAntennaRelay>(); usefulRelays = new List<IAntennaRelay>();
UsefulRelays = usefulRelays.AsReadOnly(); UsefulRelays = usefulRelays.AsReadOnly();
} }
   
private void FixedUpdate() private void FixedUpdate()
{ {
this.log.Clear(); this.log.Clear();
   
VesselCommand availableCommand; VesselCommand availableCommand;
   
if (ARConfiguration.RequireConnectionForControl) if (ARConfiguration.RequireConnectionForControl)
{ {
availableCommand = this.vessel.CurrentCommand(); availableCommand = this.vessel.CurrentCommand();
} }
else else
{ {
availableCommand = VesselCommand.Crew; availableCommand = VesselCommand.Crew;
} }
   
log.AppendFormat("availableCommand: {0}\n\t" + log.AppendFormat("availableCommand: {0}\n\t" +
"(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" + "(availableCommand & VesselCommand.Crew) == VesselCommand.Crew: {1}\n\t" +
"(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" + "(availableCommand & VesselCommand.Probe) == VesselCommand.Probe: {2}\n\t" +
"vessel.HasConnectedRelay(): {3}", "vessel.HasConnectedRelay(): {3}",
(int)availableCommand, (int)availableCommand,
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew, (availableCommand & VesselCommand.Crew) == VesselCommand.Crew,
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe, (availableCommand & VesselCommand.Probe) == VesselCommand.Probe,
vessel.HasConnectedRelay() vessel.HasConnectedRelay()
); );
   
// If we are requiring a connection for control, the vessel does not have any adequately staffed pods, // If we are requiring a connection for control, the vessel does not have any adequately staffed pods,
// and the vessel does not have any connected relays... // and the vessel does not have any connected relays...
if ( if (
HighLogic.LoadedSceneIsFlight && HighLogic.LoadedSceneIsFlight &&
ARConfiguration.RequireConnectionForControl && ARConfiguration.RequireConnectionForControl &&
this.vessel != null && this.vessel != null &&
this.vessel.vesselType != VesselType.EVA && this.vessel.vesselType != VesselType.EVA &&
!( !(
(availableCommand & VesselCommand.Crew) == VesselCommand.Crew || (availableCommand & VesselCommand.Crew) == VesselCommand.Crew ||
(availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay() (availableCommand & VesselCommand.Probe) == VesselCommand.Probe && vessel.HasConnectedRelay()
)) ))
{ {
// ...and if the controls are not currently locked... // ...and if the controls are not currently locked...
if (currentControlLock == ControlTypes.None) if (currentControlLock == ControlTypes.None)
{ {
// ...lock the controls. // ...lock the controls.
InputLockManager.SetControlLock(this.lockSet, this.lockID); InputLockManager.SetControlLock(this.lockSet, this.lockID);
} }
} }
// ...otherwise, if the controls are locked... // ...otherwise, if the controls are locked...
else if (currentControlLock != ControlTypes.None) else if (currentControlLock != ControlTypes.None)
{ {
// ...unlock the controls. // ...unlock the controls.
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
   
log.Print(); log.Print();
} }
   
private void Update() private void Update()
{ {
if (MapView.MapIsEnabled && this.mapRenderer == null) if (MapView.MapIsEnabled && this.mapRenderer == null)
{ {
this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>(); this.mapRenderer = MapView.MapCamera.gameObject.AddComponent<ARMapRenderer>();
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Enabled = MapView.MapIsEnabled; this.toolbarButton.Enabled = MapView.MapIsEnabled;
} }
   
if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready) if (this.appLauncherButton == null && !ToolbarManager.ToolbarAvailable && ApplicationLauncher.Ready)
{ {
this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication( this.appLauncherButton = ApplicationLauncher.Instance.AddModApplication(
this.buttonToggle, this.buttonToggle, this.buttonToggle, this.buttonToggle,
ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW, ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW,
this.appLauncherTextures[ConnectionStatus.None] this.appLauncherTextures[ConnectionStatus.None]
); );
} }
   
if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay) if (!this.updateTimer.IsRunning || this.updateTimer.ElapsedMilliseconds > ARConfiguration.UpdateDelay)
{ {
this.updateTimer.Restart(); this.updateTimer.Restart();
} }
else else
{ {
return; return;
} }
   
this.log.Clear(); this.log.Clear();
   
this.log.Append("[ARFlightController]: Update"); this.log.Append("[ARFlightController]: Update");
   
if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null) if (HighLogic.LoadedSceneIsFlight && FlightGlobals.ready && FlightGlobals.ActiveVessel != null)
{ {
Vessel vessel; Vessel vessel;
IAntennaRelay relay; IAntennaRelay relay;
IAntennaRelay bestActiveRelay; IAntennaRelay bestActiveRelay = null;
IList<IAntennaRelay> activeVesselRelays; IList<IAntennaRelay> activeVesselRelays;
   
usefulRelays.Clear(); usefulRelays.Clear();
   
for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++) for (int vIdx = 0; vIdx < FlightGlobals.Vessels.Count; vIdx++)
{ {
vessel = FlightGlobals.Vessels[vIdx]; vessel = FlightGlobals.Vessels[vIdx];
   
if (vessel == null || vessel == FlightGlobals.ActiveVessel) if (vessel == null || vessel == FlightGlobals.ActiveVessel)
{ {
continue; continue;
} }
   
  switch (vessel.vesselType)
  {
  case VesselType.Debris:
  case VesselType.Flag:
  case VesselType.Unknown:
  continue;
  }
   
log.AppendFormat("\nFetching best relay for vessel {0}", vessel); log.AppendFormat("\nFetching best relay for vessel {0}", vessel);
   
relay = vessel.GetBestRelay(); relay = vessel.GetBestRelay();
   
if (relay != null) if (relay != null)
{ {
log.AppendFormat("\n\tAdding useful relay {0}", relay); log.AppendFormat("\n\tAdding useful relay {0}", relay);
   
usefulRelays.Add(relay); usefulRelays.Add(relay);
} }
} }
   
activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel]; activeVesselRelays = RelayDatabase.Instance[FlightGlobals.ActiveVessel];
   
if (activeVesselRelays.Count > 0) if (activeVesselRelays.Count > 0)
{ {
bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel); bestActiveRelay = RelayDatabase.Instance.GetBestVesselRelay(FlightGlobals.ActiveVessel);
   
for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)  
{  
relay = activeVesselRelays[rIdx];  
   
// The best active relay will get checked with the other useful relays later.  
if (relay == null || relay == bestActiveRelay)  
{  
continue;  
}  
   
log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);  
   
relay.FindNearestRelay();  
}  
   
log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay); log.AppendFormat("\n\tAdding best active vessel relay {0} to usefulRelays", bestActiveRelay);
   
usefulRelays.Add(bestActiveRelay); usefulRelays.Add(bestActiveRelay);
} }
   
log.AppendFormat("\n\tDoing target searches for {0} useful relays", usefulRelays.Count); log.AppendFormat("\n\tDoing target searches for {0} useful relays", usefulRelays.Count);
   
for (int uIdx = 0; uIdx < usefulRelays.Count; uIdx++) for (int uIdx = 0; uIdx < usefulRelays.Count; uIdx++)
{ {
relay = usefulRelays[uIdx]; relay = usefulRelays[uIdx];
   
if (relay == null) if (relay == null)
{ {
continue; continue;
} }
   
log.AppendFormat("\n\tDoing target search for useful relay {0}", relay); log.AppendFormat("\n\tDoing target search for useful relay {0}", relay);
   
  relay.FindNearestRelay();
  }
   
  // Very last, find routes for the non-best relays on the active vessel.
  for (int rIdx = 0; rIdx < activeVesselRelays.Count; rIdx++)
  {
  relay = activeVesselRelays[rIdx];
   
  // The best active relay will get checked with the other useful relays later.
  if (relay == null || relay == bestActiveRelay)
  {
  continue;
  }
   
  log.AppendFormat("\nFinding nearest relay for active vessel relay {0}", relay);
   
relay.FindNearestRelay(); relay.FindNearestRelay();
} }
   
if (this.toolbarButton != null || this.appLauncherButton != null) if (this.toolbarButton != null || this.appLauncherButton != null)
{ {
log.Append("\nChecking active vessel relay status."); log.Append("\nChecking active vessel relay status.");
   
this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus(); this.currentConnectionStatus = FlightGlobals.ActiveVessel.GetConnectionStatus();
   
log.AppendFormat("\n\tcurrentConnectionStatus: {0}, setting texture to {1}", log.AppendFormat("\n\tcurrentConnectionStatus: {0}, setting texture to {1}",
this.currentConnectionStatus, this.currentConnectionTexture); this.currentConnectionStatus, this.currentConnectionTexture);
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.TexturePath = this.currentConnectionTexture; this.toolbarButton.TexturePath = this.currentConnectionTexture;
   
if (this.currentConnectionStatus == ConnectionStatus.None) if (this.currentConnectionStatus == ConnectionStatus.None)
{ {
if (!this.toolbarButton.Important) this.toolbarButton.Important = true; if (!this.toolbarButton.Important) this.toolbarButton.Important = true;
} }
else else
{ {
if (this.toolbarButton.Important) this.toolbarButton.Important = false; if (this.toolbarButton.Important) this.toolbarButton.Important = false;
} }
} }
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
this.appLauncherButton.SetTexture(this.currentAppLauncherTexture); this.appLauncherButton.SetTexture(this.currentAppLauncherTexture);
} }
} }
} }
   
log.Print(false); log.Print(false);
} }
   
private void OnDestroy() private void OnDestroy()
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
   
if (this.mapRenderer != null) if (this.mapRenderer != null)
{ {
GameObject.Destroy(this.mapRenderer); GameObject.Destroy(this.mapRenderer);
} }
   
if (this.toolbarButton != null) if (this.toolbarButton != null)
{ {
this.toolbarButton.Destroy(); this.toolbarButton.Destroy();
} }
   
if (this.appLauncherButton != null) if (this.appLauncherButton != null)
{ {
ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton); ApplicationLauncher.Instance.RemoveModApplication(this.appLauncherButton);
this.appLauncherButton = null; this.appLauncherButton = null;
} }
   
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChangeRequested);
GameEvents.onVesselChange.Remove(this.onVesselChange); GameEvents.onVesselChange.Remove(this.onVesselChange);
   
print("ARFlightController: Destroyed."); print("ARFlightController: Destroyed.");
} }
#endregion #endregion
   
private void buttonToggle() private void buttonToggle()
{ {
if (MapView.MapIsEnabled) if (MapView.MapIsEnabled)
{ {
ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines; ARConfiguration.PrettyLines = !ARConfiguration.PrettyLines;
} }
} }
   
#region Event Handlers #region Event Handlers
private void onSceneChangeRequested(GameScenes scene) private void onSceneChangeRequested(GameScenes scene)
{ {
print("ARFlightController: Requesting Destruction."); print("ARFlightController: Requesting Destruction.");
MonoBehaviour.Destroy(this); MonoBehaviour.Destroy(this);
} }
   
private void onVesselChange(Vessel vessel) private void onVesselChange(Vessel vessel)
{ {
InputLockManager.RemoveControlLock(this.lockID); InputLockManager.RemoveControlLock(this.lockID);
} }
#endregion #endregion
} }
} }
   
 Binary files a/GameData/AntennaRange/Textures/appLauncherIcon.png and b/GameData/AntennaRange/Textures/appLauncherIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png and b/GameData/AntennaRange/Textures/appLauncherIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png and b/GameData/AntennaRange/Textures/appLauncherIconSubOptimal.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIcon.png and b/GameData/AntennaRange/Textures/toolbarIcon.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconNoConnection.png and b/GameData/AntennaRange/Textures/toolbarIconNoConnection.png differ
 Binary files a/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png and b/GameData/AntennaRange/Textures/toolbarIconSubOptimal.png differ
// AntennaRange // AntennaRange
// //
// RelayDatabase.cs // RelayDatabase.cs
// //
// Copyright © 2014-2015, toadicus // Copyright © 2014-2015, toadicus
// All rights reserved. // All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// 1. Redistributions of source code must retain the above copyright notice, // 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// 2. Redistributions in binary form must reproduce the above copyright notice, // 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or other // this list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution. // materials provided with the distribution.
// //
// 3. Neither the name of the copyright holder nor the names of its contributors may be used // 3. Neither the name of the copyright holder nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior written permission. // to endorse or promote products derived from this software without specific prior written permission.
// //
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   
#pragma warning disable 1591 #pragma warning disable 1591
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase : Singleton<RelayDatabase> public class RelayDatabase : Singleton<RelayDatabase>
{ {
/* /*
* Instance members * Instance members
* */ * */
   
/* /*
* Fields * Fields
* */ * */
// Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects. // Vessel.id-keyed hash table of Part.GetHashCode()-keyed tables of relay objects.
private Dictionary<Guid, List<IAntennaRelay>> relayDatabase; private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
private Dictionary<Guid, IAntennaRelay> bestRelayTable; private Dictionary<Guid, IAntennaRelay> bestRelayTable;
   
// Vessel.id-keyed hash table of part counts, used for caching // Vessel.id-keyed hash table of part counts, used for caching
private Dictionary<Guid, int> vesselPartCountTable; private Dictionary<Guid, int> vesselPartCountTable;
   
private int cacheHits; private int cacheHits;
private int cacheMisses; private int cacheMisses;
   
/* /*
* Properties * Properties
* */ * */
// Gets the Part-hashed table of relays in a given vessel // Gets the Part-hashed table of relays in a given vessel
public IList<IAntennaRelay> this [Vessel vessel] public IList<IAntennaRelay> this [Vessel vessel]
{ {
get get
{ {
// If we don't have an entry for this vessel... // If we don't have an entry for this vessel...
if (!this.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel.id))
{ {
// ...Generate an entry for this vessel. // ...Generate an entry for this vessel.
this.AddVessel(vessel); this.AddVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) else if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count)
{ {
// ...Update the our vessel in the cache // ...Update the our vessel in the cache
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
this.cacheMisses++; this.cacheMisses++;
} }
// Otherwise, it's a hit // Otherwise, it's a hit
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
   
// Return the Part-hashed table of relays for this vessel // Return the Part-hashed table of relays for this vessel
return relayDatabase[vessel.id].AsReadOnly(); return relayDatabase[vessel.id].AsReadOnly();
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// Remove a vessel from the cache, if it exists. // Remove a vessel from the cache, if it exists.
public void DirtyVessel(Vessel vessel) public void DirtyVessel(Vessel vessel)
{ {
#if DEBUG #if DEBUG
Logging.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}", Logging.PostDebugMessage("RelayDatabase: Dirtying cache for vessel {0} in frame {1}",
vessel, new System.Diagnostics.StackTrace().ToString()); vessel, new System.Diagnostics.StackTrace().ToString());
#else #else
Logging.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName); Logging.PostLogMessage("RelayDatabase: Dirtying cache for vessel {0}", vessel.vesselName);
#endif #endif
   
this.relayDatabase.Remove(vessel.id); this.relayDatabase.Remove(vessel.id);
this.vesselPartCountTable.Remove(vessel.id); this.vesselPartCountTable.Remove(vessel.id);
this.bestRelayTable.Remove(vessel.id); this.bestRelayTable.Remove(vessel.id);
} }
   
public void ClearCache() public void ClearCache()
{ {
Logging.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache."); Logging.PostLogMessage("RelayDatabase: onSceneChange clearing entire cache.");
   
this.relayDatabase.Clear(); this.relayDatabase.Clear();
this.bestRelayTable.Clear(); this.bestRelayTable.Clear();
this.vesselPartCountTable.Clear(); this.vesselPartCountTable.Clear();
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.
public bool ContainsKey(Guid key) public bool ContainsKey(Guid key)
{ {
return this.relayDatabase.ContainsKey(key); return this.relayDatabase.ContainsKey(key);
} }
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel. // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
public bool ContainsKey(Vessel vessel) public bool ContainsKey(Vessel vessel)
{ {
return this.ContainsKey(vessel.id); return this.ContainsKey(vessel.id);
} }
   
public IAntennaRelay GetBestVesselRelay(Vessel vessel) public IAntennaRelay GetBestVesselRelay(Vessel vessel)
{ {
IAntennaRelay relay; IAntennaRelay relay;
if (this.bestRelayTable.TryGetValue(vessel.id, out relay)) if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
{ {
return relay; return relay;
} }
else else
{ {
var dump = this[vessel]; var dump = this[vessel];
return null; return null;
} }
} }
   
// Adds a vessel to the database // Adds a vessel to the database
// The return for this function isn't used yet, but seems useful for potential future API-uses // The return for this function isn't used yet, but seems useful for potential future API-uses
private bool AddVessel(Vessel vessel) private bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// ...post an error // ...post an error
Debug.LogWarning(string.Format( Debug.LogWarning(string.Format(
"{0}: Cannot add vessel '{1}' (id: {2}): Already in database.", "{0}: Cannot add vessel '{1}' (id: {2}): Already in database.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// ...and refuse to add // ...and refuse to add
return false; return false;
} }
// otherwise, add the vessel to our tables... // otherwise, add the vessel to our tables...
else else
{ {
// Build an empty table... // Build an empty table...
this.relayDatabase[vessel.id] = new List<IAntennaRelay>(); this.relayDatabase[vessel.id] = new List<IAntennaRelay>();
   
// Update the empty index // Update the empty index
this.UpdateVessel(vessel); this.UpdateVessel(vessel);
   
// Return success // Return success
return true; return true;
} }
} }
   
// Update the vessel's entry in the table // Update the vessel's entry in the table
private void UpdateVessel(Vessel vessel) private void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!this.ContainsKey(vessel)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.", "{0}: Update called for vessel '{1}' (id: {2}) not in database: vessel will be added.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; List<IAntennaRelay> vesselTable = this.relayDatabase[vessel.id];
   
// Actually build and assign the table // Actually build and assign the table
this.getVesselRelays(vessel, ref vesselTable); this.getVesselRelays(vessel, ref vesselTable);
// Set the part count // Set the part count
this.vesselPartCountTable[vessel.id] = vessel.Parts.Count; this.vesselPartCountTable[vessel.id] = vessel.Parts.Count;
} }
   
// Runs when a vessel is modified (or when we switch to one, to catch docking events) // Runs when a vessel is modified (or when we switch to one, to catch docking events)
public void onVesselEvent(Vessel vessel) public void onVesselEvent(Vessel vessel)
{ {
// If we have this vessel in our cache... // If we have this vessel in our cache...
if (this.ContainsKey(vessel)) if (this.ContainsKey(vessel))
{ {
// If our part counts disagree (such as if a part has been added or broken off, // If our part counts disagree (such as if a part has been added or broken off,
// or if we've just docked or undocked)... // or if we've just docked or undocked)...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' ({2}).", "{0}: dirtying cache for vessel '{1}' ({2}).",
this.GetType().Name, this.GetType().Name,
vessel.vesselName, vessel.vesselName,
vessel.id vessel.id
)); ));
   
// Dirty the cache (real vessels will never have negative part counts) // Dirty the cache (real vessels will never have negative part counts)
this.DirtyVessel(vessel); this.DirtyVessel(vessel);
} }
} }
} }
   
// Runs when the player requests a scene change, such as when changing vessels or leaving flight. // Runs when the player requests a scene change, such as when changing vessels or leaving flight.
private void onSceneChange(GameScenes scene) private void onSceneChange(GameScenes scene)
{ {
Logging.PostDebugMessage( Logging.PostDebugMessage(
"RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}", "RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}",
HighLogic.LoadedScene, HighLogic.LoadedScene,
scene, scene,
FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName FlightGlobals.ActiveVessel == null ? "null" : FlightGlobals.ActiveVessel.vesselName
); );
   
if (scene == GameScenes.FLIGHT) if (scene == GameScenes.FLIGHT)
{ {
if (scene == HighLogic.LoadedScene) if (scene == HighLogic.LoadedScene)
{ {
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
Logging.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.", Logging.PostDebugMessage("RelayDatabase: onSceneChange clearing {0} from cache.",
FlightGlobals.ActiveVessel.vesselName); FlightGlobals.ActiveVessel.vesselName);
   
this.onVesselEvent(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
} }
else else
{ {
this.ClearCache(); this.ClearCache();
} }
} }
} }
   
private void onGameLoaded(object data) private void onGameLoaded(object data)
{ {
this.ClearCache(); this.ClearCache();
} }
   
// Runs when parts are undocked // Runs when parts are undocked
private void onPartEvent(Part part) private void onPartEvent(Part part)
{ {
if (part != null && part.vessel != null) if (part != null && part.vessel != null)
{ {
this.onVesselEvent(part.vessel); this.onVesselEvent(part.vessel);
} }
} }
   
// Runs when parts are coupled, as in docking // Runs when parts are coupled, as in docking
private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
this.onPartEvent(data.from); this.onPartEvent(data.from);
this.onPartEvent(data.to); this.onPartEvent(data.to);
} }
   
// Produce a Part-hashed table of relays for the given vessel // Produce a Part-hashed table of relays for the given vessel
private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays) private void getVesselRelays(Vessel vessel, ref List<IAntennaRelay> relays)
{ {
// We're going to completely regen this table, so dump the current contents. // We're going to completely regen this table, so dump the current contents.
relays.Clear(); relays.Clear();
   
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Getting antenna relays from vessel {1}.", "{0}: Getting antenna relays from vessel {1}.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
double bestRelayRange = double.NegativeInfinity; double bestRelayRange = double.NegativeInfinity;
IAntennaRelay bestRelay = null; IAntennaRelay bestRelay = null;
IAntennaRelay relay; IAntennaRelay relay;
   
// If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly. // If the vessel is loaded, we can fetch modules implementing IAntennaRelay directly.
if (vessel.loaded) { if (vessel.loaded) {
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel {1} is loaded, searching for modules in loaded parts.", "{0}: vessel {1} is loaded, searching for modules in loaded parts.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
Part part; Part part;
for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++) for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
{ {
part = vessel.Parts[partIdx]; part = vessel.Parts[partIdx];
   
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
PartModule module; PartModule module;
for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++) for (int modIdx = 0; modIdx < part.Modules.Count; modIdx++)
{ {
module = part.Modules[modIdx]; module = part.Modules[modIdx];
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
relay = (module as IAntennaRelay); relay = (module as IAntennaRelay);
   
if (relay.maxTransmitDistance > bestRelayRange) if (relay.maxTransmitDistance > bestRelayRange)
{ {
bestRelayRange = relay.maxTransmitDistance; bestRelayRange = relay.maxTransmitDistance;
bestRelay = relay; bestRelay = relay;
} }
   
// ...add the module to the table // ...add the module to the table
relays.Add(relay); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
// If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots. // If the vessel is not loaded, we need to build ProtoAntennaRelays when we find relay ProtoPartSnapshots.
else else
{ {
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel {1} is not loaded, searching for modules in prototype parts.", "{0}: vessel {1} is not loaded, searching for modules in prototype parts.",
this.GetType().Name, this.GetType().Name,
vessel.vesselName vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
ProtoPartSnapshot pps; ProtoPartSnapshot pps;
for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++) for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
{ {
pps = vessel.protoVessel.protoPartSnapshots[ppsIdx]; pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
   
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Searching in protopartsnapshot {1}", "{0}: Searching in protopartsnapshot {1}",
this.GetType().Name, this.GetType().Name,
pps pps
)); ));
   
// ...Fetch the prefab, because it's more useful for what we're doing. // ...Fetch the prefab, because it's more useful for what we're doing.
Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab; Part partPrefab = PartLoader.getPartInfoByName(pps.partName).partPrefab;
   
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Got partPrefab {1} in protopartsnapshot {2}", "{0}: Got partPrefab {1} in protopartsnapshot {2}",
this.GetType().Name, this.GetType().Name,
partPrefab, partPrefab,
pps pps
)); ));
   
// ...loop through the PartModules in the prefab... // ...loop through the PartModules in the prefab...
PartModule module; PartModule module;
for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++) for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
{ {
module = partPrefab.Modules[modIdx]; module = partPrefab.Modules[modIdx];
   
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: Searching in partmodule {1}", "{0}: Searching in partmodule {1}",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: partmodule {1} is antennarelay", "{0}: partmodule {1} is antennarelay",
this.GetType().Name, this.GetType().Name,
module module
)); ));
   
relay = new ProtoAntennaRelay(module as IAntennaRelay, pps); relay = new ProtoAntennaRelay(module as IAntennaRelay, pps);
   
if (relay.maxTransmitDistance > bestRelayRange) if (relay.maxTransmitDistance > bestRelayRange)
{ {
bestRelayRange = relay.maxTransmitDistance; bestRelayRange = relay.maxTransmitDistance;
bestRelay = relay; bestRelay = relay;
} }
   
// ...build a new ProtoAntennaRelay and add it to the table // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(relay); relays.Add(relay);
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
this.bestRelayTable[vessel.id] = bestRelay; this.bestRelayTable[vessel.id] = bestRelay;
   
Logging.PostDebugMessage(string.Format( Logging.PostDebugMessage(string.Format(
"{0}: vessel '{1}' ({2}) has {3} transmitters.", "{0}: vessel '{1}' ({2}) has {3} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.vesselName, vessel.vesselName,
vessel.id, vessel.id,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
private RelayDatabase() private RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>(); this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
   
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselEvent); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselEvent); GameEvents.onVesselChange.Add(this.onVesselEvent);
GameEvents.onVesselDestroy.Add(this.onVesselEvent); GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
GameEvents.onPartCouple.Add(this.onFromPartToPartEvent); GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Add(this.onPartEvent); GameEvents.onPartUndock.Add(this.onPartEvent);
GameEvents.onGameStateLoad.Add(this.onGameLoaded); GameEvents.onGameStateLoad.Add(this.onGameLoaded);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselEvent); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselEvent); GameEvents.onVesselChange.Remove(this.onVesselEvent);
GameEvents.onVesselDestroy.Remove(this.onVesselEvent); GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent); GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
GameEvents.onPartUndock.Remove(this.onPartEvent); GameEvents.onPartUndock.Remove(this.onPartEvent);
GameEvents.onGameStateLoad.Remove(this.onGameLoaded); GameEvents.onGameStateLoad.Remove(this.onGameLoaded);
   
Logging.PostDebugMessage(this.GetType().Name + " destroyed."); Logging.PostDebugMessage(this.GetType().Name + " destroyed.");
   
KSPLog.print(string.Format( KSPLog.print(string.Format(
"{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}", "{0} destructed. Cache hits: {1}, misses: {2}, hit rate: {3:P1}",
this.GetType().Name, this.GetType().Name,
this.cacheHits, this.cacheHits,
this.cacheMisses, this.cacheMisses,
(float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits) (float)this.cacheHits / (float)(this.cacheMisses + this.cacheHits)
)); ));
} }
   
#if DEBUG #if DEBUG
public void Dump() public void Dump()
{ {
StringBuilder sb = Tools.GetStringBuilder(); using (ToadicusTools.Text.PooledStringBuilder sb = ToadicusTools.Text.PooledStringBuilder.Get())
  {
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
var dbEnum = this.relayDatabase.GetEnumerator(); var dbEnum = this.relayDatabase.GetEnumerator();
IList<IAntennaRelay> vesselRelays; IList<IAntennaRelay> vesselRelays;
while (dbEnum.MoveNext()) while (dbEnum.MoveNext())
{ {
sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key); sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
   
vesselRelays = dbEnum.Current.Value; vesselRelays = dbEnum.Current.Value;
IAntennaRelay relay; IAntennaRelay relay;
for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++) for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
{ {
relay = vesselRelays[rIdx]; relay = vesselRelays[rIdx];
sb.AppendFormat("\n\t{0}", relay.ToString()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Logging.PostDebugMessage(sb.ToString()); Logging.PostDebugMessage(sb.ToString());
  }
Tools.PutStringBuilder(sb);  
} }
#endif #endif
} }
} }