EngineSim objects are now pooled.
[VesselSimulator.git] / KerbalEngineer / VesselSimulator / PartSim.cs
blob:a/KerbalEngineer/VesselSimulator/PartSim.cs -> blob:b/KerbalEngineer/VesselSimulator/PartSim.cs
// //
// Kerbal Engineer Redux // Kerbal Engineer Redux
// //
// Copyright (C) 2014 CYBUTEK // Copyright (C) 2014 CYBUTEK
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
   
#region Using Directives #region Using Directives
   
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
   
using KerbalEngineer.Extensions; using KerbalEngineer.Extensions;
   
using UnityEngine; using UnityEngine;
   
#endregion #endregion
   
namespace KerbalEngineer.VesselSimulator namespace KerbalEngineer.VesselSimulator
{ {
using CompoundParts; using CompoundParts;
   
public class PartSim public class PartSim
{ {
private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>(); private readonly List<AttachNodeSim> attachNodes = new List<AttachNodeSim>();
public Vector3d centerOfMass; public Vector3d centerOfMass;
public double baseMass = 0d; public double baseMass = 0d;
public double cost; public double cost;
public int decoupledInStage; public int decoupledInStage;
public bool fuelCrossFeed; public bool fuelCrossFeed;
public List<PartSim> fuelTargets = new List<PartSim>(); public List<PartSim> fuelTargets = new List<PartSim>();
public bool hasModuleEngines; public bool hasModuleEngines;
public bool hasModuleEnginesFX; public bool hasModuleEnginesFX;
public bool hasMultiModeEngine; public bool hasMultiModeEngine;
   
public bool hasVessel; public bool hasVessel;
public String initialVesselName; public String initialVesselName;
public int inverseStage; public int inverseStage;
public bool isDecoupler; public bool isDecoupler;
public bool isEngine; public bool isEngine;
public bool isFuelLine; public bool isFuelLine;
public bool isFuelTank; public bool isFuelTank;
public bool isLanded; public bool isLanded;
public bool isNoPhysics; public bool isNoPhysics;
public bool isSepratron; public bool isSepratron;
public bool localCorrectThrust; public bool localCorrectThrust;
public String name; public String name;
public String noCrossFeedNodeKey; public String noCrossFeedNodeKey;
public PartSim parent; public PartSim parent;
public AttachModes parentAttach; public AttachModes parentAttach;
public Part part; // This is only set while the data structures are being initialised public Part part; // This is only set while the data structures are being initialised
public int partId = 0; public int partId = 0;
public ResourceContainer resourceDrains = new ResourceContainer(); public ResourceContainer resourceDrains = new ResourceContainer();
public ResourceContainer resourceFlowStates = new ResourceContainer(); public ResourceContainer resourceFlowStates = new ResourceContainer();
public ResourceContainer resources = new ResourceContainer(); public ResourceContainer resources = new ResourceContainer();
public double startMass = 0d; public double startMass = 0d;
public String vesselName; public String vesselName;
public VesselType vesselType; public VesselType vesselType;
   
public PartSim(Part thePart, int id, double atmosphere, LogMsg log) public PartSim(Part thePart, int id, double atmosphere, LogMsg log)
{ {
this.part = thePart; this.part = thePart;
this.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset); this.centerOfMass = thePart.transform.TransformPoint(thePart.CoMOffset);
this.partId = id; this.partId = id;
this.name = this.part.partInfo.name; this.name = this.part.partInfo.name;
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Create PartSim for " + this.name); log.buf.AppendLine("Create PartSim for " + this.name);
} }
   
this.parent = null; this.parent = null;
this.parentAttach = part.attachMode; this.parentAttach = part.attachMode;
this.fuelCrossFeed = this.part.fuelCrossFeed; this.fuelCrossFeed = this.part.fuelCrossFeed;
this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey; this.noCrossFeedNodeKey = this.part.NoCrossFeedNodeKey;
this.decoupledInStage = this.DecoupledInStage(this.part); this.decoupledInStage = this.DecoupledInStage(this.part);
this.isFuelLine = this.part.HasModule<CModuleFuelLine>(); this.isFuelLine = this.part.HasModule<CModuleFuelLine>();
this.isFuelTank = this.part is FuelTank; this.isFuelTank = this.part is FuelTank;
this.isSepratron = this.IsSepratron(); this.isSepratron = this.IsSepratron();
this.inverseStage = this.part.inverseStage; this.inverseStage = this.part.inverseStage;
//MonoBehaviour.print("inverseStage = " + inverseStage); //MonoBehaviour.print("inverseStage = " + inverseStage);
   
this.cost = this.part.GetCostWet(); this.cost = this.part.GetCostWet();
   
// Work out if the part should have no physical significance // Work out if the part should have no physical significance
this.isNoPhysics = this.part.HasModule<LaunchClamp>() || this.isNoPhysics = this.part.HasModule<LaunchClamp>() ||
this.part.physicalSignificance == Part.PhysicalSignificance.NONE || this.part.physicalSignificance == Part.PhysicalSignificance.NONE ||
this.part.PhysicsSignificance == 1; this.part.PhysicsSignificance == 1;
   
if (!this.isNoPhysics) if (!this.isNoPhysics)
{ {
this.baseMass = this.part.mass; this.baseMass = this.part.mass;
} }
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass); MonoBehaviour.print((this.isNoPhysics ? "Ignoring" : "Using") + " part.mass of " + this.part.mass);
} }
   
