diff --git a/R3.sln b/R3.sln
index 2e17bb81..994a7d1d 100644
--- a/R3.sln
+++ b/R3.sln
@@ -22,7 +22,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C7327A31-4
README.md = README.md
EndProjectSection
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "R3.WPF", "src\R3.WPF\R3.WPF.csproj", "{57AC0130-0D5F-489A-A565-B41EF9928085}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "R3.WPF", "src\R3.WPF\R3.WPF.csproj", "{57AC0130-0D5F-489A-A565-B41EF9928085}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp1", "sandbox\WpfApp1\WpfApp1.csproj", "{BA40E541-3BCD-438A-B966-2FC7BE80AB80}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -46,6 +48,10 @@ Global
{57AC0130-0D5F-489A-A565-B41EF9928085}.Debug|Any CPU.Build.0 = Debug|Any CPU
{57AC0130-0D5F-489A-A565-B41EF9928085}.Release|Any CPU.ActiveCfg = Release|Any CPU
{57AC0130-0D5F-489A-A565-B41EF9928085}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BA40E541-3BCD-438A-B966-2FC7BE80AB80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BA40E541-3BCD-438A-B966-2FC7BE80AB80}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BA40E541-3BCD-438A-B966-2FC7BE80AB80}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA40E541-3BCD-438A-B966-2FC7BE80AB80}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -55,6 +61,7 @@ Global
{72DE3CB9-195E-4740-9416-5960D75ED795} = {FAB2137C-1DBA-4F2F-8E22-DF3521C9B365}
{42F7C4F7-3BB3-4DC8-8285-9DFF7E93BC4B} = {0544806B-3BB4-43CF-8277-BC612F32208D}
{57AC0130-0D5F-489A-A565-B41EF9928085} = {9FA6D327-728B-4436-AE3A-9E46D8FEF591}
+ {BA40E541-3BCD-438A-B966-2FC7BE80AB80} = {FAB2137C-1DBA-4F2F-8E22-DF3521C9B365}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84B77761-6B9E-46BA-B132-6C77B0B6E4FA}
diff --git a/sandbox/WpfApp1/App.xaml b/sandbox/WpfApp1/App.xaml
new file mode 100644
index 00000000..2e70522d
--- /dev/null
+++ b/sandbox/WpfApp1/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/sandbox/WpfApp1/App.xaml.cs b/sandbox/WpfApp1/App.xaml.cs
new file mode 100644
index 00000000..56a3e205
--- /dev/null
+++ b/sandbox/WpfApp1/App.xaml.cs
@@ -0,0 +1,12 @@
+using System.Configuration;
+using System.Data;
+using System.Windows;
+
+namespace WpfApp1;
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application
+{
+}
+
diff --git a/sandbox/WpfApp1/AssemblyInfo.cs b/sandbox/WpfApp1/AssemblyInfo.cs
new file mode 100644
index 00000000..b0ec8275
--- /dev/null
+++ b/sandbox/WpfApp1/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/sandbox/WpfApp1/MainWindow.xaml b/sandbox/WpfApp1/MainWindow.xaml
new file mode 100644
index 00000000..8b0a0e9f
--- /dev/null
+++ b/sandbox/WpfApp1/MainWindow.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/sandbox/WpfApp1/MainWindow.xaml.cs b/sandbox/WpfApp1/MainWindow.xaml.cs
new file mode 100644
index 00000000..31e0ec7b
--- /dev/null
+++ b/sandbox/WpfApp1/MainWindow.xaml.cs
@@ -0,0 +1,49 @@
+using R3;
+using R3.WPF;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace WpfApp1;
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+
+
+
+ //Dispatcher.Yield(DispatcherPriority.Input);
+
+
+
+ R3.WPF.WpfProviderInitializer.SetDefaultProviders();
+
+
+
+
+ //var sw = Stopwatch.StartNew();
+ //Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)).Subscribe(_ =>
+ //{
+ // textBlock.Text = "Hello World:" + sw.Elapsed;
+ //});
+
+ Observable.TimerFrame(50, 100).Subscribe(_ =>
+ {
+ textBlock.Text = "Hello World:" + ObservableSystem.DefaultFrameProvider.GetFrameCount();
+ });
+ }
+}
diff --git a/sandbox/WpfApp1/WpfApp1.csproj b/sandbox/WpfApp1/WpfApp1.csproj
new file mode 100644
index 00000000..0c280836
--- /dev/null
+++ b/sandbox/WpfApp1/WpfApp1.csproj
@@ -0,0 +1,16 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
diff --git a/src/R3.WPF/DispatcherFrameProvider.cs b/src/R3.WPF/DispatcherFrameProvider.cs
deleted file mode 100644
index 42ddce39..00000000
--- a/src/R3.WPF/DispatcherFrameProvider.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Windows.Threading;
-
-namespace R3.WPF;
-
-public sealed class DispatcherFrameProvider : FrameProvider
-{
-
- public DispatcherFrameProvider(Dispatcher dispatcher)
- {
-
- var foo = dispatcher.InvokeAsync(() =>
- {
- });
-
-
- }
-
- public override long GetFrameCount()
- {
- throw new NotImplementedException();
- }
-
- public override void Register(IFrameRunnerWorkItem callback)
- {
- throw new NotImplementedException();
- }
-}
diff --git a/src/R3.WPF/DispatcherTimerProvider.cs b/src/R3.WPF/DispatcherTimerProvider.cs
deleted file mode 100644
index d699a8d6..00000000
--- a/src/R3.WPF/DispatcherTimerProvider.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System.Windows.Threading;
-
-namespace R3.WPF;
-
-public sealed class DispatcherTimerProvider : TimeProvider
-{
- public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
- {
- return base.CreateTimer(callback, state, dueTime, period);
- }
-}
-
-internal sealed class DispatcherTimerProviderTimer : ITimer
-{
- DispatcherTimer? timer;
- TimerCallback callback;
- object? state;
- EventHandler timerTick;
- TimeSpan? period;
-
- public DispatcherTimerProviderTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
- {
- this.timerTick = Timer_Tick;
- this.callback = callback;
- this.state = state;
- this.timer = new DispatcherTimer();
- timer.Tick += timerTick;
-
- Change(dueTime, period);
- }
-
- public bool Change(TimeSpan dueTime, TimeSpan period)
- {
- if (timer != null)
- {
- timer.Stop();
-
- this.period = period;
- timer.Interval = dueTime;
-
- timer.Start();
- return true;
- }
- return false;
- }
-
- void Timer_Tick(object? sender, EventArgs e)
- {
- callback(state);
-
- if (timer != null && period != null)
- {
- timer.Stop();
-
- timer.Interval = period.Value;
- period = null;
-
- timer.Start();
- }
- }
-
- public void Dispose()
- {
- if (timer != null)
- {
- timer.Stop();
- timer.Tick -= timerTick;
- timer = null;
- }
- }
-
- public ValueTask DisposeAsync()
- {
- Dispose();
- return default;
- }
-}
diff --git a/src/R3.WPF/WpfDispatcherTimerProvider.cs b/src/R3.WPF/WpfDispatcherTimerProvider.cs
new file mode 100644
index 00000000..5a03c280
--- /dev/null
+++ b/src/R3.WPF/WpfDispatcherTimerProvider.cs
@@ -0,0 +1,119 @@
+using System.Windows.Threading;
+
+namespace R3.WPF;
+
+public sealed class WpfDispatcherTimerProvider : TimeProvider
+{
+ readonly DispatcherPriority? priority;
+ readonly Dispatcher? dispatcher;
+
+ public WpfDispatcherTimerProvider()
+ {
+ this.priority = null;
+ this.dispatcher = null;
+ }
+
+ public WpfDispatcherTimerProvider(DispatcherPriority priority)
+ {
+ this.priority = priority;
+ this.dispatcher = null;
+ }
+
+ public WpfDispatcherTimerProvider(DispatcherPriority priority, Dispatcher dispatcher)
+ {
+ this.priority = priority;
+ this.dispatcher = dispatcher;
+ }
+
+ public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
+ {
+ return new WpfDispatcherTimerProviderTimer(priority, dispatcher, callback, state, dueTime, period);
+ }
+}
+
+internal sealed class WpfDispatcherTimerProviderTimer : ITimer
+{
+ DispatcherTimer? timer;
+ TimerCallback callback;
+ object? state;
+ EventHandler timerTick;
+ TimeSpan? period;
+
+ public WpfDispatcherTimerProviderTimer(DispatcherPriority? priority, Dispatcher? dispatcher, TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
+ {
+ this.timerTick = Timer_Tick;
+ this.callback = callback;
+ this.state = state;
+ if (priority == null && dispatcher == null)
+ {
+ this.timer = new DispatcherTimer();
+ }
+ if (dispatcher == null) // priority is not null
+ {
+ this.timer = new DispatcherTimer(priority!.Value);
+ }
+ else
+ {
+ this.timer = new DispatcherTimer(priority!.Value, dispatcher);
+ }
+
+ timer.Tick += timerTick;
+
+ if (dueTime != Timeout.InfiniteTimeSpan)
+ {
+ Change(dueTime, period);
+ }
+ }
+
+ public bool Change(TimeSpan dueTime, TimeSpan period)
+ {
+ if (timer != null)
+ {
+ timer.Stop();
+
+ this.period = period;
+ timer.Interval = dueTime;
+
+ timer.Start();
+ return true;
+ }
+ return false;
+ }
+
+ void Timer_Tick(object? sender, EventArgs e)
+ {
+ callback(state);
+
+ if (timer != null && period != null)
+ {
+ timer.Stop();
+
+ if (period.Value == Timeout.InfiniteTimeSpan)
+ {
+ period = null;
+ }
+ else
+ {
+ timer.Interval = period.Value;
+ period = null;
+ timer.Start();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (timer != null)
+ {
+ timer.Stop();
+ timer.Tick -= timerTick;
+ timer = null;
+ }
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ Dispose();
+ return default;
+ }
+}
diff --git a/src/R3.WPF/WpfProviderInitializer.cs b/src/R3.WPF/WpfProviderInitializer.cs
new file mode 100644
index 00000000..0399393b
--- /dev/null
+++ b/src/R3.WPF/WpfProviderInitializer.cs
@@ -0,0 +1,24 @@
+using System.Windows.Threading;
+
+namespace R3.WPF;
+
+public static class WpfProviderInitializer
+{
+ public static void SetDefaultProviders()
+ {
+ ObservableSystem.DefaultTimeProvider = new WpfDispatcherTimerProvider();
+ ObservableSystem.DefaultFrameProvider = new WpfRenderingFrameProvider();
+ }
+
+ public static void SetDefaultProviders(DispatcherPriority priority)
+ {
+ ObservableSystem.DefaultTimeProvider = new WpfDispatcherTimerProvider(priority);
+ ObservableSystem.DefaultFrameProvider = new WpfRenderingFrameProvider();
+ }
+
+ public static void SetDefaultProviders(DispatcherPriority priority, Dispatcher dispatcher)
+ {
+ ObservableSystem.DefaultTimeProvider = new WpfDispatcherTimerProvider(priority, dispatcher);
+ ObservableSystem.DefaultFrameProvider = new WpfRenderingFrameProvider();
+ }
+}
diff --git a/src/R3.WPF/WpfRenderingFrameProvider.cs b/src/R3.WPF/WpfRenderingFrameProvider.cs
new file mode 100644
index 00000000..ae45cccb
--- /dev/null
+++ b/src/R3.WPF/WpfRenderingFrameProvider.cs
@@ -0,0 +1,80 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace R3.WPF;
+
+public sealed class WpfRenderingFrameProvider : FrameProvider
+{
+ bool disposed;
+ long frameCount;
+ FreeListCore list;
+ readonly object gate = new object();
+
+ EventHandler messageLoop;
+
+ public WpfRenderingFrameProvider()
+ {
+ this.messageLoop = Run;
+ this.list = new FreeListCore(gate);
+ System.Windows.Media.CompositionTarget.Rendering += messageLoop;
+ }
+
+ public override long GetFrameCount()
+ {
+ ThrowObjectDisposedIf(disposed, typeof(NewThreadSleepFrameProvider));
+ return frameCount;
+ }
+
+ public override void Register(IFrameRunnerWorkItem callback)
+ {
+ ThrowObjectDisposedIf(disposed, typeof(NewThreadSleepFrameProvider));
+ list.Add(callback, out _);
+ }
+
+ public void Dispose()
+ {
+ disposed = true;
+ System.Windows.Media.CompositionTarget.Rendering -= messageLoop;
+ list.Dispose();
+ }
+
+ void Run(object? sender, EventArgs e)
+ {
+ frameCount++;
+
+ var span = list.AsSpan();
+ for (int i = 0; i < span.Length; i++)
+ {
+ ref readonly var item = ref span[i];
+ if (item != null)
+ {
+ try
+ {
+ if (!item.MoveNext(frameCount))
+ {
+ list.Remove(i);
+ }
+ }
+ catch (Exception ex)
+ {
+ list.Remove(i);
+ try
+ {
+ ObservableSystem.GetUnhandledExceptionHandler().Invoke(ex);
+ }
+ catch { }
+ }
+ }
+ }
+ }
+
+ static void ThrowObjectDisposedIf([DoesNotReturnIf(true)] bool condition, Type type)
+ {
+ if (condition)
+ {
+ ThrowObjectDisposedException(type);
+ }
+ }
+
+ [DoesNotReturn]
+ internal static void ThrowObjectDisposedException(Type? type) => throw new ObjectDisposedException(type?.FullName);
+}
diff --git a/src/R3/Internal/FreeListCore.cs b/src/R3/FreeListCore.cs
similarity index 97%
rename from src/R3/Internal/FreeListCore.cs
rename to src/R3/FreeListCore.cs
index fec82027..021e7089 100644
--- a/src/R3/Internal/FreeListCore.cs
+++ b/src/R3/FreeListCore.cs
@@ -1,10 +1,10 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace R3.Internal;
+namespace R3;
[StructLayout(LayoutKind.Auto)]
-internal struct FreeListCore
+public struct FreeListCore
where T : class
{
readonly object gate;
@@ -44,7 +44,7 @@ public void Add(T item, out int removeKey)
{
// full, 1, 4, 6,...resize(x1.5)
var len = values.Length;
- var newValues = len == 1 ? new T[4] : new T[len + (len / 2)];
+ var newValues = len == 1 ? new T[4] : new T[len + len / 2];
Array.Copy(values, newValues, len);
Volatile.Write(ref values, newValues);
index = len;
@@ -125,42 +125,42 @@ public void Dispose()
values = null;
lastIndex = -2; // -2 is disposed.
}
- }
-
+ }
+
#if NET6_0_OR_GREATER
-
+
static int FindNullIndex(T?[] target)
{
var span = MemoryMarshal.CreateReadOnlySpan(
ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(target)), target.Length);
return span.IndexOf(IntPtr.Zero);
- }
-
-#else
-
+ }
+
+#else
+
static unsafe int FindNullIndex(T?[] target)
- {
- ref var head = ref Unsafe.As(ref MemoryMarshal.GetReference(target.AsSpan()));
- fixed (void* p = &head)
- {
- var span = new ReadOnlySpan(p, target.Length);
-
-#if NETSTANDARD2_1
- return span.IndexOf(IntPtr.Zero);
-#else
- for (int i = 0; i < span.Length; i++)
- {
- if (span[i] == IntPtr.Zero) return i;
- }
- return -1;
-#endif
- }
- }
-
+ {
+ ref var head = ref Unsafe.As(ref MemoryMarshal.GetReference(target.AsSpan()));
+ fixed (void* p = &head)
+ {
+ var span = new ReadOnlySpan(p, target.Length);
+
+#if NETSTANDARD2_1
+ return span.IndexOf(IntPtr.Zero);
+#else
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (span[i] == IntPtr.Zero) return i;
+ }
+ return -1;
+#endif
+ }
+ }
+
#endif
-
+
#if NET8_0_OR_GREATER
-
+
static int FindLastNonNullIndex(T?[] target, int lastIndex)
{
var span = MemoryMarshal.CreateReadOnlySpan(
@@ -170,20 +170,20 @@ static int FindLastNonNullIndex(T?[] target, int lastIndex)
}
#else
-
+
static unsafe int FindLastNonNullIndex(T?[] target, int lastIndex)
{
- ref var head = ref Unsafe.As(ref MemoryMarshal.GetReference(target.AsSpan()));
- fixed (void* p = &head)
- {
- var span = new ReadOnlySpan(p, lastIndex); // without lastIndexed value.
-
+ ref var head = ref Unsafe.As(ref MemoryMarshal.GetReference(target.AsSpan()));
+ fixed (void* p = &head)
+ {
+ var span = new ReadOnlySpan(p, lastIndex); // without lastIndexed value.
+
for (var i = span.Length - 1; i >= 0; i--)
{
if (span[i] != IntPtr.Zero) return i;
- }
-
- return -1;
+ }
+
+ return -1;
}
}
diff --git a/src/R3/NewThreadSleepFrameProvider.cs b/src/R3/NewThreadSleepFrameProvider.cs
index ed2cb730..78533074 100644
--- a/src/R3/NewThreadSleepFrameProvider.cs
+++ b/src/R3/NewThreadSleepFrameProvider.cs
@@ -43,6 +43,8 @@ void Run()
{
while (!disposed)
{
+ frameCount++;
+
var span = list.AsSpan();
for (int i = 0; i < span.Length; i++)
{
@@ -69,7 +71,6 @@ void Run()
}
Thread.Sleep(sleepMilliseconds);
- frameCount++;
}
list.Dispose();
}
diff --git a/src/R3/TimerFrameProvider.cs b/src/R3/TimerFrameProvider.cs
index 833c70f5..0eb8fc68 100644
--- a/src/R3/TimerFrameProvider.cs
+++ b/src/R3/TimerFrameProvider.cs
@@ -64,6 +64,8 @@ static void Run(object? state)
lock (self.gate)
{
+ self.frameCount++;
+
var span = self.list.AsSpan();
for (int i = 0; i < span.Length; i++)
{
@@ -88,8 +90,6 @@ static void Run(object? state)
}
}
}
-
- self.frameCount++;
}
}
}