• 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

80.56
/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 DoAsync(renderingEngine);
1✔
49
            }
1✔
50
            catch (TaskCanceledException)
×
51
            {
1✔
52
                _mainLoopLifetime.Cancel();
×
53
            }
×
54
            catch (Exception ex)
×
55
            {
1✔
56
                _logger.LogMainLoopFailedWithError(ex);
×
57
                _mainLoopLifetime.Cancel();
×
58
            }
×
59
        });
1✔
60
    }
1✔
61

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

68
        var surfaces = new List<RenderingSurfaceId>
1✔
69
        {
1✔
70
            await _staThreadWriter.CreateSurface(
1✔
71
                renderingEngine,
1✔
72
                _mainLoopLifetime.Token),
1✔
73
            //await _staThreadWriter.CreateSurface(
1✔
74
            //    _mainLoopLifetime.Token),
1✔
75
        };
1✔
76

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

83
        var previous = Stopwatch.GetTimestamp();
1✔
84
        var lag = TimeSpan.Zero;
1✔
85

86
        var msPerUpdate = TimeSpan.FromSeconds(1) / 60;
1✔
87

88
        var cnt = 0;
1✔
89
        var activityConuter = 0;
1✔
90

91
        var loopActivity = Instrumentation.ActivitySource.StartActivity(
1✔
92
                "MainLoop",
1✔
93
                ActivityKind.Internal);
1✔
94

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

105
            //process input
106
            // TODO!
107

108
            while (lag >= msPerUpdate)
1✔
109
            {
110
                if (++activityConuter >= 60)
111
                {
112
                    loopActivity?.Dispose();
×
113
                    loopActivity = Instrumentation.ActivitySource.StartActivity(
×
114
                        "MainLoop",
×
115
                        ActivityKind.Internal);
×
116

117
                    activityConuter = 0;
×
118
                }
119
                using var updateActivity = Instrumentation.ActivitySource.StartActivity(
1✔
120
                    "Update",
1✔
121
                    ActivityKind.Internal);
1✔
122

123
                //Update
124
                // TODO - fix the UPS timer.. somehow :D
125
                _fpsTimer.StopUpdateTimer();
1✔
126
                RenderingSurfaceId? sId = null;
1✔
127

128
                ++cnt;
1✔
129
                foreach (var renderingSurfaceId in surfaces)
1✔
130
                {
131
                    if (!renderingEngine.TryGetRenderingSurface(
132
                        renderingSurfaceId,
1✔
133
                        out var renderingSurface))
1✔
134
                    {
135
                        _logger.LogRenderingSurfaceNotFound(
×
136
                            renderingSurfaceId);
×
137
                        break;
×
138
                    }
139

140
                    await _loopJob.Update(
1✔
141
                          renderingSurface,
1✔
142
                          keyboardHandle,
1✔
143
                          keyboardCharCache);
1✔
144

145
                    if (cnt == 10 * 60)
146
                    {
147
                        sId = await _staThreadWriter.CreateSurface(
×
148
                            renderingEngine,
×
149
                            _mainLoopLifetime.Token);
×
150

151
                        cnt = 0;
×
152
                    }
153
                }
154
                if (sId is not null)
155
                {
156
                    surfaces.Add(sId.Value);
×
157
                    sId = null;
×
158
                }
159
                lag -= msPerUpdate;
1✔
160
                _fpsTimer.StartUpdateTimer();
1✔
161
            }
1✔
162

163
            //Render
164
            foreach (var renderingSurfaceId in surfaces)
1✔
165
            {
166
                //using var renderActivity = Instrumentation.ActivitySource.StartActivity(
167
                //    "Render",
168
                //    ActivityKind.Internal);
169
                using var scope = _fpsTimer.StartRenderingTimerScope(
1✔
170
                    renderingSurfaceId);
1✔
171

172
                _loopJob.Render(
1✔
173
                    renderingEngine,
1✔
174
                    renderingSurfaceId);
1✔
175
            }
176
        }
177
        loopActivity?.Dispose();
178
        _mainLoopLifetime.Cancel();
1✔
179
    }
1✔
180

181
    private async ValueTask Dispose(bool disposing)
182
    {
183
        if (!_disposedValue)
1✔
184
        {
185
            if (disposing)
1✔
186
            {
187
            }
188

189
            _disposedValue = true;
1✔
190
            //Make sure the loop finishes
191
            _mainLoopLifetime.Cancel();
1✔
192
            await ExecutingTask;
1✔
193
        }
194
    }
1✔
195

196
    public async ValueTask DisposeAsync()
197
    {
198
        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
199
        await Dispose(disposing: true);
1✔
200
        GC.SuppressFinalize(this);
1✔
201
    }
1✔
202
}
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