foreach (PartResource resource in this.part.Resources) foreach (PartResource resource in this.part.Resources)
{ {
// Make sure it isn't NaN as this messes up the part mass and hence most of the values // Make sure it isn't NaN as this messes up the part mass and hence most of the values
// This can happen if a resource capacity is 0 and tweakable // This can happen if a resource capacity is 0 and tweakable
if (!Double.IsNaN(resource.amount)) if (!Double.IsNaN(resource.amount))
{ {
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print(resource.resourceName + " = " + resource.amount); MonoBehaviour.print(resource.resourceName + " = " + resource.amount);
} }
   
this.resources.Add(resource.info.id, resource.amount); this.resources.Add(resource.info.id, resource.amount);
this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0); this.resourceFlowStates.Add(resource.info.id, resource.flowState ? 1 : 0);
} }
else else
{ {
MonoBehaviour.print(resource.resourceName + " is NaN. Skipping."); MonoBehaviour.print(resource.resourceName + " is NaN. Skipping.");
} }
} }
   
this.startMass = this.GetMass(); this.startMass = this.GetMass();
   
this.hasVessel = (this.part.vessel != null); this.hasVessel = (this.part.vessel != null);
this.isLanded = this.hasVessel && this.part.vessel.Landed; this.isLanded = this.hasVessel && this.part.vessel.Landed;
if (this.hasVessel) if (this.hasVessel)
{ {
this.vesselName = this.part.vessel.vesselName; this.vesselName = this.part.vessel.vesselName;
this.vesselType = this.part.vesselType; this.vesselType = this.part.vesselType;
} }
this.initialVesselName = this.part.initialVesselName; this.initialVesselName = this.part.initialVesselName;
   
this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>(); this.hasMultiModeEngine = this.part.HasModule<MultiModeEngine>();
this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>(); this.hasModuleEnginesFX = this.part.HasModule<ModuleEnginesFX>();
this.hasModuleEngines = this.part.HasModule<ModuleEngines>(); this.hasModuleEngines = this.part.HasModule<ModuleEngines>();
   
this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines; this.isEngine = this.hasMultiModeEngine || this.hasModuleEnginesFX || this.hasModuleEngines;
   
if (SimManager.logOutput) if (SimManager.logOutput)
{ {
MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage); MonoBehaviour.print("Created " + this.name + ". Decoupled in stage " + this.decoupledInStage);
} }
} }
   
public ResourceContainer Resources public ResourceContainer Resources
{ {
get { return this.resources; } get { return this.resources; }
} }
   
public ResourceContainer ResourceDrains public ResourceContainer ResourceDrains
{ {
get { return this.resourceDrains; } get { return this.resourceDrains; }
} }
   
