Files
AdamuSw/src/GameLogic/ConfigurationChangeMediator.cs
2026-07-14 19:00:35 +03:00

230 lines
7.5 KiB
C#

// <copyright file="ConfigurationChangeMediator.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic;
using System.Collections.Concurrent;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A mediator which notifies about configuration changes for registered instances.
/// </summary>
public class ConfigurationChangeMediator : IConfigurationChangeMediator, IConfigurationChangeMediatorListener
{
private readonly ConcurrentDictionary<Guid, IChangeRegistration> _registrations = new();
private readonly ConcurrentDictionary<Type, ICreateRegistration> _createRegistrations = new();
/// <summary>
/// A non-generic interface for the change-registration.
/// </summary>
private interface IChangeRegistration : IDisposable
{
/// <summary>
/// Raises the <see cref="ChangeRegistration{TConfig}"/> event.
/// </summary>
ValueTask RaiseOnChangeAsync();
/// <summary>
/// Raises the <see cref="ChangeRegistration{TConfig}"/> event.
/// </summary>
ValueTask RaiseOnDeleteAsync();
}
/// <summary>
/// An non-generic interface for the <see cref="CreateRegistration{TConfig}"/>.
/// </summary>
/// <seealso cref="System.IDisposable" />
private interface ICreateRegistration : IDisposable
{
/// <summary>
/// Raises the <see cref="ChangeRegistration{TConfig}"/> event.
/// </summary>
/// <param name="created">The created configuration.</param>
ValueTask RaiseOnCreateAsync(object created);
}
/// <inheritdoc />
public IDisposable RegisterObject<TConfig, T>(TConfig config, T obj, Func<Action, TConfig, T, ValueTask>? onChange = null, Func<TConfig, T, ValueTask>? onDelete = null)
where T : class
where TConfig : class
{
var registration = (ChangeRegistration<TConfig>)this._registrations.AddOrUpdate(
config.GetId(),
_ => new ChangeRegistration<TConfig>(config),
(_, value) => value);
if (onChange is not null)
{
registration.OnChange += InvokeOnChangeAsync;
}
if (onDelete is not null)
{
registration.OnDelete += InvokeOnDeleteAsync;
}
var disposable = new Nito.Disposables.Disposable(() =>
{
if (onChange is not null)
{
registration.OnChange -= InvokeOnChangeAsync;
}
if (onDelete is not null)
{
registration.OnDelete -= InvokeOnDeleteAsync;
}
});
return disposable;
async ValueTask InvokeOnChangeAsync(TConfig changedConfig)
{
await onChange(
() =>
{
registration.OnChange -= InvokeOnChangeAsync;
if (onDelete is not null)
{
registration.OnDelete -= InvokeOnDeleteAsync;
}
},
changedConfig,
obj).ConfigureAwait(false);
}
async ValueTask InvokeOnDeleteAsync(TConfig changedConfig)
{
await onDelete(changedConfig, obj).ConfigureAwait(false);
}
}
/// <inheritdoc />
public IDisposable RegisterForNew<TConfig, T>(T obj, Func<TConfig, T, ValueTask> onNewConfig)
{
var registration = (CreateRegistration<TConfig>)this._createRegistrations.AddOrUpdate(
typeof(TConfig),
_ => new CreateRegistration<TConfig>(),
(_, value) => value);
registration.OnCreate += InvokeOnCreateAsync;
return registration;
async ValueTask InvokeOnCreateAsync(TConfig config)
{
await onNewConfig(config, obj).ConfigureAwait(false);
}
}
/// <inheritdoc />
public async ValueTask HandleConfigurationChangedAsync(Type type, Guid id, object configuration)
{
if (this._registrations.TryGetValue(id, out var registration))
{
await registration.RaiseOnChangeAsync().ConfigureAwait(false);
}
}
/// <inheritdoc />
public async ValueTask HandleConfigurationAddedAsync(Type type, Guid id, object configuration)
{
if (this._createRegistrations.TryGetValue(type, out var createRegistration)
|| this._createRegistrations.TryGetValue(type.BaseType!, out createRegistration))
{
await createRegistration.RaiseOnCreateAsync(configuration).ConfigureAwait(false);
}
}
/// <inheritdoc />
public async ValueTask HandleConfigurationRemovedAsync(Type type, Guid id)
{
if (this._registrations.Remove(id, out var registration))
{
await registration.RaiseOnDeleteAsync().ConfigureAwait(false);
registration.Dispose();
}
}
/// <summary>
/// A registration for a change of a configuration.
/// </summary>
/// <typeparam name="TConfig">The type of the configuration.</typeparam>
private class ChangeRegistration<TConfig> : Disposable, IChangeRegistration
where TConfig : class
{
/// <summary>
/// Initializes a new instance of the <see cref="ChangeRegistration{TConfig}"/> class.
/// </summary>
/// <param name="config">The configuration.</param>
public ChangeRegistration(TConfig config)
{
this.Configuration = config;
}
/// <summary>
/// Occurs when a config has been changed.
/// </summary>
public event AsyncEventHandler<TConfig>? OnChange;
/// <summary>
/// Occurs when a config has been deleted.
/// </summary>
public event AsyncEventHandler<TConfig>? OnDelete;
/// <summary>
/// Gets the configuration in which the registration is interested in.
/// </summary>
private TConfig Configuration { get; }
/// <inheritdoc />
public async ValueTask RaiseOnChangeAsync()
{
await this.OnChange.SafeInvokeAsync(this.Configuration).ConfigureAwait(false);
}
/// <inheritdoc />
public async ValueTask RaiseOnDeleteAsync()
{
await this.OnDelete.SafeInvokeAsync(this.Configuration).ConfigureAwait(false);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.OnDelete = null;
this.OnChange = null;
}
}
/// <summary>
/// A registration for a creation of a configuration.
/// </summary>
/// <typeparam name="TConfig">The type of the configuration.</typeparam>
private class CreateRegistration<TConfig> : Disposable, ICreateRegistration
{
/// <summary>
/// Occurs when a new config of <typeparamref name="TConfig"/> is created.
/// </summary>
public event AsyncEventHandler<TConfig>? OnCreate;
/// <inheritdoc />
public async ValueTask RaiseOnCreateAsync(object created)
{
if (created is not TConfig typed)
{
throw new InvalidOperationException("created object is of wrong type");
}
await this.OnCreate.SafeInvokeAsync(typed).ConfigureAwait(false);
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.OnCreate = null;
}
}
}