Compare commits

..

2 Commits

Author SHA1 Message Date
c0e75c3c39 version++ 2025-08-31 18:33:06 -05:00
bd215d0dcc coroutine vs observers 2025-08-31 18:32:48 -05:00
3 changed files with 100 additions and 158 deletions

View File

@@ -2,6 +2,6 @@
<PropertyGroup> <PropertyGroup>
<MajorVersion>2</MajorVersion> <MajorVersion>2</MajorVersion>
<MinorVersion>1</MinorVersion> <MinorVersion>1</MinorVersion>
<PatchVersion>4</PatchVersion> <PatchVersion>6</PatchVersion>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -1,79 +1,80 @@
using GalaSoft.MvvmLight.Messaging; using Game.Messages;
using Game.Events;
using Game.Messages;
using Game.State; using Game.State;
using HarmonyLib; using HarmonyLib;
using KeyValue.Runtime;
using Model; using Model;
using Model.AI; using Model.AI;
using Model.Ops; using Model.Ops;
using Model.Ops.Timetable; using Model.Ops.Timetable;
using Model.Physics;
using Network; using Network;
using Network.Messages; using Network.Messages;
using Railloader; using Railloader;
using Serilog; using Serilog;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Track; using Track;
using Track.Search; using Track.Search;
using UI; using UI;
using UI.EngineControls; using UI.EngineControls;
using UnityEngine;
using UnityEngine.UI;
using static Track.Search.RouteSearch; using static Track.Search.RouteSearch;
using Location = Track.Location; using Location = Track.Location;
namespace RMROC451.TweaksAndThings.Patches; namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerWaypointControls))] [HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))] [HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")] [HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
{ {
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch>(); private static Serilog.ILogger _log => Log.ForContext<LocomotiveControlsUIAdapter_UpdateCarText_Postfix>();
private static readonly HashSet<IDisposable> _keyChangeObservers = []; private static int lastSeenIntegrationSetCount = default;
private static string? lastLocoSeenCarId = default;
private static Coroutine? watchyWatchy = null;
private static HashSet<OpsCarPosition?> locoConsistDestinations = []; private static HashSet<OpsCarPosition?> locoConsistDestinations = [];
private static Game.GameDateTime? timetableSaveTime = null; private static Game.GameDateTime? timetableSaveTime = null;
static string getDictKey(Car car) => car.DisplayName; static string getDictKey(Car car) => car.DisplayName;
static Car placeholder = new();
static void Postfix(AutoEngineerWaypointControls __instance, ref OptionsDropdownConfiguration __result) static void Postfix(LocomotiveControlsUIAdapter __instance)
{ {
try try
{ {
PrepLocoUsage(__instance, out BaseLocomotive selectedLoco, out int numberOfCars);
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(selectedLoco)) return;
if (lastLocoSeenCarId != null && lastLocoSeenCarId.Equals(TrainController.Shared?.SelectedLocomotive.id) && watchyWatchy != null) return;
if (watchyWatchy != null) ((MonoBehaviour)__instance).StopCoroutine(watchyWatchy);
watchyWatchy = null;
if (__instance._persistence.Orders.Mode == AutoEngineerMode.Waypoint) watchyWatchy = ((MonoBehaviour)__instance).StartCoroutine(UpdateCogCoroutine(__instance));
}
catch (Exception ex)
{
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
}
}
public static IEnumerator UpdateCogCoroutine(LocomotiveControlsUIAdapter __instance)
{
TweaksAndThingsPlugin tweaksAndThings = SingletonPluginBase<TweaksAndThingsPlugin>.Shared;
WaitForSecondsRealtime wait = new WaitForSecondsRealtime(3f);
while (true)
{
if (__instance._persistence.Orders.Mode != AutoEngineerMode.Waypoint) yield return wait;
PrepLocoUsage((AutoEngineerWaypointControls)__instance.aiWaypointControls, out BaseLocomotive selectedLoco, out int numberOfCars);
HashSet<OpsCarPosition?> destinations = [];
if (!tweaksAndThings.IsEnabled() || !ShouldRecalc(__instance, selectedLoco, out destinations)) yield return wait;
timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified; timetableSaveTime = TimetableController.Shared.CurrentDocument.Modified;
Messenger.Default.Unregister(placeholder); lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
Messenger.Default.Register(placeholder, delegate (TimetableDidChange evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (CarTrainCrewChanged evt)
{
_log.Debug("Received {evt}, rebuilding {1} - {2}.", evt, selectedLoco.id, evt.CarId);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (TrainCrewsDidChange evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
Messenger.Default.Register(placeholder, delegate (UpdateTrainCrews evt)
{
_log.Debug("Received {evt}, rebuilding.", evt);
Rebuild(__instance, selectedLoco, evt.GetType().Name);
});
IterateCarsDetectDestinations( IterateCarsDetectDestinations(
__instance, (AutoEngineerWaypointControls)__instance.aiWaypointControls,
__result, ((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
selectedLoco, selectedLoco,
numberOfCars, numberOfCars,
destinations: destinations,
out List<DropdownMenu.RowData> rowDatas, out List<DropdownMenu.RowData> rowDatas,
out Action<int> func, out Action<int> func,
out int origCount, out int origCount,
@@ -81,10 +82,11 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
out AutoEngineerOrdersHelper aeoh out AutoEngineerOrdersHelper aeoh
); );
List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos = BuildJumpToOptions(__instance, selectedLoco); List<(string destinationId, string destination, float? distance, float sortDistance, Location? location)> jumpTos =
BuildJumpToOptions((AutoEngineerWaypointControls)__instance.aiWaypointControls, selectedLoco);
__result = WireUpJumpTosToSettingMenu( var config = WireUpJumpTosToSettingMenu(
__instance, (AutoEngineerWaypointControls)__instance.aiWaypointControls,
selectedLoco, selectedLoco,
rowDatas, rowDatas,
func, func,
@@ -93,23 +95,36 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
aeoh, aeoh,
ref jumpTos ref jumpTos
); );
} catch(Exception ex)
{ List<DropdownMenu.RowData> list = config.Rows;
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you."); Action<int> action = config.OnRowSelected;
}
__instance.optionsDropdown.Configure(list, action);
((Selectable)__instance.optionsDropdown).interactable = list.Count > 0;
yield return wait;
}
} }
private static bool ShouldRecalc(BaseLocomotive selectedLoco) private static bool ShouldRecalc(LocomotiveControlsUIAdapter __instance, BaseLocomotive selectedLoco, out HashSet<OpsCarPosition?> destinations)
{ {
bool output = false; bool output = false;
string locoKey = getDictKey(selectedLoco); string locoKey = getDictKey(selectedLoco);
List<Car> consist = new List<Car>(); List<Car> consist = new List<Car>();
consist = selectedLoco.EnumerateCoupled().ToList(); consist = selectedLoco.EnumerateCoupled().ToList();
HashSet<OpsCarPosition?> destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet(); destinations = consist.Where(c => GetCarDestinationIdentifier(c).HasValue).Select(GetCarDestinationIdentifier).ToHashSet();
output |= !locoConsistDestinations.Equals(destinations); //_log.Information($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
output |= !(_keyChangeObservers?.Any() ?? false); //_log.Information($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
output |= !locoConsistDestinations.SetEquals(destinations);
_log.Information($"{locoKey} 1-> {output}");
if (output) lastSeenIntegrationSetCount = default;
output |= lastSeenIntegrationSetCount != selectedLoco.set.NumberOfCars;
_log.Information($"{locoKey} 2-> {output}");
//output |= __instance.optionsDropdown.scrollRect.content.childCount != (destinations.Count + timetableDestinations.Count + 1); //+1 for the default "JumpTo" entry)
//_log.Information($"{locoKey} 2.5-> {output} {__instance.optionsDropdown.scrollRect.content.childCount} {(destinations.Count)} {timetableDestinations.Count}");
output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime; output |= selectedLoco.TryGetTimetableTrain(out _) && TimetableController.Shared.CurrentDocument.Modified != timetableSaveTime;
_log.Information($"{locoKey} 3-> {output}");
return output; return output;
} }
@@ -198,7 +213,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum) sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance ? metrics.Distance
: float.MaxValue; : float.MaxValue;
}; }
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}"); _log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add(( jumpTos.Add((
destinationId: destId, destinationId: destId,
@@ -244,7 +260,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum) sortdistance = Graph.Shared.FindRoute(start, destLoc, autoEngineer, list, out metrics, checkForCars: false, 0f, trainMomentum)
? metrics.Distance ? metrics.Distance
: float.MaxValue; : float.MaxValue;
}; }
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}"); _log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add(( jumpTos.Add((
destinationId: destId, destinationId: destId,
@@ -253,7 +270,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
, sortdistance: sortdistance , sortdistance: sortdistance
, location: (Location?)destLoc , location: (Location?)destLoc
)); ));
} catch (Exception ex) }
catch (Exception ex)
{ {
_log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}"); _log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
} }
@@ -264,42 +282,33 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return jumpTos; return jumpTos;
} }
private static void IterateCarsDetectDestinations(AutoEngineerWaypointControls __instance, OptionsDropdownConfiguration __result, BaseLocomotive selectedLoco, int numberOfCars, out List<DropdownMenu.RowData> rowDatas, out Action<int> func, out int origCount, out int maxRowOrig, out AutoEngineerOrdersHelper aeoh) private static void IterateCarsDetectDestinations(
AutoEngineerWaypointControls __instance,
OptionsDropdownConfiguration __result,
BaseLocomotive selectedLoco,
int numberOfCars,
HashSet<OpsCarPosition?> destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
out int maxRowOrig,
out AutoEngineerOrdersHelper aeoh
)
{ {
rowDatas = __result.Rows.ToList(); rowDatas = __result.Rows.ToList();
func = __result.OnRowSelected; func = __result.OnRowSelected;
origCount = rowDatas.Count; origCount = rowDatas.Count;
maxRowOrig = origCount - 1; maxRowOrig = origCount - 1;
locoConsistDestinations = []; aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
HashSet<OpsCarPosition?> seen = [];
aeoh = new AutoEngineerOrdersHelper(persistence: new AutoEngineerPersistence(selectedLoco.KeyValueObject), locomotive: selectedLoco);
Car.LogicalEnd logicalEnd = ((selectedLoco.set.IndexOfCar(selectedLoco).GetValueOrDefault(0) >= numberOfCars / 2) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
Car.LogicalEnd end = ((logicalEnd == Car.LogicalEnd.A) ? Car.LogicalEnd.B : Car.LogicalEnd.A);
bool stop = false;
int carIndex = selectedLoco.set.StartIndexForConnected(selectedLoco, logicalEnd, IntegrationSet.EnumerationCondition.Coupled);
Car car;
string locoKey = getDictKey(selectedLoco); string locoKey = getDictKey(selectedLoco);
while (!stop && (car = selectedLoco.set.NextCarConnected(ref carIndex, logicalEnd, IntegrationSet.EnumerationCondition.Coupled, out stop)) != null)
{
AddObserversToCar(__instance, car);
var ocp = GetCarDestinationIdentifier(car);
_log.Debug($"{locoKey} --> {getDictKey(car)} -> {ocp.HasValue} {ocp?.DisplayName}");
if (ocp.HasValue)
{
seen.Add(ocp.Value);
locoConsistDestinations.Add(ocp.Value);
}
}
var dropped = var dropped =
locoConsistDestinations.Except(seen).ToHashSet(); //are no longer here locoConsistDestinations.Except(destinations).ToHashSet(); //are no longer here
_log.Debug($"{locoKey} --> [{seen.Count}] -> Seen -> {string.Join(Environment.NewLine, seen.Select(k => k.Value.DisplayName))}"); _log.Debug($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
_log.Debug($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}"); _log.Debug($"{locoKey} --> [{locoConsistDestinations.Count}] -> Cache -> {string.Join(Environment.NewLine, locoConsistDestinations.Select(k => $"{locoKey}:{k.Value.DisplayName}"))}");
_log.Debug($"{locoKey} --> [{dropped.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => k.Value.DisplayName))}"); _log.Debug($"{locoKey} --> [{dropped.Count}] -> removed -> {string.Join(Environment.NewLine, dropped.Select(k => k.Value.DisplayName))}");
locoConsistDestinations = locoConsistDestinations.Intersect(seen).ToHashSet(); //remove ones that are no longer here locoConsistDestinations = destinations.ToList().ToHashSet(); //remove ones that are no longer here
seen.Clear();
} }
private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars) private static void PrepLocoUsage(AutoEngineerWaypointControls __instance, out BaseLocomotive selectedLoco, out int numberOfCars)
@@ -307,9 +316,7 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
//wire up that loco //wire up that loco
selectedLoco = __instance.Locomotive; selectedLoco = __instance.Locomotive;
numberOfCars = selectedLoco.set.NumberOfCars; numberOfCars = selectedLoco.set.NumberOfCars;
_log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]"); _log.Debug($"{selectedLoco.id} --> HI BOB[{numberOfCars}]");
foreach (var o in _keyChangeObservers) o.Dispose();
_keyChangeObservers.Clear();
} }
private static OpsCarPosition? GetCarDestinationIdentifier(Car c) private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
@@ -323,7 +330,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return destination; return destination;
} }
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive) { private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
{
bool num = _locomotive.IsStopped(); bool num = _locomotive.IsStopped();
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f)); bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
@@ -346,76 +354,16 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
} }
return num + 1.04f * (float)(coupledCarsCached.Count - 1); return num + 1.04f * (float)(coupledCarsCached.Count - 1);
} }
}
private static void AddObserversToCar(AutoEngineerWaypointControls __instance, Car c) [HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateOptionsDropdown))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class LocomotiveControlsUIAdapter_UpdateOptionsDropdown_Prefix
{
static bool Prefix(LocomotiveControlsUIAdapter __instance)
{ {
AddObserver(__instance, c, Car.KeyOpsWaybill); return false;
AddObserver(__instance, c, Car.KeyOpsRepairDestination);
AddObserver(__instance, c, nameof(Car.trainCrewId));
foreach (Car.LogicalEnd logicalEnd in CarInspector_PopulateCarPanel_Patch.ends)
{
AddObserver(__instance, c, Car.KeyValueKeyFor(Car.EndGearStateKey.IsCoupled, c.LogicalToEnd(logicalEnd)), true);
}
}
private static void AddObserver(AutoEngineerWaypointControls __instance, Model.Car car, string key, bool clearCarCache = false)
{
_keyChangeObservers.Add(
car.KeyValueObject.Observe(
key,
delegate (Value value)
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
if (!(locoConsistDestinations?.Any() ?? false)) return;
bool waybillChng = (new[] { Car.KeyOpsWaybill, Car.KeyOpsRepairDestination }).Contains(key);
string? destId =
waybillChng ?
GetCarDestinationIdentifier(car)?.Identifier ?? null :
null;
if (waybillChng)
{
if (locoConsistDestinations.Contains(GetCarDestinationIdentifier(car)))
{
return;
}
else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: destNew; {destId}; reload");
}
}
else
{
_log.Debug($"{getDictKey(car)} OBSV {key}: {value}");
}
Rebuild(__instance, car, key);
},
false
)
);
}
private static void Rebuild(AutoEngineerWaypointControls __instance, Car car, string key)
{
try
{
foreach (var o in _keyChangeObservers)
{
o.Dispose();
}
var loco = __instance.Locomotive;
if (!TrainController.Shared.SelectRecall()) TrainController.Shared.SelectedCar = null;
_keyChangeObservers.Clear();
locoConsistDestinations = [];
TrainController.Shared.SelectedCar = loco;
}
catch (Exception ex)
{
_log.ForContext("car", car).Warning(ex, $"{nameof(AddObserver)} {car} Exception logged for {key}");
}
} }
} }

