-
Notifications
You must be signed in to change notification settings - Fork 89
New persistence strategy #1559
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
New persistence strategy #1559
Changes from all commits
7c8f5af
6556ac3
21818ae
8ebe2e1
3e80477
1f3b84d
c179156
0fa9a85
9800020
f6cc9fd
5b69eaa
58d5120
1b53ed2
d04a570
6d7cf3b
b519407
fcaca36
43fa12d
fa6f141
762b217
40306ba
fc2d3ea
f1d735a
2c7e2f9
df5f375
f61d990
51caa14
3ceee80
eb0cd5d
639e4b7
8aad2fa
02254d6
4b2ef27
dc98ac1
5f03eeb
71595b0
a6a8ce1
ed6f077
0772a52
d53e13f
0788894
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Persistence.JunctionRestrictionsFeature> { | ||
| public override XName ElementName => "JunctionRestrictions"; | ||
|
|
||
| public static readonly XName segmentElementName = "Segment"; | ||
|
|
||
| public override Type DependencyTarget => typeof(JunctionRestrictionsManager); | ||
|
|
||
| public override IEnumerable<Type> GetDependencies() { | ||
| yield return typeof(LaneConnection.LaneConnectionManager); | ||
| } | ||
|
|
||
| protected override PersistenceResult OnLoadData(XElement element, ICollection<JunctionRestrictionsFeature> featuresRequired, PersistenceContext context) { | ||
|
|
||
| foreach (var segmentElement in element.Elements(segmentElementName)) { | ||
| Instance.AddSegmentModel(segmentElement.XDeserialize<SegmentEndPair<JunctionRestrictionsModel>>()); | ||
| } | ||
|
|
||
| return PersistenceResult.Success; | ||
| } | ||
|
|
||
| protected override PersistenceResult OnSaveData(XElement element, ICollection<JunctionRestrictionsFeature> featuresRequired, ICollection<JunctionRestrictionsFeature> 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, | ||
| } | ||
|
|
||
| } | ||
| } | ||
| } |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using TrafficManager.API.Traffic.Enums; | ||
|
|
||
| namespace TrafficManager.Manager.Model { | ||
| internal struct JunctionRestrictionsModel { | ||
|
|
||
| public JunctionRestrictionsFlags values; | ||
|
|
||
| public JunctionRestrictionsFlags mask; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
|
|
||
| namespace TrafficManager.Manager.Model { | ||
| internal struct SegmentEndPair<T> { | ||
|
|
||
| public ushort segmentId; | ||
|
|
||
| public T start; | ||
|
|
||
| public T end; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Xml.Linq; | ||
|
|
||
| namespace TrafficManager.Persistence { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loading save file with features blocked, will it skip loading the incompatible sections?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no attempt to preserve unsupported data through a load and re-save. That data is completely skipped. I would oppose adding this as a requirement. Putting it into practice in a reliable way is far more complicated than it might appear on the surface. When saving XML data, the save code has the option of marking its data as requiring a specific feature identified in an enum. It gets added to a When The above step is repeated until the save code either (1) is no longer adding items to Outside of dev and alpha builds, we will normally choose not to write old data, since we do not support older versions. We could decide to keep writing old data for some period of time, in case an emergency rollback is necessary. |
||
| internal sealed class FeatureFilter<TFeature> | ||
| 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 PersistenceException($"FeatureSet<{tFeature.FullName}>: Type {tFeature.Name} is not an enum"); | ||
| } | ||
|
|
||
| private readonly Dictionary<TFeature, FeatureStatus> filter; | ||
|
|
||
| public static bool CanLoad(XElement element, out FeatureFilter<TFeature> result) { | ||
| return CanLoad(element.Attribute(featuresRequiredAttributeName)?.Value, | ||
| element.Attribute(featuresForbiddenAttributeName)?.Value, | ||
| out result); | ||
| } | ||
|
|
||
| private static bool CanLoad(string requiredAttribute, string forbiddenAttribute, out FeatureFilter<TFeature> 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<TFeature, FeatureStatus>(); | ||
| 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<TFeature>(filter); | ||
| return true; | ||
| } | ||
|
|
||
| public FeatureFilter() => filter = new Dictionary<TFeature, FeatureStatus>(); | ||
|
|
||
| public FeatureFilter(IEnumerable<TFeature> required, IEnumerable<TFeature> forbidden) | ||
| : this() | ||
| { | ||
|
|
||
| if (required != null) { | ||
| if (forbidden != null) { | ||
| IEnumerable<TFeature> 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<TFeature, FeatureStatus> 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<TFeature> Enumerate(FeatureStatus status) | ||
| => filter.Where(e => e.Value == status).Select(e => e.Key).OrderByDescending(f => f); | ||
|
|
||
| public IEnumerable<TFeature> GetRequired() | ||
| => Enumerate(FeatureStatus.Required); | ||
|
|
||
| public IEnumerable<TFeature> GetForbidden() | ||
| => Enumerate(FeatureStatus.Forbidden); | ||
|
|
||
| public ICollection<TFeature> GetRequiredCollection(bool readOnly) | ||
| => new FeatureCollection(this, FeatureStatus.Required, readOnly); | ||
|
|
||
| public ICollection<TFeature> GetForbiddenCollection() | ||
| => new FeatureCollection(this, FeatureStatus.Forbidden, true); | ||
|
|
||
| private class FeatureCollection : ICollection<TFeature> { | ||
| private readonly FeatureFilter<TFeature> featureFilter; | ||
| private readonly FeatureStatus collectionFeatureStatus; | ||
| private readonly bool isReadOnly; | ||
|
|
||
| public FeatureCollection(FeatureFilter<TFeature> 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 PersistenceException($"{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<TFeature> 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(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
|
|
||
| namespace TrafficManager.Persistence { | ||
| internal static class GlobalPersistence { | ||
|
|
||
| public static List<IPersistentObject> PersistentObjects { get; } = new List<IPersistentObject>(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Xml.Linq; | ||
|
|
||
| namespace TrafficManager.Persistence { | ||
| internal interface IPersistentObject : IComparable, IComparable<IPersistentObject> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of new code has no xmldoc comments. Is this some copy pasted pattern code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I will do this. (It's not copy/pasted code, it's all new.) I just felt a little urgency to get people's eyes on this, because @krzychu124 is waiting on it for Airports DLC enhancements. btw, I will probably be cleaning up this framework a little more and publishing it separately as an open-source library, which I'll then use in other mod work. Whether TMPE switches over to that or continues to use its own version, is a decision that can be made when the time comes. |
||
|
|
||
| Type DependencyTarget { get; } | ||
|
|
||
| IEnumerable<Type> GetDependencies(); | ||
|
|
||
| XName ElementName { get; } | ||
|
|
||
| bool CanLoad(XElement element); | ||
|
|
||
| PersistenceResult LoadData(XElement element, PersistenceContext context); | ||
|
|
||
| PersistenceResult SaveData(XElement container, PersistenceContext context); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Xml.Linq; | ||
|
|
||
| namespace TrafficManager.Persistence { | ||
| internal class PersistenceContext { | ||
|
|
||
| public int Version { get; set; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.