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

DemoBytom / DemoEngine / 13189407317

06 Feb 2025 10:50PM UTC coverage: 10.456% (-0.08%) from 10.539%
13189407317

push

coveralls.net

DemoBytom
Fixed step update loop

Added a more proper fixed step update loop. Update is triggered 60 times per second (60 UPS) while Rendering is currently unbounded

227 of 2171 relevant lines covered (10.46%)

23507.79 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.Diagnostics;
5
using System.Numerics;
6
using System.Threading.Channels;
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
    internal record StaThreadWork(
×
42
        Action<IRenderingEngine> Action);
×
43

44
    private readonly Channel<StaThreadWork> _channel = Channel.CreateBounded<StaThreadWork>(
×
45
        new BoundedChannelOptions(10)
×
46
        {
×
47
            AllowSynchronousContinuations = false,
×
48
            FullMode = BoundedChannelFullMode.Wait,
×
49
            SingleReader = true,
×
50
            SingleWriter = false,
×
51
        });
×
52

53
    protected override async Task DoAsync(
54
        IRenderingEngine renderingEngine,
55
        CancellationToken cancellationToken)
56
    {
57
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
58
            cancellationToken,
×
59
            _loopCancellationTokenSource.Token);
×
60

61
        _ = await _shaderCompiler.CompileShaders(cts.Token);
×
62

63
        var keyboardHandle = await _mediator.Send(new KeyboardHandleRequest(), CancellationToken.None);
×
64
        var keyboardCharCache = await _mediator.Send(new KeyboardCharCacheRequest(), CancellationToken.None);
×
65

66
        _drawables =
×
67
        [
×
68
            _sp!.GetRequiredService<ICube>(),
×
69
        ];
×
70

71
        await _channel.Writer.WriteAsync(
×
72
            new StaThreadWork(
×
73
                re => re.CreateSurface()),
×
74
            cts.Token);
×
75

76
        await _channel.Writer.WriteAsync(
×
77
            new StaThreadWork(
×
78
                re => re.CreateSurface()),
×
79
            cts.Token);
×
80

81
        var previous = Stopwatch.GetTimestamp();
×
82
        var lag = TimeSpan.Zero;
×
83

84
        var msPerUpdate = TimeSpan.FromSeconds(1) / 60;
×
85

86
        while (
×
87
            IsRunning
×
88
            && !cts.Token.IsCancellationRequested)
×
89
        {
90
            var current = Stopwatch.GetTimestamp();
×
91
            var elapsed = Stopwatch.GetElapsedTime(previous, current);
×
92
            previous = current;
×
93
            lag += elapsed;
×
94
            //process input
95

96
            while (lag >= msPerUpdate)
×
97
            {
98
                //Update
99
                foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
100
                {
101
                    await Update(
×
102
                          renderingSurface,
×
103
                          keyboardHandle,
×
104
                          keyboardCharCache);
×
105

106
                    lag -= msPerUpdate;
×
107
                }
108
            }
109

110
            //Render
111
            foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
112
            {
113
                _fpsTimer.Start();
×
114
                await Render(
×
115
                    renderingEngine,
×
116
                    renderingSurface.ID);
×
117
                _fpsTimer.Stop();
×
118
            }
119
        }
120
    }
×
121

122
    protected override async Task STAThread(
123
        IRenderingEngine renderingEngine,
124
        IOSMessageHandler osMessageHandler,
125
        CancellationToken cancellationToken)
126
    {
127
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
128
            cancellationToken,
×
129
            _loopCancellationTokenSource.Token);
×
130

131
        var doEventsOk = true;
×
132

133
        using var periodicTimer = new PeriodicTimer(
×
134
            TimeSpan.FromSeconds(1) / 60);
×
135

136
        var timestamp = Stopwatch.GetTimestamp();
×
137

138
        while (await periodicTimer.WaitForNextTickAsync(cts.Token)
×
139
                && IsRunning
×
140
                && doEventsOk
×
141
                && !cts.Token.IsCancellationRequested)
×
142
        {
143
            //_logger.LogTrace("Tick! {elapsedMS}",
144
            //    Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds);
145
            timestamp = Stopwatch.GetTimestamp();
×
146

147
            while (_channel.Reader.TryRead(out var staAction))
×
148
            {
149
                staAction.Action.Invoke(renderingEngine);
×
150
            }
×
151
            foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
152
            {
153
                doEventsOk &= osMessageHandler.DoEvents(
×
154
                        renderingSurface.RenderingControl);
×
155
            }
156
        }
157
    }
×
158

159
    private float _r, _g, _b = 0.0f;
160

161
    private float _sin = 0.0f;
162
    private bool _fullscreen = false;
163
    private bool _f11Pressed = false;
164

165
    private ValueTask Update(
166
        IRenderingSurface renderingSurface,
167
        KeyboardHandle keyboardHandle,
168
        KeyboardCharCache keyboardCharCache)
169
    {
170
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemOpenBrackets))
×
171
        {
172
            keyboardCharCache.Clear();
×
173
        }
