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

DemoBytom / DemoEngine / 13146622201

04 Feb 2025 11:11PM UTC coverage: 10.539% (-0.2%) from 10.779%
13146622201

push

coveralls.net

DemoBytom
Fixing winforms deadlocks

an await operation must return to the thread it was spawned on, because due to using winforms the main thread must be SDA.
But because the windows messages are handled by a loop, and that loop had a async operation - the message to switch back to main thread deadlocks

227 of 2154 relevant lines covered (10.54%)

24286.3 hits per line

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

0.0
/src/Demo.Engine.Core/Services/EngineServiceNew.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.Diagnostics;
6
using System.Numerics;
7
using Demo.Engine.Core.Components.Keyboard;
8
using Demo.Engine.Core.Interfaces;
9
using Demo.Engine.Core.Interfaces.Platform;
10
using Demo.Engine.Core.Interfaces.Rendering;
11
using Demo.Engine.Core.Interfaces.Rendering.Shaders;
12
using Demo.Engine.Core.Platform;
13
using Demo.Engine.Core.Requests.Keyboard;
14
using MediatR;
15
using Microsoft.Extensions.DependencyInjection;
16
using Microsoft.Extensions.Hosting;
17
using Microsoft.Extensions.Logging;
18
using Vortice.Mathematics;
19

20
namespace Demo.Engine.Core.Services;
21

22
internal class EngineServiceNew(
23
    ILogger<EngineServiceNew> logger,
24
    IHostApplicationLifetime hostApplicationLifetime,
25
    IServiceScopeFactory scopeFactory,
26
    IMediator mediator,
27
    IShaderAsyncCompiler shaderCompiler,
28
    IFpsTimer fpsTimer)
29
    : EngineServiceBaseNew(
×
30
        logger,
×
31
        hostApplicationLifetime,
×
32
        scopeFactory)
×
33
{
34
    private readonly IMediator _mediator = mediator;
×
35
    private readonly IShaderAsyncCompiler _shaderCompiler = shaderCompiler;
×
36
    private readonly IFpsTimer _fpsTimer = fpsTimer;
×
37
    private ICube[] _drawables = [];
×
38
    private readonly CancellationTokenSource _loopCancellationTokenSource = new();
×
39
    private bool _disposedValue;
40

41
    protected override async Task DoAsync(
42
        IRenderingEngine renderingEngine,
43
        CancellationToken cancellationToken)
44
    {
45
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
46
            cancellationToken,
×
47
            _loopCancellationTokenSource.Token);
×
48

49
        _ = await _shaderCompiler.CompileShaders(cts.Token);
×
50

51
        var keyboardHandle = await _mediator.Send(new KeyboardHandleRequest(), CancellationToken.None);
×
52
        var keyboardCharCache = await _mediator.Send(new KeyboardCharCacheRequest(), CancellationToken.None);
×
53

54
        _drawables =
×
55
        [
×
56
            _sp!.GetRequiredService<ICube>(),
×
57
        ];
×
58

59
        _staThreadActions.Add(
×
60
            re => re.CreateSurface(),
×
61
            cancellationToken);
×
62

63
        _staThreadActions.Add(
×
64
            re => re.CreateSurface(),
×
65
            cancellationToken);
×
66

67
        while (
×
68
            IsRunning
×
69
            && !cts.Token.IsCancellationRequested)
×
70
        {
71
            foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
72
            {
73
                await Update(
×
74
                      renderingSurface,
×
75
                      keyboardHandle,
×
76
                      keyboardCharCache);
×
77

78
                _fpsTimer.Start();
×
79
                await Render(
×
80
                    renderingEngine,
×
81
                    renderingSurface.ID);
×
82
                _fpsTimer.Stop();
×
83
            }
×
84
        }
85
    }
×
86

87
    private readonly BlockingCollection<Action<IRenderingEngine>> _staThreadActions = [];
×
88

89
    protected override async Task STAThread(
90
        IRenderingEngine renderingEngine,
91
        IOSMessageHandler osMessageHandler,
92
        CancellationToken cancellationToken)
93
    {
94
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
95
            cancellationToken,
×
96
            _loopCancellationTokenSource.Token);
×
97

98
        var doEventsOk = true;
×
99

100
        using var periodicTimer = new PeriodicTimer(
×
101
            TimeSpan.FromSeconds(1) / 60);
×
102

103
        var timestamp = Stopwatch.GetTimestamp();
×
104
        while (await periodicTimer.WaitForNextTickAsync(cts.Token)
×
105
                && IsRunning
×
106
                && doEventsOk
×
107
                && !cts.Token.IsCancellationRequested)
×
108
        {
109
            _logger.LogTrace("Tick! {elapsedMS}",
×
110
                Stopwatch.GetElapsedTime(timestamp).Milliseconds);
×
111
            timestamp = Stopwatch.GetTimestamp();
×
112

113
            while (_staThreadActions.TryTake(
×
114
                out var staAction))
×
115
            {
116
                staAction(renderingEngine);
×
117
            }
×
118

119
            foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
120
            {
121
                doEventsOk &= osMessageHandler.DoEvents(
×
122
                        renderingSurface.RenderingControl);
×
123
            }
124
        }
125
    }
×
126

127
    private float _r, _g, _b = 0.0f;
128

129
    private float _sin = 0.0f;
130
    private bool _fullscreen = false;
