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

DemoBytom / DemoEngine / 24608431965

18 Apr 2026 03:59PM UTC coverage: 30.6% (+0.1%) from 30.483%
24608431965

push

coveralls.net

DemoBytom
Fix unit test hanging indefinitely.

Added explicit timeout.
Fixed the test to setup correct `BlockingDoEvents` method instad `DoEvents`

1291 of 4219 relevant lines covered (30.6%)

0.37 hits per line

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

82.14
/src/Demo.Engine.Core/Services/MainLoopService.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.Diagnostics;
5
using Demo.Engine.Core.Features.StaThread;
6
using Demo.Engine.Core.Interfaces;
7
using Demo.Engine.Core.Interfaces.Rendering;
8
using Demo.Engine.Core.Requests.Keyboard;
9
using Demo.Engine.Core.ValueObjects;
10
using Mediator;
11
using Microsoft.Extensions.Logging;
12

13
namespace Demo.Engine.Core.Services;
14

15
internal sealed class MainLoopService
16
    : IMainLoopService,
17
      IAsyncDisposable
18
{
19
    private readonly ILogger<MainLoopService> _logger;
20
    private readonly IStaThreadWriter _staThreadWriter;
21
    private readonly IMediator _mediator;
22
    private readonly IFpsTimer _fpsTimer;
23
    private readonly IMainLoopLifetime _mainLoopLifetime;
24
    private readonly ILoopJob _loopJob;
25
    private bool _disposedValue;
26

27
    public Task ExecutingTask { get; }
28

29
    public MainLoopService(
1✔
30
        ILogger<MainLoopService> logger,
1✔
31
        IStaThreadWriter staThreadWriter,
1✔
32
        IMediator mediator,
1✔
33
        IFpsTimer fpsTimer,
1✔
34
        IRenderingEngine renderingEngine,
1✔
35
        IMainLoopLifetime mainLoopLifetime,
1✔
36
        ILoopJob loopJob)
1✔
37
    {
38
        _logger = logger;
1✔
39
        _staThreadWriter = staThreadWriter;
1✔
40
        _mediator = mediator;
1✔
41
        _fpsTimer = fpsTimer;
1✔
42
        _mainLoopLifetime = mainLoopLifetime;
1✔
43
        _loopJob = loopJob;
1✔
44
        ExecutingTask = Task.Run(async () =>
1✔
45
        {
1✔
46
            try
1✔
47
            {
1✔
48
                await Task.WhenAll(
1✔
49
                    DoAsync(renderingEngine),
1✔
50
                    DoEventsAsync(renderingEngine));
1✔
51
            }
1✔
52
            catch (TaskCanceledException)
×
53
            {
1✔
54
                _mainLoopLifetime.Cancel();
×
55
            }
×
56
            catch (Exception ex)
×
57
            {
1✔
58
                _logger.LogMainLoopFailedWithError(ex);
×
59
                _mainLoopLifetime.Cancel();
×
60
                //throw;
1✔
61
            }
×
62
        });
1✔
63
    }
1✔
64

65
    private async Task DoAsync(
66
        IRenderingEngine renderingEngine)
67
    {
68
        var keyboardHandle = await _mediator.Send(new KeyboardHandleRequest(), CancellationToken.None);
1✔
69
        var keyboardCharCache = await _mediator.Send(new KeyboardCharCacheRequest(), CancellationToken.None);
1✔
70

71
        //await Task.Delay(10_000);
72
        var surfaces = new List<RenderingSurfaceId>
1✔
73
        {
1✔
74
            await _staThreadWriter.CreateSurface(
1✔
75
                _mainLoopLifetime.Token),
1✔
76
            //await _staThreadWriter.CreateSurface(
1✔
77
            //    _mainLoopLifetime.Token),
1✔
78
        };
1✔
79

80
        if (surfaces is [var mainFormId, ..]
1✔
81
            && renderingEngine.TryGetRenderingSurface(mainFormId, out var mainForm))
1✔
82
        {
83
            mainForm.RenderingControl.RenderingFormClosed += (_, _) => _mainLoopLifetime.Cancel();
1✔
84
        }
85

86
        var previous = Stopwatch.GetTimestamp();
1✔
87
        var lag = TimeSpan.Zero;
1✔
88

89
        var msPerUpdate = TimeSpan.FromSeconds(1) / 60;
1✔
90

91
        var doEventsOk = true;
1✔
92
        var cnt = 0;
1✔
93
        while (
1✔
94
            doEventsOk
1✔
95
            //&& IsRunning
1✔
96
            && !_disposedValue
1✔
97
            && !_mainLoopLifetime.Token.IsCancellationRequested)
1✔
98
        {
99
            var current = Stopwatch.GetTimestamp();
1✔
100
            var elapsed = Stopwatch.GetElapsedTime(previous, current);
1✔
101
            previous = current;
1✔
102
            lag += elapsed;
1✔
103

104
            //process input
105
            // TODO!
106

107
            while (lag >= msPerUpdate)
1✔
108
            {
109
                //Update
110
                // TODO - fix the UPS timer.. somehow :D
111
                _fpsTimer.StopUpdateTimer();
1✔
112
                RenderingSurfaceId? sId = null;
1✔
113

114
                ++cnt;
1✔
115
                foreach (var renderingSurfaceId in surfaces)
1✔
116
                {
117
                    if (!renderingEngine.TryGetRenderingSurface(
118
                        renderingSurfaceId,
1✔
119
                        out var renderingSurface))
1✔
120
                    {
121
                        _logger.LogRenderingSurfaceNotFound(
×
122
                            renderingSurfaceId);
×
123
                        break;
×
124
                    }
125

126
                    await _loopJob.Update(
1✔
127
                          renderingSurface,
1✔
128
                          keyboardHandle,
1✔
129
                          keyboardCharCache);
1✔
130

131
                    if (cnt == 10 * 60)
132
                    {
133
                        sId = await renderingSurface.RenderingControl.CreateSurface(
×
134
                            renderingEngine,
×
135
                            _mainLoopLifetime.Token);
×
136

137
                        cnt = 0;
×
138
                    }
139
                }
1✔
140
                if (sId is not null)
141
                {
142
                    surfaces.Add(sId.Value);
×
143
                    sId = null;
×
144
                }
145
                lag -= msPerUpdate;
1✔
146
                _fpsTimer.StartUpdateTimer();
1✔
147
            }
148

149
            //Render
150
            foreach (var renderingSurfaceId in surfaces)
1✔
151
            {
152
                //doEventsOk &= await _staThreadWriter.DoEventsOk(
153
                //    renderingSurfaceId,
154
                //    _mainLoopLifetime.Token);
155

156
                using var scope = _fpsTimer.StartRenderingTimerScope(
1✔
157
                    renderingSurfaceId);
1✔
158

159
                _loopJob.Render(
1✔
160
                    renderingEngine,
1✔
161
                    renderingSurfaceId);
1✔
162
            }
163
            //await Task.Delay(15).ConfigureAwait(true);
164
        }
165
        _mainLoopLifetime.Cancel();
1✔
166
    }
1✔
167

168
    private long _doEventsCnt = 0;
169
    private async Task DoEventsAsync(
170
        IRenderingEngine renderingEngine)
171
    {
172
        var doEventsOk = true;
1✔
173
        try
174
        {
175
            do
176
            {
177
                var surfaces = renderingEngine.RenderingSurfaces;
1✔
178
                foreach (var renderingSurfaceId in surfaces)
179
                {
180
                    _doEventsCnt += 1;
×
181
                    doEventsOk &= await _staThreadWriter.BlockingDoEventsOk(
×
182
                        renderingSurfaceId.ID,
×
183
                        _mainLoopLifetime.Token);
×
184
                }
185
            } while (doEventsOk
186
                // Currently the unit test relies on those conditions
1✔
187
                && !_disposedValue
1✔
188
                && !_mainLoopLifetime.Token.IsCancellationRequested);
1✔
189
        }
1✔
190
        finally
191
        {
192
#pragma warning disable CA1873 // Avoid potentially expensive logging
193
            _logger.LogInformation("Called DoEvents {Count} times", _doEventsCnt);
1✔
194
#pragma warning restore CA1873 // Avoid potentially expensive logging
195
            _mainLoopLifetime.Cancel();
1✔
196
        }
197
    }
1✔
198

199
    private async ValueTask Dispose(bool disposing)
200
    {
201
        if (!_disposedValue)
1✔
202
        {
203
            if (disposing)
1✔
204
            {
205
            }
206

207
            _disposedValue = true;
1✔
208
            //Make sure the loop finishes
209
            await ExecutingTask;
1✔
210
        }
211
    }
1✔
212

213
    public async ValueTask DisposeAsync()
214
    {
215
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
216
        await Dispose(disposing: true);
1✔
217
        GC.SuppressFinalize(this);
1✔
218
    }
1✔
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