RelayDatabase:
[AntennaRange.git] / RelayDatabase.cs
blob:a/RelayDatabase.cs -> blob:b/RelayDatabase.cs
// AntennaRange © 2014 toadicus // AntennaRange © 2014 toadicus
// //
// 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/
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEngine; using UnityEngine;
   
namespace AntennaRange namespace AntennaRange
{ {
public class RelayDatabase public class RelayDatabase
{ {
/* /*
* Static members * Static members
* */ * */
// Singleton storage // Singleton storage
protected static RelayDatabase _instance; protected static RelayDatabase _instance;
// Gets the singleton // Gets the singleton
public static RelayDatabase Instance public static RelayDatabase Instance
{ {
get get
{ {
if (_instance == null) if (_instance == null)
{ {
_instance = new RelayDatabase(); _instance = new RelayDatabase();
} }
   
return _instance; return _instance;
} }
} }
   
/* /*
* 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; protected Dictionary<Guid, Dictionary<int, IAntennaRelay>> relayDatabase;
   
// 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; protected 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;
   
/* /*
* 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 Dictionary<int, 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);
} }
// If our part count disagrees with the vessel's part count... // If our part count disagrees with the vessel's part count...
if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count) 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);
} }
   
// 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];
} }
} }
   
/* /*
* Methods * Methods
* */ * */
// 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) public bool AddVessel(Vessel vessel)
{ {
// If this vessel is already here... // If this vessel is already here...
if (relayDatabase.ContainsKey(vessel.id)) 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.name, 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 Dictionary<int, 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) public void UpdateVessel(Vessel vessel)
{ {
// Squak if the database doesn't have the vessel // Squak if the database doesn't have the vessel
if (!relayDatabase.ContainsKey(vessel.id)) if (!this.ContainsKey(vessel))
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
"{0}: Update called 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.name, vessel.vesselName,
vessel.id vessel.id
)); ));
} }
   
Dictionary<int, IAntennaRelay> vesselTable = this.relayDatabase[vessel.id]; Dictionary<int, 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;
} }
   
// 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);
} }
   
// 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 onVesselWasModified(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) if (this.vesselPartCountTable[vessel.id] != vessel.Parts.Count || vessel.loaded)
{ {
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: dirtying cache for vessel '{1}' (id: {2}).", "{0}: dirtying cache for vessel '{1}' (id: {2}).",
this.GetType().Name, this.GetType().Name,
vessel.name, 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.vesselPartCountTable[vessel.id] = -1; this.vesselPartCountTable[vessel.id] = -1;
} }
} }
} }
   
// 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) public void onSceneChange(GameScenes scene)
{ {
// If the active vessel is a real thing... // If the active vessel is a real thing...
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
// ... dirty its cache // ... dirty its cache
this.onVesselWasModified(FlightGlobals.ActiveVessel); this.onVesselEvent(FlightGlobals.ActiveVessel);
} }
  }
   
  // Runs when parts are undocked
  public void onPartEvent(Part part)
  {
  if (part != null && part.vessel != null)
  {
  this.onVesselEvent(part.vessel);
  }
  }
   
  // Runs when parts are coupled, as in docking
  public void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
  {
  this.onPartEvent(data.from);
  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) protected void getVesselRelays(Vessel vessel, ref Dictionary<int, 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.name vessel.vesselName
)); ));
   
// 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.name vessel.vesselName
)); ));
   
// Loop through the Parts in the Vessel... // Loop through the Parts in the Vessel...
foreach (Part part in vessel.Parts) foreach (Part part in vessel.Parts)
{ {
// ...loop through the PartModules in the Part... // ...loop through the PartModules in the Part...
foreach (PartModule module in part.Modules) foreach (PartModule module in part.Modules)
{ {
// ...if the module is a relay... // ...if the module is a relay...
if (module is IAntennaRelay) if (module is IAntennaRelay)
{ {
// ...add the module to the table // ...add the module to the table
relays.Add(part.GetHashCode(), module as IAntennaRelay); relays.Add(part.GetHashCode(), module as IAntennaRelay);
// ...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.name vessel.vesselName
)); ));
   
// Loop through the ProtoPartModuleSnapshots in the Vessel... // Loop through the ProtoPartModuleSnapshots in the Vessel...
foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots) foreach (ProtoPartSnapshot pps in vessel.protoVessel.protoPartSnapshots)
{ {
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) foreach (PartModule module in partPrefab.Modules)
{ {
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
)); ));
   
// ...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(pps.GetHashCode(), new ProtoAntennaRelay(module as IAntennaRelay, pps));
// ...neglect relay objects after the first in each part. // ...neglect relay objects after the first in each part.
break; break;
} }
} }
} }
} }
   
Tools.PostDebugMessage(string.Format( Tools.PostDebugMessage(string.Format(
"{0}: vessel '{1}' has {2} transmitters.", "{0}: vessel '{1}' has {2} transmitters.",
"IAntennaRelay", "IAntennaRelay",
vessel.name, vessel.vesselName,
relays.Count relays.Count
)); ));
} }
   
// Construct the singleton // Construct the singleton
protected RelayDatabase() protected RelayDatabase()
{ {
// Initialize the databases // Initialize the databases
relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>(); this.relayDatabase = new Dictionary<Guid, Dictionary<int, IAntennaRelay>>();
vesselPartCountTable = new Dictionary<Guid, int>(); this.vesselPartCountTable = new Dictionary<Guid, int>();
  this.CheckedVesselsTable = new Dictionary<Guid, bool>();
   
// Subscribe to some events // Subscribe to some events
GameEvents.onVesselWasModified.Add(this.onVesselWasModified); GameEvents.onVesselWasModified.Add(this.onVesselEvent);
GameEvents.onVesselChange.Add(this.onVesselWasModified); GameEvents.onVesselChange.Add(this.onVesselEvent);
  GameEvents.onVesselDestroy.Add(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Add(this.onSceneChange);
  GameEvents.onPartCouple.Add(this.onFromPartToPartEvent);
} }
   
~RelayDatabase() ~RelayDatabase()
{ {
// Unsubscribe from the events // Unsubscribe from the events
GameEvents.onVesselWasModified.Remove(this.onVesselWasModified); GameEvents.onVesselWasModified.Remove(this.onVesselEvent);
GameEvents.onVesselChange.Remove(this.onVesselWasModified); GameEvents.onVesselChange.Remove(this.onVesselEvent);
  GameEvents.onVesselDestroy.Remove(this.onVesselEvent);
GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange); GameEvents.onGameSceneLoadRequested.Remove(this.onSceneChange);
  GameEvents.onPartCouple.Remove(this.onFromPartToPartEvent);
   
Tools.PostDebugMessage(this.GetType().Name + " destroyed."); Tools.PostDebugMessage(this.GetType().Name + " destroyed.");
} }
} }
} }