From 5f9eba90b08c4712d5d34b28d732aed6575509f2 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 25 Jan 2025 16:00:36 +0100 Subject: [PATCH 01/22] Add INotifyValueChanged interface and events args for wrapper objects --- MonkeyLoader/Meta/NotifyValueChangedEvent.cs | 165 +++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 MonkeyLoader/Meta/NotifyValueChangedEvent.cs diff --git a/MonkeyLoader/Meta/NotifyValueChangedEvent.cs b/MonkeyLoader/Meta/NotifyValueChangedEvent.cs new file mode 100644 index 0000000..b6a7a34 --- /dev/null +++ b/MonkeyLoader/Meta/NotifyValueChangedEvent.cs @@ -0,0 +1,165 @@ +using MonkeyLoader.Configuration; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace MonkeyLoader.Meta +{ + /// + /// The delegate that is called for an 's + /// changed event. + /// + /// The object that sent the event. + /// The event containing details about the change. + public delegate void ValueChangedEventHandler(object sender, IValueChangedEventArgs valueChangedEventArgs); + + /// + /// The delegate that is called for an 's + /// changed event. + /// + /// The type of the key's value. + /// The object that sent the event. + /// The event containing details about the change. + public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs valueChangedEventArgs); + + /// + /// Defines the generic interface for objects that wrap another value + /// and notify others about changes to it. + /// + /// The type of the wrapped value. + public interface INotifyValueChanged : INotifyValueChanged + { + /// + /// Triggered when the internal value wrapped by this object changes. + /// + public new event ConfigKeyChangedEventHandler? Changed; + } + + /// + /// Defines the non-generic interface for objects that wrap another value + /// and notify others about changes to it. + /// + public interface INotifyValueChanged + { + /// + /// Triggered when the internal value wrapped by this object changes. + /// + public event ValueChangedEventHandler? Changed; + } + + /// + /// Represents a non-generic . + /// + public interface IValueChangedEventArgs + { + /// + /// Gets the changed collection event arguments, + /// if this configuration item's changed value originated + /// from an event. + /// + public NotifyCollectionChangedEventArgs? ChangedCollection { get; } + + /// + /// Gets the name of the property that changed, + /// if this configuration item's changed value originated + /// from an event. + /// + public string? ChangedProperty { get; } + + /// + /// Gets whether this configuration item's changed value originated + /// from an event. + /// + /// + /// true if ChangedCollection is not null; otherwise, false. + /// + [MemberNotNullWhen(true, nameof(ChangedCollection))] + public bool IsChangedCollection { get; } + + /// + /// Gets whether this configuration item's changed value originated + /// from an event. + /// + /// + /// true if ChangedProperty is not null; otherwise, false. + /// + [MemberNotNullWhen(true, nameof(ChangedProperty))] + public bool IsChangedProperty { get; } + + /// + /// Gets the new value of the configuration item.
+ /// This can be the default value. + ///
+ public object? NewValue { get; } + + /// + /// Gets the old value of the configuration item.
+ /// This can be the default value. + ///
+ public object? OldValue { get; } + } + + /// + /// Represents the data for the + /// and events. + /// + /// The type of the wrapped value. + public class ValueChangedEventArgs : IValueChangedEventArgs + { + /// + public NotifyCollectionChangedEventArgs? ChangedCollection { get; } + + /// + public string? ChangedProperty { get; } + + /// + public bool HadValue { get; } + + /// + public bool HasValue { get; } + + /// + [MemberNotNullWhen(true, nameof(ChangedCollection))] + public bool IsChangedCollection => ChangedCollection is not null; + + /// + [MemberNotNullWhen(true, nameof(ChangedProperty))] + public bool IsChangedProperty => ChangedProperty is not null; + + /// + /// Gets the new value of the .
+ /// This can be the default value. + ///
+ public T? NewValue { get; } + + object? IValueChangedEventArgs.NewValue => NewValue; + + /// + /// Gets the old value of the .
+ /// This can be the default value. + ///
+ public T? OldValue { get; } + + object? IValueChangedEventArgs.OldValue => OldValue; + + /// + /// Creates a new event args instance for a changed config item. + /// + /// The optional old value. + /// The optional new value. + /// The name of the changed property on the value. + /// The collection change arguments for the value. + public ValueChangedEventArgs(T? oldValue, T? newValue, + string? changedProperty, NotifyCollectionChangedEventArgs? changedCollection) + { + OldValue = oldValue; + NewValue = newValue; + + ChangedProperty = changedProperty; + ChangedCollection = changedCollection; + } + } +} \ No newline at end of file From 5a53d068d04cc3ad744a195ee47d7e43063faea3 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 25 Jan 2025 16:00:48 +0100 Subject: [PATCH 02/22] Add initial objects for MonkeySync system --- .../Sync/MonkeySyncMethodAttribute.cs | 12 +++ MonkeyLoader/Sync/MonkeySyncObject.cs | 85 +++++++++++++++++++ MonkeyLoader/Sync/MonkeySyncValue.cs | 82 ++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs create mode 100644 MonkeyLoader/Sync/MonkeySyncObject.cs create mode 100644 MonkeyLoader/Sync/MonkeySyncValue.cs diff --git a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs new file mode 100644 index 0000000..e653956 --- /dev/null +++ b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace MonkeyLoader.Sync +{ + /// + /// Marks a 's instance method as + /// being triggerable through the Sync system. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class MonkeySyncMethodAttribute : MonkeyLoaderAttribute + { } +} \ No newline at end of file diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs new file mode 100644 index 0000000..8e10af3 --- /dev/null +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace MonkeyLoader.Sync +{ + /// + /// Defines the non-generic interface for MonkeySync objects. + /// + public interface IMonkeySyncObject : INotifyPropertyChanged + { + /// + /// Gets whether this sync object has a link object. + /// + [MemberNotNullWhen(true, nameof(LinkObject))] + public bool HasLinkObject { get; } + + /// + /// Gets whether this sync object has a valid link. + /// + public bool IsLinkValid { get; } + + /// + /// Gets the link object used by this sync object. + /// + [MaybeNull] + public object LinkObject { get; } + } + + /// + /// Defines the generic interface for MonkeySync objects. + /// + public interface IMonkeySyncObject : IMonkeySyncObject + where TLink : class + { + /// + [MaybeNull] + public new TLink LinkObject { get; } + } + + public abstract class MonkeySyncObject : IMonkeySyncObject + where TLink : class + { + /// + [MemberNotNullWhen(true, nameof(LinkObject))] + public bool HasLinkObject => LinkObject is not null; + + /// + public abstract bool IsLinkValid { get; } + + /// + public TLink LinkObject { get; } + + object IMonkeySyncObject.LinkObject => LinkObject; + + /// + /// Creates a new instance of this sync object with the given link object. + /// + /// The link object used by this sync object. + protected MonkeySyncObject(TLink linkObject) + { + LinkObject = linkObject; + } + + /// + /// Triggers the PropertyChanged + /// event with the given . + /// + /// + /// This is automatically called for any properties. + /// + /// + protected void OnPropertyChanged(string propertyName) + { + var eventData = new PropertyChangedEventArgs(propertyName); + + PropertyChanged?.Invoke(this, eventData); + } + + /// + public event PropertyChangedEventHandler? PropertyChanged; + } +} \ No newline at end of file diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs new file mode 100644 index 0000000..a5bca10 --- /dev/null +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -0,0 +1,82 @@ +using MonkeyLoader.Meta; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonkeyLoader.Sync +{ + /// + /// Defines the non-generic interface for s. + /// + public interface IMonkeySyncValue : INotifyValueChanged + { + /// + /// Gets or sets the internal value of this sync value. + /// + public object? Value { get; set; } + } + + /// + /// Defines the generic interface for s. + /// + /// The type of the Value. + public interface IMonkeySyncValue : IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue + { + /// + public new T Value { get; set; } + } + + /// + /// Defines the interface for readonly s. + /// + /// + /// This interface exists purely to facilitate keeping a covariant list of sync values. + /// + /// The type of the Value. + public interface IReadOnlyMonkeySyncValue : IMonkeySyncValue + { + /// + /// Gets the internal value of this sync value. + /// + public new T Value { get; } + } + + /// + /// Defines the interface for writeonly s. + /// + /// + /// This interface exists purely to facilitate keeping a contravariant list of sync values. + /// + /// The type of the Value. + public interface IWriteOnlyMonkeySyncValue : IMonkeySyncValue + { + /// + /// Sets the internal value of this sync value. + /// + public new T Value { set; } + } + + public class MonkeySyncValue : IMonkeySyncValue + { + private T _value; + + /// + public T Value + { + get => _value; + set + { + if (ReferenceEquals(_value, value)) + return; + + _value = value; + } + } + + object? IMonkeySyncValue.Value + { + get => Value; + set => Value = (T)value!; + } + } +} \ No newline at end of file From 98e056f6867715afa310ccd8f860214e58438486 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 25 Jan 2025 18:50:14 +0100 Subject: [PATCH 03/22] Make use of NotifyValueChanged as base for ConfigKeyChanged --- .../Configuration/ConfigKeyChangedEvent.cs | 88 +------------------ .../Configuration/DefiningConfigKey.cs | 77 ++++++++++++---- MonkeyLoader/Meta/NotifyValueChangedEvent.cs | 8 +- 3 files changed, 64 insertions(+), 109 deletions(-) diff --git a/MonkeyLoader/Configuration/ConfigKeyChangedEvent.cs b/MonkeyLoader/Configuration/ConfigKeyChangedEvent.cs index 612b379..00d5871 100644 --- a/MonkeyLoader/Configuration/ConfigKeyChangedEvent.cs +++ b/MonkeyLoader/Configuration/ConfigKeyChangedEvent.cs @@ -1,7 +1,7 @@ using System.Collections.Specialized; -using System.ComponentModel; using System; using System.Diagnostics.CodeAnalysis; +using MonkeyLoader.Meta; namespace MonkeyLoader.Configuration { @@ -24,14 +24,8 @@ namespace MonkeyLoader.Configuration /// Represents the data for the and events. /// /// The type of the key's value. - public sealed class ConfigKeyChangedEventArgs : IConfigKeyChangedEventArgs + public sealed class ConfigKeyChangedEventArgs : ValueChangedEventArgs, IConfigKeyChangedEventArgs { - /// - public NotifyCollectionChangedEventArgs? ChangedCollection { get; } - - /// - public string? ChangedProperty { get; } - /// public Config Config { get; } @@ -45,14 +39,6 @@ public sealed class ConfigKeyChangedEventArgs : IConfigKeyChangedEventArgs /// public bool HasValue { get; } - /// - [MemberNotNullWhen(true, nameof(ChangedCollection))] - public bool IsChangedCollection => ChangedCollection is not null; - - /// - [MemberNotNullWhen(true, nameof(ChangedProperty))] - public bool IsChangedProperty => ChangedProperty is not null; - /// /// Gets the configuration item who's value changed. /// @@ -63,22 +49,6 @@ public sealed class ConfigKeyChangedEventArgs : IConfigKeyChangedEventArgs /// public string? Label { get; } - /// - /// Gets the new value of the .
- /// This can be the default value. - ///
- public T? NewValue { get; } - - object? IConfigKeyChangedEventArgs.NewValue => NewValue; - - /// - /// Gets the old value of the .
- /// This can be the default value. - ///
- public T? OldValue { get; } - - object? IConfigKeyChangedEventArgs.OldValue => OldValue; - /// /// Creates a new event args instance for a changed config item. /// @@ -94,41 +64,23 @@ public sealed class ConfigKeyChangedEventArgs : IConfigKeyChangedEventArgs public ConfigKeyChangedEventArgs(Config config, IDefiningConfigKey key, bool hadValue, T? oldValue, bool hasValue, T? newValue, string? label, string? changedProperty, NotifyCollectionChangedEventArgs? changedCollection) + : base(oldValue, newValue, changedProperty, changedCollection) { Config = config; Key = key; - OldValue = oldValue; HadValue = hadValue; - NewValue = newValue; HasValue = hasValue; Label = label; - - ChangedProperty = changedProperty; - ChangedCollection = changedCollection; } } /// /// Represents a non-generic . /// - public interface IConfigKeyChangedEventArgs + public interface IConfigKeyChangedEventArgs : IValueChangedEventArgs { - /// - /// Gets the changed collection event arguments, - /// if this configuration item's changed value originated - /// from an event. - /// - public NotifyCollectionChangedEventArgs? ChangedCollection { get; } - - /// - /// Gets the name of the property that changed, - /// if this configuration item's changed value originated - /// from an event. - /// - public string? ChangedProperty { get; } - /// /// Gets the in which the change occured. /// @@ -153,26 +105,6 @@ public interface IConfigKeyChangedEventArgs /// public bool HasValue { get; } - /// - /// Gets whether this configuration item's changed value originated - /// from an event. - /// - /// - /// true if ChangedCollection is not null; otherwise, false. - /// - [MemberNotNullWhen(true, nameof(ChangedCollection))] - public bool IsChangedCollection { get; } - - /// - /// Gets whether this configuration item's changed value originated - /// from an event. - /// - /// - /// true if ChangedProperty is not null; otherwise, false. - /// - [MemberNotNullWhen(true, nameof(ChangedProperty))] - public bool IsChangedProperty { get; } - /// /// Gets the configuration item who's value changed. /// @@ -182,17 +114,5 @@ public interface IConfigKeyChangedEventArgs /// Gets a custom label that may be set by whoever changed the configuration. /// public string? Label { get; } - - /// - /// Gets the new value of the configuration item.
- /// This can be the default value. - ///
- public object? NewValue { get; } - - /// - /// Gets the old value of the configuration item.
- /// This can be the default value. - ///
- public object? OldValue { get; } } } \ No newline at end of file diff --git a/MonkeyLoader/Configuration/DefiningConfigKey.cs b/MonkeyLoader/Configuration/DefiningConfigKey.cs index 00d05bf..06ec25e 100644 --- a/MonkeyLoader/Configuration/DefiningConfigKey.cs +++ b/MonkeyLoader/Configuration/DefiningConfigKey.cs @@ -35,8 +35,11 @@ public sealed class DefiningConfigKey : Entity>, IDefini private ConfigSection? _configSection; private bool _hasChanges; private ConfigKeyChangedEventHandler? _untypedChanged; + private ValueChangedEventHandler? _untypedValueChanged; private T? _value; + private ValueChangedEventHandler? _valueChanged; + /// public IConfigKey AsUntyped { get; } @@ -367,27 +370,34 @@ public bool Validate(T value) /// The collection change arguments for the value. private void OnChanged(bool hadValue, T? oldValue, string? eventLabel, string? changedProperty = null, NotifyCollectionChangedEventArgs? changedCollection = null) { - // Add notify changed integration - if (hadValue) + var sameReferences = ReferenceEquals(oldValue, _value); + + if (!sameReferences) { - if (oldValue is INotifyPropertyChanged oldPropertyChanged) - oldPropertyChanged.PropertyChanged -= ValuePropertyChanged; + // Remove NotifyChanged integration from old value + if (hadValue) + { + if (oldValue is INotifyPropertyChanged oldPropertyChanged) + oldPropertyChanged.PropertyChanged -= ValuePropertyChanged; - if (oldValue is INotifyCollectionChanged oldCollectionChanged) - oldCollectionChanged.CollectionChanged -= ValueCollectionChanged; - } + if (oldValue is INotifyCollectionChanged oldCollectionChanged) + oldCollectionChanged.CollectionChanged -= ValueCollectionChanged; + } - if (HasValue) - { - if (_value is INotifyPropertyChanged newPropertyChanged) - newPropertyChanged.PropertyChanged += ValuePropertyChanged; + // Add NotifyChanged integration to new value + if (HasValue) + { + if (_value is INotifyPropertyChanged newPropertyChanged) + newPropertyChanged.PropertyChanged += ValuePropertyChanged; - if (_value is INotifyCollectionChanged newCollectionChanged) - newCollectionChanged.CollectionChanged += ValueCollectionChanged; + if (_value is INotifyCollectionChanged newCollectionChanged) + newCollectionChanged.CollectionChanged += ValueCollectionChanged; + } } - // Don't fire event if value didn't change - if (ReferenceEquals(oldValue, _value) || (oldValue is not null && _value is not null && _value.Equals(oldValue))) + // Don't fire event if it wasn't triggered by event and the value didn't change + if ((sameReferences && changedProperty is null && changedCollection is null) + || (oldValue is not null && _value is not null && _value.Equals(oldValue))) return; HasChanges = true; @@ -411,6 +421,24 @@ private void OnChanged(bool hadValue, T? oldValue, string? eventLabel, string? c Logger.Error(ex.LogFormat($"Some untyped {nameof(Changed)} event subscriber(s) of key [{Id}] threw an exception:")); } + try + { + _valueChanged?.TryInvokeAll(this, eventArgs); + } + catch (AggregateException ex) + { + Logger.Error(ex.LogFormat($"Some typed {nameof(INotifyValueChanged)}.{nameof(Changed)} event subscriber(s) of key [{Id}] threw an exception:")); + } + + try + { + _untypedValueChanged?.TryInvokeAll(this, eventArgs); + } + catch (AggregateException ex) + { + Logger.Error(ex.LogFormat($"Some untyped {nameof(INotifyValueChanged)}.{nameof(Changed)} event subscriber(s) of key [{Id}] threw an exception:")); + } + Section.OnItemChanged(eventArgs); } @@ -431,13 +459,25 @@ event ConfigKeyChangedEventHandler? IDefiningConfigKey.Changed add => _untypedChanged += value; remove => _untypedChanged -= value; } + + event ValueChangedEventHandler? INotifyValueChanged.Changed + { + add => _untypedValueChanged += value; + remove => _untypedValueChanged -= value; + } + + event ValueChangedEventHandler? INotifyValueChanged.Changed + { + add => _valueChanged += value; + remove => _valueChanged -= value; + } } /// /// Defines the definition for a config item. /// public interface IDefiningConfigKey : ITypedConfigKey, IEntity, - INestedIdentifiable, IPrioritizable + INestedIdentifiable, IPrioritizable, INotifyValueChanged { /// /// Gets the config this item belongs to. @@ -541,14 +581,15 @@ public interface IDefiningConfigKey : ITypedConfigKey, IEntity /// Triggered when the internal value of this config item changes. /// - public event ConfigKeyChangedEventHandler? Changed; + public new event ConfigKeyChangedEventHandler? Changed; } /// /// Defines the typed definition for a config item. /// /// The type of the config item's value. - public interface IDefiningConfigKey : IDefiningConfigKey, ITypedConfigKey, IEntity> + public interface IDefiningConfigKey : IDefiningConfigKey, ITypedConfigKey, + IEntity>, INotifyValueChanged { /// /// Gets this config item's set value, falling back to the computed default. diff --git a/MonkeyLoader/Meta/NotifyValueChangedEvent.cs b/MonkeyLoader/Meta/NotifyValueChangedEvent.cs index b6a7a34..d641543 100644 --- a/MonkeyLoader/Meta/NotifyValueChangedEvent.cs +++ b/MonkeyLoader/Meta/NotifyValueChangedEvent.cs @@ -35,7 +35,7 @@ public interface INotifyValueChanged : INotifyValueChanged /// /// Triggered when the internal value wrapped by this object changes. /// - public new event ConfigKeyChangedEventHandler? Changed; + public new event ValueChangedEventHandler? Changed; } /// @@ -115,12 +115,6 @@ public class ValueChangedEventArgs : IValueChangedEventArgs /// public string? ChangedProperty { get; } - /// - public bool HadValue { get; } - - /// - public bool HasValue { get; } - /// [MemberNotNullWhen(true, nameof(ChangedCollection))] public bool IsChangedCollection => ChangedCollection is not null; From a9c2db1e02ebae4ab386e2111824a9a4fab3bba0 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 25 Jan 2025 18:51:24 +0100 Subject: [PATCH 04/22] Finish implementation of MonkeySyncValue --- MonkeyLoader/MonkeyLoader.csproj | 4 + MonkeyLoader/Sync/MonkeySyncValue.cs | 118 +++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 7c6495b..5ea7cec 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -77,6 +77,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index a5bca10..5c9e9d9 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -1,6 +1,10 @@ -using MonkeyLoader.Meta; +using EnumerableToolkit; +using MonkeyLoader.Meta; using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Text; namespace MonkeyLoader.Sync @@ -20,7 +24,8 @@ public interface IMonkeySyncValue : INotifyValueChanged /// Defines the generic interface for s. /// /// The type of the Value. - public interface IMonkeySyncValue : IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue + public interface IMonkeySyncValue : INotifyValueChanged, + IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue { /// public new T Value { get; set; } @@ -56,20 +61,27 @@ public interface IWriteOnlyMonkeySyncValue : IMonkeySyncValue public new T Value { set; } } + /// + /// Implements a basic version of an . + /// + /// The type of the Value. public class MonkeySyncValue : IMonkeySyncValue { + private ValueChangedEventHandler? _untypedChanged; private T _value; /// public T Value { get => _value; + + [MemberNotNull(nameof(_value))] set { - if (ReferenceEquals(_value, value)) - return; - + var oldValue = _value; _value = value; + + OnChanged(oldValue); } } @@ -78,5 +90,101 @@ public T Value get => Value; set => Value = (T)value!; } + + /// + /// Creates a new sync object instance that wraps the given . + /// + /// The value to wrap. + public MonkeySyncValue(T value) + { + Value = value; + } + + /// + /// Wraps the given into a sync object. + /// + /// The value to wrap. + public static implicit operator MonkeySyncValue(T value) => new(value); + + /// + /// Unwraps the Value from the given sync object. + /// + /// The sync object to unwrap. + public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + + /// + /// Handles the value of this config item potentially having changed. + /// If the and new value are different:
+ /// Triggers this sync object's typed and untyped Changed events. + ///
+ /// The old value. + /// The name of the changed property on the value. + /// The collection change arguments for the value. + private void OnChanged(T? oldValue, string? changedProperty = null, NotifyCollectionChangedEventArgs? changedCollection = null) + { + var sameReferences = ReferenceEquals(oldValue, _value); + + if (!sameReferences) + { + // Remove NotifyChanged integration from old value + if (oldValue is INotifyPropertyChanged oldPropertyChanged) + oldPropertyChanged.PropertyChanged -= ValuePropertyChanged; + + if (oldValue is INotifyCollectionChanged oldCollectionChanged) + oldCollectionChanged.CollectionChanged -= ValueCollectionChanged; + + // Add NotifyChanged integration to new value + if (_value is INotifyPropertyChanged newPropertyChanged) + newPropertyChanged.PropertyChanged += ValuePropertyChanged; + + if (_value is INotifyCollectionChanged newCollectionChanged) + newCollectionChanged.CollectionChanged += ValueCollectionChanged; + } + + // Don't fire event if it wasn't triggered by event and the value didn't change + if ((sameReferences && changedProperty is null && changedCollection is null) + || (oldValue is not null && _value is not null && _value.Equals(oldValue))) + return; + + var eventArgs = new ValueChangedEventArgs(oldValue, _value, changedProperty, changedCollection); + + var exceptions = new List(); + + try + { + Changed?.TryInvokeAll(this, eventArgs); + } + catch (AggregateException ex) + { + exceptions.Add(ex); + } + + try + { + _untypedChanged?.TryInvokeAll(this, eventArgs); + } + catch (AggregateException ex) + { + exceptions.Add(ex); + } + + if (exceptions.Count > 0) + throw new AggregateException($"Some {nameof(Changed)} event subscriber(s) of this MonkeySyncValue threw an exception.", exceptions); + } + + private void ValueCollectionChanged(object sender, NotifyCollectionChangedEventArgs eventArgs) + => OnChanged(_value, null, eventArgs); + + private void ValuePropertyChanged(object sender, PropertyChangedEventArgs eventArgs) + => OnChanged(_value, eventArgs.PropertyName); + + /// + public event ValueChangedEventHandler? Changed; + + event ValueChangedEventHandler? INotifyValueChanged.Changed + { + add => _untypedChanged += value; + remove => _untypedChanged -= value; + } } } \ No newline at end of file From 03775f2acc1e2cd2548d41f9d0b8e4adf0e85e8d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 25 Jan 2025 20:30:14 +0100 Subject: [PATCH 05/22] Finish pre-release version of MonkeySync feature --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 111 ++++++++++++++++++++++++-- MonkeyLoader/Sync/MonkeySyncValue.cs | 2 +- 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 5ea7cec..ad19951 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.25.0-beta + 0.24.2-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 8e10af3..a76489b 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -1,13 +1,16 @@ -using System; +using HarmonyLib; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text; namespace MonkeyLoader.Sync { /// - /// Defines the non-generic interface for MonkeySync objects. + /// Defines the non-generic interface for MonkeySync objects. /// public interface IMonkeySyncObject : INotifyPropertyChanged { @@ -30,8 +33,10 @@ public interface IMonkeySyncObject : INotifyPropertyChanged } /// - /// Defines the generic interface for MonkeySync objects. + /// Defines the generic interface for MonkeySync objects. /// + /// The type of the link object used by the sync object. public interface IMonkeySyncObject : IMonkeySyncObject where TLink : class { @@ -40,9 +45,30 @@ public interface IMonkeySyncObject : IMonkeySyncObject public new TLink LinkObject { get; } } - public abstract class MonkeySyncObject : IMonkeySyncObject + /// + /// Implements the abstract base for MonkeySync objects. + /// + /// The concrete type of the MonkeySync object. + /// + /// The -derived interface + /// that the MonkeySync values of this object must implement. + /// + /// The type of the link object used by the sync object. + public abstract class MonkeySyncObject : IMonkeySyncObject + where TSyncObject : MonkeySyncObject + where TSyncValue : IMonkeySyncValue where TLink : class { + /// + /// The detected MonkeySync nethods by their name. + /// + protected static readonly Dictionary> methodsByName = new(StringComparer.Ordinal); + + /// + /// The getters for the detected instance properties by their name. + /// + protected static readonly Dictionary> propertyAccessorsByName = new(StringComparer.Ordinal); + /// [MemberNotNullWhen(true, nameof(LinkObject))] public bool HasLinkObject => LinkObject is not null; @@ -51,17 +77,86 @@ public abstract class MonkeySyncObject : IMonkeySyncObject public abstract bool IsLinkValid { get; } /// - public TLink LinkObject { get; } + [MaybeNull] + public TLink LinkObject { get; private set; } + [MaybeNull] object IMonkeySyncObject.LinkObject => LinkObject; + static MonkeySyncObject() + { + var syncValueType = typeof(TSyncValue); + var syncValueProperties = AccessTools.GetDeclaredProperties(typeof(TSyncObject)) + .Where(property => syncValueType.IsAssignableFrom(property.PropertyType) && (!(property.GetGetMethod()?.IsStatic ?? true))); + + foreach (var property in syncValueProperties) + propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); + + var syncMethods = AccessTools.GetDeclaredMethods(typeof(TSyncObject)) + .Where(method => !method.IsStatic && !method.ContainsGenericParameters && method.ReturnType == typeof(void) && method.GetParameters().Length == 0); + + foreach (var method in syncMethods) + methodsByName.Add(method.Name, (TSyncObject instance) => method.Invoke(instance, null)); + } + /// - /// Creates a new instance of this sync object with the given link object. + /// Establishes this sync object's link with the given object. /// - /// The link object used by this sync object. - protected MonkeySyncObject(TLink linkObject) + /// + /// If the link fails or gets broken, a new instance has to be created. + /// + /// The link object to be used by this sync object. + /// true if the established link is valid; otherwise, false. + public bool LinkWith(TLink linkObject) { + if (HasLinkObject) + throw new InvalidOperationException("Can only assign a link object once!"); + LinkObject = linkObject; + + return EstablishLinkWith(linkObject); + } + + /// + /// Creates a link for the given sync value of the given name. + /// + /// The name of the sync value to link. + /// The sync value to link. + /// true if the link was successfully created; otherwise, false. + protected abstract bool EstablishLinkFor(string propertyName, TSyncValue syncValue); + + /// + /// Creates a link for the given sync method of the given name. + /// + /// The name of the sync method to link. + /// The sync method to link. + /// true if the link was successfully created; otherwise, false. + protected abstract bool EstablishLinkFor(string methodName, Action syncMethod); + + /// + /// By default: Calls EstablishLinkFor + /// for every readable instance property and + /// its overload for every + /// MonkeySync method on .
+ /// The detected properties are stored in propertyAccessorsByName, + /// while the detected methods are stored in methodsByName. + ///
+ /// This method is called by LinkWith + /// after the LinkObject has been assigned. + /// + ///
+ /// + protected virtual bool EstablishLinkWith(TLink linkObject) + { + var success = true; + + foreach (var syncValueProperty in propertyAccessorsByName) + success &= EstablishLinkFor(syncValueProperty.Key, syncValueProperty.Value((TSyncObject)this)); + + foreach (var syncMethod in methodsByName) + success &= EstablishLinkFor(syncMethod.Key, syncMethod.Value); + + return success; } /// diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index 5c9e9d9..d6c17cf 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -79,7 +79,7 @@ public T Value set { var oldValue = _value; - _value = value; + _value = value!; OnChanged(oldValue); } From d4f9ee0e238987810f54a201e92bc9db6b990062 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 26 Jan 2025 22:41:22 +0100 Subject: [PATCH 06/22] Tweak MonkeySync base --- MonkeyLoader/Configuration/DefiningConfigKey.cs | 6 +++--- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 6 +----- MonkeyLoader/Sync/MonkeySyncValue.cs | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/MonkeyLoader/Configuration/DefiningConfigKey.cs b/MonkeyLoader/Configuration/DefiningConfigKey.cs index 06ec25e..128e7e7 100644 --- a/MonkeyLoader/Configuration/DefiningConfigKey.cs +++ b/MonkeyLoader/Configuration/DefiningConfigKey.cs @@ -29,15 +29,15 @@ namespace MonkeyLoader.Configuration /// The type of the config item's value. public sealed class DefiningConfigKey : Entity>, IDefiningConfigKey { - private readonly bool _canAlwaysHaveChanges; + private static readonly Type _valueType = typeof(T); + private readonly bool _canAlwaysHaveChanges; private readonly Lazy _fullId; private ConfigSection? _configSection; private bool _hasChanges; private ConfigKeyChangedEventHandler? _untypedChanged; private ValueChangedEventHandler? _untypedValueChanged; private T? _value; - private ValueChangedEventHandler? _valueChanged; /// @@ -120,7 +120,7 @@ public ConfigSection Section IDefiningConfigKey IEntity>.Self => this; /// - public Type ValueType { get; } = typeof(T); + public Type ValueType => _valueType; /// /// Gets the logger of the config this item belongs to. diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index ad19951..22ad9b0 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.2-MonkeySync-beta + 0.24.3-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index a76489b..9f71911 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -28,7 +28,6 @@ public interface IMonkeySyncObject : INotifyPropertyChanged /// /// Gets the link object used by this sync object. /// - [MaybeNull] public object LinkObject { get; } } @@ -41,7 +40,6 @@ public interface IMonkeySyncObject : IMonkeySyncObject where TLink : class { /// - [MaybeNull] public new TLink LinkObject { get; } } @@ -77,10 +75,8 @@ public abstract class MonkeySyncObject : IMonkey public abstract bool IsLinkValid { get; } /// - [MaybeNull] - public TLink LinkObject { get; private set; } + public TLink LinkObject { get; private set; } = null!; - [MaybeNull] object IMonkeySyncObject.LinkObject => LinkObject; static MonkeySyncObject() diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index d6c17cf..dcb2aac 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -18,6 +18,12 @@ public interface IMonkeySyncValue : INotifyValueChanged /// Gets or sets the internal value of this sync value. /// public object? Value { get; set; } + + /// + /// Gets the concrete of + /// the wrapped Value. + /// + public Type ValueType { get; } } /// @@ -67,6 +73,8 @@ public interface IWriteOnlyMonkeySyncValue : IMonkeySyncValue /// The type of the Value. public class MonkeySyncValue : IMonkeySyncValue { + private static readonly Type _valueType = typeof(T); + private ValueChangedEventHandler? _untypedChanged; private T _value; @@ -91,6 +99,9 @@ public T Value set => Value = (T)value!; } + /// + public Type ValueType => _valueType; + /// /// Creates a new sync object instance that wraps the given . /// @@ -112,6 +123,9 @@ public MonkeySyncValue(T value) /// The sync object to unwrap. public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + /// + public override string ToString() => Value?.ToString() ?? ""; + /// /// Handles the value of this config item potentially having changed. /// If the and new value are different:
From f8e82127dc67c20d7030e0a1c5a582a6afb91fed Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Tue, 28 Jan 2025 14:32:40 +0100 Subject: [PATCH 07/22] Add more features to MonkeySyncObject --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 204 ++++++++++++++++++++++---- 2 files changed, 180 insertions(+), 26 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 22ad9b0..2d405c9 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.3-MonkeySync-beta + 0.24.4-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 9f71911..be3eda7 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -1,4 +1,6 @@ -using HarmonyLib; +using EnumerableToolkit; +using HarmonyLib; +using MonkeyLoader.Meta; using System; using System.Collections.Generic; using System.ComponentModel; @@ -8,11 +10,22 @@ namespace MonkeyLoader.Sync { + /// + /// Defines the generic interface for MonkeySync objects that have been linked. + /// + /// The type of the link object used by the sync object. + public interface ILinkedMonkeySyncObject : IMonkeySyncObject + { + /// + public new TLink LinkObject { get; } + } + /// /// Defines the non-generic interface for MonkeySync objects. /// - public interface IMonkeySyncObject : INotifyPropertyChanged + public interface IMonkeySyncObject : INotifyPropertyChanged, IDisposable { /// /// Gets whether this sync object has a link object. @@ -33,14 +46,22 @@ public interface IMonkeySyncObject : INotifyPropertyChanged /// /// Defines the generic interface for MonkeySync objects. + /// TSyncValues, TLink}">MonkeySync objects that are yet to be linked. /// - /// The type of the link object used by the sync object. - public interface IMonkeySyncObject : IMonkeySyncObject + /// + public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject where TLink : class { - /// - public new TLink LinkObject { get; } + /// + /// Establishes this sync object's link with the given object. + /// + /// + /// If the link fails or gets broken, a new instance has to be created. + /// + /// The link object to be used by this sync object. + /// Whether the link is being established from the remote side. + /// true if the established link is valid; otherwise, false. + public bool LinkWith(TLink linkObject, bool fromRemote = false); } /// @@ -52,7 +73,7 @@ public interface IMonkeySyncObject : IMonkeySyncObject /// that the MonkeySync values of this object must implement. /// /// The type of the link object used by the sync object. - public abstract class MonkeySyncObject : IMonkeySyncObject + public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject where TSyncObject : MonkeySyncObject where TSyncValue : IMonkeySyncValue where TLink : class @@ -67,6 +88,8 @@ public abstract class MonkeySyncObject : IMonkey /// protected static readonly Dictionary> propertyAccessorsByName = new(StringComparer.Ordinal); + private bool _disposedValue; + /// [MemberNotNullWhen(true, nameof(LinkObject))] public bool HasLinkObject => LinkObject is not null; @@ -96,21 +119,30 @@ static MonkeySyncObject() } /// - /// Establishes this sync object's link with the given object. + /// Ensures any unmanaged resources are disposed. /// - /// - /// If the link fails or gets broken, a new instance has to be created. - /// - /// The link object to be used by this sync object. - /// true if the established link is valid; otherwise, false. - public bool LinkWith(TLink linkObject) + ~MonkeySyncObject() + { + Dispose(false); + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in the 'OnDisposing()' or 'OnFinalizing()' methods + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public bool LinkWith(TLink linkObject, bool fromRemote = false) { if (HasLinkObject) throw new InvalidOperationException("Can only assign a link object once!"); LinkObject = linkObject; - return EstablishLinkWith(linkObject); + return EstablishLinkWith(linkObject, fromRemote); } /// @@ -118,43 +150,93 @@ public bool LinkWith(TLink linkObject) /// /// The name of the sync value to link. /// The sync value to link. + /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(string propertyName, TSyncValue syncValue); + protected abstract bool EstablishLinkFor(string propertyName, TSyncValue syncValue, bool fromRemote); /// /// Creates a link for the given sync method of the given name. /// /// The name of the sync method to link. /// The sync method to link. + /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(string methodName, Action syncMethod); + protected abstract bool EstablishLinkFor(string methodName, Action syncMethod, bool fromRemote); /// - /// By default: Calls EstablishLinkFor + /// By default: Sets up the event handlers + /// and calls EstablishLinkFor /// for every readable instance property and - /// its overload for every + /// its overload for every /// MonkeySync method on .
/// The detected properties are stored in propertyAccessorsByName, /// while the detected methods are stored in methodsByName. ///
/// This method is called by LinkWith - /// after the LinkObject has been assigned. + /// after the LinkObject has been assigned.
+ /// It should ensure that a link object created from the remote side + /// is handled appropriately and without duplications as well. ///
///
/// - protected virtual bool EstablishLinkWith(TLink linkObject) + protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) { var success = true; foreach (var syncValueProperty in propertyAccessorsByName) - success &= EstablishLinkFor(syncValueProperty.Key, syncValueProperty.Value((TSyncObject)this)); + { + var syncValue = syncValueProperty.Value((TSyncObject)this); + + syncValue.Changed += (sender, changedArgs) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(syncValueProperty.Key)); + + success &= EstablishLinkFor(syncValueProperty.Key, syncValue, fromRemote); + } foreach (var syncMethod in methodsByName) - success &= EstablishLinkFor(syncMethod.Key, syncMethod.Value); + success &= EstablishLinkFor(syncMethod.Key, syncMethod.Value, fromRemote); return success; } + /// + /// Cleans up any managed resources as part of disposing. + /// + /// + /// By default: Disposes the LinkObject if it's . + /// + protected virtual void OnDisposing() + { + if (LinkObject is IDisposable disposable) + disposable.Dispose(); + } + + /// + /// Cleans up any unmanaged resources as part of + /// disposing or finalization. + /// + protected virtual void OnFinalizing() + { } + + /// + /// Tries to restore the link when it becomes invalidated + /// and triggers the Invalidated when that fails.
+ /// Afterwards, the object is automatically disposed. + ///
+ /// + /// Should be called from a derived class when something happens + /// that makes IsLinkValid false. + /// + protected void OnLinkInvalidated() + { + if (!IsLinkValid && TryRestoreLink() && IsLinkValid) + return; + + Invalidated.TryInvokeAll(); + + Dispose(); + } + /// /// Triggers the PropertyChanged /// event with the given . @@ -162,14 +244,86 @@ protected virtual bool EstablishLinkWith(TLink linkObject) /// /// This is automatically called for any properties. /// - /// + /// The name of the property that changed. protected void OnPropertyChanged(string propertyName) { + // Still needs to be hooked up somewhere var eventData = new PropertyChangedEventArgs(propertyName); PropertyChanged?.Invoke(this, eventData); } + /// + /// By default: Calls TryRestoreLinkFor + /// for every readable instance property and + /// its overload for every + /// MonkeySync method on .
+ /// The detected properties are stored in propertyAccessorsByName, + /// while the detected methods are stored in methodsByName. + ///
+ /// This method is called by OnLinkInvalidated + /// if IsLinkValid has become false.
+ /// It should ensure that any still valid links are + /// handled appropriately and without duplications as well. + ///
+ ///
+ /// + protected virtual bool TryRestoreLink() + { + var success = true; + + foreach (var syncValueProperty in propertyAccessorsByName) + success &= TryRestoreLinkFor(syncValueProperty.Key, syncValueProperty.Value((TSyncObject)this)); + + foreach (var syncMethod in methodsByName) + success &= TryRestoreLinkFor(syncMethod.Key, syncMethod.Value); + + return success; + } + + /// + /// Tries to restore the link for the given sync value of the given name. + /// + /// The name of the sync value to link. + /// The sync value to link. + /// true if the link was successfully restored; otherwise, false. + protected abstract bool TryRestoreLinkFor(string propertyName, TSyncValue syncValue); + + /// + /// Tries to restore the link for the given sync method of the given name. + /// + /// The name of the sync method to link. + /// The sync method to link. + /// true if the link was successfully restored; otherwise, false. + protected abstract bool TryRestoreLinkFor(string methodName, Action syncMethod); + + private void Dispose(bool disposing) + { + if (_disposedValue) + return; + + if (disposing) + OnDisposing(); + + OnFinalizing(); + + _disposedValue = true; + } + + /// + /// Occurs when IsLinkValid becomes false + /// and it could not be restored. + /// + public event InvalidatedHandler? Invalidated; + + /// + /// Represents the method that will handle the Invalidated + /// event raised when IsLinkValid becomes false + /// and it could not be restored. + /// + /// + public delegate void InvalidatedHandler(TSyncObject syncObject); + /// public event PropertyChangedEventHandler? PropertyChanged; } From 42a0d2ac7d9190714e8321073d6735e93e7ae193 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Tue, 28 Jan 2025 23:05:33 +0100 Subject: [PATCH 08/22] Add MonkeySyncObjectRegistry --- MonkeyLoader/MonkeyLoader.csproj | 2 +- .../Sync/MonkeySyncMethodAttribute.cs | 4 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 11 +- MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs | 165 ++++++++++++++++++ MonkeyLoader/Sync/RegisteredSyncObject.cs | 51 ++++++ 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs create mode 100644 MonkeyLoader/Sync/RegisteredSyncObject.cs diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 2d405c9..a591c4c 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.4-MonkeySync-beta + 0.24.5-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs index e653956..c1cb079 100644 --- a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs +++ b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs @@ -3,8 +3,8 @@ namespace MonkeyLoader.Sync { /// - /// Marks a 's instance method as - /// being triggerable through the Sync system. + /// Marks a MonkeySync + /// object's instance method as being triggerable through the MonkeySync system. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class MonkeySyncMethodAttribute : MonkeyLoaderAttribute diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index be3eda7..e1e76be 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -10,6 +10,15 @@ namespace MonkeyLoader.Sync { + /// + /// Represents the method that will create new instances + /// of sync objects linking via . + /// + /// The type of the link object used by the sync object. + /// The created but not yet linked sync object. + public delegate IUnlinkedMonkeySyncObject SyncObjectFactory() + where TLink : class; + /// /// Defines the generic interface for MonkeySync objects that have been linked. @@ -321,7 +330,7 @@ private void Dispose(bool disposing) /// event raised when IsLinkValid becomes false /// and it could not be restored. /// - /// + /// The sync object that got invalidated. public delegate void InvalidatedHandler(TSyncObject syncObject); /// diff --git a/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs b/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs new file mode 100644 index 0000000..164cfe7 --- /dev/null +++ b/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace MonkeyLoader.Sync +{ + /// + /// Handles globally registering MonkeySync objects for particular link types, + /// so that the link implementations can create them. + /// + public static class MonkeySyncRegistry + { + /// + /// Gets the for the given . + /// + /// The type of the link object used by the sync object. + /// The name of the registered sync object. + /// The found . + /// When no sync object with the given has been registered. + public static RegisteredSyncObject GetRegisteredSyncObject(string name) + where TLink : class + => Library.GetRegisteredSyncObject(name); + + /// + /// Gets the for the given . + /// + /// The type of registered sync object. + /// + public static RegisteredSyncObject GetRegisteredSyncObject(Type syncObjectType) + where TLink : class + => Library.GetRegisteredSyncObject(syncObjectType); + + /// + /// Registers a MonkeySync object type with the given details for it. + /// + /// The type of the link object used by the sync object. + /// The -unique name for the sync object type. + /// The type of the sync object. + /// A factory method that creates new instances of this sync object type. + /// The data of the newly registered sync object type. + /// + /// When the or one with the same + /// has already been registered. + /// + public static RegisteredSyncObject RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + where TLink : class + => Library.RegisterSyncObject(name, syncObjectType, createSyncObject); + + /// + /// Tries to get the for the given . + /// + /// The type of the link object used by the sync object. + /// The name of the registered sync object. + /// The found ; otherwise, null. + /// true if the registered sync object type was found; otherwise, false. + public static bool TryGetRegisteredSyncObject(string name, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + where TLink : class + => Library.TryGetRegisteredSyncObject(name, out registeredSyncObject); + + /// + /// Tries to get the for the given . + /// + /// + /// The found ; otherwise, null. + /// true if the registered sync object type was found; otherwise, false. + public static bool TryGetRegisteredSyncObject(Type syncObjectType, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + where TLink : class + => Library.TryGetRegisteredSyncObject(syncObjectType, out registeredSyncObject); + + /// + /// Removes the sync object type with the given . + /// + /// The type of the link object used by the sync object. + /// The name of the registered sync object. + /// + /// true if the registered sync object type with the given was removed; otherwise, false. + /// + /// + /// When there's a registered sync object type with the given , + /// but none with the associated - or there's a Name mismatch. + /// + public static bool UnregisterSyncObject(string name) + where TLink : class + => Library.UnregisterSyncObject(name); + + /// + /// Removes the sync object type for the given . + /// + /// The type of the link object used by the sync object. + /// The type of the registered sync object. + /// + /// true if the registered sync object type for the given was removed; otherwise, false. + /// + /// + /// When there's a registered sync object type for the given , + /// but none with the associated Name - or there's a Type mismatch. + /// + public static bool UnregisterSyncObject(Type syncObjectType) + where TLink : class + => Library.UnregisterSyncObject(syncObjectType); + + private static class Library + where TLink : class + { + private static readonly Dictionary> _registeredObjectByType = []; + private static readonly Dictionary> _registeredObjectsByName = new(StringComparer.Ordinal); + + public static RegisteredSyncObject GetRegisteredSyncObject(string name) + => _registeredObjectsByName[name]; + + public static RegisteredSyncObject GetRegisteredSyncObject(Type type) + => _registeredObjectByType[type]; + + public static RegisteredSyncObject RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + { + if (_registeredObjectsByName.ContainsKey(name) || _registeredObjectByType.ContainsKey(syncObjectType)) + throw new ArgumentException($"Sync Object type [{syncObjectType.CompactDescription()}] with name [{name}] has already been registered!"); + + var registeredObject = new RegisteredSyncObject(name, syncObjectType, createSyncObject); + + _registeredObjectsByName.Add(name, registeredObject); + _registeredObjectByType.Add(syncObjectType, registeredObject); + + return registeredObject; + } + + public static bool TryGetRegisteredSyncObject(string name, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + => _registeredObjectsByName.TryGetValue(name, out registeredSyncObject); + + public static bool TryGetRegisteredSyncObject(Type type, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + => _registeredObjectByType.TryGetValue(type, out registeredSyncObject); + + internal static bool UnregisterSyncObject(string name) + { + if (!TryGetRegisteredSyncObject(name, out var registeredObject)) + return false; + + if (!TryGetRegisteredSyncObject(registeredObject.SyncObjectType, out var registeredObject2) || registeredObject.Name != registeredObject2.Name) + throw new InvalidOperationException($"No Sync Object type found using type [{registeredObject.SyncObjectType.CompactDescription()} based on the name [{name}], or name [{registeredObject2?.Name ?? "N/A"}] doesn't match!"); + + _registeredObjectsByName.Remove(name); + _registeredObjectByType.Remove(registeredObject.SyncObjectType); + + return true; + } + + internal static bool UnregisterSyncObject(Type syncObjectType) + { + if (!TryGetRegisteredSyncObject(syncObjectType, out var registeredObject)) + return false; + + if (!TryGetRegisteredSyncObject(registeredObject.Name, out var registeredObject2) || registeredObject.SyncObjectType != registeredObject2.SyncObjectType) + throw new InvalidOperationException($"No Sync Object type found using name [{registeredObject.Name} based on the type [{syncObjectType.CompactDescription()}], or type [{registeredObject2?.SyncObjectType.CompactDescription() ?? "N/A"}] doesn't match!"); + + _registeredObjectByType.Remove(syncObjectType); + _registeredObjectsByName.Remove(registeredObject.Name); + + return true; + } + } + } +} \ No newline at end of file diff --git a/MonkeyLoader/Sync/RegisteredSyncObject.cs b/MonkeyLoader/Sync/RegisteredSyncObject.cs new file mode 100644 index 0000000..9bd9ccc --- /dev/null +++ b/MonkeyLoader/Sync/RegisteredSyncObject.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonkeyLoader.Sync +{ + /// + /// A simple data class that allows the to track the known + /// MonkeySync object types. + /// + /// The type of the link object used by the sync object. + public sealed class RegisteredSyncObject + where TLink : class + { + private readonly SyncObjectFactory _createSyncObject; + + /// + /// Gets the -unique name for the registered + /// MonkeySync object type. + /// + public string Name { get; } + + /// + /// Gets the of the registered + /// MonkeySync object. + /// + public Type SyncObjectType { get; } + + /// + /// Creates a new instance of this data class with the given details for a + /// MonkeySync object type. + /// + /// The -unique name for the sync object type. + /// The type of the sync object. + /// A factory method that creates new instances of this sync object type. + public RegisteredSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + { + Name = name; + SyncObjectType = syncObjectType; + _createSyncObject = createSyncObject; + } + + /// + /// Creates a new instance of the registered + /// MonkeySync object. + /// + /// The created but not yet linked sync object. + public IUnlinkedMonkeySyncObject CreateSyncObject() + => _createSyncObject(); + } +} \ No newline at end of file From 8e6e5cd91d66dafe81eff884f4dd24181705befd Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 30 Jan 2025 20:48:10 +0100 Subject: [PATCH 09/22] Add linked SyncObject instance registration to SyncObjectRegistry --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 13 ++ ...ect.cs => MonkeySyncObjectRegistration.cs} | 4 +- MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs | 175 ++++++++++++++---- 4 files changed, 154 insertions(+), 40 deletions(-) rename MonkeyLoader/Sync/{RegisteredSyncObject.cs => MonkeySyncObjectRegistration.cs} (92%) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index a591c4c..5e70f9f 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.5-MonkeySync-beta + 0.24.6-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index e1e76be..c4bd080 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -1,6 +1,7 @@ using EnumerableToolkit; using HarmonyLib; using MonkeyLoader.Meta; +using MonkeyLoader.Patching; using System; using System.Collections.Generic; using System.ComponentModel; @@ -127,6 +128,18 @@ static MonkeySyncObject() methodsByName.Add(method.Name, (TSyncObject instance) => method.Invoke(instance, null)); } + /// + /// Initializes a new instance of this MonkeySync object. + /// + /// + /// When is not the type being instantiated. + /// + protected MonkeySyncObject() + { + if (GetType() != typeof(TSyncObject)) + throw new InvalidOperationException("TSyncObject must be the concrete Type being instantiated!"); + } + /// /// Ensures any unmanaged resources are disposed. /// diff --git a/MonkeyLoader/Sync/RegisteredSyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObjectRegistration.cs similarity index 92% rename from MonkeyLoader/Sync/RegisteredSyncObject.cs rename to MonkeyLoader/Sync/MonkeySyncObjectRegistration.cs index 9bd9ccc..7c4b2ae 100644 --- a/MonkeyLoader/Sync/RegisteredSyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObjectRegistration.cs @@ -9,7 +9,7 @@ namespace MonkeyLoader.Sync /// MonkeySync object types. ///
/// The type of the link object used by the sync object. - public sealed class RegisteredSyncObject + public sealed class MonkeySyncObjectRegistration where TLink : class { private readonly SyncObjectFactory _createSyncObject; @@ -33,7 +33,7 @@ public sealed class RegisteredSyncObject /// The -unique name for the sync object type. /// The type of the sync object. /// A factory method that creates new instances of this sync object type. - public RegisteredSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + public MonkeySyncObjectRegistration(string name, Type syncObjectType, SyncObjectFactory createSyncObject) { Name = name; SyncObjectType = syncObjectType; diff --git a/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs b/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs index 164cfe7..832757e 100644 --- a/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs +++ b/MonkeyLoader/Sync/MonkeySyncObjectRegistry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Text; namespace MonkeyLoader.Sync @@ -8,29 +9,69 @@ namespace MonkeyLoader.Sync /// /// Handles globally registering MonkeySync objects for particular link types, - /// so that the link implementations can create them. + /// so that the link implementations can create and reference them. /// public static class MonkeySyncRegistry { /// - /// Gets the for the given . + /// Gets the MonkeySync + /// object linked with the given link object. + /// + /// The found MonkeySync object. + /// When no sync object is registered for the link object. + /// + public static ILinkedMonkeySyncObject GetLinkedSyncObject(TLink linkObject) + where TLink : class + { + if (Library.TryGetLinkedSyncObject(linkObject, out var syncObject)) + return syncObject; + + throw new KeyNotFoundException("No sync object found for the given link object!"); + } + + /// + /// Gets the for the given . + /// + /// The type of registered sync object. + /// + public static MonkeySyncObjectRegistration GetSyncObjectRegistration(Type syncObjectType) + where TLink : class + => Library.GetSyncObjectRegistration(syncObjectType); + + /// + /// Gets the for the given . /// /// The type of the link object used by the sync object. /// The name of the registered sync object. - /// The found . + /// The found . /// When no sync object with the given has been registered. - public static RegisteredSyncObject GetRegisteredSyncObject(string name) + public static MonkeySyncObjectRegistration GetSyncObjectRegistration(string name) where TLink : class - => Library.GetRegisteredSyncObject(name); + => Library.GetSyncObjectRegistration(name); /// - /// Gets the for the given . + /// Determines whether there is a registered MonkeySync + /// object that is linked to the given link object. /// - /// The type of registered sync object. - /// - public static RegisteredSyncObject GetRegisteredSyncObject(Type syncObjectType) + /// + public static bool HasLinkedSyncObject(TLink linkObject) + where TLink : class + => Library.TryGetLinkedSyncObject(linkObject, out _); + + /// + /// Registers a linked MonkeySync + /// object so that it can be accessed + /// through a reference to its link object. + /// + /// + /// The link is held in a - + /// as such, the sync object may be garbage collected when the link object is. + /// + /// The type of the link object used by the sync object. + /// The linked sync object to register. + public static void RegisterLinkedSyncObject(ILinkedMonkeySyncObject syncObject) where TLink : class - => Library.GetRegisteredSyncObject(syncObjectType); + => Library.RegisterLinkedSyncObject(syncObject); /// /// Registers a or one with the same /// has already been registered. /// - public static RegisteredSyncObject RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + public static MonkeySyncObjectRegistration RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) where TLink : class => Library.RegisterSyncObject(name, syncObjectType, createSyncObject); /// - /// Tries to get the for the given . + /// Tries to get the MonkeySync + /// object linked with the given link object. + /// + /// The type of the link object used by the sync object. + /// The link object used by the sync object. + /// The found MonkeySync object; otherwise, null. + /// true if a sync object is registered for the given link object; otherwise, false. + public static bool TryGetLinkedSyncObject(TLink linkObject, [NotNullWhen(true)] out ILinkedMonkeySyncObject? syncObject) + where TLink : class + => Library.TryGetLinkedSyncObject(linkObject, out syncObject); + + /// + /// Tries to get the for the given . /// /// The type of the link object used by the sync object. /// The name of the registered sync object. - /// The found ; otherwise, null. + /// The found ; otherwise, null. /// true if the registered sync object type was found; otherwise, false. - public static bool TryGetRegisteredSyncObject(string name, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + public static bool TryGetSyncObjectRegistration(string name, [NotNullWhen(true)] out MonkeySyncObjectRegistration? registeredSyncObject) where TLink : class - => Library.TryGetRegisteredSyncObject(name, out registeredSyncObject); + => Library.TryGetSyncObjectRegistration(name, out registeredSyncObject); /// - /// Tries to get the for the given . + /// Tries to get the for the given . /// - /// - /// The found ; otherwise, null. + /// The type of the sync object. + /// The found ; otherwise, null. /// true if the registered sync object type was found; otherwise, false. - public static bool TryGetRegisteredSyncObject(Type syncObjectType, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + public static bool TryGetSyncObjectRegistration(Type syncObjectType, [NotNullWhen(true)] out MonkeySyncObjectRegistration? registeredSyncObject) where TLink : class - => Library.TryGetRegisteredSyncObject(syncObjectType, out registeredSyncObject); + => Library.TryGetSyncObjectRegistration(syncObjectType, out registeredSyncObject); + + /// + /// Unregisters a linked MonkeySync + /// object so that it can't be accessed + /// through a reference to its link object anymore. + /// + /// The type of the link object used by the sync object. + /// The linked sync object to unregister. + public static bool UnregisterLinkedSyncObject(ILinkedMonkeySyncObject syncObject) + where TLink : class + => Library.UnregisterLinkedSyncObject(syncObject); + + /// + /// Removes the registered MonkeySync + /// object that was linked to the given link object, + /// so that it can't be accessed through a reference to it anymore.
+ /// Optionally disposes of the sync object if necessary. + ///
+ /// The type of the link object used by the sync object. + /// The link object used by the sync object. + /// Whether to dispose the dispose of the sync object if necessary. + /// true if a sync object was registered for the given link object; otherwise, false. + public static bool UnregisterLinkedSyncObject(TLink linkObject, bool dispose = false) + where TLink : class + => Library.UnregisterLinkedSyncObject(linkObject, dispose); /// /// Removes the sync object type with the given . @@ -80,7 +158,7 @@ public static bool TryGetRegisteredSyncObject(Type syncObjectType, [NotNu /// /// /// When there's a registered sync object type with the given , - /// but none with the associated - or there's a Name mismatch. + /// but none with the associated - or there's a Name mismatch. /// public static bool UnregisterSyncObject(string name) where TLink : class @@ -96,7 +174,7 @@ public static bool UnregisterSyncObject(string name) /// /// /// When there's a registered sync object type for the given , - /// but none with the associated Name - or there's a Type mismatch. + /// but none with the associated Name - or there's a Type mismatch. /// public static bool UnregisterSyncObject(Type syncObjectType) where TLink : class @@ -105,21 +183,25 @@ public static bool UnregisterSyncObject(Type syncObjectType) private static class Library where TLink : class { - private static readonly Dictionary> _registeredObjectByType = []; - private static readonly Dictionary> _registeredObjectsByName = new(StringComparer.Ordinal); + private static readonly Dictionary> _registeredObjectByType = []; + private static readonly Dictionary> _registeredObjectsByName = new(StringComparer.Ordinal); + private static readonly ConditionalWeakTable> _syncObjectsByLinkObject = new(); + + public static MonkeySyncObjectRegistration GetSyncObjectRegistration(Type type) + => _registeredObjectByType[type]; - public static RegisteredSyncObject GetRegisteredSyncObject(string name) + public static MonkeySyncObjectRegistration GetSyncObjectRegistration(string name) => _registeredObjectsByName[name]; - public static RegisteredSyncObject GetRegisteredSyncObject(Type type) - => _registeredObjectByType[type]; + public static void RegisterLinkedSyncObject(ILinkedMonkeySyncObject syncObject) + => _syncObjectsByLinkObject.Add(syncObject.LinkObject, syncObject); - public static RegisteredSyncObject RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) + public static MonkeySyncObjectRegistration RegisterSyncObject(string name, Type syncObjectType, SyncObjectFactory createSyncObject) { if (_registeredObjectsByName.ContainsKey(name) || _registeredObjectByType.ContainsKey(syncObjectType)) throw new ArgumentException($"Sync Object type [{syncObjectType.CompactDescription()}] with name [{name}] has already been registered!"); - var registeredObject = new RegisteredSyncObject(name, syncObjectType, createSyncObject); + var registeredObject = new MonkeySyncObjectRegistration(name, syncObjectType, createSyncObject); _registeredObjectsByName.Add(name, registeredObject); _registeredObjectByType.Add(syncObjectType, registeredObject); @@ -127,18 +209,37 @@ public static RegisteredSyncObject RegisterSyncObject(string name, Type s return registeredObject; } - public static bool TryGetRegisteredSyncObject(string name, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + public static bool TryGetLinkedSyncObject(TLink linkObject, [NotNullWhen(true)] out ILinkedMonkeySyncObject? syncObject) + => _syncObjectsByLinkObject.TryGetValue(linkObject, out syncObject); + + public static bool TryGetSyncObjectRegistration(string name, [NotNullWhen(true)] out MonkeySyncObjectRegistration? registeredSyncObject) => _registeredObjectsByName.TryGetValue(name, out registeredSyncObject); - public static bool TryGetRegisteredSyncObject(Type type, [NotNullWhen(true)] out RegisteredSyncObject? registeredSyncObject) + public static bool TryGetSyncObjectRegistration(Type type, [NotNullWhen(true)] out MonkeySyncObjectRegistration? registeredSyncObject) => _registeredObjectByType.TryGetValue(type, out registeredSyncObject); - internal static bool UnregisterSyncObject(string name) + public static bool UnregisterLinkedSyncObject(TLink linkObject, bool dispose) + { + if (!dispose) + return _syncObjectsByLinkObject.Remove(linkObject); + + if (!_syncObjectsByLinkObject.TryGetValue(linkObject, out var syncObject)) + return false; + + (syncObject as IDisposable)?.Dispose(); + + return true; + } + + public static bool UnregisterLinkedSyncObject(ILinkedMonkeySyncObject syncObject) + => _syncObjectsByLinkObject.Remove(syncObject.LinkObject); + + public static bool UnregisterSyncObject(string name) { - if (!TryGetRegisteredSyncObject(name, out var registeredObject)) + if (!TryGetSyncObjectRegistration(name, out var registeredObject)) return false; - if (!TryGetRegisteredSyncObject(registeredObject.SyncObjectType, out var registeredObject2) || registeredObject.Name != registeredObject2.Name) + if (!TryGetSyncObjectRegistration(registeredObject.SyncObjectType, out var registeredObject2) || registeredObject.Name != registeredObject2.Name) throw new InvalidOperationException($"No Sync Object type found using type [{registeredObject.SyncObjectType.CompactDescription()} based on the name [{name}], or name [{registeredObject2?.Name ?? "N/A"}] doesn't match!"); _registeredObjectsByName.Remove(name); @@ -147,12 +248,12 @@ internal static bool UnregisterSyncObject(string name) return true; } - internal static bool UnregisterSyncObject(Type syncObjectType) + public static bool UnregisterSyncObject(Type syncObjectType) { - if (!TryGetRegisteredSyncObject(syncObjectType, out var registeredObject)) + if (!TryGetSyncObjectRegistration(syncObjectType, out var registeredObject)) return false; - if (!TryGetRegisteredSyncObject(registeredObject.Name, out var registeredObject2) || registeredObject.SyncObjectType != registeredObject2.SyncObjectType) + if (!TryGetSyncObjectRegistration(registeredObject.Name, out var registeredObject2) || registeredObject.SyncObjectType != registeredObject2.SyncObjectType) throw new InvalidOperationException($"No Sync Object type found using name [{registeredObject.Name} based on the type [{syncObjectType.CompactDescription()}], or type [{registeredObject2?.SyncObjectType.CompactDescription() ?? "N/A"}] doesn't match!"); _registeredObjectByType.Remove(syncObjectType); From 7aa2361d97c99fa8606838abc7d7ad82c69be0b5 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 2 Feb 2025 14:46:17 +0100 Subject: [PATCH 10/22] Tweak registry behavior and make MonkeySyncValue.Value virtual --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 9 ++++++++- MonkeyLoader/Sync/MonkeySyncValue.cs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 5e70f9f..26a7836 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.6-MonkeySync-beta + 0.24.7-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index c4bd080..126637b 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -191,6 +191,10 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// for every readable instance property and /// its overload for every /// MonkeySync method on .
+ /// If the link is successfully created, this linked sync object will be + /// added + /// to the automatically. + /// /// The detected properties are stored in propertyAccessorsByName, /// while the detected methods are stored in methodsByName. /// @@ -218,6 +222,9 @@ protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) foreach (var syncMethod in methodsByName) success &= EstablishLinkFor(syncMethod.Key, syncMethod.Value, fromRemote); + if (success) + MonkeySyncRegistry.RegisterLinkedSyncObject(this); + return success; } @@ -235,7 +242,7 @@ protected virtual void OnDisposing() /// /// Cleans up any unmanaged resources as part of - /// disposing or finalization. + /// disposing or finalization. /// protected virtual void OnFinalizing() { } diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index dcb2aac..fe023b9 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -79,7 +79,7 @@ public class MonkeySyncValue : IMonkeySyncValue private T _value; /// - public T Value + public virtual T Value { get => _value; From abbe1c8d968acab623a9903ef7ea18ce3bab6cb3 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 2 Feb 2025 21:31:41 +0100 Subject: [PATCH 11/22] Restructure how SyncValues work --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 20 +++--- MonkeyLoader/Sync/MonkeySyncValue.cs | 97 ++++++++++++++++++++------- 3 files changed, 86 insertions(+), 33 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 26a7836..6a5ae82 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.7-MonkeySync-beta + 0.24.8-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 126637b..07fd210 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -79,13 +79,13 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject /// The concrete type of the MonkeySync object. /// - /// The -derived interface + /// The -derived interface /// that the MonkeySync values of this object must implement. /// /// The type of the link object used by the sync object. public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject where TSyncObject : MonkeySyncObject - where TSyncValue : IMonkeySyncValue + where TSyncValue : IUnlinkedMonkeySyncValue where TLink : class { /// @@ -115,13 +115,13 @@ public abstract class MonkeySyncObject : IUnlink static MonkeySyncObject() { var syncValueType = typeof(TSyncValue); - var syncValueProperties = AccessTools.GetDeclaredProperties(typeof(TSyncObject)) + var syncValueProperties = typeof(TSyncObject).GetProperties(AccessTools.all) .Where(property => syncValueType.IsAssignableFrom(property.PropertyType) && (!(property.GetGetMethod()?.IsStatic ?? true))); foreach (var property in syncValueProperties) propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); - var syncMethods = AccessTools.GetDeclaredMethods(typeof(TSyncObject)) + var syncMethods = typeof(TSyncObject).GetMethods(AccessTools.all) .Where(method => !method.IsStatic && !method.ContainsGenericParameters && method.ReturnType == typeof(void) && method.GetParameters().Length == 0); foreach (var method in syncMethods) @@ -170,11 +170,16 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// /// Creates a link for the given sync value of the given name. /// + /// + /// By default: Calls + /// .EstablishLinkFor(). + /// /// The name of the sync value to link. /// The sync value to link. /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(string propertyName, TSyncValue syncValue, bool fromRemote); + protected virtual bool EstablishLinkFor(string propertyName, TSyncValue syncValue, bool fromRemote) + => syncValue.EstablishLinkFor(this, propertyName, fromRemote); /// /// Creates a link for the given sync method of the given name. @@ -214,7 +219,7 @@ protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) var syncValue = syncValueProperty.Value((TSyncObject)this); syncValue.Changed += (sender, changedArgs) - => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(syncValueProperty.Key)); + => OnPropertyChanged(syncValueProperty.Key); success &= EstablishLinkFor(syncValueProperty.Key, syncValue, fromRemote); } @@ -271,12 +276,11 @@ protected void OnLinkInvalidated() /// event with the given . /// /// - /// This is automatically called for any properties. + /// This is automatically called for any properties. /// /// The name of the property that changed. protected void OnPropertyChanged(string propertyName) { - // Still needs to be hooked up somewhere var eventData = new PropertyChangedEventArgs(propertyName); PropertyChanged?.Invoke(this, eventData); diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index fe023b9..b91a115 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -10,10 +10,21 @@ namespace MonkeyLoader.Sync { /// - /// Defines the non-generic interface for s. + /// Defines the non-generic interface for s. /// - public interface IMonkeySyncValue : INotifyValueChanged + /// + public interface ILinkedMonkeySyncValue : INotifyValueChanged { + /// + /// Gets the property name of this sync value. + /// + public string Name { get; } + + /// + /// Gets the sync object that this value belongs to. + /// + public ILinkedMonkeySyncObject SyncObject { get; } + /// /// Gets or sets the internal value of this sync value. /// @@ -27,24 +38,25 @@ public interface IMonkeySyncValue : INotifyValueChanged } /// - /// Defines the generic interface for s. + /// Defines the generic interface for linked s. /// - /// The type of the Value. - public interface IMonkeySyncValue : INotifyValueChanged, - IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue + /// The type of the link object used by the sync object. + /// The type of the Value. + public interface ILinkedMonkeySyncValue : INotifyValueChanged, + IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue { - /// + /// public new T Value { get; set; } } /// - /// Defines the interface for readonly s. + /// Defines the interface for readonly s. /// /// /// This interface exists purely to facilitate keeping a covariant list of sync values. /// - /// The type of the Value. - public interface IReadOnlyMonkeySyncValue : IMonkeySyncValue + /// + public interface IReadOnlyMonkeySyncValue : ILinkedMonkeySyncValue { /// /// Gets the internal value of this sync value. @@ -53,13 +65,29 @@ public interface IReadOnlyMonkeySyncValue : IMonkeySyncValue } /// - /// Defines the interface for writeonly s. + /// Defines the interface for not yet linked s. + /// + /// + public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue + { + /// + /// Establishes this sync value's association and link through the given sync object. + /// + /// The sync object that this value belongs to. + /// The property name of this sync value. + /// Whether the link is being established from the remote side. + /// true if the established link is valid; otherwise, false. + public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote); + } + + /// + /// Defines the interface for writeonly s. /// /// /// This interface exists purely to facilitate keeping a contravariant list of sync values. /// - /// The type of the Value. - public interface IWriteOnlyMonkeySyncValue : IMonkeySyncValue + /// + public interface IWriteOnlyMonkeySyncValue : ILinkedMonkeySyncValue { /// /// Sets the internal value of this sync value. @@ -68,16 +96,22 @@ public interface IWriteOnlyMonkeySyncValue : IMonkeySyncValue } /// - /// Implements a basic version of an . + /// Implements an abstract base for s. /// - /// The type of the Value. - public class MonkeySyncValue : IMonkeySyncValue + /// + public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue, ILinkedMonkeySyncValue { private static readonly Type _valueType = typeof(T); private ValueChangedEventHandler? _untypedChanged; private T _value; + /// + public string Name { get; private set; } = null!; + + /// + public ILinkedMonkeySyncObject SyncObject { get; private set; } = null!; + /// public virtual T Value { @@ -93,7 +127,7 @@ public virtual T Value } } - object? IMonkeySyncValue.Value + object? ILinkedMonkeySyncValue.Value { get => Value; set => Value = (T)value!; @@ -111,21 +145,36 @@ public MonkeySyncValue(T value) Value = value; } - /// - /// Wraps the given into a sync object. - /// - /// The value to wrap. - public static implicit operator MonkeySyncValue(T value) => new(value); - /// /// Unwraps the Value from the given sync object. /// /// The sync object to unwrap. - public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + + /// + /// Sets this sync value's SyncObject + /// and Name to the ones provided.
+ /// Then calls the internal link method. + ///
+ /// + public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote) + { + SyncObject = syncObject; + Name = propertyName; + + return EstablishLinkForInternal(syncObject, propertyName, fromRemote); + } /// public override string ToString() => Value?.ToString() ?? ""; + /// + /// Handles the aspects of establishing a link that are + /// particular to s as a link object. + /// + /// + protected abstract bool EstablishLinkForInternal(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote); + /// /// Handles the value of this config item potentially having changed. /// If the and new value are different:
From 3a8949ccb11d1bf2e36dcfc8a99809217e947822 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 2 Feb 2025 21:45:14 +0100 Subject: [PATCH 12/22] Tweak MonkeySyncValue link establishing --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncValue.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 6a5ae82..fa5079f 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.8-MonkeySync-beta + 0.24.9-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index b91a115..c913dea 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -154,7 +154,7 @@ public MonkeySyncValue(T value) /// /// Sets this sync value's SyncObject /// and Name to the ones provided.
- /// Then calls the internal link method. + /// Then calls the internal link method. ///
/// public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote) @@ -162,7 +162,7 @@ public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string p SyncObject = syncObject; Name = propertyName; - return EstablishLinkForInternal(syncObject, propertyName, fromRemote); + return EstablishLinkInternal(fromRemote); } /// @@ -170,10 +170,13 @@ public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string p /// /// Handles the aspects of establishing a link that are - /// particular to s as a link object. + /// particular to s as a link object.
+ /// The SyncObject and Name + /// have already been assigned by the EstablishLinkFor + /// method that calls this. ///
/// - protected abstract bool EstablishLinkForInternal(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote); + protected abstract bool EstablishLinkInternal(bool fromRemote); /// /// Handles the value of this config item potentially having changed. From 610e2d081cebde18bf619c9419932b6890e91f77 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 2 Feb 2025 22:38:43 +0100 Subject: [PATCH 13/22] Fix missing MonkeySyncMethodAttribute check --- MonkeyLoader/Sync/MonkeySyncObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 07fd210..450b11c 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -7,6 +7,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Text; namespace MonkeyLoader.Sync @@ -122,7 +123,7 @@ static MonkeySyncObject() propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); var syncMethods = typeof(TSyncObject).GetMethods(AccessTools.all) - .Where(method => !method.IsStatic && !method.ContainsGenericParameters && method.ReturnType == typeof(void) && method.GetParameters().Length == 0); + .Where(method => !method.IsStatic && !method.ContainsGenericParameters && method.ReturnType == typeof(void) && method.GetParameters().Length == 0 && method.GetCustomAttribute() is not null); foreach (var method in syncMethods) methodsByName.Add(method.Name, (TSyncObject instance) => method.Invoke(instance, null)); From 61aa20337e92d4abe91c5e82f8e8e816e87f1db9 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 6 Feb 2025 21:23:50 +0100 Subject: [PATCH 14/22] Adjust link establishment --- MonkeyLoader/Sync/MonkeySyncObject.cs | 84 +++++++++++++++------------ MonkeyLoader/Sync/MonkeySyncValue.cs | 9 +++ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 450b11c..eb4332d 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -47,6 +47,7 @@ public interface IMonkeySyncObject : INotifyPropertyChanged, IDisposable /// /// Gets whether this sync object has a valid link. /// + [MemberNotNullWhen(true, nameof(LinkObject))] public bool IsLinkValid { get; } /// @@ -106,6 +107,7 @@ public abstract class MonkeySyncObject : IUnlink public bool HasLinkObject => LinkObject is not null; /// + [MemberNotNullWhen(true, nameof(LinkObject))] public abstract bool IsLinkValid { get; } /// @@ -165,37 +167,14 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) LinkObject = linkObject; - return EstablishLinkWith(linkObject, fromRemote); + return EstablishLink(fromRemote); } - /// - /// Creates a link for the given sync value of the given name. - /// - /// - /// By default: Calls - /// .EstablishLinkFor(). - /// - /// The name of the sync value to link. - /// The sync value to link. - /// Whether the link is being established from the remote side. - /// true if the link was successfully created; otherwise, false. - protected virtual bool EstablishLinkFor(string propertyName, TSyncValue syncValue, bool fromRemote) - => syncValue.EstablishLinkFor(this, propertyName, fromRemote); - - /// - /// Creates a link for the given sync method of the given name. - /// - /// The name of the sync method to link. - /// The sync method to link. - /// Whether the link is being established from the remote side. - /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(string methodName, Action syncMethod, bool fromRemote); - /// /// By default: Sets up the event handlers - /// and calls EstablishLinkFor + /// and calls EstablishLinkFor /// for every readable instance property and - /// its overload for every + /// its overload for every /// MonkeySync method on .
/// If the link is successfully created, this linked sync object will be /// added @@ -211,7 +190,7 @@ protected virtual bool EstablishLinkFor(string propertyName, TSyncValue syncValu ///
///
/// - protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) + protected virtual bool EstablishLink(bool fromRemote) { var success = true; @@ -222,11 +201,11 @@ protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) syncValue.Changed += (sender, changedArgs) => OnPropertyChanged(syncValueProperty.Key); - success &= EstablishLinkFor(syncValueProperty.Key, syncValue, fromRemote); + success &= EstablishLinkFor(syncValue, syncValueProperty.Key, fromRemote); } foreach (var syncMethod in methodsByName) - success &= EstablishLinkFor(syncMethod.Key, syncMethod.Value, fromRemote); + success &= EstablishLinkFor(syncMethod.Value, syncMethod.Key, fromRemote); if (success) MonkeySyncRegistry.RegisterLinkedSyncObject(this); @@ -234,6 +213,29 @@ protected virtual bool EstablishLinkWith(TLink linkObject, bool fromRemote) return success; } + /// + /// Creates a link for the given sync value of the given name. + /// + /// + /// By default: Calls + /// .EstablishLinkFor(). + /// + /// The sync value to link. + /// The name of the sync value to link. + /// Whether the link is being established from the remote side. + /// true if the link was successfully created; otherwise, false. + protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyName, bool fromRemote) + => syncValue.EstablishLinkFor(this, propertyName, fromRemote); + + /// + /// Creates a link for the given sync method of the given name. + /// + /// The sync method to link. + /// The name of the sync method to link. + /// Whether the link is being established from the remote side. + /// true if the link was successfully created; otherwise, false. + protected abstract bool EstablishLinkFor(Action syncMethod, string methodName, bool fromRemote); + /// /// Cleans up any managed resources as part of disposing. /// @@ -267,7 +269,11 @@ protected void OnLinkInvalidated() if (!IsLinkValid && TryRestoreLink() && IsLinkValid) return; - Invalidated.TryInvokeAll(); + try + { + Invalidated.TryInvokeAll(); + } + catch { } Dispose(); } @@ -288,9 +294,9 @@ protected void OnPropertyChanged(string propertyName) } /// - /// By default: Calls TryRestoreLinkFor + /// By default: Calls TryRestoreLinkFor /// for every readable instance property and - /// its overload for every + /// its overload for every /// MonkeySync method on .
/// The detected properties are stored in propertyAccessorsByName, /// while the detected methods are stored in methodsByName. @@ -307,29 +313,31 @@ protected virtual bool TryRestoreLink() var success = true; foreach (var syncValueProperty in propertyAccessorsByName) - success &= TryRestoreLinkFor(syncValueProperty.Key, syncValueProperty.Value((TSyncObject)this)); + success &= TryRestoreLinkFor(syncValueProperty.Value((TSyncObject)this), syncValueProperty.Key); foreach (var syncMethod in methodsByName) - success &= TryRestoreLinkFor(syncMethod.Key, syncMethod.Value); + success &= TryRestoreLinkFor(syncMethod.Value, syncMethod.Key); return success; } + // Implement the sync value one through a method on sync values + /// /// Tries to restore the link for the given sync value of the given name. /// - /// The name of the sync value to link. /// The sync value to link. + /// The name of the sync value to link. /// true if the link was successfully restored; otherwise, false. - protected abstract bool TryRestoreLinkFor(string propertyName, TSyncValue syncValue); + protected abstract bool TryRestoreLinkFor(TSyncValue syncValue, string propertyName); /// /// Tries to restore the link for the given sync method of the given name. /// - /// The name of the sync method to link. /// The sync method to link. + /// The name of the sync method to link. /// true if the link was successfully restored; otherwise, false. - protected abstract bool TryRestoreLinkFor(string methodName, Action syncMethod); + protected abstract bool TryRestoreLinkFor(Action syncMethod, string methodName); private void Dispose(bool disposing) { diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index c913dea..d53f501 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -45,6 +45,12 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged public interface ILinkedMonkeySyncValue : INotifyValueChanged, IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue { + /// + /// Gets the LinkObject + /// of the SyncObject that this value belongs to. + /// + public TLink LinkObject { get; } + /// public new T Value { get; set; } } @@ -106,6 +112,9 @@ public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue + public TLink LinkObject => SyncObject.LinkObject; + /// public string Name { get; private set; } = null!; From 83567b289bfbae2e631cf39d314f5e073d293f6c Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Thu, 13 Feb 2025 17:33:18 +0100 Subject: [PATCH 15/22] Add link restoring method to SyncValue and automatically dispose them from sync object --- MonkeyLoader/Sync/MonkeySyncObject.cs | 46 ++++++++++++----- MonkeyLoader/Sync/MonkeySyncValue.cs | 73 ++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 21 deletions(-) diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index eb4332d..d3b53b5 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -1,7 +1,6 @@ using EnumerableToolkit; using HarmonyLib; using MonkeyLoader.Meta; -using MonkeyLoader.Patching; using System; using System.Collections.Generic; using System.ComponentModel; @@ -100,6 +99,11 @@ public abstract class MonkeySyncObject : IUnlink ///
protected static readonly Dictionary> propertyAccessorsByName = new(StringComparer.Ordinal); + /// + /// The instances associated with this sync object. + /// + protected readonly HashSet syncValues = []; + private bool _disposedValue; /// @@ -217,19 +221,26 @@ protected virtual bool EstablishLink(bool fromRemote) /// Creates a link for the given sync value of the given name. ///
/// - /// By default: Calls - /// .EstablishLinkFor(). + /// By default: Adds the given sync value to the set of instances and calls + /// .EstablishLinkFor(…). /// /// The sync value to link. /// The name of the sync value to link. /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyName, bool fromRemote) - => syncValue.EstablishLinkFor(this, propertyName, fromRemote); + { + syncValues.Add(syncValue); + return syncValue.EstablishLinkFor(this, propertyName, fromRemote); + } /// /// Creates a link for the given sync method of the given name. /// + /// + /// Any s created for this + /// must be added to the set of instances. + /// /// The sync method to link. /// The name of the sync method to link. /// Whether the link is being established from the remote side. @@ -240,10 +251,15 @@ protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyNam /// Cleans up any managed resources as part of disposing. ///
/// - /// By default: Disposes the LinkObject if it's . + /// By default: Disposes all instances + /// that were added to the set of instances, + /// and the LinkObject if it's . /// protected virtual void OnDisposing() { + foreach (var syncValue in syncValues) + syncValue.Dispose(); + if (LinkObject is IDisposable disposable) disposable.Dispose(); } @@ -294,9 +310,9 @@ protected void OnPropertyChanged(string propertyName) } /// - /// By default: Calls TryRestoreLinkFor + /// By default: Calls TryRestoreLinkFor /// for every readable instance property and - /// its overload for every + /// its overload for every /// MonkeySync method on .
/// The detected properties are stored in propertyAccessorsByName, /// while the detected methods are stored in methodsByName. @@ -312,8 +328,8 @@ protected virtual bool TryRestoreLink() { var success = true; - foreach (var syncValueProperty in propertyAccessorsByName) - success &= TryRestoreLinkFor(syncValueProperty.Value((TSyncObject)this), syncValueProperty.Key); + foreach (var syncValues in propertyAccessorsByName.Values) + success &= TryRestoreLinkFor(syncValues((TSyncObject)this)); foreach (var syncMethod in methodsByName) success &= TryRestoreLinkFor(syncMethod.Value, syncMethod.Key); @@ -321,15 +337,17 @@ protected virtual bool TryRestoreLink() return success; } - // Implement the sync value one through a method on sync values - /// - /// Tries to restore the link for the given sync value of the given name. + /// Tries to restore the link for the given sync value. /// + /// + /// By default: Calls + /// .TryRestoreLink(). + /// /// The sync value to link. - /// The name of the sync value to link. /// true if the link was successfully restored; otherwise, false. - protected abstract bool TryRestoreLinkFor(TSyncValue syncValue, string propertyName); + protected virtual bool TryRestoreLinkFor(TSyncValue syncValue) + => syncValue.TryRestoreLink(); /// /// Tries to restore the link for the given sync method of the given name. diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index d53f501..ba2f06e 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -13,8 +13,14 @@ namespace MonkeyLoader.Sync /// Defines the non-generic interface for s. /// /// - public interface ILinkedMonkeySyncValue : INotifyValueChanged + public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDisposable { + /// + /// Gets the LinkObject + /// of the SyncObject that this value belongs to. + /// + public TLink LinkObject { get; } + /// /// Gets the property name of this sync value. /// @@ -35,6 +41,12 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged /// the wrapped Value. ///
public Type ValueType { get; } + + /// + /// Tries to restore the link of this sync value. + /// + /// true if the link was successfully restored; otherwise, false. + public bool TryRestoreLink(); } /// @@ -45,12 +57,6 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged public interface ILinkedMonkeySyncValue : INotifyValueChanged, IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue { - /// - /// Gets the LinkObject - /// of the SyncObject that this value belongs to. - /// - public TLink LinkObject { get; } - /// public new T Value { get; set; } } @@ -109,6 +115,7 @@ public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue + /// Ensures any unmanaged resources are disposed. + /// + ~MonkeySyncValue() + { + Dispose(false); + } + /// /// Unwraps the Value from the given sync object. /// /// The sync object to unwrap. public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in the 'OnDisposing()' or 'OnFinalizing()' methods + Dispose(true); + GC.SuppressFinalize(this); + } + /// /// Sets this sync value's SyncObject /// and Name to the ones provided.
@@ -177,6 +200,9 @@ public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string p /// public override string ToString() => Value?.ToString() ?? ""; + /// + public abstract bool TryRestoreLink(); + /// /// Handles the aspects of establishing a link that are /// particular to s as a link object.
@@ -187,6 +213,39 @@ public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string p /// protected abstract bool EstablishLinkInternal(bool fromRemote); + /// + /// Cleans up any managed resources as part of disposing. + /// + /// + /// By default: Sets the SyncObject to null + /// and the internal value to its default. + /// + protected virtual void OnDisposing() + { + SyncObject = null!; + _value = default!; + } + + /// + /// Cleans up any unmanaged resources as part of + /// disposing or finalization. + /// + protected virtual void OnFinalizing() + { } + + private void Dispose(bool disposing) + { + if (_disposedValue) + return; + + if (disposing) + OnDisposing(); + + OnFinalizing(); + + _disposedValue = true; + } + /// /// Handles the value of this config item potentially having changed. /// If the and new value are different:
From e9aca64925e3c6e2498b42a056a5a22775df7152 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Fri, 7 Mar 2025 19:54:28 +0100 Subject: [PATCH 16/22] Tweak sync method system --- MonkeyLoader/MonkeyLoader.csproj | 2 +- .../Sync/MonkeySyncMethodAttribute.cs | 21 +++++++++++++- MonkeyLoader/Sync/MonkeySyncObject.cs | 28 +++++++++++++------ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index fa5079f..0622d69 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.9-MonkeySync-beta + 0.24.10-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs index c1cb079..d482495 100644 --- a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs +++ b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace MonkeyLoader.Sync { @@ -6,7 +7,25 @@ namespace MonkeyLoader.Sync /// Marks a MonkeySync /// object's instance method as being triggerable through the MonkeySync system. ///
+ /// + /// The method must be + /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class MonkeySyncMethodAttribute : MonkeyLoaderAttribute - { } + { + /// + /// Determines whether the given + /// is suitable to be triggerable through the MonkeySync system. + /// + /// + /// To be suitable, a method must be a non-generic parameterless void instance method, + /// which is decorated with this attribute. + /// + /// The method to check for suitability. + /// true if the method is suitable; otherwise, false. + public static bool IsValid(MethodInfo method) + => !method.IsStatic && !method.ContainsGenericParameters + && method.ReturnType == typeof(void) && method.GetParameters().Length == 0 + && method.GetCustomAttribute() is not null; + } } \ No newline at end of file diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index d3b53b5..65783e0 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -90,15 +90,22 @@ public abstract class MonkeySyncObject : IUnlink where TLink : class { /// - /// The detected MonkeySync nethods by their name. + /// The s of the detected MonkeySync + /// methods of this type by their name. /// - protected static readonly Dictionary> methodsByName = new(StringComparer.Ordinal); + protected static readonly Dictionary methodInfosByName = new(StringComparer.Ordinal); /// /// The getters for the detected instance properties by their name. /// protected static readonly Dictionary> propertyAccessorsByName = new(StringComparer.Ordinal); + /// + /// The s to invoke the detected MonkeySync + /// methods of this type by their name. + /// + protected readonly Dictionary methodsByName = new(StringComparer.Ordinal); + /// /// The instances associated with this sync object. /// @@ -129,10 +136,10 @@ static MonkeySyncObject() propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); var syncMethods = typeof(TSyncObject).GetMethods(AccessTools.all) - .Where(method => !method.IsStatic && !method.ContainsGenericParameters && method.ReturnType == typeof(void) && method.GetParameters().Length == 0 && method.GetCustomAttribute() is not null); + .Where(MonkeySyncMethodAttribute.IsValid); foreach (var method in syncMethods) - methodsByName.Add(method.Name, (TSyncObject instance) => method.Invoke(instance, null)); + methodInfosByName.Add(method.Name, method); } /// @@ -145,6 +152,9 @@ protected MonkeySyncObject() { if (GetType() != typeof(TSyncObject)) throw new InvalidOperationException("TSyncObject must be the concrete Type being instantiated!"); + + foreach (var methodEntry in methodInfosByName) + methodsByName.Add(methodEntry.Key, AccessTools.MethodDelegate(methodEntry.Value, this)); } /// @@ -185,7 +195,7 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// to the automatically. /// /// The detected properties are stored in propertyAccessorsByName, - /// while the detected methods are stored in methodsByName. + /// while the detected methods are stored in methodsByName. /// /// This method is called by LinkWith /// after the LinkObject has been assigned.
@@ -245,7 +255,7 @@ protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyNam /// The name of the sync method to link. /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(Action syncMethod, string methodName, bool fromRemote); + protected abstract bool EstablishLinkFor(Action syncMethod, string methodName, bool fromRemote); /// /// Cleans up any managed resources as part of disposing. @@ -312,10 +322,10 @@ protected void OnPropertyChanged(string propertyName) /// /// By default: Calls TryRestoreLinkFor /// for every readable instance property and - /// its overload for every + /// its overload for every /// MonkeySync method on .
/// The detected properties are stored in propertyAccessorsByName, - /// while the detected methods are stored in methodsByName. + /// while the detected methods are stored in methodsByName. ///
/// This method is called by OnLinkInvalidated /// if IsLinkValid has become false.
@@ -355,7 +365,7 @@ protected virtual bool TryRestoreLinkFor(TSyncValue syncValue) /// The sync method to link. /// The name of the sync method to link. /// true if the link was successfully restored; otherwise, false. - protected abstract bool TryRestoreLinkFor(Action syncMethod, string methodName); + protected abstract bool TryRestoreLinkFor(Action syncMethod, string methodName); private void Dispose(bool disposing) { From 5f13612bbad78c101615daec3db413cb0b1bed0f Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Fri, 7 Mar 2025 23:18:09 +0100 Subject: [PATCH 17/22] Move SyncObject registration --- MonkeyLoader/Sync/MonkeySyncObject.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 65783e0..75a829d 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -64,7 +64,10 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject - /// Establishes this sync object's link with the given object. + /// Establishes this sync object's link with the given object.
+ /// If the link is successfully created, the now linked sync object will be + /// added + /// to the . ///
/// /// If the link fails or gets broken, a new instance has to be created. @@ -135,6 +138,8 @@ static MonkeySyncObject() foreach (var property in syncValueProperties) propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); + // Replace this with a special MonkeySyncMethod type that takes an action as the target + // The invoke method can then be overridden to for example set the user field that triggers the method. var syncMethods = typeof(TSyncObject).GetMethods(AccessTools.all) .Where(MonkeySyncMethodAttribute.IsValid); @@ -181,7 +186,11 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) LinkObject = linkObject; - return EstablishLink(fromRemote); + if (!EstablishLink(fromRemote)) + return false; + + MonkeySyncRegistry.RegisterLinkedSyncObject(this); + return true; } /// @@ -189,10 +198,7 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// and calls EstablishLinkFor /// for every readable instance property and /// its overload for every - /// MonkeySync method on .
- /// If the link is successfully created, this linked sync object will be - /// added - /// to the automatically. + /// MonkeySync method on . ///
/// The detected properties are stored in propertyAccessorsByName, /// while the detected methods are stored in methodsByName. @@ -221,9 +227,6 @@ protected virtual bool EstablishLink(bool fromRemote) foreach (var syncMethod in methodsByName) success &= EstablishLinkFor(syncMethod.Value, syncMethod.Key, fromRemote); - if (success) - MonkeySyncRegistry.RegisterLinkedSyncObject(this); - return success; } From f00fe3e83ed8745b4a3f19f4f30029f983dc7e45 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 8 Mar 2025 18:34:17 +0100 Subject: [PATCH 18/22] Make MonkeySync methods tied to a value and support fields + properties --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/IMonkeySyncMethod.cs | 88 +++++++++++++++++ MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs | 13 +++ .../Sync/MonkeySyncMethodAttribute.cs | 31 ------ MonkeyLoader/Sync/MonkeySyncObject.cs | 94 +++++++++---------- MonkeyLoader/Sync/MonkeySyncValue.cs | 8 +- 6 files changed, 149 insertions(+), 87 deletions(-) create mode 100644 MonkeyLoader/Sync/IMonkeySyncMethod.cs create mode 100644 MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs delete mode 100644 MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 0622d69..6c5ae36 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.10-MonkeySync-beta + 0.24.11-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/IMonkeySyncMethod.cs b/MonkeyLoader/Sync/IMonkeySyncMethod.cs new file mode 100644 index 0000000..7969774 --- /dev/null +++ b/MonkeyLoader/Sync/IMonkeySyncMethod.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonkeyLoader.Sync +{ + /// + /// The delegate that is represented by MonkeySync methods. + /// + public delegate void MonkeySyncAction(); + + /// + /// The delegate that is represented by MonkeySync methods. + /// + /// The value that triggered this method. + /// + public delegate void MonkeySyncFunc(T trigger); + + /// + /// Defines the non-generic interface for s. + /// + /// + public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncValue + { + } + + /// + /// Defines the generic interface for linked s. + /// + /// The type of the link object used by the sync object that this sync value links to. + /// The type of the Value that can trigger this sync method. + public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, ILinkedMonkeySyncValue + { + /// + /// Gets the delegate that can be triggered by this sync method. + /// + public MonkeySyncFunc Function { get; } + } + + /// + /// Defines the interface for not yet linked s. + /// + /// + public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, IUnlinkedMonkeySyncValue + { } + + /// + /// Implements an abstract example for s. + /// + /// + /// This class is in all likelihood not useful to actually derive from.
+ /// It mainly serves as an example for how an implementation could look. + ///
+ /// + public abstract class MonkeySyncMethod : MonkeySyncValue, + IUnlinkedMonkeySyncMethod, ILinkedMonkeySyncMethod + { + /// + public MonkeySyncFunc Function { get; } + + /// + /// Creates a new sync method instance that wraps the given , + /// changes of which can trigger the target . + /// + /// The delegate that can be triggered by this sync method. + /// The value to wrap. + protected MonkeySyncMethod(MonkeySyncFunc function, T value) : base(value) + { + Function = function; + } + + /// + /// Creates a new sync method instance that wraps the given , + /// changes of which can trigger the target . + /// + /// The delegate that can be triggered by this sync method. + /// The value to wrap. + protected MonkeySyncMethod(MonkeySyncAction action, T value) + : this(_ => action(), value) + { } + + /// + /// Creates structures that make this method triggerable by others locally or in shared environments, + /// whether they have the mod implementing the method or not. + /// + public abstract void MakeInvocation(); + } +} \ No newline at end of file diff --git a/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs new file mode 100644 index 0000000..9aac0ee --- /dev/null +++ b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace MonkeyLoader.Sync +{ + /// + /// Marks a field or property with a compatible type + /// on a class deriving from + /// to be excluded from the automatically detected fields and properties. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class IgnoreSyncValueAttribute : MonkeyLoaderAttribute + { } +} \ No newline at end of file diff --git a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs b/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs deleted file mode 100644 index d482495..0000000 --- a/MonkeyLoader/Sync/MonkeySyncMethodAttribute.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Reflection; - -namespace MonkeyLoader.Sync -{ - /// - /// Marks a MonkeySync - /// object's instance method as being triggerable through the MonkeySync system. - /// - /// - /// The method must be - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public sealed class MonkeySyncMethodAttribute : MonkeyLoaderAttribute - { - /// - /// Determines whether the given - /// is suitable to be triggerable through the MonkeySync system. - /// - /// - /// To be suitable, a method must be a non-generic parameterless void instance method, - /// which is decorated with this attribute. - /// - /// The method to check for suitability. - /// true if the method is suitable; otherwise, false. - public static bool IsValid(MethodInfo method) - => !method.IsStatic && !method.ContainsGenericParameters - && method.ReturnType == typeof(void) && method.GetParameters().Length == 0 - && method.GetCustomAttribute() is not null; - } -} \ No newline at end of file diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 75a829d..b4ca178 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -81,6 +81,13 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject /// Implements the abstract base for MonkeySync objects. ///
+ /// + /// Automatically detects any instance fields / properties containing + /// MonkeySync values or methods compatible with .
+ /// Property names take precedence over their backing fields.
+ /// Use the to mark fields / properties that should be ignored. + /// If a property is marked, its backing field will be ignored too. + ///
/// The concrete type of the MonkeySync object. /// /// The -derived interface @@ -93,21 +100,14 @@ public abstract class MonkeySyncObject : IUnlink where TLink : class { /// - /// The s of the detected MonkeySync - /// methods of this type by their name. + /// The getters for the automatically detected instance fields / properties by their name. /// - protected static readonly Dictionary methodInfosByName = new(StringComparer.Ordinal); - - /// - /// The getters for the detected instance properties by their name. - /// - protected static readonly Dictionary> propertyAccessorsByName = new(StringComparer.Ordinal); - - /// - /// The s to invoke the detected MonkeySync - /// methods of this type by their name. - /// - protected readonly Dictionary methodsByName = new(StringComparer.Ordinal); + /// + /// Property names take precedence over their backing fields.
+ /// Use the to mark fields / properties that should be ignored. + /// If a property is marked, its backing field will be ignored too. + ///
+ protected static readonly Dictionary> syncValueAccessorsByName = new(StringComparer.Ordinal); /// /// The instances associated with this sync object. @@ -132,19 +132,34 @@ public abstract class MonkeySyncObject : IUnlink static MonkeySyncObject() { var syncValueType = typeof(TSyncValue); + var syncValueFields = typeof(TSyncObject).GetFields(AccessTools.all) + .Where(field => !field.IsStatic && syncValueType.IsAssignableFrom(field.FieldType) && field.GetCustomAttribute() is null); + var syncValueProperties = typeof(TSyncObject).GetProperties(AccessTools.all) .Where(property => syncValueType.IsAssignableFrom(property.PropertyType) && (!(property.GetGetMethod()?.IsStatic ?? true))); + foreach (var field in syncValueFields) + syncValueAccessorsByName.Add(field.Name, (TSyncObject instance) => (TSyncValue)field.GetValue(instance)); + foreach (var property in syncValueProperties) - propertyAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); + { + var fieldName = $"<{property.Name}>"; + var potentialField = syncValueAccessorsByName.FirstOrDefault(entry => entry.Key.Contains(fieldName)); + var hasIgnoreAttribute = property.GetCustomAttribute() is null; + + if (potentialField.Value is not null) + { + syncValueAccessorsByName.Remove(potentialField.Key); - // Replace this with a special MonkeySyncMethod type that takes an action as the target - // The invoke method can then be overridden to for example set the user field that triggers the method. - var syncMethods = typeof(TSyncObject).GetMethods(AccessTools.all) - .Where(MonkeySyncMethodAttribute.IsValid); + if (!hasIgnoreAttribute) + syncValueAccessorsByName.Add(property.Name, potentialField.Value); - foreach (var method in syncMethods) - methodInfosByName.Add(method.Name, method); + continue; + } + + if (!hasIgnoreAttribute) + syncValueAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); + } } /// @@ -157,9 +172,6 @@ protected MonkeySyncObject() { if (GetType() != typeof(TSyncObject)) throw new InvalidOperationException("TSyncObject must be the concrete Type being instantiated!"); - - foreach (var methodEntry in methodInfosByName) - methodsByName.Add(methodEntry.Key, AccessTools.MethodDelegate(methodEntry.Value, this)); } /// @@ -196,12 +208,9 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// /// By default: Sets up the event handlers /// and calls EstablishLinkFor - /// for every readable instance property and - /// its overload for every - /// MonkeySync method on . + /// for every instance field / property on . /// - /// The detected properties are stored in propertyAccessorsByName, - /// while the detected methods are stored in methodsByName. + /// The detected fields / properties are stored in syncValueAccessorsByName. /// /// This method is called by LinkWith /// after the LinkObject has been assigned.
@@ -214,7 +223,7 @@ protected virtual bool EstablishLink(bool fromRemote) { var success = true; - foreach (var syncValueProperty in propertyAccessorsByName) + foreach (var syncValueProperty in syncValueAccessorsByName) { var syncValue = syncValueProperty.Value((TSyncObject)this); @@ -224,9 +233,6 @@ protected virtual bool EstablishLink(bool fromRemote) success &= EstablishLinkFor(syncValue, syncValueProperty.Key, fromRemote); } - foreach (var syncMethod in methodsByName) - success &= EstablishLinkFor(syncMethod.Value, syncMethod.Key, fromRemote); - return success; } @@ -312,7 +318,7 @@ protected void OnLinkInvalidated() /// event with the given . ///
/// - /// This is automatically called for any properties. + /// This is automatically called for any fields / properties. /// /// The name of the property that changed. protected void OnPropertyChanged(string propertyName) @@ -324,11 +330,8 @@ protected void OnPropertyChanged(string propertyName) /// /// By default: Calls TryRestoreLinkFor - /// for every readable instance property and - /// its overload for every - /// MonkeySync method on .
- /// The detected properties are stored in propertyAccessorsByName, - /// while the detected methods are stored in methodsByName. + /// for every instance field on .
+ /// The detected fields / properties are stored in syncValueAccessorsByName. ///
/// This method is called by OnLinkInvalidated /// if IsLinkValid has become false.
@@ -341,12 +344,9 @@ protected virtual bool TryRestoreLink() { var success = true; - foreach (var syncValues in propertyAccessorsByName.Values) + foreach (var syncValues in syncValueAccessorsByName.Values) success &= TryRestoreLinkFor(syncValues((TSyncObject)this)); - foreach (var syncMethod in methodsByName) - success &= TryRestoreLinkFor(syncMethod.Value, syncMethod.Key); - return success; } @@ -362,14 +362,6 @@ protected virtual bool TryRestoreLink() protected virtual bool TryRestoreLinkFor(TSyncValue syncValue) => syncValue.TryRestoreLink(); - /// - /// Tries to restore the link for the given sync method of the given name. - /// - /// The sync method to link. - /// The name of the sync method to link. - /// true if the link was successfully restored; otherwise, false. - protected abstract bool TryRestoreLinkFor(Action syncMethod, string methodName); - private void Dispose(bool disposing) { if (_disposedValue) diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index ba2f06e..50f25d6 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -52,7 +52,7 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDispo /// /// Defines the generic interface for linked s. /// - /// The type of the link object used by the sync object. + /// The type of the link object used by the sync object that this sync value links to. /// The type of the Value. public interface ILinkedMonkeySyncValue : INotifyValueChanged, IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue @@ -86,10 +86,10 @@ public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue /// Establishes this sync value's association and link through the given sync object. ///
/// The sync object that this value belongs to. - /// The property name of this sync value. + /// The name of this sync value. /// Whether the link is being established from the remote side. /// true if the established link is valid; otherwise, false. - public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote); + public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string name, bool fromRemote); } /// @@ -153,7 +153,7 @@ public virtual T Value public Type ValueType => _valueType; /// - /// Creates a new sync object instance that wraps the given . + /// Creates a new sync value instance that wraps the given . /// /// The value to wrap. public MonkeySyncValue(T value) From 8d831b1010aa635e6c8e637d30ceda6ce022075f Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 8 Mar 2025 19:29:12 +0100 Subject: [PATCH 19/22] Add SyncObject type parameter to sync values --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs | 4 +- ...onkeySyncMethod.cs => MonkeySyncMethod.cs} | 21 +++++--- MonkeyLoader/Sync/MonkeySyncObject.cs | 10 ++-- MonkeyLoader/Sync/MonkeySyncValue.cs | 49 +++++++++++-------- 5 files changed, 49 insertions(+), 37 deletions(-) rename MonkeyLoader/Sync/{IMonkeySyncMethod.cs => MonkeySyncMethod.cs} (72%) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 6c5ae36..6f84a74 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.11-MonkeySync-beta + 0.24.12-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs index 9aac0ee..d70ed20 100644 --- a/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs +++ b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs @@ -3,8 +3,8 @@ namespace MonkeyLoader.Sync { /// - /// Marks a field or property with a compatible type - /// on a class deriving from + /// Marks a field or property with a compatible type + /// on a class deriving from /// to be excluded from the automatically detected fields and properties. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] diff --git a/MonkeyLoader/Sync/IMonkeySyncMethod.cs b/MonkeyLoader/Sync/MonkeySyncMethod.cs similarity index 72% rename from MonkeyLoader/Sync/IMonkeySyncMethod.cs rename to MonkeyLoader/Sync/MonkeySyncMethod.cs index 7969774..9954442 100644 --- a/MonkeyLoader/Sync/IMonkeySyncMethod.cs +++ b/MonkeyLoader/Sync/MonkeySyncMethod.cs @@ -20,16 +20,19 @@ namespace MonkeyLoader.Sync /// Defines the non-generic interface for s. /// /// - public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncValue + public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { } /// - /// Defines the generic interface for linked s. + /// Defines the generic interface for linked s. /// /// The type of the link object used by the sync object that this sync value links to. - /// The type of the Value that can trigger this sync method. - public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, ILinkedMonkeySyncValue + /// The type of the sync object that may contain this sync value. + /// The type of the Value that can trigger this sync method. + public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, ILinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { /// /// Gets the delegate that can be triggered by this sync method. @@ -38,10 +41,11 @@ public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncMethod } /// - /// Defines the interface for not yet linked s. + /// Defines the interface for not yet linked s. /// /// - public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, IUnlinkedMonkeySyncValue + public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, IUnlinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { } /// @@ -52,8 +56,9 @@ public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySyncMethod /// - public abstract class MonkeySyncMethod : MonkeySyncValue, - IUnlinkedMonkeySyncMethod, ILinkedMonkeySyncMethod + public abstract class MonkeySyncMethod : MonkeySyncValue, + IUnlinkedMonkeySyncMethod, ILinkedMonkeySyncMethod + where TSyncObject : class, ILinkedMonkeySyncObject { /// public MonkeySyncFunc Function { get; } diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index b4ca178..14726c2 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -90,13 +90,13 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject /// The concrete type of the MonkeySync object. /// - /// The -derived interface + /// The -derived interface /// that the MonkeySync values of this object must implement. /// /// The type of the link object used by the sync object. public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject where TSyncObject : MonkeySyncObject - where TSyncValue : IUnlinkedMonkeySyncValue + where TSyncValue : IUnlinkedMonkeySyncValue where TLink : class { /// @@ -241,7 +241,7 @@ protected virtual bool EstablishLink(bool fromRemote) /// /// /// By default: Adds the given sync value to the set of instances and calls - /// .EstablishLinkFor(…). + /// .EstablishLinkFor(…). /// /// The sync value to link. /// The name of the sync value to link. @@ -250,7 +250,7 @@ protected virtual bool EstablishLink(bool fromRemote) protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyName, bool fromRemote) { syncValues.Add(syncValue); - return syncValue.EstablishLinkFor(this, propertyName, fromRemote); + return syncValue.EstablishLinkFor((TSyncObject)this, propertyName, fromRemote); } /// @@ -355,7 +355,7 @@ protected virtual bool TryRestoreLink() /// /// /// By default: Calls - /// .TryRestoreLink(). + /// .TryRestoreLink(). /// /// The sync value to link. /// true if the link was successfully restored; otherwise, false. diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index 50f25d6..947a663 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -13,11 +13,12 @@ namespace MonkeyLoader.Sync /// Defines the non-generic interface for s. /// /// - public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDisposable + public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDisposable + where TSyncObject : ILinkedMonkeySyncObject { /// /// Gets the LinkObject - /// of the SyncObject that this value belongs to. + /// of the SyncObject that this value belongs to. /// public TLink LinkObject { get; } @@ -29,7 +30,7 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDispo /// /// Gets the sync object that this value belongs to. /// - public ILinkedMonkeySyncObject SyncObject { get; } + public TSyncObject SyncObject { get; } /// /// Gets or sets the internal value of this sync value. @@ -50,25 +51,28 @@ public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDispo } /// - /// Defines the generic interface for linked s. + /// Defines the generic interface for linked s. /// /// The type of the link object used by the sync object that this sync value links to. - /// The type of the Value. - public interface ILinkedMonkeySyncValue : INotifyValueChanged, - IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue + /// The type of the sync object that may contain this sync value. + /// The type of the Value. + public interface ILinkedMonkeySyncValue : INotifyValueChanged, + IReadOnlyMonkeySyncValue, IWriteOnlyMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { - /// + /// public new T Value { get; set; } } /// - /// Defines the interface for readonly s. + /// Defines the interface for readonly s. /// /// /// This interface exists purely to facilitate keeping a covariant list of sync values. /// /// - public interface IReadOnlyMonkeySyncValue : ILinkedMonkeySyncValue + public interface IReadOnlyMonkeySyncValue : ILinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { /// /// Gets the internal value of this sync value. @@ -77,10 +81,11 @@ public interface IReadOnlyMonkeySyncValue : ILinkedMonkeySyncV } /// - /// Defines the interface for not yet linked s. + /// Defines the interface for not yet linked s. /// /// - public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue + public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { /// /// Establishes this sync value's association and link through the given sync object. @@ -89,17 +94,18 @@ public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue /// The name of this sync value. /// Whether the link is being established from the remote side. /// true if the established link is valid; otherwise, false. - public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string name, bool fromRemote); + public bool EstablishLinkFor(TSyncObject syncObject, string name, bool fromRemote); } /// - /// Defines the interface for writeonly s. + /// Defines the interface for writeonly s. /// /// /// This interface exists purely to facilitate keeping a contravariant list of sync values. /// /// - public interface IWriteOnlyMonkeySyncValue : ILinkedMonkeySyncValue + public interface IWriteOnlyMonkeySyncValue : ILinkedMonkeySyncValue + where TSyncObject : ILinkedMonkeySyncObject { /// /// Sets the internal value of this sync value. @@ -111,7 +117,8 @@ public interface IWriteOnlyMonkeySyncValue : ILinkedMonkeySyncV /// Implements an abstract base for s. /// /// - public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue, ILinkedMonkeySyncValue + public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue, ILinkedMonkeySyncValue + where TSyncObject : class, ILinkedMonkeySyncObject { private static readonly Type _valueType = typeof(T); @@ -126,7 +133,7 @@ public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue - public ILinkedMonkeySyncObject SyncObject { get; private set; } = null!; + public TSyncObject SyncObject { get; private set; } = null!; /// public virtual T Value @@ -143,7 +150,7 @@ public virtual T Value } } - object? ILinkedMonkeySyncValue.Value + object? ILinkedMonkeySyncValue.Value { get => Value; set => Value = (T)value!; @@ -173,7 +180,7 @@ public MonkeySyncValue(T value) /// Unwraps the Value from the given sync object. /// /// The sync object to unwrap. - public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; /// public void Dispose() @@ -189,7 +196,7 @@ public void Dispose() /// Then calls the internal link method. /// /// - public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string propertyName, bool fromRemote) + public bool EstablishLinkFor(TSyncObject syncObject, string propertyName, bool fromRemote) { SyncObject = syncObject; Name = propertyName; @@ -210,7 +217,7 @@ public bool EstablishLinkFor(ILinkedMonkeySyncObject syncObject, string p /// have already been assigned by the EstablishLinkFor /// method that calls this. /// - /// + /// protected abstract bool EstablishLinkInternal(bool fromRemote); /// From d2850a3aeb60aa85e3285e92ae115c36aeb26cf4 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sat, 8 Mar 2025 22:11:51 +0100 Subject: [PATCH 20/22] More generics make everything better... right? --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs | 4 +- MonkeyLoader/Sync/MonkeySyncMethod.cs | 22 +++-- MonkeyLoader/Sync/MonkeySyncObject.cs | 94 +++++++++---------- MonkeyLoader/Sync/MonkeySyncValue.cs | 34 ++++--- 5 files changed, 82 insertions(+), 74 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index 6f84a74..fe26000 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.12-MonkeySync-beta + 0.24.13-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs index d70ed20..8fd5cd0 100644 --- a/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs +++ b/MonkeyLoader/Sync/IgnoreSyncValueAttribute.cs @@ -3,8 +3,8 @@ namespace MonkeyLoader.Sync { /// - /// Marks a field or property with a compatible type - /// on a class deriving from + /// Marks a field or property with a compatible type + /// on a class deriving from /// to be excluded from the automatically detected fields and properties. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] diff --git a/MonkeyLoader/Sync/MonkeySyncMethod.cs b/MonkeyLoader/Sync/MonkeySyncMethod.cs index 9954442..56789a3 100644 --- a/MonkeyLoader/Sync/MonkeySyncMethod.cs +++ b/MonkeyLoader/Sync/MonkeySyncMethod.cs @@ -19,14 +19,14 @@ namespace MonkeyLoader.Sync /// /// Defines the non-generic interface for s. /// - /// + /// public interface ILinkedMonkeySyncMethod : ILinkedMonkeySyncValue where TSyncObject : ILinkedMonkeySyncObject { } /// - /// Defines the generic interface for linked s. + /// Defines the generic interface for linked s. /// /// The type of the link object used by the sync object that this sync value links to. /// The type of the sync object that may contain this sync value. @@ -41,11 +41,12 @@ public interface ILinkedMonkeySyncMethod : ILinke } /// - /// Defines the interface for not yet linked s. + /// Defines the interface for not yet linked s. /// - /// - public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySyncMethod, IUnlinkedMonkeySyncValue + /// + public interface IUnlinkedMonkeySyncMethod : IUnlinkedMonkeySyncValue where TSyncObject : ILinkedMonkeySyncObject + where TLinkedSyncValue : ILinkedMonkeySyncValue { } /// @@ -55,10 +56,12 @@ public interface IUnlinkedMonkeySyncMethod : ILinkedMonkeySy /// This class is in all likelihood not useful to actually derive from.
/// It mainly serves as an example for how an implementation could look. /// - /// - public abstract class MonkeySyncMethod : MonkeySyncValue, - IUnlinkedMonkeySyncMethod, ILinkedMonkeySyncMethod + /// + public abstract class MonkeySyncMethod : MonkeySyncValue, + IUnlinkedMonkeySyncMethod, + ILinkedMonkeySyncMethod where TSyncObject : class, ILinkedMonkeySyncObject + where TSyncValue : MonkeySyncMethod { /// public MonkeySyncFunc Function { get; } @@ -84,6 +87,9 @@ protected MonkeySyncMethod(MonkeySyncAction action, T value) : this(_ => action(), value) { } + TSyncValue? IUnlinkedMonkeySyncValue.EstablishLinkFor(TSyncObject syncObject, string name, bool fromRemote) + => EstablishLinkFor(syncObject, name, fromRemote); + /// /// Creates structures that make this method triggerable by others locally or in shared environments, /// whether they have the mod implementing the method or not. diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index 14726c2..d921705 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -83,36 +83,41 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject /// /// Automatically detects any instance fields / properties containing - /// MonkeySync values or methods compatible with .
+ /// MonkeySync values or methods compatible with .
/// Property names take precedence over their backing fields.
/// Use the to mark fields / properties that should be ignored. /// If a property is marked, its backing field will be ignored too. ///
/// The concrete type of the MonkeySync object. - /// - /// The -derived interface - /// that the MonkeySync values of this object must implement. + /// + /// The -derived interface + /// that the unlinked MonkeySync values of this object must implement. + /// + /// + /// The -derived interface + /// that the linked MonkeySync values of this object must implement. /// /// The type of the link object used by the sync object. - public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject - where TSyncObject : MonkeySyncObject - where TSyncValue : IUnlinkedMonkeySyncValue + public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject + where TSyncObject : MonkeySyncObject + where TLinkedSyncValue : ILinkedMonkeySyncValue + where TUnlinkedSyncValue : IUnlinkedMonkeySyncValue where TLink : class { /// - /// The getters for the automatically detected instance fields / properties by their name. + /// The getters for the automatically detected instance fields / properties by their name. /// /// /// Property names take precedence over their backing fields.
/// Use the to mark fields / properties that should be ignored. /// If a property is marked, its backing field will be ignored too. ///
- protected static readonly Dictionary> syncValueAccessorsByName = new(StringComparer.Ordinal); + protected static readonly Dictionary> syncValueAccessorsByName = new(StringComparer.Ordinal); /// - /// The instances associated with this sync object. + /// The instances associated with this sync object. /// - protected readonly HashSet syncValues = []; + protected readonly HashSet syncValues = []; private bool _disposedValue; @@ -131,7 +136,7 @@ public abstract class MonkeySyncObject : IUnlink static MonkeySyncObject() { - var syncValueType = typeof(TSyncValue); + var syncValueType = typeof(TUnlinkedSyncValue); var syncValueFields = typeof(TSyncObject).GetFields(AccessTools.all) .Where(field => !field.IsStatic && syncValueType.IsAssignableFrom(field.FieldType) && field.GetCustomAttribute() is null); @@ -139,7 +144,7 @@ static MonkeySyncObject() .Where(property => syncValueType.IsAssignableFrom(property.PropertyType) && (!(property.GetGetMethod()?.IsStatic ?? true))); foreach (var field in syncValueFields) - syncValueAccessorsByName.Add(field.Name, (TSyncObject instance) => (TSyncValue)field.GetValue(instance)); + syncValueAccessorsByName.Add(field.Name, (TSyncObject instance) => (TUnlinkedSyncValue)field.GetValue(instance)); foreach (var property in syncValueProperties) { @@ -158,7 +163,7 @@ static MonkeySyncObject() } if (!hasIgnoreAttribute) - syncValueAccessorsByName.Add(property.Name, (TSyncObject instance) => (TSyncValue)property.GetValue(instance)); + syncValueAccessorsByName.Add(property.Name, (TSyncObject instance) => (TUnlinkedSyncValue)property.GetValue(instance)); } } @@ -207,8 +212,8 @@ public bool LinkWith(TLink linkObject, bool fromRemote = false) /// /// By default: Sets up the event handlers - /// and calls EstablishLinkFor - /// for every instance field / property on . + /// and calls EstablishLinkFor + /// for every instance field / property on . /// /// The detected fields / properties are stored in syncValueAccessorsByName. /// @@ -223,14 +228,11 @@ protected virtual bool EstablishLink(bool fromRemote) { var success = true; - foreach (var syncValueProperty in syncValueAccessorsByName) + foreach (var syncValueAccessor in syncValueAccessorsByName) { - var syncValue = syncValueProperty.Value((TSyncObject)this); - - syncValue.Changed += (sender, changedArgs) - => OnPropertyChanged(syncValueProperty.Key); + var syncValue = syncValueAccessor.Value((TSyncObject)this); - success &= EstablishLinkFor(syncValue, syncValueProperty.Key, fromRemote); + success &= EstablishLinkFor(syncValue, syncValueAccessor.Key, fromRemote); } return success; @@ -241,36 +243,33 @@ protected virtual bool EstablishLink(bool fromRemote) ///
/// /// By default: Adds the given sync value to the set of instances and calls - /// .EstablishLinkFor(…). + /// .EstablishLinkFor(…).
+ /// If successful, the handler is subscribed to + /// the event of the linked value as well. ///
- /// The sync value to link. + /// The sync value to link. /// The name of the sync value to link. /// Whether the link is being established from the remote side. /// true if the link was successfully created; otherwise, false. - protected virtual bool EstablishLinkFor(TSyncValue syncValue, string propertyName, bool fromRemote) + protected virtual bool EstablishLinkFor(TUnlinkedSyncValue unlinkedSyncValue, string propertyName, bool fromRemote) { - syncValues.Add(syncValue); - return syncValue.EstablishLinkFor((TSyncObject)this, propertyName, fromRemote); - } + var linkedValue = unlinkedSyncValue.EstablishLinkFor((TSyncObject)this, propertyName, fromRemote); - /// - /// Creates a link for the given sync method of the given name. - /// - /// - /// Any s created for this - /// must be added to the set of instances. - /// - /// The sync method to link. - /// The name of the sync method to link. - /// Whether the link is being established from the remote side. - /// true if the link was successfully created; otherwise, false. - protected abstract bool EstablishLinkFor(Action syncMethod, string methodName, bool fromRemote); + if (linkedValue is null) + return false; + + syncValues.Add(linkedValue); + linkedValue.Changed += (sender, changedArgs) + => OnPropertyChanged(propertyName); + + return true; + } /// /// Cleans up any managed resources as part of disposing. /// /// - /// By default: Disposes all instances + /// By default: Disposes all instances /// that were added to the set of instances, /// and the LinkObject if it's . /// @@ -318,7 +317,7 @@ protected void OnLinkInvalidated() /// event with the given . ///
/// - /// This is automatically called for any fields / properties. + /// This is automatically called for any fields / properties. /// /// The name of the property that changed. protected void OnPropertyChanged(string propertyName) @@ -329,9 +328,8 @@ protected void OnPropertyChanged(string propertyName) } /// - /// By default: Calls TryRestoreLinkFor - /// for every instance field on .
- /// The detected fields / properties are stored in syncValueAccessorsByName. + /// By default: Calls TryRestoreLinkFor + /// for every in . ///
/// This method is called by OnLinkInvalidated /// if IsLinkValid has become false.
@@ -344,8 +342,8 @@ protected virtual bool TryRestoreLink() { var success = true; - foreach (var syncValues in syncValueAccessorsByName.Values) - success &= TryRestoreLinkFor(syncValues((TSyncObject)this)); + foreach (var syncValue in syncValues) + success &= TryRestoreLinkFor(syncValue); return success; } @@ -359,7 +357,7 @@ protected virtual bool TryRestoreLink() ///
/// The sync value to link. /// true if the link was successfully restored; otherwise, false. - protected virtual bool TryRestoreLinkFor(TSyncValue syncValue) + protected virtual bool TryRestoreLinkFor(TLinkedSyncValue syncValue) => syncValue.TryRestoreLink(); private void Dispose(bool disposing) diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index 947a663..aa99fec 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -12,12 +12,12 @@ namespace MonkeyLoader.Sync /// /// Defines the non-generic interface for s. /// - /// + /// public interface ILinkedMonkeySyncValue : INotifyValueChanged, IDisposable where TSyncObject : ILinkedMonkeySyncObject { /// - /// Gets the LinkObject + /// Gets the LinkObject /// of the SyncObject that this value belongs to. /// public TLink LinkObject { get; } @@ -70,7 +70,7 @@ public interface ILinkedMonkeySyncValue : INotify /// /// This interface exists purely to facilitate keeping a covariant list of sync values. /// - /// + /// public interface IReadOnlyMonkeySyncValue : ILinkedMonkeySyncValue where TSyncObject : ILinkedMonkeySyncObject { @@ -81,11 +81,12 @@ public interface IReadOnlyMonkeySyncValue : I } /// - /// Defines the interface for not yet linked s. + /// Defines the interface for not yet linked s. /// - /// - public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyncValue + /// + public interface IUnlinkedMonkeySyncValue where TSyncObject : ILinkedMonkeySyncObject + where TLinkedSyncValue : ILinkedMonkeySyncValue { /// /// Establishes this sync value's association and link through the given sync object. @@ -93,8 +94,8 @@ public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyn /// The sync object that this value belongs to. /// The name of this sync value. /// Whether the link is being established from the remote side. - /// true if the established link is valid; otherwise, false. - public bool EstablishLinkFor(TSyncObject syncObject, string name, bool fromRemote); + /// The linked sync value if the established link is valid; otherwise, null. + public TLinkedSyncValue? EstablishLinkFor(TSyncObject syncObject, string name, bool fromRemote); } /// @@ -103,7 +104,7 @@ public interface IUnlinkedMonkeySyncValue : ILinkedMonkeySyn /// /// This interface exists purely to facilitate keeping a contravariant list of sync values. /// - /// + /// public interface IWriteOnlyMonkeySyncValue : ILinkedMonkeySyncValue where TSyncObject : ILinkedMonkeySyncObject { @@ -116,9 +117,12 @@ public interface IWriteOnlyMonkeySyncValue : I /// /// Implements an abstract base for s. /// - /// - public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue, ILinkedMonkeySyncValue + /// + public abstract class MonkeySyncValue + : IUnlinkedMonkeySyncValue, + ILinkedMonkeySyncValue where TSyncObject : class, ILinkedMonkeySyncObject + where TSyncValue : MonkeySyncValue { private static readonly Type _valueType = typeof(T); @@ -180,7 +184,7 @@ public MonkeySyncValue(T value) /// Unwraps the Value from the given sync object. /// /// The sync object to unwrap. - public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; + public static implicit operator T(MonkeySyncValue syncValue) => syncValue.Value; /// public void Dispose() @@ -196,12 +200,12 @@ public void Dispose() /// Then calls the internal link method. /// /// - public bool EstablishLinkFor(TSyncObject syncObject, string propertyName, bool fromRemote) + public TSyncValue? EstablishLinkFor(TSyncObject syncObject, string propertyName, bool fromRemote) { SyncObject = syncObject; Name = propertyName; - return EstablishLinkInternal(fromRemote); + return (TSyncValue?)(EstablishLinkInternal(fromRemote) ? this : null); } /// @@ -217,7 +221,7 @@ public bool EstablishLinkFor(TSyncObject syncObject, string propertyName, bool f /// have already been assigned by the EstablishLinkFor /// method that calls this. /// - /// + /// protected abstract bool EstablishLinkInternal(bool fromRemote); /// From 390e365b35acb124b5e05ef1c5437f59132ab11f Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 9 Mar 2025 12:26:43 +0100 Subject: [PATCH 21/22] Tweak generic signature of SyncObject --- MonkeyLoader/MonkeyLoader.csproj | 2 +- MonkeyLoader/Sync/MonkeySyncObject.cs | 10 +++++----- MonkeyLoader/Sync/MonkeySyncValue.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MonkeyLoader/MonkeyLoader.csproj b/MonkeyLoader/MonkeyLoader.csproj index fe26000..c1df3f2 100644 --- a/MonkeyLoader/MonkeyLoader.csproj +++ b/MonkeyLoader/MonkeyLoader.csproj @@ -11,7 +11,7 @@ True MonkeyLoader Banane9 - 0.24.13-MonkeySync-beta + 0.24.14-MonkeySync-beta A convenience and extendability focused mod loader using NuGet packages. README.md LGPL-3.0-or-later diff --git a/MonkeyLoader/Sync/MonkeySyncObject.cs b/MonkeyLoader/Sync/MonkeySyncObject.cs index d921705..76418b9 100644 --- a/MonkeyLoader/Sync/MonkeySyncObject.cs +++ b/MonkeyLoader/Sync/MonkeySyncObject.cs @@ -22,7 +22,7 @@ public delegate IUnlinkedMonkeySyncObject SyncObjectFactory() /// /// Defines the generic interface for MonkeySync objects that have been linked. + /// TUnlinkedSyncValue, TLinkedSyncValue, TLink}">MonkeySync objects that have been linked. /// /// The type of the link object used by the sync object. public interface ILinkedMonkeySyncObject : IMonkeySyncObject @@ -33,7 +33,7 @@ public interface ILinkedMonkeySyncObject : IMonkeySyncObject /// /// Defines the non-generic interface for MonkeySync objects. + /// TUnlinkedSyncValue, TLinkedSyncValue, TLink}">MonkeySync objects. /// public interface IMonkeySyncObject : INotifyPropertyChanged, IDisposable { @@ -57,7 +57,7 @@ public interface IMonkeySyncObject : INotifyPropertyChanged, IDisposable /// /// Defines the generic interface for MonkeySync objects that are yet to be linked. + /// TUnlinkedSyncValue, TLinkedSyncValue, TLink}">MonkeySync objects that are yet to be linked. /// /// public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObject @@ -100,8 +100,8 @@ public interface IUnlinkedMonkeySyncObject : ILinkedMonkeySyncObjectThe type of the link object used by the sync object. public abstract class MonkeySyncObject : IUnlinkedMonkeySyncObject where TSyncObject : MonkeySyncObject - where TLinkedSyncValue : ILinkedMonkeySyncValue - where TUnlinkedSyncValue : IUnlinkedMonkeySyncValue + where TLinkedSyncValue : ILinkedMonkeySyncValue> + where TUnlinkedSyncValue : IUnlinkedMonkeySyncValue, TLinkedSyncValue> where TLink : class { /// diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index aa99fec..2473263 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -51,7 +51,7 @@ public interface ILinkedMonkeySyncValue : INotifyVal } /// - /// Defines the generic interface for linked s. + /// Defines the generic interface for linked s. /// /// The type of the link object used by the sync object that this sync value links to. /// The type of the sync object that may contain this sync value. From c7e7ce90480aa527c2aa9144ddb3e081bfaf039d Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Sun, 9 Mar 2025 21:02:15 +0100 Subject: [PATCH 22/22] Fix typeparam docs --- MonkeyLoader/Sync/MonkeySyncValue.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MonkeyLoader/Sync/MonkeySyncValue.cs b/MonkeyLoader/Sync/MonkeySyncValue.cs index 2473263..e1d0b91 100644 --- a/MonkeyLoader/Sync/MonkeySyncValue.cs +++ b/MonkeyLoader/Sync/MonkeySyncValue.cs @@ -117,7 +117,10 @@ public interface IWriteOnlyMonkeySyncValue : I /// /// Implements an abstract base for s. /// - /// + /// The type of the link object used by the sync object that this sync value links to. + /// The type of the sync object that may contain this sync value. + /// The concrete type of the sync value. + /// The type of the Value. public abstract class MonkeySyncValue : IUnlinkedMonkeySyncValue, ILinkedMonkeySyncValue