diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09feb1e7..05c744f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: uses: KSP-RO/BuildTools/update-assembly-info@master with: path: ${GITHUB_WORKSPACE}/Source/assembly/AssemblyInfoRF.cs - tag: "2.0.0.0" + tag: "15.9.0.0" - name: Build mod solution run: msbuild ${GITHUB_WORKSPACE}/Source/RealFuels.sln /t:build /restore /p:RestorePackagesConfig=true /p:Configuration=Release /p:ReferencePath="${{ steps.download-assemblies.outputs.ksp-dll-path }}" diff --git a/Source/Engines/ModuleEngineConfigs.cs b/Source/Engines/ModuleEngineConfigs.cs index e1c4a8d2..43185e6f 100644 --- a/Source/Engines/ModuleEngineConfigs.cs +++ b/Source/Engines/ModuleEngineConfigs.cs @@ -4,10 +4,11 @@ using System.Reflection; using System.Linq; using UnityEngine; -using Debug = UnityEngine.Debug; +using RealFuels.Tanks; using RealFuels.TechLevels; using KSP.UI.Screens; using KSP.Localization; +using Debug = UnityEngine.Debug; namespace RealFuels { @@ -870,8 +871,7 @@ protected void SetConfiguration(ConfigNode newConfig, bool resetTechLevels) if (HighLogic.LoadedSceneIsEditor && EditorLogic.fetch.ship != null) { - foreach (Part p in EditorLogic.fetch.ship.parts) - p.SendMessage("UpdateUsedBy", SendMessageOptions.DontRequireReceiver); + EditorPartSetMaintainer.Instance.ScheduleUsedBySetsUpdate(); } SetupFX(); diff --git a/Source/Engines/ModuleEnginesRF.cs b/Source/Engines/ModuleEnginesRF.cs index f19b0d28..a0d730a9 100644 --- a/Source/Engines/ModuleEnginesRF.cs +++ b/Source/Engines/ModuleEnginesRF.cs @@ -403,7 +403,7 @@ public override void OnSave(ConfigNode node) } // Event fired by MEC after changing a part. - public virtual void UpdateUsedBy() { } + public virtual void OnEngineConfigurationChanged() { if (started) Start(); // Wait until we've started once to allow this path to restart diff --git a/Source/RealFuels.csproj b/Source/RealFuels.csproj index 0733d6c9..c018998d 100644 --- a/Source/RealFuels.csproj +++ b/Source/RealFuels.csproj @@ -103,7 +103,7 @@ - + diff --git a/Source/Tanks/EditorCrossfeedSetMaintainer.cs b/Source/Tanks/EditorCrossfeedSetMaintainer.cs deleted file mode 100644 index 6ee01ef4..00000000 --- a/Source/Tanks/EditorCrossfeedSetMaintainer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; - -namespace RealFuels.Tanks -{ - [KSPAddon(KSPAddon.Startup.EditorAny, false)] - public class EditorCrossfeedSetMaintainer : MonoBehaviour - { - public void Start() - { - GameEvents.onEditorShipModified.Add(UpdateCrossfeedSets); - } - - public void OnDestroy() - { - GameEvents.onEditorShipModified.Remove(UpdateCrossfeedSets); - } - - private void UpdateCrossfeedSets(ShipConstruct ship) - { - PartSet.BuildPartSets(ship.parts, null); - } - } -} diff --git a/Source/Tanks/EditorPartSetMaintainer.cs b/Source/Tanks/EditorPartSetMaintainer.cs new file mode 100644 index 00000000..fe802ee5 --- /dev/null +++ b/Source/Tanks/EditorPartSetMaintainer.cs @@ -0,0 +1,138 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace RealFuels.Tanks +{ + [KSPAddon(KSPAddon.Startup.EditorAny, false)] + public class EditorPartSetMaintainer : MonoBehaviour + { + public static EditorPartSetMaintainer Instance { get; private set; } + + private bool _updateScheduled = false; + + protected void Awake() + { + if (Instance != null) + Destroy(Instance); + + Instance = this; + } + + protected void Start() + { + GameEvents.onEditorShipModified.Add(UpdateCrossfeedSets); + GameEvents.onPartAttach.Add(OnPartAttach); + GameEvents.onPartRemove.Add(OnPartRemove); + } + + protected void OnDestroy() + { + GameEvents.onEditorShipModified.Remove(UpdateCrossfeedSets); + GameEvents.onPartAttach.Remove(OnPartAttach); + GameEvents.onPartRemove.Remove(OnPartRemove); + + if (Instance == this) + Instance = null; + } + + public void ScheduleUsedBySetsUpdate() + { + if (!_updateScheduled) + { + StartCoroutine(UsedBySetsUpdateRoutine()); + _updateScheduled = true; + } + } + + private void UpdateCrossfeedSets(ShipConstruct ship) + { + PartSet.BuildPartSets(ship.parts, null); + } + + // Only trigger updates if a part in the tree that was added/removed is a fuel consumer + // Note that part packed status will be false when a ShipContruct is being loaded. + // We don't want to run the checks and scheduling in this case. + // Also note that we do not run updates on ghosted parts. + private void OnPartAttach(GameEvents.HostTargetAction hostTarget) + { + // Attaching: host is the incoming part + if (hostTarget.target?.packed == true) + ScheduleUpdateIfNeeded(hostTarget, isAttachEvent: true); + } + + private void OnPartRemove(GameEvents.HostTargetAction hostTarget) + { + // Removing: target is the detaching part + if (hostTarget.target.localRoot == EditorLogic.RootPart) + ScheduleUpdateIfNeeded(hostTarget, isAttachEvent: false); + } + + private void ScheduleUpdateIfNeeded(GameEvents.HostTargetAction hostTarget, bool isAttachEvent) + { + if (PartContainsEngineOrRCS(hostTarget.host, testChildren: isAttachEvent) || + PartContainsEngineOrRCS(hostTarget.target, testChildren: !isAttachEvent)) + { + ScheduleUsedBySetsUpdate(); + } + } + + private static bool PartContainsEngineOrRCS(Part p, bool testChildren = false) + { + if (p == null) return false; + bool result = p.FindModuleImplementing() || p.FindModuleImplementing(); + if (testChildren && !result) + foreach (Part p2 in p.children) + result |= PartContainsEngineOrRCS(p2, testChildren); + return result; + } + + private IEnumerator UsedBySetsUpdateRoutine() + { + yield return new WaitForEndOfFrame(); + _updateScheduled = false; + UpdateUsedBySets(); + } + + private static void UpdateUsedBySets() + { + Debug.Log("[RF] UpdateUsedBySets start"); + if (EditorLogic.fetch.ship == null) + return; + + var thrusterModules = new List(); + var tankModules = new List(); + List parts = EditorLogic.fetch.ship.parts; + foreach (Part p in parts) + { + foreach (PartModule m in p.Modules) + { + if (m is ModuleEngines || m is ModuleRCS) + thrusterModules.Add(m); + else if (m is ModuleFuelTanks mft) + tankModules.Add(mft); + } + } + + foreach (ModuleFuelTanks mft in tankModules) + { + mft.usedBy.Clear(); + mft.usedByTanks.Clear(); + + foreach (PartModule m in thrusterModules) + { + FuelInfo f = null; + if (m is ModuleEngines me) + f = new FuelInfo(me.propellants, mft, m); + else if (m is ModuleRCS mRcs) + f = new FuelInfo(mRcs.propellants, mft, m); + + if (f?.valid == true) + mft.UpdateFuelInfo(f, m); + } + + mft.UpdateTweakableButtonsDelegate(); + } + } + } +} diff --git a/Source/Tanks/FuelInfo.cs b/Source/Tanks/FuelInfo.cs index 9e288604..e85ba104 100644 --- a/Source/Tanks/FuelInfo.cs +++ b/Source/Tanks/FuelInfo.cs @@ -28,7 +28,6 @@ private string BuildLabel() { Propellant tfuel = kvp.Key; if (PartResourceLibrary.Instance.GetDefinition(tfuel.name).resourceTransferMode != ResourceTransferMode.NONE && !IgnoreFuel(tfuel.name)) - //labelString.Add($"{Math.Round(100 * tfuel.ratio * kvp.Value / efficiency, 3)}% {tfuel.name}"); labelString.Add($"{Math.Round(100 * tfuel.ratio * kvp.Value / efficiency, 3)}% {tfuel.displayName}"); } return string.Join(" / ", labelString); @@ -42,15 +41,15 @@ public FuelInfo(List props, ModuleFuelTanks tank, PartModule source) this.source = source; string _title = source.part.partInfo.title; - if (source.part.Modules.GetModule("ModuleEngineConfigs") is PartModule pm && pm != null) - _title = $"{pm.Fields["configuration"].GetValue(pm)}: {_title}"; + if (source.part.FindModuleImplementing() is ModuleEngineConfigs mec) + _title = $"{mec.configuration}: {_title}"; title = _title; ratioFactor = 0.0; efficiency = 0.0; // Error conditions: Resource not defined in library, or resource has no tank and is not in IgnoreFuel - var missingRes = props.FirstOrDefault(p => PartResourceLibrary.Instance.GetDefinition(p.name) == null); - var noTanks = props.Where(p => !tank.tanksDict.ContainsKey(p.name)); + Propellant missingRes = props.FirstOrDefault(p => PartResourceLibrary.Instance.GetDefinition(p.name) == null); + IEnumerable noTanks = props.Where(p => !tank.tanksDict.ContainsKey(p.name)); bool noTanksAndNotIgnored = noTanks.Any(p => !IgnoreFuel(p.name)); if (missingRes != null) Debug.LogError($"[MFT/RF] FuelInfo: Unknown RESOURCE: {missingRes.name}"); diff --git a/Source/Tanks/ModuleFuelTanks.cs b/Source/Tanks/ModuleFuelTanks.cs index ce9a3ece..952e1985 100644 --- a/Source/Tanks/ModuleFuelTanks.cs +++ b/Source/Tanks/ModuleFuelTanks.cs @@ -8,6 +8,7 @@ using System.Reflection; using KSP.Localization; using ROUtils; +using UnityEngine.Profiling; // ReSharper disable InconsistentNaming, CompareOfFloatsByEqualityOperator @@ -273,8 +274,6 @@ public override void OnStart(StartState state) { if (HighLogic.LoadedSceneIsEditor) { - GameEvents.onPartAttach.Add(OnPartAttach); - GameEvents.onPartRemove.Add(OnPartRemove); GameEvents.onEditorShipModified.Add(OnEditorShipModified); GameEvents.onPartActionUIDismiss.Add(OnPartActionGuiDismiss); GameEvents.onPartActionUIShown.Add(OnPartActionUIShown); @@ -287,7 +286,7 @@ public override void OnStart(StartState state) Fields[nameof(utilization)].uiControlEditor.onSymmetryFieldChanged += OnUtilizationChanged; Fields[nameof(typeDisp)].uiControlEditor.onFieldChanged += OnTypeDispChanged; Fields[nameof(typeDisp)].uiControlEditor.onSymmetryFieldChanged += OnTypeDispChanged; - UpdateUsedBy(); + EditorPartSetMaintainer.Instance.ScheduleUsedBySetsUpdate(); } OnStartRF(state); @@ -301,8 +300,6 @@ public override void OnStart(StartState state) void OnDestroy() { - GameEvents.onPartAttach.Remove(OnPartAttach); - GameEvents.onPartRemove.Remove(OnPartRemove); GameEvents.onEditorShipModified.Remove(OnEditorShipModified); GameEvents.onPartActionUIDismiss.Remove(OnPartActionGuiDismiss); GameEvents.onPartActionUIShown.Remove(OnPartActionUIShown); @@ -331,31 +328,6 @@ private void OnTypeDispChanged(BaseField f, object obj) private void OnEditorShipModified(ShipConstruct _) => PartResourcesChanged(); - private bool PartContainsEngineOrRCS(Part p, bool testChildren = false) - { - if (p == null) return false; - bool result = p.FindModuleImplementing() || p.FindModuleImplementing(); - if (testChildren && !result) - foreach (Part p2 in p.children) - result |= PartContainsEngineOrRCS(p2, testChildren); - return result; - } - - // Only trigger updates if a part in the tree that was added/removed is a fuel consumer - private void OnPartAttach(GameEvents.HostTargetAction hostTarget) - { - // Attaching: host is the incoming part - if (PartContainsEngineOrRCS(hostTarget.host, true) || PartContainsEngineOrRCS(hostTarget.target, false)) - UpdateUsedBy(); - } - - private void OnPartRemove(GameEvents.HostTargetAction hostTarget) - { - // Removing: target is the detaching part - if (PartContainsEngineOrRCS(hostTarget.host, false) || PartContainsEngineOrRCS(hostTarget.target, true)) - UpdateUsedBy(); - } - private void OnPartActionUIShown(UIPartActionWindow window, Part p) { if (p == part && windowDirty) @@ -587,7 +559,7 @@ private void UpdateTankType (bool initializeAmounts = false) massDirty = true; } - UpdateUsedBy(); + EditorPartSetMaintainer.Instance?.ScheduleUsedBySetsUpdate(); UpdateTankTypeRF(def); UpdateTestFlight(); @@ -1009,46 +981,14 @@ private void SetUtilization(float value) internal readonly Dictionary usedBy = new Dictionary(); internal readonly HashSet usedByTanks = new HashSet(); - private void UpdateFuelInfo(FuelInfo f, PartModule source) + internal void UpdateFuelInfo(FuelInfo f, PartModule source) { + Profiler.BeginSample("UpdateFuelInfo"); usedBy[source] = f; foreach (Propellant tfuel in f.propellantVolumeMults.Keys) if (tanksDict.TryGetValue(tfuel.name, out FuelTank tank) && tank.canHave) usedByTanks.Add(tank); - } - - public void UpdateUsedBy() - { - if (!HighLogic.LoadedSceneIsEditor) return; - - usedBy.Clear(); - usedByTanks.Clear(); - - // Get part list - List parts; - if (HighLogic.LoadedSceneIsEditor && EditorLogic.fetch.ship != null) - parts = EditorLogic.fetch.ship.parts; - else if (HighLogic.LoadedSceneIsFlight && vessel != null) - parts = vessel.parts; - else - return; - - foreach(Part p in parts) - { - string title = p.partInfo.title; - foreach(PartModule m in p.Modules) - { - FuelInfo f = null; - if (m is ModuleEngines) - f = new FuelInfo((m as ModuleEngines).propellants, this, m); - else if (m is ModuleRCS) - f = new FuelInfo((m as ModuleRCS).propellants, this, m); - if (f?.valid == true) - UpdateFuelInfo(f, m); - } - } - - UpdateTweakableButtonsDelegate(); + Profiler.EndSample(); } private readonly HashSet displayedParts = new HashSet(); diff --git a/Source/assembly/AssemblyInfoRF.cs b/Source/assembly/AssemblyInfoRF.cs index da14f9c2..44a8be73 100644 --- a/Source/assembly/AssemblyInfoRF.cs +++ b/Source/assembly/AssemblyInfoRF.cs @@ -24,8 +24,8 @@ [assembly: AssemblyFileVersion("@MAJOR@.@MINOR@.@PATCH@.@BUILD@")] [assembly: KSPAssembly("RealFuels", @MAJOR@, @MINOR@, @PATCH@)] #else -[assembly: AssemblyFileVersion("15.8.1.0")] -[assembly: KSPAssembly("RealFuels", 15, 8, 1)] +[assembly: AssemblyFileVersion("15.9.0.0")] +[assembly: KSPAssembly("RealFuels", 15, 9, 0)] #endif // The following attributes are used to specify the signing key for the assembly,