public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log) public void CreateEngineSims(List<EngineSim> allEngines, double atmosphere, double mach, bool vectoredThrust, bool fullThrust, LogMsg log)
{ {
bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part); bool correctThrust = SimManager.DoesEngineUseCorrectedThrust(this.part);
if (log != null) if (log != null)
{ {
log.buf.AppendLine("CreateEngineSims for " + this.name); log.buf.AppendLine("CreateEngineSims for " + this.name);
   
foreach (PartModule partMod in this.part.Modules) foreach (PartModule partMod in this.part.Modules)
{ {
log.buf.AppendLine("Module: " + partMod.moduleName); log.buf.AppendLine("Module: " + partMod.moduleName);
} }
   
log.buf.AppendLine("correctThrust = " + correctThrust); log.buf.AppendLine("correctThrust = " + correctThrust);
} }
   
if (this.hasMultiModeEngine) if (this.hasMultiModeEngine)
{ {
// A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point // A multi-mode engine has multiple ModuleEnginesFX but only one is active at any point
// The mode of the engine is the engineID of the ModuleEnginesFX that is active // The mode of the engine is the engineID of the ModuleEnginesFX that is active
string mode = this.part.GetModule<MultiModeEngine>().mode; string mode = this.part.GetModule<MultiModeEngine>().mode;
   
foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>()) foreach (ModuleEnginesFX engine in this.part.GetModules<ModuleEnginesFX>())
{ {
if (engine.engineID == mode) if (engine.engineID == mode)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = EngineSim.GetPoolObject().Init(this,
atmosphere, atmosphere,
(float)mach, (float)mach,
engine.maxFuelFlow, engine.maxFuelFlow,
engine.minFuelFlow, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
thrustvec, thrustvec,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.atmChangeFlow, engine.atmChangeFlow,
engine.useAtmCurve ? engine.atmCurve : null, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelCurve ? engine.velCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.currentThrottle, engine.currentThrottle,
engine.throttleLocked || fullThrust, engine.throttleLocked || fullThrust,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
engine.resultingThrust, engine.resultingThrust,
engine.thrustTransforms); engine.thrustTransforms);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
else else
{ {
if (this.hasModuleEngines) if (this.hasModuleEngines)
{ {
foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>()) foreach (ModuleEngines engine in this.part.GetModules<ModuleEngines>())
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Module: " + engine.moduleName); log.buf.AppendLine("Module: " + engine.moduleName);
} }
   
Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log); Vector3 thrustvec = this.CalculateThrustVector(vectoredThrust ? engine.thrustTransforms : null, log);
   
EngineSim engineSim = new EngineSim(this, EngineSim engineSim = EngineSim.GetPoolObject().Init(this,
atmosphere, atmosphere,
(float)mach, (float)mach,
engine.maxFuelFlow, engine.maxFuelFlow,
engine.minFuelFlow, engine.minFuelFlow,
engine.thrustPercentage, engine.thrustPercentage,
thrustvec, thrustvec,
engine.atmosphereCurve, engine.atmosphereCurve,
engine.atmChangeFlow, engine.atmChangeFlow,
engine.useAtmCurve ? engine.atmCurve : null, engine.useAtmCurve ? engine.atmCurve : null,
engine.useVelCurve ? engine.velCurve : null, engine.useVelCurve ? engine.velCurve : null,
engine.currentThrottle, engine.currentThrottle,
engine.throttleLocked || fullThrust, engine.throttleLocked || fullThrust,
engine.propellants, engine.propellants,
engine.isOperational, engine.isOperational,
engine.resultingThrust, engine.resultingThrust,
engine.thrustTransforms); engine.thrustTransforms);
allEngines.Add(engineSim); allEngines.Add(engineSim);
} }
} }
} }
   
if (log != null) if (log != null)
{ {
log.Flush(); log.Flush();
} }
} }
   
private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log) private Vector3 CalculateThrustVector(List<Transform> thrustTransforms, LogMsg log)
{ {
if (thrustTransforms == null) if (thrustTransforms == null)
{ {
return Vector3.forward; return Vector3.forward;
} }
   
Vector3 thrustvec = Vector3.zero; Vector3 thrustvec = Vector3.zero;
foreach (Transform trans in thrustTransforms) foreach (Transform trans in thrustTransforms)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude); log.buf.AppendFormat("Transform = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", trans.forward.x, trans.forward.y, trans.forward.z, trans.forward.magnitude);
} }
   