View File

@@ -40,7 +40,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
static TweaksAndThingsPlugin() static TweaksAndThingsPlugin()
{ {
Log.Debug("Hello! Static Constructor was called!");
} }
public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self) public TweaksAndThingsPlugin(IModdingContext moddingContext, IModDefinition self)
@@ -58,7 +57,6 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public override void OnEnable() public override void OnEnable()
{ {
logger.Debug("OnEnable() was called!");
var harmony = new Harmony(modDefinition.Id); var harmony = new Harmony(modDefinition.Id);
harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty)); harmony.PatchCategory(modDefinition.Id.Replace(".", string.Empty));
} }
@@ -72,13 +70,10 @@ public class TweaksAndThingsPlugin : SingletonPluginBase<TweaksAndThingsPlugin>,
public void Update() public void Update()
{ {
logger.Verbose("UPDATE()");
} }
public void ModTabDidOpen(UIPanelBuilder builder) public void ModTabDidOpen(UIPanelBuilder builder)
{ {
logger.Debug("Daytime!");
if (settings == null) settings = new(); if (settings == null) settings = new();
if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList(); if (!settings?.WebhookSettingsList?.Any() ?? true) settings.WebhookSettingsList = new[] { new WebhookSettings() }.ToList();
if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new(); if (settings?.EngineRosterFuelColumnSettings == null) settings.EngineRosterFuelColumnSettings = new();
@@ -347,7 +342,6 @@ AutoHotboxSpotter Update: decrease the random wait from 30 - 300 seconds to 15 -
public void ModTabDidClose() public void ModTabDidClose()
{ {
logger.Debug("Nighttime...");
this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new()); this.moddingContext.SaveSettingsData(this.modDefinition.Id, settings ?? new());
} }
} }