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

DemoBytom / DemoEngine / 24967157351

26 Apr 2026 09:11PM UTC coverage: 28.482% (+0.2%) from 28.251%
24967157351

push

coveralls.net

DemoBytom
Add OpenTelemetry to engine

1225 of 4301 relevant lines covered (28.48%)

0.32 hits per line

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

97.01
/src/Demo.Engine.Platform.Windows/WindowsMessagePump.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 Demo.Engine.Core.Features.StaThread;
6
using Demo.Engine.Core.Maths.Interop;
7
using Microsoft.Extensions.Hosting;
8
using Microsoft.Extensions.Logging;
9

10
namespace Demo.Engine.Platform.Windows;
11

12
internal sealed class WindowsMessagePump
13
    : BackgroundService
14
    , IAsyncDisposable
15
{
16
    private bool _disposedValue;
17
    private readonly Thread _thread;
18
    private readonly ILogger<WindowsMessagePump> _logger;
19
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
20
    private readonly IStaThreadReader _staThreadReader;
21
    private readonly CancellationTokenSource _cancelStaRequestHandler;
22
    private readonly Task _staRequestHandler;
23

24
    private readonly TaskCompletionSource<Control> _marshallingControl = new(
1✔
25
        TaskCreationOptions.RunContinuationsAsynchronously);
1✔
26
    private readonly TaskCompletionSource _staThreadJob = new(
1✔
27
        TaskCreationOptions.RunContinuationsAsynchronously);
1✔
28

29
    public WindowsMessagePump(
1✔
30
        ILogger<WindowsMessagePump> logger,
1✔
31
        IHostApplicationLifetime hostApplicationLifetime,
1✔
32
        IStaThreadReader staThreadReader)
1✔
33
    {
34
        _thread = new Thread(ThreadInner)
1✔
35
        {
1✔
36
            IsBackground = false,
1✔
37
        };
1✔
38

39
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
40
        {
41
            _thread.SetApartmentState(ApartmentState.STA);
1✔
42
            _thread.Name = "Main STA thread";
1✔
43
        }
44
        else
45
        {
46
            throw new InvalidOperationException(
47
                "This is Windows Only pump!");
48
        }
49

50
        _logger = logger;
1✔
51
        _hostApplicationLifetime = hostApplicationLifetime;
1✔
52
        _staThreadReader = staThreadReader;
1✔
53
        _cancelStaRequestHandler = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping);
1✔
54

55
        _staRequestHandler = RunStaRequestHandler();
1✔
56
    }
1✔
57

58
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
59
    {
60
        _thread.Start();
1✔
61

62
        return _staThreadJob.Task;
1✔
63
    }
64

65
    private async Task RunStaRequestHandler()
66
    {
67
        var cancellationToken = _cancelStaRequestHandler.Token;
1✔
68
        try
69
        {
70
            var marshallingControl = await _marshallingControl.Task.WaitAsync(cancellationToken);
1✔
71

72
            await _staThreadReader.Invoke(
1✔
73
                marshallingControl.InvokeAsync,
1✔
74
                cancellationToken);
1✔
75
        }
76
        catch (OperationCanceledException)
1✔
77
        {
78
        }
1✔
79
    }
1✔
80

81
    private unsafe void ThreadInner()
