EngineSim objects are now pooled.
[VesselSimulator.git] / KerbalEngineer / VesselSimulator / EngineSim.cs
blob:a/KerbalEngineer/VesselSimulator/EngineSim.cs -> blob:b/KerbalEngineer/VesselSimulator/EngineSim.cs
--- a/KerbalEngineer/VesselSimulator/EngineSim.cs
+++ b/KerbalEngineer/VesselSimulator/EngineSim.cs
@@ -26,129 +26,182 @@
     using Helpers;
     using UnityEngine;
 
-    public class EngineSim : Pool<EngineSim>
+    public class EngineSim
     {
-        public double actualThrust = 0.0;
-        public List<AppliedForce> appliedForces = new List<AppliedForce>();
+        private static readonly Pool<EngineSim> pool = new Pool<EngineSim>(Create, Reset);
+
+        private readonly ResourceContainer resourceConsumptions = new ResourceContainer();
+        private readonly ResourceContainer resourceFlowModes = new ResourceContainer();
+
+        public double actualThrust = 0;
         public bool isActive = false;
         public double isp = 0;
         public PartSim partSim;
+        public List<AppliedForce> appliedForces = new List<AppliedForce>();
         public float maxMach;
 
         public double thrust = 0;
 
         // Add thrust vector to account for directional losses
         public Vector3 thrustVec;
-        private readonly ResourceContainer resourceConsumptions = new ResourceContainer();
-
-        public EngineSim Init(PartSim theEngine,
-            double atmosphere,
-            float machNumber,
-            float maxFuelFlow,
-            float minFuelFlow,
-            float thrustPercentage,
-            Vector3 vecThrust,
-            FloatCurve atmosphereCurve,
-            bool atmChangeFlow,
-            FloatCurve atmCurve,
-            FloatCurve velCurve,
-            float currentThrottle,
-            bool throttleLocked,
-            List<Propellant> propellants,
-            bool active,
-            float resultingThrust,
-            List<Transform> thrustTransforms)
-        {
-            StringBuilder buffer = null;
-
-            isp = 0.0;
-            maxMach = 0.0f;
-            actualThrust = 0.0;
-            partSim = theEngine;
-            isActive = active;
-            thrustVec = vecThrust;
-            resourceConsumptions.Reset();
-            appliedForces.Clear();
+
+        private static EngineSim Create()
+        {
+            return new EngineSim();
+        }
+
+        private static void Reset(EngineSim engineSim)
+        {
+            engineSim.resourceConsumptions.Reset();
+            engineSim.resourceFlowModes.Reset();
+            engineSim.actualThrust = 0;
+            engineSim.isActive = false;
+            engineSim.isp = 0;
+            for (int i = 0; i < engineSim.appliedForces.Count; i++)
+            {
+                engineSim.appliedForces[i].Release();
+            }
+            engineSim.appliedForces.Clear();
+            engineSim.thrust = 0;
+            engineSim.maxMach = 0f;
+        }
+
+        public void Release()
+        {
+            pool.Release(this);
+        }
+
+        public static EngineSim New(PartSim theEngine,
+                         double atmosphere,
+                         float machNumber,
+                         float maxFuelFlow,
+                         float minFuelFlow,
+                         float thrustPercentage,
+                         Vector3 vecThrust,
+                         FloatCurve atmosphereCurve,
+                         bool atmChangeFlow,
+                         FloatCurve atmCurve,
+                         FloatCurve velCurve,
+                         float currentThrottle,
+                         float IspG,
+                         bool throttleLocked,
+                         List<Propellant> propellants,
+                         bool active,
+                         float resultingThrust,
+                         List<Transform> thrustTransforms,
+                        LogMsg log)
+        {
+            EngineSim engineSim = pool.Borrow();
+
+            engineSim.isp = 0.0;
+            engineSim.maxMach = 0.0f;
+            engineSim.actualThrust = 0.0;
+            engineSim.partSim = theEngine;
+            engineSim.isActive = active;
+            engineSim.thrustVec = vecThrust;
+            engineSim.resourceConsumptions.Reset();
+            engineSim.resourceFlowModes.Reset();
+            engineSim.appliedForces.Clear();
 
             double flowRate = 0.0;
-            if (partSim.hasVessel)
-            {
-                float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, partSim.part.atmDensity, velCurve, machNumber, ref maxMach);
-                isp = atmosphereCurve.Evaluate((float)atmosphere);
-                thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, isp);
-                actualThrust = isActive ? resultingThrust : 0.0;
+            if (engineSim.partSim.hasVessel)
+            {
+                if (log != null) log.buf.AppendLine("hasVessel is true"); 
+
+                float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, engineSim.partSim.part.atmDensity, velCurve, machNumber, ref engineSim.maxMach);
+                engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere);
+                engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp);
+                engineSim.actualThrust = engineSim.isActive ? resultingThrust : 0.0;
+                if (log != null)
+                {
+                    log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier);
+                    log.buf.AppendFormat("isp     = {0:g6}\n", engineSim.isp);
+                    log.buf.AppendFormat("thrust  = {0:g6}\n", engineSim.thrust);
+                    log.buf.AppendFormat("actual  = {0:g6}\n", engineSim.actualThrust);
+                }
 
                 if (throttleLocked)
                 {
-                    flowRate = GetFlowRate(thrust, isp);
+                    if (log != null) log.buf.AppendLine("throttleLocked is true, using thrust for flowRate");
+                    flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
                 }
                 else
                 {
-                    if (currentThrottle > 0.0f && partSim.isLanded == false)
-                    {
-                        flowRate = GetFlowRate(actualThrust, isp);
+                    if (currentThrottle > 0.0f && engineSim.partSim.isLanded == false)
+                    {
+                        if (log != null) log.buf.AppendLine("throttled up and not landed, using actualThrust for flowRate");
+                        flowRate = GetFlowRate(engineSim.actualThrust, engineSim.isp);
                     }
                     else
                     {
-                        flowRate = GetFlowRate(thrust, isp);
+                        if (log != null) log.buf.AppendLine("throttled down or landed, using thrust for flowRate");
+                        flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
                     }
                 }
             }
             else
             {
-                float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, ref maxMach);
-                isp = atmosphereCurve.Evaluate((float)atmosphere);
-                thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, isp);
-                flowRate = GetFlowRate(thrust, isp);
-            }
-
-            if (SimManager.logOutput)
-            {
-                buffer = new StringBuilder(1024);
-                buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);
-            }
+                if (log != null) log.buf.AppendLine("hasVessel is false");
+                float flowModifier = GetFlowModifier(atmChangeFlow, atmCurve, CelestialBodies.SelectedBody.GetDensity(BuildAdvanced.Altitude), velCurve, machNumber, ref engineSim.maxMach);
+                engineSim.isp = atmosphereCurve.Evaluate((float)atmosphere);
+                engineSim.thrust = GetThrust(Mathf.Lerp(minFuelFlow, maxFuelFlow, GetThrustPercent(thrustPercentage)) * flowModifier, engineSim.isp);
+                engineSim.actualThrust = 0d;
+                if (log != null)
+                {
+                    log.buf.AppendFormat("flowMod = {0:g6}\n", flowModifier);
+                    log.buf.AppendFormat("isp     = {0:g6}\n", engineSim.isp);
+                    log.buf.AppendFormat("thrust  = {0:g6}\n", engineSim.thrust);
+                    log.buf.AppendFormat("actual  = {0:g6}\n", engineSim.actualThrust);
+                }
+
+                if (log != null) log.buf.AppendLine("no vessel, using thrust for flowRate");
+                flowRate = GetFlowRate(engineSim.thrust, engineSim.isp);
+            }
+
+            if (log != null) log.buf.AppendFormat("flowRate = {0:g6}\n", flowRate);
 
             float flowMass = 0f;
