Updated the copyright year.
[AntennaRange.git] / RelayDatabase.cs
blob:a/RelayDatabase.cs -> blob:b/RelayDatabase.cs
// 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
   
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.
protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase; private Dictionary<Guid, List<IAntennaRelay>> relayDatabase;
  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
protected Dictionary<Guid, int> vesselPartCountTable; private Dictionary<Guid, int> vesselPartCountTable;
   
// Vessel.id-keyed hash table of booleans to track what vessels have been checked so far this time. private int cacheHits;
public Dictionary<Guid, bool> CheckedVesselsTable; private int cacheMisses;
   
protected int cacheHits;  
protected 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 Dictionary<int, 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]; return relayDatabase[vessel.id].AsReadOnly();
} }
} }
   
/* /*
* Methods * Methods
* */ * */
  // 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.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.
  public bool ContainsKey(Guid key)
  {
  return this.relayDatabase.ContainsKey(key);
  }
   
  // Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.
  public bool ContainsKey(Vessel vessel)
  {
  return this.ContainsKey(vessel.id);
  }
   
  public IAntennaRelay GetBestVesselRelay(Vessel vessel)
  {
  IAntennaRelay relay;
  if (this.bestRelayTable.TryGetValue(vessel.id, out relay))
  {
  return relay;
  }
  else
  {
  var dump = this[vessel];
  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
public 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 Dictionary<int, 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
public 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
)); ));
} }
   
Dictionary<int, 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;
}  
   
// Remove a vessel from the cache, if it exists.  
public void DirtyVessel(Vessel vessel)  
{  
if (this.relayDatabase.ContainsKey(vessel.id))  
{  
this.relayDatabase.Remove(vessel.id);  
}  
if (this.vesselPartCountTable.ContainsKey(vessel.id))  
{  
this.vesselPartCountTable.Remove(vessel.id);  
}  
}  
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel id.  
public bool ContainsKey(Guid key)  
{  
return this.relayDatabase.ContainsKey(key);  
}  
   
// Returns true if both the relayDatabase and the vesselPartCountDB contain the vessel.  
public bool ContainsKey(Vessel vessel)  
{  
return this.ContainsKey(vessel.id);  
} }
   
// 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)
{ {
Tools.PostDebugMessage(string.Format( Tools.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.
public void onSceneChange(GameScenes scene) private void onSceneChange(GameScenes scene)
{ {
// If the active vessel is a real thing... Tools.PostDebugMessage(
if (FlightGlobals.ActiveVessel != null) "RelayDatabase: caught onSceneChangeRequested in scene {0} to scene {1}. ActiveVessel is {2}",
{ HighLogic.LoadedScene,
// ... dirty its cache scene,
this.onVesselEvent(FlightGlobals.ActiveVessel); 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 // Runs when parts are undocked
public 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
public 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
protected void getVesselRelays(Vessel vessel, ref Dictionary<int, 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();
   
Tools.PostDebugMessage(string.Format( Tools.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;
  IAntennaRelay bestRelay = null;
  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) {
Tools.PostDebugMessage(string.Format( Tools.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...
foreach (Part part in vessel.Parts) Part part;
{ for (int partIdx = 0; partIdx < vessel.Parts.Count; partIdx++)
  {
  part = vessel.Parts[partIdx];
   
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
foreach (PartModule module in part.Modules) PartModule module;
  for (int modIdx = 0; modIdx < part.Modules.Count; 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);
   
  if (relay.maxTransmitDistance > bestRelayRange)
  {
  bestRelayRange = relay.maxTransmitDistance;
  bestRelay = relay;
  }
   
// ...add the module to the table // ...add the module to the table
relays.Add(part.GetHashCode(), module as IAntennaRelay); 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
{ {
Tools.PostDebugMessage(string.Format( Tools.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...
foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots) ProtoPartSnapshot pps;
{ for (int ppsIdx = 0; ppsIdx < vessel.protoVessel.protoPartSnapshots.Count; ppsIdx++)
  {
  pps = vessel.protoVessel.protoPartSnapshots[ppsIdx];
   
Tools.PostDebugMessage(string.Format( Tools.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;
   
Tools.PostDebugMessage(string.Format( Tools.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...
foreach (PartModule module in partPrefab.Modules) PartModule module;
  for (int modIdx = 0; modIdx < partPrefab.Modules.Count; modIdx++)
{ {
  module = partPrefab.Modules[modIdx];
   
Tools.PostDebugMessage(string.Format( Tools.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)
{ {
Tools.PostDebugMessage(string.Format( Tools.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);
   
  if (relay.maxTransmitDistance > bestRelayRange)
  {
  bestRelayRange = relay.maxTransmitDistance;
  bestRelay = relay;
  }
   
// ...build a new ProtoAntennaRelay and add it to the table // ...build a new ProtoAntennaRelay and add it to the table
relays.Add(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps)); 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;
   
Tools.PostDebugMessage(string.Format( Tools.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
protected RelayDatabase() private RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, List<IAntennaRelay>>();
  this.bestRelayTable = new Dictionary<Guid, IAntennaRelay>();
this.vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
this.CheckedVesselsTable = new Dictionary<Guid, bool>();  
   
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);
} }
   
~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);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Tools.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 = new StringBuilder(); StringBuilder sb = Tools.GetStringBuilder();
   
sb.Append("Dumping RelayDatabase:"); sb.Append("Dumping RelayDatabase:");
   
foreach (Guid id in this.relayDatabase.Keys) var dbEnum = this.relayDatabase.GetEnumerator();
{ IList<IAntennaRelay> vesselRelays;
sb.AppendFormat("\nVessel {0}:", id); while (dbEnum.MoveNext())
  {
foreach (IAntennaRelay relay in this.relayDatabase[id].Values) sb.AppendFormat("\nVessel {0}:", dbEnum.Current.Key);
{  
  vesselRelays = dbEnum.Current.Value;
  IAntennaRelay relay;
  for (int rIdx = 0; rIdx < vesselRelays.Count; rIdx++)
  {
  relay = vesselRelays[rIdx];
sb.AppendFormat("\n\t{0}", relay.ToString()); sb.AppendFormat("\n\t{0}", relay.ToString());
} }
} }
   
Tools.PostDebugMessage(sb.ToString()); Tools.PostDebugMessage(sb.ToString());
   
  Tools.PutStringBuilder(sb);
} }
#endif #endif
} }
} }