82
    {
83
        try
84
        {
85
            var cancellationToken = _hostApplicationLifetime.ApplicationStopping;
1✔
86
            var marshallingControl = new MarshallingControl();
1✔
87
            _marshallingControl.SetResult(marshallingControl);
1✔
88

89
            using var tokenRegistration = cancellationToken.Register(
1✔
90
                () => marshallingControl.Invoke(
1✔
91
                    () => User32.PostQuitMessage(0)));
1✔
92

93
            NativeMessage msg;
94
            RawBool getMessageW;
95

96
            while (getMessageW = User32
1✔
97
                .GetMessageW(
1✔
98
                    lpMsg: &msg,
1✔
99
                    hWnd: IntPtr.Zero,
1✔
100
                    wMsgFilterMin: 0,
1✔
101
                    wMsgFilterMax: 0)
1✔
102
                == true)
1✔
103
            {
104
                if ((int)getMessageW == -1)
105
                {
106
                    _logger.LogErroInMainLoopProcessingWindowsMessages(
107
                        Marshal.GetLastWin32Error);
108

109
                    _hostApplicationLifetime.StopApplication();
110
                    return;
111
                }
112

113
                var message = Message.Create(
1✔
114
                    hWnd: msg.HWnd,
1✔
115
                    msg: (int)(nint)msg.Message,
1✔
116
                    wparam: (nint)msg.WParam,
1✔
117
                    lparam: msg.LParam);
1✔
118

119
                if (!Application.FilterMessage(ref message))
1✔
120
                {
121
                    _ = User32.TranslateMessage(&msg);
1✔
122
                    _ = User32.DispatchMessageW(&msg);
1✔
123
                }
124
            }
125
        }
1✔
126
        catch (Exception ex)
127
        {
128
            _logger.LogExceptionInMessageLoopProcessingWindowsMessages(ex);
129
        }
130
        finally
131
        {
132
            _ = _staThreadJob.TrySetResult();
1✔
133
        }
1✔
134
    }
1✔
135

136
    public async ValueTask DisposeAsync()
137
    {
138
        if (!_disposedValue)
139
        {
140
            _cancelStaRequestHandler.Cancel();
141
            _cancelStaRequestHandler.Dispose();
142

143
            await AwaitAndLogAnyErrors(_staRequestHandler.WaitAsync)
144
                .ConfigureAwait(false);
145

146
            _thread.Join();
147

148
            await AwaitAndLogAnyErrors(async ct =>
149
            {
150
                var marshallingCtrl = await _marshallingControl.Task
151
                    .WaitAsync(ct)
152
                    .ConfigureAwait(false);
153

154
                marshallingCtrl.Dispose();
155
            }).ConfigureAwait(false);
156

157
            _hostApplicationLifetime.StopApplication();
158
        }
159
        _disposedValue = true;
160
        GC.SuppressFinalize(this);
161

162
        return;
163

164
        /* local function */
165
        async ValueTask AwaitAndLogAnyErrors(
166
            Func<CancellationToken, Task> callback)
167
        {
168
            try
169
            {
170
                using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
171
                await callback(cts.Token).ConfigureAwait(false);
172
            }
173
            catch (Exception ex)
174
            {
175
                _logger.LogExceptionDisposingWindowsMessagePump(ex);
176
            }
177
        }
178
    }
179

180
    /// <summary>
181
    /// Marshalling control used to dispatch messages to the main STA thread that created it
182
    /// </summary>
183
    private sealed class MarshallingControl : Control
184
    {
185
#pragma warning disable IDE1006 // Naming Styles
186
        private static readonly IntPtr HWND_MESSAGE = new(-3);
2✔
187
#pragma warning restore IDE1006 // Naming Styles
188

189
        public MarshallingControl()
2✔
190
        {
191
            Visible = false;
2✔
192
            SetTopLevel(false);
2✔
193
            CreateControl();
2✔
194
            CreateHandle();
2✔
195
        }
2✔
196

197
        protected override CreateParams CreateParams
198
        {
199
            get
200
            {
201
                var cp = base.CreateParams;
2✔
202

203
                // Message only windows are cheaper and have fewer issues than full blown invisible windows.
204
                cp.Parent = HWND_MESSAGE;
2✔
205
                return cp;
2✔
206
            }
207
        }
208

209
        protected override void OnLayout(LayoutEventArgs levent)
210
        {
211

212
        }
×
213

214
        protected override void OnSizeChanged(EventArgs e)
215
        {
216
            // Don't do anything here -- small perf game of avoiding layout, etc.
217
        }
×
218
    }
219
}
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