ModuleDB: Added new System.Object overloads for use with reflection, and some debug code.
ModuleDB: Added new System.Object overloads for use with reflection, and some debug code.

// ModuleDB // ModuleDB
// //
// ModuleDB.cs // ModuleDB.cs
// //
// Copyright © 2014, toadicus // Copyright © 2014, 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.
// //
// 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.
   
using KSP; using KSP;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ToadicusTools; using ToadicusTools;
using UnityEngine; using UnityEngine;
   
namespace ModuleDB namespace ModuleDB
{ {
/// <summary> /// <summary>
/// A generic, caching database of PartModule derivatives keyed by Vessel and Part. /// A generic, caching database of PartModule derivatives keyed by Vessel and Part.
/// </summary> /// </summary>
public class ModuleDB<T> : IModuleDB<T> public class ModuleDB<T> : IModuleDB<T>
where T : PartModule where T : PartModule
{ {
private static IModuleDB<T> _instance; private static IModuleDB<T> _instance;
   
/// <summary> /// <summary>
/// Gets the ModuleDB instance for the specified type /// Gets the ModuleDB instance for the specified type
/// </summary> /// </summary>
/// <value>The ModuleDB instance for the specified type</value> /// <value>The ModuleDB instance for the specified type</value>
public static IModuleDB<T> Instance public static IModuleDB<T> Instance
{ {
get get
{ {
if (_instance == null) if (_instance == null)
{ {
_instance = new ModuleDB<T>(); _instance = new ModuleDB<T>();
  #if DEBUG
  Debug.Log(string.Format("[ModuleDB<{0}>]: Built new instance {1}.",
  typeof(T).Name,
  _instance
  ));
  #endif
} }
   
return _instance; return _instance;
} }
} }
   
private Dictionary<Guid, Dictionary<uint, List<T>>> vesselPartModuleDB; private Dictionary<Guid, Dictionary<uint, List<T>>> vesselPartModuleDB;
   
private Dictionary<Guid, List<T>> vesselModuleTable; private Dictionary<Guid, List<T>> vesselModuleTable;
   
private Guid editorVesselID; private Guid editorVesselID;
   
#if BENCH #if BENCH
public int cacheHits { get; protected set; } public int cacheHits { get; protected set; }
public int cacheMisses { get; protected set; } public int cacheMisses { get; protected set; }
#endif #endif
   
private ModuleDB() private ModuleDB()
{ {
// Initialize the caches. // Initialize the caches.
vesselPartModuleDB = new Dictionary<Guid, Dictionary<uint, List<T>>>(); vesselPartModuleDB = new Dictionary<Guid, Dictionary<uint, List<T>>>();
vesselModuleTable = new Dictionary<Guid, List<T>>(); vesselModuleTable = new Dictionary<Guid, List<T>>();
   
editorVesselID = new Guid( editorVesselID = new Guid(
new byte[] { 209, 101, 40, 249, 114, 36, 27, 95, 191, 207, 7, 197, 51, 109, 223, 212 } new byte[] { 209, 101, 40, 249, 114, 36, 27, 95, 191, 207, 7, 197, 51, 109, 223, 212 }
); );
   
// Subscribe to events to keep the cache fresh. // Subscribe to events to keep the cache fresh.
GameEvents.onVesselWasModified.Add(onVesselEvent); GameEvents.onVesselWasModified.Add(onVesselEvent);
GameEvents.onVesselChange.Add(onVesselEvent); GameEvents.onVesselChange.Add(onVesselEvent);
GameEvents.onVesselDestroy.Add(onVesselEvent); GameEvents.onVesselDestroy.Add(onVesselEvent);
GameEvents.onGameSceneLoadRequested.Add(onSceneChange); GameEvents.onGameSceneLoadRequested.Add(onSceneChange);
GameEvents.onPartUndock.Add(onPartEvent); GameEvents.onPartUndock.Add(onPartEvent);
GameEvents.onPartCouple.Add(onFromPartToPartEvent); GameEvents.onPartCouple.Add(onFromPartToPartEvent);
   
#if BENCH #if BENCH
this.cacheHits = 0; this.cacheHits = 0;
this.cacheMisses = 0; this.cacheMisses = 0;
  #endif
   
  #if DEBUG
  Debug.Log(string.Format("[ModuleDB<{0}>]: Constructed.",
  this.GetType().Name));
#endif #endif
} }
   
// Called for a subject Vessel when destroyed, changed, or modified // Called for a subject Vessel when destroyed, changed, or modified
private void onVesselEvent(Vessel vessel) private void onVesselEvent(Vessel vessel)
{ {
// If the deep cache contains the Vessel... // If the deep cache contains the Vessel...
if (vesselPartModuleDB.ContainsKey(vessel.id)) if (vesselPartModuleDB.ContainsKey(vessel.id))
{ {
// ...remove it from the deep cache. // ...remove it from the deep cache.
vesselPartModuleDB.Remove(vessel.id); vesselPartModuleDB.Remove(vessel.id);
} }
   
// If the shallow cache contains the Vessel... // If the shallow cache contains the Vessel...
if (vesselModuleTable.ContainsKey(vessel.id)) if (vesselModuleTable.ContainsKey(vessel.id))
{ {
// ..remove it fromt he shallow cache. // ..remove it fromt he shallow cache.
vesselModuleTable.Remove(vessel.id); vesselModuleTable.Remove(vessel.id);
} }
} }
   
// Called when a scene changed is requested // Called when a scene changed is requested
private void onSceneChange(GameScenes scene) private void onSceneChange(GameScenes scene)
{ {
// If there is an active vessel when changing scenes... // If there is an active vessel when changing scenes...
if (FlightGlobals.ActiveVessel != null) if (FlightGlobals.ActiveVessel != null)
{ {
// ...remove it from the caches. // ...remove it from the caches.
onVesselEvent(FlightGlobals.ActiveVessel); onVesselEvent(FlightGlobals.ActiveVessel);
} }
   
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
if (vesselPartModuleDB.ContainsKey(this.editorVesselID)) if (vesselPartModuleDB.ContainsKey(this.editorVesselID))
{ {
vesselPartModuleDB.Remove(this.editorVesselID); vesselPartModuleDB.Remove(this.editorVesselID);
} }
   
if (vesselModuleTable.ContainsKey(this.editorVesselID)) if (vesselModuleTable.ContainsKey(this.editorVesselID))
{ {
vesselModuleTable.Remove(this.editorVesselID); vesselModuleTable.Remove(this.editorVesselID);
} }
} }
#if BENCH #if BENCH
if (scene == GameScenes.MAINMENU) if (scene == GameScenes.MAINMENU)
{ {
int cacheSwings = this.cacheHits + this.cacheMisses; int cacheSwings = this.cacheHits + this.cacheMisses;
KSPLog.print(string.Format("ModuleDB<{4}> Destructing. Cache hits: {0} ({1}%), cache misses: {2} ({3}%).", KSPLog.print(string.Format("ModuleDB<{4}> Destructing. Cache hits: {0} ({1}%), cache misses: {2} ({3}%).",
this.cacheHits, (float)this.cacheHits / (float)cacheSwings * 100f, this.cacheHits, (float)this.cacheHits / (float)cacheSwings * 100f,
this.cacheMisses, (float)this.cacheMisses / (float)cacheSwings * 100f, this.cacheMisses, (float)this.cacheMisses / (float)cacheSwings * 100f,
typeof(T).Name typeof(T).Name
)); ));
} }
#endif #endif
} }
   
// Called for a subject Part when undocked // Called for a subject Part when undocked
private void onPartEvent(Part part) private void onPartEvent(Part part)
{ {
// If the Part's Vessel is defined... // If the Part's Vessel is defined...
if (part.vessel != null) if (part.vessel != null)
{ {
// ...remove it from the caches. // ...remove it from the caches.
onVesselEvent(part.vessel); onVesselEvent(part.vessel);
} }
} }
   
// Called for two subject Parts when coupled // Called for two subject Parts when coupled
private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data) private void onFromPartToPartEvent(GameEvents.FromToAction<Part, Part> data)
{ {
// Remove both the from and the to Parts fromt he caches. // Remove both the from and the to Parts fromt he caches.
onPartEvent(data.from); onPartEvent(data.from);
onPartEvent(data.to); onPartEvent(data.to);
} }
   
   
// Gets a flat list of all modules of type T in the given Vessel. Returns an empty list if none exist. // Gets a flat list of all modules of type T in the given Vessel. Returns an empty list if none exist.
public List<T> getModules(Vessel vessel) public List<T> getModules(Vessel vessel)
{ {
// If the vessel's Parts list is defined and includes any Parts... // If the vessel's Parts list is defined and includes any Parts...
if (vessel.Parts != null && vessel.Parts.Count > 0) if (vessel.Parts != null && vessel.Parts.Count > 0)
{ {
// ...and if the vessel is not in the shallow cache... // ...and if the vessel is not in the shallow cache...
if (!vesselModuleTable.ContainsKey(vessel.id)) if (!vesselModuleTable.ContainsKey(vessel.id))
{ {
#if BENCH #if BENCH
this.cacheMisses++; this.cacheMisses++;
#endif #endif
// ...create a list flat list of modules // ...create a list flat list of modules
List<T> modulesInVessel = new List<T>(); List<T> modulesInVessel = new List<T>();
   
// ...loop through the Vessel's Parts... // ...loop through the Vessel's Parts...
foreach (Part part in vessel.Parts) foreach (Part part in vessel.Parts)
{ {
// ...loop through each Part's Modules... // ...loop through each Part's Modules...
foreach (T module in getModules(part)) foreach (T module in getModules(part))
{ {
// ...and add each matching Module to the new list // ...and add each matching Module to the new list
modulesInVessel.Add(module); modulesInVessel.Add(module);
} }
} }
   
// ...set the shallow cache entry to the new list // ...set the shallow cache entry to the new list
vesselModuleTable[vessel.id] = modulesInVessel; vesselModuleTable[vessel.id] = modulesInVessel;
} }
#if BENCH #if BENCH
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
#endif #endif
   
// ...return the shallow cache for the queried Vessel // ...return the shallow cache for the queried Vessel
return vesselModuleTable[vessel.id]; return vesselModuleTable[vessel.id];
} }
   
// Otherwise, return an empty list. // Otherwise, return an empty list.
return new List<T>(); return new List<T>();
} }
   
// Gets a flat list of all modules of type T in the given Part. Returns an empty list if none exist. // Gets a flat list of all modules of type T in the given Part. Returns an empty list if none exist.
public List<T> getModules(Part part) public List<T> getModules(Part part)
{ {
Guid id; Guid id;
   
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
id = this.editorVesselID; id = this.editorVesselID;
} }
// If the Part's Vessel is defined... // If the Part's Vessel is defined...
else if (part.vessel != null) else if (part.vessel != null)
{ {
id = part.vessel.id; id = part.vessel.id;
} }
else else
{ {
// Otherwise, return an empty list // Otherwise, return an empty list
return new List<T>(); return new List<T>();
} }
   
// ...and if the Vessel is not in the deep cache... // ...and if the Vessel is not in the deep cache...
if (!vesselPartModuleDB.ContainsKey(id)) if (!vesselPartModuleDB.ContainsKey(id))
{ {
// ...create a new table for the Vessel in the deep cache. // ...create a new table for the Vessel in the deep cache.
vesselPartModuleDB[id] = new Dictionary<uint, List<T>>(); vesselPartModuleDB[id] = new Dictionary<uint, List<T>>();
} }
   
// ...and if the Part is not in the Vessel's table in the deep cache... // ...and if the Part is not in the Vessel's table in the deep cache...
if (!vesselPartModuleDB[id].ContainsKey(part.uid)) if (!vesselPartModuleDB[id].ContainsKey(part.uid))
{ {
#if BENCH #if BENCH
this.cacheMisses++; this.cacheMisses++;
#endif #endif
   
// ...create a flat list of modules // ...create a flat list of modules
List<T> modulesInPart = new List<T>(); List<T> modulesInPart = new List<T>();
   
// ...loop through the Part's modules... // ...loop through the Part's modules...
foreach (PartModule module in part.Modules) foreach (PartModule module in part.Modules)
{ {
// ...if any module matches... // ...if any module matches...
if (module is T) if (module is T)
{ {
// ...add it to the list // ...add it to the list
modulesInPart.Add((T)module); modulesInPart.Add((T)module);
} }
} }
   
// ...set the deep cache entry to the new list // ...set the deep cache entry to the new list
vesselPartModuleDB[id][part.uid] = modulesInPart; vesselPartModuleDB[id][part.uid] = modulesInPart;
} }
#if BENCH #if BENCH
else else
{ {
this.cacheHits++; this.cacheHits++;
} }
#endif #endif
   
// ...return the deep cache entry for the queried Part. // ...return the deep cache entry for the queried Part.
return vesselPartModuleDB[id][part.uid]; return vesselPartModuleDB[id][part.uid];
} }
   
  public List<T> getModules(System.Object obj)
  {
  if (obj is Vessel)
  {
  return this.getModules(obj as Vessel);
  }
  else if (obj is Part)
  {
  return this.getModules(obj as Part);
  }
  else
  {
  throw new ArgumentException(
  string.Format(
  "[ModuleDB<{0}>].getModules is only defined for Vessel and Part objects.",
  typeof(T).Name
  )
  );
  }
  }
   
