Signal WIP
This commit is contained in:
@@ -155,6 +155,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Database", "Ap
|
||||
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}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -502,6 +526,10 @@ Global
|
||||
{53E5B762-B620-4106-B481-31A478A1E14F} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
|
||||
{610C9140-4B05-46A2-BFF4-501049EBA25E} = {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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
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