coroutine vs observers

This commit is contained in:
2025-08-31 18:32:48 -05:00
parent 554ea11790
commit bd215d0dcc
2 changed files with 99 additions and 157 deletions

View File

@@ -1,79 +1,80 @@
using GalaSoft.MvvmLight.Messaging;
using Game.Events;
using Game.Messages;
using Game.Messages;
using Game.State;
using HarmonyLib;
using KeyValue.Runtime;
using Model;
using Model.AI;
using Model.Ops;
using Model.Ops.Timetable;
using Model.Physics;
using Network;
using Network.Messages;
using Railloader;
using Serilog;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Track;
using Track.Search;
using UI;
using UI.EngineControls;
using UnityEngine;
using UnityEngine.UI;
using static Track.Search.RouteSearch;
using Location = Track.Location;
namespace RMROC451.TweaksAndThings.Patches;
[HarmonyPatch(typeof(AutoEngineerWaypointControls))]
[HarmonyPatch(nameof(AutoEngineerWaypointControls.ConfigureOptionsDropdown))]
[HarmonyPatch(typeof(LocomotiveControlsUIAdapter))]
[HarmonyPatch(nameof(LocomotiveControlsUIAdapter.UpdateCarText))]
[HarmonyPatchCategory("RMROC451TweaksAndThings")]
internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
internal class LocomotiveControlsUIAdapter_UpdateCarText_Postfix()
{
private static Serilog.ILogger _log => Log.ForContext<AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch>();
private static readonly HashSet<IDisposable> _keyChangeObservers = [];
private static Serilog.ILogger _log => Log.ForContext<LocomotiveControlsUIAdapter_UpdateCarText_Postfix>();
private static int lastSeenIntegrationSetCount = default;
private static string? lastLocoSeenCarId = default;
private static Coroutine? watchyWatchy = null;
private static HashSet<OpsCarPosition?> locoConsistDestinations = [];
private static Game.GameDateTime? timetableSaveTime = null;
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
{
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;
Messenger.Default.Unregister(placeholder);
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);
});
lastSeenIntegrationSetCount = selectedLoco.set.NumberOfCars;
IterateCarsDetectDestinations(
__instance,
__result,
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
((AutoEngineerWaypointControls)__instance.aiWaypointControls).ConfigureOptionsDropdown(),
selectedLoco,
numberOfCars,
destinations: destinations,
out List<DropdownMenu.RowData> rowDatas,
out Action<int> func,
out int origCount,
@@ -81,10 +82,11 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
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(
__instance,
var config = WireUpJumpTosToSettingMenu(
(AutoEngineerWaypointControls)__instance.aiWaypointControls,
selectedLoco,
rowDatas,
func,
@@ -93,23 +95,36 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
aeoh,
ref jumpTos
);
} catch(Exception ex)
{
_log.Error(ex, "I have a very unique set of skills; I will find you and I will squash you.");
List<DropdownMenu.RowData> list = config.Rows;
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;
string locoKey = getDictKey(selectedLoco);
List<Car> consist = new List<Car>();
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);
output |= !(_keyChangeObservers?.Any() ?? false);
//_log.Information($"{locoKey} --> [{destinations.Count}] -> Seen -> {string.Join(Environment.NewLine, destinations.Select(k => k.Value.DisplayName))}");
//_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;
_log.Information($"{locoKey} 3-> {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)
? metrics.Distance
: float.MaxValue;
};
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
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)
? metrics.Distance
: float.MaxValue;
};
}
;
_log.Debug($"{getDictKey(selectedLoco)} -> {destName} {destId} {distance?.ToString()}");
jumpTos.Add((
destinationId: destId,
@@ -253,7 +270,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
, sortdistance: sortdistance
, location: (Location?)destLoc
));
} catch (Exception ex)
}
catch (Exception ex)
{
_log.Warning(ex, $"Timetable entry not added to AE gear cog options {stp}");
}
@@ -264,42 +282,33 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
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();
func = __result.OnRowSelected;
origCount = rowDatas.Count;
maxRowOrig = origCount - 1;
locoConsistDestinations = [];
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);
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 =
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} --> [{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
seen.Clear();
locoConsistDestinations = destinations.ToList().ToHashSet(); //remove ones that are no longer here
}
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
selectedLoco = __instance.Locomotive;
numberOfCars = selectedLoco.set.NumberOfCars;
_log.Debug($"{getDictKey(selectedLoco)} --> HI BOB[{numberOfCars}]");
foreach (var o in _keyChangeObservers) o.Dispose();
_keyChangeObservers.Clear();
_log.Debug($"{selectedLoco.id} --> HI BOB[{numberOfCars}]");
}
private static OpsCarPosition? GetCarDestinationIdentifier(Car c)
@@ -323,7 +330,8 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
return destination;
}
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive) {
private static Location RouteStartLocation(AutoEngineerWaypointControls __instance, BaseLocomotive _locomotive)
{
bool num = _locomotive.IsStopped();
bool? flag = (num ? null : new bool?(_locomotive.velocity >= 0f));
@@ -347,75 +355,15 @@ internal class AutoEngineerWaypointControls_ConfigureOptionsDropdown_Patch
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);
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}");
}
return false;
}
}

View File

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