Skip to content

Commit edbcb61

Browse files
committed
Add bidirectional config key binding
1 parent a9066ad commit edbcb61

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace MonkeyLoader.Configuration
6+
{
7+
/// <summary>
8+
/// Represents the functionality for an <see cref="IDefiningConfigKey{T}"/>,
9+
/// which propagate any changes in value in both directions
10+
/// between the two associated config items.
11+
/// </summary>
12+
/// <inheritdoc cref="IConfigKeyBidirectionalBinding{T}"/>
13+
public sealed class ConfigKeyBidirectionalBinding<T> : IConfigKeyBidirectionalBinding<T>
14+
{
15+
/// <inheritdoc/>
16+
public IDefiningConfigKey<T> Owner { get; private set; } = null!;
17+
18+
IDefiningConfigKey IConfigKeyBidirectionalBinding.Owner => Owner;
19+
20+
/// <inheritdoc/>
21+
public IDefiningConfigKey<T> Target { get; }
22+
23+
IDefiningConfigKey IConfigKeyBidirectionalBinding.Target => Target;
24+
25+
/// <summary>
26+
/// Creates a new bidirectional binding targeting the given config item.
27+
/// </summary>
28+
/// <param name="target">The other config item to propagate changes to and from.</param>
29+
public ConfigKeyBidirectionalBinding(IDefiningConfigKey<T> target)
30+
{
31+
Target = target;
32+
}
33+
34+
/// <remarks>
35+
/// Adds the <see cref="IDefiningConfigKey{T}.Changed">Changed</see> event
36+
/// listeners to propagate changes between the linked config items.
37+
/// </remarks>
38+
/// <exception cref="InvalidOperationException">When the binding has already been initialized or is targeted at itself.</exception>
39+
/// <inheritdoc/>
40+
public void Initialize(IDefiningConfigKey<T> entity)
41+
{
42+
if (Owner is not null)
43+
throw new InvalidOperationException($"This binding targetting [{Target}] is already owned by [{Owner}]!");
44+
45+
if (ReferenceEquals(Target, entity))
46+
throw new InvalidOperationException($"Can't bind [{Target}] to itself!");
47+
48+
Owner = entity;
49+
50+
// Shouldn't need circular check because Changed event is only fired for actual changes
51+
Owner.Changed += (_, args) => Target.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalOwnerEventLabel));
52+
Target.Changed += (_, args) => Owner.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalTargetEventLabel));
53+
}
54+
}
55+
56+
/// <typeparam name="T">The type of the config item's value.</typeparam>
57+
/// <inheritdoc cref="IConfigKeyBidirectionalBinding"/>
58+
public interface IConfigKeyBidirectionalBinding<T> : IConfigKeyComponent<IDefiningConfigKey<T>>, IConfigKeyBidirectionalBinding
59+
{
60+
/// <inheritdoc cref="IConfigKeyBidirectionalBinding.Owner"/>
61+
public new IDefiningConfigKey<T> Owner { get; }
62+
63+
/// <inheritdoc cref="IConfigKeyBidirectionalBinding.Target"/>
64+
public new IDefiningConfigKey<T> Target { get; }
65+
}
66+
67+
/// <summary>
68+
/// Defines the interface for config key components,
69+
/// which propagate any changes in value in both directions
70+
/// between the two associated config items.
71+
/// </summary>
72+
public interface IConfigKeyBidirectionalBinding
73+
{
74+
/// <summary>
75+
/// Gets the config item that this component was initialized to.
76+
/// </summary>
77+
public IDefiningConfigKey Owner { get; }
78+
79+
/// <summary>
80+
/// Gets the config item that this binding targets.
81+
/// </summary>
82+
public IDefiningConfigKey Target { get; }
83+
}
84+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using MonkeyLoader.Components;
2+
using MonkeyLoader.Meta;
3+
4+
namespace MonkeyLoader.Configuration
5+
{
6+
/// <summary>
7+
/// Contains constants and extensions for <see cref="ConfigKeyBidirectionalBinding{T}"/>.
8+
/// </summary>
9+
public static class ConfigKeyBindings
10+
{
11+
/// <summary>
12+
/// The base event label used when a config item's value is set from a
13+
/// <see cref="IConfigKeyBidirectionalBinding{T}.Owner"/>'s changed value being propagated.
14+
/// </summary>
15+
/// <remarks>
16+
/// The actual event label will have the format:
17+
/// <c>BidirectionalBindingOwner:<see cref="IIdentifiable.FullId">TriggerFullId</see>:<see cref="IConfigKeyChangedEventArgs.Label">TriggerLabel</see></c>.
18+
/// </remarks>
19+
public const string SetFromBidirectionalOwnerEventLabel = "BidirectionalBindingOwner";
20+
21+
/// <summary>
22+
/// The base event label used when a config item's value is set from a
23+
/// <see cref="IConfigKeyBidirectionalBinding{T}.Target"/>'s changed value being propagated.
24+
/// </summary>
25+
/// <remarks>
26+
/// The actual event label will have the format:
27+
/// <c>BidirectionalBindingTarget:<see cref="IIdentifiable.FullId">TriggerFullId</see>:<see cref="IConfigKeyChangedEventArgs.Label">TriggerLabel</see></c>.
28+
/// </remarks>
29+
public const string SetFromBidirectionalTargetEventLabel = "BidirectionalBindingTarget";
30+
31+
/// <summary>
32+
/// Creates a new bidirectional binding that propagates changes between the two config items.
33+
/// </summary>
34+
/// <typeparam name="T">The type of the config item's value.</typeparam>
35+
/// <param name="owner">The config item that the component will be initialized to.</param>
36+
/// <param name="target">The config item to propagate changes to and from.</param>
37+
/// <returns>The newly created bidirectional binding component.</returns>
38+
public static IConfigKeyBidirectionalBinding<T> BindBidirectionallyTo<T>(this IDefiningConfigKey<T> owner, IDefiningConfigKey<T> target)
39+
{
40+
var binding = new ConfigKeyBidirectionalBinding<T>(target);
41+
42+
owner.Add(binding);
43+
44+
return binding;
45+
}
46+
}
47+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using MonkeyLoader.Meta;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Text;
5+
6+
namespace MonkeyLoader.Configuration
7+
{
8+
/// <summary>
9+
/// Contains extensions methods related to the config system.
10+
/// </summary>
11+
public static class ConfigSystemExtensions
12+
{
13+
/// <summary>
14+
/// Gets the <see cref="IConfigKeyChangedEventArgs.Label">Label</see> for
15+
/// a propagated <see cref="IDefiningConfigKey.Changed">Changed</see> event.<br/>
16+
/// The created event label will have this format:
17+
/// <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>.
18+
/// </summary>
19+
/// <param name="changedEventArgs">The changed event that triggered this propagation.</param>
20+
/// <param name="baseLabel">The new base label for the event.</param>
21+
/// <returns>The formatted label for the propagated event.</returns>
22+
public static string GetPropagatedEventLabel(this IConfigKeyChangedEventArgs changedEventArgs, string baseLabel)
23+
=> $"{baseLabel}:{changedEventArgs.Key.FullId}:{changedEventArgs.Label}";
24+
}
25+
}

0 commit comments

Comments
 (0)