thrustvec -= trans.forward; thrustvec -= trans.forward;
} }
   
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVec = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
thrustvec.Normalize(); thrustvec.Normalize();
   
if (log != null) if (log != null)
{ {
log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude); log.buf.AppendFormat("ThrustVecN = ({0:g6}, {1:g6}, {2:g6}) length = {3:g6}\n", thrustvec.x, thrustvec.y, thrustvec.z, thrustvec.magnitude);
} }
   
return thrustvec; return thrustvec;
} }
   
public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupParent(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (this.part.parent != null) if (this.part.parent != null)
{ {
this.parent = null; this.parent = null;
if (partSimLookup.TryGetValue(this.part.parent, out this.parent)) if (partSimLookup.TryGetValue(this.part.parent, out this.parent))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId); log.buf.AppendLine("Parent part is " + this.parent.name + ":" + this.parent.partId);
} }
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")"); log.buf.AppendLine("No PartSim for parent part (" + this.part.parent.partInfo.name + ")");
} }
} }
} }
} }
   
public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log) public void SetupAttachNodes(Dictionary<Part, PartSim> partSimLookup, LogMsg log)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + ""); log.buf.AppendLine("SetupAttachNodes for " + this.name + ":" + this.partId + "");
} }
   
this.attachNodes.Clear(); this.attachNodes.Clear();
foreach (AttachNode attachNode in this.part.attachNodes) foreach (AttachNode attachNode in this.part.attachNodes)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null")); log.buf.AppendLine("AttachNode " + attachNode.id + " = " + (attachNode.attachedPart != null ? attachNode.attachedPart.partInfo.name : "null"));
} }
   
if (attachNode.attachedPart != null && attachNode.id != "Strut") if (attachNode.attachedPart != null && attachNode.id != "Strut")
{ {
PartSim attachedSim; PartSim attachedSim;
if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim)) if (partSimLookup.TryGetValue(attachNode.attachedPart, out attachedSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + ""); log.buf.AppendLine("Adding attached node " + attachedSim.name + ":" + attachedSim.partId + "");
} }
   
this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType)); this.attachNodes.Add(new AttachNodeSim(attachedSim, attachNode.id, attachNode.nodeType));
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")"); log.buf.AppendLine("No PartSim for attached part (" + attachNode.attachedPart.partInfo.name + ")");
} }
} }
} }
} }
   
foreach (Part p in this.part.fuelLookupTargets) foreach (Part p in this.part.fuelLookupTargets)
{ {
if (p != null) if (p != null)
{ {
PartSim targetSim; PartSim targetSim;
if (partSimLookup.TryGetValue(p, out targetSim)) if (partSimLookup.TryGetValue(p, out targetSim))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId); log.buf.AppendLine("Fuel target: " + targetSim.name + ":" + targetSim.partId);
} }
   
this.fuelTargets.Add(targetSim); this.fuelTargets.Add(targetSim);
} }
else else
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")"); log.buf.AppendLine("No PartSim for fuel target (" + p.name + ")");
} }
} }
} }
} }
} }
   
private int DecoupledInStage(Part thePart, int stage = -1) private int DecoupledInStage(Part thePart, int stage = -1)
{ {
if (this.IsDecoupler(thePart)) if (this.IsDecoupler(thePart))
{ {
if (thePart.inverseStage > stage) if (thePart.inverseStage > stage)
{ {
stage = thePart.inverseStage; stage = thePart.inverseStage;
} }
} }
   
if (thePart.parent != null) if (thePart.parent != null)
{ {
stage = this.DecoupledInStage(thePart.parent, stage); stage = this.DecoupledInStage(thePart.parent, stage);
} }
   
return stage; return stage;
} }
   
private bool IsDecoupler(Part thePart) private bool IsDecoupler(Part thePart)
{ {
return thePart.HasModule<ModuleDecouple>() || return thePart.HasModule<ModuleDecouple>() ||
thePart.HasModule<ModuleAnchoredDecoupler>(); thePart.HasModule<ModuleAnchoredDecoupler>();
} }
   
