Skip to content

Commit

Permalink
Rework tank UsedBy logic (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
siimav authored Sep 15, 2024
1 parent 9e7cc8f commit 0829f66
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand Down
6 changes: 3 additions & 3 deletions Source/Engines/ModuleEngineConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Source/Engines/ModuleEnginesRF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Source/RealFuels.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
<Compile Include="EntryCosts\EntryCostDatabase.cs" />
<Compile Include="EntryCosts\PartEntryCostHolder.cs" />
<Compile Include="Pumps\RefuelingPump.cs" />
<Compile Include="Tanks\EditorCrossfeedSetMaintainer.cs" />
<Compile Include="Tanks\EditorPartSetMaintainer.cs" />
<Compile Include="Tanks\FuelInfo.cs" />
<Compile Include="Tanks\FuelTank.cs" />
<Compile Include="Tanks\FuelTankList.cs" />
Expand Down
23 changes: 0 additions & 23 deletions Source/Tanks/EditorCrossfeedSetMaintainer.cs

This file was deleted.

138 changes: 138 additions & 0 deletions Source/Tanks/EditorPartSetMaintainer.cs
Original file line number Diff line number Diff line change
@@ -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<Part, Part> hostTarget)
{
// Attaching: host is the incoming part
if (hostTarget.target?.packed == true)
ScheduleUpdateIfNeeded(hostTarget, isAttachEvent: true);
}

private void OnPartRemove(GameEvents.HostTargetAction<Part, Part> hostTarget)
{
// Removing: target is the detaching part
if (hostTarget.target.localRoot == EditorLogic.RootPart)
ScheduleUpdateIfNeeded(hostTarget, isAttachEvent: false);
}

private void ScheduleUpdateIfNeeded(GameEvents.HostTargetAction<Part, Part> 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<ModuleEngines>() || p.FindModuleImplementing<ModuleRCS>();
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<PartModule>();
var tankModules = new List<ModuleFuelTanks>();
List<Part> 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();
}
}
}
}
9 changes: 4 additions & 5 deletions Source/Tanks/FuelInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -42,15 +41,15 @@ public FuelInfo(List<Propellant> 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<string>(pm)}: {_title}";
if (source.part.FindModuleImplementing<ModuleEngineConfigs>() 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<Propellant> 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}");
Expand Down
72 changes: 6 additions & 66 deletions Source/Tanks/ModuleFuelTanks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reflection;
using KSP.Localization;
using ROUtils;
using UnityEngine.Profiling;

// ReSharper disable InconsistentNaming, CompareOfFloatsByEqualityOperator

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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<ModuleEngines>() || p.FindModuleImplementing<ModuleRCS>();
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<Part, Part> hostTarget)
{
// Attaching: host is the incoming part
if (PartContainsEngineOrRCS(hostTarget.host, true) || PartContainsEngineOrRCS(hostTarget.target, false))
UpdateUsedBy();
}

private void OnPartRemove(GameEvents.HostTargetAction<Part, Part> 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)
Expand Down Expand Up @@ -587,7 +559,7 @@ private void UpdateTankType (bool initializeAmounts = false)

massDirty = true;
}
UpdateUsedBy();
EditorPartSetMaintainer.Instance?.ScheduleUsedBySetsUpdate();

UpdateTankTypeRF(def);
UpdateTestFlight();
Expand Down Expand Up @@ -1009,46 +981,14 @@ private void SetUtilization(float value)
internal readonly Dictionary<PartModule, FuelInfo> usedBy = new Dictionary<PartModule, FuelInfo>();
internal readonly HashSet<FuelTank> usedByTanks = new HashSet<FuelTank>();

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<Part> 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<string> displayedParts = new HashSet<string>();
Expand Down
4 changes: 2 additions & 2 deletions Source/assembly/AssemblyInfoRF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 0829f66

Please sign in to comment.