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