diff --git a/MechJeb2/MechJebModuleStageStats.cs b/MechJeb2/MechJebModuleStageStats.cs index 9c4cc3268..0daac700d 100644 --- a/MechJeb2/MechJebModuleStageStats.cs +++ b/MechJeb2/MechJebModuleStageStats.cs @@ -1,4 +1,4 @@ -extern alias JetBrainsAnnotations; +extern alias JetBrainsAnnotations; using System; using System.Collections.Generic; using System.Diagnostics; @@ -21,6 +21,10 @@ public class MechJebModuleStageStats : ComputerModule public double AltSLT = 0; public double Mach = 0; + public int HalfStageIndex => _vesselManagerVac.HalfStageIndex; + [Persistent(pass = (int)(Pass.TYPE | Pass.GLOBAL))] + public readonly EditableDoubleMult HalfStageEndMass = new EditableDoubleMult(0, 0.001); + private int _vabRebuildTimer = 1; public readonly List AtmoStats = new List(); @@ -148,6 +152,7 @@ private void RunSimulation() _vesselManagerVac.SetConditions(0, 0, 0); _vesselManagerVac.SetInitial(VesselState.Time, VesselState.OrbitalPosition.WorldToV3Rotated(), VesselState.OrbitalVelocity.WorldToV3Rotated(), VesselState.Forward.WorldToV3Rotated()); + _vesselManagerVac.SetupStageAndAHalf(HalfStageEndMass); if (!_vesselManagerVac.TryStartFuelFlowSimulationJob()) throw new Exception("[MechJebModuleStageStats] could not start vac stats job"); } @@ -158,7 +163,7 @@ private void RunSimulation() _vesselManagerAtmo.SetConditions(atmDensity, staticPressureKpa * PhysicsGlobals.KpaToAtmospheres, mach); _vesselManagerAtmo.SetInitial(VesselState.Time, VesselState.OrbitalPosition.WorldToV3Rotated(), VesselState.OrbitalVelocity.WorldToV3Rotated(), VesselState.Forward.WorldToV3Rotated()); - //_vesselManagerAtmo.PrintVessel(); + _vesselManagerAtmo.SetupStageAndAHalf(HalfStageEndMass); if (!_vesselManagerAtmo.TryStartFuelFlowSimulationJob()) throw new Exception("[MechJebModuleStageStats] could not start atmo stats job"); } diff --git a/MechJeb2/MechJebModuleStagingController.cs b/MechJeb2/MechJebModuleStagingController.cs index 72462a8a3..f38585675 100644 --- a/MechJeb2/MechJebModuleStagingController.cs +++ b/MechJeb2/MechJebModuleStagingController.cs @@ -300,6 +300,12 @@ public override void OnFixedUpdate() UpdateBurnedResources(); _shouldDropSolids = null; // invalidate the cache; _droppingSolids recomputes lazily if needed this update + if (Vessel.currentStage == _stats.HalfStageIndex && Vessel.totalMass <= _stats.HalfStageEndMass) + { + Stage(); + return; + } + // don't decouple active or idle engines or tanks if (InverseStageDecouplesActiveOrIdleEngineOrTank(Vessel.currentStage - 1, _burnedResources, _activeModuleEngines) && !InverseStageReleasesClamps(Vessel.currentStage - 1)) @@ -613,7 +619,7 @@ private bool HasActiveOrIdleEngineOrTankDescendant(Part p, List tankResourc PartResource r = p.Resources.Get(propellant.id); if (r.amount <= p.resourceRequestRemainingThreshold) - continue; + return false; if (r.info.id == PartResourceLibrary.ElectricityHashcode) continue; if (!tankResources.Contains(r.info.id)) diff --git a/MechJeb2/MechJebStageStatsHelper.cs b/MechJeb2/MechJebStageStatsHelper.cs index c6ef962f0..953e28cf2 100644 --- a/MechJeb2/MechJebStageStatsHelper.cs +++ b/MechJeb2/MechJebStageStatsHelper.cs @@ -344,6 +344,15 @@ public void AllStageStats() Profiler.EndSample(); + if (stats.HalfStageIndex != -1) + { + GUILayout.BeginHorizontal(); + GUILayout.Label($"Half-stage index: {stats.HalfStageIndex}", GUILayout.Width(130)); + GUILayout.Space(30); + GuiUtils.SimpleTextBox("End mass: ", stats.HalfStageEndMass, "kg"); + GUILayout.EndHorizontal(); + } + Profiler.BeginSample("AllStageStats.DrawColumns"); GUILayout.BeginHorizontal(); diff --git a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs index 96cfc4428..432c94cb2 100644 --- a/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs +++ b/MechJebLib/FuelFlowSimulation/FuelFlowSimulation.cs @@ -1,10 +1,11 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using MechJebLib.FuelFlowSimulation.PartModules; using MechJebLib.Utils; @@ -25,6 +26,7 @@ public class FuelFlowSimulation : AsyncJob private readonly HashSet _partsWithRCSDrains = new HashSet(); private readonly HashSet _partsWithRCSDrains2 = new HashSet(); private bool _allocatedFirstSegment; + private bool _halfStageIsDetected; public override void Run(object? o = null) { @@ -34,6 +36,7 @@ public override void Run(object? o = null) if (!(o is SimVessel vessel)) throw new ArgumentException("o is not a SimVessel", nameof(o)); + _halfStageIsDetected = false; _allocatedFirstSegment = false; _time = 0; Segments.Clear(); @@ -53,6 +56,9 @@ public override void Run(object? o = null) Segments.Reverse(); _partsWithResourceDrains.Clear(); + + if (!_halfStageIsDetected) + vessel.HalfStageIndex = -1; } private void SimulateRCS(SimVessel vessel, bool max) @@ -129,7 +135,29 @@ private void SimulateStage(SimVessel vessel) ComputeRcsUllageTime(vessel); UpdateResourceDrainsAndResiduals(vessel); - double currentThrust = vessel.ThrustMagnitude; + int activeAngines = vessel.ActiveEngines.Count; + + if (!_halfStageIsDetected //is anyone insane enough to build a rocket with multiple half-stages? You never know + && vessel.ActiveEngines.Count > 0 + && _partsWithResourceDrains.Count > 0) + { + int earliestDroppedEgineInStage = 0, earliestDroppedTankInStage = 0; + for (int i = 0; i < vessel.ActiveEngines.Count; i++) + { + earliestDroppedEgineInStage = Max(earliestDroppedEgineInStage, vessel.ActiveEngines[i].Part.DecoupledInStage); + } + + foreach (var tank in _partsWithResourceDrains) + { + earliestDroppedTankInStage = Max(earliestDroppedTankInStage, tank.DecoupledInStage); + } + + if (earliestDroppedEgineInStage > earliestDroppedTankInStage) + { + _halfStageIsDetected = true; + vessel.HalfStageIndex = earliestDroppedEgineInStage + 1; + } + } for (int steps = MAXSTEPS; steps > 0; steps--) { @@ -137,16 +165,19 @@ private void SimulateStage(SimVessel vessel) return; double dt = MinimumTimeStep(); + if (_currentSegment.KSPStage == vessel.HalfStageIndex) + { + double massFlow = ResourceMaxMassFlow(vessel); + dt = Min(dt, (vessel.Mass - vessel.HalfStageEndMass) / massFlow); + } - // FIXME: if we have constructed a segment which is > 0 dV, but less than 0.02s, and there's a - // prior > 0dV segment in the same kspStage we should add those together to reduce clutter. - if (Abs(vessel.ThrustMagnitude - currentThrust) > 1e-12) + if (dt >= 0.02 && activeAngines != vessel.ActiveEngines.Count) { ClearResiduals(); ComputeRcsMaxValues(vessel); FinishSegment(vessel); GetNextSegment(vessel); - currentThrust = vessel.ThrustMagnitude; + activeAngines = vessel.ActiveEngines.Count; } _time += dt; @@ -388,7 +419,7 @@ private double MinimumTimeStep() { double maxTime = ResourceMaxTime(); - return maxTime < double.MaxValue && maxTime >= 0 ? maxTime : 0; + return maxTime < double.MaxValue && maxTime > 0.001 ? maxTime : 0.001; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -402,6 +433,17 @@ private double ResourceMaxTime() return maxTime; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private double ResourceMaxMassFlow(SimVessel vessel) + { + double massFlow = 0; + + foreach (SimModuleEngines engine in vessel.ActiveEngines) + massFlow += engine.MassFlowRate; + + return massFlow; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void FinishRcsSegment(bool max, double deltaTime, double startMass, double endMass, double rcsThrust) { @@ -488,6 +530,9 @@ private static bool AllowedToStage(SimVessel vessel) if (vessel.ActiveEngines.Count == 0) return true; + if (vessel.CurrentStage == vessel.HalfStageIndex && vessel.Mass - vessel.HalfStageEndMass < 1e-6) + return true; + for (int i = 0; i < vessel.ActiveEngines.Count; i++) { SimModuleEngines e = vessel.ActiveEngines[i]; diff --git a/MechJebLib/FuelFlowSimulation/SimResource.cs b/MechJebLib/FuelFlowSimulation/SimResource.cs index d2d94d64e..269aebde1 100644 --- a/MechJebLib/FuelFlowSimulation/SimResource.cs +++ b/MechJebLib/FuelFlowSimulation/SimResource.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -27,8 +27,8 @@ public double Amount public SimResource Drain(double resourceDrain) { _amount -= resourceDrain; - if (_amount < 0) - _amount = 0; + if (_amount < ResidualThreshold) + _amount = ResidualThreshold; return this; } diff --git a/MechJebLib/FuelFlowSimulation/SimVessel.cs b/MechJebLib/FuelFlowSimulation/SimVessel.cs index 4ca27b5d8..c5ed9e2e6 100644 --- a/MechJebLib/FuelFlowSimulation/SimVessel.cs +++ b/MechJebLib/FuelFlowSimulation/SimVessel.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -49,6 +49,9 @@ public class SimVessel : IDisposable public double T; public V3 R, V, U; + public int HalfStageIndex = -1; + public double HalfStageEndMass = 0; + // CurrentStage gets scribbled over by the FuelFlowSimulation, SetCurrentStage() is intended to be used in // the VesselBuilder and DecouplingAnalyzer to figure out the right value, ResetCurrentStage() is called by // the VesselUpdater to reset it back. @@ -83,6 +86,15 @@ public void SetInitial(double t, V3 r, V3 v, V3 u) U = u; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetupStageAndAHalf(double endMass) + { + if (HalfStageIndex != -1) + { + HalfStageEndMass = endMass; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void UpdateMass() { diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs index 30a3581d6..538b02947 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselBuilder.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -36,17 +36,9 @@ public class SimVesselBuilder private static readonly CrewMass _crewMassDelegate; - private SimVessel _vessel - { - get => _manager._vessel; - set => _manager._vessel = value; - } + private SimVessel _vessel => _manager._vessel; - private IShipconstruct _kspVessel - { - get => _manager._kspVessel; - set => _manager._kspVessel = value; - } + private IShipconstruct _kspVessel => _manager._kspVessel; private readonly SimVesselManager _manager; @@ -63,7 +55,7 @@ static SimVesselBuilder() private static double CrewMassNew(ProtoCrewMember crew) => PhysicsGlobals.KerbalCrewMass + crew.ResourceMass() + crew.InventoryMass(); - public SimVesselBuilder(SimVesselManager manager) + internal SimVesselBuilder(SimVesselManager manager) { _manager = manager; } @@ -121,14 +113,12 @@ internal void UpdateSymmetryParts() } } - internal void BuildVessel(IShipconstruct kspVessel) + internal void BuildVessel() { - _vessel.Dispose(); - _vessel = SimVessel.Borrow(); - _kspVessel = kspVessel; + BuildParts(); } - internal void BuildParts() + private void BuildParts() { _vessel.SetCurrentStage(StageManager.CurrentStage); diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs index 7d816727d..eea8c8b80 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselManager.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -32,6 +32,8 @@ public partial class SimVesselManager public V3 V => _vessel.V; public V3 U => _vessel.U; + public int HalfStageIndex => _vessel.HalfStageIndex; + public SimVesselManager() { _builder = new SimVesselBuilder(this); @@ -42,9 +44,12 @@ public SimVesselManager() public void Build(IShipconstruct vessel) { + _vessel.Dispose(); + _vessel = SimVessel.Borrow(); + _kspVessel = vessel; + Clear(); - _builder.BuildVessel(vessel); - _builder.BuildParts(); + _builder.BuildVessel(); Update(); _builder.UpdateLinks(); _builder.UpdateCrossFeedSet(); @@ -62,6 +67,8 @@ public void SetConditions(double atmDensity, double atmPressure, double machNumb public void SetInitial(double t, V3 r, V3 v, V3 u) => _vessel.SetInitial(t, r, v, u); + public void SetupStageAndAHalf(double endMass) => _vessel.SetupStageAndAHalf(endMass); + public bool TryStartFuelFlowSimulationJob() { FuelFlowSimulation.DVLinearThrust = DVLinearThrust; diff --git a/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs b/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs index 36cb0096b..8ea97315c 100644 --- a/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs +++ b/MechJebLibBindings/FuelFlowSimulation/SimVesselUpdater.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors * SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+ */ @@ -38,7 +38,7 @@ static SimVesselUpdater() _isRealFuelsLoadedCorrectly = IsLoadedRealFuels && _rfPredictedMaximumResiduals.IsValid; } - public SimVesselUpdater(SimVesselManager manager) + internal SimVesselUpdater(SimVesselManager manager) { _manager = manager; }