Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config key bindings that propagate changed values #50

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions MonkeyLoader/Configuration/ConfigKeyBidirectionalBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace MonkeyLoader.Configuration
{
/// <summary>
/// Represents the functionality for an <see cref="IDefiningConfigKey{T}"/>,
/// which propagate any changes in value in both directions
/// between the two associated config items.
/// </summary>
/// <inheritdoc cref="IConfigKeyBidirectionalBinding{T}"/>
public sealed class ConfigKeyBidirectionalBinding<T> : IConfigKeyBidirectionalBinding<T>
{
/// <inheritdoc/>
public IDefiningConfigKey<T> Owner { get; private set; } = null!;

IDefiningConfigKey IConfigKeyBidirectionalBinding.Owner => Owner;

/// <inheritdoc/>
public IDefiningConfigKey<T> Target { get; }

IDefiningConfigKey IConfigKeyBidirectionalBinding.Target => Target;

/// <summary>
/// Creates a new bidirectional binding targeting the given config item.
/// </summary>
/// <param name="target">The other config item to propagate changes to and from.</param>
public ConfigKeyBidirectionalBinding(IDefiningConfigKey<T> target)
{
Target = target;
}

/// <remarks>
/// Adds the <see cref="IDefiningConfigKey{T}.Changed">Changed</see> event
/// listeners to propagate changes between the linked config items.
/// </remarks>
/// <exception cref="InvalidOperationException">When the binding has already been initialized or is targeted at itself.</exception>
/// <inheritdoc/>
public void Initialize(IDefiningConfigKey<T> entity)
{
if (Owner is not null)
throw new InvalidOperationException($"This binding targetting [{Target}] is already owned by [{Owner}]!");

if (ReferenceEquals(Target, entity))
throw new InvalidOperationException($"Can't bind [{Target}] to itself!");

Owner = entity;

// Shouldn't need circular check because Changed event is only fired for actual changes
Owner.Changed += (_, args) => Target.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalOwnerEventLabel));
Target.Changed += (_, args) => Owner.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalTargetEventLabel));
}
}

/// <typeparam name="T">The type of the config item's value.</typeparam>
/// <inheritdoc cref="IConfigKeyBidirectionalBinding"/>
public interface IConfigKeyBidirectionalBinding<T> : IConfigKeyComponent<IDefiningConfigKey<T>>, IConfigKeyBidirectionalBinding
{
/// <inheritdoc cref="IConfigKeyBidirectionalBinding.Owner"/>
public new IDefiningConfigKey<T> Owner { get; }

/// <inheritdoc cref="IConfigKeyBidirectionalBinding.Target"/>
public new IDefiningConfigKey<T> Target { get; }
}

/// <summary>
/// Defines the interface for config key components,
/// which propagate any changes in value in both directions
/// between the two associated config items.
/// </summary>
public interface IConfigKeyBidirectionalBinding
{
/// <summary>
/// Gets the config item that this component was initialized to.
/// </summary>
public IDefiningConfigKey Owner { get; }

/// <summary>
/// Gets the config item that this binding targets.
/// </summary>
public IDefiningConfigKey Target { get; }
}
}
47 changes: 47 additions & 0 deletions MonkeyLoader/Configuration/ConfigKeyBindings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using MonkeyLoader.Components;
using MonkeyLoader.Meta;

namespace MonkeyLoader.Configuration
{
/// <summary>
/// Contains constants and extensions for <see cref="ConfigKeyBidirectionalBinding{T}"/>.
/// </summary>
public static class ConfigKeyBindings
{
/// <summary>
/// The base event label used when a config item's value is set from a
/// <see cref="IConfigKeyBidirectionalBinding{T}.Owner"/>'s changed value being propagated.
/// </summary>
/// <remarks>
/// The actual event label will have the format:
/// <c>BidirectionalBindingOwner:<see cref="IIdentifiable.FullId">TriggerFullId</see>:<see cref="IConfigKeyChangedEventArgs.Label">TriggerLabel</see></c>.
/// </remarks>
public const string SetFromBidirectionalOwnerEventLabel = "BidirectionalBindingOwner";

/// <summary>
/// The base event label used when a config item's value is set from a
/// <see cref="IConfigKeyBidirectionalBinding{T}.Target"/>'s changed value being propagated.
/// </summary>
/// <remarks>
/// The actual event label will have the format:
/// <c>BidirectionalBindingTarget:<see cref="IIdentifiable.FullId">TriggerFullId</see>:<see cref="IConfigKeyChangedEventArgs.Label">TriggerLabel</see></c>.
/// </remarks>
public const string SetFromBidirectionalTargetEventLabel = "BidirectionalBindingTarget";

/// <summary>
/// Creates a new bidirectional binding that propagates changes between the two config items.
/// </summary>
/// <typeparam name="T">The type of the config item's value.</typeparam>
/// <param name="owner">The config item that the component will be initialized to.</param>
/// <param name="target">The config item to propagate changes to and from.</param>
/// <returns>The newly created bidirectional binding component.</returns>
public static IConfigKeyBidirectionalBinding<T> BindBidirectionallyTo<T>(this IDefiningConfigKey<T> owner, IDefiningConfigKey<T> target)
{
var binding = new ConfigKeyBidirectionalBinding<T>(target);

owner.Add(binding);

return binding;
}
}
}
25 changes: 25 additions & 0 deletions MonkeyLoader/Configuration/ConfigSystemExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using MonkeyLoader.Meta;
using System;
using System.Collections.Generic;
using System.Text;

namespace MonkeyLoader.Configuration
{
/// <summary>
/// Contains extensions methods related to the config system.
/// </summary>
public static class ConfigSystemExtensions
{
/// <summary>
/// Gets the <see cref="IConfigKeyChangedEventArgs.Label">Label</see> for
/// a propagated <see cref="IDefiningConfigKey.Changed">Changed</see> event.<br/>
/// The created event label will have this format:
/// <c>$"{<paramref name="baseLabel"/>}:{<paramref name="changedEventArgs"/>.<see cref="IConfigKeyChangedEventArgs.Key">Key</see>.<see cref="IIdentifiable.FullId">FullId</see>}:{<paramref name="changedEventArgs"/>.<see cref="IConfigKeyChangedEventArgs.Label">Label</see>}"</c>.
/// </summary>
/// <param name="changedEventArgs">The changed event that triggered this propagation.</param>
/// <param name="baseLabel">The new base label for the event.</param>
/// <returns>The formatted label for the propagated event.</returns>
public static string GetPropagatedEventLabel(this IConfigKeyChangedEventArgs changedEventArgs, string baseLabel)
=> $"{baseLabel}:{changedEventArgs.Key.FullId}:{changedEventArgs.Label}";
}
}
3 changes: 3 additions & 0 deletions MonkeyLoader/Configuration/DefiningConfigKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ void IDefiningConfigKey.SetValue(object? value, string? eventLabel)
throw new ArgumentException($"Tried to set key [{Id}] to invalid value!", nameof(value));
}

/// <inheritdoc/>
public override string ToString() => $"{Id} ({(_value is null ? "null" : _value.ToString())})";

/// <inheritdoc/>
bool IDefiningConfigKey.TryComputeDefault(out object? defaultValue)
{
Expand Down