174
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemCloseBrackets))
×
175
        {
176
            var str = keyboardCharCache?.ReadCache();
×
177
            if (!string.IsNullOrEmpty(str))
×
178
            {
179
                _logger.LogInformation(str);
×
180
            }
181
        }
182
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Escape))
×
183
        {
184
            _loopCancellationTokenSource.Cancel();
×
185
            foreach (var drawable in _drawables)
×
186
            {
187
                (drawable as IDisposable)?.Dispose();
×
188
            }
189

190
            _drawables = [];
×
191
            return ValueTask.CompletedTask;
×
192
        }
193
        if (keyboardHandle.GetKeyPressed(VirtualKeys.F11))
×
194
        {
195
            if (!_f11Pressed)
×
196
            {
197
                _fullscreen = !_fullscreen;
×
198
            }
199
            _f11Pressed = true;
×
200
        }
201
        else
202
        {
203
            _f11Pressed = false;
×
204
        }
205

206
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Back)
×
207
            && _drawables.ElementAtOrDefault(0) is IDisposable d)
×
208
        {
209
            Debug.WriteLine("Removing cube!");
210

211
            _drawables = _drawables.Length > 0
×
212
                ? _drawables[1..]
×
213
                : [];
×
214

215
            d?.Dispose();
×
216

217
            Debug.WriteLine("Cube removed!");
218
        }
219
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Enter)
×
220
            && _drawables.Length < 2
×
221
            && _sp is not null)
×
222
        {
223
            Debug.WriteLine("Adding new Cube!");
224
            _drawables = new List<ICube>(_drawables)
×
225
                {
×
226
                    _sp.GetRequiredService<ICube>()
×
227
                }.ToArray();
×
228
            Debug.WriteLine("Cube added!!");
229
        }
230

231
        //if (_drawables.Length == 2)
232
        //{
233
        //    foreach (var drawable in _drawables)
234
        //    {
235
        //        (drawable as IDisposable)?.Dispose();
236
        //    }
237

238
        //    _drawables = Array.Empty<ICube>();
239
        //}
240
        //else if (_drawables.Length < 2 && _sp is not null /*&& _dontCreate == false*/)
241
        //{
242
        //    _drawables = new List<ICube>(_drawables)
243
        //        {
244
        //            _sp.GetRequiredService<ICube>()
245
        //        }.ToArray();
246
        //    //_dontCreate = true;
247
        //}
248

249
        //Share the rainbow
250
        _r = MathF.Sin((_sin + 0) * MathF.PI / 180);
×
251
        _g = MathF.Sin((_sin + 120) * MathF.PI / 180);
×
252
        _b = MathF.Sin((_sin + 240) * MathF.PI / 180);
×
253

254
        //Taste the rainbow
255
        if (++_sin > 360)
×
256
        {
257
            _sin = 0;
×
258
        }
259
        _angleInRadians = (_angleInRadians + 0.01f) % TWO_PI;
×
260

261
        _drawables.ElementAtOrDefault(0)
×
262
            ?.Update(renderingSurface, Vector3.Zero, _angleInRadians);
×
263
        _drawables.ElementAtOrDefault(1)
×
264
            ?.Update(renderingSurface, new Vector3(0.5f, 0.0f, -0.5f), -_angleInRadians * 1.5f);
×
265

266
        return ValueTask.CompletedTask;
×
267
    }
268

269
    /// <summary>
270
    /// https://bitbucket.org/snippets/DemoBytom/aejA59/maps-value-between-from-one-min-max-range
271
    /// </summary>
272
    public static float Map(float value, float inMin, float inMax, float outMin, float outMax)
273
        => ((value - inMin) * (outMax - outMin) / (inMax - inMin)) + outMin;
×
274

275
    private float _angleInRadians = 0.0f;
276
    private const float TWO_PI = MathHelper.TwoPi;
277

278
    private ValueTask Render(
279
        IRenderingEngine renderingEngine,
280
        Guid renderingSurfaceId)
281
    {
282
        renderingEngine.Draw(
×
283
            color: new Color4(_r, _g, _b, 1.0f),
×
284
            renderingSurfaceId: renderingSurfaceId,
×
285
            drawables: _drawables);
×
286

287
        //if (renderingEngine.Control.IsFullscreen != _fullscreen)
288
        //{
289
        //    renderingEngine.SetFullscreen(_fullscreen);
290
        //}
291

292
        return ValueTask.CompletedTask;
×
293
    }
294

295
    protected override void Dispose(bool disposing)
296
    {
297
        if (!_disposedValue)
×
298
        {
299
            if (disposing)
×
300
            {
301
                _loopCancellationTokenSource.Dispose();
×
302

303
                foreach (var drawable in _drawables)
×
304
                {
305
                    if (drawable is IDisposable disposable)
×
306
                    {
307
                        disposable.Dispose();
×
308
                    }
309
                }
310

311
                _disposedValue = true;
×
312
            }
313
            base.Dispose(disposing);
×
314
        }
315
    }
×
316
}
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