private bool IsActiveDecoupler(Part thePart) private bool IsActiveDecoupler(Part thePart)
{ {
return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) || return thePart.FindModulesImplementing<ModuleDecouple>().Any(mod => !mod.isDecoupled) ||
thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled); thePart.FindModulesImplementing<ModuleAnchoredDecoupler>().Any(mod => !mod.isDecoupled);
} }
   
private bool IsSepratron() private bool IsSepratron()
{ {
if (!this.part.ActivatesEvenIfDisconnected) if (!this.part.ActivatesEvenIfDisconnected)
{ {
return false; return false;
} }
   
if (this.part is SolidRocket) if (this.part is SolidRocket)
{ {
return true; return true;
} }
   
var modList = this.part.Modules.OfType<ModuleEngines>(); var modList = this.part.Modules.OfType<ModuleEngines>();
if (modList.Count() == 0) if (modList.Count() == 0)
{ {
return false; return false;
} }
   
if (modList.First().throttleLocked) if (modList.First().throttleLocked)
{ {
return true; return true;
} }
   
return false; return false;
} }
   
public void ReleasePart() public void ReleasePart()
{ {
this.part = null; this.part = null;
} }
   
// All functions below this point must not rely on the part member (it may be null) // All functions below this point must not rely on the part member (it may be null)
// //
   
public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent) public HashSet<PartSim> GetSourceSet(int type, List<PartSim> allParts, HashSet<PartSim> visited, LogMsg log, String indent)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId); log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + this.name + ":" + this.partId);
indent += " "; indent += " ";
} }
   
HashSet<PartSim> allSources = new HashSet<PartSim>(); HashSet<PartSim> allSources = new HashSet<PartSim>();
HashSet<PartSim> partSources = null; HashSet<PartSim> partSources = null;
   
// Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list. // Rule 1: Each part can be only visited once, If it is visited for second time in particular search it returns empty list.
if (visited.Contains(this)) if (visited.Contains(this))
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning empty set, already visited (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
   
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding this to visited"); // log.buf.AppendLine(indent + "Adding this to visited");
   
visited.Add(this); visited.Add(this);
   
// Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed. // Rule 2: Part performs scan on start of every fuel pipe ending in it. This scan is done in order in which pipes were installed.
// Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result. // Then it makes an union of fuel tank sets each pipe scan returned. If the resulting list is not empty, it is returned as result.
//MonoBehaviour.print("foreach fuel line"); //MonoBehaviour.print("foreach fuel line");
   
foreach (PartSim partSim in this.fuelTargets) foreach (PartSim partSim in this.fuelTargets)
{ {
if (visited.Contains(partSim)) if (visited.Contains(partSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")"); // log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");
   
partSources = partSim.GetSourceSet(type, allParts, visited, log, indent); partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
   
// Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed // Rule 3: This rule has been removed and merged with rules 4 and 7 to fix issue with fuel tanks with disabled crossfeed
   
// Rule 4: Part performs scan on each of its axially mounted neighbors. // Rule 4: Part performs scan on each of its axially mounted neighbors.
// Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side, // Couplers (bicoupler, tricoupler, ...) are an exception, they only scan one attach point on the single attachment side,
// skip the points on the side where multiple points are. [Experiment] // skip the points on the side where multiple points are. [Experiment]
// Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list. // Again, the part creates union of scan lists from each of its neighbor and if it is not empty, returns this list.
// The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment] // The order in which mount points of a part are scanned appears to be fixed and defined by the part specification file. [Experiment]
if (this.fuelCrossFeed) if (this.fuelCrossFeed)
{ {
//MonoBehaviour.print("foreach attach node"); //MonoBehaviour.print("foreach attach node");
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
if (attachSim.attachedPartSim != null) if (attachSim.attachedPartSim != null)
{ {
if (attachSim.nodeType == AttachNode.NodeType.Stack) if (attachSim.nodeType == AttachNode.NodeType.Stack)
{ {
if (!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey))) if (!(this.noCrossFeedNodeKey != null && this.noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(this.noCrossFeedNodeKey)))
{ {
if (visited.Contains(attachSim.attachedPartSim)) if (visited.Contains(attachSim.attachedPartSim))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
} }
else else
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")"); // log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
   
partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent); partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
if (partSources.Count > 0) if (partSources.Count > 0)
{ {
allSources.UnionWith(partSources); allSources.UnionWith(partSources);
partSources.Clear(); partSources.Clear();
} }
} }
} }
} }
} }
} }
   
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
} }
   
// Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // Rule 5: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel
// type was not disabled [Experiment]) and it contains fuel, it returns itself. // type was not disabled [Experiment]) and it contains fuel, it returns itself.
// Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel // Rule 6: If the part is fuel container for searched type of fuel (i.e. it has capability to contain that type of fuel and the fuel
// type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment] // type was not disabled) but it does not contain the requested fuel, it returns empty list. [Experiment]
if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0) if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0)
{ {
if (this.resources[type] > SimManager.RESOURCE_MIN) if (this.resources[type] > SimManager.RESOURCE_MIN)
{ {
allSources.Add(this); allSources.Add(this);
   
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning enabled tank as only source (" + this.name + ":" + this.partId + ")");
} }
} }
   
return allSources; return allSources;
} }
   
// Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its // Rule 7: If the part is radially attached to another part and it is child of that part in the ship's tree structure, it scans its
// parent and returns whatever the parent scan returned. [Experiment] [Experiment] // parent and returns whatever the parent scan returned. [Experiment] [Experiment]
if (this.parent != null && this.parentAttach == AttachModes.SRF_ATTACH) if (this.parent != null && this.parentAttach == AttachModes.SRF_ATTACH)
{ {
if (this.fuelCrossFeed) if (this.fuelCrossFeed)
{ {
if (visited.Contains(this.parent)) if (visited.Contains(this.parent))
{ {
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")"); // log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
} }
else else
{ {
allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent); allSources = this.parent.GetSourceSet(type, allParts, visited, log, indent);
if (allSources.Count > 0) if (allSources.Count > 0)
{ {
if (log != null) if (log != null)
{ {
log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")"); log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + this.name + ":" + this.partId + ")");
} }
   
return allSources; return allSources;
} }
} }
} }
} }
   
// Rule 8: If all preceding rules failed, part returns empty list. // Rule 8: If all preceding rules failed, part returns empty list.
//if (log != null) //if (log != null)
// log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")"); // log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");
   
return allSources; return allSources;
} }
   
public void RemoveAttachedParts(HashSet<PartSim> partSims) public void RemoveAttachedParts(HashSet<PartSim> partSims)
{ {
// Loop through the attached parts // Loop through the attached parts
foreach (AttachNodeSim attachSim in this.attachNodes) foreach (AttachNodeSim attachSim in this.attachNodes)
{ {
// If the part is in the set then "remove" it by clearing the PartSim reference // If the part is in the set then "remove" it by clearing the PartSim reference
if (partSims.Contains(attachSim.attachedPartSim)) if (partSims.Contains(attachSim.attachedPartSim))
{ {
attachSim.attachedPartSim = null; attachSim.attachedPartSim = null;
} }
} }
} }
   
public void DrainResources(double time) public void DrainResources(double time)
{ {
//MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")"); //MonoBehaviour.print("DrainResources(" + name + ":" + partId + ", " + time + ")");
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
//MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type)); //MonoBehaviour.print("draining " + (time * resourceDrains[type]) + " " + ResourceContainer.GetResourceName(type));
this.resources.Add(type, -time * this.resourceDrains[type]); this.resources.Add(type, -time * this.resourceDrains[type]);
//MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]); //MonoBehaviour.print(ResourceContainer.GetResourceName(type) + " left = " + resources[type]);
} }
} }
   
public double TimeToDrainResource() public double TimeToDrainResource()
{ {
//MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")"); //MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ")");
double time = double.MaxValue; double time = double.MaxValue;
   
foreach (int type in this.resourceDrains.Types) foreach (int type in this.resourceDrains.Types)
{ {
if (this.resourceDrains[type] > 0) if (this.resourceDrains[type] > 0)
{ {
time = Math.Min(time, this.resources[type] / this.resourceDrains[type]); time = Math.Min(time, this.resources[type] / this.resourceDrains[type]);
//MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time); //MonoBehaviour.print("type = " + ResourceContainer.GetResourceName(type) + " amount = " + resources[type] + " rate = " + resourceDrains[type] + " time = " + time);
} }
} }
   
//if (time < double.MaxValue) //if (time < double.MaxValue)
// MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time); // MonoBehaviour.print("TimeToDrainResource(" + name + ":" + partId + ") = " + time);
return time; return time;
} }
   
