Skip to content

Commit

Permalink
Merge pull request #169 from r-koubou/feature/event-managements
Browse files Browse the repository at this point in the history
feature/event-managements
  • Loading branch information
r-koubou authored Dec 1, 2024
2 parents 65f3c11 + aab4418 commit 2cd26dc
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 0 deletions.
23 changes: 23 additions & 0 deletions Compiler/Domain.Tests/Events/EventTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using KSPCompiler.Domain.Events;

using NUnit.Framework;

namespace KSPCompiler.Domain.Tests.Events;

[TestFixture]
public class EventTest
{
[Test]
public void DispatchTest()
{
var dispatcher = new EventDispatcher();
var observer = new MockIntEventObserver();
using var token = dispatcher.Subscribe( observer );

dispatcher.Dispatch( new MockIntEvent( 1 ) );
dispatcher.Dispatch( new MockIntEvent( 2 ) );
dispatcher.Dispatch( new MockIntEvent( 3 ) );

Assert.That( observer.total, Is.EqualTo( 6 ) );
}
}
8 changes: 8 additions & 0 deletions Compiler/Domain.Tests/Events/MockIntEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using KSPCompiler.Domain.Events;

namespace KSPCompiler.Domain.Tests.Events;

public class MockIntEvent( int value ) : IEvent
{
public int Value { get; } = value;
}
14 changes: 14 additions & 0 deletions Compiler/Domain.Tests/Events/MockIntEventObserver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace KSPCompiler.Domain.Tests.Events;

public class MockIntEventObserver : IObserver<MockIntEvent>
{
public int total = 0;
public void OnNext( MockIntEvent value )
{
total += value.Value;
}
public void OnCompleted() {}
public void OnError( Exception error ) {}
}
141 changes: 141 additions & 0 deletions Compiler/Domain/Events/EventDispatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;

namespace KSPCompiler.Domain.Events;

/// <summary>
/// Default implementation of <see cref="IEventDispatcher"/>.
/// </summary>
public sealed class EventDispatcher : IEventDispatcher
{
// object : DefaultEventDispatcher.EventObservable<TEvent>
// 任意のIObservable<T>型を出し入れしたいため
private readonly IDictionary<Type, object> registered = new Dictionary<Type, object>();

/// <inheritdoc />
public IDisposable Subscribe<TEvent>( IObserver<TEvent> observer ) where TEvent : IEvent
{
return AsObservable<TEvent>().Subscribe( observer );
}

/// <inheritdoc />
public void Dispatch<TEvent>( TEvent evt ) where TEvent : IEvent
{
if( TryGetObservable( out IObservable<TEvent> observable ) )
{
( (EventObservable<TEvent>)observable ).Publish( evt );
}
}

/// <inheritdoc />
public IObservable<TEvent> AsObservable<TEvent>() where TEvent : IEvent
{
if( TryGetObservable( out IObservable<TEvent> result ) )
{
return result;
}

result = new EventObservable<TEvent>();
registered.Add( typeof( TEvent ), result );

return result;
}

public void Dispose()
{
registered.Clear();
}

private bool TryGetObservable<TEvent>( out IObservable<TEvent> result ) where TEvent : IEvent
{
var type = typeof( TEvent );

result = NullObservable<TEvent>.Instance;

if( !registered.TryGetValue( type, out var value ) )
{
return false;
}

result = (IObservable<TEvent>)value;

return true;
}

#region IObservable Implementation

#region Null Observable

private class NullObservable<TEvent> : IObservable<TEvent> where TEvent : IEvent
{
public static readonly IObservable<TEvent> Instance = new NullObservable<TEvent>();

private NullObservable() {}

/// <inheritdoc />
public IDisposable Subscribe( IObserver<TEvent> observer )
{
return new NullDisposer();
}

private class NullDisposer : IDisposable
{
public void Dispose() {}
}
}

#endregion ~Null Observable

private class EventObservable<TEvent> : IObservable<TEvent> where TEvent : IEvent
{
private readonly List<IObserver<TEvent>> subscribers = [];

#region IObservable

/// <inheritdoc />
public IDisposable Subscribe( IObserver<TEvent> observer )
{
subscribers.Add( observer );

return new AnonymousDisposer( () =>
{
observer.OnCompleted();
subscribers.Remove( observer );
}
);
}

#endregion ~IObservable

public void Publish( TEvent evt )
{
foreach( var subscriber in subscribers )
{
try
{
subscriber.OnNext( evt );
}
catch( Exception e )
{
subscriber.OnError( e );
}
}
}
}
#endregion ~IObservable Implementation

#region Disposer

private class AnonymousDisposer : IDisposable
{
private readonly Action dispose;

public AnonymousDisposer( Action dispose )
=> this.dispose = dispose;

public void Dispose()
=> dispose();
}

#endregion ~Disposer
}
34 changes: 34 additions & 0 deletions Compiler/Domain/Events/Extensions/IEventDispatcherExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;

namespace KSPCompiler.Domain.Events.Extensions;

public static class IEventDispatcherExtension
{
public static IDisposable Subscribe<TEvent>( this IEventDispatcher self, Action<TEvent> action ) where TEvent : IEvent
{
return self.Subscribe( new AnonymousObserver<TEvent>( action ) );
}

private class AnonymousObserver<T> : IObserver<T> where T : IEvent
{
private readonly Action<T> onNext;
private readonly Action? onCompleted;
private readonly Action<Exception>? onError;

public AnonymousObserver( Action<T> onNext, Action? onCompleted = null, Action<Exception>? onError = null )
{
this.onNext = onNext;
this.onCompleted = onCompleted;
this.onError = onError;
}

public void OnNext( T value )
=> onNext.Invoke( value );

public void OnCompleted()
=> onCompleted?.Invoke();

public void OnError( Exception error )
=> onError?.Invoke( error );
}
}
6 changes: 6 additions & 0 deletions Compiler/Domain/Events/IEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace KSPCompiler.Domain.Events;

/// <summary>
/// The base interface for configuring events.
/// </summary>
public interface IEvent {}
13 changes: 13 additions & 0 deletions Compiler/Domain/Events/IEventDispatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace KSPCompiler.Domain.Events;

/// <summary>
/// The basic interface for managing event subscribers and building event transmission management and execution.
/// </summary>
public interface IEventDispatcher : IDisposable
{
IDisposable Subscribe<TEvent>( IObserver<TEvent> observer ) where TEvent : IEvent;
void Dispatch<TEvent>( TEvent evt ) where TEvent : IEvent;
IObservable<TEvent> AsObservable<TEvent>() where TEvent : IEvent;
}

0 comments on commit 2cd26dc

Please sign in to comment.