// Returns true if the given Vessel exists in the deep cache, false otherwise. // Returns true if the given Vessel exists in the deep cache, false otherwise.
public bool inDeepCache(Vessel vessel) public bool inDeepCache(Vessel vessel)
{ {
return vesselPartModuleDB.ContainsKey(HighLogic.LoadedSceneIsEditor ? this.editorVesselID : vessel.id); return vesselPartModuleDB.ContainsKey(HighLogic.LoadedSceneIsEditor ? this.editorVesselID : vessel.id);
} }
   
// Returns true if the given Part exists in the deep cache, false otherwise. // Returns true if the given Part exists in the deep cache, false otherwise.
public bool inDeepCache(Part part) public bool inDeepCache(Part part)
{ {
if (HighLogic.LoadedSceneIsEditor) if (HighLogic.LoadedSceneIsEditor)
{ {
return vesselPartModuleDB.ContainsKey(this.editorVesselID) && return vesselPartModuleDB.ContainsKey(this.editorVesselID) &&
vesselPartModuleDB[this.editorVesselID].ContainsKey(part.uid); vesselPartModuleDB[this.editorVesselID].ContainsKey(part.uid);
} }
else else
{ {
if (part.vessel == null) if (part.vessel == null)
{ {
return false; return false;
} }
   
return inDeepCache(part.vessel) && vesselPartModuleDB[part.vessel.id].ContainsKey(part.uid); return inDeepCache(part.vessel) && vesselPartModuleDB[part.vessel.id].ContainsKey(part.uid);
} }
} }
   
  public bool inDeepCache(System.Object obj)
  {
  if (obj is Vessel)
  {
  return this.inDeepCache(obj as Vessel);
  }
  else if (obj is Part)
  {
  return this.inDeepCache(obj as Part);
  }
  else
  {
  throw new ArgumentException(
  string.Format(
  "[ModuleDB<{0}>].inDeepCache is only defined for Vessel and Part objects.",
  typeof(T).Name
  )
  );
  }
  }
   