131
    private bool _f11Pressed = false;
132

133
    private ValueTask Update(
134
        IRenderingSurface renderingSurface,
135
        KeyboardHandle keyboardHandle,
136
        KeyboardCharCache keyboardCharCache)
137
    {
138
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemOpenBrackets))
×
139
        {
140
            keyboardCharCache.Clear();
×
141
        }
142
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemCloseBrackets))
×
143
        {
144
            var str = keyboardCharCache?.ReadCache();
×
145
            if (!string.IsNullOrEmpty(str))
×
146
            {
147
                _logger.LogInformation(str);
×
148
            }
149
        }
150
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Escape))
×
151
        {
152
            _loopCancellationTokenSource.Cancel();
×
153
            foreach (var drawable in _drawables)
×
154
            {
155
                (drawable as IDisposable)?.Dispose();
×
156
            }
157

158
            _drawables = [];
×
159
            return ValueTask.CompletedTask;
×
160
        }
161
        if (keyboardHandle.GetKeyPressed(VirtualKeys.F11))
×
162
        {
163
            if (!_f11Pressed)
×
164
            {
165
                _fullscreen = !_fullscreen;
×
166
            }
167
            _f11Pressed = true;
×
168
        }
169
        else
170
        {
171
            _f11Pressed = false;
×
172
        }
173

174
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Back)
×
175
            && _drawables.ElementAtOrDefault(0) is IDisposable d)
×
176
        {
177
            Debug.WriteLine("Removing cube!");
178

179
            _drawables = _drawables.Length > 0
×
180
                ? _drawables[1..]
×
181
                : [];
×
182

183
            d?.Dispose();
×
184

185
            Debug.WriteLine("Cube removed!");
186
        }
187
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Enter)
×
188
            && _drawables.Length < 2
×
189
            && _sp is not null)
×
190
        {
191
            Debug.WriteLine("Adding new Cube!");
192
            _drawables = new List<ICube>(_drawables)
×
193
                {
×
194
                    _sp.GetRequiredService<ICube>()
×
195
                }.ToArray();
×
196
            Debug.WriteLine("Cube added!!");
197
        }
198

199
        //if (_drawables.Length == 2)
200
        //{
201
        //    foreach (var drawable in _drawables)
202
        //    {
203
        //        (drawable as IDisposable)?.Dispose();
204
        //    }
205

206
        //    _drawables = Array.Empty<ICube>();
207
        //}
208
        //else if (_drawables.Length < 2 && _sp is not null /*&& _dontCreate == false*/)
209
        //{
210
        //    _drawables = new List<ICube>(_drawables)
211
        //        {
212
        //            _sp.GetRequiredService<ICube>()
213
        //        }.ToArray();
214
        //    //_dontCreate = true;
215
        //}
216

217
        //Share the rainbow
218
        _r = MathF.Sin((_sin + 0) * MathF.PI / 180);
×
219
        _g = MathF.Sin((_sin + 120) * MathF.PI / 180);
×
220
        _b = MathF.Sin((_sin + 240) * MathF.PI / 180);
×
221

222
        //Taste the rainbow
223
        if (++_sin > 360)
×
224
        {
225
            _sin = 0;
×
226
        }
227

228
        _drawables.ElementAtOrDefault(0)
×
229
            ?.Update(renderingSurface, Vector3.Zero, _angleInRadians);
×
230
        _drawables.ElementAtOrDefault(1)
×
231
            ?.Update(renderingSurface, new Vector3(0.5f, 0.0f, -0.5f), -_angleInRadians * 1.5f);
×
232

233
        return ValueTask.CompletedTask;
×
234
    }
235

236
    /// <summary>
237
    /// https://bitbucket.org/snippets/DemoBytom/aejA59/maps-value-between-from-one-min-max-range
238
    /// </summary>
239
    public static float Map(float value, float inMin, float inMax, float outMin, float outMax)
240
        => ((value - inMin) * (outMax - outMin) / (inMax - inMin)) + outMin;
×
241

242
    private float _angleInRadians = 0.0f;
243
    private const float TWO_PI = MathHelper.TwoPi;
244

245
    private ValueTask Render(
246
        IRenderingEngine renderingEngine,
247
        Guid renderingSurfaceId)
248
    {
249
        _angleInRadians = (_angleInRadians + 0.01f) % TWO_PI;
×
250

251
        renderingEngine.Draw(
×
252
            color: new Color4(_r, _g, _b, 1.0f),
×
253
            renderingSurfaceId: renderingSurfaceId,
×
254
            drawables: _drawables);
×
255

256
        //if (renderingEngine.Control.IsFullscreen != _fullscreen)
257
        //{
258
        //    renderingEngine.SetFullscreen(_fullscreen);
259
        //}
260

261
        return ValueTask.CompletedTask;
×
262
    }
263

264
    protected override void Dispose(bool disposing)
265
    {
266
        if (!_disposedValue)
×
267
        {
268
            if (disposing)
×
269
            {
270
                _loopCancellationTokenSource.Dispose();
×
271

272
                foreach (var drawable in _drawables)
×
273
                {
274
                    if (drawable is IDisposable disposable)
×
275
                    {
276
                        disposable.Dispose();
×
277
                    }
278
                }
279

280
                _disposedValue = true;
×
281
            }
282
            base.Dispose(disposing);
×
283
        }
284
    }
×
285
}
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