diff --git a/README.md b/README.md index fd6c5c1b..097ff7aa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # R3 -The evolution of [dotnet/reactive](https://github.com/dotnet/reactive/) and [UniRx](https://github.com/neuecc/UniRx), which support many platforms including Unity, Godot, Avalonia, WPF, etc(planning MAUI, Stride, LogicLooper). +The evolution of [dotnet/reactive](https://github.com/dotnet/reactive/) and [UniRx](https://github.com/neuecc/UniRx), which support many platforms including [Unity](https://unity.com/), [Godot](https://godotengine.org/), [Avalonia](https://avaloniaui.net/), WPF, etc(planning MAUI, [Stride](https://www.stride3d.net/), [LogicLooper](https://github.com/Cysharp/LogicLooper)). > [!NOTE] > This project is currently in preview. We are seeking a lot of feedback. We are considering fundamental changes such as [changing the name of the library (Uni(fied)Rx)](https://github.com/Cysharp/R3/issues/9) or [reverting back to the use of `IObservable`](https://github.com/Cysharp/R3/issues/10) and others, if you have any opinions, please post them in the [Issues](https://github.com/Cysharp/R3/issues). @@ -18,10 +18,38 @@ I have over 10 years of experience with Rx, experience in implementing a custom In other words, LINQ is not for EveryThing, and we believe that the essence of Rx lies in the processing of in-memory messaging (LINQ to Events), which will be our focus. Our main intended uses are UI frameworks and game engines, and we are not concerned with communication processes like [Reactive Streams](https://www.reactive-streams.org/). -To address the shortcomings of dotnet/reactive, we have made changes to the core interfaces, so R3 might appear to be an imitation of Rx. However, in recent years, Rx-like frameworks optimized for language features, such as [Kotlin Flow](https://kotlinlang.org/docs/flow.html) and [Swift Combine](https://developer.apple.com/documentation/combine), have been standardized. C# has also evolved significantly, now at C# 12, and we believe there is a need for an Rx that aligns with the latest C#. +To address the shortcomings of dotnet/reactive, we have made changes to the core interfaces. In recent years, Rx-like frameworks optimized for language features, such as [Kotlin Flow](https://kotlinlang.org/docs/flow.html) and [Swift Combine](https://developer.apple.com/documentation/combine), have been standardized. C# has also evolved significantly, now at C# 12, and we believe there is a need for an Rx that aligns with the latest C#. + +> [!NOTE] +> TODO: mention about perfomrance and subscription tracker. Getting Started --- +This library is distributed via NuGet, supporting .NET Standard 2.0, .NET Standard 2.1, .NET 6(.NET 7) and .NET 8 or above. + +> PM> Install-Package [R3](https://www.nuget.org/packages/R3) + +Some platforms(WPF, Avalonia, Unity, Godot) requires additional step to install. Please see [Platform Supports](#platform-supports) section in below. + +R3 code is almostly same as standard Rx. Make the Observable via factory methods(Timer, Interval, FromEvent, Subject, etc...) and chain operator via LINQ methods. Therefore, your knowledge about Rx and documentation on Rx can be almost directly applied. + +```csharp +using R3; + +var subscription = Observable.Interval(TimeSpan.FromSeconds(1)) + .Select((_, i) => i) + .Where(x => x % 2 == 0) + .Subscribe(x => Console.WriteLine($"Interval:{x}")); + +var cts = new CancellationTokenSource(); +_ = Task.Run(() => { Console.ReadLine(); cts.Cancel(); }); + +await Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3)) + .TakeUntil(cts.Token) + .ForEachAsync(x => Console.WriteLine($"Timer")); + +subscription.Dispose(); +``` @@ -38,6 +66,15 @@ Getting Started + + + + +---- + + + + ```csharp public abstract class Observable { @@ -57,7 +94,7 @@ public static Observable IntervalFrame(int periodFrame, FrameProvider fram - +`IObservable` being the dual of `IEnumerable` is a beautiful definition, but it was not very practical in use. @@ -107,7 +144,347 @@ lower supported version: Unity 2021.3 Operator Reference --- - +### Factory + +| Name(Parameter) | ReturnType | +| --- | --- | +| **Amb**(params `Observable[]` sources) | `Observable` | +| **Amb**(`IEnumerable>` sources) | `Observable` | +| **CombineLatest**(params `Observable[]` sources) | `Observable` | +| **CombineLatest**(`IEnumerable>` sources) | `Observable` | +| **Concat**(params `Observable[]` sources) | `Observable` | +| **Concat**(`IEnumerable>` sources) | `Observable` | +| **Create**(`Func, IDisposable>` subscribe) | `Observable` | +| **Create**(`TState` state, `Func, TState, IDisposable>` subscribe) | `Observable` | +| **Defer**(`Func>` observableFactory) | `Observable` | +| **Empty**() | `Observable` | +| **Empty**(`TimeProvider` timeProvider) | `Observable` | +| **Empty**(`TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **EveryUpdate**() | `Observable` | +| **EveryUpdate**(`CancellationToken` cancellationToken) | `Observable` | +| **EveryUpdate**(`FrameProvider` frameProvider) | `Observable` | +| **EveryUpdate**(`FrameProvider` frameProvider, `CancellationToken` cancellationToken) | `Observable` | +| **EveryValueChanged**(`TSource` source, `Func` propertySelector, `CancellationToken` cancellationToken = default) | `Observable` | +| **EveryValueChanged**(`TSource` source, `Func` propertySelector, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **EveryValueChanged**(`TSource` source, `Func` propertySelector, `EqualityComparer` equalityComparer, `CancellationToken` cancellationToken = default) | `Observable` | +| **EveryValueChanged**(`TSource` source, `Func` propertySelector, `FrameProvider` frameProvider, `EqualityComparer` equalityComparer, `CancellationToken` cancellationToken = default) | `Observable` | +| **FromEvent**(`Action` addHandler, `Action` removeHandler, `CancellationToken` cancellationToken = default) | `Observable` | +| **FromEvent**(`Action>` addHandler, `Action>` removeHandler, `CancellationToken` cancellationToken = default) | `Observable` | +| **FromEvent**(`Func` conversion, `Action` addHandler, `Action` removeHandler, `CancellationToken` cancellationToken = default) | `Observable` | +| **FromEvent**(`Func, TDelegate>` conversion, `Action` addHandler, `Action` removeHandler, `CancellationToken` cancellationToken = default) | `Observable` | +| **FromEventHandler**(`Action` addHandler, `Action` removeHandler, `CancellationToken` cancellationToken = default) | `Observable>` | +| **FromEventHandler**(`Action>` addHandler, `Action>` removeHandler, `CancellationToken` cancellationToken = default) | `Observable>` | +| **Interval**(`TimeSpan` period, `CancellationToken` cancellationToken = default) | `Observable` | +| **Interval**(`TimeSpan` period, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **IntervalFrame**(`Int32` periodFrame, `CancellationToken` cancellationToken = default) | `Observable` | +| **IntervalFrame**(`Int32` periodFrame, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Merge**(params `Observable[]` sources) | `Observable` | +| **Merge**(`IEnumerable>` sources) | `Observable` | +| **Never**() | `Observable` | +| **NextFrame**(`CancellationToken` cancellationToken = default) | `Observable` | +| **NextFrame**(`FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Range**(`Int32` start, `Int32` count) | `Observable` | +| **Range**(`Int32` start, `Int32` count, `CancellationToken` cancellationToken) | `Observable` | +| **Repeat**(`T` value, `Int32` count) | `Observable` | +| **Repeat**(`T` value, `Int32` count, `CancellationToken` cancellationToken) | `Observable` | +| **Return**(`T` value) | `Observable` | +| **Return**(`T` value, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Return**(`T` value, `TimeSpan` dueTime, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Return**(`Unit` value) | `Observable` | +| **Return**(`Boolean` value) | `Observable` | +| **Return**(`Int32` value) | `Observable` | +| **ReturnFrame**(`T` value, `CancellationToken` cancellationToken = default) | `Observable` | +| **ReturnFrame**(`T` value, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **ReturnFrame**(`T` value, `Int32` dueTimeFrame, `CancellationToken` cancellationToken = default) | `Observable` | +| **ReturnFrame**(`T` value, `Int32` dueTimeFrame, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **ReturnOnCompleted**(`Result` result) | `Observable` | +| **ReturnOnCompleted**(`Result` result, `TimeProvider` timeProvider) | `Observable` | +| **ReturnOnCompleted**(`Result` result, `TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **ReturnUnit**() | `Observable` | +| **Throw**(`Exception` exception) | `Observable` | +| **Throw**(`Exception` exception, `TimeProvider` timeProvider) | `Observable` | +| **Throw**(`Exception` exception, `TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **Timer**(`TimeSpan` dueTime, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`DateTimeOffset` dueTime, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`TimeSpan` dueTime, `TimeSpan` period, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`DateTimeOffset` dueTime, `TimeSpan` period, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`TimeSpan` dueTime, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`DateTimeOffset` dueTime, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`TimeSpan` dueTime, `TimeSpan` period, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Timer**(`DateTimeOffset` dueTime, `TimeSpan` period, `TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **TimerFrame**(`Int32` dueTimeFrame, `CancellationToken` cancellationToken = default) | `Observable` | +| **TimerFrame**(`Int32` dueTimeFrame, `Int32` periodFrame, `CancellationToken` cancellationToken = default) | `Observable` | +| **TimerFrame**(`Int32` dueTimeFrame, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **TimerFrame**(`Int32` dueTimeFrame, `Int32` periodFrame, `FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **ToObservable**(this `Task` task) | `Observable` | +| **ToObservable**(this `IEnumerable` source, `CancellationToken` cancellationToken = default) | `Observable` | +| **ToObservable**(this `IAsyncEnumerable` source) | `Observable` | +| **ToObservable**(this `IObservable` source) | `Observable` | +| **Yield**(`CancellationToken` cancellationToken = default) | `Observable` | +| **Yield**(`TimeProvider` timeProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **YieldFrame**(`CancellationToken` cancellationToken = default) | `Observable` | +| **YieldFrame**(`FrameProvider` frameProvider, `CancellationToken` cancellationToken = default) | `Observable` | +| **Zip**(params `Observable[]` sources) | `Observable` | +| **Zip**(`IEnumerable>` sources) | `Observable` | +| **ZipLatest**(params `Observable[]` sources) | `Observable` | +| **ZipLatest**(`IEnumerable>` sources) | `Observable` | + +### Operator + +| Name(Parameter) | ReturnType | +| --- | --- | +| **AggregateAsync**(this `Observable` source, `TAccumulate` seed, `Func` func, `Func` resultSelector, `CancellationToken` cancellationToken = default) | `Task` | +| **AllAsync**(this `Observable` source, `Func` predicate, `CancellationToken` cancellationToken = default) | `Task` | +| **Amb**(this `Observable` source, `Observable` second) | `Observable` | +| **AnyAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **AnyAsync**(this `Observable` source, `Func` predicate, `CancellationToken` cancellationToken = default) | `Task` | +| **Append**(this `Observable` source, `T` value) | `Observable` | +| **AsIObservable**(this `Observable` source) | `IObservable` | +| **AsObservable**(this `Observable` source) | `Observable` | +| **AsUnitObservable**(this `Observable` source) | `Observable` | +| **AverageAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **CancelOnCompleted**(this `Observable` source, `CancellationTokenSource` cancellationTokenSource) | `Observable` | +| **Cast**(this `Observable` source) | `Observable` | +| **Catch**(this `Observable` source, `Observable` second) | `Observable` | +| **Catch**(this `Observable` source, `Func>` errorHandler) | `Observable` | +| **Chunk**(this `Observable` source, `Int32` count) | `Observable` | +| **Chunk**(this `Observable` source, `TimeSpan` timeSpan) | `Observable` | +| **Chunk**(this `Observable` source, `TimeSpan` timeSpan, `TimeProvider` timeProvider) | `Observable` | +| **Chunk**(this `Observable` source, `TimeSpan` timeSpan, `Int32` count) | `Observable` | +| **Chunk**(this `Observable` source, `TimeSpan` timeSpan, `Int32` count, `TimeProvider` timeProvider) | `Observable` | +| **Chunk**(this `Observable` source, `Observable` windowBoundaries) | `Observable` | +| **ChunkFrame**(this `Observable` source) | `Observable` | +| **ChunkFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **ChunkFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **ChunkFrame**(this `Observable` source, `Int32` frameCount, `Int32` count) | `Observable` | +| **ChunkFrame**(this `Observable` source, `Int32` frameCount, `Int32` count, `FrameProvider` frameProvider) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Func` resultSelector) | `Observable` | +| **CombineLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Observable` source15, `Func` resultSelector) | `Observable` | +| **Concat**(this `Observable` source, `Observable` second) | `Observable` | +| **ContainsAsync**(this `Observable` source, `T` value, `CancellationToken` cancellationToken = default) | `Task` | +| **ContainsAsync**(this `Observable` source, `T` value, `IEqualityComparer` equalityComparer, `CancellationToken` cancellationToken = default) | `Task` | +| **CountAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **Debounce**(this `Observable` source, `TimeSpan` timeSpan) | `Observable` | +| **Debounce**(this `Observable` source, `TimeSpan` timeSpan, `TimeProvider` timeProvider) | `Observable` | +| **DebounceFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **DebounceFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **DefaultIfEmpty**(this `Observable` source) | `Observable` | +| **DefaultIfEmpty**(this `Observable` source, `T` defaultValue) | `Observable` | +| **Delay**(this `Observable` source, `TimeSpan` dueTime) | `Observable` | +| **Delay**(this `Observable` source, `TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **DelayFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **DelayFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **DelaySubscription**(this `Observable` source, `TimeSpan` dueTime) | `Observable` | +| **DelaySubscription**(this `Observable` source, `TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **DelaySubscriptionFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **DelaySubscriptionFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **Dematerialize**(this `Observable>` source) | `Observable` | +| **Distinct**(this `Observable` source) | `Observable` | +| **Distinct**(this `Observable` source, `IEqualityComparer` comparer) | `Observable` | +| **DistinctBy**(this `Observable` source, `Func` keySelector) | `Observable` | +| **DistinctBy**(this `Observable` source, `Func` keySelector, `IEqualityComparer` comparer) | `Observable` | +| **DistinctUntilChanged**(this `Observable` source) | `Observable` | +| **DistinctUntilChanged**(this `Observable` source, `IEqualityComparer` comparer) | `Observable` | +| **DistinctUntilChangedBy**(this `Observable` source, `Func` keySelector) | `Observable` | +| **DistinctUntilChangedBy**(this `Observable` source, `Func` keySelector, `IEqualityComparer` comparer) | `Observable` | +| **Do**(this `Observable` source, `Action` onNext = default, `Action` onErrorResume = default, `Action` onCompleted = default, `Action` onDispose = default, `Action` onSubscribe = default) | `Observable` | +| **Do**(this `Observable` source, `TState` state, `Action` onNext = default, `Action` onErrorResume = default, `Action` onCompleted = default, `Action` onDispose = default, `Action` onSubscribe = default) | `Observable` | +| **ElementAtAsync**(this `Observable` source, `Int32` index, `CancellationToken` cancellationToken = default) | `Task` | +| **ElementAtAsync**(this `Observable` source, `Index` index, `CancellationToken` cancellationToken = default) | `Task` | +| **ElementAtOrDefaultAsync**(this `Observable` source, `Int32` index, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **ElementAtOrDefaultAsync**(this `Observable` source, `Index` index, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **FirstAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **FirstAsync**(this `Observable` source, `Func` predicate, `CancellationToken` cancellationToken = default) | `Task` | +| **FirstOrDefaultAsync**(this `Observable` source, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **FirstOrDefaultAsync**(this `Observable` source, `Func` predicate, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **ForEachAsync**(this `Observable` source, `Action` action, `CancellationToken` cancellationToken = default) | `Task` | +| **ForEachAsync**(this `Observable` source, `Action` action, `CancellationToken` cancellationToken = default) | `Task` | +| **IgnoreElements**(this `Observable` source) | `Observable` | +| **IgnoreElements**(this `Observable` source, `Action` doOnNext) | `Observable` | +| **IgnoreOnErrorResume**(this `Observable` source) | `Observable` | +| **IgnoreOnErrorResume**(this `Observable` source, `Action` doOnErrorResume) | `Observable` | +| **IsEmptyAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **LastAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **LastAsync**(this `Observable` source, `Func` predicate, `CancellationToken` cancellationToken = default) | `Task` | +| **LastOrDefaultAsync**(this `Observable` source, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **LastOrDefaultAsync**(this `Observable` source, `Func` predicate, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **LongCountAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **Materialize**(this `Observable` source) | `Observable>` | +| **MaxAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **MaxByAsync**(this `Observable` source, `Func` keySelector, `CancellationToken` cancellationToken = default) | `Task` | +| **MaxByAsync**(this `Observable` source, `Func` keySelector, `IComparer` comparer, `CancellationToken` cancellationToken = default) | `Task` | +| **Merge**(this `Observable` source, `Observable` second) | `Observable` | +| **MinAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **MinByAsync**(this `Observable` source, `Func` keySelector, `CancellationToken` cancellationToken = default) | `Task` | +| **MinByAsync**(this `Observable` source, `Func` keySelector, `IComparer` comparer, `CancellationToken` cancellationToken = default) | `Task` | +| **MinMaxAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task>` | +| **Multicast**(this `Observable` source, `ISubject` subject) | `ConnectableObservable` | +| **ObserveOn**(this `Observable` source, `SynchronizationContext` synchronizationContext) | `Observable` | +| **ObserveOn**(this `Observable` source, `TimeProvider` timeProvider) | `Observable` | +| **ObserveOn**(this `Observable` source, `FrameProvider` frameProvider) | `Observable` | +| **ObserveOnCurrentSynchronizationContext**(this `Observable` source) | `Observable` | +| **ObserveOnThreadPool**(this `Observable` source) | `Observable` | +| **OfType**(this `Observable` source) | `Observable` | +| **OnErrorResumeAsFailure**(this `Observable` source) | `Observable` | +| **Pairwise**(this `Observable` source) | `Observable>` | +| **Prepend**(this `Observable` source, `T` value) | `Observable` | +| **Publish**(this `Observable` source) | `ConnectableObservable` | +| **Publish**(this `Observable` source, `T` initialValue) | `ConnectableObservable` | +| **RefCount**(this `ConnectableObservable` source) | `Observable` | +| **Replay**(this `Observable` source) | `ConnectableObservable` | +| **Replay**(this `Observable` source, `Int32` bufferSize) | `ConnectableObservable` | +| **Replay**(this `Observable` source, `TimeSpan` window) | `ConnectableObservable` | +| **Replay**(this `Observable` source, `TimeSpan` window, `TimeProvider` timeProvider) | `ConnectableObservable` | +| **Replay**(this `Observable` source, `Int32` bufferSize, `TimeSpan` window) | `ConnectableObservable` | +| **Replay**(this `Observable` source, `Int32` bufferSize, `TimeSpan` window, `TimeProvider` timeProvider) | `ConnectableObservable` | +| **ReplayFrame**(this `Observable` source, `Int32` window) | `ConnectableObservable` | +| **ReplayFrame**(this `Observable` source, `Int32` window, `FrameProvider` frameProvider) | `ConnectableObservable` | +| **ReplayFrame**(this `Observable` source, `Int32` bufferSize, `Int32` window) | `ConnectableObservable` | +| **ReplayFrame**(this `Observable` source, `Int32` bufferSize, `Int32` window, `FrameProvider` frameProvider) | `ConnectableObservable` | +| **Sample**(this `Observable` source, `TimeSpan` timeSpan) | `Observable` | +| **Sample**(this `Observable` source, `TimeSpan` timeSpan, `TimeProvider` timeProvider) | `Observable` | +| **SampleFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **SampleFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **Scan**(this `Observable` source, `Func` accumulator) | `Observable` | +| **Scan**(this `Observable` source, `TAccumulate` seed, `Func` accumulator) | `Observable` | +| **Select**(this `Observable` source, `Func` selector) | `Observable` | +| **Select**(this `Observable` source, `Func` selector) | `Observable` | +| **Select**(this `Observable` source, `TState` state, `Func` selector) | `Observable` | +| **Select**(this `Observable` source, `TState` state, `Func` selector) | `Observable` | +| **SelectMany**(this `Observable` source, `Func>` selector) | `Observable` | +| **SelectMany**(this `Observable` source, `Func>` collectionSelector, `Func` resultSelector) | `Observable` | +| **SelectMany**(this `Observable` source, `Func>` selector) | `Observable` | +| **SelectMany**(this `Observable` source, `Func>` collectionSelector, `Func` resultSelector) | `Observable` | +| **SequenceEqualAsync**(this `Observable` source, `Observable` second, `CancellationToken` cancellationToken = default) | `Task` | +| **SequenceEqualAsync**(this `Observable` source, `Observable` second, `IEqualityComparer` equalityComparer, `CancellationToken` cancellationToken = default) | `Task` | +| **Share**(this `Observable` source) | `Observable` | +| **SingleAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **SingleAsync**(this `Observable` source, `Func` predicate, `CancellationToken` cancellationToken = default) | `Task` | +| **SingleOrDefaultAsync**(this `Observable` source, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **SingleOrDefaultAsync**(this `Observable` source, `Func` predicate, `T` defaultValue = default, `CancellationToken` cancellationToken = default) | `Task` | +| **Skip**(this `Observable` source, `Int32` count) | `Observable` | +| **Skip**(this `Observable` source, `TimeSpan` duration) | `Observable` | +| **Skip**(this `Observable` source, `TimeSpan` duration, `TimeProvider` timeProvider) | `Observable` | +| **SkipFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **SkipFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **SkipLast**(this `Observable` source, `Int32` count) | `Observable` | +| **SkipLast**(this `Observable` source, `TimeSpan` duration) | `Observable` | +| **SkipLast**(this `Observable` source, `TimeSpan` duration, `TimeProvider` timeProvider) | `Observable` | +| **SkipLastFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **SkipLastFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **SkipUntil**(this `Observable` source, `Observable` other) | `Observable` | +| **SkipUntil**(this `Observable` source, `CancellationToken` cancellationToken) | `Observable` | +| **SkipUntil**(this `Observable` source, `Task` task) | `Observable` | +| **SkipWhile**(this `Observable` source, `Func` predicate) | `Observable` | +| **SkipWhile**(this `Observable` source, `Func` predicate) | `Observable` | +| **SubscribeOn**(this `Observable` source, `SynchronizationContext` synchronizationContext) | `Observable` | +| **SubscribeOn**(this `Observable` source, `TimeProvider` timeProvider) | `Observable` | +| **SubscribeOn**(this `Observable` source, `FrameProvider` frameProvider) | `Observable` | +| **SubscribeOnCurrentSynchronizationContext**(this `Observable` source) | `Observable` | +| **SubscribeOnThreadPool**(this `Observable` source) | `Observable` | +| **SumAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **Switch**(this `Observable>` sources) | `Observable` | +| **Synchronize**(this `Observable` source) | `Observable` | +| **Synchronize**(this `Observable` source, `Object` gate) | `Observable` | +| **Take**(this `Observable` source, `Int32` count) | `Observable` | +| **Take**(this `Observable` source, `TimeSpan` duration) | `Observable` | +| **Take**(this `Observable` source, `TimeSpan` duration, `TimeProvider` timeProvider) | `Observable` | +| **TakeFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **TakeFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **TakeLast**(this `Observable` source, `Int32` count) | `Observable` | +| **TakeLast**(this `Observable` source, `TimeSpan` duration) | `Observable` | +| **TakeLast**(this `Observable` source, `TimeSpan` duration, `TimeProvider` timeProvider) | `Observable` | +| **TakeLastFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **TakeLastFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **TakeUntil**(this `Observable` source, `Observable` other) | `Observable` | +| **TakeUntil**(this `Observable` source, `CancellationToken` cancellationToken) | `Observable` | +| **TakeUntil**(this `Observable` source, `Task` task) | `Observable` | +| **TakeWhile**(this `Observable` source, `Func` predicate) | `Observable` | +| **TakeWhile**(this `Observable` source, `Func` predicate) | `Observable` | +| **ThrottleFirst**(this `Observable` source, `TimeSpan` timeSpan) | `Observable` | +| **ThrottleFirst**(this `Observable` source, `TimeSpan` timeSpan, `TimeProvider` timeProvider) | `Observable` | +| **ThrottleFirstFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **ThrottleFirstFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **Timeout**(this `Observable` source, `TimeSpan` dueTime) | `Observable` | +| **Timeout**(this `Observable` source, `TimeSpan` dueTime, `TimeProvider` timeProvider) | `Observable` | +| **TimeoutFrame**(this `Observable` source, `Int32` frameCount) | `Observable` | +| **TimeoutFrame**(this `Observable` source, `Int32` frameCount, `FrameProvider` frameProvider) | `Observable` | +| **ToArrayAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **ToAsyncEnumerable**(this `Observable` source, `CancellationToken` cancellationToken = default) | `IAsyncEnumerable` | +| **ToDictionaryAsync**(this `Observable` source, `Func` keySelector, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToDictionaryAsync**(this `Observable` source, `Func` keySelector, `IEqualityComparer` keyComparer, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToDictionaryAsync**(this `Observable` source, `Func` keySelector, `Func` elementSelector, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToDictionaryAsync**(this `Observable` source, `Func` keySelector, `Func` elementSelector, `IEqualityComparer` keyComparer, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToHashSetAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToHashSetAsync**(this `Observable` source, `IEqualityComparer` equalityComparer, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToListAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToLiveList**(this `Observable` source) | `LiveList` | +| **ToLiveList**(this `Observable` source, `Int32` bufferSize) | `LiveList` | +| **ToLookupAsync**(this `Observable` source, `Func` keySelector, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToLookupAsync**(this `Observable` source, `Func` keySelector, `IEqualityComparer` keyComparer, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToLookupAsync**(this `Observable` source, `Func` keySelector, `Func` elementSelector, `CancellationToken` cancellationToken = default) | `Task>` | +| **ToLookupAsync**(this `Observable` source, `Func` keySelector, `Func` elementSelector, `IEqualityComparer` keyComparer, `CancellationToken` cancellationToken = default) | `Task>` | +| **WaitAsync**(this `Observable` source, `CancellationToken` cancellationToken = default) | `Task` | +| **Where**(this `Observable` source, `Func` predicate) | `Observable` | +| **Where**(this `Observable` source, `Func` predicate) | `Observable` | +| **Where**(this `Observable` source, `TState` state, `Func` predicate) | `Observable` | +| **Where**(this `Observable` source, `TState` state, `Func` predicate) | `Observable` | +| **WithLatestFrom**(this `Observable` first, `Observable` second, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Func` resultSelector) | `Observable` | +| **Zip**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Observable` source15, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Func` resultSelector) | `Observable` | +| **ZipLatest**(this `Observable` source1, `Observable` source2, `Observable` source3, `Observable` source4, `Observable` source5, `Observable` source6, `Observable` source7, `Observable` source8, `Observable` source9, `Observable` source10, `Observable` source11, `Observable` source12, `Observable` source13, `Observable` source14, `Observable` source15, `Func` resultSelector) | `Observable` | + + + +Class/Method name changes from dotnet/reactive and UniRx +--- +* `Buffer` -> `Chunk` +* `BatchFrame` -> `ChunkFrame` +* `Throttle` -> `Debounce` +* `ThrottleFrame` -> `DebounceFrame` +* `ObserveEveryValueChanged(this T value)` -> `Observable.EveryValueChanged(T value)` +* `Finally` -> `Do(onDisposed:)` +* `Do***` -> `Do(on***:)` +* `BehaviorSubject` -> `ReactiveProperty` +* `StableCompositeDisposable` -> `Disposable.Combine` +* `IScheduler` -> `TimeProvider` License --- diff --git a/sandbox/ConsoleApp1/ConsoleApp1.csproj b/sandbox/ConsoleApp1/ConsoleApp1.csproj index eba52b13..5bb45fc9 100644 --- a/sandbox/ConsoleApp1/ConsoleApp1.csproj +++ b/sandbox/ConsoleApp1/ConsoleApp1.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/sandbox/ConsoleApp1/Dump.cs b/sandbox/ConsoleApp1/Dump.cs new file mode 100644 index 00000000..aafcdcd6 --- /dev/null +++ b/sandbox/ConsoleApp1/Dump.cs @@ -0,0 +1,25 @@ +using MarkdownWikiGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ConsoleApp1; + +public static class Dump +{ + public static void Factory() + { + var emptyCommentTable = Enumerable.Empty().ToLookup(x => x, _ => new XmlDocumentComment()); + var t = new MarkdownableType(typeof(R3.Observable), emptyCommentTable); + Console.WriteLine(t.ToString()); + } + + public static void Operator() + { + var emptyCommentTable = Enumerable.Empty().ToLookup(x => x, _ => new XmlDocumentComment()); + var t = new MarkdownableType(typeof(R3.ObservableExtensions), emptyCommentTable); + Console.WriteLine(t.ToString()); + } +} diff --git a/sandbox/ConsoleApp1/MarkdownGenerator/Beautifier.cs b/sandbox/ConsoleApp1/MarkdownGenerator/Beautifier.cs new file mode 100644 index 00000000..18f3351c --- /dev/null +++ b/sandbox/ConsoleApp1/MarkdownGenerator/Beautifier.cs @@ -0,0 +1,76 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace MarkdownWikiGenerator +{ + public static class Beautifier + { + public static string BeautifyType(Type t, bool isFull = false) + { + if (t == null) return ""; + if (t == typeof(void)) return "void"; + if (t.IsArray) + { + var innerFormat = BeautifyType(t.GetElementType(), isFull); + return innerFormat + "[]"; + } + + if (!t.IsGenericType) return (isFull) ? t.FullName : t.Name; + + { + var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x))); + return Regex.Replace(isFull ? t.GetGenericTypeDefinition().FullName : t.GetGenericTypeDefinition().Name, @"`.+$", "") + "<" + innerFormat + ">"; + } + } + + public static string ToMarkdownMethodInfo(MethodInfo methodInfo) + { + var isExtension = IsExtensionMethod(methodInfo); + + var seq = methodInfo.GetParameters().Select(x => + { + var isParams = IsParamsParameter(x); + var refKind = IsOutParameter(x) ? "out " : IsRefParameter(x) ? "ref " : IsInParameter(x) ? "in " : ""; + var prefix = isParams ? "params " : refKind; + var suffix = x.HasDefaultValue ? (" = " + (x.DefaultValue ?? $"default")) : ""; + return prefix + "`" + BeautifyType(x.ParameterType) + "` " + x.Name + suffix; + }); + + // NOTE: modify ** + return "**" + methodInfo.Name + "**" + "(" + (isExtension ? "this " : "") + string.Join(", ", seq) + ")"; + } + + static bool IsExtensionMethod(MethodInfo method) + { + return method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false); + } + + static bool IsParamsParameter(ParameterInfo parameter) + { + return parameter.IsDefined(typeof(ParamArrayAttribute), false); + } + + static bool IsOutParameter(ParameterInfo parameter) + { + return parameter.IsOut; + } + + static bool IsRefParameter(ParameterInfo parameter) + { + return parameter.ParameterType.IsByRef && !parameter.IsOut; + } + + static bool IsInParameter(ParameterInfo parameter) + { + return parameter.IsIn && parameter.ParameterType.IsByRef; + } + } +} diff --git a/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownBuilder.cs b/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownBuilder.cs new file mode 100644 index 00000000..e9f862cd --- /dev/null +++ b/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownBuilder.cs @@ -0,0 +1,146 @@ +#nullable disable + +using System.Collections.Generic; +using System.Text; + +namespace MarkdownWikiGenerator +{ + public class MarkdownBuilder + { + public static string MarkdownCodeQuote(string code) + { + return "`" + code + "`"; + } + + + StringBuilder sb = new StringBuilder(); + + public void Append(string text) + { + sb.Append(text); + } + + public void AppendLine() + { + sb.AppendLine(); + } + + public void AppendLine(string text) + { + sb.AppendLine(text); + } + + public void Header(int level, string text) + { + for (int i = 0; i < level; i++) + { + sb.Append("#"); + } + sb.Append(" "); + sb.AppendLine(text); + } + + public void HeaderWithCode(int level, string code) + { + for (int i = 0; i < level; i++) + { + sb.Append("#"); + } + sb.Append(" "); + CodeQuote(code); + sb.AppendLine(); + } + + public void HeaderWithLink(int level, string text, string url) + { + for (int i = 0; i < level; i++) + { + sb.Append("#"); + } + sb.Append(" "); + Link(text, url); + sb.AppendLine(); + } + + public void Link(string text, string url) + { + sb.Append("["); + sb.Append(text); + sb.Append("]"); + sb.Append("("); + sb.Append(url); + sb.Append(")"); + } + + public void Image(string altText, string imageUrl) + { + sb.Append("!"); + Link(altText, imageUrl); + } + + public void Code(string language, string code) + { + sb.Append("```"); + sb.AppendLine(language); + sb.AppendLine(code); + sb.AppendLine("```"); + } + + public void CodeQuote(string code) + { + sb.Append("`"); + sb.Append(code); + sb.Append("`"); + } + + public void Table(string[] headers, IEnumerable items) + { + sb.Append("| "); + foreach (var item in headers) + { + sb.Append(item); + sb.Append(" | "); + } + sb.AppendLine(); + + sb.Append("| "); + foreach (var item in headers) + { + sb.Append("---"); + sb.Append(" | "); + } + sb.AppendLine(); + + + foreach (var item in items) + { + sb.Append("| "); + foreach (var item2 in item) + { + sb.Append(item2); + sb.Append(" | "); + } + sb.AppendLine(); + } + sb.AppendLine(); + } + + public void List(string text) // nest zero + { + sb.Append("- "); + sb.AppendLine(text); + } + + public void ListLink(string text, string url) // nest zero + { + sb.Append("- "); + Link(text, url); + sb.AppendLine(); + } + + public override string ToString() + { + return sb.ToString(); + } + } +} diff --git a/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownGenerator.cs b/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownGenerator.cs new file mode 100644 index 00000000..f7cf8035 --- /dev/null +++ b/sandbox/ConsoleApp1/MarkdownGenerator/MarkdownGenerator.cs @@ -0,0 +1,273 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace MarkdownWikiGenerator +{ + public class MarkdownableType + { + readonly Type type; + readonly ILookup commentLookup; + + public string Namespace => type.Namespace; + public string Name => type.Name; + public string BeautifyName => Beautifier.BeautifyType(type); + + public MarkdownableType(Type type, ILookup commentLookup) + { + this.type = type; + this.commentLookup = commentLookup; + } + + MethodInfo[] GetMethods() + { + return type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.InvokeMethod) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any() && !x.IsPrivate) + .ToArray(); + } + + PropertyInfo[] GetProperties() + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.GetProperty | BindingFlags.SetProperty) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any()) + .Where(y => + { + var get = y.GetGetMethod(true); + var set = y.GetSetMethod(true); + if (get != null && set != null) + { + return !(get.IsPrivate && set.IsPrivate); + } + else if (get != null) + { + return !get.IsPrivate; + } + else if (set != null) + { + return !set.IsPrivate; + } + else + { + return false; + } + }) + .ToArray(); + } + + FieldInfo[] GetFields() + { + return type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.GetField | BindingFlags.SetField) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any() && !x.IsPrivate) + .ToArray(); + } + + EventInfo[] GetEvents() + { + return type.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any()) + .ToArray(); + } + + FieldInfo[] GetStaticFields() + { + return type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.GetField | BindingFlags.SetField) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any() && !x.IsPrivate) + .ToArray(); + } + + PropertyInfo[] GetStaticProperties() + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.GetProperty | BindingFlags.SetProperty) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any()) + .Where(y => + { + var get = y.GetGetMethod(true); + var set = y.GetSetMethod(true); + if (get != null && set != null) + { + return !(get.IsPrivate && set.IsPrivate); + } + else if (get != null) + { + return !get.IsPrivate; + } + else if (set != null) + { + return !set.IsPrivate; + } + else + { + return false; + } + }) + .ToArray(); + } + + MethodInfo[] GetStaticMethods() + { + return type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.InvokeMethod) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any() && !x.IsPrivate) + .ToArray(); + } + + EventInfo[] GetStaticEvents() + { + return type.GetEvents(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) + .Where(x => !x.IsSpecialName && !x.GetCustomAttributes().Any()) + .ToArray(); + } + void BuildTable(MarkdownBuilder mb, string label, T[] array, IEnumerable docs, Func type, Func name, Func finalName) + { + if (array.Any()) + { + mb.AppendLine(label); + mb.AppendLine(); + + + // NOTE: configure, no summary, return is right + //string[] head = (this.type.IsEnum) + // ? new[] { "Value", "Name", "Summary" } + // : new[] { "Type", "Name", "Summary" }; + + string[] head = (this.type.IsEnum) + ? new[] { "Name", "Value" } + : new[] { "Name(Parameter)", "ReturnType" }; + + IEnumerable seq = array; + if (!this.type.IsEnum) + { + seq = array.OrderBy(x => name(x)); + } + + var data = seq.Select(item2 => + { + var summary = docs.FirstOrDefault(x => x.MemberName == name(item2) || x.MemberName.StartsWith(name(item2) + "`"))?.Summary ?? ""; + // NOTE: modify + // return new[] { MarkdownBuilder.MarkdownCodeQuote(type(item2)), finalName(item2), summary }; + return new[] { finalName(item2), MarkdownBuilder.MarkdownCodeQuote(type(item2)) }; + }); + + mb.Table(head, data); + mb.AppendLine(); + } + } + + public override string ToString() + { + var mb = new MarkdownBuilder(); + + mb.HeaderWithCode(2, Beautifier.BeautifyType(type, false)); + mb.AppendLine(); + + var desc = commentLookup[type.FullName].FirstOrDefault(x => x.MemberType == MemberType.Type)?.Summary ?? ""; + if (desc != "") + { + mb.AppendLine(desc); + } + { + var sb = new StringBuilder(); + + var stat = (type.IsAbstract && type.IsSealed) ? "static " : ""; + var abst = (type.IsAbstract && !type.IsInterface && !type.IsSealed) ? "abstract " : ""; + var classOrStructOrEnumOrInterface = type.IsInterface ? "interface" : type.IsEnum ? "enum" : type.IsValueType ? "struct" : "class"; + + sb.AppendLine($"public {stat}{abst}{classOrStructOrEnumOrInterface} {Beautifier.BeautifyType(type, true)}"); + var impl = string.Join(", ", new[] { type.BaseType }.Concat(type.GetInterfaces()).Where(x => x != null && x != typeof(object) && x != typeof(ValueType)).Select(x => Beautifier.BeautifyType(x))); + if (impl != "") + { + sb.AppendLine(" : " + impl); + } + + mb.Code("csharp", sb.ToString()); + } + + mb.AppendLine(); + + if (type.IsEnum) + { + var underlyingEnumType = Enum.GetUnderlyingType(type); + + var enums = Enum.GetNames(type) + .Select(x => new { Name = x, Value = (Convert.ChangeType(Enum.Parse(type, x), underlyingEnumType)) }) + .OrderBy(x => x.Value) + .ToArray(); + + BuildTable(mb, "Enum", enums, commentLookup[type.FullName], x => x.Value.ToString(), x => x.Name, x => x.Name); + } + else + { + BuildTable(mb, "Fields", GetFields(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.FieldType), x => x.Name, x => x.Name); + BuildTable(mb, "Properties", GetProperties(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.PropertyType), x => x.Name, x => x.Name); + BuildTable(mb, "Events", GetEvents(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.EventHandlerType), x => x.Name, x => x.Name); + BuildTable(mb, "Methods", GetMethods(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.ReturnType), x => x.Name, x => Beautifier.ToMarkdownMethodInfo(x)); + BuildTable(mb, "Static Fields", GetStaticFields(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.FieldType), x => x.Name, x => x.Name); + BuildTable(mb, "Static Properties", GetStaticProperties(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.PropertyType), x => x.Name, x => x.Name); + BuildTable(mb, "Static Methods", GetStaticMethods(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.ReturnType), x => x.Name, x => Beautifier.ToMarkdownMethodInfo(x)); + BuildTable(mb, "Static Events", GetStaticEvents(), commentLookup[type.FullName], x => Beautifier.BeautifyType(x.EventHandlerType), x => x.Name, x => x.Name); + } + + return mb.ToString(); + } + } + + + public static class MarkdownGenerator + { + public static MarkdownableType[] Load(string dllPath, string namespaceMatch) + { + var xmlPath = Path.Combine(Directory.GetParent(dllPath).FullName, Path.GetFileNameWithoutExtension(dllPath) + ".xml"); + + XmlDocumentComment[] comments = new XmlDocumentComment[0]; + if (File.Exists(xmlPath)) + { + comments = VSDocParser.ParseXmlComment(XDocument.Parse(File.ReadAllText(xmlPath)), namespaceMatch); + } + var commentsLookup = comments.ToLookup(x => x.ClassName); + + var namespaceRegex = + !string.IsNullOrEmpty(namespaceMatch) ? new Regex(namespaceMatch) : null; + + var markdownableTypes = new[] { Assembly.LoadFrom(dllPath) } + .SelectMany(x => + { + try + { + return x.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null); + } + catch + { + return Type.EmptyTypes; + } + }) + .Where(x => x != null) + .Where(x => x.IsPublic && !typeof(Delegate).IsAssignableFrom(x) && !x.GetCustomAttributes().Any()) + .Where(x => IsRequiredNamespace(x, namespaceRegex)) + .Select(x => new MarkdownableType(x, commentsLookup)) + .ToArray(); + + + return markdownableTypes; + } + + static bool IsRequiredNamespace(Type type, Regex regex) + { + if (regex == null) + { + return true; + } + return regex.IsMatch(type.Namespace != null ? type.Namespace : string.Empty); + } + } +} diff --git a/sandbox/ConsoleApp1/MarkdownGenerator/Program.cs b/sandbox/ConsoleApp1/MarkdownGenerator/Program.cs new file mode 100644 index 00000000..75b47b9c --- /dev/null +++ b/sandbox/ConsoleApp1/MarkdownGenerator/Program.cs @@ -0,0 +1,66 @@ +//using System; +//using System.Collections.Generic; +//using System.IO; +//using System.Linq; +//using System.Text; +//using System.Text.RegularExpressions; +//using System.Threading.Tasks; + +//namespace MarkdownWikiGenerator +//{ +// class Program +// { +// // 0 = dll src path, 1 = dest root +// static void Main(string[] args) +// { +// // put dll & xml on same diretory. +// var target = "UniRx.dll"; // :) +// string dest = "md"; +// string namespaceMatch = string.Empty; +// if (args.Length == 1) +// { +// target = args[0]; +// } +// else if (args.Length == 2) +// { +// target = args[0]; +// dest = args[1]; +// } +// else if (args.Length == 3) +// { +// target = args[0]; +// dest = args[1]; +// namespaceMatch = args[2]; +// } + +// var types = MarkdownGenerator.Load(target, namespaceMatch); + +// // Home Markdown Builder +// var homeBuilder = new MarkdownBuilder(); +// homeBuilder.Header(1, "References"); +// homeBuilder.AppendLine(); + +// foreach (var g in types.GroupBy(x => x.Namespace).OrderBy(x => x.Key)) +// { +// if (!Directory.Exists(dest)) Directory.CreateDirectory(dest); + +// homeBuilder.HeaderWithLink(2, g.Key, g.Key); +// homeBuilder.AppendLine(); + +// var sb = new StringBuilder(); +// foreach (var item in g.OrderBy(x => x.Name)) +// { +// homeBuilder.ListLink(MarkdownBuilder.MarkdownCodeQuote(item.BeautifyName), g.Key + "#" + item.BeautifyName.Replace("<", "").Replace(">", "").Replace(",", "").Replace(" ", "-").ToLower()); + +// sb.Append(item.ToString()); +// } + +// File.WriteAllText(Path.Combine(dest, g.Key + ".md"), sb.ToString()); +// homeBuilder.AppendLine(); +// } + +// // Gen Home +// File.WriteAllText(Path.Combine(dest, "Home.md"), homeBuilder.ToString()); +// } +// } +//} diff --git a/sandbox/ConsoleApp1/MarkdownGenerator/VSDocParser.cs b/sandbox/ConsoleApp1/MarkdownGenerator/VSDocParser.cs new file mode 100644 index 00000000..54e2bf44 --- /dev/null +++ b/sandbox/ConsoleApp1/MarkdownGenerator/VSDocParser.cs @@ -0,0 +1,121 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace MarkdownWikiGenerator +{ + public enum MemberType + { + Field = 'F', + Property = 'P', + Type = 'T', + Event = 'E', + Method = 'M', + None = 0 + } + + public class XmlDocumentComment + { + public MemberType MemberType { get; set; } + public string ClassName { get; set; } + public string MemberName { get; set; } + public string Summary { get; set; } + public string Remarks { get; set; } + public Dictionary Parameters { get; set; } + public string Returns { get; set; } + + public override string ToString() + { + return MemberType + ":" + ClassName + "." + MemberName; + } + } + + public static class VSDocParser + { + public static XmlDocumentComment[] ParseXmlComment(XDocument xDocument) { + return ParseXmlComment(xDocument, null); + } + + // cheap, quick hack parser:) + internal static XmlDocumentComment[] ParseXmlComment(XDocument xDocument, string namespaceMatch) { + + var assemblyName = xDocument.Descendants("assembly").First().Elements("name").First().Value; + + return xDocument.Descendants("member") + .Select(x => { + var match = Regex.Match(x.Attribute("name").Value, @"(.):(.+)\.([^.()]+)?(\(.+\)|$)"); + if (!match.Groups[1].Success) return null; + + var memberType = (MemberType)match.Groups[1].Value[0]; + if (memberType == MemberType.None) return null; + + var summaryXml = x.Elements("summary").FirstOrDefault()?.ToString() + ?? x.Element("summary")?.ToString() + ?? ""; + summaryXml = Regex.Replace(summaryXml, @"<\/?summary>", string.Empty); + summaryXml = Regex.Replace(summaryXml, @"", Environment.NewLine); + summaryXml = Regex.Replace(summaryXml, @"", m => ResolveSeeElement(m, assemblyName)); + + var parsed = Regex.Replace(summaryXml, @"<(type)*paramref name=""([^\""]*)""\s*\/>", e => $"`{e.Groups[1].Value}`"); + + var summary = parsed; + + if (summary != "") { + summary = string.Join(" ", summary.Split(new[] { "\r", "\n", "\t" }, StringSplitOptions.RemoveEmptyEntries).Select(y => y.Trim())); + } + + var returns = ((string)x.Element("returns")) ?? ""; + var remarks = ((string)x.Element("remarks")) ?? ""; + var parameters = x.Elements("param") + .Select(e => Tuple.Create(e.Attribute("name").Value, e)) + .Distinct(new Item1EqualityCompaerer()) + .ToDictionary(e => e.Item1, e => e.Item2.Value); + + var className = (memberType == MemberType.Type) + ? match.Groups[2].Value + "." + match.Groups[3].Value + : match.Groups[2].Value; + + return new XmlDocumentComment { + MemberType = memberType, + ClassName = className, + MemberName = match.Groups[3].Value, + Summary = summary.Trim(), + Remarks = remarks.Trim(), + Parameters = parameters, + Returns = returns.Trim() + }; + }) + .Where(x => x != null) + .ToArray(); + } + + private static string ResolveSeeElement(Match m, string ns) { + var typeName = m.Groups[1].Value; + if (!string.IsNullOrWhiteSpace(ns)) { + if (typeName.StartsWith(ns)) { + return $"[{typeName}]({Regex.Replace(typeName, $"\\.(?:.(?!\\.))+$", me => me.Groups[0].Value.Replace(".", "#").ToLower())})"; + } + } + return $"`{typeName}`"; + } + + class Item1EqualityCompaerer : EqualityComparer> + { + public override bool Equals(Tuple x, Tuple y) + { + return x.Item1.Equals(y.Item1); + } + + public override int GetHashCode(Tuple obj) + { + return obj.Item1.GetHashCode(); + } + } + } +} diff --git a/sandbox/ConsoleApp1/Program.cs b/sandbox/ConsoleApp1/Program.cs index 0b317b98..2b4af7ac 100644 --- a/sandbox/ConsoleApp1/Program.cs +++ b/sandbox/ConsoleApp1/Program.cs @@ -1,61 +1,70 @@ -using Microsoft.Extensions.Logging; +using ConsoleApp1; using R3; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reactive.Linq; -using System.Threading.Channels; -using ZLogger; -SubscriptionTracker.EnableTracking = true; -SubscriptionTracker.EnableStackTrace = true; -using var factory = LoggerFactory.Create(x => -{ - x.SetMinimumLevel(LogLevel.Trace); - x.AddZLoggerConsole(); -}); -// ObservableSystem.Logger = factory.CreateLogger(); -var logger = factory.CreateLogger(); +//Dump.Factory(); +Dump.Operator(); -var sw = Stopwatch.StartNew(); -var subject1 = new System.Reactive.Subjects.Subject(); -var subject2 = new System.Reactive.Subjects.Subject(); -//subject1.WithLatestFrom(subject2.Finally(() => Console.WriteLine("finally subject2")), (x, y) => (x, y)).Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("end")); -subject1.Scan((x, y) => x + y).Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("end")); -subject1.OnNext(1); -subject1.OnNext(10); + + + + +//SubscriptionTracker.EnableTracking = true; +//SubscriptionTracker.EnableStackTrace = true; + +//using var factory = LoggerFactory.Create(x => +//{ +// x.SetMinimumLevel(LogLevel.Trace); +// x.AddZLoggerConsole(); +//}); +//// ObservableSystem.Logger = factory.CreateLogger(); +//var logger = factory.CreateLogger(); + + + + +//var sw = Stopwatch.StartNew(); +//var subject1 = new System.Reactive.Subjects.Subject(); +//var subject2 = new System.Reactive.Subjects.Subject(); +////subject1.WithLatestFrom(subject2.Finally(() => Console.WriteLine("finally subject2")), (x, y) => (x, y)).Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("end")); + +//subject1.Scan((x, y) => x + y).Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("end")); + + +//subject1.OnNext(1); //subject1.OnNext(10); -//subject1.OnNext(100); +////subject1.OnNext(10); +////subject1.OnNext(100); -// subject1.SequenceEqual( +//// subject1.SequenceEqual( -// System.Reactive.Linq.Observable.Switch( +//// System.Reactive.Linq.Observable.Switch( -public static class Extensions -{ - public static IDisposable WriteLine(this Observable source) - { - return source.Subscribe(x => Console.WriteLine(x), x => Console.WriteLine(x)); - } -} +//public static class Extensions +//{ +// public static IDisposable WriteLine(this Observable source) +// { +// return source.Subscribe(x => Console.WriteLine(x), x => Console.WriteLine(x)); +// } +//} -class TestDisposable : IDisposable -{ - public int CalledCount = 0; +//class TestDisposable : IDisposable +//{ +// public int CalledCount = 0; - public void Dispose() - { - CalledCount += 1; - } -} +// public void Dispose() +// { +// CalledCount += 1; +// } +//}