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

DemoBytom / DemoEngine / 13230565482

09 Feb 2025 11:21PM UTC coverage: 10.29% (-0.2%) from 10.456%
13230565482

push

coveralls.net

DemoBytom
Further refactoring the main loop WIP

227 of 2206 relevant lines covered (10.29%)

22935.06 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 abstract record StaThreadWork
42
    {
43
        private TaskCompletionSource<bool> _tcs = new();
×
44

45
        public Task<bool> Invoked => _tcs.Task;
×
46

47
        protected abstract bool InvokeFuncInternal(
48
            IRenderingEngine renderingEngine,
49
            IOSMessageHandler osMessageHandler);
50
        public bool Invoke(
51
            IRenderingEngine renderingEngine,
52
            IOSMessageHandler osMessageHandler)
53
        {
54
            if (Invoked.IsCompleted)
×
55
            {
56
                return Invoked.Result;
×
57
            }
58

59
            try
60
            {
61
                var returnValue = InvokeFuncInternal(renderingEngine, osMessageHandler);
×
62
                _tcs.SetResult(returnValue);
×
63
                return returnValue;
×
64
            }
65
            catch (Exception ex)
×
66
            {
67
                _tcs.SetException(ex);
×
68
            }
×
69
            return false;
×
70
        }
×
71

72
        protected void Reset()
73
        {
74
            if (!_tcs.Task.IsCompleted)
×
75
            {
76
                _ = _tcs.TrySetException(
×
77
                    new TaskCanceledException("Message reset!"));
×
78
            }
79
            _tcs = new TaskCompletionSource<bool>();
×
80
        }
×
81
    }
82

83
    internal sealed record CreateSurface()
×
84
        : StaThreadWork
85
    {
86
        protected override bool InvokeFuncInternal(
87
            IRenderingEngine renderingEngine,
88
            IOSMessageHandler osMessageHandler)
89
        {
90
            renderingEngine.CreateSurface();
×
91
            return true;
×
92
        }
93
    }
94

95
    internal sealed record DoEventsOk
96
        : StaThreadWork
97
    {
98
        private IRenderingSurface _renderingSurface;
99

100
        public DoEventsOk(
×
101
            IRenderingSurface renderingSurface)
×
102
            => _renderingSurface = renderingSurface;
×
103

104
        protected override bool InvokeFuncInternal(
105
            IRenderingEngine renderingEngine,
106
            IOSMessageHandler osMessageHandler)
107
            => osMessageHandler.DoEvents(
×
108
                _renderingSurface.RenderingControl);
×
109

110
        public void Reset(
111
            IRenderingSurface renderingSurface)
112
        {
113
            Reset();
×
114
            _renderingSurface = renderingSurface;
×
115
        }
×
116
    }
117

118
    private readonly Channel<StaThreadWork> _channel = Channel.CreateBounded<StaThreadWork>(
×
119
        new BoundedChannelOptions(10)
×
120
        {
×
121
            AllowSynchronousContinuations = false,
×
122
            FullMode = BoundedChannelFullMode.Wait,
×
123
            SingleReader = true,
×
124
            SingleWriter = false,
×
125
        });
×
126

127
    protected override async Task DoAsync(
128
        IRenderingEngine renderingEngine,
129
        CancellationToken cancellationToken)
130
    {
131
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
132
            cancellationToken,
×
133
            _loopCancellationTokenSource.Token);
×
134

135
        _ = await _shaderCompiler.CompileShaders(cts.Token);
×
136

137
        var keyboardHandle = await _mediator.Send(new KeyboardHandleRequest(), CancellationToken.None);
×
138
        var keyboardCharCache = await _mediator.Send(new KeyboardCharCacheRequest(), CancellationToken.None);
×
139

140
        _drawables =
×
141
        [
×
142
            _sp!.GetRequiredService<ICube>(),
×
143
        ];
×
144

145
        await _channel.Writer.WriteAsync(
×
146
            new CreateSurface(),
×
147
            cts.Token);
×
148

149
        await _channel.Writer.WriteAsync(
×
150
            new CreateSurface(),
×
151
            cts.Token);
×
152

153
        var previous = Stopwatch.GetTimestamp();
×
154
        var lag = TimeSpan.Zero;
×
155

156
        var msPerUpdate = TimeSpan.FromSeconds(1) / 60;
×
157