// Returns true if the given Vessel exists in the shallow cache, false otherwise. // Returns true if the given Vessel exists in the shallow cache, false otherwise.
public bool inShallowCache(Vessel vessel) public bool inShallowCache(Vessel vessel)
{ {
return vesselModuleTable.ContainsKey(HighLogic.LoadedSceneIsEditor ? this.editorVesselID : vessel.id); return vesselModuleTable.ContainsKey(HighLogic.LoadedSceneIsEditor ? this.editorVesselID : vessel.id);
  }
   
  public bool inShallowCache(System.Object obj)
  {
  if (obj is Vessel)
  {
  return this.inShallowCache(obj as Vessel);
  }
  else
  {
  throw new ArgumentException(
  string.Format(
  "[ModuleDB<{0}>].inShallowCache is only defined for Vessel objects.",
  typeof(T).Name
  )
  );
  }
} }
   
#if BENCH #if BENCH
~ModuleDB() ~ModuleDB()
{ {
int cacheSwings = this.cacheHits + this.cacheMisses; int cacheSwings = this.cacheHits + this.cacheMisses;
KSPLog.print(string.Format("ModuleDB<{4}> Destructing. Cache hits: {0} ({1}%), cache misses: {2} ({3}%).", KSPLog.print(string.Format("ModuleDB<{4}> Destructing. Cache hits: {0} ({1}%), cache misses: {2} ({3}%).",
this.cacheHits, (float)this.cacheHits / (float)cacheSwings * 100f, this.cacheHits, (float)this.cacheHits / (float)cacheSwings * 100f,
this.cacheMisses, (float)this.cacheMisses / (float)cacheSwings * 100f, this.cacheMisses, (float)this.cacheMisses / (float)cacheSwings * 100f,
typeof(T).Name typeof(T).Name
)); ));
} }
#endif #endif
} }
} }