From eead352af26d0fcc5cac147d0eb5ec384854d931 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 21 Apr 2018 20:37:17 -0400 Subject: rewrite world/player state tracking (#453) --- .../FieldWatchers/BaseDisposableWatcher.cs | 36 +++++++ .../FieldWatchers/ComparableWatcher.cs | 62 +++++++++++++ .../FieldWatchers/NetDictionaryWatcher.cs | 103 +++++++++++++++++++++ .../StateTracking/FieldWatchers/NetValueWatcher.cs | 83 +++++++++++++++++ .../FieldWatchers/ObservableCollectionWatcher.cs | 86 +++++++++++++++++ .../StateTracking/FieldWatchers/WatcherFactory.cs | 54 +++++++++++ 6 files changed, 424 insertions(+) create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs (limited to 'src/SMAPI/Framework/StateTracking/FieldWatchers') diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs new file mode 100644 index 00000000..40ec6c57 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs @@ -0,0 +1,36 @@ +using System; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// The base implementation for a disposable watcher. + internal abstract class BaseDisposableWatcher : IDisposable + { + /********* + ** Properties + *********/ + /// Whether the watcher has been disposed. + protected bool IsDisposed { get; private set; } + + + /********* + ** Public methods + *********/ + /// Stop watching the field and release all references. + public virtual void Dispose() + { + this.IsDisposed = true; + } + + + /********* + ** Protected methods + *********/ + /// Throw an exception if the watcher is disposed. + /// The watcher is disposed. + protected void AssertNotDisposed() + { + if (this.IsDisposed) + throw new ObjectDisposedException(this.GetType().Name); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs new file mode 100644 index 00000000..d51fc2ac --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// A watcher which detects changes to a value using a specified instance. + internal class ComparableWatcher : IValueWatcher + { + /********* + ** Properties + *********/ + /// Get the current value. + private readonly Func GetValue; + + /// The equality comparer. + private readonly IEqualityComparer Comparer; + + + /********* + ** Accessors + *********/ + /// The field value at the last reset. + public T PreviousValue { get; private set; } + + /// The latest value. + public T CurrentValue { get; private set; } + + /// Whether the value changed since the last reset. + public bool IsChanged { get; private set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Get the current value. + /// The equality comparer which indicates whether two values are the same. + public ComparableWatcher(Func getValue, IEqualityComparer comparer) + { + this.GetValue = getValue; + this.Comparer = comparer; + this.PreviousValue = getValue(); + } + + /// Update the current value if needed. + public void Update() + { + this.CurrentValue = this.GetValue(); + this.IsChanged = !this.Comparer.Equals(this.PreviousValue, this.CurrentValue); + } + + /// Set the current value as the baseline. + public void Reset() + { + this.PreviousValue = this.CurrentValue; + this.IsChanged = false; + } + + /// Release any references if needed when the field is no longer needed. + public void Dispose() { } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs new file mode 100644 index 00000000..7a2bf84e --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Netcode; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// A watcher which detects changes to a net dictionary field. + /// The dictionary key type. + /// The dictionary value type. + /// The net type equivalent to . + /// The serializable dictionary type that can store the keys and values. + /// The net field instance type. + internal class NetDictionaryWatcher : BaseDisposableWatcher, IDictionaryWatcher + where TField : class, INetObject, new() + where TSerialDict : IDictionary, new() + where TSelf : NetDictionary + { + /********* + ** Properties + *********/ + /// The pairs added since the last reset. + private readonly IDictionary PairsAdded = new Dictionary(); + + /// The pairs demoved since the last reset. + private readonly IDictionary PairsRemoved = new Dictionary(); + + /// The field being watched. + private readonly NetDictionary Field; + + + /********* + ** Accessors + *********/ + /// Whether the collection changed since the last reset. + public bool IsChanged => this.PairsAdded.Count > 0 || this.PairsRemoved.Count > 0; + + /// The values added since the last reset. + public IEnumerable> Added => this.PairsAdded; + + /// The values removed since the last reset. + public IEnumerable> Removed => this.PairsRemoved; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The field to watch. + public NetDictionaryWatcher(NetDictionary field) + { + this.Field = field; + + field.OnValueAdded += this.OnValueAdded; + field.OnValueRemoved += this.OnValueRemoved; + } + + /// Update the current value if needed. + public void Update() + { + this.AssertNotDisposed(); + } + + /// Set the current value as the baseline. + public void Reset() + { + this.AssertNotDisposed(); + + this.PairsAdded.Clear(); + this.PairsRemoved.Clear(); + } + + /// Stop watching the field and release all references. + public override void Dispose() + { + if (!this.IsDisposed) + { + this.Field.OnValueAdded -= this.OnValueAdded; + this.Field.OnValueRemoved -= this.OnValueRemoved; + } + base.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// A callback invoked when an entry is added to the dictionary. + /// The entry key. + /// The entry value. + private void OnValueAdded(TKey key, TValue value) + { + this.PairsAdded[key] = value; + } + + /// A callback invoked when an entry is removed from the dictionary. + /// The entry key. + /// The entry value. + private void OnValueRemoved(TKey key, TValue value) + { + if (!this.PairsRemoved.ContainsKey(key)) + this.PairsRemoved[key] = value; + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs new file mode 100644 index 00000000..188ed9f3 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs @@ -0,0 +1,83 @@ +using Netcode; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// A watcher which detects changes to a net value field. + internal class NetValueWatcher : BaseDisposableWatcher, IValueWatcher where TSelf : NetFieldBase + { + /********* + ** Properties + *********/ + /// The field being watched. + private readonly NetFieldBase Field; + + + /********* + ** Accessors + *********/ + /// Whether the value changed since the last reset. + public bool IsChanged { get; private set; } + + /// The field value at the last reset. + public T PreviousValue { get; private set; } + + /// The latest value. + public T CurrentValue { get; private set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The field to watch. + public NetValueWatcher(NetFieldBase field) + { + this.Field = field; + this.PreviousValue = field.Value; + this.CurrentValue = field.Value; + + field.fieldChangeVisibleEvent += this.OnValueChanged; + field.fieldChangeEvent += this.OnValueChanged; + } + + /// Update the current value if needed. + public void Update() + { + this.AssertNotDisposed(); + } + + /// Set the current value as the baseline. + public void Reset() + { + this.AssertNotDisposed(); + + this.PreviousValue = this.CurrentValue; + this.IsChanged = false; + } + + /// Stop watching the field and release all references. + public override void Dispose() + { + if (!this.IsDisposed) + { + this.Field.fieldChangeEvent -= this.OnValueChanged; + this.Field.fieldChangeVisibleEvent -= this.OnValueChanged; + } + base.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// A callback invoked when the field's value changes. + /// The field being watched. + /// The old field value. + /// The new field value. + private void OnValueChanged(TSelf field, T oldValue, T newValue) + { + this.CurrentValue = newValue; + this.IsChanged = true; + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs new file mode 100644 index 00000000..34a97097 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// A watcher which detects changes to an observable collection. + internal class ObservableCollectionWatcher : BaseDisposableWatcher, ICollectionWatcher + { + /********* + ** Properties + *********/ + /// The field being watched. + private readonly ObservableCollection Field; + + /// The pairs added since the last reset. + private readonly List AddedImpl = new List(); + + /// The pairs demoved since the last reset. + private readonly List RemovedImpl = new List(); + + + /********* + ** Accessors + *********/ + /// Whether the collection changed since the last reset. + public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0; + + /// The values added since the last reset. + public IEnumerable Added => this.AddedImpl; + + /// The values removed since the last reset. + public IEnumerable Removed => this.RemovedImpl; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The field to watch. + public ObservableCollectionWatcher(ObservableCollection field) + { + this.Field = field; + field.CollectionChanged += this.OnCollectionChanged; + } + + /// Update the current value if needed. + public void Update() + { + this.AssertNotDisposed(); + } + + /// Set the current value as the baseline. + public void Reset() + { + this.AssertNotDisposed(); + + this.AddedImpl.Clear(); + this.RemovedImpl.Clear(); + } + + /// Stop watching the field and release all references. + public override void Dispose() + { + if (!this.IsDisposed) + this.Field.CollectionChanged -= this.OnCollectionChanged; + base.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// A callback invoked when an entry is added or removed from the collection. + /// The event sender. + /// The event arguments. + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + this.AddedImpl.AddRange(e.NewItems.Cast()); + if (e.OldItems != null) + this.RemovedImpl.AddRange(e.OldItems.Cast()); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs new file mode 100644 index 00000000..bf261bb5 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Netcode; +using StardewModdingAPI.Framework.StateTracking.Comparers; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// Provides convenience wrappers for creating watchers. + internal static class WatcherFactory + { + /********* + ** Public methods + *********/ + /// Get a watcher for an value. + /// The value type. + /// Get the current value. + public static ComparableWatcher ForEquatable(Func getValue) where T : IEquatable + { + return new ComparableWatcher(getValue, new EquatableComparer()); + } + + /// Get a watcher which detects when an object reference changes. + /// The value type. + /// Get the current value. + public static ComparableWatcher ForReference(Func getValue) + { + return new ComparableWatcher(getValue, new ObjectReferenceComparer()); + } + + /// Get a watcher for an observable collection. + /// The value type. + /// The observable collection. + public static ObservableCollectionWatcher ForObservableCollection(ObservableCollection collection) + { + return new ObservableCollectionWatcher(collection); + } + + /// Get a watcher for a net dictionary. + /// The dictionary key type. + /// The dictionary value type. + /// The net type equivalent to . + /// The serializable dictionary type that can store the keys and values. + /// The net field instance type. + /// The net field. + public static NetDictionaryWatcher ForNetDictionary(NetDictionary field) + where TField : class, INetObject, new() + where TSerialDict : IDictionary, new() + where TSelf : NetDictionary + { + return new NetDictionaryWatcher(field); + } + } +} -- cgit From b8fd3aedfe884741bdda8c68398427f875585456 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 5 May 2018 01:31:06 -0400 Subject: rewrite location events for multiplayer --- .../FieldWatchers/NetCollectionWatcher.cs | 93 ++++++++++++++++++++++ .../StateTracking/FieldWatchers/WatcherFactory.cs | 8 ++ 2 files changed, 101 insertions(+) create mode 100644 src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs (limited to 'src/SMAPI/Framework/StateTracking/FieldWatchers') diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs new file mode 100644 index 00000000..f92edb90 --- /dev/null +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Netcode; + +namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers +{ + /// A watcher which detects changes to a Netcode collection. + internal class NetCollectionWatcher : BaseDisposableWatcher, ICollectionWatcher + where TValue : INetObject + { + /********* + ** Properties + *********/ + /// The field being watched. + private readonly NetCollection Field; + + /// The pairs added since the last reset. + private readonly List AddedImpl = new List(); + + /// The pairs demoved since the last reset. + private readonly List RemovedImpl = new List(); + + + /********* + ** Accessors + *********/ + /// Whether the collection changed since the last reset. + public bool IsChanged => this.AddedImpl.Count > 0 || this.RemovedImpl.Count > 0; + + /// The values added since the last reset. + public IEnumerable Added => this.AddedImpl; + + /// The values removed since the last reset. + public IEnumerable Removed => this.RemovedImpl; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The field to watch. + public NetCollectionWatcher(NetCollection field) + { + this.Field = field; + field.OnValueAdded += this.OnValueAdded; + field.OnValueRemoved += this.OnValueRemoved; + } + + /// Update the current value if needed. + public void Update() + { + this.AssertNotDisposed(); + } + + /// Set the current value as the baseline. + public void Reset() + { + this.AssertNotDisposed(); + + this.AddedImpl.Clear(); + this.RemovedImpl.Clear(); + } + + /// Stop watching the field and release all references. + public override void Dispose() + { + if (!this.IsDisposed) + { + this.Field.OnValueAdded -= this.OnValueAdded; + this.Field.OnValueRemoved -= this.OnValueRemoved; + } + + base.Dispose(); + } + + + /********* + ** Private methods + *********/ + /// A callback invoked when an entry is added to the collection. + /// The added value. + private void OnValueAdded(TValue value) + { + this.AddedImpl.Add(value); + } + + /// A callback invoked when an entry is removed from the collection. + /// The added value. + private void OnValueRemoved(TValue value) + { + this.RemovedImpl.Add(value); + } + } +} diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index bf261bb5..a4982faa 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -36,6 +36,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers return new ObservableCollectionWatcher(collection); } + /// Get a watcher for a net collection. + /// The value type. + /// The net collection. + public static NetCollectionWatcher ForNetCollection(NetCollection collection) where T : INetObject + { + return new NetCollectionWatcher(collection); + } + /// Get a watcher for a net dictionary. /// The dictionary key type. /// The dictionary value type. -- cgit From 9d3e37317893126a9e0598f8f850abd23cad36c7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 May 2018 16:14:41 -0400 Subject: update for Stardew Valley 1.3.11 (#521) --- .../Framework/StateTracking/FieldWatchers/WatcherFactory.cs | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/SMAPI/Framework/StateTracking/FieldWatchers') diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index a4982faa..4f1ac9f4 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -36,6 +36,15 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers return new ObservableCollectionWatcher(collection); } + /// Get a watcher for a net collection. + /// The value type. + /// The net field instance type. + /// The net collection. + public static NetValueWatcher ForNetValue(NetFieldBase field) where TSelf : NetFieldBase + { + return new NetValueWatcher(field); + } + /// Get a watcher for a net collection. /// The value type. /// The net collection. -- cgit From 235d67623de648499db521606e4b9033d35388e5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 10 Jun 2018 12:06:29 -0400 Subject: create watcher core (#310) --- src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/SMAPI/Framework/StateTracking/FieldWatchers') diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index 4f1ac9f4..d7a02668 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -12,6 +12,14 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /********* ** Public methods *********/ + /// Get a watcher which compares values using their method. This method should only be used when won't work, since this doesn't validate whether they're comparable. + /// The value type. + /// Get the current value. + public static ComparableWatcher ForGenericEquality(Func getValue) where T : struct + { + return new ComparableWatcher(getValue, new GenericEqualsComparer()); + } + /// Get a watcher for an value. /// The value type. /// Get the current value. -- cgit