158
        var doEventsFunc = new DoEventsOk(null!);
×
159

160
        var doEventsOk = true;
×
161

162
        while (
×
163
            doEventsOk
×
164
            && IsRunning
×
165
            && !cts.Token.IsCancellationRequested)
×
166
        {
167
            var current = Stopwatch.GetTimestamp();
×
168
            var elapsed = Stopwatch.GetElapsedTime(previous, current);
×
169
            previous = current;
×
170
            lag += elapsed;
×
171
            //process input
172

173
            while (lag >= msPerUpdate)
×
174
            {
175
                //Update
176
                foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
177
                {
178
                    await Update(
×
179
                          renderingSurface,
×
180
                          keyboardHandle,
×
181
                          keyboardCharCache);
×
182

183
                    lag -= msPerUpdate;
×
184
                }
185
            }
186

187
            //Render
188
            foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
×
189
            {
190
                doEventsFunc.Reset(renderingSurface);
×
191

192
                //var update = new DoEventsOk(renderingSurface);
193
                await _channel.Writer.WriteAsync(doEventsFunc, cts.Token);
×
194

195
                doEventsOk &= await doEventsFunc.Invoked;
×
196

197
                _fpsTimer.Start();
×
198
                await Render(
×
199
                    renderingEngine,
×
200
                    renderingSurface.ID);
×
201
                _fpsTimer.Stop();
×
202
            }
×
203
        }
204
    }
×
205

206
    protected override async Task STAThread(
207
        IRenderingEngine renderingEngine,
208
        IOSMessageHandler osMessageHandler,
209
        CancellationToken cancellationToken)
210
    {
211
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(
×
212
            cancellationToken,
×
213
            _loopCancellationTokenSource.Token);
×
214

215
        var doEventsOk = true;
×
216

217
        //using var periodicTimer = new PeriodicTimer(
218
        //    TimeSpan.FromSeconds(1) / 60);
219

220
        var timestamp = Stopwatch.GetTimestamp();
×
221

222
        while (
×
223
            //await periodicTimer.WaitForNextTickAsync(cts.Token)
×
224
            await _channel.Reader.WaitToReadAsync(cts.Token)
×
225
                && IsRunning
×
226
                && doEventsOk
×
227
                && !cts.Token.IsCancellationRequested)
×
228
        {
229
            //_logger.LogTrace("Tick! {elapsedMS}",
230
            //    Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds);
231
            timestamp = Stopwatch.GetTimestamp();
×
232

233
            //await _channel.Reader.WaitToReadAsync(cts.Token)
234
            while (
×
235
                doEventsOk
×
236
                && _channel.Reader.TryRead(out var staAction))
×
237
            {
238
                doEventsOk &= staAction.Invoke(renderingEngine, osMessageHandler);
×
239
            }
×
240
            //foreach (var renderingSurface in renderingEngine.RenderingSurfaces)
241
            //{
242
            //    doEventsOk &= osMessageHandler.DoEvents(
243
            //            renderingSurface.RenderingControl);
244
            //}
245
        }
246
    }
×
247

248
    private float _r, _g, _b = 0.0f;
249

250
    private float _sin = 0.0f;
251
    private bool _fullscreen = false;
252
    private bool _f11Pressed = false;
253

254
    private ValueTask Update(
255
        IRenderingSurface renderingSurface,
256
        KeyboardHandle keyboardHandle,
257
        KeyboardCharCache keyboardCharCache)
258
    {
259
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemOpenBrackets))
×
260
        {
261
            keyboardCharCache.Clear();
×
262
        }
263
        if (keyboardHandle.GetKeyPressed(VirtualKeys.OemCloseBrackets))
×
264
        {
265
            var str = keyboardCharCache?.ReadCache();
×
266
            if (!string.IsNullOrEmpty(str))
×
267
            {
268
                _logger.LogInformation(str);
×
269
            }
270
        }
271
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Escape))
×
272
        {
273
            _loopCancellationTokenSource.Cancel();
×
274
            foreach (var drawable in _drawables)
×
275
            {
276
                (drawable as IDisposable)?.Dispose();
×
277
            }
278

279
            _drawables = [];
×
280
            return ValueTask.CompletedTask;
×
281
        }
282
        if (keyboardHandle.GetKeyPressed(VirtualKeys.F11))
