// // 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; } } }