public bool EmptyOf(HashSet<int> types) public bool EmptyOf(HashSet<int> types)
{ {
foreach (int type in types) foreach (int type in types)
{ {
if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0 && (double)this.resources[type] > SimManager.RESOURCE_MIN) if (this.resources.HasType(type) && this.resourceFlowStates[type] != 0 && (double)this.resources[type] > SimManager.RESOURCE_MIN)
{ {
return false; return false;
} }
} }
   
return true; return true;
} }
   
public int DecouplerCount() public int DecouplerCount()
{ {
int count = 0; int count = 0;
PartSim partSim = this; PartSim partSim = this;
while (partSim != null) while (partSim != null)
{ {
if (partSim.isDecoupler) if (partSim.isDecoupler)
{ {
count++; count++;
} }
   
partSim = partSim.parent; partSim = partSim.parent;
} }
return count; return count;
} }
   
public double GetStartMass() public double GetStartMass()
{ {
return this.startMass; return this.startMass;
} }
   
public double GetMass() public double GetMass()
{ {
double mass = this.baseMass; double mass = this.baseMass;
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
{ {
mass += this.resources.GetResourceMass(type); mass += this.resources.GetResourceMass(type);
} }
   
return mass; return mass;
} }
   
public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix) public String DumpPartAndParentsToBuffer(StringBuilder buffer, String prefix)
{ {
if (this.parent != null) if (this.parent != null)
{ {
prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " "; prefix = this.parent.DumpPartAndParentsToBuffer(buffer, prefix) + " ";
} }
   
this.DumpPartToBuffer(buffer, prefix); this.DumpPartToBuffer(buffer, prefix);
   
return prefix; return prefix;
} }
   
public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null) public void DumpPartToBuffer(StringBuilder buffer, String prefix, List<PartSim> allParts = null)
{ {
buffer.Append(prefix); buffer.Append(prefix);
buffer.Append(this.name); buffer.Append(this.name);
buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage); buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", this.partId, this.decoupledInStage, this.inverseStage);
   
buffer.AppendFormat(", vesselName = '{0}'", this.vesselName); buffer.AppendFormat(", vesselName = '{0}'", this.vesselName);
buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType)); buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(this.vesselType));
buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName); buffer.AppendFormat(", initialVesselName = '{0}'", this.initialVesselName);
   
buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed); buffer.AppendFormat(", fuelCF = {0}", this.fuelCrossFeed);
buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey); buffer.AppendFormat(", noCFNKey = '{0}'", this.noCrossFeedNodeKey);
   
buffer.AppendFormat(", isSep = {0}", this.isSepratron); buffer.AppendFormat(", isSep = {0}", this.isSepratron);
   
foreach (int type in this.resources.Types) foreach (int type in this.resources.Types)
{ {
buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]); buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), this.resources[type]);
} }
   
if (this.attachNodes.Count > 0) if (this.attachNodes.Count > 0)
{ {
buffer.Append(", attached = <"); buffer.Append(", attached = <");
this.attachNodes[0].DumpToBuffer(buffer); this.attachNodes[0].DumpToBuffer(buffer);
for (int i = 1; i < this.attachNodes.Count; i++) for (int i = 1; i < this.attachNodes.Count; i++)
{ {
buffer.Append(", "); buffer.Append(", ");
this.attachNodes[i].DumpToBuffer(buffer); this.attachNodes[i].DumpToBuffer(buffer);
} }
buffer.Append(">"); buffer.Append(">");
} }
   
// Add more info here // Add more info here
   
buffer.Append("]\n"); buffer.Append("]\n");
   
if (allParts != null) if (allParts != null)
{ {
String newPrefix = prefix + " "; String newPrefix = prefix + " ";
foreach (PartSim partSim in allParts) foreach (PartSim partSim in allParts)
{ {
if (partSim.parent == this) if (partSim.parent == this)
{ {
partSim.DumpPartToBuffer(buffer, newPrefix, allParts); partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
} }
} }
} }
} }
} }
} }