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

DemoBytom / DemoEngine / 13296199563

12 Feb 2025 10:32PM UTC coverage: 10.129% (-0.01%) from 10.143%
13296199563

push

coveralls.net

DemoBytom
Moving main loop into a MainLoopService class

227 of 2241 relevant lines covered (10.13%)

22869.19 hits per line

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

0.0
/src/Demo.Engine.Core/Services/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.Collections.Concurrent;
5
using System.Runtime.InteropServices;
6
using System.Threading.Channels;
7
using Demo.Engine.Core.Interfaces;
8
using Demo.Engine.Core.Interfaces.Platform;
9
using Demo.Engine.Core.Interfaces.Rendering;
10
using Microsoft.Extensions.Hosting;
11

12
namespace Demo.Engine.Core.Services;
13

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

23
    public Task ExecutingTask { get; }
×
24
    public bool IsRunning { get; private set; }
×
25

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

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

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

×
62
                IsRunning = false;
×
63
                tcs.SetResult();
×
64
            }
×
65
            catch (OperationCanceledException)
×
66
            {
×
67
                IsRunning = false;
×
68
                tcs.SetResult();
×
69
            }
×
70
            catch (Exception ex)
×
71
            {
×
72
                tcs.SetException(ex);
×
73
            }
×
74
        });
×
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);
×
79
        }
80

81
        thread.Start();
×
82

83
        return tcs.Task;
×
84
    }
85

86
    private async Task STAThread(
87
        IRenderingEngine renderingEngine,
88
        IOSMessageHandler osMessageHandler,
89
        CancellationToken cancellationToken)
90
    {
91
        var doEventsOk = true;
×
92
        while (
×
93
            await _channelReader.WaitToReadAsync(cancellationToken)
×
94
                && IsRunning
×
95
                && doEventsOk
×
96
                && !cancellationToken.IsCancellationRequested)
×
97
        {
98
            while (
×
99
            doEventsOk
×
100
                && _channelReader.TryRead(out var staAction))
×
101
            {
102
                if (staAction is StaThreadRequests.DoEventsOkRequest doEventsOkRequest)
×
103
                {
104
                    doEventsOk &= doEventsOkRequest.Invoke(renderingEngine, osMessageHandler);
×
105
                }
106
                else
107
                {
108
                    _ = staAction.Invoke(renderingEngine, osMessageHandler);
×
109
                }
110
            }
×
111
        }
112

113
        _mainLoopLifetime.Cancel();
×
114
    }
×
115

116
    private void Dispose(bool disposing)
117
    {
118
        if (!_disposedValue)
×
119
        {
120
            if (disposing)
×
121
            {
122
            }
123

124
            _disposedValue = true;
×
125
        }
126
    }
×
127

128
    public void Dispose()
129
    {
130
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
131
        Dispose(disposing: true);
×
132
        GC.SuppressFinalize(this);
×
133
    }
×
134

135
    private sealed class SingleThreadedSynchronizationContext
136
            : SynchronizationContext
137
    {
138
        private readonly BlockingCollection<(SendOrPostCallback d, object? state)> _queue = [];
×
139

140
        public override void Post(SendOrPostCallback d, object? state)
141
            => _queue.Add((d, state));
×
142

143
        public override void Send(SendOrPostCallback d, object? state)
144
            => throw new InvalidOperationException(
×
145
                "Synchronous operations are not supported!");
×
146

147
        public static void Await(
148
            Func<Task> taskInvoker)
149
        {
150
            var originalContext = Current;
×
151
            try
152
            {
153
                var context = new SingleThreadedSynchronizationContext();
×
154
                SetSynchronizationContext(context);
×
155

156
                var task = taskInvoker.Invoke();
×
157
                _ = task.ContinueWith(_ => context._queue.CompleteAdding());
×
158

159
                while (context._queue.TryTake(out var work, Timeout.Infinite))
×
160
                {
161
                    work.d.Invoke(work.state);
×
162
                }
×
163

164
                task.GetAwaiter().GetResult();
×
165
            }
×
166
            finally
167
            {
168
                SetSynchronizationContext(originalContext);
×
169
            }
×
170
        }
×
171
    }
172
}
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