-            foreach (Propellant propellant in propellants)
-            {
-                flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
-            }
-
-            if (SimManager.logOutput)
-            {
-                buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);
-            }
-
-            foreach (Propellant propellant in propellants)
-            {
+            for (int i = 0; i < propellants.Count; ++i)
+            {
+                Propellant propellant = propellants[i];
+                if (!propellant.ignoreForIsp)
+                    flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
+            }
+
+            if (log != null) log.buf.AppendFormat("flowMass = {0:g6}\n", flowMass);
+
+            for (int i = 0; i < propellants.Count; ++i)
+            {
+                Propellant propellant = propellants[i];
+
                 if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                 {
                     continue;
                 }
 
                 double consumptionRate = propellant.ratio * flowRate / flowMass;
-                if (SimManager.logOutput)
-                {
-                    buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);
-                }
-                resourceConsumptions.Add(propellant.id, consumptionRate);
-            }
-
-            if (SimManager.logOutput)
-            {
-                MonoBehaviour.print(buffer);
-            }
-
-            appliedForces.Clear();
-            double thrustPerThrustTransform = thrust / thrustTransforms.Count;
-            foreach (Transform thrustTransform in thrustTransforms)
-            {
+                if (log != null) log.buf.AppendFormat(
+                        "Add consumption({0}, {1}:{2:d}) = {3:g6}\n",
+                        ResourceContainer.GetResourceName(propellant.id),
+                        theEngine.name,
+                        theEngine.partId,
+                        consumptionRate);
+                engineSim.resourceConsumptions.Add(propellant.id, consumptionRate);
+                engineSim.resourceFlowModes.Add(propellant.id, (double)propellant.GetFlowMode());
+            }
+
+            double thrustPerThrustTransform = engineSim.thrust / thrustTransforms.Count;
+            for (int i = 0; i < thrustTransforms.Count; i++)
+            {
+                Transform thrustTransform = thrustTransforms[i];
                 Vector3d direction = thrustTransform.forward.normalized;
                 Vector3d position = thrustTransform.position;
-                appliedForces.Add(new AppliedForce(direction * thrustPerThrustTransform, position));
-            }
-
-            return this;
+
+                AppliedForce appliedForce = AppliedForce.New(direction * thrustPerThrustTransform, position);
+                engineSim.appliedForces.Add(appliedForce);
+            }
+
+            return engineSim;
         }
 
         public ResourceContainer ResourceConsumptions
@@ -213,49 +266,91 @@
             buffer.AppendFormat("[thrust = {0:g6}, actual = {1:g6}, isp = {2:g6}\n", thrust, actualThrust, isp);
         }
 
+        // A dictionary to hold a set of parts for each resource
+        Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();
+
+        Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();
+
+        HashSet<PartSim> visited = new HashSet<PartSim>();
+
+        public void DumpSourcePartSets(String msg)
+        {
+            MonoBehaviour.print("DumpSourcePartSets " + msg);
+            foreach (int type in sourcePartSets.Keys)
+            {
+                MonoBehaviour.print("SourcePartSet for " + ResourceContainer.GetResourceName(type));
+                HashSet<PartSim> sourcePartSet = sourcePartSets[type];
+                if (sourcePartSet.Count > 0)
+                {
+                    foreach (PartSim partSim in sourcePartSet)
+                    {
+                        MonoBehaviour.print("Part " + partSim.name + ":" + partSim.partId);
+                    }
+                }
+                else
+                {
+                    MonoBehaviour.print("No parts");
+                }
+            }
+        }
+
         public bool SetResourceDrains(List<PartSim> allParts, List<PartSim> allFuelLines, HashSet<PartSim> drainingParts)
         {
             LogMsg log = null;
-
-            // A dictionary to hold a set of parts for each resource
-            Dictionary<int, HashSet<PartSim>> sourcePartSets = new Dictionary<int, HashSet<PartSim>>();
-
-            foreach (int type in resourceConsumptions.Types)
-            {
-                HashSet<PartSim> sourcePartSet = null;
-                switch (ResourceContainer.GetResourceFlowMode(type))
+            //DumpSourcePartSets("before clear");
+            foreach (HashSet<PartSim> sourcePartSet in sourcePartSets.Values)
+            {
+                sourcePartSet.Clear();
+            }
+            //DumpSourcePartSets("after clear");
+
+            for (int index = 0; index < this.resourceConsumptions.Types.Count; index++)
+            {
+                int type = this.resourceConsumptions.Types[index];
+
+                HashSet<PartSim> sourcePartSet;
+                if (!sourcePartSets.TryGetValue(type, out sourcePartSet))
+                {
+                    sourcePartSet = new HashSet<PartSim>();
+                    sourcePartSets.Add(type, sourcePartSet);
+                }
+
+                switch ((ResourceFlowMode)this.resourceFlowModes[type])
                 {
                     case ResourceFlowMode.NO_FLOW:
                         if (partSim.resources[type] > SimManager.RESOURCE_MIN && partSim.resourceFlowStates[type] != 0)
                         {
-                            sourcePartSet = new HashSet<PartSim>();
+                            //sourcePartSet = new HashSet<PartSim>();
                             //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
                             sourcePartSet.Add(partSim);
                         }
                         break;
 
                     case ResourceFlowMode.ALL_VESSEL:
-                        foreach (PartSim aPartSim in allParts)
-                        {
+                    case ResourceFlowMode.ALL_VESSEL_BALANCE:
+                        for (int i = 0; i < allParts.Count; i++)
+                        {
+                            PartSim aPartSim = allParts[i];
                             if (aPartSim.resources[type] > SimManager.RESOURCE_MIN && aPartSim.resourceFlowStates[type] != 0)
                             {
-                                if (sourcePartSet == null)
-                                {
-                                    sourcePartSet = new HashSet<PartSim>();
-                                }
-
                                 sourcePartSet.Add(aPartSim);
                             }
                         }
                         break;
 
                     case ResourceFlowMode.STAGE_PRIORITY_FLOW:
-                        Dictionary<int, HashSet<PartSim>> stagePartSets = new Dictionary<int, HashSet<PartSim>>();
-                        int maxStage = -1;
+                    case ResourceFlowMode.STAGE_PRIORITY_FLOW_BALANCE:
+
+                        foreach (HashSet<PartSim> stagePartSet in stagePartSets.Values)
+                        {
+                            stagePartSet.Clear();
+                        }
+                        var maxStage = -1;
 
                         //Logger.Log(type);
-                        foreach (PartSim aPartSim in allParts)
-                        {
+                        for (int i = 0; i < allParts.Count; i++)
+                        {
+                            var aPartSim = allParts[i];
                             if (aPartSim.resources[type] <= SimManager.RESOURCE_MIN || aPartSim.resourceFlowStates[type] == 0)
                             {
                                 continue;
@@ -267,34 +362,57 @@
                                 maxStage = stage;
                             }
 
-                            if (!stagePartSets.TryGetValue(stage, out sourcePartSet))
+                            HashSet<PartSim> tempPartSet;
+                            if (!stagePartSets.TryGetValue(stage, out tempPartSet))
                             {
-                                sourcePartSet = new HashSet<PartSim>();
-                                stagePartSets.Add(stage, sourcePartSet);
+                                tempPartSet = new HashSet<PartSim>();
+                                stagePartSets.Add(stage, tempPartSet);
                             }
-                            sourcePartSet.Add(aPartSim);
-                        }
-
-                        for (int i = 0; i <= maxStage; i++)
+                            tempPartSet.Add(aPartSim);
+                        }
+
+                        for (int j = maxStage; j >= 0; j--)
                         {
                             HashSet<PartSim> stagePartSet;
-                            if (stagePartSets.TryGetValue(i, out stagePartSet) && stagePartSet.Count > 0)
+                            if (stagePartSets.TryGetValue(j, out stagePartSet) && stagePartSet.Count > 0)
                             {
-                                sourcePartSet = stagePartSet;
+                                // We have to copy the contents of the set here rather than copying the set reference or 
+                                // bad things (tm) happen
+                                foreach (PartSim aPartSim in stagePartSet)
+                                {
+                                    sourcePartSet.Add(aPartSim);
+                                }
+                                break;
                             }
                         }
                         break;
 
                     case ResourceFlowMode.STACK_PRIORITY_SEARCH:
-                        HashSet<PartSim> visited = new HashSet<PartSim>();
+                        visited.Clear();
 
                         if (SimManager.logOutput)
                         {
                             log = new LogMsg();
                             log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
                         }
-                        sourcePartSet = partSim.GetSourceSet(type, allParts, visited, log, "");
+                        partSim.GetSourceSet(type, PhysicsGlobals.Stack_PriUsesSurf, allParts, visited, sourcePartSet, log, "");
+                        if (SimManager.logOutput && log != null)
+                        {
+                            MonoBehaviour.print(log.buf);
+                        }
+                        break;
+
+                    case ResourceFlowMode.STAGE_STACK_FLOW:
+                    case ResourceFlowMode.STAGE_STACK_FLOW_BALANCE:
+                        visited.Clear();
+
                         if (SimManager.logOutput)
+                        {
+                            log = new LogMsg();
+                            log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
+                        }
+                        partSim.GetSourceSet(type, true, allParts, visited, sourcePartSet, log, "");
+                        if (SimManager.logOutput && log != null)
                         {
                             MonoBehaviour.print(log.buf);
                         }
@@ -305,10 +423,9 @@
                         break;
                 }
 
-                if (sourcePartSet != null && sourcePartSet.Count > 0)
-                {
-                    sourcePartSets[type] = sourcePartSet;
-                    if (SimManager.logOutput)
+                if (SimManager.logOutput)
+                {
+                    if (sourcePartSet.Count > 0)
                     {
                         log = new LogMsg();
                         log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");
@@ -319,12 +436,16 @@
                         MonoBehaviour.print(log.buf);
                     }
                 }
-            }
-
+
+                //DumpSourcePartSets("after " + ResourceContainer.GetResourceName(type));
+            }
+            
             // If we don't have sources for all the needed resources then return false without setting up any drains
-            foreach (int type in resourceConsumptions.Types)
-            {
-                if (!sourcePartSets.ContainsKey(type))
+            for (int i = 0; i < this.resourceConsumptions.Types.Count; i++)
+            {
+                int type = this.resourceConsumptions.Types[i];
+                HashSet<PartSim> sourcePartSet; 
+                if (!sourcePartSets.TryGetValue(type, out sourcePartSet) || sourcePartSet.Count == 0)
                 {
                     if (SimManager.logOutput)
                     {
@@ -337,8 +458,9 @@
             }
 
             // Now we set the drains on the members of the sets and update the draining parts set
-            foreach (int type in resourceConsumptions.Types)
-            {
+            for (int i = 0; i < this.resourceConsumptions.Types.Count; i++)
+            {
+                int type = this.resourceConsumptions.Types[i];
                 HashSet<PartSim> sourcePartSet = sourcePartSets[type];
                 // Loop through the members of the set 
                 double amount = resourceConsumptions[type] / sourcePartSet.Count;
@@ -346,14 +468,15 @@
                 {
                     if (SimManager.logOutput)
                     {
-                        MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);
+                        MonoBehaviour.print(
+                            "Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" +
+                            partSim.partId);
                     }
 
                     partSim.resourceDrains.Add(type, amount);
                     drainingParts.Add(partSim);
                 }
             }
-
             return true;
         }
     }