×
283
        {
284
            if (!_f11Pressed)
×
285
            {
286
                _fullscreen = !_fullscreen;
×
287
            }
288
            _f11Pressed = true;
×
289
        }
290
        else
291
        {
292
            _f11Pressed = false;
×
293
        }
294

295
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Back)
×
296
            && _drawables.ElementAtOrDefault(0) is IDisposable d)
×
297
        {
298
            Debug.WriteLine("Removing cube!");
299

300
            _drawables = _drawables.Length > 0
×
301
                ? _drawables[1..]
×
302
                : [];
×
303

304
            d?.Dispose();
×
305

306
            Debug.WriteLine("Cube removed!");
307
        }
308
        if (keyboardHandle.GetKeyPressed(VirtualKeys.Enter)
×
309
            && _drawables.Length < 2
×
310
            && _sp is not null)
×
311
        {
312
            Debug.WriteLine("Adding new Cube!");
313
            _drawables = new List<ICube>(_drawables)
×
314
                {
×
315
                    _sp.GetRequiredService<ICube>()
×
316
                }.ToArray();
×
317
            Debug.WriteLine("Cube added!!");
318
        }
319

320
        //if (_drawables.Length == 2)
321
        //{
322
        //    foreach (var drawable in _drawables)
323
        //    {
324
        //        (drawable as IDisposable)?.Dispose();
325
        //    }
326

327
        //    _drawables = Array.Empty<ICube>();
328
        //}
329
        //else if (_drawables.Length < 2 && _sp is not null /*&& _dontCreate == false*/)
330
        //{
331
        //    _drawables = new List<ICube>(_drawables)
332
        //        {
333
        //            _sp.GetRequiredService<ICube>()
334
        //        }.ToArray();
335
        //    //_dontCreate = true;
336
        //}
337

338
        //Share the rainbow
339
        _r = MathF.Sin((_sin + 0) * MathF.PI / 180);
×
340
        _g = MathF.Sin((_sin + 120) * MathF.PI / 180);
×
341
        _b = MathF.Sin((_sin + 240) * MathF.PI / 180);
×
342

343
        //Taste the rainbow
344
        if (++_sin > 360)
×
345
        {
346
            _sin = 0;
×
347
        }
348
        _angleInRadians = (_angleInRadians + 0.01f) % TWO_PI;
×
349

350
        _drawables.ElementAtOrDefault(0)
×
351
            ?.Update(renderingSurface, Vector3.Zero, _angleInRadians);
×
352
        _drawables.ElementAtOrDefault(1)
×
353
            ?.Update(renderingSurface, new Vector3(0.5f, 0.0f, -0.5f), -_angleInRadians * 1.5f);
×
354

355
        return ValueTask.CompletedTask;
×
356
    }
357

358
    /// <summary>
359
    /// https://bitbucket.org/snippets/DemoBytom/aejA59/maps-value-between-from-one-min-max-range
360
    /// </summary>
361
    public static float Map(float value, float inMin, float inMax, float outMin, float outMax)
362
        => ((value - inMin) * (outMax - outMin) / (inMax - inMin)) + outMin;
×
363

364
    private float _angleInRadians = 0.0f;
365
    private const float TWO_PI = MathHelper.TwoPi;
366

367
    private ValueTask Render(
368
        IRenderingEngine renderingEngine,
369
        Guid renderingSurfaceId)
370
    {
371
        renderingEngine.Draw(
×
372
            color: new Color4(_r, _g, _b, 1.0f),
×
373
            renderingSurfaceId: renderingSurfaceId,
×
374
            drawables: _drawables);
×
375

376
        //if (renderingEngine.Control.IsFullscreen != _fullscreen)
377
        //{
378
        //    renderingEngine.SetFullscreen(_fullscreen);
379
        //}
380

381
        return ValueTask.CompletedTask;
×
382
    }
383

384
    protected override void Dispose(bool disposing)
385
    {
386
        if (!_disposedValue)
×
387
        {
388
            if (disposing)
×
389
            {
390
                _loopCancellationTokenSource.Dispose();
×
391

392
                foreach (var drawable in _drawables)
×
393
                {
394
                    if (drawable is IDisposable disposable)
×
395
                    {
396
                        disposable.Dispose();
×
397
                    }
398
                }
399

400
                _disposedValue = true;
×
401
            }
402
            base.Dispose(disposing);
×
403
        }
404
    }
×
405
}
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