Signal WIP
This commit is contained in:
@@ -155,6 +155,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database", "Ap
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database.Abstractions", "AppCommon\FileTime.App.Database.Abstractions\FileTime.App.Database.Abstractions.csproj", "{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database.Abstractions", "AppCommon\FileTime.App.Database.Abstractions\FileTime.App.Database.Abstractions.csproj", "{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal", "Library\Signal\Signal.csproj", "{3917AA16-F78A-460F-8214-85306E532FDC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal.Tests", "Library\Signal.Tests\Signal.Tests.csproj", "{2E2DACC0-9718-47D4-B6AE-E57624E6EE62}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal.Tests.Memory", "Library\Signal.Tests.Memory\Signal.Tests.Memory.csproj", "{C2A81E12-FED1-4943-85F4-EDFE49BF32EB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Signal.Benchmark", "Library\Signal.Benchmark\Signal.Benchmark.csproj", "{3D8097B2-0B9D-4916-BBF2-C45F7200553C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -429,6 +437,22 @@ Global
|
|||||||
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3917AA16-F78A-460F-8214-85306E532FDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3917AA16-F78A-460F-8214-85306E532FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3917AA16-F78A-460F-8214-85306E532FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3917AA16-F78A-460F-8214-85306E532FDC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2E2DACC0-9718-47D4-B6AE-E57624E6EE62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2E2DACC0-9718-47D4-B6AE-E57624E6EE62}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2E2DACC0-9718-47D4-B6AE-E57624E6EE62}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2E2DACC0-9718-47D4-B6AE-E57624E6EE62}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C2A81E12-FED1-4943-85F4-EDFE49BF32EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C2A81E12-FED1-4943-85F4-EDFE49BF32EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C2A81E12-FED1-4943-85F4-EDFE49BF32EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C2A81E12-FED1-4943-85F4-EDFE49BF32EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3D8097B2-0B9D-4916-BBF2-C45F7200553C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3D8097B2-0B9D-4916-BBF2-C45F7200553C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3D8097B2-0B9D-4916-BBF2-C45F7200553C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3D8097B2-0B9D-4916-BBF2-C45F7200553C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -502,6 +526,10 @@ Global
|
|||||||
{53E5B762-B620-4106-B481-31A478A1E14F} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
|
{53E5B762-B620-4106-B481-31A478A1E14F} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
|
||||||
{610C9140-4B05-46A2-BFF4-501049EBA25E} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
{610C9140-4B05-46A2-BFF4-501049EBA25E} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
||||||
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
{635DC6E5-A762-409E-BBCC-CE1D29F4DDB9} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
||||||
|
{3917AA16-F78A-460F-8214-85306E532FDC} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
|
{2E2DACC0-9718-47D4-B6AE-E57624E6EE62} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
|
{C2A81E12-FED1-4943-85F4-EDFE49BF32EB} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
|
{3D8097B2-0B9D-4916-BBF2-C45F7200553C} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
3
src/GuiApp/Avalonia/FileTime.GuiApp/build_local.sh
Executable file
3
src/GuiApp/Avalonia/FileTime.GuiApp/build_local.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
dotnet publish -c Release /p:DefineConstants=VERBOSE_LOGGING
|
||||||
58
src/Library/Signal.Benchmark/MapBenchmark.cs
Normal file
58
src/Library/Signal.Benchmark/MapBenchmark.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
|
namespace Signal.Benchmark;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
[ShortRunJob]
|
||||||
|
public class MapBenchmark
|
||||||
|
{
|
||||||
|
private static readonly Signal<int> _signalInt = new(10);
|
||||||
|
private static readonly IReadOnlySignal<int> _signalIntMapped;
|
||||||
|
|
||||||
|
private static readonly Signal<string> _signalString = new("test");
|
||||||
|
private static readonly IReadOnlySignal<string> _signalStringMapped;
|
||||||
|
|
||||||
|
static MapBenchmark()
|
||||||
|
{
|
||||||
|
_signalIntMapped = _signalInt.Map(v => v * 2);
|
||||||
|
_signalStringMapped = _signalString.Map(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask NoOpInt()
|
||||||
|
{
|
||||||
|
var signal = new Signal<int>(10);
|
||||||
|
var mappedSignal = signal.Map(value => value * 2);
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask GetValueAsyncInt()
|
||||||
|
{
|
||||||
|
var signal = new Signal<int>(10);
|
||||||
|
var mappedSignal = signal.Map(value => value * 2);
|
||||||
|
await mappedSignal.GetValueAsync();
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask GetValueAsyncIntStatic()
|
||||||
|
{
|
||||||
|
await _signalIntMapped.GetValueAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask NoOpString()
|
||||||
|
{
|
||||||
|
var signal = new Signal<string>("test");
|
||||||
|
var mappedSignal = signal.Map(value => value);
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask GetValueAsyncString()
|
||||||
|
{
|
||||||
|
var signal = new Signal<string>("test");
|
||||||
|
var mappedSignal = signal.Map(value => value);
|
||||||
|
await mappedSignal.GetValueAsync();
|
||||||
|
}
|
||||||
|
[Benchmark]
|
||||||
|
public async ValueTask GetValueAsyncStringStatic()
|
||||||
|
{
|
||||||
|
await _signalStringMapped.GetValueAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/Library/Signal.Benchmark/Program.cs
Normal file
4
src/Library/Signal.Benchmark/Program.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
using Signal.Benchmark;
|
||||||
|
|
||||||
|
var summary = BenchmarkRunner.Run<MapBenchmark>();
|
||||||
18
src/Library/Signal.Benchmark/Signal.Benchmark.csproj
Normal file
18
src/Library/Signal.Benchmark/Signal.Benchmark.csproj
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Signal\Signal.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
26
src/Library/Signal.Tests.Memory/MapSignalTests.cs
Normal file
26
src/Library/Signal.Tests.Memory/MapSignalTests.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using JetBrains.dotMemoryUnit;
|
||||||
|
|
||||||
|
namespace Signal.Tests.Memory;
|
||||||
|
|
||||||
|
public class MapSignalTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
[AssertTraffic]
|
||||||
|
public async Task Map_WhenNoAllocation_ShouldNoAllocationHappen()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<int>(10);
|
||||||
|
var mapped = signal.Map(s => s);
|
||||||
|
var memorySnapShot = dotMemory.Check();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await mapped.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
//Assert.Equal(10, result);
|
||||||
|
dotMemory.Check(memory =>
|
||||||
|
{
|
||||||
|
Assert.Equal(0, memory.GetDifference(memorySnapShot).GetNewObjects().ObjectsCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/Library/Signal.Tests.Memory/Signal.Tests.Memory.csproj
Normal file
33
src/Library/Signal.Tests.Memory/Signal.Tests.Memory.csproj
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.5.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="dotMemory.Unit">
|
||||||
|
<HintPath>..\..\..\..\..\.nuget\packages\jetbrains.dotmemoryunit\3.2.20220510\lib\netstandard2.0\dotMemory.Unit.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Signal\Signal.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
41
src/Library/Signal.Tests/CombineLatestTests.cs
Normal file
41
src/Library/Signal.Tests/CombineLatestTests.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using Xunit;
|
||||||
|
using static Signal.Helpers;
|
||||||
|
|
||||||
|
namespace Signal.Tests;
|
||||||
|
|
||||||
|
public class CombineLatestTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CombineLatest_With2ValidInput_ShouldCombineInputs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal1 = new Signal<string>("test1");
|
||||||
|
var signal2 = new Signal<string>("test2");
|
||||||
|
var combinedSignal = CombineLatest(signal1, signal2, (t1, t2) => t1 + " " + t2);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await combinedSignal.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("test1 test2", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CombineLatest_With2ValidInputAndAsyncCombiner_ShouldCombineInputs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal1 = new Signal<string>("test1");
|
||||||
|
var signal2 = new Signal<string>("test2");
|
||||||
|
var combinedSignal = CombineLatest(signal1, signal2, async (t1, t2) =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return t1 + " " + t2;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await combinedSignal.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("test1 test2", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/Library/Signal.Tests/MapSignalTests.cs
Normal file
79
src/Library/Signal.Tests/MapSignalTests.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Signal.Tests;
|
||||||
|
|
||||||
|
public class MapSignalTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Map_WhenNotRead_ShouldBeDirty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<string>("test");
|
||||||
|
var mapped = signal.Map(s => s);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(mapped.IsDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Map_WhenNotReadButBaseIsNotDirty_ShouldBeDirty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<string>("test");
|
||||||
|
await signal.GetValueAsync();
|
||||||
|
var mapped = signal.Map(s => s);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(mapped.IsDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Map_WhenAlreadyRead_ShouldNotBeDirty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<string>("test");
|
||||||
|
var mapped = signal.Map(s => s);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await mapped.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(mapped.IsDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetValueAsync_WithSyncMapper_ShouldReturnCorrectData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<string>("tEsT");
|
||||||
|
var mapped = signal.Map(s => s.ToUpper());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await mapped.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("TEST", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetValueAsync_WithAsyncMapper_ShouldReturnCorrectData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var signal = new Signal<string>("tEsT");
|
||||||
|
var mapped = signal.Map(async s =>
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
return s.ToUpper();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await mapped.GetValueAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("TEST", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Library/Signal.Tests/Signal.Tests.csproj
Normal file
18
src/Library/Signal.Tests/Signal.Tests.csproj
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="xunit.v3" Version="0.1.1-pre.396" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Signal\Signal.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
36
src/Library/Signal/CombineLatestSignal.cs
Normal file
36
src/Library/Signal/CombineLatestSignal.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public class CombineLatestSignal<T1, T2, TResult> : SignalBase<TResult>
|
||||||
|
{
|
||||||
|
private readonly Func<ValueTask<TResult>> _combine;
|
||||||
|
private TResult _result;
|
||||||
|
|
||||||
|
public CombineLatestSignal(IReadOnlySignal<T1> signal1, IReadOnlySignal<T2> signal2, Func<T1, T2, TResult> combine)
|
||||||
|
: base(new IReadOnlySignal[] { signal1, signal2 })
|
||||||
|
{
|
||||||
|
_combine = CombineAsync;
|
||||||
|
|
||||||
|
async ValueTask<TResult> CombineAsync() => combine(await signal1.GetValueAsync(), await signal2.GetValueAsync());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombineLatestSignal(IReadOnlySignal<T1> signal1, IReadOnlySignal<T2> signal2, Func<T1, T2, Task<TResult>> combine)
|
||||||
|
: base(new IReadOnlySignal[] { signal1, signal2 })
|
||||||
|
{
|
||||||
|
_combine = CombineAsync;
|
||||||
|
|
||||||
|
async ValueTask<TResult> CombineAsync() => await combine(await signal1.GetValueAsync(), await signal2.GetValueAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask<TResult> GetValueAsync()
|
||||||
|
{
|
||||||
|
//TODO synchronization
|
||||||
|
if (!IsDirty)
|
||||||
|
{
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
IsDirty = false;
|
||||||
|
_result = await _combine();
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Library/Signal/Extensions.cs
Normal file
13
src/Library/Signal/Extensions.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static IReadOnlySignal<TResult> Map<T, TResult>(this IReadOnlySignal<T> signal, Func<T, TResult> map)
|
||||||
|
{
|
||||||
|
return new MapSignal<T, TResult>(signal, map);
|
||||||
|
}
|
||||||
|
public static IReadOnlySignal<TResult> Map<T, TResult>(this IReadOnlySignal<T> signal, Func<T, Task<TResult>> map)
|
||||||
|
{
|
||||||
|
return new MapSignal<T, TResult>(signal, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Library/Signal/Helpers.cs
Normal file
15
src/Library/Signal/Helpers.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public static class Helpers
|
||||||
|
{
|
||||||
|
public static IReadOnlySignal<TResult> CombineLatest<T1, T2, TResult>(IReadOnlySignal<T1> signal1,
|
||||||
|
IReadOnlySignal<T2> signal2, Func<T1, T2, TResult> combine)
|
||||||
|
{
|
||||||
|
return new CombineLatestSignal<T1, T2, TResult>(signal1, signal2, combine);
|
||||||
|
}
|
||||||
|
public static IReadOnlySignal<TResult> CombineLatest<T1, T2, TResult>(IReadOnlySignal<T1> signal1,
|
||||||
|
IReadOnlySignal<T2> signal2, Func<T1, T2, Task<TResult>> combine)
|
||||||
|
{
|
||||||
|
return new CombineLatestSignal<T1, T2, TResult>(signal1, signal2, combine);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Library/Signal/IReadOnlySignal.cs
Normal file
12
src/Library/Signal/IReadOnlySignal.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public interface IReadOnlySignal
|
||||||
|
{
|
||||||
|
bool IsDirty { get; }
|
||||||
|
event Action<bool> IsDirtyChanged;
|
||||||
|
internal void SetDirty();
|
||||||
|
}
|
||||||
|
public interface IReadOnlySignal<T> : IReadOnlySignal
|
||||||
|
{
|
||||||
|
ValueTask<T> GetValueAsync();
|
||||||
|
}
|
||||||
6
src/Library/Signal/ISignal.cs
Normal file
6
src/Library/Signal/ISignal.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public interface ISignal<T> : IReadOnlySignal<T>
|
||||||
|
{
|
||||||
|
void SetValue(T value);
|
||||||
|
}
|
||||||
31
src/Library/Signal/MapSignal.cs
Normal file
31
src/Library/Signal/MapSignal.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public class MapSignal<T, TResult> : SignalBase<TResult>
|
||||||
|
{
|
||||||
|
private readonly Func<ValueTask<TResult>> _map;
|
||||||
|
private TResult _result;
|
||||||
|
public MapSignal(IReadOnlySignal<T> signal, Func<T, TResult> map) : base(signal)
|
||||||
|
{
|
||||||
|
_map = MapValueAsync;
|
||||||
|
|
||||||
|
async ValueTask<TResult> MapValueAsync() => map(await signal.GetValueAsync());
|
||||||
|
}
|
||||||
|
public MapSignal(IReadOnlySignal<T> signal, Func<T, Task<TResult>> map) : base(signal)
|
||||||
|
{
|
||||||
|
_map = MapValueAsync;
|
||||||
|
|
||||||
|
async ValueTask<TResult> MapValueAsync() => await map(await signal.GetValueAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask<TResult> GetValueAsync()
|
||||||
|
{
|
||||||
|
//TODO synchronization
|
||||||
|
if (!IsDirty)
|
||||||
|
{
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
IsDirty = false;
|
||||||
|
_result = await _map();
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Library/Signal/Signal.cs
Normal file
23
src/Library/Signal/Signal.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public class Signal<T> : SignalBase<T>, ISignal<T>
|
||||||
|
{
|
||||||
|
private T _value;
|
||||||
|
|
||||||
|
public Signal(T value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(T value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
SetDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValueTask<T> GetValueAsync()
|
||||||
|
{
|
||||||
|
IsDirty = false;
|
||||||
|
return new ValueTask<T>(_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Library/Signal/Signal.csproj
Normal file
9
src/Library/Signal/Signal.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
50
src/Library/Signal/SignalBase.cs
Normal file
50
src/Library/Signal/SignalBase.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace Signal;
|
||||||
|
|
||||||
|
public abstract class SignalBase<T> : IReadOnlySignal<T>
|
||||||
|
{
|
||||||
|
private readonly List<SignalBase<T>> _dependentSignals = [];
|
||||||
|
public bool IsDirty { get; protected set; } = true;
|
||||||
|
public event Action<bool>? IsDirtyChanged;
|
||||||
|
|
||||||
|
public SignalBase()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalBase(IReadOnlySignal baseSignal)
|
||||||
|
{
|
||||||
|
HandleDependentSignal(baseSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignalBase(IEnumerable<IReadOnlySignal> baseSignal)
|
||||||
|
{
|
||||||
|
foreach (var signal in baseSignal)
|
||||||
|
{
|
||||||
|
HandleDependentSignal(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDependentSignal(IReadOnlySignal baseSignal)
|
||||||
|
{
|
||||||
|
baseSignal.IsDirtyChanged += isDirty =>
|
||||||
|
{
|
||||||
|
if (isDirty)
|
||||||
|
{
|
||||||
|
SetDirty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDirty()
|
||||||
|
{
|
||||||
|
IsDirty = true;
|
||||||
|
for (var i = 0; i < _dependentSignals.Count; i++)
|
||||||
|
{
|
||||||
|
_dependentSignals[i].SetDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
IsDirtyChanged?.Invoke(IsDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ValueTask<T> GetValueAsync();
|
||||||
|
}
|
||||||
8
src/nuget.config
Normal file
8
src/nuget.config
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
<add key="xunit-ci" value="https://f.feedz.io/xunit/xunit/nuget/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
Reference in New Issue
Block a user