From 7c8f5af16fb4e47b63062998e2c75ce8d1f4aee0 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 29 Mar 2022 10:05:08 -0500 Subject: [PATCH 01/31] An untested concept for json-based persistence --- .../Lifecycle/SerializableDataExtension.cs | 36 +++++++++++++ TLM/TLM/State/Configuration.cs | 5 ++ TLM/TLM/State/IManagerSerialization.cs | 15 ++++++ .../State/ManagerSerializationReference.cs | 50 +++++++++++++++++++ TLM/TLM/TLM.csproj | 5 ++ TLM/TLM/packages.config | 1 + 6 files changed, 112 insertions(+) create mode 100644 TLM/TLM/State/IManagerSerialization.cs create mode 100644 TLM/TLM/State/ManagerSerializationReference.cs diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 8a011d365..15bc9056b 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -11,6 +11,8 @@ namespace TrafficManager.Lifecycle { using TrafficManager.State; using UI.WhatsNew; using Util; + using System.Linq; + using Newtonsoft.Json; [UsedImplicitly] public class SerializableDataExtension @@ -28,6 +30,21 @@ public class SerializableDataExtension public override void OnLoadData() => Load(); public override void OnSaveData() => Save(); + private static IList GetManagerSerialization() { + var result = new List(); + + foreach (var manager in TMPELifecycle.Instance.RegisteredManagers) { + var serialization = ManagerSerializationReference.ForManager(manager); + if (serialization != null) { + result.Add(serialization); + } + } + + result.Sort(); + + return result; + } + public static void Load() { Log.Info("Loading Traffic Manager: PE Data"); TMPELifecycle.Instance.Deserializing = true; @@ -341,6 +358,17 @@ private static void LoadDataState(out bool error) { } else { Log.Info("Segment-at-node structure undefined!"); } + + var managerSerialization = GetManagerSerialization(); + + if (_configuration.ManagerContainers != null) { + foreach (var e in _configuration.ManagerContainers) { + var ms = managerSerialization.SingleOrDefault(ms => ms.ContainerType.FullName == e.Key); + if (ms != null) { + ms.LoadData(JsonConvert.DeserializeObject(e.Value)); + } + } + } } public static void Save() { @@ -422,6 +450,14 @@ public static void Save() { configuration.LaneAllowedVehicleTypes = VehicleRestrictionsManager.Instance.SaveData(ref success); configuration.ParkingRestrictions = ParkingRestrictionsManager.Instance.SaveData(ref success); + //------------------ + // Individually Serialized Managers + //------------------ + configuration.ManagerContainers = new Dictionary(); + foreach (var ms in GetManagerSerialization()) { + configuration.ManagerContainers[ms.ContainerType.FullName] = JsonConvert.SerializeObject(ms.SaveData()); + } + //------------------ // Version //------------------ diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index bad0933d2..8d2f20385 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -355,6 +355,11 @@ public ExtCitizenData(uint citizenId) { /// public List ParkingRestrictions = new List(); + /// + /// Individually serialized manager containers + /// + public Dictionary ManagerContainers = new Dictionary(); + [Obsolete] public string NodeTrafficLights = string.Empty; diff --git a/TLM/TLM/State/IManagerSerialization.cs b/TLM/TLM/State/IManagerSerialization.cs new file mode 100644 index 000000000..a0c54acc4 --- /dev/null +++ b/TLM/TLM/State/IManagerSerialization.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.State { + internal interface IManagerSerialization { + + IEnumerable GetSerializationDependencies(); + + bool LoadData(T data); + + T SaveData(ref bool success); + } +} diff --git a/TLM/TLM/State/ManagerSerializationReference.cs b/TLM/TLM/State/ManagerSerializationReference.cs new file mode 100644 index 000000000..7ce4b183e --- /dev/null +++ b/TLM/TLM/State/ManagerSerializationReference.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace TrafficManager.State { + internal class ManagerSerializationReference : IComparable { + + public Type ManagerType { get; set; } + + public Type ContainerType { get; set; } + + public Func> GetDependencies { get; set; } + + public Func LoadData { get; set; } + + public Func SaveData { get; set; } + + public static ManagerSerializationReference ForManager(object manager) { + + var genericType = manager.GetType() + .GetInterfaces() + .SingleOrDefault(i => i.IsGenericType + && i.GetGenericTypeDefinition() == typeof(IManagerSerialization<>)); + + if (genericType != null) { + var getSerializationDependencies = genericType.GetMethod("GetSerializationDependencies"); + var loadData = genericType.GetMethod("LoadData"); + var saveData = genericType.GetMethod("SaveData"); + return new ManagerSerializationReference { + ManagerType = manager.GetType(), + ContainerType = genericType.GetGenericArguments()[0], + GetDependencies = () => (IEnumerable)getSerializationDependencies.Invoke(manager, null), + LoadData = data => (bool)loadData.Invoke(manager, new[] { data }), + SaveData = () => saveData.Invoke(manager, null), + }; + } + return null; + } + + public int CompareTo(ManagerSerializationReference other) { + var referencesOther = GetDependencies()?.Any(t => t.IsAssignableFrom(other.ManagerType)) ?? false; + var referencedByOther = other.GetDependencies()?.Any(t => t.IsAssignableFrom(ManagerType)) ?? false; + return referencesOther + ? referencedByOther ? 0 : 1 + : referencedByOther ? -1 : 0; + } + } +} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 31e3b9f28..f79892381 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -107,6 +107,9 @@ ..\libs\MoveItIntegration.dll + + ..\packages\Newtonsoft.Json.13.0.1\lib\net35\Newtonsoft.Json.dll + $(MangedDLLPath)\System.Core.dll @@ -151,6 +154,8 @@ + + diff --git a/TLM/TLM/packages.config b/TLM/TLM/packages.config index 72f3bb12b..7d407081c 100644 --- a/TLM/TLM/packages.config +++ b/TLM/TLM/packages.config @@ -3,6 +3,7 @@ + From 21818aebcd6f7fda4618ea29e99f4b2d937c75e4 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 29 Mar 2022 10:22:56 -0500 Subject: [PATCH 02/31] Untested demonstration code will certainly have issues like this --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 15bc9056b..c126d02b9 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -365,7 +365,7 @@ private static void LoadDataState(out bool error) { foreach (var e in _configuration.ManagerContainers) { var ms = managerSerialization.SingleOrDefault(ms => ms.ContainerType.FullName == e.Key); if (ms != null) { - ms.LoadData(JsonConvert.DeserializeObject(e.Value)); + ms.LoadData(JsonConvert.DeserializeObject(e.Value, ms.ContainerType)); } } } From 8ebe2e1ed0346d07b5d5e1b928aa1d7c110a5c72 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 2 Apr 2022 15:54:58 -0500 Subject: [PATCH 03/31] Move manager containers to their own stream so old serialization data is completely untouched --- .../Lifecycle/SerializableDataExtension.cs | 304 +++++++++--------- TLM/TLM/State/Configuration.cs | 5 - 2 files changed, 148 insertions(+), 161 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index c126d02b9..a0ea86c31 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -22,8 +22,12 @@ public class SerializableDataExtension private const string DATA_ID = "TrafficManager_v1.0"; private const string VERSION_INFO_DATA_ID = "TrafficManager_VersionInfo_v1.0"; + private const string CONTAINERS_ID = "TrafficManager_Containers_v1.0"; private static ISerializableData SerializableData => SimulationManager.instance.m_SerializableDataWrapper; + private static Dictionary _containers; + private static IList _managerSerialization; + private static Type[] _managerMigration; private static Configuration _configuration; private static VersionInfoConfiguration _versionInfoConfiguration; @@ -81,15 +85,36 @@ public static void Load() { loadingSucceeded = false; } + try { + byte[] data = SerializableData.LoadData(CONTAINERS_ID); + DeserializeContainerCollection(data); + } + catch (Exception e) { + Log.Error($"OnLoadData: Error while deserializing container collection (old savegame?): {e}"); + } + + bool loadedData = false; try { byte[] data = SerializableData.LoadData(DATA_ID); DeserializeData(data); + loadedData = true; } catch (Exception e) { Log.Error($"OnLoadData: Error while deserializing data: {e}"); + } + + bool loadedContainers = false; + try { + DeserializeContainers(); + loadedContainers = true; + } + catch (Exception e) { + Log.Error($"OnLoadData: Error while deserializing containers: {e}"); loadingSucceeded = false; } + loadingSucceeded &= loadedData || loadedContainers; + // load options (empty byte array causes default options to be applied) try { if (TMPELifecycle.InGameOrEditor()) { @@ -170,11 +195,61 @@ private static void DeserializeVersionData(byte[] data) { } } + private static void DeserializeContainerCollection(byte[] data) { + try { + if (data?.Length > 0) { + using (var memoryStream = new MemoryStream(data)) { + using (var streamReader = new StreamReader(memoryStream)) { + _containers = JsonConvert.DeserializeObject>(streamReader.ReadToEnd()); + + _managerSerialization = GetManagerSerialization(); + _managerMigration = _managerSerialization + .Where(ms => _containers.ContainsKey(ms.ContainerType.FullName)) + .Select(ms => ms.ManagerType) + .ToArray(); + } + } + } else { + Log.Info("No container collection to deserialize!"); + } + } + catch (Exception ex) { + Log.Error($"Error deserializing containers: {ex}"); + Log.Info(ex.StackTrace); + throw new ApplicationException("An error occurred while loading"); + } + } + + private static void DeserializeContainers() { + try { + if (_containers?.Count > 0 && _managerSerialization != null) { + foreach (var ms in _managerSerialization) { + if (_containers.TryGetValue(ms.ContainerType.FullName, out var containerData)) { + try { + ms.LoadData(JsonConvert.DeserializeObject(containerData, ms.ContainerType)); + } + catch (Exception ex) { + Log.Error($"Error deserializing container {ms.ContainerType.FullName}: {ex}"); + Log.Info(ex.StackTrace); + } + } + } + } else { + Log.Info("No containers to deserialize!"); + } + } + catch (Exception ex) { + Log.Error($"Error deserializing containers: {ex}"); + Log.Info(ex.StackTrace); + throw new ApplicationException("An error occurred while loading"); + } + } + private static void DeserializeData(byte[] data) { bool error = false; try { if (data != null && data.Length != 0) { - Log.Info($"Loading Data from New Load Routine! Length={data.Length}"); + Log.Info($"Loading Data from Old 'New' Load Routine! Length={data.Length}"); var memoryStream = new MemoryStream(); memoryStream.Write(data, 0, data.Length); memoryStream.Position = 0; @@ -219,156 +294,48 @@ private static void ReportVersionInfo(out bool error) { } } - private static void LoadDataState(out bool error) { - error = false; - - Log.Info("Loading State from Config"); - if (_configuration == null) { - Log.Warning("Configuration NULL, Couldn't load save data. Possibly a new game?"); - return; - } - - // load ext. citizens - if (_configuration.ExtCitizens != null) { - if (!ExtCitizenManager.Instance.LoadData(_configuration.ExtCitizens)) { - error = true; + private static void LoadDataState(ICustomDataManager manager, T data, string description, ref bool error) { + if (_managerMigration?.Contains(manager.GetType()) == true) { + if (data != null) { + Log.Info($"{manager.GetType().FullName} is in migration to manager-container strategy. {description} data structure ignored."); } - } else { - Log.Info("Ext. citizen data structure undefined!"); - } - - // load ext. citizen instances - if (_configuration.ExtCitizenInstances != null) { - if (!ExtCitizenInstanceManager.Instance.LoadData(_configuration.ExtCitizenInstances)) { - error = true; - } - } else { - Log.Info("Ext. citizen instance data structure undefined!"); } - - // load priority segments - if (_configuration.PrioritySegments != null) { - if (!TrafficPriorityManager.Instance.LoadData(_configuration.PrioritySegments)) { - error = true; + else if (data != null) { + if (_managerSerialization?.Any(ms => ms.ManagerType == manager.GetType()) == true) { + Log.Info($"Reading legacy {description} data structure (no manager-container was found)."); } - } else { - Log.Info("Priority segments data structure (old) undefined!"); - } - - if (_configuration.CustomPrioritySegments != null) { - if (!TrafficPriorityManager.Instance.LoadData(_configuration.CustomPrioritySegments)) { - error = true; - } - } else { - Log.Info("Priority segments data structure (new) undefined!"); - } - - // load parking restrictions - if (_configuration.ParkingRestrictions != null) { - if (!ParkingRestrictionsManager.Instance.LoadData(_configuration.ParkingRestrictions)) { - error = true; - } - } else { - Log.Info("Parking restrctions structure undefined!"); - } - - // load vehicle restrictions (warning: has to be done before loading timed lights!) - if (_configuration.LaneAllowedVehicleTypes != null) { - if (!VehicleRestrictionsManager.Instance.LoadData(_configuration.LaneAllowedVehicleTypes)) { - error = true; - } - } else { - Log.Info("Vehicle restrctions structure undefined!"); - } - - if (_configuration.TimedLights != null) { - if (!TrafficLightSimulationManager.Instance.LoadData(_configuration.TimedLights)) { - error = true; - } - } else { - Log.Info("Timed traffic lights data structure undefined!"); - } - - // load toggled traffic lights (old method) - if (_configuration.NodeTrafficLights != null) { - if (!TrafficLightManager.Instance.LoadData(_configuration.NodeTrafficLights)) { - error = true; - } - } else { - Log.Info("Junction traffic lights data structure (old) undefined!"); - } - - // load toggled traffic lights (new method) - if (_configuration.ToggledTrafficLights != null) { - if (!TrafficLightManager.Instance.LoadData(_configuration.ToggledTrafficLights)) { - error = true; - } - } else { - Log.Info("Junction traffic lights data structure (new) undefined!"); - } - - // load lane arrrows (old method) - if (_configuration.LaneFlags != null) { - if (!LaneArrowManager.Instance.LoadData(_configuration.LaneFlags)) { - error = true; - } - } else { - Log.Info("Lane arrow data structure (old) undefined!"); - } - - // load lane arrows (new method) - if (_configuration.LaneArrows != null) { - if (!LaneArrowManager.Instance.LoadData(_configuration.LaneArrows)) { - error = true; - } - } else { - Log.Info("Lane arrow data structure (new) undefined!"); - } - - // load lane connections - if (_configuration.LaneConnections != null) { - if (!LaneConnectionManager.Instance.LoadData(_configuration.LaneConnections)) { - error = true; - } - } else { - Log.Info("Lane connection data structure undefined!"); - } - - // Load custom default speed limits - if (_configuration.CustomDefaultSpeedLimits != null) { - if (!SpeedLimitManager.Instance.LoadData(_configuration.CustomDefaultSpeedLimits)) { + if (!manager.LoadData(data)) { error = true; } + } else if (description != null) { + Log.Info($"{description} data structure undefined!"); } + } - // load speed limits - if (_configuration.LaneSpeedLimits != null) { - if (!SpeedLimitManager.Instance.LoadData(_configuration.LaneSpeedLimits)) { - error = true; - } - } else { - Log.Info("Lane speed limit structure undefined!"); - } + private static void LoadDataState(out bool error) { + error = false; - // Load segment-at-node flags - if (_configuration.SegmentNodeConfs != null) { - if (!JunctionRestrictionsManager.Instance.LoadData(_configuration.SegmentNodeConfs)) { - error = true; - } - } else { - Log.Info("Segment-at-node structure undefined!"); + Log.Info("Loading State from Config"); + if (_configuration == null) { + Log.Warning("Configuration NULL, Couldn't load save data. Possibly a new game?"); + return; } - var managerSerialization = GetManagerSerialization(); - - if (_configuration.ManagerContainers != null) { - foreach (var e in _configuration.ManagerContainers) { - var ms = managerSerialization.SingleOrDefault(ms => ms.ContainerType.FullName == e.Key); - if (ms != null) { - ms.LoadData(JsonConvert.DeserializeObject(e.Value, ms.ContainerType)); - } - } - } + LoadDataState(ExtCitizenManager.Instance, _configuration.ExtCitizens, "Ext. citizen", ref error); + LoadDataState(ExtCitizenInstanceManager.Instance, _configuration.ExtCitizenInstances, "Ext. citizen instance", ref error); + LoadDataState(TrafficPriorityManager.Instance, _configuration.PrioritySegments, "Priority segments (old)", ref error); + LoadDataState(TrafficPriorityManager.Instance, _configuration.CustomPrioritySegments, "Priority segments (new)", ref error); + LoadDataState(ParkingRestrictionsManager.Instance, _configuration.ParkingRestrictions, "Parking restrctions", ref error); + LoadDataState(VehicleRestrictionsManager.Instance, _configuration.LaneAllowedVehicleTypes, "Vehicle restrctions", ref error); + LoadDataState(TrafficLightSimulationManager.Instance, _configuration.TimedLights, "Timed traffic lights", ref error); + LoadDataState(TrafficLightManager.Instance, _configuration.NodeTrafficLights, "Junction traffic lights (old)", ref error); + LoadDataState(TrafficLightManager.Instance, _configuration.ToggledTrafficLights, "Junction traffic lights (new)", ref error); + LoadDataState(LaneArrowManager.Instance, _configuration.LaneFlags, "Lane arrow (old)", ref error); + LoadDataState(LaneArrowManager.Instance, _configuration.LaneArrows, "Lane arrow (new)", ref error); + LoadDataState(LaneConnectionManager.Instance, _configuration.LaneConnections, "Lane connection", ref error); + LoadDataState(SpeedLimitManager.Instance, _configuration.CustomDefaultSpeedLimits, "Default speed limit", ref error); + LoadDataState(SpeedLimitManager.Instance, _configuration.LaneSpeedLimits, "Lane speed limit", ref error); + LoadDataState(JunctionRestrictionsManager.Instance, _configuration.SegmentNodeConfs, "Segment-at-node", ref error); } public static void Save() { @@ -450,14 +417,6 @@ public static void Save() { configuration.LaneAllowedVehicleTypes = VehicleRestrictionsManager.Instance.SaveData(ref success); configuration.ParkingRestrictions = ParkingRestrictionsManager.Instance.SaveData(ref success); - //------------------ - // Individually Serialized Managers - //------------------ - configuration.ManagerContainers = new Dictionary(); - foreach (var ms in GetManagerSerialization()) { - configuration.ManagerContainers[ms.ContainerType.FullName] = JsonConvert.SerializeObject(ms.SaveData()); - } - //------------------ // Version //------------------ @@ -472,10 +431,12 @@ public static void Save() { memoryStreamVersion.Position = 0; Log.Info($"Version data byte length {memoryStreamVersion.Length}"); SerializableData.SaveData(VERSION_INFO_DATA_ID, memoryStreamVersion.ToArray()); - } catch (Exception ex) { + } + catch (Exception ex) { Log.Error("Unexpected error while saving version data: " + ex); success = false; - } finally { + } + finally { memoryStreamVersion.Close(); } @@ -483,7 +444,8 @@ public static void Save() { if (TMPELifecycle.PlayMode) { SerializableData.SaveData("TMPE_Options", OptionsManager.Instance.SaveData(ref success)); } - } catch (Exception ex) { + } + catch (Exception ex) { Log.Error("Unexpected error while saving options: " + ex.Message); success = false; } @@ -496,13 +458,17 @@ public static void Save() { memoryStream.Position = 0; Log.Info($"Save data byte length {memoryStream.Length}"); SerializableData.SaveData(DATA_ID, memoryStream.ToArray()); - } catch (Exception ex) { + } + catch (Exception ex) { Log.Error("Unexpected error while saving data: " + ex); success = false; - } finally { + } + finally { memoryStream.Close(); } + SaveContainers(ref success); + var reverseManagers = new List(TMPELifecycle.Instance.RegisteredManagers); reverseManagers.Reverse(); foreach (ICustomManager manager in reverseManagers) { @@ -516,7 +482,8 @@ public static void Save() { success = false; } } - } catch (Exception e) { + } + catch (Exception e) { success = false; Log.Error($"Error occurred while saving data: {e}"); @@ -527,5 +494,30 @@ public static void Save() { // the steps under 'In case problems arise'.", true); } } + + private static void SaveContainers(ref bool success) { + + try { + var managerContainers = new Dictionary(); + foreach (var ms in GetManagerSerialization()) { + managerContainers[ms.ContainerType.FullName] = JsonConvert.SerializeObject(ms.SaveData()); + } + + using (var memoryStream = new MemoryStream()) { + using (var streamWriter = new StreamWriter(memoryStream)) { + streamWriter.Write(JsonConvert.SerializeObject(managerContainers)); + } + + memoryStream.Position = 0; + Log.Info($"Save containers byte length {memoryStream.Length}"); + + SerializableData.SaveData(CONTAINERS_ID, memoryStream.ToArray()); + } + } + catch (Exception ex) { + Log.Error("Unexpected error while saving containers: " + ex); + success = false; + } + } } -} \ No newline at end of file +} diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index 8d2f20385..bad0933d2 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -355,11 +355,6 @@ public ExtCitizenData(uint citizenId) { /// public List ParkingRestrictions = new List(); - /// - /// Individually serialized manager containers - /// - public Dictionary ManagerContainers = new Dictionary(); - [Obsolete] public string NodeTrafficLights = string.Empty; From 3e80477329aa4684caaf49349781580c0f9aabb9 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 2 Apr 2022 16:24:07 -0500 Subject: [PATCH 04/31] UTF-8 will pretty much cut the size of the serialized data in half, since non-ASCII characters are expected to be rare in TMPE data. --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index a0ea86c31..e69e783a7 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -13,6 +13,7 @@ namespace TrafficManager.Lifecycle { using Util; using System.Linq; using Newtonsoft.Json; + using System.Text; [UsedImplicitly] public class SerializableDataExtension @@ -199,7 +200,7 @@ private static void DeserializeContainerCollection(byte[] data) { try { if (data?.Length > 0) { using (var memoryStream = new MemoryStream(data)) { - using (var streamReader = new StreamReader(memoryStream)) { + using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { _containers = JsonConvert.DeserializeObject>(streamReader.ReadToEnd()); _managerSerialization = GetManagerSerialization(); @@ -504,7 +505,7 @@ private static void SaveContainers(ref bool success) { } using (var memoryStream = new MemoryStream()) { - using (var streamWriter = new StreamWriter(memoryStream)) { + using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { streamWriter.Write(JsonConvert.SerializeObject(managerContainers)); } From c1791561443765a627bbc14c292321afcf41fa39 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Thu, 7 Apr 2022 01:10:23 -0500 Subject: [PATCH 05/31] Basic Linq to XML based framework --- .../Lifecycle/SerializableDataExtension.cs | 97 +++++----- TLM/TLM/State/FeatureFilter.cs | 173 ++++++++++++++++++ TLM/TLM/State/IManagerSerialization.cs | 15 -- TLM/TLM/State/IPersistentObject.cs | 22 +++ .../State/ManagerSerializationReference.cs | 50 ----- TLM/TLM/State/Persistence.cs | 11 ++ TLM/TLM/State/PersistenceContext.cs | 12 ++ TLM/TLM/State/PersistenceResult.cs | 12 ++ TLM/TLM/State/PersistentObject.cs | 138 ++++++++++++++ TLM/TLM/State/StatePersistenceException.cs | 12 ++ TLM/TLM/TLM.csproj | 13 +- TLM/TLM/packages.config | 1 - 12 files changed, 432 insertions(+), 124 deletions(-) create mode 100644 TLM/TLM/State/FeatureFilter.cs delete mode 100644 TLM/TLM/State/IManagerSerialization.cs create mode 100644 TLM/TLM/State/IPersistentObject.cs delete mode 100644 TLM/TLM/State/ManagerSerializationReference.cs create mode 100644 TLM/TLM/State/Persistence.cs create mode 100644 TLM/TLM/State/PersistenceContext.cs create mode 100644 TLM/TLM/State/PersistenceResult.cs create mode 100644 TLM/TLM/State/PersistentObject.cs create mode 100644 TLM/TLM/State/StatePersistenceException.cs diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index e69e783a7..e5063d01e 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -12,8 +12,8 @@ namespace TrafficManager.Lifecycle { using UI.WhatsNew; using Util; using System.Linq; - using Newtonsoft.Json; using System.Text; + using System.Xml.Linq; [UsedImplicitly] public class SerializableDataExtension @@ -23,33 +23,17 @@ public class SerializableDataExtension private const string DATA_ID = "TrafficManager_v1.0"; private const string VERSION_INFO_DATA_ID = "TrafficManager_VersionInfo_v1.0"; - private const string CONTAINERS_ID = "TrafficManager_Containers_v1.0"; + private const string DOM_ID = "TrafficManager_Document_v1.0"; private static ISerializableData SerializableData => SimulationManager.instance.m_SerializableDataWrapper; - private static Dictionary _containers; - private static IList _managerSerialization; - private static Type[] _managerMigration; + private static XDocument _dom; + private static Type[] _persistenceMigration; private static Configuration _configuration; private static VersionInfoConfiguration _versionInfoConfiguration; public override void OnLoadData() => Load(); public override void OnSaveData() => Save(); - private static IList GetManagerSerialization() { - var result = new List(); - - foreach (var manager in TMPELifecycle.Instance.RegisteredManagers) { - var serialization = ManagerSerializationReference.ForManager(manager); - if (serialization != null) { - result.Add(serialization); - } - } - - result.Sort(); - - return result; - } - public static void Load() { Log.Info("Loading Traffic Manager: PE Data"); TMPELifecycle.Instance.Deserializing = true; @@ -87,8 +71,8 @@ public static void Load() { } try { - byte[] data = SerializableData.LoadData(CONTAINERS_ID); - DeserializeContainerCollection(data); + byte[] data = SerializableData.LoadData(DOM_ID); + LoadDom(data); } catch (Exception e) { Log.Error($"OnLoadData: Error while deserializing container collection (old savegame?): {e}"); @@ -106,7 +90,7 @@ public static void Load() { bool loadedContainers = false; try { - DeserializeContainers(); + LoadDomElements(); loadedContainers = true; } catch (Exception e) { @@ -196,51 +180,55 @@ private static void DeserializeVersionData(byte[] data) { } } - private static void DeserializeContainerCollection(byte[] data) { + private static void LoadDom(byte[] data) { try { if (data?.Length > 0) { using (var memoryStream = new MemoryStream(data)) { using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { - _containers = JsonConvert.DeserializeObject>(streamReader.ReadToEnd()); + _dom = XDocument.Load(streamReader); - _managerSerialization = GetManagerSerialization(); - _managerMigration = _managerSerialization - .Where(ms => _containers.ContainsKey(ms.ContainerType.FullName)) - .Select(ms => ms.ManagerType) - .ToArray(); + _persistenceMigration = Persistence.PersistentObjects + .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) + .Select(o => o.DependencyTarget) + .Distinct() + .ToArray(); } } } else { - Log.Info("No container collection to deserialize!"); + Log.Info("No DOM to load!"); } } catch (Exception ex) { - Log.Error($"Error deserializing containers: {ex}"); + Log.Error($"Error loading DOM: {ex}"); Log.Info(ex.StackTrace); throw new ApplicationException("An error occurred while loading"); } } - private static void DeserializeContainers() { + private static void LoadDomElements() { try { - if (_containers?.Count > 0 && _managerSerialization != null) { - foreach (var ms in _managerSerialization) { - if (_containers.TryGetValue(ms.ContainerType.FullName, out var containerData)) { + if (_dom?.Root.HasElements == true && Persistence.PersistentObjects.Count > 0) { + foreach (var o in Persistence.PersistentObjects) { + var containers = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); + if (containers?.Any() == true) { + if (containers.Count() > 1) { + Log.Error($"More than one compatible element {o.ElementName} was found. Using the last one."); + } try { - ms.LoadData(JsonConvert.DeserializeObject(containerData, ms.ContainerType)); + o.LoadData(containers.Last(), new PersistenceContext { Version = Version }); } catch (Exception ex) { - Log.Error($"Error deserializing container {ms.ContainerType.FullName}: {ex}"); + Log.Error($"Error deserializing DOM element {o.ElementName}: {ex}"); Log.Info(ex.StackTrace); } } } } else { - Log.Info("No containers to deserialize!"); + Log.Info("No DOM elements to load!"); } } catch (Exception ex) { - Log.Error($"Error deserializing containers: {ex}"); + Log.Error($"Error loading DOM elements: {ex}"); Log.Info(ex.StackTrace); throw new ApplicationException("An error occurred while loading"); } @@ -296,14 +284,14 @@ private static void ReportVersionInfo(out bool error) { } private static void LoadDataState(ICustomDataManager manager, T data, string description, ref bool error) { - if (_managerMigration?.Contains(manager.GetType()) == true) { + if (_persistenceMigration?.Contains(manager.GetType()) == true) { if (data != null) { - Log.Info($"{manager.GetType().FullName} is in migration to manager-container strategy. {description} data structure ignored."); + Log.Info($"{manager.GetType().FullName} is in migration to DOM. {description} data structure ignored."); } } else if (data != null) { - if (_managerSerialization?.Any(ms => ms.ManagerType == manager.GetType()) == true) { - Log.Info($"Reading legacy {description} data structure (no manager-container was found)."); + if (Persistence.PersistentObjects.Any(o => o.DependencyTarget == manager.GetType())) { + Log.Info($"Reading legacy {description} data structure (no DOM element was found)."); } if (!manager.LoadData(data)) { error = true; @@ -468,7 +456,7 @@ public static void Save() { memoryStream.Close(); } - SaveContainers(ref success); + SaveDom(ref success); var reverseManagers = new List(TMPELifecycle.Instance.RegisteredManagers); reverseManagers.Reverse(); @@ -496,27 +484,30 @@ public static void Save() { } } - private static void SaveContainers(ref bool success) { + private static void SaveDom(ref bool success) { try { - var managerContainers = new Dictionary(); - foreach (var ms in GetManagerSerialization()) { - managerContainers[ms.ContainerType.FullName] = JsonConvert.SerializeObject(ms.SaveData()); + _dom = new XDocument(); + foreach (var o in Persistence.PersistentObjects) { + var container = new XElement(o.ElementName); + if (o.SaveData(container, new PersistenceContext { Version = Version }) == PersistenceResult.Success) { + _dom.Root.Add(container); + } } using (var memoryStream = new MemoryStream()) { using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { - streamWriter.Write(JsonConvert.SerializeObject(managerContainers)); + _dom.Save(streamWriter); } memoryStream.Position = 0; - Log.Info($"Save containers byte length {memoryStream.Length}"); + Log.Info($"Save DOM byte length {memoryStream.Length}"); - SerializableData.SaveData(CONTAINERS_ID, memoryStream.ToArray()); + SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); } } catch (Exception ex) { - Log.Error("Unexpected error while saving containers: " + ex); + Log.Error("Unexpected error while saving DOM: " + ex); success = false; } } diff --git a/TLM/TLM/State/FeatureFilter.cs b/TLM/TLM/State/FeatureFilter.cs new file mode 100644 index 000000000..ab1cacb5e --- /dev/null +++ b/TLM/TLM/State/FeatureFilter.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.State { + internal sealed class FeatureFilter where TFeature : struct { + + private const string featuresRequiredAttributeName = "featuresRequired"; + private const string featuresForbiddenAttributeName = "featuresForbidden"; + + private enum FeatureStatus { + Forbidden, + Required, + } + + static FeatureFilter() { + Type tFeature = typeof(TFeature); + if (!tFeature.IsEnum) + throw new StatePersistenceException($"FeatureSet<{tFeature.FullName}>: Type {tFeature.Name} is not an enum"); + } + + private readonly Dictionary filter; + + public static bool CanLoad(XElement element, out FeatureFilter result) { + return CanRead(element.Attribute(featuresRequiredAttributeName)?.Value, + element.Attribute(featuresForbiddenAttributeName)?.Value, + out result); + } + + public static bool CanRead(string requiredAttribute, string forbiddenAttribute, out FeatureFilter result) { + + result = null; + + if (!string.IsNullOrEmpty(forbiddenAttribute)) { + foreach (var featureName in forbiddenAttribute.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) { + try { + Enum.Parse(typeof(TFeature), featureName); + return false; + } + catch { + } + } + } + var filter = new Dictionary(); + if (!string.IsNullOrEmpty(requiredAttribute)) { + foreach (var featureName in requiredAttribute.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) { + try { + filter[(TFeature)Enum.Parse(typeof(TFeature), featureName)] = FeatureStatus.Required; + } + catch { + return false; + } + } + } + result = new FeatureFilter(filter); + return true; + } + + public FeatureFilter() => filter = new Dictionary(); + + private FeatureFilter(Dictionary filter) => this.filter = filter; + + public void Clear() => filter.Clear(); + + public void Require(TFeature feature) => filter[feature] = FeatureStatus.Required; + + public void Forbid(TFeature feature) => filter[feature] = FeatureStatus.Forbidden; + + public void Disregard(TFeature feature) => filter.Remove(feature); + + public bool IsRequired(TFeature feature) { + return filter.TryGetValue(feature, out var status) + && status == FeatureStatus.Required; + } + + public bool IsForbidden(TFeature feature) { + return filter.TryGetValue(feature, out var status) + && status == FeatureStatus.Forbidden; + } + + public bool IsAnyRequired() => filter.ContainsValue(FeatureStatus.Required); + + public bool IsAnyForbidden() => filter.ContainsValue(FeatureStatus.Forbidden); + + private IEnumerable Enumerate(FeatureStatus status) + => filter.Where(e => e.Value == status).Select(e => e.Key).OrderByDescending(f => f); + + public IEnumerable GetRequired() + => Enumerate(FeatureStatus.Required); + + public IEnumerable GetForbidden() + => Enumerate(FeatureStatus.Forbidden); + + public ICollection GetRequiredCollection(bool readOnly) + => new FeatureCollection(this, FeatureStatus.Required, readOnly); + + public ICollection GetForbiddenCollection() + => new FeatureCollection(this, FeatureStatus.Forbidden, true); + + private class FeatureCollection : ICollection { + private readonly FeatureFilter featureFilter; + private readonly FeatureStatus collectionFeatureStatus; + private readonly bool isReadOnly; + + public FeatureCollection(FeatureFilter featureFilter, FeatureStatus collectionFeatureStatus, bool isReadOnly) { + this.featureFilter = featureFilter; + this.collectionFeatureStatus = collectionFeatureStatus; + this.isReadOnly = isReadOnly; + } + + public int Count => featureFilter.filter.Values.Where(v => v == collectionFeatureStatus).Count(); + + public bool IsReadOnly => isReadOnly; + + public void Add(TFeature item) { + if (isReadOnly) + throw new NotSupportedException(); + if (featureFilter.filter.TryGetValue(item, out var existingStatus) && existingStatus != collectionFeatureStatus) + throw new StatePersistenceException($"{item} could not be added to the required collection because it is already forbidden."); + featureFilter.filter[item] = collectionFeatureStatus; + } + + public void Clear() { + if (isReadOnly) + throw new NotSupportedException(); + foreach (var e in featureFilter.filter.Where(e => e.Value == collectionFeatureStatus).ToArray()) + featureFilter.filter.Remove(e.Key); + } + + public bool Contains(TFeature item) { + return featureFilter.filter.TryGetValue(item, out var status) + && status == collectionFeatureStatus; + } + + public void CopyTo(TFeature[] array, int arrayIndex) { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + => featureFilter.Enumerate(collectionFeatureStatus).GetEnumerator(); + + public bool Remove(TFeature item) { + if (isReadOnly) + throw new NotSupportedException(); + + return featureFilter.filter.TryGetValue(item, out var status) + && status == collectionFeatureStatus + && featureFilter.filter.Remove(item); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public object[] GetAttributes() { + var result = new ArrayList(); + + if (IsAnyRequired()) { + result.Add(new XAttribute(featuresRequiredAttributeName, + string.Join(" ", GetRequired().Select(f => f.ToString()).ToArray()))); + } + + if (IsAnyForbidden()) { + result.Add(new XAttribute(featuresForbiddenAttributeName, + string.Join(" ", GetForbidden().Select(f => f.ToString()).ToArray()))); + } + + return result.ToArray(); + } + } +} diff --git a/TLM/TLM/State/IManagerSerialization.cs b/TLM/TLM/State/IManagerSerialization.cs deleted file mode 100644 index a0c54acc4..000000000 --- a/TLM/TLM/State/IManagerSerialization.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State { - internal interface IManagerSerialization { - - IEnumerable GetSerializationDependencies(); - - bool LoadData(T data); - - T SaveData(ref bool success); - } -} diff --git a/TLM/TLM/State/IPersistentObject.cs b/TLM/TLM/State/IPersistentObject.cs new file mode 100644 index 000000000..d58a6d5ae --- /dev/null +++ b/TLM/TLM/State/IPersistentObject.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.State { + internal interface IPersistentObject { + + Type DependencyTarget { get; } + + IEnumerable GetDependencies(); + + string ElementName { get; } + + bool CanLoad(XElement element); + + PersistenceResult LoadData(XElement element, PersistenceContext context); + + PersistenceResult SaveData(XElement container, PersistenceContext context); + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ManagerSerializationReference.cs b/TLM/TLM/State/ManagerSerializationReference.cs deleted file mode 100644 index 7ce4b183e..000000000 --- a/TLM/TLM/State/ManagerSerializationReference.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace TrafficManager.State { - internal class ManagerSerializationReference : IComparable { - - public Type ManagerType { get; set; } - - public Type ContainerType { get; set; } - - public Func> GetDependencies { get; set; } - - public Func LoadData { get; set; } - - public Func SaveData { get; set; } - - public static ManagerSerializationReference ForManager(object manager) { - - var genericType = manager.GetType() - .GetInterfaces() - .SingleOrDefault(i => i.IsGenericType - && i.GetGenericTypeDefinition() == typeof(IManagerSerialization<>)); - - if (genericType != null) { - var getSerializationDependencies = genericType.GetMethod("GetSerializationDependencies"); - var loadData = genericType.GetMethod("LoadData"); - var saveData = genericType.GetMethod("SaveData"); - return new ManagerSerializationReference { - ManagerType = manager.GetType(), - ContainerType = genericType.GetGenericArguments()[0], - GetDependencies = () => (IEnumerable)getSerializationDependencies.Invoke(manager, null), - LoadData = data => (bool)loadData.Invoke(manager, new[] { data }), - SaveData = () => saveData.Invoke(manager, null), - }; - } - return null; - } - - public int CompareTo(ManagerSerializationReference other) { - var referencesOther = GetDependencies()?.Any(t => t.IsAssignableFrom(other.ManagerType)) ?? false; - var referencedByOther = other.GetDependencies()?.Any(t => t.IsAssignableFrom(ManagerType)) ?? false; - return referencesOther - ? referencedByOther ? 0 : 1 - : referencedByOther ? -1 : 0; - } - } -} diff --git a/TLM/TLM/State/Persistence.cs b/TLM/TLM/State/Persistence.cs new file mode 100644 index 000000000..46ae24dda --- /dev/null +++ b/TLM/TLM/State/Persistence.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.State { + internal static class Persistence { + + public static List PersistentObjects { get; } = new List(); + } +} diff --git a/TLM/TLM/State/PersistenceContext.cs b/TLM/TLM/State/PersistenceContext.cs new file mode 100644 index 000000000..b3d9e7cfc --- /dev/null +++ b/TLM/TLM/State/PersistenceContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.State { + internal class PersistenceContext { + + public int Version { get; set; } + } +} diff --git a/TLM/TLM/State/PersistenceResult.cs b/TLM/TLM/State/PersistenceResult.cs new file mode 100644 index 000000000..ce5a418d7 --- /dev/null +++ b/TLM/TLM/State/PersistenceResult.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.State { + internal enum PersistenceResult { + Success = 0, + Failure = 1, + Skip = 2, + } +} diff --git a/TLM/TLM/State/PersistentObject.cs b/TLM/TLM/State/PersistentObject.cs new file mode 100644 index 000000000..d11457d2c --- /dev/null +++ b/TLM/TLM/State/PersistentObject.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.State { + internal abstract class PersistentObject : IPersistentObject + where TFeature : struct + { + + protected abstract Type DependencyTarget { get; } + + protected abstract IEnumerable GetDependencies(); + + public abstract string ElementName { get; } + + Type IPersistentObject.DependencyTarget => DependencyTarget; + + /// + /// Moves one or more of 's required features to the forbidden list, + /// then clears any remaining required features. + /// + /// + protected virtual void ChooseVictim(FeatureFilter featureFilter) { + var victim = featureFilter.GetRequired().First(); + featureFilter.GetRequiredCollection(false).Clear(); + featureFilter.Forbid(victim); + } + + /// + /// Checks whether the element is feature-compatible with this build. + /// + /// + /// true if feature-compatible, otherwise false + public bool CanLoad(XElement element) => FeatureFilter.CanLoad(element, out var _); + + /// + /// Override to load data + /// + /// An XML element that was filled by + /// The object that was loaded + /// A read-only collection of features that were known to the build that created this element. + /// When a particular feature caused a breaking change in the save data, the absence of that feature from this collection + /// means that the data must be read the old way. + /// The persistence context of this load operation + /// + protected abstract PersistenceResult LoadData(XElement element, out TObject obj, ICollection featuresRequired, PersistenceContext context); + + /// + /// Verifies feature compatibility and conditionally calls the overridable + /// . + /// + /// + /// + /// + /// + public PersistenceResult LoadData(XElement element, out TObject obj, PersistenceContext context) { + + obj = default; + return FeatureFilter.CanLoad(element, out var featureFilter) + ? LoadData(element, out obj, featureFilter.GetRequiredCollection(true), context) + : PersistenceResult.Skip; + } + + /// + /// Override to save data + /// + /// An XML element to be filled with save data + /// The object to be saved + /// A collection of features this save data depends on. When a new feature introduces a + /// breaking change, the feature must be added to this collection to avoid data loss when if the player reverts to an earlier build. + /// A collection of features that are to be omitted from the save data for backward compatibility. + /// If the implementation cannot write backward-compatible save data that omits the features in this collection, + /// it must return . + /// The persistence context of this save operation + /// + protected abstract PersistenceResult SaveData(XElement element, TObject obj, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); + + /// + /// Calls . + /// When that method reports the use of features that introduced breaking changes, successive calls are made to request + /// backward-compatible save data. + /// + /// + /// + /// + /// + public PersistenceResult SaveData(XElement container, TObject obj, PersistenceContext context) { + var featureFilter = new FeatureFilter(); + + var element = new XElement(ElementName); + var result = SaveData(element, + obj, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection(), + context); + + if (result == PersistenceResult.Success) { + + element.Add(featureFilter.GetAttributes()); + container.Add(element); + + while (result == PersistenceResult.Success && featureFilter.IsAnyRequired()) { + + ChooseVictim(featureFilter); + + if (featureFilter.GetForbiddenCollection().Count == Enum.GetValues(typeof(TFeature)).Length) + break; + + element = new XElement(ElementName); + result = SaveData(element, + obj, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection(), + context); + + if (result == PersistenceResult.Success) { + element.Add(featureFilter.GetAttributes()); + container.Add(element); + } + } + if (result == PersistenceResult.Skip) + result = PersistenceResult.Success; + } + + return result; + } + + PersistenceResult IPersistentObject.LoadData(XElement element, PersistenceContext context) + => LoadData(element, out var _, context); + + PersistenceResult IPersistentObject.SaveData(XElement container, PersistenceContext context) + => SaveData(container, default, context); + + IEnumerable IPersistentObject.GetDependencies() => GetDependencies(); + } +} diff --git a/TLM/TLM/State/StatePersistenceException.cs b/TLM/TLM/State/StatePersistenceException.cs new file mode 100644 index 000000000..64e3410f9 --- /dev/null +++ b/TLM/TLM/State/StatePersistenceException.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.State { + internal class StatePersistenceException : Exception { + public StatePersistenceException(string message) + : base(message) + { } + } +} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index b6052d9c0..8f05e1f00 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -107,15 +107,13 @@ ..\libs\MoveItIntegration.dll - - ..\packages\Newtonsoft.Json.13.0.1\lib\net35\Newtonsoft.Json.dll - $(MangedDLLPath)\System.Core.dll + ..\packages\UnifiedUILib.2.2.1\lib\net35\UnifiedUILib.dll @@ -154,8 +152,11 @@ - - + + + + + @@ -172,6 +173,8 @@ + + diff --git a/TLM/TLM/packages.config b/TLM/TLM/packages.config index 7d407081c..72f3bb12b 100644 --- a/TLM/TLM/packages.config +++ b/TLM/TLM/packages.config @@ -3,7 +3,6 @@ - From 0fa9a854ad08bb16ec5955d72978ff05e8a1ed1b Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Thu, 7 Apr 2022 20:30:57 -0500 Subject: [PATCH 06/31] Account for persistent object dependencies --- .../Lifecycle/SerializableDataExtension.cs | 21 +++++++++---------- TLM/TLM/State/IPersistentObject.cs | 2 +- TLM/TLM/State/PersistenceResult.cs | 17 +++++++++++++++ TLM/TLM/State/PersistentObject.cs | 15 ++++++++++++- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index e5063d01e..291a2b247 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -208,17 +208,18 @@ private static void LoadDom(byte[] data) { private static void LoadDomElements() { try { if (_dom?.Root.HasElements == true && Persistence.PersistentObjects.Count > 0) { - foreach (var o in Persistence.PersistentObjects) { - var containers = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); - if (containers?.Any() == true) { - if (containers.Count() > 1) { + foreach (var o in Persistence.PersistentObjects.OrderBy(o => o)) { + var elements = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); + if (elements?.Any() == true) { + if (elements.Count() > 1) { Log.Error($"More than one compatible element {o.ElementName} was found. Using the last one."); } try { - o.LoadData(containers.Last(), new PersistenceContext { Version = Version }); + var result = o.LoadData(elements.Last(), new PersistenceContext { Version = Version }); + result.LogMessage($"LoadData for DOM element {o.ElementName} reported {result}."); } catch (Exception ex) { - Log.Error($"Error deserializing DOM element {o.ElementName}: {ex}"); + Log.Error($"Error loading DOM element {o.ElementName}: {ex}"); Log.Info(ex.StackTrace); } } @@ -488,11 +489,9 @@ private static void SaveDom(ref bool success) { try { _dom = new XDocument(); - foreach (var o in Persistence.PersistentObjects) { - var container = new XElement(o.ElementName); - if (o.SaveData(container, new PersistenceContext { Version = Version }) == PersistenceResult.Success) { - _dom.Root.Add(container); - } + foreach (var o in Persistence.PersistentObjects.OrderBy(o => o)) { + var result = o.SaveData(_dom.Root, new PersistenceContext { Version = Version }); + result.LogMessage($"SaveData for DOM element {o.ElementName} reported {result}."); } using (var memoryStream = new MemoryStream()) { diff --git a/TLM/TLM/State/IPersistentObject.cs b/TLM/TLM/State/IPersistentObject.cs index d58a6d5ae..2859342ff 100644 --- a/TLM/TLM/State/IPersistentObject.cs +++ b/TLM/TLM/State/IPersistentObject.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace TrafficManager.State { - internal interface IPersistentObject { + internal interface IPersistentObject : IComparable { Type DependencyTarget { get; } diff --git a/TLM/TLM/State/PersistenceResult.cs b/TLM/TLM/State/PersistenceResult.cs index ce5a418d7..55a6aba5f 100644 --- a/TLM/TLM/State/PersistenceResult.cs +++ b/TLM/TLM/State/PersistenceResult.cs @@ -1,3 +1,4 @@ +using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; @@ -9,4 +10,20 @@ internal enum PersistenceResult { Failure = 1, Skip = 2, } + + internal static class PersistenceResultExtensions { + public static void LogMessage(this PersistenceResult result, string message) { + switch (result) { + case PersistenceResult.Failure: + default: + Log.Warning(message); + break; + + case PersistenceResult.Success: + case PersistenceResult.Skip: + Log.Info(message); + break; + } + } + } } diff --git a/TLM/TLM/State/PersistentObject.cs b/TLM/TLM/State/PersistentObject.cs index d11457d2c..de289729b 100644 --- a/TLM/TLM/State/PersistentObject.cs +++ b/TLM/TLM/State/PersistentObject.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace TrafficManager.State { - internal abstract class PersistentObject : IPersistentObject + internal abstract class PersistentObject : IPersistentObject, IComparable> where TFeature : struct { @@ -134,5 +134,18 @@ PersistenceResult IPersistentObject.SaveData(XElement container, PersistenceCont => SaveData(container, default, context); IEnumerable IPersistentObject.GetDependencies() => GetDependencies(); + + public int CompareTo(PersistentObject other) { + + bool thisDependsOnOther = GetDependencies()?.Contains(other.DependencyTarget) == true; + bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; + + return + thisDependsOnOther == otherDependsOnThis ? ElementName.CompareTo(other.ElementName) + : thisDependsOnOther ? 1 + : -1; + } + + int IComparable.CompareTo(object obj) => CompareTo((PersistentObject)obj); } } From f6cc9fd7abbec87d5c95bf65ed6f8522b9e3490e Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Fri, 8 Apr 2022 00:47:54 -0500 Subject: [PATCH 07/31] Persistence namespace --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 11 ++++++----- TLM/TLM/{State => Persistence}/FeatureFilter.cs | 9 +++++---- .../GlobalPersistence.cs} | 4 ++-- .../{State => Persistence}/IPersistentObject.cs | 2 +- .../{State => Persistence}/PersistenceContext.cs | 2 +- TLM/TLM/Persistence/PersistenceException.cs | 11 +++++++++++ .../{State => Persistence}/PersistenceResult.cs | 2 +- TLM/TLM/{State => Persistence}/PersistentObject.cs | 5 ++--- TLM/TLM/State/StatePersistenceException.cs | 12 ------------ TLM/TLM/TLM.csproj | 14 +++++++------- 10 files changed, 36 insertions(+), 36 deletions(-) rename TLM/TLM/{State => Persistence}/FeatureFilter.cs (94%) rename TLM/TLM/{State/Persistence.cs => Persistence/GlobalPersistence.cs} (70%) rename TLM/TLM/{State => Persistence}/IPersistentObject.cs (92%) rename TLM/TLM/{State => Persistence}/PersistenceContext.cs (83%) create mode 100644 TLM/TLM/Persistence/PersistenceException.cs rename TLM/TLM/{State => Persistence}/PersistenceResult.cs (94%) rename TLM/TLM/{State => Persistence}/PersistentObject.cs (98%) delete mode 100644 TLM/TLM/State/StatePersistenceException.cs diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 291a2b247..957f6cca0 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -14,6 +14,7 @@ namespace TrafficManager.Lifecycle { using System.Linq; using System.Text; using System.Xml.Linq; + using TrafficManager.Persistence; [UsedImplicitly] public class SerializableDataExtension @@ -187,7 +188,7 @@ private static void LoadDom(byte[] data) { using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { _dom = XDocument.Load(streamReader); - _persistenceMigration = Persistence.PersistentObjects + _persistenceMigration = GlobalPersistence.PersistentObjects .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) .Select(o => o.DependencyTarget) .Distinct() @@ -207,8 +208,8 @@ private static void LoadDom(byte[] data) { private static void LoadDomElements() { try { - if (_dom?.Root.HasElements == true && Persistence.PersistentObjects.Count > 0) { - foreach (var o in Persistence.PersistentObjects.OrderBy(o => o)) { + if (_dom?.Root.HasElements == true && GlobalPersistence.PersistentObjects.Count > 0) { + foreach (var o in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { var elements = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); if (elements?.Any() == true) { if (elements.Count() > 1) { @@ -291,7 +292,7 @@ private static void LoadDataState(ICustomDataManager manager, T data, stri } } else if (data != null) { - if (Persistence.PersistentObjects.Any(o => o.DependencyTarget == manager.GetType())) { + if (GlobalPersistence.PersistentObjects.Any(o => o.DependencyTarget == manager.GetType())) { Log.Info($"Reading legacy {description} data structure (no DOM element was found)."); } if (!manager.LoadData(data)) { @@ -489,7 +490,7 @@ private static void SaveDom(ref bool success) { try { _dom = new XDocument(); - foreach (var o in Persistence.PersistentObjects.OrderBy(o => o)) { + foreach (var o in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { var result = o.SaveData(_dom.Root, new PersistenceContext { Version = Version }); result.LogMessage($"SaveData for DOM element {o.ElementName} reported {result}."); } diff --git a/TLM/TLM/State/FeatureFilter.cs b/TLM/TLM/Persistence/FeatureFilter.cs similarity index 94% rename from TLM/TLM/State/FeatureFilter.cs rename to TLM/TLM/Persistence/FeatureFilter.cs index ab1cacb5e..617cf812f 100644 --- a/TLM/TLM/State/FeatureFilter.cs +++ b/TLM/TLM/Persistence/FeatureFilter.cs @@ -5,8 +5,9 @@ using System.Text; using System.Xml.Linq; -namespace TrafficManager.State { - internal sealed class FeatureFilter where TFeature : struct { +namespace TrafficManager.Persistence { + internal sealed class FeatureFilter + where TFeature : struct { private const string featuresRequiredAttributeName = "featuresRequired"; private const string featuresForbiddenAttributeName = "featuresForbidden"; @@ -19,7 +20,7 @@ private enum FeatureStatus { static FeatureFilter() { Type tFeature = typeof(TFeature); if (!tFeature.IsEnum) - throw new StatePersistenceException($"FeatureSet<{tFeature.FullName}>: Type {tFeature.Name} is not an enum"); + throw new PersistenceException($"FeatureSet<{tFeature.FullName}>: Type {tFeature.Name} is not an enum"); } private readonly Dictionary filter; @@ -119,7 +120,7 @@ public void Add(TFeature item) { if (isReadOnly) throw new NotSupportedException(); if (featureFilter.filter.TryGetValue(item, out var existingStatus) && existingStatus != collectionFeatureStatus) - throw new StatePersistenceException($"{item} could not be added to the required collection because it is already forbidden."); + throw new PersistenceException($"{item} could not be added to the required collection because it is already forbidden."); featureFilter.filter[item] = collectionFeatureStatus; } diff --git a/TLM/TLM/State/Persistence.cs b/TLM/TLM/Persistence/GlobalPersistence.cs similarity index 70% rename from TLM/TLM/State/Persistence.cs rename to TLM/TLM/Persistence/GlobalPersistence.cs index 46ae24dda..c9ed34529 100644 --- a/TLM/TLM/State/Persistence.cs +++ b/TLM/TLM/Persistence/GlobalPersistence.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Text; -namespace TrafficManager.State { - internal static class Persistence { +namespace TrafficManager.Persistence { + internal static class GlobalPersistence { public static List PersistentObjects { get; } = new List(); } diff --git a/TLM/TLM/State/IPersistentObject.cs b/TLM/TLM/Persistence/IPersistentObject.cs similarity index 92% rename from TLM/TLM/State/IPersistentObject.cs rename to TLM/TLM/Persistence/IPersistentObject.cs index 2859342ff..5bf0f9607 100644 --- a/TLM/TLM/State/IPersistentObject.cs +++ b/TLM/TLM/Persistence/IPersistentObject.cs @@ -4,7 +4,7 @@ using System.Text; using System.Xml.Linq; -namespace TrafficManager.State { +namespace TrafficManager.Persistence { internal interface IPersistentObject : IComparable { Type DependencyTarget { get; } diff --git a/TLM/TLM/State/PersistenceContext.cs b/TLM/TLM/Persistence/PersistenceContext.cs similarity index 83% rename from TLM/TLM/State/PersistenceContext.cs rename to TLM/TLM/Persistence/PersistenceContext.cs index b3d9e7cfc..ab2853c03 100644 --- a/TLM/TLM/State/PersistenceContext.cs +++ b/TLM/TLM/Persistence/PersistenceContext.cs @@ -4,7 +4,7 @@ using System.Text; using System.Xml.Linq; -namespace TrafficManager.State { +namespace TrafficManager.Persistence { internal class PersistenceContext { public int Version { get; set; } diff --git a/TLM/TLM/Persistence/PersistenceException.cs b/TLM/TLM/Persistence/PersistenceException.cs new file mode 100644 index 000000000..78195675f --- /dev/null +++ b/TLM/TLM/Persistence/PersistenceException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.Persistence { + internal class PersistenceException : Exception { + public PersistenceException(string message) + : base(message) { } + } +} diff --git a/TLM/TLM/State/PersistenceResult.cs b/TLM/TLM/Persistence/PersistenceResult.cs similarity index 94% rename from TLM/TLM/State/PersistenceResult.cs rename to TLM/TLM/Persistence/PersistenceResult.cs index 55a6aba5f..3903b4c18 100644 --- a/TLM/TLM/State/PersistenceResult.cs +++ b/TLM/TLM/Persistence/PersistenceResult.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; -namespace TrafficManager.State { +namespace TrafficManager.Persistence { internal enum PersistenceResult { Success = 0, Failure = 1, diff --git a/TLM/TLM/State/PersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs similarity index 98% rename from TLM/TLM/State/PersistentObject.cs rename to TLM/TLM/Persistence/PersistentObject.cs index de289729b..183ca6378 100644 --- a/TLM/TLM/State/PersistentObject.cs +++ b/TLM/TLM/Persistence/PersistentObject.cs @@ -4,10 +4,9 @@ using System.Text; using System.Xml.Linq; -namespace TrafficManager.State { +namespace TrafficManager.Persistence { internal abstract class PersistentObject : IPersistentObject, IComparable> - where TFeature : struct - { + where TFeature : struct { protected abstract Type DependencyTarget { get; } diff --git a/TLM/TLM/State/StatePersistenceException.cs b/TLM/TLM/State/StatePersistenceException.cs deleted file mode 100644 index 64e3410f9..000000000 --- a/TLM/TLM/State/StatePersistenceException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State { - internal class StatePersistenceException : Exception { - public StatePersistenceException(string message) - : base(message) - { } - } -} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 703dc1e3d..2de343ed6 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -152,11 +152,11 @@ - - - - - + + + + + @@ -173,8 +173,8 @@ - - + + From 5b69eaab07bc47225e616184fbcd6af7ce6ff31d Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Fri, 8 Apr 2022 21:25:26 -0500 Subject: [PATCH 08/31] Clean up persistence framework and stub out new ttl persistence --- .../Lifecycle/SerializableDataExtension.cs | 4 +- ...afficLightSimulationManager.Persistence.cs | 37 +++++++ .../Impl/TrafficLightSimulationManager.cs | 5 +- .../Persistence/AbstractPersistentObject.cs | 74 +++++++++++++ TLM/TLM/Persistence/FeatureFilter.cs | 4 +- TLM/TLM/Persistence/GlobalPersistence.cs | 2 +- TLM/TLM/Persistence/GlobalPersistentObject.cs | 79 ++++++++++++++ ...ntObject.cs => IGlobalPersistentObject.cs} | 2 +- TLM/TLM/Persistence/PersistentObject.cs | 102 ++---------------- TLM/TLM/TLM.csproj | 5 +- 10 files changed, 215 insertions(+), 99 deletions(-) create mode 100644 TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs create mode 100644 TLM/TLM/Persistence/AbstractPersistentObject.cs create mode 100644 TLM/TLM/Persistence/GlobalPersistentObject.cs rename TLM/TLM/Persistence/{IPersistentObject.cs => IGlobalPersistentObject.cs} (88%) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 957f6cca0..94b685b16 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -213,10 +213,10 @@ private static void LoadDomElements() { var elements = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); if (elements?.Any() == true) { if (elements.Count() > 1) { - Log.Error($"More than one compatible element {o.ElementName} was found. Using the last one."); + Log.Error($"More than one compatible element {o.ElementName} was found. Using the first one."); } try { - var result = o.LoadData(elements.Last(), new PersistenceContext { Version = Version }); + var result = o.LoadData(elements.First(), new PersistenceContext { Version = Version }); result.LogMessage($"LoadData for DOM element {o.ElementName} reported {result}."); } catch (Exception ex) { diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs new file mode 100644 index 000000000..2fe0caddd --- /dev/null +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using TrafficManager.API.TrafficLight; +using TrafficManager.Persistence; + +namespace TrafficManager.Manager.Impl { + + partial class TrafficLightSimulationManager { + + internal class Persistence : GlobalPersistentObject { + + public override Type DependencyTarget => typeof(TrafficLightSimulationManager); + + public override string ElementName => "TimedTrafficLights"; + + public override IEnumerable GetDependencies() { + yield break; + } + + protected override PersistenceResult LoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { + return PersistenceResult.Skip; + } + + protected override PersistenceResult SaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + return PersistenceResult.Skip; + } + + public enum TtlFeature { + + XmlPersistence, + } + } + + } +} diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs index 73f66f9d4..74630f70b 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs @@ -17,8 +17,9 @@ namespace TrafficManager.Manager.Impl { using TrafficManager.Util; using ColossalFramework; using TrafficManager.Util.Extensions; + using TrafficManager.Persistence; - public class TrafficLightSimulationManager + public partial class TrafficLightSimulationManager : AbstractGeometryObservingManager, ICustomDataManager>, ITrafficLightSimulationManager @@ -31,6 +32,8 @@ private TrafficLightSimulationManager() { for (int i = 0; i < TrafficLightSimulations.Length; ++i) { TrafficLightSimulations[i] = new TrafficLightSimulation((ushort)i); } + + GlobalPersistence.PersistentObjects.Add(new Persistence()); } public static readonly TrafficLightSimulationManager Instance = diff --git a/TLM/TLM/Persistence/AbstractPersistentObject.cs b/TLM/TLM/Persistence/AbstractPersistentObject.cs new file mode 100644 index 000000000..4e0a639ff --- /dev/null +++ b/TLM/TLM/Persistence/AbstractPersistentObject.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.Persistence { + + internal abstract class AbstractPersistentObject + where TFeature : struct { + + public abstract string ElementName { get; } + + /// + /// Moves one or more of 's required features to the forbidden list, + /// then clears any remaining required features. + /// + /// + protected virtual void ChooseVictim(FeatureFilter featureFilter) { + featureFilter.Forbid(featureFilter.GetRequired().First()); + featureFilter.GetRequiredCollection(false).Clear(); + } + + /// + /// Checks whether the element is feature-compatible with this build. + /// + /// + /// true if feature-compatible, otherwise false + public bool CanLoad(XElement element) => FeatureFilter.CanLoad(element, out var _); + + protected PersistenceResult LoadData(XElement element, Func, PersistenceResult> loadData) { + return FeatureFilter.CanLoad(element, out var featureFilter) + ? loadData(featureFilter.GetRequiredCollection(true)) + : PersistenceResult.Skip; + } + + protected PersistenceResult SaveData(XElement container, Func, ICollection, PersistenceResult> saveData) { + var featureFilter = new FeatureFilter(); + + var element = new XElement(ElementName); + var result = saveData(element, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection()); + + if (result == PersistenceResult.Success) { + + element.Add(featureFilter.GetAttributes()); + container.Add(element); + + while (result == PersistenceResult.Success && featureFilter.IsAnyRequired()) { + + ChooseVictim(featureFilter); + + if (featureFilter.GetForbiddenCollection().Count == Enum.GetValues(typeof(TFeature)).Length) + break; + + element = new XElement(ElementName); + result = saveData(element, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection()); + + if (result == PersistenceResult.Success) { + element.Add(featureFilter.GetAttributes()); + container.Add(element); + } + } + if (result == PersistenceResult.Skip) + result = PersistenceResult.Success; + } + + return result; + } + } +} diff --git a/TLM/TLM/Persistence/FeatureFilter.cs b/TLM/TLM/Persistence/FeatureFilter.cs index 617cf812f..4ece6fd6c 100644 --- a/TLM/TLM/Persistence/FeatureFilter.cs +++ b/TLM/TLM/Persistence/FeatureFilter.cs @@ -26,12 +26,12 @@ static FeatureFilter() { private readonly Dictionary filter; public static bool CanLoad(XElement element, out FeatureFilter result) { - return CanRead(element.Attribute(featuresRequiredAttributeName)?.Value, + return CanLoad(element.Attribute(featuresRequiredAttributeName)?.Value, element.Attribute(featuresForbiddenAttributeName)?.Value, out result); } - public static bool CanRead(string requiredAttribute, string forbiddenAttribute, out FeatureFilter result) { + private static bool CanLoad(string requiredAttribute, string forbiddenAttribute, out FeatureFilter result) { result = null; diff --git a/TLM/TLM/Persistence/GlobalPersistence.cs b/TLM/TLM/Persistence/GlobalPersistence.cs index c9ed34529..b5187f47a 100644 --- a/TLM/TLM/Persistence/GlobalPersistence.cs +++ b/TLM/TLM/Persistence/GlobalPersistence.cs @@ -6,6 +6,6 @@ namespace TrafficManager.Persistence { internal static class GlobalPersistence { - public static List PersistentObjects { get; } = new List(); + public static List PersistentObjects { get; } = new List(); } } diff --git a/TLM/TLM/Persistence/GlobalPersistentObject.cs b/TLM/TLM/Persistence/GlobalPersistentObject.cs new file mode 100644 index 000000000..386f2d620 --- /dev/null +++ b/TLM/TLM/Persistence/GlobalPersistentObject.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.Persistence { + + internal abstract class GlobalPersistentObject + : AbstractPersistentObject, + IGlobalPersistentObject, + IComparable> + where TFeature : struct { + + public abstract Type DependencyTarget { get; } + + int IComparable.CompareTo(object obj) => CompareTo((GlobalPersistentObject)obj); + + public int CompareTo(GlobalPersistentObject other) { + + bool thisDependsOnOther = GetDependencies()?.Contains(other.DependencyTarget) == true; + bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; + + return + thisDependsOnOther == otherDependsOnThis ? ElementName.CompareTo(other.ElementName) + : thisDependsOnOther ? 1 + : -1; + } + + public abstract IEnumerable GetDependencies(); + + /// + /// Override to load data + /// + /// An XML element that was filled by + /// A read-only collection of features that were known to the build that created this element. + /// When a particular feature caused a breaking change in the save data, the absence of that feature from this collection + /// means that the data must be read the old way. + /// The persistence context of this load operation + /// + protected abstract PersistenceResult LoadData(XElement element, ICollection featuresRequired, PersistenceContext context); + + /// + /// Verifies feature compatibility and conditionally calls the overridable + /// . + /// + /// + /// + /// + public PersistenceResult LoadData(XElement element, PersistenceContext context) { + return LoadData(element, f => LoadData(element, f, context)); + } + + /// + /// Override to save data + /// + /// An XML element to be filled with save data + /// A collection of features this save data depends on. When a new feature introduces a + /// breaking change, the feature must be added to this collection to avoid data loss when if the player reverts to an earlier build. + /// A collection of features that are to be omitted from the save data for backward compatibility. + /// If the implementation cannot write backward-compatible save data that omits the features in this collection, + /// it must return . + /// The persistence context of this save operation + /// + protected abstract PersistenceResult SaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); + + /// + /// Calls . + /// When that method reports the use of features that introduced breaking changes, successive calls are made to request + /// backward-compatible save data. + /// + /// + /// + /// + public PersistenceResult SaveData(XElement container, PersistenceContext context) { + return SaveData(container, (e, r, f) => SaveData(e, r, f, context)); + } + } +} diff --git a/TLM/TLM/Persistence/IPersistentObject.cs b/TLM/TLM/Persistence/IGlobalPersistentObject.cs similarity index 88% rename from TLM/TLM/Persistence/IPersistentObject.cs rename to TLM/TLM/Persistence/IGlobalPersistentObject.cs index 5bf0f9607..89a4f0f26 100644 --- a/TLM/TLM/Persistence/IPersistentObject.cs +++ b/TLM/TLM/Persistence/IGlobalPersistentObject.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace TrafficManager.Persistence { - internal interface IPersistentObject : IComparable { + internal interface IGlobalPersistentObject : IComparable { Type DependencyTarget { get; } diff --git a/TLM/TLM/Persistence/PersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs index 183ca6378..1b6a867a1 100644 --- a/TLM/TLM/Persistence/PersistentObject.cs +++ b/TLM/TLM/Persistence/PersistentObject.cs @@ -5,34 +5,10 @@ using System.Xml.Linq; namespace TrafficManager.Persistence { - internal abstract class PersistentObject : IPersistentObject, IComparable> - where TFeature : struct { - - protected abstract Type DependencyTarget { get; } - - protected abstract IEnumerable GetDependencies(); - - public abstract string ElementName { get; } - - Type IPersistentObject.DependencyTarget => DependencyTarget; - - /// - /// Moves one or more of 's required features to the forbidden list, - /// then clears any remaining required features. - /// - /// - protected virtual void ChooseVictim(FeatureFilter featureFilter) { - var victim = featureFilter.GetRequired().First(); - featureFilter.GetRequiredCollection(false).Clear(); - featureFilter.Forbid(victim); - } - /// - /// Checks whether the element is feature-compatible with this build. - /// - /// - /// true if feature-compatible, otherwise false - public bool CanLoad(XElement element) => FeatureFilter.CanLoad(element, out var _); + internal abstract class PersistentObject + : AbstractPersistentObject + where TFeature : struct { /// /// Override to load data @@ -55,11 +31,13 @@ protected virtual void ChooseVictim(FeatureFilter featureFilter) { /// /// public PersistenceResult LoadData(XElement element, out TObject obj, PersistenceContext context) { - - obj = default; - return FeatureFilter.CanLoad(element, out var featureFilter) - ? LoadData(element, out obj, featureFilter.GetRequiredCollection(true), context) - : PersistenceResult.Skip; + TObject result = default; + try { + return LoadData(element, r => LoadData(element, out result, r, context)); + } + finally { + obj = result; + } } /// @@ -86,65 +64,7 @@ public PersistenceResult LoadData(XElement element, out TObject obj, Persistence /// /// public PersistenceResult SaveData(XElement container, TObject obj, PersistenceContext context) { - var featureFilter = new FeatureFilter(); - - var element = new XElement(ElementName); - var result = SaveData(element, - obj, - featureFilter.GetRequiredCollection(false), - featureFilter.GetForbiddenCollection(), - context); - - if (result == PersistenceResult.Success) { - - element.Add(featureFilter.GetAttributes()); - container.Add(element); - - while (result == PersistenceResult.Success && featureFilter.IsAnyRequired()) { - - ChooseVictim(featureFilter); - - if (featureFilter.GetForbiddenCollection().Count == Enum.GetValues(typeof(TFeature)).Length) - break; - - element = new XElement(ElementName); - result = SaveData(element, - obj, - featureFilter.GetRequiredCollection(false), - featureFilter.GetForbiddenCollection(), - context); - - if (result == PersistenceResult.Success) { - element.Add(featureFilter.GetAttributes()); - container.Add(element); - } - } - if (result == PersistenceResult.Skip) - result = PersistenceResult.Success; - } - - return result; + return SaveData(container, (e, r, f) => SaveData(e, obj, r, f, context)); } - - PersistenceResult IPersistentObject.LoadData(XElement element, PersistenceContext context) - => LoadData(element, out var _, context); - - PersistenceResult IPersistentObject.SaveData(XElement container, PersistenceContext context) - => SaveData(container, default, context); - - IEnumerable IPersistentObject.GetDependencies() => GetDependencies(); - - public int CompareTo(PersistentObject other) { - - bool thisDependsOnOther = GetDependencies()?.Contains(other.DependencyTarget) == true; - bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; - - return - thisDependsOnOther == otherDependsOnThis ? ElementName.CompareTo(other.ElementName) - : thisDependsOnOther ? 1 - : -1; - } - - int IComparable.CompareTo(object obj) => CompareTo((PersistentObject)obj); } } diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 2de343ed6..4181affa2 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -151,9 +151,12 @@ + + - + + From 58d51209d2b076683e76e1f1aea54304f810695f Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 9 Apr 2022 17:08:44 -0500 Subject: [PATCH 09/31] initial draft of save code --- ...afficLightSimulationManager.Persistence.cs | 200 +++++++++++++++++- .../Impl/TrafficLightSimulationManager.cs | 9 + .../Persistence/AbstractPersistentObject.cs | 8 +- TLM/TLM/Persistence/FeatureFilter.cs | 24 +++ TLM/TLM/Persistence/GlobalPersistentObject.cs | 14 +- TLM/TLM/Persistence/PersistentObject.cs | 14 +- TLM/TLM/TLM.csproj | 1 + TLM/TLM/Util/AnonymousDisposable.cs | 35 +++ 8 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 TLM/TLM/Util/AnonymousDisposable.cs diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index 2fe0caddd..807cfb028 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -1,9 +1,13 @@ +using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; +using TrafficManager.API.Traffic.Enums; using TrafficManager.API.TrafficLight; using TrafficManager.Persistence; +using TrafficManager.Util; +using TrafficManager.Util.Extensions; namespace TrafficManager.Manager.Impl { @@ -11,25 +15,205 @@ partial class TrafficLightSimulationManager { internal class Persistence : GlobalPersistentObject { + public enum TtlFeature { + + None = 0, + } + public override Type DependencyTarget => typeof(TrafficLightSimulationManager); public override string ElementName => "TimedTrafficLights"; - public override IEnumerable GetDependencies() { - yield break; - } + public override IEnumerable GetDependencies() => null; + + private readonly TtlPersistence ttlPersistence = new TtlPersistence(); - protected override PersistenceResult LoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { + protected override PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { return PersistenceResult.Skip; } - protected override PersistenceResult SaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { - return PersistenceResult.Skip; + protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + + var result = PersistenceResult.Success; + + using (ttlPersistence.PrepareForSave()) { + foreach (var ttl in Instance.EnumerateTimedTrafficLights()) { + try { +#if DEBUGSAVE + Log._Debug($"Going to save timed light at node {ttl.NodeId}."); +#endif + ttl.OnGeometryUpdate(); + + ttlPersistence.SaveData(element, ttl, featuresForbidden, context); + } + catch (Exception e) { + Log.Error( + $"Exception occurred while saving timed traffic light @ {ttl.NodeId}: {e}"); + result = PersistenceResult.Failure; + } + } + } + + return result; } - public enum TtlFeature { + internal class TtlPersistence : PersistentObject { + + public override string ElementName => "Ttl"; + + private Dictionary masterNodeIdBySlaveNodeId = null; + private Dictionary> nodeGroupByMasterNodeId = null; + + /// + /// TODO Is this obscenity really necessary? + /// + /// + public IDisposable PrepareForSave() { + var nodesWithSimulation = new HashSet(); + + foreach (var timedLights in Instance.EnumerateTimedTrafficLights()) { + nodesWithSimulation.Add(timedLights.NodeId); + } + + masterNodeIdBySlaveNodeId = new Dictionary(); + nodeGroupByMasterNodeId = new Dictionary>(); + + foreach (var timedLights in Instance.EnumerateTimedTrafficLights()) { + // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed + // enforce uniqueness of node ids + List currentNodeGroup = timedLights.NodeGroup.Distinct().ToList(); + + if (!currentNodeGroup.Contains(timedLights.NodeId)) { + currentNodeGroup.Add(timedLights.NodeId); + } + + // remove any nodes that are not configured to have a simulation + currentNodeGroup = new List( + currentNodeGroup.Intersect(nodesWithSimulation)); + + // remove invalid nodes from the group; find if any of the nodes in the group is already a master node + ushort masterNodeId = 0; + int foundMasterNodes = 0; + + for (int i = 0; i < currentNodeGroup.Count;) { + ushort nodeId = currentNodeGroup[i]; + ref NetNode netNode = ref nodeId.ToNode(); + + if (!netNode.IsValid()) { + currentNodeGroup.RemoveAt(i); + continue; + } + + if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { + // this is a known master node + if (foundMasterNodes > 0) { + // we already found another master node. ignore this node. + currentNodeGroup.RemoveAt(i); + continue; + } + + // we found the first master node + masterNodeId = nodeId; + ++foundMasterNodes; + } + + ++i; + } + + if (masterNodeId == 0) { + // no master node defined yet, set the first node as a master node + masterNodeId = currentNodeGroup[0]; + } + + // ensure the master node is the first node in the list (TimedTrafficLights + // depends on this at the moment...) + currentNodeGroup.Remove(masterNodeId); + currentNodeGroup.Insert(0, masterNodeId); + + // update the saved node group and master-slave info + nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; + + foreach (ushort nodeId in currentNodeGroup) { + masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; + } + } + + return new AnonymousDisposable(() => { + masterNodeIdBySlaveNodeId = null; + nodeGroupByMasterNodeId = null; + }); + } + + protected override PersistenceResult OnLoadData(XElement element, out ITimedTrafficLights obj, ICollection featuresRequired, PersistenceContext context) { + throw new NotImplementedException(); + } + + protected override PersistenceResult OnSaveData(XElement element, ITimedTrafficLights ttl, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + + if (masterNodeIdBySlaveNodeId == null || nodeGroupByMasterNodeId == null) { + throw new InvalidOperationException("Must call PrepareForSave() before SaveData in TtlPersistence"); + } + + if (!masterNodeIdBySlaveNodeId.ContainsKey(ttl.NodeId)) { + return PersistenceResult.Skip; + } + + element.Add(new XAttribute(nameof(ttl.NodeId), ttl.NodeId)); + + ushort masterNodeId = masterNodeIdBySlaveNodeId[ttl.NodeId]; + foreach (var groupNodeId in nodeGroupByMasterNodeId[masterNodeId]) { + element.Add(new XElement(nameof(ttl.NodeGroup), groupNodeId)); + } + element.Add(new XAttribute(nameof(ttl.IsStarted), ttl.IsStarted())); + + int currentStep = ttl.CurrentStep; + if (ttl.IsStarted() && + ttl.GetStep(ttl.CurrentStep).IsInEndTransition()) { + // if in end transition save the next step + currentStep = (currentStep + 1) % ttl.NumSteps(); + } + + element.Add(new XAttribute(nameof(ttl.CurrentStep), currentStep)); + + foreach (var step in Enumerable.Range(0, ttl.NumSteps()).Select(i => ttl.GetStep(i))) { + + var stepElement = new XElement("Step"); + + stepElement.Add(new XAttribute(nameof(step.MinTime), step.MinTime)); + stepElement.Add(new XAttribute(nameof(step.MaxTime), step.MaxTime)); + stepElement.Add(new XAttribute(nameof(step.ChangeMetric), (int)step.ChangeMetric)); + stepElement.Add(new XAttribute(nameof(step.WaitFlowBalance), step.WaitFlowBalance)); + + foreach (var segmentLights in step.CustomSegmentLights.Values) { + + var segLightElement = new XElement("SegmentLights"); + + segLightElement.Add(new XAttribute(nameof(segmentLights.NodeId), segmentLights.NodeId)); + segLightElement.Add(new XAttribute(nameof(segmentLights.SegmentId), segmentLights.SegmentId)); + + foreach (var light in segmentLights.CustomLights) { + + var lightElement = new XElement("Light"); + + lightElement.Add(new XAttribute(nameof(ExtVehicleType), light.Key)); + lightElement.Add(new XAttribute(nameof(light.Value.CurrentMode), light.Value.CurrentMode)); + lightElement.Add(new XAttribute(nameof(light.Value.LightLeft), (int)light.Value.LightLeft)); + lightElement.Add(new XAttribute(nameof(light.Value.LightMain), (int)light.Value.LightMain)); + lightElement.Add(new XAttribute(nameof(light.Value.LightRight), (int)light.Value.LightRight)); + + segLightElement.Add(lightElement); + } + + segLightElement.Add(new XAttribute(nameof(segmentLights.PedestrianLightState), segmentLights.PedestrianLightState)); + segLightElement.Add(new XAttribute(nameof(segmentLights.ManualPedestrianMode), segmentLights.ManualPedestrianMode)); + + stepElement.Add(segLightElement); + } - XmlPersistence, + element.Add(stepElement); + } + return PersistenceResult.Success; + } } } diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs index 74630f70b..807d2b88b 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs @@ -39,6 +39,15 @@ private TrafficLightSimulationManager() { public static readonly TrafficLightSimulationManager Instance = new TrafficLightSimulationManager(); + private IEnumerable EnumerateTimedTrafficLights() { + + for (int i = 0; i < NetManager.MAX_NODE_COUNT; i++) { + if (TrafficLightSimulations[i].IsTimedLight()) { + yield return TrafficLightSimulations[i].timedLight; + } + } + } + /// /// For each node id: traffic light simulation assigned to the node /// diff --git a/TLM/TLM/Persistence/AbstractPersistentObject.cs b/TLM/TLM/Persistence/AbstractPersistentObject.cs index 4e0a639ff..121cdd83a 100644 --- a/TLM/TLM/Persistence/AbstractPersistentObject.cs +++ b/TLM/TLM/Persistence/AbstractPersistentObject.cs @@ -34,8 +34,14 @@ protected PersistenceResult LoadData(XElement element, Func, ICollection, PersistenceResult> saveData) { + protected PersistenceResult SaveData(XElement container, ICollection featuresForbidden, Func, ICollection, PersistenceResult> saveData) { + var featureFilter = new FeatureFilter(); + if (featuresForbidden != null) { + foreach (var feature in featuresForbidden) { + featureFilter.Forbid(feature); + } + } var element = new XElement(ElementName); var result = saveData(element, diff --git a/TLM/TLM/Persistence/FeatureFilter.cs b/TLM/TLM/Persistence/FeatureFilter.cs index 4ece6fd6c..12866e6fb 100644 --- a/TLM/TLM/Persistence/FeatureFilter.cs +++ b/TLM/TLM/Persistence/FeatureFilter.cs @@ -62,6 +62,30 @@ private static bool CanLoad(string requiredAttribute, string forbiddenAttribute, public FeatureFilter() => filter = new Dictionary(); + public FeatureFilter(IEnumerable required, IEnumerable forbidden) + : this() + { + + if (required != null) { + if (forbidden != null) { + IEnumerable conflictingFeatures = required.Intersect(forbidden); + if (conflictingFeatures.Any()) { + throw new ArgumentException($"{conflictingFeatures.Count()} features including {conflictingFeatures.First()} were found in both the required and forbidden collections"); + } + } + + foreach (var f in required) { + Require(f); + } + } + + if (forbidden != null) { + foreach (var f in forbidden) { + Forbid(f); + } + } + } + private FeatureFilter(Dictionary filter) => this.filter = filter; public void Clear() => filter.Clear(); diff --git a/TLM/TLM/Persistence/GlobalPersistentObject.cs b/TLM/TLM/Persistence/GlobalPersistentObject.cs index 386f2d620..e57365611 100644 --- a/TLM/TLM/Persistence/GlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/GlobalPersistentObject.cs @@ -32,23 +32,23 @@ public int CompareTo(GlobalPersistentObject other) { /// /// Override to load data /// - /// An XML element that was filled by + /// An XML element that was filled by /// A read-only collection of features that were known to the build that created this element. /// When a particular feature caused a breaking change in the save data, the absence of that feature from this collection /// means that the data must be read the old way. /// The persistence context of this load operation /// - protected abstract PersistenceResult LoadData(XElement element, ICollection featuresRequired, PersistenceContext context); + protected abstract PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, PersistenceContext context); /// /// Verifies feature compatibility and conditionally calls the overridable - /// . + /// . /// /// /// /// public PersistenceResult LoadData(XElement element, PersistenceContext context) { - return LoadData(element, f => LoadData(element, f, context)); + return LoadData(element, f => OnLoadData(element, f, context)); } /// @@ -62,10 +62,10 @@ public PersistenceResult LoadData(XElement element, PersistenceContext context) /// it must return . /// The persistence context of this save operation /// - protected abstract PersistenceResult SaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); + protected abstract PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); /// - /// Calls . + /// Calls . /// When that method reports the use of features that introduced breaking changes, successive calls are made to request /// backward-compatible save data. /// @@ -73,7 +73,7 @@ public PersistenceResult LoadData(XElement element, PersistenceContext context) /// /// public PersistenceResult SaveData(XElement container, PersistenceContext context) { - return SaveData(container, (e, r, f) => SaveData(e, r, f, context)); + return SaveData(container, null, (e, r, f) => OnSaveData(e, r, f, context)); } } } diff --git a/TLM/TLM/Persistence/PersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs index 1b6a867a1..3a54c3202 100644 --- a/TLM/TLM/Persistence/PersistentObject.cs +++ b/TLM/TLM/Persistence/PersistentObject.cs @@ -20,11 +20,11 @@ internal abstract class PersistentObject /// means that the data must be read the old way. /// The persistence context of this load operation /// - protected abstract PersistenceResult LoadData(XElement element, out TObject obj, ICollection featuresRequired, PersistenceContext context); + protected abstract PersistenceResult OnLoadData(XElement element, out TObject obj, ICollection featuresRequired, PersistenceContext context); /// /// Verifies feature compatibility and conditionally calls the overridable - /// . + /// . /// /// /// @@ -33,7 +33,7 @@ internal abstract class PersistentObject public PersistenceResult LoadData(XElement element, out TObject obj, PersistenceContext context) { TObject result = default; try { - return LoadData(element, r => LoadData(element, out result, r, context)); + return LoadData(element, r => OnLoadData(element, out result, r, context)); } finally { obj = result; @@ -52,10 +52,10 @@ public PersistenceResult LoadData(XElement element, out TObject obj, Persistence /// it must return . /// The persistence context of this save operation /// - protected abstract PersistenceResult SaveData(XElement element, TObject obj, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); + protected abstract PersistenceResult OnSaveData(XElement element, TObject obj, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); /// - /// Calls . + /// Calls . /// When that method reports the use of features that introduced breaking changes, successive calls are made to request /// backward-compatible save data. /// @@ -63,8 +63,8 @@ public PersistenceResult LoadData(XElement element, out TObject obj, Persistence /// /// /// - public PersistenceResult SaveData(XElement container, TObject obj, PersistenceContext context) { - return SaveData(container, (e, r, f) => SaveData(e, obj, r, f, context)); + public PersistenceResult SaveData(XElement container, TObject obj, ICollection featuresForbidden, PersistenceContext context) { + return SaveData(container, featuresForbidden, (e, r, f) => OnSaveData(e, obj, r, f, context)); } } } diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 4181affa2..fc0feb0b7 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -187,6 +187,7 @@ + diff --git a/TLM/TLM/Util/AnonymousDisposable.cs b/TLM/TLM/Util/AnonymousDisposable.cs new file mode 100644 index 000000000..66e645056 --- /dev/null +++ b/TLM/TLM/Util/AnonymousDisposable.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace TrafficManager.Util { + /// + /// Implements IDisposable as a proxy to an delegate. + /// + public sealed class AnonymousDisposable : IDisposable { + private volatile Action dispose; + + /// + /// An IDisposable object that does nothing. This may be returned when an overriding + /// method is required to return IDisposable but has no need for its functionality. + /// It may also be returned as a placeholder when anticipating a future need for + /// IDisposable. + /// + public static readonly IDisposable Empty = new AnonymousDisposable(null); + + /// + /// Constructs an object that invokes the specified action when its + /// method is called. + /// + /// + public AnonymousDisposable(Action dispose) => this.dispose = dispose; + + /// + /// On the first call, invokes the delegate that was provided to the constructor. + /// On subsequent calls, does nothing. + /// + public void Dispose() => Interlocked.Exchange(ref dispose, null)?.Invoke(); + } +} From d04a5701df2d168434aa9bd91fcafca23a955765 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sun, 10 Apr 2022 14:34:54 -0500 Subject: [PATCH 10/31] TTL save code reworked, plus partial implementation of load --- ...afficLightSimulationManager.Persistence.cs | 170 +++++++----------- .../Persistence/AbstractPersistentObject.cs | 2 +- TLM/TLM/Persistence/GlobalPersistentObject.cs | 2 +- .../Persistence/IGlobalPersistentObject.cs | 2 +- TLM/TLM/Persistence/XmlLinqExtensions.cs | 23 +++ TLM/TLM/TLM.csproj | 1 + .../TrafficLight/Impl/TimedTrafficLights.cs | 19 ++ .../Impl/TimedTrafficLightsStep.cs | 28 ++- 8 files changed, 133 insertions(+), 114 deletions(-) create mode 100644 TLM/TLM/Persistence/XmlLinqExtensions.cs diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index d213d3115..9b0ef2a84 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -8,6 +8,7 @@ using TrafficManager.TrafficLight.Impl; using TrafficManager.Util; using TrafficManager.Util.Extensions; +using static RoadBaseAI; namespace TrafficManager.Manager.Impl { @@ -22,7 +23,7 @@ public enum TtlFeature { public override Type DependencyTarget => typeof(TrafficLightSimulationManager); - public override string ElementName => "TimedTrafficLights"; + public override XName ElementName => "TimedTrafficLights"; public override IEnumerable GetDependencies() => null; @@ -36,21 +37,19 @@ protected override PersistenceResult OnSaveData(XElement element, ICollection { - public override string ElementName => "Ttl"; + public override XName ElementName => "Ttl"; - private Dictionary masterNodeIdBySlaveNodeId = null; - private Dictionary> nodeGroupByMasterNodeId = null; + private static readonly XName StepName = "Step"; + private static readonly XName CustomLightsName = "CustomLight"; + private static readonly XName VehicleTypeName = "VehicleType"; - /// - /// TODO Is this obscenity really necessary? - /// - /// - public IDisposable PrepareForSave() { - var nodesWithSimulation = new HashSet(); - foreach (var timedLights in Instance.EnumerateTimedTrafficLights()) { - nodesWithSimulation.Add(timedLights.NodeId); - } + protected override PersistenceResult OnLoadData(XElement element, out TimedTrafficLights ttl, ICollection featuresRequired, PersistenceContext context) { - masterNodeIdBySlaveNodeId = new Dictionary(); - nodeGroupByMasterNodeId = new Dictionary>(); + var result = PersistenceResult.Success; - foreach (var timedLights in Instance.EnumerateTimedTrafficLights()) { - // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed - // enforce uniqueness of node ids - List currentNodeGroup = timedLights.NodeGroup.Distinct().ToList(); + ushort nodeId = element.Attribute(nameof(ttl.NodeId)).AsUInt16(); + var nodeGroup = element.Attributes(nameof(ttl.NodeGroup)).Select(a => a.AsUInt16()); + var isStarted = (bool)element.Attribute(nameof(ttl.IsStarted)); - if (!currentNodeGroup.Contains(timedLights.NodeId)) { - currentNodeGroup.Add(timedLights.NodeId); - } + ttl = TimedTrafficLights.CreateLoadingInstance(nodeId, nodeGroup, isStarted); - // remove any nodes that are not configured to have a simulation - currentNodeGroup = new List( - currentNodeGroup.Intersect(nodesWithSimulation)); + ttl.CurrentStep = (int)element.Attribute(nameof(ttl.CurrentStep)); - // remove invalid nodes from the group; find if any of the nodes in the group is already a master node - ushort masterNodeId = 0; - int foundMasterNodes = 0; + foreach (var stepElement in element.Elements(StepName)) { - for (int i = 0; i < currentNodeGroup.Count;) { - ushort nodeId = currentNodeGroup[i]; - ref NetNode netNode = ref nodeId.ToNode(); + var step = TimedTrafficLightsStep.CreateLoadingInstance(); - if (!netNode.IsValid()) { - currentNodeGroup.RemoveAt(i); - continue; - } + step.MinTime = (int)stepElement.Attribute(nameof(step.MinTime)); + step.MaxTime = (int)stepElement.Attribute(nameof(step.MaxTime)); + step.ChangeMetric = stepElement.Attribute(nameof(step.ChangeMetric)).AsEnum(); + step.WaitFlowBalance = (float)stepElement.Attribute(nameof(step.WaitFlowBalance)); - if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { - // this is a known master node - if (foundMasterNodes > 0) { - // we already found another master node. ignore this node. - currentNodeGroup.RemoveAt(i); - continue; - } - - // we found the first master node - masterNodeId = nodeId; - ++foundMasterNodes; - } + foreach (var segLightElement in stepElement.Elements(nameof(step.CustomSegmentLights))) { - ++i; - } + var segLightNodeId = segLightElement.Attribute(nameof(CustomSegmentLights.NodeId)).AsUInt16(); + var segmentId = segLightElement.Attribute(nameof(CustomSegmentLights.SegmentId)).AsUInt16(); + var startNode = segmentId.ToSegment().IsStartNode(segLightNodeId); + var segmentLights = new CustomSegmentLights(step, segmentId, startNode, false, false); - if (masterNodeId == 0) { - // no master node defined yet, set the first node as a master node - masterNodeId = currentNodeGroup[0]; - } + foreach (var lightElement in segLightElement.Elements(CustomLightsName)) { - // ensure the master node is the first node in the list (TimedTrafficLights - // depends on this at the moment...) - currentNodeGroup.Remove(masterNodeId); - currentNodeGroup.Insert(0, masterNodeId); + var vehicleType = lightElement.Attribute(VehicleTypeName).AsEnum(); - // update the saved node group and master-slave info - nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; + var lightLeft = lightElement.Attribute(nameof(CustomSegmentLight.LightLeft)).AsEnum(); + var lightMain = lightElement.Attribute(nameof(CustomSegmentLight.LightMain)).AsEnum(); + var lightRight = lightElement.Attribute(nameof(CustomSegmentLight.LightRight)).AsEnum(); - foreach (ushort nodeId in currentNodeGroup) { - masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; - } - } + var light = new CustomSegmentLight(segmentLights, lightMain, lightLeft, lightRight); - return new AnonymousDisposable(() => { - masterNodeIdBySlaveNodeId = null; - nodeGroupByMasterNodeId = null; - }); - } + light.CurrentMode = lightElement.Attribute(nameof(light.CurrentMode)).AsEnum(); - protected override PersistenceResult OnLoadData(XElement element, out TimedTrafficLights obj, ICollection featuresRequired, PersistenceContext context) { - throw new NotImplementedException(); - } + segmentLights.CustomLights.Add(vehicleType, light); + } - protected override PersistenceResult OnSaveData(XElement element, TimedTrafficLights ttl, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + step.AddLoadingSegmentLights(segmentLights); + } - if (masterNodeIdBySlaveNodeId == null || nodeGroupByMasterNodeId == null) { - throw new InvalidOperationException("Must call PrepareForSave() before SaveData in TtlPersistence"); + ttl.AddLoadingStep(step); } - if (!masterNodeIdBySlaveNodeId.ContainsKey(ttl.NodeId)) { - return PersistenceResult.Skip; - } + return result; + } - element.Add(new XAttribute(nameof(ttl.NodeId), ttl.NodeId)); + protected override PersistenceResult OnSaveData(XElement element, TimedTrafficLights ttl, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { - ushort masterNodeId = masterNodeIdBySlaveNodeId[ttl.NodeId]; - foreach (var groupNodeId in nodeGroupByMasterNodeId[masterNodeId]) { - element.Add(new XElement(nameof(ttl.NodeGroup), groupNodeId)); - } - element.Add(new XAttribute(nameof(ttl.IsStarted), ttl.IsStarted())); + element.Add(new XAttribute(nameof(ttl.NodeId), ttl.NodeId)); - int currentStep = ttl.CurrentStep; - if (ttl.IsStarted() && - ttl.GetStep(ttl.CurrentStep).IsInEndTransition()) { - // if in end transition save the next step - currentStep = (currentStep + 1) % ttl.NumSteps(); + foreach (var node in ttl.NodeGroup) { + element.Add(new XAttribute(nameof(ttl.NodeGroup), node)); } - element.Add(new XAttribute(nameof(ttl.CurrentStep), currentStep)); - - foreach (var step in Enumerable.Range(0, ttl.NumSteps()).Select(i => ttl.GetStep(i))) { + element.Add(new XAttribute(nameof(ttl.IsStarted), ttl.IsStarted())); + element.Add(new XAttribute(nameof(ttl.CurrentStep), ttl.CurrentStep)); - var stepElement = new XElement("Step"); + foreach (var step in ttl.EnumerateSteps()) { + var stepElement = new XElement(StepName); stepElement.Add(new XAttribute(nameof(step.MinTime), step.MinTime)); stepElement.Add(new XAttribute(nameof(step.MaxTime), step.MaxTime)); stepElement.Add(new XAttribute(nameof(step.ChangeMetric), (int)step.ChangeMetric)); stepElement.Add(new XAttribute(nameof(step.WaitFlowBalance), step.WaitFlowBalance)); - foreach (var segmentLights in step.CustomSegmentLights.Values) { + foreach (var segmentLights in step.CustomSegmentLights.Select(s => s.Value)) { - var segLightElement = new XElement("SegmentLights"); + var segLightElement = new XElement(nameof(step.CustomSegmentLights)); segLightElement.Add(new XAttribute(nameof(segmentLights.NodeId), segmentLights.NodeId)); segLightElement.Add(new XAttribute(nameof(segmentLights.SegmentId), segmentLights.SegmentId)); foreach (var light in segmentLights.CustomLights) { - var lightElement = new XElement("Light"); + var lightElement = new XElement(CustomLightsName); - lightElement.Add(new XAttribute(nameof(ExtVehicleType), light.Key)); - lightElement.Add(new XAttribute(nameof(light.Value.CurrentMode), light.Value.CurrentMode)); + lightElement.Add(new XAttribute(VehicleTypeName, light.Key)); + lightElement.Add(new XAttribute(nameof(light.Value.CurrentMode), (int)light.Value.CurrentMode)); lightElement.Add(new XAttribute(nameof(light.Value.LightLeft), (int)light.Value.LightLeft)); lightElement.Add(new XAttribute(nameof(light.Value.LightMain), (int)light.Value.LightMain)); lightElement.Add(new XAttribute(nameof(light.Value.LightRight), (int)light.Value.LightRight)); diff --git a/TLM/TLM/Persistence/AbstractPersistentObject.cs b/TLM/TLM/Persistence/AbstractPersistentObject.cs index 121cdd83a..aaa5ae184 100644 --- a/TLM/TLM/Persistence/AbstractPersistentObject.cs +++ b/TLM/TLM/Persistence/AbstractPersistentObject.cs @@ -9,7 +9,7 @@ namespace TrafficManager.Persistence { internal abstract class AbstractPersistentObject where TFeature : struct { - public abstract string ElementName { get; } + public abstract XName ElementName { get; } /// /// Moves one or more of 's required features to the forbidden list, diff --git a/TLM/TLM/Persistence/GlobalPersistentObject.cs b/TLM/TLM/Persistence/GlobalPersistentObject.cs index e57365611..56d326492 100644 --- a/TLM/TLM/Persistence/GlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/GlobalPersistentObject.cs @@ -22,7 +22,7 @@ public int CompareTo(GlobalPersistentObject other) { bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; return - thisDependsOnOther == otherDependsOnThis ? ElementName.CompareTo(other.ElementName) + thisDependsOnOther == otherDependsOnThis ? ElementName.ToString().CompareTo(other.ElementName.ToString()) : thisDependsOnOther ? 1 : -1; } diff --git a/TLM/TLM/Persistence/IGlobalPersistentObject.cs b/TLM/TLM/Persistence/IGlobalPersistentObject.cs index 89a4f0f26..ed91dbc4f 100644 --- a/TLM/TLM/Persistence/IGlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/IGlobalPersistentObject.cs @@ -11,7 +11,7 @@ internal interface IGlobalPersistentObject : IComparable { IEnumerable GetDependencies(); - string ElementName { get; } + XName ElementName { get; } bool CanLoad(XElement element); diff --git a/TLM/TLM/Persistence/XmlLinqExtensions.cs b/TLM/TLM/Persistence/XmlLinqExtensions.cs new file mode 100644 index 000000000..6e8c92394 --- /dev/null +++ b/TLM/TLM/Persistence/XmlLinqExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.Persistence { + internal static class XmlLinqExtensions { + + public static ushort AsUInt16(this XElement element) => (ushort)(uint)element; + + public static ushort AsUInt16(this XAttribute attribute) => (ushort)(uint)attribute; + + public static T AsEnum(this XElement element) + where T : struct + => (T)Convert.ChangeType((long)element, typeof(T)); + + + public static T AsEnum(this XAttribute attribute) + where T : struct + => (T)Convert.ChangeType((long)attribute, typeof(T)); + } +} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index e362795d4..670ee9a39 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -160,6 +160,7 @@ + diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs index 5b676d874..b4f31f9b8 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs @@ -29,6 +29,16 @@ public TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup) { started = false; } + private TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup, bool started) + : this(nodeId, nodeGroup) + { + + this.started = started; + } + + internal static TimedTrafficLights CreateLoadingInstance(ushort nodeId, IEnumerable nodeGroup, bool started) + => new TimedTrafficLights(nodeId, nodeGroup, started); + public ushort NodeId { get; } @@ -324,6 +334,13 @@ public TimedTrafficLightsStep AddStep(int minTime, return step; } + internal void AddLoadingStep(TimedTrafficLightsStep step) { + // TODO PERSISTENCE set this up for hot swapping ttl at load time + + step.Initialize(this); + Steps.Add(step); + } + public void Start() { Start(0); } @@ -524,6 +541,8 @@ public TimedTrafficLightsStep GetStep(int stepId) { return Steps[stepId]; } + public IEnumerable EnumerateSteps() => Steps; + // TODO this method is currently called on each node, but should be called on the master node only public void SimulationStep() { #if DEBUG diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs index 2b5be2a7a..87db45002 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs @@ -23,18 +23,36 @@ public TimedTrafficLightsStep(TimedTrafficLights timedNode, int maxTime, StepChangeMetric stepChangeMode, float waitFlowBalance, - bool makeRed = false) { + bool makeRed = false) + : this() + { + MinTime = minTime; MaxTime = maxTime; ChangeMetric = stepChangeMode; WaitFlowBalance = waitFlowBalance; - this.timedNode = timedNode; CurrentFlow = Single.NaN; CurrentWait = Single.NaN; + Initialize(timedNode, makeRed); + } + + private TimedTrafficLightsStep() { + endTransitionStart = null; stepDone = false; + } + + internal static TimedTrafficLightsStep CreateLoadingInstance() => new TimedTrafficLightsStep(); + + internal void Initialize(TimedTrafficLights timedNode, bool makeRed = false) { + + if (this.timedNode != null) { + throw new InvalidOperationException($"TimedTrafficLightsStep instance is already initialized for node {this.timedNode.NodeId}."); + } + + this.timedNode = timedNode; ref NetNode node = ref timedNode.NodeId.ToNode(); @@ -1294,6 +1312,12 @@ internal bool AddSegment(ushort segmentId, bool startNode, bool makeRed) { return customSegLightsMan.ApplyLightModes(segmentId, startNode, clonedLights); } + internal void AddLoadingSegmentLights(CustomSegmentLights segLights) { + // TODO PERSISTENCE set this up for hot swapping ttl at load time + + CustomSegmentLights.Add(segLights.SegmentId, segLights); + } + public bool SetSegmentLights(ushort nodeId, ushort segmentId, CustomSegmentLights lights) { #if DEBUG if (DebugSwitch.TimedTrafficLights.Get() && DebugSettings.NodeId == timedNode.NodeId) { From 6d7cf3bb58e32ed4000eb52a0807b3665ee998f2 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 12 Apr 2022 21:34:56 -0500 Subject: [PATCH 11/31] Remove extra layer originally intended for per-node versioning, directly copy and update old load/save code --- ...afficLightSimulationManager.Persistence.cs | 366 ++++++++++++++---- TLM/TLM/Persistence/XmlLinqExtensions.cs | 95 ++++- .../TrafficLight/Impl/TimedTrafficLights.cs | 2 - 3 files changed, 369 insertions(+), 94 deletions(-) diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index 9b0ef2a84..f826d49be 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -25,147 +25,341 @@ public enum TtlFeature { public override XName ElementName => "TimedTrafficLights"; - public override IEnumerable GetDependencies() => null; + private static readonly XName ttlNodeName = "TtlNode"; + private static readonly XName stepName = "Step"; + private static readonly XName segLightsName = "SegmentLights"; + private static readonly XName lightName = "Light"; + private static readonly XName vehicleTypeName = "VehicleType"; - private readonly TtlPersistence ttlPersistence = new TtlPersistence(); + public override IEnumerable GetDependencies() => null; protected override PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { - return PersistenceResult.Skip; - } + var result = PersistenceResult.Success; - protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + Log.Info($"Loading timed traffic lights (XML method)"); - var result = PersistenceResult.Success; + var nodesWithSimulation = new HashSet(); + + foreach (var ttlElement in element.Elements(ttlNodeName)) { + nodesWithSimulation.Add(ttlElement.Attribute(nameof(TimedTrafficLights.NodeId))); + } + + var masterNodeIdBySlaveNodeId = new Dictionary(); + var nodeGroupByMasterNodeId = new Dictionary>(); + + foreach (var ttlElement in element.Elements(ttlNodeName)) { + + var ttlNodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); - foreach (var ttl in Instance.EnumerateTimedTrafficLights()) { try { -#if DEBUGSAVE - Log._Debug($"Going to save timed light at node {ttl.NodeId}."); -#endif - ttl.OnGeometryUpdate(); + // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed + // enforce uniqueness of node ids + List currentNodeGroup = ttlElement.Elements(nameof(TimedTrafficLights.NodeId)) + .Distinct().ToList(); + + if (!currentNodeGroup.Contains(ttlNodeId)) { + currentNodeGroup.Add(ttlNodeId); + } + + // remove any nodes that are not configured to have a simulation + currentNodeGroup = new List( + currentNodeGroup.Intersect(nodesWithSimulation)); + + // remove invalid nodes from the group; find if any of the nodes in the group is already a master node + ushort masterNodeId = 0; + int foundMasterNodes = 0; + + for (int i = 0; i < currentNodeGroup.Count;) { + ushort nodeId = currentNodeGroup[i]; + ref NetNode netNode = ref nodeId.ToNode(); + + if (!netNode.IsValid()) { + currentNodeGroup.RemoveAt(i); + continue; + } + + if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { + // this is a known master node + if (foundMasterNodes > 0) { + // we already found another master node. ignore this node. + currentNodeGroup.RemoveAt(i); + continue; + } + + // we found the first master node + masterNodeId = nodeId; + ++foundMasterNodes; + } - ttlPersistence.SaveData(element, ttl, featuresForbidden, context); + ++i; + } + + if (masterNodeId == 0) { + // no master node defined yet, set the first node as a master node + masterNodeId = currentNodeGroup[0]; + } + + // ensure the master node is the first node in the list (TimedTrafficLights + // depends on this at the moment...) + currentNodeGroup.Remove(masterNodeId); + currentNodeGroup.Insert(0, masterNodeId); + + // update the saved node group and master-slave info + nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; + + foreach (ushort nodeId in currentNodeGroup) { + masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; + } } catch (Exception e) { - Log.Error( - $"Exception occurred while saving timed traffic light @ {ttl.NodeId}: {e}"); + Log.WarningFormat( + "Error building timed traffic light group for TimedNode {0} (NodeGroup: {1}): {2}", + ttlNodeId, + string.Join(", ", ttlElement.Elements(nameof(TimedTrafficLights.NodeId)).Select(e => e.Value).ToArray()), + e); result = PersistenceResult.Failure; } } - return result; - } + foreach (var ttlElement in element.Elements(ttlNodeName)) { + try { + var ttlNodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); - internal class TtlPersistence : PersistentObject { + if (!masterNodeIdBySlaveNodeId.ContainsKey(ttlNodeId)) { + continue; + } - public override XName ElementName => "Ttl"; + ushort masterNodeId = masterNodeIdBySlaveNodeId[ttlNodeId]; + List nodeGroup = nodeGroupByMasterNodeId[masterNodeId]; - private static readonly XName StepName = "Step"; - private static readonly XName CustomLightsName = "CustomLight"; - private static readonly XName VehicleTypeName = "VehicleType"; +#if DEBUGLOAD + Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: "+ + $"{string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); +#endif + Instance.SetUpTimedTrafficLight(ttlNodeId, nodeGroup); + int j = 0; + foreach (var stepElement in ttlElement.Elements(nameof(stepName))) { +#if DEBUGLOAD + Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}"); +#endif + TimedTrafficLightsStep step = + Instance.TrafficLightSimulations[ttlNodeId].timedLight.AddStep( + stepElement.Attribute(nameof(TimedTrafficLightsStep.MinTime)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.MaxTime)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.ChangeMetric)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.WaitFlowBalance))); - protected override PersistenceResult OnLoadData(XElement element, out TimedTrafficLights ttl, ICollection featuresRequired, PersistenceContext context) { + foreach (var segLightsElement in stepElement.Elements(segLightsName)) { - var result = PersistenceResult.Success; + var segmentId = segLightsElement.Attribute(nameof(CustomSegmentLights.SegmentId)); + ref NetSegment netSegment = ref segmentId.ToSegment(); - ushort nodeId = element.Attribute(nameof(ttl.NodeId)).AsUInt16(); - var nodeGroup = element.Attributes(nameof(ttl.NodeGroup)).Select(a => a.AsUInt16()); - var isStarted = (bool)element.Attribute(nameof(ttl.IsStarted)); + if (!netSegment.IsValid()) { + continue; + } - ttl = TimedTrafficLights.CreateLoadingInstance(nodeId, nodeGroup, isStarted); + var nodeId = ttlNodeId; - ttl.CurrentStep = (int)element.Attribute(nameof(ttl.CurrentStep)); +#if DEBUGLOAD + Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}"); +#endif + if (!step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { +#if DEBUGLOAD + Log._Debug($"No segment lights found at timed step {j} for segment "+ + $"{segmentId}, node {nodeId}"); +#endif + continue; + } - foreach (var stepElement in element.Elements(StepName)) { +#if DEBUGLOAD + Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: "+ + $"{cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}"); +#endif + lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(lights.ManualPedestrianMode)); + lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(lights.PedestrianLightState)); + + bool first = true; // v1.10.2 transitional code + foreach (var lightElement in segLightsElement.Elements(lightName)) { +#if DEBUGLOAD + Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType "+ + $"{e2.Key} at node {cnfTimedLights.nodeId}"); +#endif + var vehicleType = lightElement.Attribute(vehicleTypeName); + + if (!lights.CustomLights.TryGetValue( + vehicleType, + out CustomSegmentLight light)) { +#if DEBUGLOAD + Log._Debug($"No segment light found for timed step {j}, segment "+ + $"{e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); +#endif + // v1.10.2 transitional code START + if (first) { + first = false; + if (!lights.CustomLights.TryGetValue( + CustomSegmentLights + .DEFAULT_MAIN_VEHICLETYPE, + out light)) { +#if DEBUGLOAD + Log._Debug($"No segment light found for timed step {j}, "+ + $"segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} "+ + $"at node {cnfTimedLights.nodeId}"); +#endif + continue; + } + } else { + // v1.10.2 transitional code END + continue; + + // v1.10.2 transitional code START + } + + // v1.10.2 transitional code END + } + + light.InternalCurrentMode = lightElement.Attribute(nameof(light.CurrentMode)); // TODO improve & remove + light.SetStates( + lightElement.Attribute(nameof(light.LightMain)), + lightElement.Attribute(nameof(light.LightLeft)), + lightElement.Attribute(nameof(light.LightRight)), + false); + } + } - var step = TimedTrafficLightsStep.CreateLoadingInstance(); + ++j; + } + } + catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning($"Error loading data from TimedNode (new method): {e}"); + result = PersistenceResult.Failure; + } + } - step.MinTime = (int)stepElement.Attribute(nameof(step.MinTime)); - step.MaxTime = (int)stepElement.Attribute(nameof(step.MaxTime)); - step.ChangeMetric = stepElement.Attribute(nameof(step.ChangeMetric)).AsEnum(); - step.WaitFlowBalance = (float)stepElement.Attribute(nameof(step.WaitFlowBalance)); + foreach (var ttlElement in element.Elements(ttlNodeName)) { - foreach (var segLightElement in stepElement.Elements(nameof(step.CustomSegmentLights))) { + var nodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); - var segLightNodeId = segLightElement.Attribute(nameof(CustomSegmentLights.NodeId)).AsUInt16(); - var segmentId = segLightElement.Attribute(nameof(CustomSegmentLights.SegmentId)).AsUInt16(); - var startNode = segmentId.ToSegment().IsStartNode(segLightNodeId); - var segmentLights = new CustomSegmentLights(step, segmentId, startNode, false, false); + try { + TimedTrafficLights timedNode = + Instance.TrafficLightSimulations[nodeId].timedLight; - foreach (var lightElement in segLightElement.Elements(CustomLightsName)) { + timedNode.Housekeeping(); + if (ttlElement.Attribute(nameof(TimedTrafficLights.IsStarted))) { + timedNode.Start(ttlElement.Attribute(nameof(TimedTrafficLights.CurrentStep))); + } + } + catch (Exception e) { + Log.Warning($"Error starting timed light @ {nodeId}: {e}"); + result = PersistenceResult.Failure; + } + } - var vehicleType = lightElement.Attribute(VehicleTypeName).AsEnum(); + return result; + } - var lightLeft = lightElement.Attribute(nameof(CustomSegmentLight.LightLeft)).AsEnum(); - var lightMain = lightElement.Attribute(nameof(CustomSegmentLight.LightMain)).AsEnum(); - var lightRight = lightElement.Attribute(nameof(CustomSegmentLight.LightRight)).AsEnum(); + protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + var result = PersistenceResult.Success; - var light = new CustomSegmentLight(segmentLights, lightMain, lightLeft, lightRight); + foreach (var timedNode in Instance.EnumerateTimedTrafficLights()) { - light.CurrentMode = lightElement.Attribute(nameof(light.CurrentMode)).AsEnum(); + try { +#if DEBUGSAVE + Log._Debug($"Going to save timed light at node {nodeId}."); +#endif + timedNode.OnGeometryUpdate(); - segmentLights.CustomLights.Add(vehicleType, light); - } + var ttlNodeElement = new XElement(ttlNodeName); - step.AddLoadingSegmentLights(segmentLights); - } + ttlNodeElement.AddAttribute(nameof(timedNode.NodeId), timedNode.NodeId); + ttlNodeElement.AddElements(nameof(timedNode.NodeGroup), timedNode.NodeGroup); + ttlNodeElement.AddAttribute(nameof(timedNode.IsStarted), timedNode.IsStarted()); - ttl.AddLoadingStep(step); - } + element.Add(ttlNodeElement); - return result; - } + int stepIndex = timedNode.CurrentStep; + if (timedNode.IsStarted() && + timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { + // if in end transition save the next step + stepIndex = (stepIndex + 1) % timedNode.NumSteps(); + } - protected override PersistenceResult OnSaveData(XElement element, TimedTrafficLights ttl, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + ttlNodeElement.AddAttribute(nameof(timedNode.CurrentStep), stepIndex); - element.Add(new XAttribute(nameof(ttl.NodeId), ttl.NodeId)); + for (var j = 0; j < timedNode.NumSteps(); j++) { +#if DEBUGSAVE + Log._Debug($"Saving timed light step {j} at node {nodeId}."); +#endif + TimedTrafficLightsStep timedStep = timedNode.GetStep(j); - foreach (var node in ttl.NodeGroup) { - element.Add(new XAttribute(nameof(ttl.NodeGroup), node)); - } + var stepElement = new XElement(stepName); - element.Add(new XAttribute(nameof(ttl.IsStarted), ttl.IsStarted())); - element.Add(new XAttribute(nameof(ttl.CurrentStep), ttl.CurrentStep)); + stepElement.AddAttribute(nameof(timedStep.MinTime), timedStep.MinTime); + stepElement.AddAttribute(nameof(timedStep.MaxTime), timedStep.MaxTime); + stepElement.AddAttribute(nameof(timedStep.ChangeMetric), timedStep.ChangeMetric); + stepElement.AddAttribute(nameof(timedStep.WaitFlowBalance), timedStep.WaitFlowBalance); - foreach (var step in ttl.EnumerateSteps()) { - var stepElement = new XElement(StepName); + ttlNodeElement.Add(stepElement); - stepElement.Add(new XAttribute(nameof(step.MinTime), step.MinTime)); - stepElement.Add(new XAttribute(nameof(step.MaxTime), step.MaxTime)); - stepElement.Add(new XAttribute(nameof(step.ChangeMetric), (int)step.ChangeMetric)); - stepElement.Add(new XAttribute(nameof(step.WaitFlowBalance), step.WaitFlowBalance)); + foreach (var segLights in timedStep.CustomSegmentLights.Values) { +#if DEBUGSAVE + Log._Debug($"Saving timed light step {j}, segment {e.Key} at node {nodeId}."); +#endif - foreach (var segmentLights in step.CustomSegmentLights.Select(s => s.Value)) { + var segLightsElement = new XElement(segLightsName); - var segLightElement = new XElement(nameof(step.CustomSegmentLights)); + segLightsElement.AddAttribute(nameof(segLights.NodeId), segLights.NodeId); + segLightsElement.AddAttribute(nameof(segLights.SegmentId), segLights.SegmentId); + segLightsElement.AddAttribute(nameof(segLights.PedestrianLightState), (int?)segLights.PedestrianLightState); + segLightsElement.AddAttribute(nameof(segLights.ManualPedestrianMode), segLights.ManualPedestrianMode); - segLightElement.Add(new XAttribute(nameof(segmentLights.NodeId), segmentLights.NodeId)); - segLightElement.Add(new XAttribute(nameof(segmentLights.SegmentId), segmentLights.SegmentId)); + if (segLights.NodeId == 0 || segLights.NodeId != timedNode.NodeId) { + Log.Warning( + "Inconsistency detected: Timed traffic light @ node " + + $"{timedNode.NodeId} contains custom traffic lights for the invalid " + + $"segment ({segLights.SegmentId}) at step {j}: nId={segLights.NodeId}"); + continue; + } - foreach (var light in segmentLights.CustomLights) { + stepElement.Add(segLightsElement); - var lightElement = new XElement(CustomLightsName); +#if DEBUGSAVE + Log._Debug($"Saving pedestrian light @ seg. {e.Key}, step {j}: "+ + $"{cnfSegLights.pedestrianLightState} {cnfSegLights.manualPedestrianMode}"); +#endif - lightElement.Add(new XAttribute(VehicleTypeName, light.Key)); - lightElement.Add(new XAttribute(nameof(light.Value.CurrentMode), (int)light.Value.CurrentMode)); - lightElement.Add(new XAttribute(nameof(light.Value.LightLeft), (int)light.Value.LightLeft)); - lightElement.Add(new XAttribute(nameof(light.Value.LightMain), (int)light.Value.LightMain)); - lightElement.Add(new XAttribute(nameof(light.Value.LightRight), (int)light.Value.LightRight)); + foreach (var e2 in segLights.CustomLights) { +#if DEBUGSAVE + Log._Debug($"Saving timed light step {j}, segment {e.Key}, vehicleType "+ + $"{e2.Key} at node {nodeId}."); +#endif - segLightElement.Add(lightElement); - } + var lightElement = new XElement(lightName); + CustomSegmentLight segLight = e2.Value; - segLightElement.Add(new XAttribute(nameof(segmentLights.PedestrianLightState), segmentLights.PedestrianLightState)); - segLightElement.Add(new XAttribute(nameof(segmentLights.ManualPedestrianMode), segmentLights.ManualPedestrianMode)); + lightElement.AddAttribute(nameof(segLights.NodeId), segLights.NodeId); + lightElement.AddAttribute(nameof(segLights.SegmentId), segLights.SegmentId); + lightElement.AddAttribute(nameof(segLight.CurrentMode), segLight.CurrentMode); + lightElement.AddAttribute(nameof(segLight.LightLeft), segLight.LightLeft); + lightElement.AddAttribute(nameof(segLight.LightMain), segLight.LightMain); + lightElement.AddAttribute(nameof(segLight.LightRight), segLight.LightRight); - stepElement.Add(segLightElement); - } + lightElement.AddAttribute(vehicleTypeName, e2.Key); - element.Add(stepElement); + segLightsElement.Add(lightElement); + } + } + } + } + catch (Exception e) { + Log.Error( + $"Exception occurred while saving timed traffic light @ {timedNode.NodeId}: {e}"); + result = PersistenceResult.Failure; } - return PersistenceResult.Success; } + + return result; } } diff --git a/TLM/TLM/Persistence/XmlLinqExtensions.cs b/TLM/TLM/Persistence/XmlLinqExtensions.cs index 6e8c92394..3d39dd339 100644 --- a/TLM/TLM/Persistence/XmlLinqExtensions.cs +++ b/TLM/TLM/Persistence/XmlLinqExtensions.cs @@ -1,23 +1,106 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; +using System.Xml; using System.Xml.Linq; namespace TrafficManager.Persistence { internal static class XmlLinqExtensions { - public static ushort AsUInt16(this XElement element) => (ushort)(uint)element; + public static void AddAttribute(this XElement element, XName name, T value) { + if (!(typeof(T).IsClass && ReferenceEquals(value, default(T)))) { + element.Add(new XAttribute(name, ConvertToXml(value))); + } + } - public static ushort AsUInt16(this XAttribute attribute) => (ushort)(uint)attribute; + public static void AddElement(this XElement element, XName name, T value) { + if (!(typeof(T).IsClass && ReferenceEquals(value, default(T)))) { + element.Add(new XElement(name, ConvertToXml(value))); + } + } - public static T AsEnum(this XElement element) + public static void AddElements(this XElement element, XName name, IEnumerable values) { + foreach (var value in values) { + element.AddElement(name, value); + } + } + + public static void AddElements(this XElement element, XName name, params T[] values) { + foreach (var value in values) { + element.AddElement(name, value); + } + } + + private static string ConvertToXml(object value) { + switch (value) { + case DateTime dateTime: + return dateTime.ToString("O", CultureInfo.InvariantCulture); + + default: + if (value.GetType().IsEnum) { + return ConvertToXml(Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()))); + } else { + return (string)Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture); + } + } + } + + public static void AddAttribute(this XElement element, XName name, T? value) where T : struct - => (T)Convert.ChangeType((long)element, typeof(T)); + { + if (value.HasValue) { + element.Add(name, value.Value); + } + } - public static T AsEnum(this XAttribute attribute) + public static T Attribute(this XElement element, XName name) { + + var value = element.Attribute(name)?.Value; + return value == null ? (T)(object)value : ConvertFromXml(value); + } + + public static T? NullableAttribute(this XElement element, XName name) where T : struct - => (T)Convert.ChangeType((long)attribute, typeof(T)); + { + + var value = element.Attribute(name)?.Value; + return value == null ? default : ConvertFromXml(value); + } + + public static T Element(this XElement element, XName name) { + + var value = element.Element(name)?.Value; + return value == null ? (T)(object)value : ConvertFromXml(value); + } + + public static IEnumerable Elements(this XElement element, XName name) + => element.Elements(name).Select(e => ConvertFromXml(e.Value)); + + public static T? NullableElement(this XElement element, XName name) + where T : struct { + + var value = element.Element(name)?.Value; + return value == null ? default : ConvertFromXml(value); + } + + public static T Value(this XElement element) => ConvertFromXml(element.Value); + + private static T ConvertFromXml(string value) { + switch (Type.GetTypeCode(typeof(T))) { + + case TypeCode.DateTime: + return (T)(object)DateTime.ParseExact(value, "O", CultureInfo.InvariantCulture); + + default: + if (typeof(T).IsEnum) { + return (T)Enum.Parse(typeof(T), value); + } else { + return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); + } + } + } } } diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs index b4f31f9b8..3443836c7 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs @@ -541,8 +541,6 @@ public TimedTrafficLightsStep GetStep(int stepId) { return Steps[stepId]; } - public IEnumerable EnumerateSteps() => Steps; - // TODO this method is currently called on each node, but should be called on the master node only public void SimulationStep() { #if DEBUG From fcaca36326ef1318627b9f66036e77e9e5f6604d Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Thu, 14 Apr 2022 10:44:12 -0500 Subject: [PATCH 12/31] Further breaking down of large methods for clarity --- ...afficLightSimulationManager.Persistence.cs | 419 +++++++++--------- 1 file changed, 213 insertions(+), 206 deletions(-) diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index f826d49be..cbe31d191 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -34,19 +34,158 @@ public enum TtlFeature { public override IEnumerable GetDependencies() => null; protected override PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { - var result = PersistenceResult.Success; Log.Info($"Loading timed traffic lights (XML method)"); + // This is really terrible. The way grouped traffic lights are modeled, there are situations + // where the data we've saved can't be used to load grouped traffic lights properly unless + // we scan for and build all the traffic light groups in advance. + + // TODO Legacy code just keeps going if this step fails. Is that okay? + var result = BuildTtlGroups(element, out var masterNodeLookup, out var ttlGroups); + + // Now we can load the traffic lights + + foreach (var ttlElement in element.Elements(ttlNodeName)) { + try { + var ttlNodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); + + if (!masterNodeLookup.ContainsKey(ttlNodeId)) { + continue; + } + + ushort masterNodeId = masterNodeLookup[ttlNodeId]; + List nodeGroup = ttlGroups[masterNodeId]; + + Instance.SetUpTimedTrafficLight(ttlNodeId, nodeGroup); + + foreach (var stepElement in ttlElement.Elements(nameof(stepName))) { + LoadStep(stepElement, ttlNodeId); + } + } + catch (Exception e) { + // ignore, as it's probably corrupt save data. it'll be culled on next save + Log.Warning($"Error loading data from TimedNode (new method): {e}"); + result = PersistenceResult.Failure; + } + } + + // Since grouped traffic lights are stored in pieces, we can't start + // any of the traffic lights until all of them have been loaded. + + foreach (var ttlElement in element.Elements(ttlNodeName)) { + + var nodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); + + try { + TimedTrafficLights timedNode = + Instance.TrafficLightSimulations[nodeId].timedLight; + + timedNode.Housekeeping(); + if (ttlElement.Attribute(nameof(TimedTrafficLights.IsStarted))) { + timedNode.Start(ttlElement.Attribute(nameof(TimedTrafficLights.CurrentStep))); + } + } + catch (Exception e) { + Log.Warning($"Error starting timed light @ {nodeId}: {e}"); + result = PersistenceResult.Failure; + } + } + + return result; + } + + private static void LoadStep(XElement stepElement, ushort ttlNodeId) { + + TimedTrafficLightsStep step = + Instance.TrafficLightSimulations[ttlNodeId].timedLight.AddStep( + stepElement.Attribute(nameof(TimedTrafficLightsStep.MinTime)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.MaxTime)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.ChangeMetric)), + stepElement.Attribute(nameof(TimedTrafficLightsStep.WaitFlowBalance))); + + foreach (var segLightsElement in stepElement.Elements(segLightsName)) { + LoadSegLights(segLightsElement, step); + } + } + + private static void LoadSegLights(XElement segLightsElement, TimedTrafficLightsStep step) { + + var segmentId = segLightsElement.Attribute(nameof(CustomSegmentLights.SegmentId)); + ref NetSegment netSegment = ref segmentId.ToSegment(); + + if (netSegment.IsValid() && step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { + + lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(lights.ManualPedestrianMode)); + lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(lights.PedestrianLightState)); + + bool first = true; // v1.10.2 transitional code (dark arts that no one understands) + foreach (var lightElement in segLightsElement.Elements(lightName)) { + LoadLight(lightElement, lights, ref first); + } + } + } + + private static void LoadLight(XElement lightElement, CustomSegmentLights lights, ref bool first) { + + var vehicleType = lightElement.Attribute(vehicleTypeName); + + if (!lights.CustomLights.TryGetValue( + vehicleType, + out CustomSegmentLight light)) { + + // BEGIN dark arts that no one understands + // TODO learn the dark arts +#if DEBUGLOAD + Log._Debug($"No segment light found for timed step {j}, segment "+ + $"{e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); +#endif + // v1.10.2 transitional code START + if (first) { + first = false; + if (!lights.CustomLights.TryGetValue( + CustomSegmentLights + .DEFAULT_MAIN_VEHICLETYPE, + out light)) { +#if DEBUGLOAD + Log._Debug($"No segment light found for timed step {j}, "+ + $"segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} "+ + $"at node {cnfTimedLights.nodeId}"); +#endif + return; + } + } else { + // v1.10.2 transitional code END + return; + + // v1.10.2 transitional code START + } + + // v1.10.2 transitional code END + + // END dark arts + } + + light.InternalCurrentMode = lightElement.Attribute(nameof(light.CurrentMode)); // TODO improve & remove + light.SetStates( + lightElement.Attribute(nameof(light.LightMain)), + lightElement.Attribute(nameof(light.LightLeft)), + lightElement.Attribute(nameof(light.LightRight)), + false); + } + + private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary masterNodeLookup, out Dictionary> ttlGroups) { + + var result = PersistenceResult.Success; + var nodesWithSimulation = new HashSet(); foreach (var ttlElement in element.Elements(ttlNodeName)) { nodesWithSimulation.Add(ttlElement.Attribute(nameof(TimedTrafficLights.NodeId))); } - var masterNodeIdBySlaveNodeId = new Dictionary(); - var nodeGroupByMasterNodeId = new Dictionary>(); - + masterNodeLookup = new Dictionary(); + ttlGroups = new Dictionary>(); foreach (var ttlElement in element.Elements(ttlNodeName)) { var ttlNodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); @@ -78,7 +217,7 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection 0) { // we already found another master node. ignore this node. @@ -105,10 +244,10 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection(nameof(TimedTrafficLights.NodeId)); - - if (!masterNodeIdBySlaveNodeId.ContainsKey(ttlNodeId)) { - continue; - } - - ushort masterNodeId = masterNodeIdBySlaveNodeId[ttlNodeId]; - List nodeGroup = nodeGroupByMasterNodeId[masterNodeId]; - -#if DEBUGLOAD - Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: "+ - $"{string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); -#endif - Instance.SetUpTimedTrafficLight(ttlNodeId, nodeGroup); - - int j = 0; - foreach (var stepElement in ttlElement.Elements(nameof(stepName))) { -#if DEBUGLOAD - Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}"); -#endif - TimedTrafficLightsStep step = - Instance.TrafficLightSimulations[ttlNodeId].timedLight.AddStep( - stepElement.Attribute(nameof(TimedTrafficLightsStep.MinTime)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.MaxTime)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.ChangeMetric)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.WaitFlowBalance))); - - foreach (var segLightsElement in stepElement.Elements(segLightsName)) { + return result; + } - var segmentId = segLightsElement.Attribute(nameof(CustomSegmentLights.SegmentId)); - ref NetSegment netSegment = ref segmentId.ToSegment(); + protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + var result = PersistenceResult.Success; - if (!netSegment.IsValid()) { - continue; - } + foreach (var timedNode in Instance.EnumerateTimedTrafficLights()) { - var nodeId = ttlNodeId; + try { + // prepare ttl for write -#if DEBUGLOAD - Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}"); -#endif - if (!step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { -#if DEBUGLOAD - Log._Debug($"No segment lights found at timed step {j} for segment "+ - $"{segmentId}, node {nodeId}"); -#endif - continue; - } + timedNode.OnGeometryUpdate(); -#if DEBUGLOAD - Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: "+ - $"{cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}"); -#endif - lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(lights.ManualPedestrianMode)); - lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(lights.PedestrianLightState)); + // we don't save transition states; instead, we save the next step + int currentStep = timedNode.CurrentStep; + if (timedNode.IsStarted() && + timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { + currentStep = (currentStep + 1) % timedNode.NumSteps(); + } - bool first = true; // v1.10.2 transitional code - foreach (var lightElement in segLightsElement.Elements(lightName)) { -#if DEBUGLOAD - Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType "+ - $"{e2.Key} at node {cnfTimedLights.nodeId}"); -#endif - var vehicleType = lightElement.Attribute(vehicleTypeName); + // build ttl element - if (!lights.CustomLights.TryGetValue( - vehicleType, - out CustomSegmentLight light)) { -#if DEBUGLOAD - Log._Debug($"No segment light found for timed step {j}, segment "+ - $"{e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); -#endif - // v1.10.2 transitional code START - if (first) { - first = false; - if (!lights.CustomLights.TryGetValue( - CustomSegmentLights - .DEFAULT_MAIN_VEHICLETYPE, - out light)) { -#if DEBUGLOAD - Log._Debug($"No segment light found for timed step {j}, "+ - $"segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} "+ - $"at node {cnfTimedLights.nodeId}"); -#endif - continue; - } - } else { - // v1.10.2 transitional code END - continue; - - // v1.10.2 transitional code START - } - - // v1.10.2 transitional code END - } - - light.InternalCurrentMode = lightElement.Attribute(nameof(light.CurrentMode)); // TODO improve & remove - light.SetStates( - lightElement.Attribute(nameof(light.LightMain)), - lightElement.Attribute(nameof(light.LightLeft)), - lightElement.Attribute(nameof(light.LightRight)), - false); - } - } + var ttlNodeElement = new XElement(ttlNodeName); - ++j; - } - } - catch (Exception e) { - // ignore, as it's probably corrupt save data. it'll be culled on next save - Log.Warning($"Error loading data from TimedNode (new method): {e}"); - result = PersistenceResult.Failure; - } - } + ttlNodeElement.AddAttribute(nameof(timedNode.NodeId), timedNode.NodeId); + ttlNodeElement.AddElements(nameof(timedNode.NodeGroup), timedNode.NodeGroup); + ttlNodeElement.AddAttribute(nameof(timedNode.IsStarted), timedNode.IsStarted()); + ttlNodeElement.AddAttribute(nameof(timedNode.CurrentStep), currentStep); - foreach (var ttlElement in element.Elements(ttlNodeName)) { + element.Add(ttlNodeElement); - var nodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); + // add steps to the saved ttl - try { - TimedTrafficLights timedNode = - Instance.TrafficLightSimulations[nodeId].timedLight; - - timedNode.Housekeeping(); - if (ttlElement.Attribute(nameof(TimedTrafficLights.IsStarted))) { - timedNode.Start(ttlElement.Attribute(nameof(TimedTrafficLights.CurrentStep))); + for (var stepIndex = 0; stepIndex < timedNode.NumSteps(); stepIndex++) { + SaveStep(ttlNodeElement, timedNode, stepIndex); } } catch (Exception e) { - Log.Warning($"Error starting timed light @ {nodeId}: {e}"); + Log.Error( + $"Exception occurred while saving timed traffic light @ {timedNode.NodeId}: {e}"); result = PersistenceResult.Failure; } } @@ -259,107 +307,66 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { - var result = PersistenceResult.Success; + private static void SaveStep(XElement ttlNodeElement, TimedTrafficLights timedNode, int stepIndex) { - foreach (var timedNode in Instance.EnumerateTimedTrafficLights()) { + TimedTrafficLightsStep timedStep = timedNode.GetStep(stepIndex); - try { -#if DEBUGSAVE - Log._Debug($"Going to save timed light at node {nodeId}."); -#endif - timedNode.OnGeometryUpdate(); - - var ttlNodeElement = new XElement(ttlNodeName); - - ttlNodeElement.AddAttribute(nameof(timedNode.NodeId), timedNode.NodeId); - ttlNodeElement.AddElements(nameof(timedNode.NodeGroup), timedNode.NodeGroup); - ttlNodeElement.AddAttribute(nameof(timedNode.IsStarted), timedNode.IsStarted()); + var stepElement = new XElement(stepName); - element.Add(ttlNodeElement); + stepElement.AddAttribute(nameof(timedStep.MinTime), timedStep.MinTime); + stepElement.AddAttribute(nameof(timedStep.MaxTime), timedStep.MaxTime); + stepElement.AddAttribute(nameof(timedStep.ChangeMetric), timedStep.ChangeMetric); + stepElement.AddAttribute(nameof(timedStep.WaitFlowBalance), timedStep.WaitFlowBalance); - int stepIndex = timedNode.CurrentStep; - if (timedNode.IsStarted() && - timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { - // if in end transition save the next step - stepIndex = (stepIndex + 1) % timedNode.NumSteps(); - } + ttlNodeElement.Add(stepElement); - ttlNodeElement.AddAttribute(nameof(timedNode.CurrentStep), stepIndex); - - for (var j = 0; j < timedNode.NumSteps(); j++) { -#if DEBUGSAVE - Log._Debug($"Saving timed light step {j} at node {nodeId}."); -#endif - TimedTrafficLightsStep timedStep = timedNode.GetStep(j); - - var stepElement = new XElement(stepName); + foreach (var segLights in timedStep.CustomSegmentLights.Values) { + SaveSegLights(stepElement, timedNode.NodeId, stepIndex, segLights); + } + } - stepElement.AddAttribute(nameof(timedStep.MinTime), timedStep.MinTime); - stepElement.AddAttribute(nameof(timedStep.MaxTime), timedStep.MaxTime); - stepElement.AddAttribute(nameof(timedStep.ChangeMetric), timedStep.ChangeMetric); - stepElement.AddAttribute(nameof(timedStep.WaitFlowBalance), timedStep.WaitFlowBalance); + private static void SaveSegLights(XElement stepElement, ushort ttlNodeId, int stepIndex, CustomSegmentLights segLights) { - ttlNodeElement.Add(stepElement); + var segLightsElement = new XElement(segLightsName); - foreach (var segLights in timedStep.CustomSegmentLights.Values) { -#if DEBUGSAVE - Log._Debug($"Saving timed light step {j}, segment {e.Key} at node {nodeId}."); -#endif + // validation - var segLightsElement = new XElement(segLightsName); + if (segLights.NodeId == 0 || segLights.NodeId != ttlNodeId) { + Log.Warning( + "Inconsistency detected: Timed traffic light @ node " + + $"{ttlNodeId} contains custom traffic lights for the invalid " + + $"segment ({segLights.SegmentId}) at step {stepIndex}: nId={segLights.NodeId}"); + return; + } - segLightsElement.AddAttribute(nameof(segLights.NodeId), segLights.NodeId); - segLightsElement.AddAttribute(nameof(segLights.SegmentId), segLights.SegmentId); - segLightsElement.AddAttribute(nameof(segLights.PedestrianLightState), (int?)segLights.PedestrianLightState); - segLightsElement.AddAttribute(nameof(segLights.ManualPedestrianMode), segLights.ManualPedestrianMode); + // build segment lights element - if (segLights.NodeId == 0 || segLights.NodeId != timedNode.NodeId) { - Log.Warning( - "Inconsistency detected: Timed traffic light @ node " + - $"{timedNode.NodeId} contains custom traffic lights for the invalid " + - $"segment ({segLights.SegmentId}) at step {j}: nId={segLights.NodeId}"); - continue; - } + segLightsElement.AddAttribute(nameof(segLights.NodeId), segLights.NodeId); + segLightsElement.AddAttribute(nameof(segLights.SegmentId), segLights.SegmentId); + segLightsElement.AddAttribute(nameof(segLights.PedestrianLightState), (int?)segLights.PedestrianLightState); + segLightsElement.AddAttribute(nameof(segLights.ManualPedestrianMode), segLights.ManualPedestrianMode); - stepElement.Add(segLightsElement); + stepElement.Add(segLightsElement); -#if DEBUGSAVE - Log._Debug($"Saving pedestrian light @ seg. {e.Key}, step {j}: "+ - $"{cnfSegLights.pedestrianLightState} {cnfSegLights.manualPedestrianMode}"); -#endif + // add lights to the saved segment lights collection - foreach (var e2 in segLights.CustomLights) { -#if DEBUGSAVE - Log._Debug($"Saving timed light step {j}, segment {e.Key}, vehicleType "+ - $"{e2.Key} at node {nodeId}."); -#endif + foreach (var e2 in segLights.CustomLights) { + SaveLight(segLightsElement, e2.Key, e2.Value); + } + } - var lightElement = new XElement(lightName); - CustomSegmentLight segLight = e2.Value; + private static void SaveLight(XElement segLightsElement, ExtVehicleType vehicleType, CustomSegmentLight segLight) { - lightElement.AddAttribute(nameof(segLights.NodeId), segLights.NodeId); - lightElement.AddAttribute(nameof(segLights.SegmentId), segLights.SegmentId); - lightElement.AddAttribute(nameof(segLight.CurrentMode), segLight.CurrentMode); - lightElement.AddAttribute(nameof(segLight.LightLeft), segLight.LightLeft); - lightElement.AddAttribute(nameof(segLight.LightMain), segLight.LightMain); - lightElement.AddAttribute(nameof(segLight.LightRight), segLight.LightRight); + var lightElement = new XElement(lightName); - lightElement.AddAttribute(vehicleTypeName, e2.Key); + lightElement.AddAttribute(nameof(segLight.CurrentMode), segLight.CurrentMode); + lightElement.AddAttribute(nameof(segLight.LightLeft), segLight.LightLeft); + lightElement.AddAttribute(nameof(segLight.LightMain), segLight.LightMain); + lightElement.AddAttribute(nameof(segLight.LightRight), segLight.LightRight); - segLightsElement.Add(lightElement); - } - } - } - } - catch (Exception e) { - Log.Error( - $"Exception occurred while saving timed traffic light @ {timedNode.NodeId}: {e}"); - result = PersistenceResult.Failure; - } - } + lightElement.AddAttribute(vehicleTypeName, vehicleType); - return result; + segLightsElement.Add(lightElement); } } From 43fa12d497aa8b55e4523f66fc9e0a2744dd3390 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Wed, 27 Apr 2022 20:24:37 -0500 Subject: [PATCH 13/31] half-baked traffic light model --- ...afficLightSimulationManager.Persistence.cs | 112 ++++++++---------- TLM/TLM/TLM.csproj | 4 + .../TrafficLight/Impl/CustomSegmentLight.cs | 1 + .../TrafficLight/Impl/CustomSegmentLights.cs | 20 +++- .../TrafficLight/Impl/TimedTrafficLights.cs | 5 +- .../Impl/TimedTrafficLightsStep.cs | 8 +- .../Model/CustomSegmentLightModel.cs | 20 ++++ .../Model/ICustomSegmentLightsModel.cs | 17 +++ .../Model/ITimedTrafficLightsModel.cs | 17 +++ .../Model/ITimedTrafficLightsStepModel.cs | 20 ++++ 10 files changed, 159 insertions(+), 65 deletions(-) create mode 100644 TLM/TLM/TrafficLight/Model/CustomSegmentLightModel.cs create mode 100644 TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs create mode 100644 TLM/TLM/TrafficLight/Model/ITimedTrafficLightsModel.cs create mode 100644 TLM/TLM/TrafficLight/Model/ITimedTrafficLightsStepModel.cs diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index cbe31d191..ffa1c6af3 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -6,6 +6,7 @@ using TrafficManager.API.Traffic.Enums; using TrafficManager.Persistence; using TrafficManager.TrafficLight.Impl; +using TrafficManager.TrafficLight.Model; using TrafficManager.Util; using TrafficManager.Util.Extensions; using static RoadBaseAI; @@ -25,11 +26,10 @@ public enum TtlFeature { public override XName ElementName => "TimedTrafficLights"; - private static readonly XName ttlNodeName = "TtlNode"; - private static readonly XName stepName = "Step"; - private static readonly XName segLightsName = "SegmentLights"; - private static readonly XName lightName = "Light"; - private static readonly XName vehicleTypeName = "VehicleType"; + private static readonly XName ttlNodeElementName = "TtlNode"; + private static readonly XName stepElementName = "Step"; + private static readonly XName segLightsElementName = "SegmentLights"; + private static readonly XName lightElementName = "Light"; public override IEnumerable GetDependencies() => null; @@ -46,9 +46,9 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection(nameof(TimedTrafficLights.NodeId)); + var ttlNodeId = ttlElement.Attribute(nameof(ITimedTrafficLightsModel.NodeId)); if (!masterNodeLookup.ContainsKey(ttlNodeId)) { continue; @@ -59,7 +59,7 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection(nameof(TimedTrafficLights.NodeId)); + var nodeId = ttlElement.Attribute(nameof(ITimedTrafficLightsModel.NodeId)); try { TimedTrafficLights timedNode = Instance.TrafficLightSimulations[nodeId].timedLight; timedNode.Housekeeping(); - if (ttlElement.Attribute(nameof(TimedTrafficLights.IsStarted))) { - timedNode.Start(ttlElement.Attribute(nameof(TimedTrafficLights.CurrentStep))); + if (ttlElement.Attribute(nameof(ITimedTrafficLightsModel.IsStarted))) { + timedNode.Start(ttlElement.Attribute(nameof(ITimedTrafficLightsModel.CurrentStep))); } } catch (Exception e) { @@ -99,28 +99,28 @@ private static void LoadStep(XElement stepElement, ushort ttlNodeId) { TimedTrafficLightsStep step = Instance.TrafficLightSimulations[ttlNodeId].timedLight.AddStep( - stepElement.Attribute(nameof(TimedTrafficLightsStep.MinTime)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.MaxTime)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.ChangeMetric)), - stepElement.Attribute(nameof(TimedTrafficLightsStep.WaitFlowBalance))); + stepElement.Attribute(nameof(ITimedTrafficLightsStepModel.MinTime)), + stepElement.Attribute(nameof(ITimedTrafficLightsStepModel.MaxTime)), + stepElement.Attribute(nameof(ITimedTrafficLightsStepModel.ChangeMetric)), + stepElement.Attribute(nameof(ITimedTrafficLightsStepModel.WaitFlowBalance))); - foreach (var segLightsElement in stepElement.Elements(segLightsName)) { + foreach (var segLightsElement in stepElement.Elements(segLightsElementName)) { LoadSegLights(segLightsElement, step); } } private static void LoadSegLights(XElement segLightsElement, TimedTrafficLightsStep step) { - var segmentId = segLightsElement.Attribute(nameof(CustomSegmentLights.SegmentId)); + var segmentId = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.SegmentId)); ref NetSegment netSegment = ref segmentId.ToSegment(); if (netSegment.IsValid() && step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { - lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(lights.ManualPedestrianMode)); - lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(lights.PedestrianLightState)); + lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.ManualPedestrianMode)); + lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(ICustomSegmentLightsModel.PedestrianLightState)); bool first = true; // v1.10.2 transitional code (dark arts that no one understands) - foreach (var lightElement in segLightsElement.Elements(lightName)) { + foreach (var lightElement in segLightsElement.Elements(lightElementName)) { LoadLight(lightElement, lights, ref first); } } @@ -128,7 +128,7 @@ private static void LoadSegLights(XElement segLightsElement, TimedTrafficLightsS private static void LoadLight(XElement lightElement, CustomSegmentLights lights, ref bool first) { - var vehicleType = lightElement.Attribute(vehicleTypeName); + var vehicleType = lightElement.Attribute(nameof(CustomSegmentLightModel.VehicleType)); if (!lights.CustomLights.TryGetValue( vehicleType, @@ -166,11 +166,11 @@ private static void LoadLight(XElement lightElement, CustomSegmentLights lights, // END dark arts } - light.InternalCurrentMode = lightElement.Attribute(nameof(light.CurrentMode)); // TODO improve & remove + light.InternalCurrentMode = lightElement.Attribute(nameof(CustomSegmentLightModel.CurrentMode)); // TODO improve & remove light.SetStates( - lightElement.Attribute(nameof(light.LightMain)), - lightElement.Attribute(nameof(light.LightLeft)), - lightElement.Attribute(nameof(light.LightRight)), + lightElement.Attribute(nameof(CustomSegmentLightModel.LightMain)), + lightElement.Attribute(nameof(CustomSegmentLightModel.LightLeft)), + lightElement.Attribute(nameof(CustomSegmentLightModel.LightRight)), false); } @@ -180,13 +180,13 @@ private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary var nodesWithSimulation = new HashSet(); - foreach (var ttlElement in element.Elements(ttlNodeName)) { + foreach (var ttlElement in element.Elements(ttlNodeElementName)) { nodesWithSimulation.Add(ttlElement.Attribute(nameof(TimedTrafficLights.NodeId))); } masterNodeLookup = new Dictionary(); ttlGroups = new Dictionary>(); - foreach (var ttlElement in element.Elements(ttlNodeName)) { + foreach (var ttlElement in element.Elements(ttlNodeElementName)) { var ttlNodeId = ttlElement.Attribute(nameof(TimedTrafficLights.NodeId)); @@ -266,35 +266,37 @@ private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { var result = PersistenceResult.Success; - foreach (var timedNode in Instance.EnumerateTimedTrafficLights()) { + foreach (var timedNodeImpl in Instance.EnumerateTimedTrafficLights()) { + + ITimedTrafficLightsModel timedNode = timedNodeImpl; try { // prepare ttl for write - timedNode.OnGeometryUpdate(); + timedNodeImpl.OnGeometryUpdate(); // we don't save transition states; instead, we save the next step int currentStep = timedNode.CurrentStep; - if (timedNode.IsStarted() && - timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { - currentStep = (currentStep + 1) % timedNode.NumSteps(); + if (timedNode.IsStarted && + timedNodeImpl.GetStep(timedNode.CurrentStep).IsInEndTransition()) { + currentStep = (currentStep + 1) % timedNodeImpl.NumSteps(); } // build ttl element - var ttlNodeElement = new XElement(ttlNodeName); + var ttlNodeElement = new XElement(ttlNodeElementName); ttlNodeElement.AddAttribute(nameof(timedNode.NodeId), timedNode.NodeId); ttlNodeElement.AddElements(nameof(timedNode.NodeGroup), timedNode.NodeGroup); - ttlNodeElement.AddAttribute(nameof(timedNode.IsStarted), timedNode.IsStarted()); + ttlNodeElement.AddAttribute(nameof(timedNode.IsStarted), timedNode.IsStarted); ttlNodeElement.AddAttribute(nameof(timedNode.CurrentStep), currentStep); element.Add(ttlNodeElement); // add steps to the saved ttl - for (var stepIndex = 0; stepIndex < timedNode.NumSteps(); stepIndex++) { - SaveStep(ttlNodeElement, timedNode, stepIndex); + for (var stepIndex = 0; stepIndex < timedNodeImpl.NumSteps(); stepIndex++) { + SaveStep(ttlNodeElement, timedNodeImpl.GetStep(stepIndex)); } } catch (Exception e) { @@ -307,11 +309,9 @@ protected override PersistenceResult OnSaveData(XElement element, ICollection + + + + diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs index a410ca3af..ba0ade5ef 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs @@ -10,6 +10,7 @@ namespace TrafficManager.TrafficLight.Impl { using TrafficManager.API.Traffic.Enums; using TrafficManager.Manager.Impl; using TrafficManager.State.ConfigData; + using TrafficManager.TrafficLight.Model; using TrafficManager.Util; using TrafficManager.Util.Extensions; diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs index 33fa2fb21..614653f16 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs @@ -14,12 +14,14 @@ namespace TrafficManager.TrafficLight.Impl { using TrafficManager.Util; using TrafficManager.Manager.Impl; using TrafficManager.Util.Extensions; + using TrafficManager.TrafficLight.Model; + using System.Collections; /// /// Represents the set of custom traffic lights located at a node /// public class CustomSegmentLights - : SegmentEndId + : SegmentEndId, ICustomSegmentLightsModel { // private static readonly ExtVehicleType[] SINGLE_LANE_VEHICLETYPES // = new ExtVehicleType[] { ExtVehicleType.Tram, ExtVehicleType.Service, @@ -855,5 +857,19 @@ public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { CustomLights.DictionaryToString()); } } // end Housekeeping() + + IEnumerator IEnumerable.GetEnumerator() { + foreach (var light in CustomLights) { + yield return new CustomSegmentLightModel() { + VehicleType = light.Key, + CurrentMode = light.Value.CurrentMode, + LightLeft = light.Value.LightLeft, + LightMain = light.Value.LightMain, + LightRight = light.Value.LightRight, + }; + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); } // end class -} \ No newline at end of file +} diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs index 3443836c7..e6b3429f1 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs @@ -14,9 +14,10 @@ namespace TrafficManager.TrafficLight.Impl { using TrafficManager.Util; using UnityEngine; using TrafficManager.Util.Extensions; + using TrafficManager.TrafficLight.Model; // TODO define TimedTrafficLights per node group, not per individual nodes - public class TimedTrafficLights { + public class TimedTrafficLights : ITimedTrafficLightsModel { public TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup) { NodeId = nodeId; NodeGroup = new List(nodeGroup); @@ -69,6 +70,8 @@ public ushort MasterNodeId { public IDictionary> Directions { get; private set; } + bool ITimedTrafficLightsModel.IsStarted => IsStarted(); + /// /// Segment ends that were set up for this timed traffic light /// diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs index 87db45002..a6df9f701 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs @@ -14,9 +14,10 @@ namespace TrafficManager.TrafficLight.Impl { using TrafficManager.Util; using ColossalFramework; using TrafficManager.Util.Extensions; + using TrafficManager.TrafficLight.Model; // TODO class should be completely reworked, approx. in version 1.10 - public class TimedTrafficLightsStep : ITrafficLightContainer + public class TimedTrafficLightsStep : ITrafficLightContainer, ITimedTrafficLightsStepModel { public TimedTrafficLightsStep(TimedTrafficLights timedNode, int minTime, @@ -1417,5 +1418,10 @@ public void RemoveSegmentLight(ushort segmentId, bool startNode) { public bool IsSegmentLight(ushort segmentId, bool startNode) { throw new NotImplementedException(); } + + IEnumerable ITimedTrafficLightsStepModel.EnumerateCustomSegmentLights() { + foreach (var segLights in CustomSegmentLights.Values) + yield return segLights; + } } } \ No newline at end of file diff --git a/TLM/TLM/TrafficLight/Model/CustomSegmentLightModel.cs b/TLM/TLM/TrafficLight/Model/CustomSegmentLightModel.cs new file mode 100644 index 000000000..02b67df82 --- /dev/null +++ b/TLM/TLM/TrafficLight/Model/CustomSegmentLightModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TrafficManager.API.Traffic.Enums; + +namespace TrafficManager.TrafficLight.Model { + internal class CustomSegmentLightModel { + + public ExtVehicleType VehicleType { get; set; } + + public LightMode CurrentMode { get; set; } + + public RoadBaseAI.TrafficLightState LightLeft { get; set; } + + public RoadBaseAI.TrafficLightState LightMain { get; set; } + + public RoadBaseAI.TrafficLightState LightRight { get; set; } + } +} diff --git a/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs b/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs new file mode 100644 index 000000000..8d6e16e4b --- /dev/null +++ b/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.TrafficLight.Model { + internal interface ICustomSegmentLightsModel : IEnumerable { + + ushort NodeId { get; } + + ushort SegmentId { get; } + + RoadBaseAI.TrafficLightState? PedestrianLightState { get; set; } + + bool ManualPedestrianMode { get; set; } + } +} diff --git a/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsModel.cs b/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsModel.cs new file mode 100644 index 000000000..161b44624 --- /dev/null +++ b/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.TrafficLight.Model { + internal interface ITimedTrafficLightsModel { + + int CurrentStep { get; set; } + + bool IsStarted { get; } + + ushort NodeId { get; } + + IList NodeGroup { get; } + } +} diff --git a/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsStepModel.cs b/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsStepModel.cs new file mode 100644 index 000000000..cd148611e --- /dev/null +++ b/TLM/TLM/TrafficLight/Model/ITimedTrafficLightsStepModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TrafficManager.API.Traffic.Enums; + +namespace TrafficManager.TrafficLight.Model { + internal interface ITimedTrafficLightsStepModel { + + int MinTime { get; set; } + + int MaxTime { get; set; } + + StepChangeMetric ChangeMetric { get; set; } + + float WaitFlowBalance { get; set; } + + IEnumerable EnumerateCustomSegmentLights(); + } +} From 762b2179934eb3fba1d8de756dad34a194b9e2fc Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 3 May 2022 21:30:27 -0500 Subject: [PATCH 14/31] Some XML DOM bugs --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 10 ++++++---- TLM/TLM/Persistence/XmlLinqExtensions.cs | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 3de281e6d..76cf21fd6 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -490,6 +490,7 @@ private static void SaveDom(ref bool success) { try { _dom = new XDocument(); + _dom.Add(new XElement("TmpSaveData")); foreach (var o in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { var result = o.SaveData(_dom.Root, new PersistenceContext { Version = Version }); result.LogMessage($"SaveData for DOM element {o.ElementName} reported {result}."); @@ -497,13 +498,14 @@ private static void SaveDom(ref bool success) { using (var memoryStream = new MemoryStream()) { using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { + _dom.Save(streamWriter); - } - memoryStream.Position = 0; - Log.Info($"Save DOM byte length {memoryStream.Length}"); + memoryStream.Position = 0; + Log.Info($"Save DOM byte length {memoryStream.Length}"); - SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); + SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); + } } } catch (Exception ex) { diff --git a/TLM/TLM/Persistence/XmlLinqExtensions.cs b/TLM/TLM/Persistence/XmlLinqExtensions.cs index 3d39dd339..0879481c2 100644 --- a/TLM/TLM/Persistence/XmlLinqExtensions.cs +++ b/TLM/TLM/Persistence/XmlLinqExtensions.cs @@ -15,6 +15,12 @@ public static void AddAttribute(this XElement element, XName name, T value) { } } + public static void AddElement(this XElement element, XName name, object value) { + if (value != null) { + element.Add(new XElement(name, ConvertToXml(value))); + } + } + public static void AddElement(this XElement element, XName name, T value) { if (!(typeof(T).IsClass && ReferenceEquals(value, default(T)))) { element.Add(new XElement(name, ConvertToXml(value))); @@ -27,7 +33,16 @@ public static void AddElements(this XElement element, XName name, IEnumerable } } - public static void AddElements(this XElement element, XName name, params T[] values) { + public static void AddElements(this XElement element, XName name, params object[] values) { + foreach (var value in values) { + element.AddElement(name, value); + } + } + + public static void AddElements(this XElement element, XName name, params T[] values) + where T : struct + { + foreach (var value in values) { element.AddElement(name, value); } From 40306ba1a5004d5ed9d8c8a61efbe63aaa5c758f Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Wed, 4 May 2022 20:52:17 -0500 Subject: [PATCH 15/31] Workaround for Mono bug --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 76cf21fd6..d652856aa 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -15,6 +15,7 @@ namespace TrafficManager.Lifecycle { using System.Text; using System.Xml.Linq; using TrafficManager.Persistence; + using System.Xml; [UsedImplicitly] public class SerializableDataExtension @@ -186,7 +187,13 @@ private static void LoadDom(byte[] data) { if (data?.Length > 0) { using (var memoryStream = new MemoryStream(data)) { using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { - _dom = XDocument.Load(streamReader); + + XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { + ProhibitDtd = false, + XmlResolver = null, + }; + using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) + _dom = XDocument.Load(xmlReader); _persistenceMigration = GlobalPersistence.PersistentObjects .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) From fc2d3ea7e0c7b9f0dded92063bb1439e8cc4918f Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Fri, 6 May 2022 01:08:08 -0500 Subject: [PATCH 16/31] Typos and logging --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 8 ++++++++ TLM/TLM/Persistence/XmlLinqExtensions.cs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index d652856aa..9d47aba0f 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -195,6 +195,10 @@ private static void LoadDom(byte[] data) { using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) _dom = XDocument.Load(xmlReader); +#if DEBUG//NEVER + Log._Debug("Loaded DOM:\r" + _dom.ToString()); +#endif + _persistenceMigration = GlobalPersistence.PersistentObjects .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) .Select(o => o.DependencyTarget) @@ -506,6 +510,10 @@ private static void SaveDom(ref bool success) { using (var memoryStream = new MemoryStream()) { using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { +#if DEBUG//NEVER + Log._Debug("Saving DOM:\r" + _dom.ToString()); +#endif + _dom.Save(streamWriter); memoryStream.Position = 0; diff --git a/TLM/TLM/Persistence/XmlLinqExtensions.cs b/TLM/TLM/Persistence/XmlLinqExtensions.cs index 0879481c2..5bad033c5 100644 --- a/TLM/TLM/Persistence/XmlLinqExtensions.cs +++ b/TLM/TLM/Persistence/XmlLinqExtensions.cs @@ -67,7 +67,7 @@ public static void AddAttribute(this XElement element, XName name, T? value) { if (value.HasValue) { - element.Add(name, value.Value); + element.AddAttribute(name, value.Value); } } From f1d735a0d888df39d0cfa8d88c312d443831aad3 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 13:39:51 -0500 Subject: [PATCH 17/31] Fix all the little problems that kept this from working --- .../Lifecycle/SerializableDataExtension.cs | 4 +- ...afficLightSimulationManager.Persistence.cs | 75 +++++++++++++++---- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 9d47aba0f..3feb16ab0 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -195,7 +195,7 @@ private static void LoadDom(byte[] data) { using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) _dom = XDocument.Load(xmlReader); -#if DEBUG//NEVER +#if DEBUGLOAD Log._Debug("Loaded DOM:\r" + _dom.ToString()); #endif @@ -510,7 +510,7 @@ private static void SaveDom(ref bool success) { using (var memoryStream = new MemoryStream()) { using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { -#if DEBUG//NEVER +#if DEBUGSAVE Log._Debug("Saving DOM:\r" + _dom.ToString()); #endif diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index ffa1c6af3..1ff9da25a 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -1,3 +1,5 @@ +#define DEBUGLOAD + using CSUtil.Commons; using System; using System.Collections.Generic; @@ -10,6 +12,8 @@ using TrafficManager.Util; using TrafficManager.Util.Extensions; using static RoadBaseAI; +using DebugSwitch = TrafficManager.State.ConfigData.DebugSwitch; +using static TrafficManager.State.ConfigData.DebugSwitchExtensions; namespace TrafficManager.Manager.Impl { @@ -57,10 +61,20 @@ protected override PersistenceResult OnLoadData(XElement element, ICollection nodeGroup = ttlGroups[masterNodeId]; +#if DEBUGLOAD + Log._Debug($"Adding timed light at node {ttlNodeId}. NodeGroup: " + + $"{string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); +#endif + Instance.SetUpTimedTrafficLight(ttlNodeId, nodeGroup); - foreach (var stepElement in ttlElement.Elements(nameof(stepElementName))) { + int step = 0; + foreach (var stepElement in ttlElement.Elements(stepElementName)) { +#if DEBUGLOAD + Log._Debug($"Loading timed step {step} at node {ttlNodeId}"); +#endif LoadStep(stepElement, ttlNodeId); + ++step; } } catch (Exception e) { @@ -114,15 +128,39 @@ private static void LoadSegLights(XElement segLightsElement, TimedTrafficLightsS var segmentId = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.SegmentId)); ref NetSegment netSegment = ref segmentId.ToSegment(); - if (netSegment.IsValid() && step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { + if (netSegment.IsValid()) { + +#if DEBUGLOAD + Log._Debug($"Loading segment {segmentId} of for ttl step"); +#endif + + if (step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { + + var manualPedestrianMode = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.ManualPedestrianMode)); + var pedestrianLightState = segLightsElement.NullableAttribute(nameof(ICustomSegmentLightsModel.PedestrianLightState)); + +#if DEBUGLOAD + Log._Debug($"Loading pedestrian light at segment {segmentId}: " + + $"{pedestrianLightState} {manualPedestrianMode}"); +#endif + + lights.ManualPedestrianMode = manualPedestrianMode; + lights.PedestrianLightState = pedestrianLightState; - lights.ManualPedestrianMode = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.ManualPedestrianMode)); - lights.PedestrianLightState = segLightsElement.NullableAttribute(nameof(ICustomSegmentLightsModel.PedestrianLightState)); + bool first = true; // v1.10.2 transitional code (dark arts that no one understands) + foreach (var lightElement in segLightsElement.Elements(lightElementName)) { + LoadLight(lightElement, lights, ref first); + } + } else { +#if DEBUGLOAD + Log._Debug($"No segment lights found for segment {segmentId}"); +#endif - bool first = true; // v1.10.2 transitional code (dark arts that no one understands) - foreach (var lightElement in segLightsElement.Elements(lightElementName)) { - LoadLight(lightElement, lights, ref first); } + } else { +#if DEBUGLOAD + Log._Debug($"Invalid segment {segmentId} for ttl step"); +#endif } } @@ -130,6 +168,10 @@ private static void LoadLight(XElement lightElement, CustomSegmentLights lights, var vehicleType = lightElement.Attribute(nameof(CustomSegmentLightModel.VehicleType)); +#if DEBUGLOAD + Log._Debug($"Loading light: vehicleType={vehicleType}"); +#endif + if (!lights.CustomLights.TryGetValue( vehicleType, out CustomSegmentLight light)) { @@ -137,8 +179,7 @@ private static void LoadLight(XElement lightElement, CustomSegmentLights lights, // BEGIN dark arts that no one understands // TODO learn the dark arts #if DEBUGLOAD - Log._Debug($"No segment light found for timed step {j}, segment "+ - $"{e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); + Log._Debug($"No segment light found for vehicleType {vehicleType}"); #endif // v1.10.2 transitional code START if (first) { @@ -148,9 +189,7 @@ private static void LoadLight(XElement lightElement, CustomSegmentLights lights, .DEFAULT_MAIN_VEHICLETYPE, out light)) { #if DEBUGLOAD - Log._Debug($"No segment light found for timed step {j}, "+ - $"segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} "+ - $"at node {cnfTimedLights.nodeId}"); + Log._Debug($"No segment light found for DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} "); #endif return; } @@ -184,6 +223,11 @@ private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary nodesWithSimulation.Add(ttlElement.Attribute(nameof(TimedTrafficLights.NodeId))); } +#if DEBUGLOAD + Log._Debug($"TrafficLightSimulationManager.Persistence.BuildTtlGroups: " + + $"nodesWithSimulation=[{string.Join(",", nodesWithSimulation.Select(n => n.ToString()).ToArray())}]"); +#endif + masterNodeLookup = new Dictionary(); ttlGroups = new Dictionary>(); foreach (var ttlElement in element.Elements(ttlNodeElementName)) { @@ -193,7 +237,7 @@ private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary try { // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed // enforce uniqueness of node ids - List currentNodeGroup = ttlElement.Elements(nameof(TimedTrafficLights.NodeId)) + List currentNodeGroup = ttlElement.Elements(nameof(TimedTrafficLights.NodeGroup)) .Distinct().ToList(); if (!currentNodeGroup.Contains(ttlNodeId)) { @@ -249,6 +293,11 @@ private static PersistenceResult BuildTtlGroups(XElement element, out Dictionary foreach (ushort nodeId in currentNodeGroup) { masterNodeLookup[nodeId] = masterNodeId; } + +#if DEBUGLOAD + Log._Debug($"Node {ttlNodeId}: masterNodeId={masterNodeId}, " + + $"currentNodeGroup=[{string.Join(",", currentNodeGroup.Select(n => n.ToString()).ToArray())}]"); +#endif } catch (Exception e) { Log.WarningFormat( From 2c7e2f9bc3fed040379a286f3587d5a66e1864b6 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 13:45:48 -0500 Subject: [PATCH 18/31] Remove the DEBUGLOAD symbol --- .../Manager/Impl/TrafficLightSimulationManager.Persistence.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index 1ff9da25a..b0def4721 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -1,5 +1,3 @@ -#define DEBUGLOAD - using CSUtil.Commons; using System; using System.Collections.Generic; From df5f375bbc82a0c61b8e823c8984a1901281e61f Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 13:56:33 -0500 Subject: [PATCH 19/31] Remove some unused stuff --- .../Persistence/AbstractPersistentObject.cs | 80 ------------------- TLM/TLM/Persistence/GlobalPersistentObject.cs | 71 +++++++++++++++- TLM/TLM/Persistence/PersistentObject.cs | 70 ---------------- TLM/TLM/TLM.csproj | 4 +- 4 files changed, 70 insertions(+), 155 deletions(-) delete mode 100644 TLM/TLM/Persistence/AbstractPersistentObject.cs delete mode 100644 TLM/TLM/Persistence/PersistentObject.cs diff --git a/TLM/TLM/Persistence/AbstractPersistentObject.cs b/TLM/TLM/Persistence/AbstractPersistentObject.cs deleted file mode 100644 index aaa5ae184..000000000 --- a/TLM/TLM/Persistence/AbstractPersistentObject.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Linq; - -namespace TrafficManager.Persistence { - - internal abstract class AbstractPersistentObject - where TFeature : struct { - - public abstract XName ElementName { get; } - - /// - /// Moves one or more of 's required features to the forbidden list, - /// then clears any remaining required features. - /// - /// - protected virtual void ChooseVictim(FeatureFilter featureFilter) { - featureFilter.Forbid(featureFilter.GetRequired().First()); - featureFilter.GetRequiredCollection(false).Clear(); - } - - /// - /// Checks whether the element is feature-compatible with this build. - /// - /// - /// true if feature-compatible, otherwise false - public bool CanLoad(XElement element) => FeatureFilter.CanLoad(element, out var _); - - protected PersistenceResult LoadData(XElement element, Func, PersistenceResult> loadData) { - return FeatureFilter.CanLoad(element, out var featureFilter) - ? loadData(featureFilter.GetRequiredCollection(true)) - : PersistenceResult.Skip; - } - - protected PersistenceResult SaveData(XElement container, ICollection featuresForbidden, Func, ICollection, PersistenceResult> saveData) { - - var featureFilter = new FeatureFilter(); - if (featuresForbidden != null) { - foreach (var feature in featuresForbidden) { - featureFilter.Forbid(feature); - } - } - - var element = new XElement(ElementName); - var result = saveData(element, - featureFilter.GetRequiredCollection(false), - featureFilter.GetForbiddenCollection()); - - if (result == PersistenceResult.Success) { - - element.Add(featureFilter.GetAttributes()); - container.Add(element); - - while (result == PersistenceResult.Success && featureFilter.IsAnyRequired()) { - - ChooseVictim(featureFilter); - - if (featureFilter.GetForbiddenCollection().Count == Enum.GetValues(typeof(TFeature)).Length) - break; - - element = new XElement(ElementName); - result = saveData(element, - featureFilter.GetRequiredCollection(false), - featureFilter.GetForbiddenCollection()); - - if (result == PersistenceResult.Success) { - element.Add(featureFilter.GetAttributes()); - container.Add(element); - } - } - if (result == PersistenceResult.Skip) - result = PersistenceResult.Success; - } - - return result; - } - } -} diff --git a/TLM/TLM/Persistence/GlobalPersistentObject.cs b/TLM/TLM/Persistence/GlobalPersistentObject.cs index 56d326492..4e7042580 100644 --- a/TLM/TLM/Persistence/GlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/GlobalPersistentObject.cs @@ -7,11 +7,78 @@ namespace TrafficManager.Persistence { internal abstract class GlobalPersistentObject - : AbstractPersistentObject, - IGlobalPersistentObject, + : IGlobalPersistentObject, IComparable> where TFeature : struct { + public abstract XName ElementName { get; } + + /// + /// Moves one or more of 's required features to the forbidden list, + /// then clears any remaining required features. + /// + /// + protected virtual void ChooseVictim(FeatureFilter featureFilter) { + featureFilter.Forbid(featureFilter.GetRequired().First()); + featureFilter.GetRequiredCollection(false).Clear(); + } + + /// + /// Checks whether the element is feature-compatible with this build. + /// + /// + /// true if feature-compatible, otherwise false + public bool CanLoad(XElement element) => FeatureFilter.CanLoad(element, out var _); + + protected PersistenceResult LoadData(XElement element, Func, PersistenceResult> loadData) { + return FeatureFilter.CanLoad(element, out var featureFilter) + ? loadData(featureFilter.GetRequiredCollection(true)) + : PersistenceResult.Skip; + } + + protected PersistenceResult SaveData(XElement container, ICollection featuresForbidden, Func, ICollection, PersistenceResult> saveData) { + + var featureFilter = new FeatureFilter(); + if (featuresForbidden != null) { + foreach (var feature in featuresForbidden) { + featureFilter.Forbid(feature); + } + } + + var element = new XElement(ElementName); + var result = saveData(element, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection()); + + if (result == PersistenceResult.Success) { + + element.Add(featureFilter.GetAttributes()); + container.Add(element); + + while (result == PersistenceResult.Success && featureFilter.IsAnyRequired()) { + + ChooseVictim(featureFilter); + + if (featureFilter.GetForbiddenCollection().Count == Enum.GetValues(typeof(TFeature)).Length) + break; + + element = new XElement(ElementName); + result = saveData(element, + featureFilter.GetRequiredCollection(false), + featureFilter.GetForbiddenCollection()); + + if (result == PersistenceResult.Success) { + element.Add(featureFilter.GetAttributes()); + container.Add(element); + } + } + if (result == PersistenceResult.Skip) + result = PersistenceResult.Success; + } + + return result; + } + public abstract Type DependencyTarget { get; } int IComparable.CompareTo(object obj) => CompareTo((GlobalPersistentObject)obj); diff --git a/TLM/TLM/Persistence/PersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs deleted file mode 100644 index 3a54c3202..000000000 --- a/TLM/TLM/Persistence/PersistentObject.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Linq; - -namespace TrafficManager.Persistence { - - internal abstract class PersistentObject - : AbstractPersistentObject - where TFeature : struct { - - /// - /// Override to load data - /// - /// An XML element that was filled by - /// The object that was loaded - /// A read-only collection of features that were known to the build that created this element. - /// When a particular feature caused a breaking change in the save data, the absence of that feature from this collection - /// means that the data must be read the old way. - /// The persistence context of this load operation - /// - protected abstract PersistenceResult OnLoadData(XElement element, out TObject obj, ICollection featuresRequired, PersistenceContext context); - - /// - /// Verifies feature compatibility and conditionally calls the overridable - /// . - /// - /// - /// - /// - /// - public PersistenceResult LoadData(XElement element, out TObject obj, PersistenceContext context) { - TObject result = default; - try { - return LoadData(element, r => OnLoadData(element, out result, r, context)); - } - finally { - obj = result; - } - } - - /// - /// Override to save data - /// - /// An XML element to be filled with save data - /// The object to be saved - /// A collection of features this save data depends on. When a new feature introduces a - /// breaking change, the feature must be added to this collection to avoid data loss when if the player reverts to an earlier build. - /// A collection of features that are to be omitted from the save data for backward compatibility. - /// If the implementation cannot write backward-compatible save data that omits the features in this collection, - /// it must return . - /// The persistence context of this save operation - /// - protected abstract PersistenceResult OnSaveData(XElement element, TObject obj, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context); - - /// - /// Calls . - /// When that method reports the use of features that introduced breaking changes, successive calls are made to request - /// backward-compatible save data. - /// - /// - /// - /// - /// - public PersistenceResult SaveData(XElement container, TObject obj, ICollection featuresForbidden, PersistenceContext context) { - return SaveData(container, featuresForbidden, (e, r, f) => OnSaveData(e, obj, r, f, context)); - } - } -} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 4318bc2da..f0aedb8a9 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -1,4 +1,4 @@ - + @@ -155,13 +155,11 @@ - - From f61d990e470d7c139d5f10def0ed7657f4e8677f Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 13:58:29 -0500 Subject: [PATCH 20/31] Renaming --- .../Impl/TrafficLightSimulationManager.Persistence.cs | 2 +- TLM/TLM/Persistence/GlobalPersistence.cs | 2 +- ...IGlobalPersistentObject.cs => IPersistentObject.cs} | 2 +- .../{GlobalPersistentObject.cs => PersistentObject.cs} | 10 +++++----- TLM/TLM/TLM.csproj | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) rename TLM/TLM/Persistence/{IGlobalPersistentObject.cs => IPersistentObject.cs} (88%) rename TLM/TLM/Persistence/{GlobalPersistentObject.cs => PersistentObject.cs} (95%) diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index b0def4721..a97750538 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -17,7 +17,7 @@ namespace TrafficManager.Manager.Impl { partial class TrafficLightSimulationManager { - internal class Persistence : GlobalPersistentObject { + internal class Persistence : PersistentObject { public enum TtlFeature { diff --git a/TLM/TLM/Persistence/GlobalPersistence.cs b/TLM/TLM/Persistence/GlobalPersistence.cs index b5187f47a..c9ed34529 100644 --- a/TLM/TLM/Persistence/GlobalPersistence.cs +++ b/TLM/TLM/Persistence/GlobalPersistence.cs @@ -6,6 +6,6 @@ namespace TrafficManager.Persistence { internal static class GlobalPersistence { - public static List PersistentObjects { get; } = new List(); + public static List PersistentObjects { get; } = new List(); } } diff --git a/TLM/TLM/Persistence/IGlobalPersistentObject.cs b/TLM/TLM/Persistence/IPersistentObject.cs similarity index 88% rename from TLM/TLM/Persistence/IGlobalPersistentObject.cs rename to TLM/TLM/Persistence/IPersistentObject.cs index ed91dbc4f..19a3c5d46 100644 --- a/TLM/TLM/Persistence/IGlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/IPersistentObject.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace TrafficManager.Persistence { - internal interface IGlobalPersistentObject : IComparable { + internal interface IPersistentObject : IComparable { Type DependencyTarget { get; } diff --git a/TLM/TLM/Persistence/GlobalPersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs similarity index 95% rename from TLM/TLM/Persistence/GlobalPersistentObject.cs rename to TLM/TLM/Persistence/PersistentObject.cs index 4e7042580..dd8d3915d 100644 --- a/TLM/TLM/Persistence/GlobalPersistentObject.cs +++ b/TLM/TLM/Persistence/PersistentObject.cs @@ -6,9 +6,9 @@ namespace TrafficManager.Persistence { - internal abstract class GlobalPersistentObject - : IGlobalPersistentObject, - IComparable> + internal abstract class PersistentObject + : IPersistentObject, + IComparable> where TFeature : struct { public abstract XName ElementName { get; } @@ -81,9 +81,9 @@ protected PersistenceResult SaveData(XElement container, ICollection f public abstract Type DependencyTarget { get; } - int IComparable.CompareTo(object obj) => CompareTo((GlobalPersistentObject)obj); + int IComparable.CompareTo(object obj) => CompareTo((PersistentObject)obj); - public int CompareTo(GlobalPersistentObject other) { + public int CompareTo(PersistentObject other) { bool thisDependsOnOther = GetDependencies()?.Contains(other.DependencyTarget) == true; bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index f0aedb8a9..9aa16dea5 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -156,8 +156,8 @@ - - + + From 51caa14da897d529eaf6191112a40ffd9b24ec18 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 14:09:11 -0500 Subject: [PATCH 21/31] Unused usings didn't work in release build --- .../Manager/Impl/TrafficLightSimulationManager.Persistence.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index a97750538..92b8f4ace 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -10,8 +10,6 @@ using TrafficManager.Util; using TrafficManager.Util.Extensions; using static RoadBaseAI; -using DebugSwitch = TrafficManager.State.ConfigData.DebugSwitch; -using static TrafficManager.State.ConfigData.DebugSwitchExtensions; namespace TrafficManager.Manager.Impl { From 3ceee80a553a85395a9bf5dd0e46825cf1267c5c Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 7 May 2022 17:07:34 -0500 Subject: [PATCH 22/31] Include System.Xml.Linq.dll in deployment --- TLM/TLM/TLM.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 9aa16dea5..dea178770 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -113,7 +113,9 @@ - + + True + ..\packages\UnifiedUILib.2.2.1\lib\net35\UnifiedUILib.dll @@ -1281,6 +1283,7 @@ xcopy /y "$(TargetDir)CSUtil.Commons.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)CitiesHarmony.API.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)MoveItIntegration.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)UnifiedUILib.dll" "%25DEPLOYDIR%25" +xcopy /y "$(TargetDir)System.Xml.Linq.dll" "%25DEPLOYDIR%25" rem To avoid double hot reload, TrafficManager.dll must be replaced last and fast. rem Once TrafficManager.dll is re-loaded, all other dlls will be reloaded as well From 639e4b7f109716376d4bfc8de2ab9ec8be52ae41 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sun, 8 May 2022 10:06:20 -0500 Subject: [PATCH 23/31] Fully persistable model for JunctionRestrictionsManager --- .../Impl/JunctionRestrictionsManager.cs | 44 +++++++------------ .../Manager/Model/JunctionRestrictionFlags.cs | 16 +++++++ .../Model/JunctionRestrictionsModel.cs | 13 ++++++ TLM/TLM/TLM.csproj | 2 + 4 files changed, 48 insertions(+), 27 deletions(-) create mode 100644 TLM/TLM/Manager/Model/JunctionRestrictionFlags.cs create mode 100644 TLM/TLM/Manager/Model/JunctionRestrictionsModel.cs diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs index cd2ca801a..4ea37cf2a 100644 --- a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs @@ -17,6 +17,7 @@ namespace TrafficManager.Manager.Impl { using TrafficManager.Util; using TrafficManager.Util.Extensions; using TrafficManager.Lifecycle; + using TrafficManager.Manager.Model; public class JunctionRestrictionsManager : AbstractGeometryObservingManager, @@ -1318,15 +1319,6 @@ public bool LoadData(List data) { return ret; } - private enum JunctionRestrictionFlags { - AllowUTurn = 1 << 0, - AllowNearTurnOnRed = 1 << 1, - AllowFarTurnOnRed = 1 << 2, - AllowForwardLaneChange = 1 << 3, - AllowEnterWhenBlocked = 1 << 4, - AllowPedestrianCrossing = 1 << 5, - } - private struct SegmentJunctionRestrictions { public JunctionRestrictions startNodeRestrictions; public JunctionRestrictions endNodeRestrictions; @@ -1370,16 +1362,14 @@ public override string ToString() { private struct JunctionRestrictions { - private JunctionRestrictionFlags values; - - private JunctionRestrictionFlags mask; + private JunctionRestrictionsModel model; private JunctionRestrictionFlags defaults; public void ClearValue(JunctionRestrictionFlags flags) { - values &= ~flags; - mask &= ~flags; + model.values &= ~flags; + model.mask &= ~flags; } public void SetDefault(JunctionRestrictionFlags flags, bool value) { @@ -1394,36 +1384,36 @@ public bool GetDefault(JunctionRestrictionFlags flags) { } public bool HasValue(JunctionRestrictionFlags flags) { - return (mask & flags) == flags; + return (model.mask & flags) == flags; } public TernaryBool GetTernaryBool(JunctionRestrictionFlags flags) { - return (mask & flags) == flags - ? (values & flags) == flags + return (model.mask & flags) == flags + ? (model.values & flags) == flags ? TernaryBool.True : TernaryBool.False : TernaryBool.Undefined; } public bool GetValueOrDefault(JunctionRestrictionFlags flags) { - return ((values & flags & mask) | (defaults & flags & ~mask)) == flags; + return ((model.values & flags & model.mask) | (defaults & flags & ~model.mask)) == flags; } public void SetValue(JunctionRestrictionFlags flags, TernaryBool value) { switch (value) { case TernaryBool.True: - values |= flags; - mask |= flags; + model.values |= flags; + model.mask |= flags; break; case TernaryBool.False: - values &= ~flags; - mask |= flags; + model.values &= ~flags; + model.mask |= flags; break; case TernaryBool.Undefined: - values &= ~flags; - mask &= ~flags; + model.values &= ~flags; + model.mask &= ~flags; break; default: @@ -1432,11 +1422,11 @@ public void SetValue(JunctionRestrictionFlags flags, TernaryBool value) { } public bool IsDefault() { - return ((values & mask) | (defaults & ~mask)) == defaults; + return ((model.values & model.mask) | (defaults & ~model.mask)) == defaults; } public void Reset(bool resetDefaults = true) { - values = mask = default; + model.values = model.mask = default; if (resetDefaults) { defaults = default; @@ -1445,7 +1435,7 @@ public void Reset(bool resetDefaults = true) { public override string ToString() { return string.Format( - $"[JunctionRestrictions\n\tvalues = {values}\n\tmask = {mask}\n" + + $"[JunctionRestrictions\n\tvalues = {model.values}\n\tmask = {model.mask}\n" + $"defaults = {defaults}\n" + "JunctionRestrictions]"); } diff --git a/TLM/TLM/Manager/Model/JunctionRestrictionFlags.cs b/TLM/TLM/Manager/Model/JunctionRestrictionFlags.cs new file mode 100644 index 000000000..aed0554f5 --- /dev/null +++ b/TLM/TLM/Manager/Model/JunctionRestrictionFlags.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.Manager.Model { + internal enum JunctionRestrictionFlags { + AllowUTurn = 1 << 0, + AllowNearTurnOnRed = 1 << 1, + AllowFarTurnOnRed = 1 << 2, + AllowForwardLaneChange = 1 << 3, + AllowEnterWhenBlocked = 1 << 4, + AllowPedestrianCrossing = 1 << 5, + } + +} diff --git a/TLM/TLM/Manager/Model/JunctionRestrictionsModel.cs b/TLM/TLM/Manager/Model/JunctionRestrictionsModel.cs new file mode 100644 index 000000000..d51e104ee --- /dev/null +++ b/TLM/TLM/Manager/Model/JunctionRestrictionsModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.Manager.Model { + internal struct JunctionRestrictionsModel { + + public JunctionRestrictionFlags values; + + public JunctionRestrictionFlags mask; + } +} diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index dea178770..439c944c5 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -156,6 +156,8 @@ + + From 8aad2fa78edaa12741b52de9b83a076d29abd8ae Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 14 May 2022 14:53:09 -0500 Subject: [PATCH 24/31] Load/Save Junction Restrictions in DOM --- .../Lifecycle/SerializableDataExtension.cs | 3 + ...JunctionRestrictionsManager.Persistence.cs | 49 ++++++++++++ .../Impl/JunctionRestrictionsManager.cs | 54 ++++++++++++- ...afficLightSimulationManager.Persistence.cs | 2 +- TLM/TLM/Manager/Model/SegmentEndPair.cs | 15 ++++ TLM/TLM/Persistence/IPersistentObject.cs | 2 +- TLM/TLM/Persistence/PersistentObject.cs | 7 +- TLM/TLM/Persistence/XSerializer.cs | 76 +++++++++++++++++++ TLM/TLM/Persistence/XmlLinqExtensions.cs | 65 +++++++++++----- TLM/TLM/TLM.csproj | 3 + 10 files changed, 250 insertions(+), 26 deletions(-) create mode 100644 TLM/TLM/Manager/Impl/JunctionRestrictionsManager.Persistence.cs create mode 100644 TLM/TLM/Manager/Model/SegmentEndPair.cs create mode 100644 TLM/TLM/Persistence/XSerializer.cs diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 3feb16ab0..e7cdad803 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -1,3 +1,6 @@ +#define DEBUGLOAD +#define DEBUGSAVE + namespace TrafficManager.Lifecycle { using CSUtil.Commons; using ICities; diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.Persistence.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.Persistence.cs new file mode 100644 index 000000000..89c18b841 --- /dev/null +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.Persistence.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using TrafficManager.Manager.Model; +using TrafficManager.Persistence; + +namespace TrafficManager.Manager.Impl { + partial class JunctionRestrictionsManager { + + internal class Persistence : PersistentObject { + public override XName ElementName => "JunctionRestrictions"; + + public static readonly XName segmentElementName = "Segment"; + + public override Type DependencyTarget => typeof(JunctionRestrictionsManager); + + public override IEnumerable GetDependencies() { + yield return typeof(LaneConnection.LaneConnectionManager); + } + + protected override PersistenceResult OnLoadData(XElement element, ICollection featuresRequired, PersistenceContext context) { + + foreach (var segmentElement in element.Elements(segmentElementName)) { + Instance.AddSegmentModel(segmentElement.XDeserialize>()); + } + + return PersistenceResult.Success; + } + + protected override PersistenceResult OnSaveData(XElement element, ICollection featuresRequired, ICollection featuresForbidden, PersistenceContext context) { + var result = PersistenceResult.Success; + + foreach (var segment in Instance.EnumerateSegmentModels()) { + element.Add(segment.XSerialize(segmentElementName)); + } + + return result; + } + + public enum JunctionRestrictionsFeature { + + None = 0, + } + + } + } +} diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs index 4ea37cf2a..330275f9c 100644 --- a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs @@ -18,8 +18,9 @@ namespace TrafficManager.Manager.Impl { using TrafficManager.Util.Extensions; using TrafficManager.Lifecycle; using TrafficManager.Manager.Model; + using TrafficManager.Persistence; - public class JunctionRestrictionsManager + public partial class JunctionRestrictionsManager : AbstractGeometryObservingManager, ICustomDataManager>, IJunctionRestrictionsManager @@ -37,8 +38,58 @@ public class JunctionRestrictionsManager private JunctionRestrictionsManager() { segmentRestrictions = new SegmentJunctionRestrictions[NetManager.MAX_SEGMENT_COUNT]; invalidSegmentRestrictions = new SegmentJunctionRestrictions[NetManager.MAX_SEGMENT_COUNT]; + + GlobalPersistence.PersistentObjects.Add(new Persistence()); } + private IEnumerable> EnumerateSegmentModels() { + + for (ushort segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; segmentId++) { + + var netSegment = segmentId.ToSegment(); + + if (netSegment.IsValid() + && netSegment.m_startNode.ToNode().IsValid() + && netSegment.m_endNode.ToNode().IsValid() + && !segmentRestrictions[segmentId].IsDefault()) { + + yield return new SegmentEndPair { + segmentId = segmentId, + start = segmentRestrictions[segmentId].startNodeRestrictions.Model, + end = segmentRestrictions[segmentId].endNodeRestrictions.Model, + }; + } + } + } + + private void AddSegmentModel(SegmentEndPair model) { + + AddSegmentModel(model.start, model.segmentId, true); + AddSegmentModel(model.end, model.segmentId, false); + } + + private void AddSegmentModel(JunctionRestrictionsModel model, ushort segmentId, bool startNode) { + + ref var segment = ref segmentId.ToSegment(); + ref var node = ref (startNode ? ref segment.m_startNode.ToNode() : ref segment.m_endNode.ToNode()); + + SetFlag(JunctionRestrictionFlags.AllowUTurn, IsUturnAllowedConfigurable, SetUturnAllowed, ref node); + SetFlag(JunctionRestrictionFlags.AllowNearTurnOnRed, IsNearTurnOnRedAllowedConfigurable, SetNearTurnOnRedAllowed, ref node); + SetFlag(JunctionRestrictionFlags.AllowFarTurnOnRed, IsFarTurnOnRedAllowedConfigurable, SetFarTurnOnRedAllowed, ref node); + SetFlag(JunctionRestrictionFlags.AllowForwardLaneChange, IsLaneChangingAllowedWhenGoingStraightConfigurable, SetLaneChangingAllowedWhenGoingStraight, ref node); + SetFlag(JunctionRestrictionFlags.AllowEnterWhenBlocked, IsEnteringBlockedJunctionAllowedConfigurable, SetEnteringBlockedJunctionAllowed, ref node); + SetFlag(JunctionRestrictionFlags.AllowPedestrianCrossing, IsPedestrianCrossingAllowedConfigurable, SetPedestrianCrossingAllowed, ref node); + + void SetFlag(JunctionRestrictionFlags property, InternalGet isConfigurable, Func set, ref NetNode node) { + + if ((model.mask & property) != 0 && isConfigurable(segmentId, startNode, ref node)) { + set(segmentId, startNode, (model.values & property) != 0); + } + } + } + + private delegate bool InternalGet(ushort segmentId, bool startNode, ref NetNode node); + private void AddInvalidSegmentJunctionRestrictions(ushort segmentId, bool startNode, ref JunctionRestrictions restrictions) { @@ -1366,6 +1417,7 @@ private struct JunctionRestrictions { private JunctionRestrictionFlags defaults; + public JunctionRestrictionsModel Model => model; public void ClearValue(JunctionRestrictionFlags flags) { model.values &= ~flags; diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs index 92b8f4ace..a9ff197d9 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.Persistence.cs @@ -133,7 +133,7 @@ private static void LoadSegLights(XElement segLightsElement, TimedTrafficLightsS if (step.CustomSegmentLights.TryGetValue(segmentId, out var lights)) { var manualPedestrianMode = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.ManualPedestrianMode)); - var pedestrianLightState = segLightsElement.NullableAttribute(nameof(ICustomSegmentLightsModel.PedestrianLightState)); + var pedestrianLightState = segLightsElement.Attribute(nameof(ICustomSegmentLightsModel.PedestrianLightState)); #if DEBUGLOAD Log._Debug($"Loading pedestrian light at segment {segmentId}: " + diff --git a/TLM/TLM/Manager/Model/SegmentEndPair.cs b/TLM/TLM/Manager/Model/SegmentEndPair.cs new file mode 100644 index 000000000..aa33eb671 --- /dev/null +++ b/TLM/TLM/Manager/Model/SegmentEndPair.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TrafficManager.Manager.Model { + internal struct SegmentEndPair { + + public ushort segmentId; + + public T start; + + public T end; + } +} diff --git a/TLM/TLM/Persistence/IPersistentObject.cs b/TLM/TLM/Persistence/IPersistentObject.cs index 19a3c5d46..19a23e390 100644 --- a/TLM/TLM/Persistence/IPersistentObject.cs +++ b/TLM/TLM/Persistence/IPersistentObject.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; namespace TrafficManager.Persistence { - internal interface IPersistentObject : IComparable { + internal interface IPersistentObject : IComparable, IComparable { Type DependencyTarget { get; } diff --git a/TLM/TLM/Persistence/PersistentObject.cs b/TLM/TLM/Persistence/PersistentObject.cs index dd8d3915d..feb839170 100644 --- a/TLM/TLM/Persistence/PersistentObject.cs +++ b/TLM/TLM/Persistence/PersistentObject.cs @@ -7,8 +7,7 @@ namespace TrafficManager.Persistence { internal abstract class PersistentObject - : IPersistentObject, - IComparable> + : IPersistentObject where TFeature : struct { public abstract XName ElementName { get; } @@ -81,9 +80,9 @@ protected PersistenceResult SaveData(XElement container, ICollection f public abstract Type DependencyTarget { get; } - int IComparable.CompareTo(object obj) => CompareTo((PersistentObject)obj); + int IComparable.CompareTo(object obj) => CompareTo((IPersistentObject)obj); - public int CompareTo(PersistentObject other) { + public int CompareTo(IPersistentObject other) { bool thisDependsOnOther = GetDependencies()?.Contains(other.DependencyTarget) == true; bool otherDependsOnThis = other.GetDependencies()?.Contains(DependencyTarget) == true; diff --git a/TLM/TLM/Persistence/XSerializer.cs b/TLM/TLM/Persistence/XSerializer.cs new file mode 100644 index 000000000..641736a59 --- /dev/null +++ b/TLM/TLM/Persistence/XSerializer.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml.Linq; + +namespace TrafficManager.Persistence { + public static class XSerializer { + + public static XElement XSerialize(this T value, XName name) + where T : new() + => value.XSerialize(typeof(T), name); + + public static XElement XSerialize(this object value, Type type, XName name) { + + XElement element = null; + + if (value != null) { + + if (Type.GetTypeCode(type) != TypeCode.Object) { + throw new ArgumentException("Non-primitive type required"); + } + + element = new XElement(name); + + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic)) { + + var fieldValue = field.GetValue(value); + + if (fieldValue != null) { + + if (Type.GetTypeCode(field.FieldType) == TypeCode.Object) { + element.Add(fieldValue.XSerialize(field.FieldType, field.Name)); + } else { + element.AddAttribute(field.Name, fieldValue); + } + } + } + } + return element; + } + + public static T XDeserialize(this XElement element) + where T : new() + => (T)element.XDeserialize(typeof(T)); + + public static object XDeserialize(this XElement element, Type type) { + + object result = null; + + if (element != null) { + + if (Type.GetTypeCode(type) != TypeCode.Object) { + throw new ArgumentException("Non-primitive type required"); + } + + result = Activator.CreateInstance(type); + + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic)) { + + if (Type.GetTypeCode(field.FieldType) == TypeCode.Object) { + var memberElement = element.Element(field.Name); + if (memberElement != null) { + field.SetValue(result, memberElement.XDeserialize(field.FieldType)); + } + } else { + field.SetValue(result, element.Attribute(field.FieldType, field.Name)); + } + } + } + + return result; + } + } +} diff --git a/TLM/TLM/Persistence/XmlLinqExtensions.cs b/TLM/TLM/Persistence/XmlLinqExtensions.cs index 5bad033c5..224bb3abd 100644 --- a/TLM/TLM/Persistence/XmlLinqExtensions.cs +++ b/TLM/TLM/Persistence/XmlLinqExtensions.cs @@ -1,7 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; using System.Xml; using System.Xml.Linq; @@ -9,12 +11,24 @@ namespace TrafficManager.Persistence { internal static class XmlLinqExtensions { + public static XElement NewElement(this XElement element, XName name) { + var result = new XElement(name); + element.Add(result); + return result; + } + public static void AddAttribute(this XElement element, XName name, T value) { if (!(typeof(T).IsClass && ReferenceEquals(value, default(T)))) { element.Add(new XAttribute(name, ConvertToXml(value))); } } + public static void AddAttribute(this XElement element, XName name, object value) { + if (value != null) { + element.Add(new XAttribute(name, ConvertToXml(value))); + } + } + public static void AddElement(this XElement element, XName name, object value) { if (value != null) { element.Add(new XElement(name, ConvertToXml(value))); @@ -74,46 +88,59 @@ public static void AddAttribute(this XElement element, XName name, T? value) public static T Attribute(this XElement element, XName name) { var value = element.Attribute(name)?.Value; - return value == null ? (T)(object)value : ConvertFromXml(value); + return value == null ? default : ConvertFromXml(value); } - public static T? NullableAttribute(this XElement element, XName name) - where T : struct - { - + public static object Attribute(this XElement element, Type type, XName name) { var value = element.Attribute(name)?.Value; - return value == null ? default : ConvertFromXml(value); + return value == null + ? type.IsValueType + ? Activator.CreateInstance(type) + : null + : ConvertFromXml(value, type); } public static T Element(this XElement element, XName name) { var value = element.Element(name)?.Value; - return value == null ? (T)(object)value : ConvertFromXml(value); + return value == null ? default : ConvertFromXml(value); } - public static IEnumerable Elements(this XElement element, XName name) - => element.Elements(name).Select(e => ConvertFromXml(e.Value)); - - public static T? NullableElement(this XElement element, XName name) - where T : struct { + public static object Element(this XElement element, Type type, XName name) { var value = element.Element(name)?.Value; - return value == null ? default : ConvertFromXml(value); + return value == null + ? type.IsValueType + ? Activator.CreateInstance(type) + : null + : ConvertFromXml(value, type); } + public static IEnumerable Elements(this XElement element, XName name) + => element.Elements(name).Select(e => ConvertFromXml(e.Value)); + public static T Value(this XElement element) => ConvertFromXml(element.Value); - private static T ConvertFromXml(string value) { - switch (Type.GetTypeCode(typeof(T))) { + public static object Value(this XElement element, Type type) => ConvertFromXml(element.Value, type); + + private static T ConvertFromXml(string value) => (T)ConvertFromXml(value, typeof(T)); + + private static object ConvertFromXml(string value, Type type) { + + if (type.IsValueType) { + type = Nullable.GetUnderlyingType(type) ?? type; + } + + switch (Type.GetTypeCode(type)) { case TypeCode.DateTime: - return (T)(object)DateTime.ParseExact(value, "O", CultureInfo.InvariantCulture); + return DateTime.ParseExact(value, "O", CultureInfo.InvariantCulture); default: - if (typeof(T).IsEnum) { - return (T)Enum.Parse(typeof(T), value); + if (type.IsEnum) { + return Enum.Parse(type, value); } else { - return (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture); + return Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } } } diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 439c944c5..d29c629f7 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -150,6 +150,7 @@ + @@ -158,6 +159,7 @@ + @@ -165,6 +167,7 @@ + From 4b2ef270c3be76d560ade3a12db57abe68264e84 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 14 May 2022 15:52:48 -0500 Subject: [PATCH 25/31] Remove setters from read-only traffic light model --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 3 --- TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs | 4 ++-- TLM/TLM/TrafficLight/Model/ITimedTrafficLightsModel.cs | 2 +- .../TrafficLight/Model/ITimedTrafficLightsStepModel.cs | 8 ++++---- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index e7cdad803..3feb16ab0 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -1,6 +1,3 @@ -#define DEBUGLOAD -#define DEBUGSAVE - namespace TrafficManager.Lifecycle { using CSUtil.Commons; using ICities; diff --git a/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs b/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs index 8d6e16e4b..007630eb3 100644 --- a/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs +++ b/TLM/TLM/TrafficLight/Model/ICustomSegmentLightsModel.cs @@ -10,8 +10,8 @@ internal interface ICustomSegmentLightsModel : IEnumerable EnumerateCustomSegmentLights(); } From dc98ac15234ec779646b694fb8b577723cd7005e Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Sat, 14 May 2022 17:53:19 -0500 Subject: [PATCH 26/31] Compression --- .../Lifecycle/SerializableDataExtension.cs | 97 +++++++++++++++---- 1 file changed, 76 insertions(+), 21 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 3feb16ab0..3dd87263e 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -16,6 +16,7 @@ namespace TrafficManager.Lifecycle { using System.Xml.Linq; using TrafficManager.Persistence; using System.Xml; + using System.IO.Compression; [UsedImplicitly] public class SerializableDataExtension @@ -185,27 +186,50 @@ private static void DeserializeVersionData(byte[] data) { private static void LoadDom(byte[] data) { try { if (data?.Length > 0) { - using (var memoryStream = new MemoryStream(data)) { - using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { - XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { - ProhibitDtd = false, - XmlResolver = null, - }; - using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) - _dom = XDocument.Load(xmlReader); + try { + using (var memoryStream = new MemoryStream(data)) { + using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { + using (var streamReader = new StreamReader(compressionStream, Encoding.UTF8)) { + + XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { + ProhibitDtd = false, + XmlResolver = null, + }; + using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) + _dom = XDocument.Load(xmlReader); + } + } + } + } + catch (Exception ex) { -#if DEBUGLOAD - Log._Debug("Loaded DOM:\r" + _dom.ToString()); -#endif + Log.Error("Load DOM failed, attempting without compression. " + ex); + + using (var memoryStream = new MemoryStream(data)) { + using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { - _persistenceMigration = GlobalPersistence.PersistentObjects - .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) - .Select(o => o.DependencyTarget) - .Distinct() - .ToArray(); + XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { + ProhibitDtd = false, + XmlResolver = null, + }; + using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) + _dom = XDocument.Load(xmlReader); + } } + + Log.Info("Load DOM without compression succeeded."); + } +#if DEBUGLOAD + Log._Debug("Loaded DOM:\r" + _dom.ToString()); +#endif + + _persistenceMigration = GlobalPersistence.PersistentObjects + .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) + .Select(o => o.DependencyTarget) + .Distinct() + .ToArray(); } else { Log.Info("No DOM to load!"); } @@ -507,14 +531,20 @@ private static void SaveDom(ref bool success) { result.LogMessage($"SaveData for DOM element {o.ElementName} reported {result}."); } - using (var memoryStream = new MemoryStream()) { - using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { - #if DEBUGSAVE - Log._Debug("Saving DOM:\r" + _dom.ToString()); + Log._Debug("Saving DOM:\r" + _dom.ToString()); #endif - _dom.Save(streamWriter); + try { + using (var memoryStream = new MemoryStream()) { + + using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { + + using (var streamWriter = new StreamWriter(compressionStream, Encoding.UTF8)) { + + _dom.Save(streamWriter); + } + } memoryStream.Position = 0; Log.Info($"Save DOM byte length {memoryStream.Length}"); @@ -522,6 +552,31 @@ private static void SaveDom(ref bool success) { SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); } } + catch (Exception ex) { + + Log.Error("Save DOM failed, attempting without compression. " + ex); + + try { + SerializableData.EraseData(DOM_ID); + } + catch { + } + + using (var memoryStream = new MemoryStream()) { + using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { + + _dom.Save(streamWriter); + + memoryStream.Position = 0; + Log.Info($"Save DOM byte length {memoryStream.Length}"); + + SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); + } + } + + Log.Info("Save DOM without compression succeeded."); + + } } catch (Exception ex) { Log.Error("Unexpected error while saving DOM: " + ex); From 5f03eeb36fd22686c3d699c8865bf778077c6ef8 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Fri, 20 May 2022 20:46:40 -0500 Subject: [PATCH 27/31] Separate container for each manager's save data --- .../Lifecycle/SerializableDataExtension.cs | 212 ++++++++++-------- TLM/TLM/TLM.csproj | 1 + TLM/TLM/Util/ReferenceEqualityComparer.cs | 19 ++ 3 files changed, 133 insertions(+), 99 deletions(-) create mode 100644 TLM/TLM/Util/ReferenceEqualityComparer.cs diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 3dd87263e..0fe684385 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -26,11 +26,10 @@ public class SerializableDataExtension private const string DATA_ID = "TrafficManager_v1.0"; private const string VERSION_INFO_DATA_ID = "TrafficManager_VersionInfo_v1.0"; - private const string DOM_ID = "TrafficManager_Document_v1.0"; private static ISerializableData SerializableData => SimulationManager.instance.m_SerializableDataWrapper; - private static XDocument _dom; - private static Type[] _persistenceMigration; + private static Dictionary _domCollection = new Dictionary(new ReferenceEqualityComparer()); + private static HashSet _persistenceMigration = new HashSet(new ReferenceEqualityComparer()); private static Configuration _configuration; private static VersionInfoConfiguration _versionInfoConfiguration; @@ -74,11 +73,10 @@ public static void Load() { } try { - byte[] data = SerializableData.LoadData(DOM_ID); - LoadDom(data); + LoadDomCollection(); } catch (Exception e) { - Log.Error($"OnLoadData: Error while deserializing container collection (old savegame?): {e}"); + Log.Error($"OnLoadData: Error while loading DOM collection: {e}"); } bool loadedData = false; @@ -183,85 +181,97 @@ private static void DeserializeVersionData(byte[] data) { } } - private static void LoadDom(byte[] data) { - try { - if (data?.Length > 0) { + private static void LoadDomCollection() { - try { - using (var memoryStream = new MemoryStream(data)) { - using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { - using (var streamReader = new StreamReader(compressionStream, Encoding.UTF8)) { + _domCollection.Clear(); + _persistenceMigration.Clear(); + + foreach (var po in GlobalPersistence.PersistentObjects) { + + byte[] data; + try { + data = SerializableData.LoadData(po.DependencyTarget.FullName); + } + catch { + Log.Info($"No DOM found for {po.DependencyTarget.Name}"); + data = null; + } + + try { + if (data?.Length > 0) { + + Log.Info($"Attempting to load DOM for {po.DependencyTarget.Name}"); + + try { + using (var memoryStream = new MemoryStream(data)) { + using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { + using (var streamReader = new StreamReader(compressionStream, Encoding.UTF8)) { + + XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { + ProhibitDtd = false, + XmlResolver = null, + }; + using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) + _domCollection[po] = XDocument.Load(xmlReader); + } + } + } + } + catch (Exception ex) { + + Log.Error("Load DOM failed, attempting without compression. " + ex); + + using (var memoryStream = new MemoryStream(data)) { + using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { ProhibitDtd = false, XmlResolver = null, }; using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) - _dom = XDocument.Load(xmlReader); + _domCollection[po] = XDocument.Load(xmlReader); } } - } - } - catch (Exception ex) { - Log.Error("Load DOM failed, attempting without compression. " + ex); + Log.Info("Load DOM without compression succeeded."); - using (var memoryStream = new MemoryStream(data)) { - using (var streamReader = new StreamReader(memoryStream, Encoding.UTF8)) { - - XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { - ProhibitDtd = false, - XmlResolver = null, - }; - using (var xmlReader = XmlReader.Create(streamReader, xmlReaderSettings)) - _dom = XDocument.Load(xmlReader); - } } - Log.Info("Load DOM without compression succeeded."); - - } + if (_domCollection[po]?.Root?.Elements(po.ElementName)?.Any(e => po.CanLoad(e)) == true) + _persistenceMigration.Add(po.DependencyTarget); #if DEBUGLOAD - Log._Debug("Loaded DOM:\r" + _dom.ToString()); + Log._Debug($"Loaded DOM for {po.DependencyTarget.Name}: {_domCollection[po]}\r"); #endif - - _persistenceMigration = GlobalPersistence.PersistentObjects - .Where(o => _dom.Root.Elements(o.ElementName)?.Any(e => o.CanLoad(e)) == true) - .Select(o => o.DependencyTarget) - .Distinct() - .ToArray(); - } else { - Log.Info("No DOM to load!"); + } + } + catch (Exception ex) { + Log.Error($"Error loading DOM for {po.DependencyTarget.Name}: {ex}"); + Log.Info(ex.StackTrace); } - } - catch (Exception ex) { - Log.Error($"Error loading DOM: {ex}"); - Log.Info(ex.StackTrace); - throw new ApplicationException("An error occurred while loading"); } } private static void LoadDomElements() { + try { - if (_dom?.Root.HasElements == true && GlobalPersistence.PersistentObjects.Count > 0) { - foreach (var o in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { - var elements = _dom.Root.Elements(o.ElementName)?.Where(c => o.CanLoad(c)); - if (elements?.Any() == true) { - if (elements.Count() > 1) { - Log.Error($"More than one compatible element {o.ElementName} was found. Using the first one."); - } - try { - var result = o.LoadData(elements.First(), new PersistenceContext { Version = Version }); - result.LogMessage($"LoadData for DOM element {o.ElementName} reported {result}."); - } - catch (Exception ex) { - Log.Error($"Error loading DOM element {o.ElementName}: {ex}"); - Log.Info(ex.StackTrace); - } + foreach (var e in _domCollection.OrderBy(e => e.Key)) { + var po = e.Key; + var dom = e.Value; + + var elements = dom.Root.Elements(po.ElementName)?.Where(c => po.CanLoad(c)); + if (elements?.Any() == true) { + if (elements.Count() > 1) { + Log.Error($"More than one compatible element {po.ElementName} was found for {po.DependencyTarget.Name}. Using the first one."); + } + try { + var result = po.LoadData(elements.First(), new PersistenceContext { Version = Version }); + result.LogMessage($"LoadData of DOM element {po.ElementName} for {po.DependencyTarget.Name} reported {result}."); + } + catch (Exception ex) { + Log.Error($"Error loading DOM element {po.ElementName} for {po.DependencyTarget.Name}: {ex}"); + Log.Info(ex.StackTrace); } } - } else { - Log.Info("No DOM elements to load!"); } } catch (Exception ex) { @@ -523,64 +533,68 @@ public static void Save() { private static void SaveDom(ref bool success) { - try { - _dom = new XDocument(); - _dom.Add(new XElement("TmpSaveData")); - foreach (var o in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { - var result = o.SaveData(_dom.Root, new PersistenceContext { Version = Version }); - result.LogMessage($"SaveData for DOM element {o.ElementName} reported {result}."); - } + foreach (var po in GlobalPersistence.PersistentObjects.OrderBy(o => o)) { + + try { + var dom = new XDocument(); + dom.Add(new XElement(po.DependencyTarget.Name)); + + var result = po.SaveData(dom.Root, new PersistenceContext { Version = Version }); + + result.LogMessage($"SaveData of DOM element {po.ElementName} for {po.DependencyTarget.Name} reported {result}."); #if DEBUGSAVE - Log._Debug("Saving DOM:\r" + _dom.ToString()); + Log._Debug($"Saving DOM for {po.DependencyTarget.Name}: {dom}"); #endif - try { - using (var memoryStream = new MemoryStream()) { + try { + using (var memoryStream = new MemoryStream()) { - using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { + using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { - using (var streamWriter = new StreamWriter(compressionStream, Encoding.UTF8)) { + using (var streamWriter = new StreamWriter(compressionStream, Encoding.UTF8)) { - _dom.Save(streamWriter); + dom.Save(streamWriter); + } } - } - memoryStream.Position = 0; - Log.Info($"Save DOM byte length {memoryStream.Length}"); + memoryStream.Position = 0; + Log.Info($"Save DOM for {po.DependencyTarget.Name} byte length {memoryStream.Length}"); - SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); + SerializableData.SaveData(po.DependencyTarget.FullName, memoryStream.ToArray()); + } } - } - catch (Exception ex) { + catch (Exception ex) { - Log.Error("Save DOM failed, attempting without compression. " + ex); + Log.Error("Save DOM failed, attempting without compression. " + ex); - try { - SerializableData.EraseData(DOM_ID); - } - catch { - } + try { + SerializableData.EraseData(po.DependencyTarget.FullName); + } + catch { + } - using (var memoryStream = new MemoryStream()) { - using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { + using (var memoryStream = new MemoryStream()) { + using (var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8)) { - _dom.Save(streamWriter); + dom.Save(streamWriter); - memoryStream.Position = 0; - Log.Info($"Save DOM byte length {memoryStream.Length}"); + memoryStream.Position = 0; + Log.Info($"Save DOM for {po.DependencyTarget.Name} byte length {memoryStream.Length}"); - SerializableData.SaveData(DOM_ID, memoryStream.ToArray()); + SerializableData.SaveData(po.DependencyTarget.FullName, memoryStream.ToArray()); + } } - } - Log.Info("Save DOM without compression succeeded."); + Log.Info("Save DOM without compression succeeded."); + } + } + catch (Exception ex) { + + Log.Error($"Unexpected error while saving DOM for {po.DependencyTarget.Name}: {ex}"); + success = false; } - } - catch (Exception ex) { - Log.Error("Unexpected error while saving DOM: " + ex); - success = false; } } } diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index d29c629f7..376756cb5 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -491,6 +491,7 @@ + diff --git a/TLM/TLM/Util/ReferenceEqualityComparer.cs b/TLM/TLM/Util/ReferenceEqualityComparer.cs new file mode 100644 index 000000000..f9a7a7f30 --- /dev/null +++ b/TLM/TLM/Util/ReferenceEqualityComparer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace TrafficManager.Util { + internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { + + public bool Equals(T x, T y) => ReferenceEquals(x, y); + + public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); + + bool IEqualityComparer.Equals(object x, object y) => ReferenceEquals(x, y); + + int IEqualityComparer.GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); + } +} From 71595b08b9bda24616f7345513a81cbc1d62ef26 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Fri, 20 May 2022 20:58:57 -0500 Subject: [PATCH 28/31] make ReferenceEqualityComparer a singleton --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 4 ++-- TLM/TLM/Util/ReferenceEqualityComparer.cs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 0fe684385..e9bb4edb9 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -28,8 +28,8 @@ public class SerializableDataExtension private const string VERSION_INFO_DATA_ID = "TrafficManager_VersionInfo_v1.0"; private static ISerializableData SerializableData => SimulationManager.instance.m_SerializableDataWrapper; - private static Dictionary _domCollection = new Dictionary(new ReferenceEqualityComparer()); - private static HashSet _persistenceMigration = new HashSet(new ReferenceEqualityComparer()); + private static Dictionary _domCollection = new Dictionary(ReferenceEqualityComparer.Instance); + private static HashSet _persistenceMigration = new HashSet(ReferenceEqualityComparer.Instance); private static Configuration _configuration; private static VersionInfoConfiguration _versionInfoConfiguration; diff --git a/TLM/TLM/Util/ReferenceEqualityComparer.cs b/TLM/TLM/Util/ReferenceEqualityComparer.cs index f9a7a7f30..add2d2187 100644 --- a/TLM/TLM/Util/ReferenceEqualityComparer.cs +++ b/TLM/TLM/Util/ReferenceEqualityComparer.cs @@ -8,6 +8,10 @@ namespace TrafficManager.Util { internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer { + public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); + + private ReferenceEqualityComparer() { } + public bool Equals(T x, T y) => ReferenceEquals(x, y); public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); From a6a8ce1176e76e9ac1e7956483702a146193fca1 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Wed, 25 May 2022 19:58:51 -0500 Subject: [PATCH 29/31] Some better logging --- TLM/TLM/Lifecycle/SerializableDataExtension.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index e9bb4edb9..81ed80f15 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -258,13 +258,15 @@ private static void LoadDomElements() { var po = e.Key; var dom = e.Value; - var elements = dom.Root.Elements(po.ElementName)?.Where(c => po.CanLoad(c)); + var elements = dom.Root.Elements(po.ElementName)?.Where(po.CanLoad); if (elements?.Any() == true) { if (elements.Count() > 1) { Log.Error($"More than one compatible element {po.ElementName} was found for {po.DependencyTarget.Name}. Using the first one."); } try { - var result = po.LoadData(elements.First(), new PersistenceContext { Version = Version }); + XElement element = elements.First(); + Log.Info($"Calling LoadData of DOM element {po.ElementName} for {po.DependencyTarget.Name}, {element.Attribute("featuresRequired")} {element.Attribute("featuresForbidden")}"); + var result = po.LoadData(element, new PersistenceContext { Version = Version }); result.LogMessage($"LoadData of DOM element {po.ElementName} for {po.DependencyTarget.Name} reported {result}."); } catch (Exception ex) { From 0772a520483d0fd6e2f13857db08265658da64c1 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 31 May 2022 17:20:50 -0500 Subject: [PATCH 30/31] Use SharpZipLib that is bundled with CS --- .../Lifecycle/SerializableDataExtension.cs | 19 +++++++++++-------- TLM/TLM/TLM.csproj | 4 ++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/TLM/TLM/Lifecycle/SerializableDataExtension.cs b/TLM/TLM/Lifecycle/SerializableDataExtension.cs index 81ed80f15..092180848 100644 --- a/TLM/TLM/Lifecycle/SerializableDataExtension.cs +++ b/TLM/TLM/Lifecycle/SerializableDataExtension.cs @@ -16,7 +16,7 @@ namespace TrafficManager.Lifecycle { using System.Xml.Linq; using TrafficManager.Persistence; using System.Xml; - using System.IO.Compression; + using ICSharpCode.SharpZipLib.GZip; [UsedImplicitly] public class SerializableDataExtension @@ -204,7 +204,7 @@ private static void LoadDomCollection() { try { using (var memoryStream = new MemoryStream(data)) { - using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Decompress)) { + using (var compressionStream = new GZipInputStream(memoryStream)) { using (var streamReader = new StreamReader(compressionStream, Encoding.UTF8)) { XmlReaderSettings xmlReaderSettings = new XmlReaderSettings { @@ -552,18 +552,21 @@ private static void SaveDom(ref bool success) { try { using (var memoryStream = new MemoryStream()) { - using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) { + using (var compressionStream = new GZipOutputStream(memoryStream)) { using (var streamWriter = new StreamWriter(compressionStream, Encoding.UTF8)) { dom.Save(streamWriter); - } - } - memoryStream.Position = 0; - Log.Info($"Save DOM for {po.DependencyTarget.Name} byte length {memoryStream.Length}"); + streamWriter.Flush(); + compressionStream.Finish(); + + memoryStream.Position = 0; + Log.Info($"Save DOM for {po.DependencyTarget.Name} byte length {memoryStream.Length}"); - SerializableData.SaveData(po.DependencyTarget.FullName, memoryStream.ToArray()); + SerializableData.SaveData(po.DependencyTarget.FullName, memoryStream.ToArray()); + } + } } } catch (Exception ex) { diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 0a8587235..0b97dc572 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -104,6 +104,10 @@ $(MangedDLLPath)\ICities.dll False + + False + ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\ICSharpCode.SharpZipLib.dll + ..\libs\MoveItIntegration.dll From 0788894dbd3f40fe7df9c7aec4405845b76e6ea0 Mon Sep 17 00:00:00 2001 From: Elesbaan70 <95266227+Elesbaan70@users.noreply.github.com> Date: Tue, 31 May 2022 18:17:12 -0500 Subject: [PATCH 31/31] Fix DLL reference --- TLM/TLM/TLM.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 4b70b306f..5ce2554c0 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -1,4 +1,4 @@ - + @@ -106,7 +106,7 @@ False - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\ICSharpCode.SharpZipLib.dll + $(MangedDLLPath)\ICSharpCode.SharpZipLib.dll ..\libs\MoveItIntegration.dll