• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DemoBytom / DemoEngine / 22077308408

16 Feb 2026 08:57PM UTC coverage: 33.098% (+0.02%) from 33.08%
22077308408

push

coveralls.net

DemoBytom
Further refactoring `.Initialize()` to be fully functional

Refactoring `DescriptorHeapAllocator.Initialize()` to be fully functional using `ValueResult<T, E>`

1221 of 3689 relevant lines covered (33.1%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/src/Demo.Engine.Core/Features/StaThread/StaThreadService.cs
1
// Copyright © Michał Dembski and contributors.
2
// Distributed under MIT license. See LICENSE file in the root for more information.
3

4
using System.Runtime.InteropServices;
5
using System.Threading.Channels;
6
using Demo.Engine.Core.Interfaces;
7
using Demo.Engine.Core.Interfaces.Platform;
8
using Demo.Engine.Core.Interfaces.Rendering;
9
using Microsoft.Extensions.Hosting;
10

11
namespace Demo.Engine.Core.Features.StaThread;
12

13
internal sealed class StaThreadService
14
    : IStaThreadService,
15
      IDisposable
16
{
17
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
18
    private readonly ChannelReader<StaThreadRequests> _channelReader;
19
    private readonly IMainLoopLifetime _mainLoopLifetime;
20
    private bool _disposedValue;
21

22
    public Task ExecutingTask { get; }
1✔
23
    public bool IsRunning { get; private set; }
1✔
24

25
    public StaThreadService(
1✔
26
        IHostApplicationLifetime hostApplicationLifetime,
1✔
27
        IRenderingEngine renderingEngine,
1✔
28
        IOSMessageHandler osMessageHandler,
1✔
29
        ChannelReader<StaThreadRequests> channelReader,
1✔
30
        IMainLoopLifetime mainLoopLifetime)
1✔
31
    {
32
        _hostApplicationLifetime = hostApplicationLifetime;
1✔
33
        _channelReader = channelReader;
1✔
34
        _mainLoopLifetime = mainLoopLifetime;
1✔
35
        IsRunning = true;
1✔
36
        ExecutingTask = RunSTAThread(
1✔
37
            renderingEngine,
1✔
38
            osMessageHandler);
1✔
39
    }
1✔
40

41
    private Task RunSTAThread(
42
        IRenderingEngine renderingEngine,
43
        IOSMessageHandler osMessageHandler)
44
    {
45
        var tcs = new TaskCompletionSource();
1✔
46
        var thread = new Thread(()
1✔
47
            =>
1✔
48
        {
1✔
49
            using var cts = CancellationTokenSource.CreateLinkedTokenSource(
1✔
50
                _hostApplicationLifetime.ApplicationStopping,
1✔
51
                _mainLoopLifetime.Token);
1✔
52

1✔
53
            try
1✔
54
            {
1✔
55

1✔
56
                SingleThreadedSynchronizationContextChannel.Await(async ()
1✔
57
                    => await STAThread(
1✔
58
                        renderingEngine: renderingEngine,
1✔
59
                        osMessageHandler: osMessageHandler,
1✔
60
                        cancellationToken: cts.Token));
1✔
61

1✔
62
                tcs.SetResult();
63
            }
64
            catch (OperationCanceledException)
1✔
65
            {
1✔
66
                tcs.SetResult();
1✔
67
            }
1✔
68
            catch (Exception ex)
69
            {
1✔
70
                tcs.SetException(ex);
71
            }
72
            IsRunning = false;
1✔
73
            _mainLoopLifetime.Cancel();
1✔
74
        });
1✔
75
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
76
        {
77
            //Can only by set on the Windows machine. Doesn't work on Linux/MacOS
78
            thread.SetApartmentState(ApartmentState.STA);
1✔
79
            thread.Name = "Main STA thread";
1✔
80
        }
81
        else
82
        {
83
            thread.Name = "Main thread";
84
        }
85

86
        thread.Start();
1✔
87

88
        return tcs.Task;
1✔
89
    }
90

91
    private async Task STAThread(
92
        IRenderingEngine renderingEngine,
93
        IOSMessageHandler osMessageHandler,
94
        CancellationToken cancellationToken)
95
    {
96
        var doEventsOk = true;
1✔
97

98
        await foreach (var staAction in _channelReader
99
            .ReadAllAsync(cancellationToken)
1✔
100
            .WithCancellation(cancellationToken))
1✔
101
        {
102
            switch (staAction)
103
            {
104
                case StaThreadRequests.DoEventsOkRequest doEventsOkRequest:
105
                    doEventsOk &= doEventsOkRequest.Invoke(renderingEngine, osMessageHandler);
106
                    break;
107

108
                default:
109
                    _ = staAction.Invoke(renderingEngine, osMessageHandler);
1✔
110
                    break;
111
            }
112

113
            if (!doEventsOk || !IsRunning || cancellationToken.IsCancellationRequested)
114
            {
115
                break;
116
            }
117
        }
118
    }
119

120
    private void Dispose(bool disposing)
121
    {
122
        if (!_disposedValue)
1✔
123
        {
124
            if (disposing)
1✔
125
            {
126
            }
127

128
            _disposedValue = true;
1✔
129
        }
130
    }
1✔
131

132
    public void Dispose()
133
    {
134
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
135
        Dispose(disposing: true);
1✔
136
        GC.SuppressFinalize(this);
1✔
137
    }
1✔
138

139
    private sealed class SingleThreadedSynchronizationContextChannel
140
        : SynchronizationContext
141
    {
142
        private readonly Channel<(SendOrPostCallback d, object? state)> _channel =
2✔
143
            Channel.CreateUnbounded<(SendOrPostCallback d, object? state)>(
2✔
144
                new UnboundedChannelOptions { SingleReader = true, SingleWriter = false });
2✔
145

146
        public override void Post(SendOrPostCallback d, object? state)
147
            => _channel.Writer.TryWrite((d, state));
2✔
148

149
        public override void Send(SendOrPostCallback d, object? state)
150
            => throw new InvalidOperationException("Synchronous operations are not supported!");
×
151

152
        public static void Await(Func<Task> taskInvoker)
153
        {
154
            var originalContext = Current;
2✔
155
            try
156
            {
157
                var context = new SingleThreadedSynchronizationContextChannel();
2✔
158
                SetSynchronizationContext(context);
2✔
159

160
                Task task;
161
                try
162
                {
163
                    task = taskInvoker.Invoke();
2✔
164
                }
2✔
165
                catch (Exception ex)
×
166
                {
167
                    // If the invoker throws synchronously, complete the channel so the pump can exit.
168
                    context._channel.Writer.Complete(ex);
×
169
                    throw;
×
170
                }
171

172
                _ = task.ContinueWith(t
2✔
173
                    => context._channel.Writer.Complete(t.Exception),
2✔
174
                    TaskScheduler.Default);
2✔
175

176
                // Pump loop: block synchronously until items are available or the writer completes.
177
                while (context._channel.Reader.WaitToReadAsync().Preserve().GetAwaiter().GetResult())
2✔
178
                {
179
                    while (context._channel.Reader.TryRead(out var work))
2✔
180
                    {
181
                        work.d.Invoke(work.state);
2✔
182
                    }
2✔
183
                }
184

185
                task.GetAwaiter().GetResult();
2✔
186
            }
×
187
            finally
188
            {
189
                SetSynchronizationContext(originalContext);
2✔
190
            }
2✔
191
        }
×
192
    }
193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc