From efe1bf212550452c8aff7823d32431319b529891 Mon Sep 17 00:00:00 2001 From: Christer C Date: Fri, 19 Jul 2024 16:19:59 +0200 Subject: [PATCH] Misc refactoring and bug fixes. (#108) * Make sure SystemRunner always uses the same system (C64 etc) in the Renderer, InputHandler, and AudioHandler as is used for emulation. * Remove generic versions of IRenderer, IInputHandler, and IAudioHandler to reduce unnecessary complexity. Make SystemRunner more obvious to use. Remove SystemRunnerBuilder. * Refactor use of Init and Cleanup methods in Renderers, InputHandlers, AudioHandlers (and their associated context objects) to be more consistent. * Update doc * Fix WASM C64 joystick keyboard * Fix WASM host UI render bug * Bump SkiaSharp from SkiaSharp to 3.0.0-preview.3.1. to 3.0.0-preview.4.1. This fixes WASM host rendering to work in debug mode again. --- .../Commodore64/C64ExecuteFrameBenchmark.cs | 8 +- .../C64ExecuteInstructionBenchmark.cs | 8 +- doc/SYSTEMS_C64.md | 5 +- doc/SYSYEM_DIAGRAM.md | 47 +-- .../Program.cs | 5 +- .../SilkNetRenderContextContainer.cs | 10 +- .../SilkNetWindow.cs | 16 +- .../SystemSetup/C64Setup.cs | 30 +- .../SystemSetup/GenericComputerSetup.cs | 21 +- .../Highbyte.DotNet6502.App.WASM.csproj | 2 +- .../Pages/Commodore64/C64Menu.razor | 119 ++++--- .../Pages/Generic/GenericMenu.razor | 51 +-- .../Pages/Index.razor | 331 +++++++++--------- .../Pages/Index.razor.cs | 24 +- .../Skia/C64Setup.cs | 25 +- .../Skia/GenericComputerSetup.cs | 24 +- .../Skia/WasmHost.cs | 17 +- .../Commodore64/Audio/C64WASMAudioHandler.cs | 33 +- .../Input/C64AspNetInputHandler.cs | 33 +- .../GenericComputerAspNetInputHandler.cs | 26 +- .../WASMAudioHandlerContext.cs | 4 + .../Audio/C64NAudioAudioHandler.cs | 40 ++- .../NAudioAudioHandlerContext.cs | 12 +- .../Input/C64SadConsoleInputHandler.cs | 28 +- .../Video/C64SadConsoleRenderer.cs | 28 +- .../EmulatorHost.cs | 39 +-- .../Input/GenericSadConsoleInputHandler.cs | 29 +- .../Video/GenericSadConsoleRenderer.cs | 31 +- .../SadConsoleMain.cs | 2 +- .../SadConsoleRenderContext.cs | 5 +- .../Input/C64SilkNetInputHandler.cs | 40 +-- .../Video/C64SilkNetOpenGlRenderer.cs | 57 ++- .../GenericComputerSilkNetInputHandler.cs | 25 +- .../SilkNetOpenGlRenderContext.cs | 5 +- .../Commodore64/Video/v1/C64SkiaRenderer.cs | 54 ++- .../Commodore64/Video/v2/C64SkiaRenderer2.cs | 54 ++- .../Commodore64/Video/v2/C64SkiaRenderer2b.cs | 113 +++--- .../Video/GenericComputerSkiaRenderer.cs | 42 +-- .../Highbyte.DotNet6502.Impl.Skia.csproj | 2 +- .../SkiaRenderContext.cs | 7 +- .../Commodore64/C64.cs | 48 +-- .../Highbyte.DotNet6502.Systems/SystemList.cs | 8 + .../Systems/IAudioHandler.cs | 35 +- .../Systems/IAudioHandlerContext.cs | 14 +- .../Systems/IInputHandler.cs | 31 +- .../Systems/IInputHandlerContext.cs | 15 +- .../Systems/IRenderContext.cs | 16 +- .../Highbyte.DotNet6502/Systems/IRenderer.cs | 28 +- .../Systems/SystemRunner.cs | 80 +++-- .../Systems/SystemRunnerBuilder.cs | 53 --- .../Systems/SystemRunnerBuilderTests.cs | 254 -------------- .../Systems/SystemRunnerTests.cs | 272 +++++++++++++- 52 files changed, 1110 insertions(+), 1196 deletions(-) delete mode 100644 src/libraries/Highbyte.DotNet6502/Systems/SystemRunnerBuilder.cs delete mode 100644 tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerBuilderTests.cs diff --git a/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs b/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs index 65ac9e92..d4c6ae1d 100644 --- a/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs +++ b/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs @@ -49,12 +49,8 @@ public void Setup() LoadProgram(_c64WithInstrumentation.Mem, _startAddress); LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress); - var systemRunnerBuilderWithInstrumentation = new SystemRunnerBuilder(_c64WithInstrumentation); - _systemRunnerWithInstrumentation = systemRunnerBuilderWithInstrumentation.Build(); - - var systemRunnerBuilderWithoutInstrumentation = new SystemRunnerBuilder(_c64WithoutInstrumentation); - _systemRunnerWithoutInstrumentation = systemRunnerBuilderWithoutInstrumentation.Build(); - + _systemRunnerWithInstrumentation = new SystemRunner(_c64WithInstrumentation); + _systemRunnerWithoutInstrumentation = new SystemRunner(_c64WithoutInstrumentation); } private void LoadProgram(Memory mem, ushort startAddress) diff --git a/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs b/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs index eefcb1c8..c3ee8a8b 100644 --- a/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs +++ b/benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs @@ -49,12 +49,8 @@ public void Setup() LoadProgram(_c64WithInstrumentation.Mem, _startAddress); LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress); - var systemRunnerBuilderWithInstrumentation = new SystemRunnerBuilder(_c64WithInstrumentation); - _systemRunnerWithInstrumentation = systemRunnerBuilderWithInstrumentation.Build(); - - var systemRunnerBuilderWithoutInstrumentation = new SystemRunnerBuilder(_c64WithoutInstrumentation); - _systemRunnerWithoutInstrumentation = systemRunnerBuilderWithoutInstrumentation.Build(); - + _systemRunnerWithInstrumentation = new SystemRunner(_c64WithInstrumentation); + _systemRunnerWithoutInstrumentation = new SystemRunner(_c64WithoutInstrumentation); } private void LoadProgram(Memory mem, ushort startAddress) diff --git a/doc/SYSTEMS_C64.md b/doc/SYSTEMS_C64.md index 532a3fbf..b6aa1a8f 100644 --- a/doc/SYSTEMS_C64.md +++ b/doc/SYSTEMS_C64.md @@ -8,10 +8,11 @@ Current capabilities - Run Commodore Basic 2.0 from ROM (user supplied Kernal, Basic, and Chargen ROM files) in text mode. - Limited VIC2 video chip support - Standard, extended and multi-color character modes - - Standard and multi-color bitmap mode _(native app OpenGL renderer only)_ + - Standard and multi-color bitmap mode _(newer SkiaRenderer 2/2b in native & WASM, and OpenGL renderer in native only) - Sprites (hi-res & multi-color) - IRQ (raster, sprite collision) - - Background and border color possible to change per raster line + - Background and border color possible to per raster line + - Fine scrolling per raster line (new newer SkiaRenderer 2b in native & WASM only) - Limited CIA chip support - Keyboard - Joystick diff --git a/doc/SYSYEM_DIAGRAM.md b/doc/SYSYEM_DIAGRAM.md index 75e64b51..a712f184 100644 --- a/doc/SYSYEM_DIAGRAM.md +++ b/doc/SYSYEM_DIAGRAM.md @@ -17,6 +17,7 @@ App_SilkNetNative --> SystemRunner App_SilkNetNative --> System_C64 App_SilkNetNative --> Impl_Skia_C64 App_SilkNetNative --> Impl_SilkNet_C64 +App_SilkNetNative --> Impl_NAudio_C64 App_SilkNetNative --> System_X App_SilkNetNative --> Impl_Skia_X App_SilkNetNative --> Impl_SilkNet_X @@ -43,14 +44,20 @@ Impl_Skia_X --> IRenderer Impl_SilkNet_C64 Impl_SilkNet_C64 --> System_C64 Impl_SilkNet_C64 --> IInputHandler +Impl_SilkNet_C64 --> IRenderer Impl_SilkNet_X Impl_SilkNet_X --> System_X Impl_SilkNet_X --> IInputHandler +Impl_NAudio_C64 +Impl_NAudio_C64 --> System_C64 +Impl_NAudio_C64 --> IAudioHandler + Impl_AspNet_C64 Impl_AspNet_C64 --> System_C64 Impl_AspNet_C64 --> IInputHandler +Impl_AspNet_C64 --> IAudioHandler Impl_AspNet_X Impl_AspNet_X --> System_X @@ -81,11 +88,12 @@ SystemRunner SystemRunner --> ISystem SystemRunner --> IInputHandler SystemRunner --> IRenderer -SystemRunner : bool Run -SystemRunner : bool RunOneFrame -SystemRunner : bool ProcessInput -SystemRunner : bool RunEmulatorOneFrame -SystemRunner : bool Draw +SystemRunner --> IAudioHandler +SystemRunner : void Init +SystemRunner : void ProcessInputBeforeFrame +SystemRunner : ExecEvaluatorTriggerResult RunEmulatorOneFrame +SystemRunner : void Draw +SystemRunner : void Cleanup ISystem ISystem --> CPU @@ -96,24 +104,23 @@ CPU CPU : void Execute(Mem mem) IRenderer -IRenderer : void Init(ISystem system, IRenderContext renderContext) -IRenderer : void Draw(ISystem system) +IRenderer : void Init() +IRenderer : void DrawFrame() +IRenderer : void Cleanup() IInputHandler -IInputHandler : void Init(ISystem system, IInputHandlerContext inputHandlerContext) -IInputHandler : void ProcessInput(ISystem system) -``` - -```mermaid -classDiagram -SystemRunnerBuilder -SystemRunnerBuilder --> SystemRunner -SystemRunnerBuilder : ctor(~TSystem~ system) -SystemRunnerBuilder : SystemRunnerBuilder WithRenderer(IRenderer~TSystem, TRenderContext~ renderer) -SystemRunnerBuilder : SystemRunnerBuilder WithInputHandler(IRenderer~TSystem, TInputHandlerContext~ inputHandler) -SystemRunnerBuilder : SystemRunner Build() +IInputHandler : void Init() +IInputHandler : void BeforeFrame() +IInputHandler : void Cleanup() + +IAudioHandler +IAudioHandler : void Init() +IAudioHandler : void AfterFrame() +IAudioHandler : void StartPlaying() +IAudioHandler : void StopPlaying() +IAudioHandler : void PausePlaying() +IAudioHandler : void Cleanup() -SystemRunner ``` diff --git a/src/apps/Highbyte.DotNet6502.App.ConsoleMonitor/Program.cs b/src/apps/Highbyte.DotNet6502.App.ConsoleMonitor/Program.cs index d9fa3518..a26f5b29 100644 --- a/src/apps/Highbyte.DotNet6502.App.ConsoleMonitor/Program.cs +++ b/src/apps/Highbyte.DotNet6502.App.ConsoleMonitor/Program.cs @@ -22,9 +22,8 @@ // }); var computer = computerBuilder.Build(); -var systemRunnerBuilder = new SystemRunnerBuilder(computer); - -var systemRunner = systemRunnerBuilder.Build(); +var systemRunner = new SystemRunner(computer); +systemRunner.Init(); var monitorConfig = new MonitorConfig { diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetRenderContextContainer.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetRenderContextContainer.cs index a28209de..f092b9e2 100644 --- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetRenderContextContainer.cs +++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetRenderContextContainer.cs @@ -18,9 +18,13 @@ public SilkNetRenderContextContainer(SkiaRenderContext skiaRenderContext, SilkNe _silkNetOpenGlRenderContext = silkNetOpenGlRenderContext; } - internal void Cleanup() + public void Init() { - SkiaRenderContext?.Cleanup(); - SilkNetOpenGlRenderContext?.Cleanup(); + } + + public void Cleanup() + { + _skiaRenderContext?.Cleanup(); + _silkNetOpenGlRenderContext?.Cleanup(); } } diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs index 13e429be..42dd553e 100644 --- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs +++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SilkNetWindow.cs @@ -16,6 +16,7 @@ using Highbyte.DotNet6502.App.SilkNetNative.SystemSetup; using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Video; using AutoMapper.Internal.Mappers; +using NAudio.Wave.SampleProviders; namespace Highbyte.DotNet6502.App.SilkNetNative; @@ -182,14 +183,13 @@ protected void OnClosing() // _statsPanel.Cleanup(); DestroyImGuiController(); - // Cleanup SkiaSharp resources - _silkNetRenderContextContainer.Cleanup(); - - // Cleanup SilkNet input resources - _silkNetInputHandlerContext.Cleanup(); + // Cleanup systemrunner (which also cleanup renderer, inputhandler, and audiohandler) + _systemRunner?.Cleanup(); - // Cleanup NAudio audio resources - _naudioAudioHandlerContext.Cleanup(); + // Cleanup contexts + _silkNetRenderContextContainer?.Cleanup(); + _silkNetInputHandlerContext?.Cleanup(); + _naudioAudioHandlerContext?.Cleanup(); } /// @@ -359,7 +359,7 @@ private void RunEmulator() if (!_atLeastOneImGuiWindowHasFocus) { _inputTime.Start(); - _systemRunner.ProcessInput(); + _systemRunner.ProcessInputBeforeFrame(); _inputTime.Stop(); } diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/C64Setup.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/C64Setup.cs index 7e8d3895..bec51936 100644 --- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/C64Setup.cs +++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/C64Setup.cs @@ -105,43 +105,27 @@ NAudioAudioHandlerContext audioHandlerContext var c64 = (C64)system; IRenderer renderer; - IRenderContext renderContext; switch (c64HostConfig.Renderer) { case C64HostRenderer.SkiaSharp: - renderer = new C64SkiaRenderer(); - renderContext = renderContextContainer.SkiaRenderContext; + renderer = new C64SkiaRenderer(c64, renderContextContainer.SkiaRenderContext); break; case C64HostRenderer.SkiaSharp2: - renderer = new C64SkiaRenderer2(); - renderContext = renderContextContainer.SkiaRenderContext; + renderer = new C64SkiaRenderer2(c64, renderContextContainer.SkiaRenderContext); break; case C64HostRenderer.SkiaSharp2b: - renderer = new C64SkiaRenderer2b(); - renderContext = renderContextContainer.SkiaRenderContext; + renderer = new C64SkiaRenderer2b(c64, renderContextContainer.SkiaRenderContext); break; case C64HostRenderer.SilkNetOpenGl: - renderer = new C64SilkNetOpenGlRenderer(c64HostConfig.SilkNetOpenGlRendererConfig); - renderContext = renderContextContainer.SilkNetOpenGlRenderContext; + renderer = new C64SilkNetOpenGlRenderer(c64, renderContextContainer.SilkNetOpenGlRenderContext, c64HostConfig.SilkNetOpenGlRendererConfig); break; default: throw new NotImplementedException($"Renderer {c64HostConfig.Renderer} not implemented."); } - var inputHandler = new C64SilkNetInputHandler(_loggerFactory, _c64HostConfig.InputConfig); - var audioHandler = new C64NAudioAudioHandler(_loggerFactory); + var inputHandler = new C64SilkNetInputHandler(c64, inputHandlerContext, _loggerFactory, _c64HostConfig.InputConfig); + var audioHandler = new C64NAudioAudioHandler(c64, audioHandlerContext, _loggerFactory); - - renderer.Init(c64, renderContext); - inputHandler.Init(c64, inputHandlerContext); - audioHandler.Init(c64, audioHandlerContext); - - var systemRunnerBuilder = new SystemRunnerBuilder(c64); - var systemRunner = systemRunnerBuilder - .WithRenderer(renderer) - .WithInputHandler(inputHandler) - .WithAudioHandler(audioHandler) - .Build(); - return systemRunner; + return new SystemRunner(c64, renderer, inputHandler, audioHandler); } } diff --git a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/GenericComputerSetup.cs b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/GenericComputerSetup.cs index 36b73f00..e87d611f 100644 --- a/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/GenericComputerSetup.cs +++ b/src/apps/Highbyte.DotNet6502.App.SilkNetNative/SystemSetup/GenericComputerSetup.cs @@ -4,6 +4,7 @@ using Highbyte.DotNet6502.Impl.Skia; using Highbyte.DotNet6502.Impl.Skia.Generic.Video; using Highbyte.DotNet6502.Systems; +using Highbyte.DotNet6502.Systems.Commodore64; using Highbyte.DotNet6502.Systems.Generic; using Highbyte.DotNet6502.Systems.Generic.Config; using Microsoft.Extensions.Logging; @@ -83,23 +84,13 @@ public SystemRunner BuildSystemRunner( SilkNetInputHandlerContext inputHandlerContext, NAudioAudioHandlerContext audioHandlerContext) { - var genericComputerConfig = (GenericComputerConfig)systemConfig; - - var renderer = new GenericComputerSkiaRenderer(genericComputerConfig.Memory.Screen); - var inputHandler = new GenericComputerSilkNetInputHandler(genericComputerConfig.Memory.Input); - var audioHandler = new NullAudioHandler(); - var genericComputer = (GenericComputer)system; + var genericComputerConfig = (GenericComputerConfig)systemConfig; - renderer.Init(genericComputer, renderContext.SkiaRenderContext); - inputHandler.Init(genericComputer, inputHandlerContext); + var renderer = new GenericComputerSkiaRenderer(genericComputer, renderContext.SkiaRenderContext, genericComputerConfig.Memory.Screen); + var inputHandler = new GenericComputerSilkNetInputHandler(genericComputer, inputHandlerContext, genericComputerConfig.Memory.Input); + var audioHandler = new NullAudioHandler(genericComputer); - var systemRunnerBuilder = new SystemRunnerBuilder(genericComputer); - var systemRunner = systemRunnerBuilder - .WithRenderer(renderer) - .WithInputHandler(inputHandler) - .WithAudioHandler(audioHandler) - .Build(); - return systemRunner; + return new SystemRunner(genericComputer, renderer, inputHandler, audioHandler); } } diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj b/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj index 3017856a..ed4039a7 100644 --- a/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj +++ b/src/apps/Highbyte.DotNet6502.App.WASM/Highbyte.DotNet6502.App.WASM.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor index a83015c6..440f3d1d 100644 --- a/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor +++ b/src/apps/Highbyte.DotNet6502.App.WASM/Pages/Commodore64/C64Menu.razor @@ -4,77 +4,82 @@ @using Highbyte.DotNet6502.Systems.Commodore64.Video; @using static Highbyte.DotNet6502.App.WASM.Pages.Index; -
+@if(Parent.Initialized) +{ +
- - -

- - - - -

Load/save files

- - - - -

Assembly example files

-
- + @foreach (var joystick in _c64HostConfig.InputConfig.AvailableJoysticks.ToArray()) { - + } - -
- -

Basic example files

-
- + - + +

Load/save files

+ + + + +

Assembly example files

+
+ + +
+ +

Basic example files

+
+ + +
+
+ +
+ @_latestFileError
-
-
- @_latestFileError -
+
+
-
-
+
+ +
-
- -
+ + .systemHelpStyle { + display: @Parent.GetSystemVisibilityDisplayStyle("Help", SYSTEM_NAME) + } + + .systemConfigStyle { + display: @Parent.GetSystemVisibilityDisplayStyle("Config", SYSTEM_NAME) + } + +}
-public class C64SkiaRenderer : IRenderer +public class C64SkiaRenderer : IRenderer { + private readonly C64 _c64; + public ISystem System => _c64; + private readonly SkiaRenderContext _skiaRenderContext; + + private Func _getSkCanvas = default!; private C64SkiaPaint _c64SkiaPaint = default!; @@ -53,75 +58,68 @@ public class C64SkiaRenderer : IRenderer // Instrumentations public Instrumentations Instrumentations { get; } = new(); + + private const string StatsCategory = "SkiaSharp-Custom"; private ElapsedMillisecondsTimedStatSystem _borderStat; private ElapsedMillisecondsTimedStatSystem _backgroundStat; private ElapsedMillisecondsTimedStatSystem _textScreenStat; private ElapsedMillisecondsTimedStatSystem _spritesStat; - public C64SkiaRenderer() + public C64SkiaRenderer(C64 c64, SkiaRenderContext skiaRenderContext) { + _c64 = c64; + _skiaRenderContext = skiaRenderContext; } - public void Init(C64 c64, SkiaRenderContext skiaRenderContext) + public void Init() { _charGen = new CharGen(); - _spriteImages = new SKImage[c64.Vic2.SpriteManager.NumberOfSprites]; + _spriteImages = new SKImage[_c64.Vic2.SpriteManager.NumberOfSprites]; - _getSkCanvas = skiaRenderContext.GetCanvas; + _getSkCanvas = _skiaRenderContext.GetCanvas; - _c64SkiaPaint = new C64SkiaPaint(c64.ColorMapName); + _c64SkiaPaint = new C64SkiaPaint(_c64.ColorMapName); - InitCharset(c64); + InitCharset(_c64); - _backgroundStat = Instrumentations.Add($"{StatsCategory}-Background", new ElapsedMillisecondsTimedStatSystem(c64)); - _borderStat = Instrumentations.Add($"{StatsCategory}-Border", new ElapsedMillisecondsTimedStatSystem(c64)); - _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(c64)); - _textScreenStat = Instrumentations.Add($"{StatsCategory}-TextScreen", new ElapsedMillisecondsTimedStatSystem(c64)); - } - - public void Init(ISystem system, IRenderContext renderContext) - { - Init((C64)system, (SkiaRenderContext)renderContext); + _backgroundStat = Instrumentations.Add($"{StatsCategory}-Background", new ElapsedMillisecondsTimedStatSystem(_c64)); + _borderStat = Instrumentations.Add($"{StatsCategory}-Border", new ElapsedMillisecondsTimedStatSystem(_c64)); + _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(_c64)); + _textScreenStat = Instrumentations.Add($"{StatsCategory}-TextScreen", new ElapsedMillisecondsTimedStatSystem(_c64)); } public void Cleanup() { } - public void Draw(C64 c64) + public void DrawFrame() { var canvas = _getSkCanvas(); canvas.Clear(); _backgroundStat.Start(); - DrawRasterLinesBackground(c64, canvas); + DrawRasterLinesBackground(_c64, canvas); _backgroundStat.Stop(); _spritesStat.Start(); - RenderSprites(c64, canvas, spritesWithPriorityOverForeground: false); + RenderSprites(_c64, canvas, spritesWithPriorityOverForeground: false); _spritesStat.Stop(); _textScreenStat.Start(); - RenderMainScreen(c64, canvas); + RenderMainScreen(_c64, canvas); _textScreenStat.Stop(); _borderStat.Start(); - DrawRasterLinesBorder(c64, canvas); + DrawRasterLinesBorder(_c64, canvas); _borderStat.Stop(); _spritesStat.Start(cont: true); - RenderSprites(c64, canvas, spritesWithPriorityOverForeground: true); + RenderSprites(_c64, canvas, spritesWithPriorityOverForeground: true); _spritesStat.Stop(cont: true); } - public void Draw(ISystem system) - { - Draw((C64)system); - } - - private void InitCharset(C64 c64) { // Generate and remember images of the CharGen ROM charset. diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs index 1ab7dfc7..c6b65f02 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2.cs @@ -28,8 +28,12 @@ namespace Highbyte.DotNet6502.Impl.Skia.Commodore64.Video.v2; /// Compared to C64SkiaRenderer: /// - A bit slower, but supports Bitmap graphics and still able to run in a browser with Blazor WebAssembly. /// -public class C64SkiaRenderer2 : IRenderer +public class C64SkiaRenderer2 : IRenderer { + private readonly C64 _c64; + public ISystem System => _c64; + + private readonly SkiaRenderContext _skiaRenderContext; private Func _getSkCanvas = default!; private SkiaBitmapBackedByPixelArray _skiaPixelArrayBitmap_TextAndBitmap; @@ -129,6 +133,7 @@ private enum ShaderLineData : int // Instrumentations public Instrumentations Instrumentations { get; } = new(); + private const string StatsCategory = "SkiaSharp-Custom"; private ElapsedMillisecondsTimedStatSystem _borderStat; private ElapsedMillisecondsTimedStatSystem _textAndBitmapScreenStat; @@ -136,55 +141,48 @@ private enum ShaderLineData : int private ElapsedMillisecondsTimedStatSystem _lineDataImageStat; private ElapsedMillisecondsTimedStatSystem _drawCanvasWithShaderStat; - public C64SkiaRenderer2() + public C64SkiaRenderer2(C64 c64, SkiaRenderContext skiaRenderContext) { + _c64 = c64; + _skiaRenderContext = skiaRenderContext; } - public void Init(C64 c64, SkiaRenderContext skiaRenderContext) + public void Init() { - _getSkCanvas = skiaRenderContext.GetCanvas; + _getSkCanvas = _skiaRenderContext.GetCanvas; - _c64SkiaColors = new C64SkiaColors(c64.ColorMapName); + _c64SkiaColors = new C64SkiaColors(_c64.ColorMapName); - InitTextAndSpritesBitmap(c64); - InitLineDataBitmap(c64); + InitTextAndSpritesBitmap(_c64); + InitLineDataBitmap(_c64); - InitBitPatternToPixelMapsForTextDisplay(c64); + InitBitPatternToPixelMapsForTextDisplay(_c64); InitBitPatternToPixelMapsForBitmapDisplay(); - InitShader(c64); + InitShader(_c64); Instrumentations.Clear(); - _borderStat = Instrumentations.Add($"{StatsCategory}-Border", new ElapsedMillisecondsTimedStatSystem(c64)); - _textAndBitmapScreenStat = Instrumentations.Add($"{StatsCategory}-Screen", new ElapsedMillisecondsTimedStatSystem(c64)); - _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(c64)); - _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(c64)); - _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); + _borderStat = Instrumentations.Add($"{StatsCategory}-Border", new ElapsedMillisecondsTimedStatSystem(_c64)); + _textAndBitmapScreenStat = Instrumentations.Add($"{StatsCategory}-Screen", new ElapsedMillisecondsTimedStatSystem(_c64)); + _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(_c64)); + _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(_c64)); + _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(_c64)); } - public void Init(ISystem system, IRenderContext renderContext) - { - Init((C64)system, (SkiaRenderContext)renderContext); - } - public void Draw(C64 c64) + public void DrawFrame() { // Draw border and screen to bitmap - DrawBorderAndScreenToBitmapBackedByPixelArray(c64, _skiaPixelArrayBitmap_TextAndBitmap.PixelArray); + DrawBorderAndScreenToBitmapBackedByPixelArray(_c64, _skiaPixelArrayBitmap_TextAndBitmap.PixelArray); // Draw sprites to separate bitmap - DrawSpritesToBitmapBackedByPixelArray(c64, _skiaPixelArrayBitmap_Sprites.PixelArray); + DrawSpritesToBitmapBackedByPixelArray(_c64, _skiaPixelArrayBitmap_Sprites.PixelArray); // Write line data (color values of VIC2 registers per raster line) to separate bitmap. To be used later in the shader to determine colors per raster line. - WriteLineDataToBitmapBackedByPixelArray(c64, _skiaPixelArrayBitmap_LineData.PixelArray); + WriteLineDataToBitmapBackedByPixelArray(_c64, _skiaPixelArrayBitmap_LineData.PixelArray); // Draw to a canvas using a shader with texture info from screen and sprite bitmaps, together with line data bitmap - WriteBitmapToCanvas(_skiaPixelArrayBitmap_TextAndBitmap.Bitmap, _skiaPixelArrayBitmap_Sprites.Bitmap, _skiaPixelArrayBitmap_LineData.Bitmap, _getSkCanvas(), c64); - } - - public void Draw(ISystem system) - { - Draw((C64)system); + WriteBitmapToCanvas(_skiaPixelArrayBitmap_TextAndBitmap.Bitmap, _skiaPixelArrayBitmap_Sprites.Bitmap, _skiaPixelArrayBitmap_LineData.Bitmap, _getSkCanvas(), _c64); } public void Cleanup() diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs index f74803e5..7c2bedc9 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/v2/C64SkiaRenderer2b.cs @@ -32,8 +32,13 @@ namespace Highbyte.DotNet6502.Impl.Skia.Commodore64.Video.v2; /// - Allows for more accurate rendering of the C64 screen due to text and bitmap is generated after each instruction (including fine scroll). /// - A simpler implementation. /// -public class C64SkiaRenderer2b : IRenderer +public class C64SkiaRenderer2b : IRenderer { + private readonly C64 _c64; + public ISystem System => _c64; + + private readonly SkiaRenderContext _skiaRenderContext; + private Func _getSkCanvas = default!; private SkiaBitmapBackedByPixelArray _skiaPixelArrayBitmap_BackgroundAndBorder; @@ -124,6 +129,7 @@ private enum ShaderLineData : int // Instrumentations public Instrumentations Instrumentations { get; } = new(); + private const string StatsCategory = "SkiaSharp-Custom"; private ElapsedMillisecondsTimedStatSystem _spritesStat; private ElapsedMillisecondsTimedStatSystem _lineDataImageStat; @@ -202,19 +208,23 @@ private enum ShaderLineData : int private int _screenLayoutInclNonVisibleLeftBorderStartX; private int _screenLayoutInclNonVisibleRightBorderEndX; - public C64SkiaRenderer2b() + + public C64SkiaRenderer2b(C64 c64, SkiaRenderContext skiaRenderContext) { + _c64 = c64; + _skiaRenderContext = skiaRenderContext; } - public void Init(C64 c64, SkiaRenderContext skiaRenderContext) + public void Init() { - c64.SetAfterInstructionCallback(AfterInstructionExecuted); - c64.RememberVic2RegistersPerRasterLine = true; // Set to false if/when sprites are drawn directly to the screen bitmap in the "after instruction" callback here. + // Configure callback method for video generation after each instruction + _c64.SetPostInstructionVideoCallback(AfterInstructionExecuted); + _c64.RememberVic2RegistersPerRasterLine = true; // Set to false if/when sprites are drawn directly to the screen bitmap in the "after instruction" callback here. // Init class variables with C64 screen values that should'nt change // Entire screen area, including non-visible parts. Without consideration to 38 column mode or 24 row mode. - var screenLayoutInclNonVisible = c64.Vic2.ScreenLayouts.GetLayout(LayoutType.Visible, for24RowMode: false, for38ColMode: false); // Full area of raster lines, including non-visible. Borders don't start at 0,0 + var screenLayoutInclNonVisible = _c64.Vic2.ScreenLayouts.GetLayout(LayoutType.Visible, for24RowMode: false, for38ColMode: false); // Full area of raster lines, including non-visible. Borders don't start at 0,0 _screenLayoutInclNonVisibleTopBorderStartY = screenLayoutInclNonVisible.TopBorder.Start.Y; _screenLayoutInclNonVisibleBottomBorderEndY = screenLayoutInclNonVisible.BottomBorder.End.Y; @@ -227,7 +237,7 @@ public void Init(C64 c64, SkiaRenderContext skiaRenderContext) _screenLayoutInclNonVisibleScreenEndY = screenLayoutInclNonVisible.Screen.End.Y; // Entire screen area with only visible parts (borders, screen). Without consideration to 38 column mode or 24 row mode. - var visibleMainScreenAreaNormalized = c64.Vic2.ScreenLayouts.GetLayout(LayoutType.VisibleNormalized, for24RowMode: false, for38ColMode: false); + var visibleMainScreenAreaNormalized = _c64.Vic2.ScreenLayouts.GetLayout(LayoutType.VisibleNormalized, for24RowMode: false, for38ColMode: false); // Not considering 24 row mode or 38 col mode or fine scroll _screenStartX = visibleMainScreenAreaNormalized.Screen.Start.X; @@ -253,52 +263,43 @@ public void Init(C64 c64, SkiaRenderContext skiaRenderContext) _rightBorderEndX = visibleMainScreenAreaNormalized.RightBorder.End.X; _rightBorderEndY = visibleMainScreenAreaNormalized.RightBorder.End.Y; - _vic2ScreenTextCols = c64.Vic2.Vic2Screen.TextCols; - _vic2ScreenCharacterHeight = c64.Vic2.Vic2Screen.CharacterHeight; - _width = c64.Vic2.Vic2Screen.VisibleWidth; - _height = c64.Vic2.Vic2Screen.VisibleHeight; - _drawableAreaHeight = c64.Vic2.Vic2Screen.DrawableAreaHeight; - _drawableAreaWidth = c64.Vic2.Vic2Screen.DrawableAreaWidth; - _cyclesPerLine = c64.Vic2.Vic2Model.CyclesPerLine; + _vic2ScreenTextCols = _c64.Vic2.Vic2Screen.TextCols; + _vic2ScreenCharacterHeight = _c64.Vic2.Vic2Screen.CharacterHeight; + _width = _c64.Vic2.Vic2Screen.VisibleWidth; + _height = _c64.Vic2.Vic2Screen.VisibleHeight; + _drawableAreaHeight = _c64.Vic2.Vic2Screen.DrawableAreaHeight; + _drawableAreaWidth = _c64.Vic2.Vic2Screen.DrawableAreaWidth; + _cyclesPerLine = _c64.Vic2.Vic2Model.CyclesPerLine; _lastScreenLineDataUpdate = -1; - _getSkCanvas = skiaRenderContext.GetCanvas; - _c64SkiaColors = new C64SkiaColors(c64.ColorMapName); + _getSkCanvas = _skiaRenderContext.GetCanvas; + _c64SkiaColors = new C64SkiaColors(_c64.ColorMapName); - InitBitmaps(c64); - InitLineDataBitmap(c64); + InitBitmaps(_c64); + InitLineDataBitmap(_c64); - InitBitPatternToPixelMaps(c64); + InitBitPatternToPixelMaps(_c64); - InitShader(c64); + InitShader(_c64); Instrumentations.Clear(); - _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(c64)); - _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(c64)); - _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(c64)); + _spritesStat = Instrumentations.Add($"{StatsCategory}-Sprites", new ElapsedMillisecondsTimedStatSystem(_c64)); + _lineDataImageStat = Instrumentations.Add($"{StatsCategory}-LineDataImage", new ElapsedMillisecondsTimedStatSystem(_c64)); + _drawCanvasWithShaderStat = Instrumentations.Add($"{StatsCategory}-DrawCanvasWithShader", new ElapsedMillisecondsTimedStatSystem(_c64)); } - public void Init(ISystem system, IRenderContext renderContext) - { - Init((C64)system, (SkiaRenderContext)renderContext); - } - public void Draw(C64 c64) + public void DrawFrame() { // Draw sprites to background of foreground bitmaps - DrawSpritesToBitmapBackedByPixelArray(c64, _skiaPixelArrayBitmap_BackgroundAndBorder.PixelArray, _skiaPixelArrayBitmap_Foreground.PixelArray); + DrawSpritesToBitmapBackedByPixelArray(_c64, _skiaPixelArrayBitmap_BackgroundAndBorder.PixelArray, _skiaPixelArrayBitmap_Foreground.PixelArray); // "Draw" line data (color values of VIC2 registers per raster line) to separate bitmap (currently only used for sprites). - DrawLineDataToBitmapBackedByPixelArray(c64, _skiaPixelArrayBitmap_LineData.PixelArray); + DrawLineDataToBitmapBackedByPixelArray(_c64, _skiaPixelArrayBitmap_LineData.PixelArray); // Draw to a canvas using a shader with texture info from screen and sprite bitmaps, together with line data bitmap - WriteBitmapToCanvas(_skiaPixelArrayBitmap_BackgroundAndBorder.Bitmap, _skiaPixelArrayBitmap_Foreground.Bitmap, _skiaPixelArrayBitmap_LineData.Bitmap, _getSkCanvas(), c64); - } - - public void Draw(ISystem system) - { - Draw((C64)system); + WriteBitmapToCanvas(_skiaPixelArrayBitmap_BackgroundAndBorder.Bitmap, _skiaPixelArrayBitmap_Foreground.Bitmap, _skiaPixelArrayBitmap_LineData.Bitmap, _getSkCanvas(), _c64); } public void Cleanup() @@ -598,18 +599,18 @@ private void WriteBitmapToCanvas(SKBitmap backgroundAndBorderBitmap, SKBitmap fo } - private void AfterInstructionExecuted(C64 c64, InstructionExecResult instructionExecResult) + private void AfterInstructionExecuted(InstructionExecResult instructionExecResult) { // Loop cycles since last time we processed (each instruction) - for (var cycleCurrentVblank = _lastCyclesConsumedCurrentVblank; cycleCurrentVblank < c64.Vic2.CyclesConsumedCurrentVblank; cycleCurrentVblank++) + for (var cycleCurrentVblank = _lastCyclesConsumedCurrentVblank; cycleCurrentVblank < _c64.Vic2.CyclesConsumedCurrentVblank; cycleCurrentVblank++) { // For the cycle processed in current loop iteration, get line and x position. // Skip if not within visible C64 border/text/bitmap area // Line var rasterLine = (int)(cycleCurrentVblank / _cyclesPerLine); - var screenLine = c64.Vic2.Vic2Model.ConvertRasterLineToScreenLine(rasterLine); + var screenLine = _c64.Vic2.Vic2Model.ConvertRasterLineToScreenLine(rasterLine); if (screenLine < _screenLayoutInclNonVisibleTopBorderStartY || screenLine > _screenLayoutInclNonVisibleBottomBorderEndY) continue; @@ -633,26 +634,26 @@ private void AfterInstructionExecuted(C64 c64, InstructionExecResult instruction } // C64 screen data is updated each line. TODO: For more accurate rendering, this should be done after each instruction (but may be too slow). - _vic2VideoMatrixBaseAddress = c64.Vic2.VideoMatrixBaseAddress; - _vic2BitmapBaseAddress = c64.Vic2.BitmapManager.BitmapAddressInVIC2Bank; - _vic2CharacterSetAddressInVIC2Bank = c64.Vic2.CharsetManager.CharacterSetAddressInVIC2Bank; + _vic2VideoMatrixBaseAddress = _c64.Vic2.VideoMatrixBaseAddress; + _vic2BitmapBaseAddress = _c64.Vic2.BitmapManager.BitmapAddressInVIC2Bank; + _vic2CharacterSetAddressInVIC2Bank = _c64.Vic2.CharsetManager.CharacterSetAddressInVIC2Bank; - _isTextMode = c64.Vic2.DisplayMode == DispMode.Text; - _characterMode = c64.Vic2.CharacterMode; - _bitmapMode = c64.Vic2.BitmapMode; - _scrollX = c64.Vic2.GetScrollX(); - _scrollY = c64.Vic2.GetScrollY(); + _isTextMode = _c64.Vic2.DisplayMode == DispMode.Text; + _characterMode = _c64.Vic2.CharacterMode; + _bitmapMode = _c64.Vic2.BitmapMode; + _scrollX = _c64.Vic2.GetScrollX(); + _scrollY = _c64.Vic2.GetScrollY(); - _borderColor = c64.ReadIOStorage(Vic2Addr.BORDER_COLOR); + _borderColor = _c64.ReadIOStorage(Vic2Addr.BORDER_COLOR); - _backgroundColor0 = c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_0); - _backgroundColor1 = c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_1); - _backgroundColor2 = c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_2); - _backgroundColor3 = c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_3); + _backgroundColor0 = _c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_0); + _backgroundColor1 = _c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_1); + _backgroundColor2 = _c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_2); + _backgroundColor3 = _c64.ReadIOStorage(Vic2Addr.BACKGROUND_COLOR_3); - _is38ColModeEnabled = c64.Vic2.Is38ColumnDisplayEnabled; - _is24RowModeEnabled = c64.Vic2.Is24RowDisplayEnabled; + _is38ColModeEnabled = _c64.Vic2.Is38ColumnDisplayEnabled; + _is24RowModeEnabled = _c64.Vic2.Is24RowDisplayEnabled; _leftBorderEndXAdjusted = _leftBorderEndX + (_is38ColModeEnabled ? Vic2Screen.COL_38_LEFT_BORDER_END_X_DELTA : 0); _leftBorderLengthAdjusted = _leftBorderEndXAdjusted - _leftBorderStartX + 1; @@ -671,12 +672,12 @@ private void AfterInstructionExecuted(C64 c64, InstructionExecResult instruction if (!(screenLine < _screenLayoutInclNonVisibleScreenStartY || screenLine > _screenLayoutInclNonVisibleScreenEndY || posX < _screenLayoutInclNonVisibleScreenStartX || posX > _screenLayoutInclNonVisibleScreenEndX)) { - DrawTextAndBitmapPixels(c64, drawLine: screenLine - _screenLayoutInclNonVisibleScreenStartY, col: (posX - _screenLayoutInclNonVisibleScreenStartX) / 8); + DrawTextAndBitmapPixels(_c64, drawLine: screenLine - _screenLayoutInclNonVisibleScreenStartY, col: (posX - _screenLayoutInclNonVisibleScreenStartX) / 8); } } // End for each cycle - _lastCyclesConsumedCurrentVblank = c64.Vic2.CyclesConsumedCurrentVblank; + _lastCyclesConsumedCurrentVblank = _c64.Vic2.CyclesConsumedCurrentVblank; } private void DrawBorderPixels(int normalizedScreenLine) diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Generic/Video/GenericComputerSkiaRenderer.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Generic/Video/GenericComputerSkiaRenderer.cs index 88bff82e..df36df66 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Generic/Video/GenericComputerSkiaRenderer.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Generic/Video/GenericComputerSkiaRenderer.cs @@ -4,11 +4,15 @@ using Highbyte.DotNet6502.Systems; using Highbyte.DotNet6502.Systems.Generic; using Highbyte.DotNet6502.Systems.Generic.Config; +using Highbyte.DotNet6502.Systems.Commodore64; namespace Highbyte.DotNet6502.Impl.Skia.Generic.Video; -public class GenericComputerSkiaRenderer : IRenderer +public class GenericComputerSkiaRenderer : IRenderer { + private readonly GenericComputer _genericComputer; + public ISystem System => _genericComputer; + private readonly SkiaRenderContext _skiaRenderContext; private Func _getSkCanvas = default!; private SKPaintMaps _skPaintMaps = default!; @@ -20,14 +24,16 @@ public class GenericComputerSkiaRenderer : IRenderer - + diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs index dfacdefc..836dafde 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/SkiaRenderContext.cs @@ -1,5 +1,4 @@ using Highbyte.DotNet6502.Systems; -using SkiaSharp; namespace Highbyte.DotNet6502.Impl.Skia; @@ -75,8 +74,14 @@ public SkiaRenderContext(GRGlGetProcedureAddressDelegate getProcAddress, int siz _canvas = _renderSurface.Canvas; } + public void Init() + { + } + public void Cleanup() { + GetCanvas().Clear(); + _canvas?.Dispose(); _canvas = null; _renderSurface?.Dispose(); diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/C64.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/C64.cs index e15534ab..7686f1cd 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/C64.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/C64.cs @@ -41,15 +41,16 @@ public class C64 : ISystem, ISystemMonitor private readonly ILogger _logger; public const ushort BASIC_LOAD_ADDRESS = 0x0801; - private Action? _afterInstructionCallback = null; + private Action? _postInstructionAudioCallback = null; + private Action? _postInstructionVideoCallback = null; // Instrumentations public bool InstrumentationEnabled { get; set; } public Instrumentations Instrumentations { get; } = new(); private const string StatsCategory = "Custom"; private readonly ElapsedMillisecondsTimedStatSystem _spriteCollisionStat; - private readonly ElapsedMillisecondsTimedStatSystem _audioStat; - private readonly ElapsedMillisecondsTimedStatSystem _postInstructionCallbackStat; + private readonly ElapsedMillisecondsTimedStatSystem _postInstructionAudioCallbackStat; + private readonly ElapsedMillisecondsTimedStatSystem _postInstructionVideoCallbackStat; public bool RememberVic2RegistersPerRasterLine { get; set; } = true; @@ -77,8 +78,8 @@ public ExecEvaluatorTriggerResult ExecuteOneFrame( SystemRunner systemRunner, IExecEvaluator? execEvaluator = null) { - _audioStat.Reset(); // Reset stat, will be continiously updated after each instruction - _postInstructionCallbackStat.Reset(); // Reset stat, will be continiously updated after each instruction + _postInstructionAudioCallbackStat.Reset(); // Reset stat, will be continiously updated after each instruction + _postInstructionVideoCallbackStat.Reset(); // Reset stat, will be continiously updated after each instruction ulong cyclesToExecute = (Vic2.Vic2Model.CyclesPerFrame - Vic2.CyclesConsumedCurrentVblank); //_logger.LogTrace($"Executing one frame, {cyclesToExecute} CPU cycles."); @@ -95,8 +96,8 @@ public ExecEvaluatorTriggerResult ExecuteOneFrame( } } - _audioStat.Stop(); // Stop stat (was continiously updated after each instruction) - _postInstructionCallbackStat.Stop(); // Stop stat (was continiously updated after each instruction) + _postInstructionAudioCallbackStat.Stop(); // Stop stat (was continiously updated after each instruction) + _postInstructionVideoCallbackStat.Stop(); // Stop stat (was continiously updated after each instruction) // Update sprite collision state _spriteCollisionStat.Start(); @@ -130,20 +131,20 @@ public ExecEvaluatorTriggerResult ExecuteOneInstruction( var cycleOnRasterLineBeforeInstruction = Vic2.CyclesConsumedCurrentVblank; Vic2.AdvanceRaster(instructionExecResult.CyclesConsumed); - // Handle output processing needed after each instruction. - if (AudioEnabled) + // Handle audio processing after each instruction. + if (AudioEnabled && _postInstructionAudioCallback != null) { - _audioStat.Start(cont: true); - systemRunner.GenerateAudio(); - _audioStat.Stop(cont: true); + _postInstructionAudioCallbackStat.Start(cont: true); + _postInstructionAudioCallback.Invoke(instructionExecResult); + _postInstructionAudioCallbackStat.Stop(cont: true); } - // Callback to possible registered Action to a renderer (or other processing) after each instruction. The Action is exepected to have all state of the C64 available to it. - if (_afterInstructionCallback != null) + // Handle video processing after each instruction. + if (_postInstructionVideoCallback != null) { - _postInstructionCallbackStat.Start(cont: true); - _afterInstructionCallback.Invoke(this, instructionExecResult); - _postInstructionCallbackStat.Stop(cont: true); + _postInstructionVideoCallbackStat.Start(cont: true); + _postInstructionVideoCallback.Invoke(instructionExecResult); + _postInstructionVideoCallbackStat.Stop(cont: true); } // Check for debugger breakpoints (or other possible IExecEvaluator implementations used). @@ -162,8 +163,8 @@ private C64(ILogger logger) { _logger = logger; _spriteCollisionStat = Instrumentations.Add($"{StatsCategory}-SpriteCollision", new ElapsedMillisecondsTimedStatSystem(this)); - _audioStat = Instrumentations.Add($"{StatsCategory}-Audio", new ElapsedMillisecondsTimedStatSystem(this)); - _postInstructionCallbackStat = Instrumentations.Add($"{StatsCategory}-PostInstrCallback", new ElapsedMillisecondsTimedStatSystem(this)); + _postInstructionAudioCallbackStat = Instrumentations.Add($"{StatsCategory}-AudioPostInstrCallback", new ElapsedMillisecondsTimedStatSystem(this)); + _postInstructionVideoCallbackStat = Instrumentations.Add($"{StatsCategory}-VideoPostInstrCallback", new ElapsedMillisecondsTimedStatSystem(this)); } public static C64 BuildC64(C64Config c64Config, ILoggerFactory loggerFactory) @@ -490,13 +491,14 @@ public ushort GetBasicProgramEndAddress() return (ushort)(Mem.FetchWord(0x2d) - 1); } - public void SetAfterInstructionCallback(Action afterInstructionCallback) + public void SetPostInstructionVideoCallback(Action callback) { - _afterInstructionCallback = afterInstructionCallback; + _postInstructionVideoCallback = callback; } - public void RemoveAfterInstructionCallback() + + public void SetPostInstructionAudioCallback(Action callback) { - _afterInstructionCallback = null; + _postInstructionAudioCallback = callback; } } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs b/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs index e66a8880..a5cb28d8 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/SystemList.cs @@ -1,6 +1,9 @@ namespace Highbyte.DotNet6502.Systems; public class SystemList + where TRenderContext : IRenderContext + where TInputHandlerContext : IInputHandlerContext + where TAudioHandlerContext : IAudioHandlerContext { private Func? _getRenderContext; private Func? _getInputHandlerContext; @@ -23,6 +26,10 @@ public void InitContext( Func getInputHandlerContext, Func getAudioHandlerContext) { + getRenderContext().Init(); + getInputHandlerContext().Init(); + getAudioHandlerContext().Init(); + _getRenderContext = getRenderContext; _getInputHandlerContext = getInputHandlerContext; _getAudioHandlerContext = getAudioHandlerContext; @@ -126,6 +133,7 @@ public async Task BuildSystemRunner( var systemConfig = await GetCurrentSystemConfig(systemName, configurationVariant); var hostSystemConfig = await _systemConfigurers[systemName].GetHostSystemConfig(); var systemRunner = _systemConfigurers[systemName].BuildSystemRunner(system, systemConfig, hostSystemConfig, _getRenderContext(), _getInputHandlerContext(), _getAudioHandlerContext()); + systemRunner.Init(); return systemRunner; } diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandler.cs b/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandler.cs index 0d97b820..501dee89 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandler.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandler.cs @@ -4,50 +4,45 @@ namespace Highbyte.DotNet6502.Systems; public interface IAudioHandler { - void Init(ISystem system, IAudioHandlerContext audioHandlerContext); - void GenerateAudio(ISystem system); - + void Init(); + void AfterFrame(); void StartPlaying(); void StopPlaying(); void PausePlaying(); + void Cleanup(); + List GetDebugInfo(); Instrumentations Instrumentations { get; } -} - -public interface IAudioHandler : IAudioHandler - where TSystem : ISystem - where TAudioHandlerContext : IAudioHandlerContext -{ - void Init(TSystem system, TAudioHandlerContext audioHandlerContext); - - void GenerateAudio(TSystem system); + ISystem System { get; } } public class NullAudioHandler : IAudioHandler { - public NullAudioHandler() + private readonly ISystem _system; + public ISystem System => _system; + + public NullAudioHandler(ISystem system) { + _system = system; } - - public void Init(ISystem system, IAudioHandlerContext audioHandlerContext) + public void Init() { } - - public void GenerateAudio(ISystem system) + public void AfterFrame() { } - public void StartPlaying() { } - public void PausePlaying() { } - public void StopPlaying() { } + public void Cleanup() + { + } public List GetDebugInfo() => new(); diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandlerContext.cs b/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandlerContext.cs index 5888deab..7a6e1cbc 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandlerContext.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IAudioHandlerContext.cs @@ -2,14 +2,16 @@ namespace Highbyte.DotNet6502.Systems; public interface IAudioHandlerContext { -} - -public interface IAudioHandlerContext : IAudioHandlerContext - where TSystem : ISystem -{ + void Init(); + void Cleanup(); } public class NullAudioHandlerContext : IAudioHandlerContext { - + public void Cleanup() + { + } + public void Init() + { + } } diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IInputHandler.cs b/src/libraries/Highbyte.DotNet6502/Systems/IInputHandler.cs index 4cbf1b4f..7f205064 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IInputHandler.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IInputHandler.cs @@ -4,31 +4,34 @@ namespace Highbyte.DotNet6502.Systems; public interface IInputHandler { - void Init(ISystem system, IInputHandlerContext inputContext); - void ProcessInput(ISystem system); + void Init(); + void BeforeFrame(); + void Cleanup(); List GetDebugInfo(); Instrumentations Instrumentations { get; } -} - -public interface IInputHandler : IInputHandler - where TSystem : ISystem - where TInputHandlerContext : IInputHandlerContext -{ - void Init(TSystem system, TInputHandlerContext inputContext); - - void ProcessInput(TSystem system); + ISystem System { get; } } public class NullInputHandler : IInputHandler { - public void Init(ISystem system, IInputHandlerContext inputHandlerContext) + private readonly ISystem _system; + public ISystem System => _system; + + public NullInputHandler(ISystem system) { + _system = system; } - - public void ProcessInput(ISystem system) + public void Init() { } + public void BeforeFrame() + { + } + public void Cleanup() + { + } + public List GetDebugInfo() => new(); public Instrumentations Instrumentations { get; } = new(); diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IInputHandlerContext.cs b/src/libraries/Highbyte.DotNet6502/Systems/IInputHandlerContext.cs index 6f7ac021..19b08cb7 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IInputHandlerContext.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IInputHandlerContext.cs @@ -2,13 +2,16 @@ namespace Highbyte.DotNet6502.Systems; public interface IInputHandlerContext { -} - -public interface IInputHandlerContext : IInputHandlerContext - where TSystem : ISystem -{ + void Init(); + void Cleanup(); } public class NullInputHandlerContext : IInputHandlerContext { -} \ No newline at end of file + public void Cleanup() + { + } + public void Init() + { + } +} diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IRenderContext.cs b/src/libraries/Highbyte.DotNet6502/Systems/IRenderContext.cs index ea6c1bae..6f94debc 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IRenderContext.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IRenderContext.cs @@ -2,14 +2,16 @@ namespace Highbyte.DotNet6502.Systems; public interface IRenderContext { + void Init(); + void Cleanup(); } -public interface IRenderContext : IRenderContext - where TSystem : ISystem -{ -} - - public class NullRenderContext : IRenderContext { -} \ No newline at end of file + public void Cleanup() + { + } + public void Init() + { + } +} diff --git a/src/libraries/Highbyte.DotNet6502/Systems/IRenderer.cs b/src/libraries/Highbyte.DotNet6502/Systems/IRenderer.cs index 090619ff..b80dc48f 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/IRenderer.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/IRenderer.cs @@ -4,34 +4,30 @@ namespace Highbyte.DotNet6502.Systems; public interface IRenderer { - void Init(ISystem system, IRenderContext renderContext); - void Draw(ISystem system); - + void Init(); + void DrawFrame(); void Cleanup(); - Instrumentations Instrumentations { get; } -} - -public interface IRenderer : IRenderer - where TSystem : ISystem - where TRenderContext : IRenderContext -{ - void Init(TSystem system, TRenderContext renderContext); - void Draw(TSystem system); + ISystem System { get; } } public class NullRenderer : IRenderer { + private readonly ISystem _system; + public ISystem System => _system; public Instrumentations Instrumentations { get; } = new(); - public void Init(ISystem system, IRenderContext renderContext) + + public NullRenderer(ISystem system) { + _system = system; } - - public void Draw(ISystem system) + public void Init() + { + } + public void DrawFrame() { } - public void Cleanup() { } diff --git a/src/libraries/Highbyte.DotNet6502/Systems/SystemRunner.cs b/src/libraries/Highbyte.DotNet6502/Systems/SystemRunner.cs index 23d51390..8219aaa6 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/SystemRunner.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/SystemRunner.cs @@ -3,21 +3,58 @@ namespace Highbyte.DotNet6502.Systems; public class SystemRunner { private readonly ISystem _system; - private IRenderer _renderer = default!; - private IInputHandler _inputHandler = default!; - private IAudioHandler _audioHandler = default!; + private readonly IRenderer _renderer = default!; + private readonly IInputHandler _inputHandler = default!; + private readonly IAudioHandler _audioHandler = default!; public ISystem System => _system; - public IRenderer Renderer { get => _renderer; set => _renderer = value; } - public IInputHandler InputHandler { get => _inputHandler; set => _inputHandler = value; } - public IAudioHandler AudioHandler { get => _audioHandler; set => _audioHandler = value; } + public IRenderer Renderer { get => _renderer; } + public IInputHandler InputHandler { get => _inputHandler; } + public IAudioHandler AudioHandler { get => _audioHandler; } private IExecEvaluator? _customExecEvaluator; public IExecEvaluator? CustomExecEvaluator => _customExecEvaluator; - public SystemRunner(ISystem system) + public SystemRunner(ISystem system) : this(system, new NullRenderer(system), new NullInputHandler(system), new NullAudioHandler(system)) { + } + + public SystemRunner(ISystem system, IRenderer renderer) : this(system, renderer, new NullInputHandler(system), new NullAudioHandler(system)) + { + } + + public SystemRunner(ISystem system, IInputHandler inputHandler) : this(system, new NullRenderer(system), inputHandler, new NullAudioHandler(system)) + { + } + + public SystemRunner(ISystem system, IAudioHandler audioHandler) : this(system, new NullRenderer(system), new NullInputHandler(system), audioHandler) + { + } + + public SystemRunner(ISystem system, IRenderer renderer, IInputHandler inputHandler) : this(system, renderer, inputHandler, new NullAudioHandler(system)) + { + } + + public SystemRunner(ISystem system, IRenderer renderer, IInputHandler inputHandler, IAudioHandler audioHandler) + { + if (system != renderer.System) + throw new DotNet6502Exception("Renderer must be for the same system as the SystemRunner."); + if (system != inputHandler.System) + throw new DotNet6502Exception("InputHandler must be for the same system as the SystemRunner."); + if (system != audioHandler.System) + throw new DotNet6502Exception("AudioHandler must be for the same system as the SystemRunner."); + _system = system; + _renderer = renderer; + _inputHandler = inputHandler; + _audioHandler = audioHandler; + } + + public void Init() + { + _renderer.Init(); + _audioHandler.Init(); + _inputHandler.Init(); } /// @@ -39,9 +76,9 @@ public void ClearCustomExecEvaluator() /// Called by host app that runs the emulator. /// Typically before RunEmulatorOneFrame is called. /// - public void ProcessInput() + public void ProcessInputBeforeFrame() { - _inputHandler?.ProcessInput(_system); + _inputHandler.BeforeFrame(); } /// @@ -55,32 +92,17 @@ public ExecEvaluatorTriggerResult RunEmulatorOneFrame() } /// - /// Called by host app that runs the emulator, typically once per frame tied to the host app rendering frequency. + /// Called by host app that runs the emulator, once per frame tied to the host app rendering frequency. /// public void Draw() { - _renderer?.Draw(_system); - } - - /// - /// Called by the specific ISystem implementation after each instruction or entire frame worth of instructions, depending how audio is implemented. - /// - /// - public void GenerateAudio() - { - _audioHandler?.GenerateAudio(_system); - - //var t = new Task(() => _audioHandler?.GenerateAudio(system)); - //t.RunSynchronously(); + _renderer.DrawFrame(); } public void Cleanup() { - _renderer?.Cleanup(); - - _audioHandler?.StopPlaying(); - //_audioHandler?.Cleanup(); - - //_inputHandler?.Cleanup(); + _renderer.Cleanup(); + _audioHandler.Cleanup(); + _inputHandler.Cleanup(); } } diff --git a/src/libraries/Highbyte.DotNet6502/Systems/SystemRunnerBuilder.cs b/src/libraries/Highbyte.DotNet6502/Systems/SystemRunnerBuilder.cs deleted file mode 100644 index a5945e1b..00000000 --- a/src/libraries/Highbyte.DotNet6502/Systems/SystemRunnerBuilder.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Highbyte.DotNet6502.Systems; - -public class SystemRunnerBuilder - where TSystem : ISystem - where TRenderContext : IRenderContext - where TInputHandlerContext : IInputHandlerContext - where TAudioHandlerContext: IAudioHandlerContext -{ - private readonly SystemRunner _systemRunner; - - public SystemRunnerBuilder(TSystem system) - { - _systemRunner = new SystemRunner(system); - } - - public SystemRunnerBuilder WithRenderer(IRenderer renderer) - { - _systemRunner.Renderer = renderer; - return this; - } - public SystemRunnerBuilder WithRenderer(IRenderer renderer) - { - _systemRunner.Renderer = renderer; - return this; - } - - public SystemRunnerBuilder WithInputHandler(IInputHandler inputHandler) - { - _systemRunner.InputHandler = inputHandler; - return this; - } - public SystemRunnerBuilder WithInputHandler(IInputHandler inputHandler) - { - _systemRunner.InputHandler = inputHandler; - return this; - } - - public SystemRunnerBuilder WithAudioHandler(IAudioHandler audioHandler) - { - _systemRunner.AudioHandler = audioHandler; - return this; - } - public SystemRunnerBuilder WithAudioHandler(IAudioHandler audioHandler) - { - _systemRunner.AudioHandler = audioHandler; - return this; - } - - public SystemRunner Build() - { - return _systemRunner; - } -} diff --git a/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerBuilderTests.cs b/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerBuilderTests.cs deleted file mode 100644 index c661c08a..00000000 --- a/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerBuilderTests.cs +++ /dev/null @@ -1,254 +0,0 @@ -using Highbyte.DotNet6502.Instrumentation; -using Highbyte.DotNet6502.Systems; - -namespace Highbyte.DotNet6502.Tests.Systems; - -public class SystemRunnerBuilderTests -{ - [Fact] - public void WithRenderer_WhenGivenARenderer_SetsTheRendererOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var renderer = new TestRenderer(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithRenderer(renderer); - - // Assert - var result = builder.Build(); - Assert.Equal(renderer, result.Renderer); - } - - [Fact] - public void WithRenderer_WhenGivenANonGenericRenderer_SetsTheRendererOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var renderer = new TestRendererNonGeneric(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithRenderer(renderer); - - // Assert - var result = builder.Build(); - Assert.Equal(renderer, result.Renderer); - } - - [Fact] - public void WithInputHandler_WhenGivenAnInputHandler_SetsTheInputHandlerOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var handler = new TestInputHandler(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithInputHandler(handler); - - // Assert - var result = builder.Build(); - Assert.Equal(handler, result.InputHandler); - } - [Fact] - public void WithInputHandler_WhenGivenAnNonGenreicInputHandler_SetsTheInputHandlerOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var handler = new TestInputHandlerNonGeneric(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithInputHandler(handler); - - // Assert - var result = builder.Build(); - Assert.Equal(handler, result.InputHandler); - } - - [Fact] - public void WithAudioHandler_WhenGivenAnAudioHandler_SetsTheAudioHandlerOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var handler = new TestAudioHandler(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithAudioHandler(handler); - - // Assert - var result = builder.Build(); - Assert.Equal(handler, result.AudioHandler); - } - - [Fact] - public void WithAudioHandler_WhenGivenAnNonGenericAudioHandler_SetsTheAudioHandlerOnTheSystemRunner() - { - // Arrange - var system = new TestSystem(); - var handler = new TestAudioHandlerNonGeneric(); - var builder = new SystemRunnerBuilder(system); - - // Act - builder.WithAudioHandler(handler); - - // Assert - var result = builder.Build(); - Assert.Equal(handler, result.AudioHandler); - } -} - -public class TestSystem : ISystem -{ - public string Name => "Test"; - - public List SystemInfo => new List(); - - public CPU CPU => throw new NotImplementedException(); - - public Memory Mem => throw new NotImplementedException(); - - public IScreen Screen => throw new NotImplementedException(); - - public bool InstrumentationEnabled { get; set; } = false; - - public Instrumentations Instrumentations { get; } = new(); - - public ExecEvaluatorTriggerResult ExecuteOneFrame(SystemRunner systemRunner, IExecEvaluator? execEvaluator = null) - { - return new ExecEvaluatorTriggerResult(); - } - - public ExecEvaluatorTriggerResult ExecuteOneInstruction(SystemRunner systemRunner, out InstructionExecResult instructionExecResult, IExecEvaluator? execEvaluator = null) - { - instructionExecResult = new InstructionExecResult(); - return new ExecEvaluatorTriggerResult(); - } -} - -public class TestRenderer : IRenderer -{ - public bool CleanUpWasCalled = false; - - public void Draw(TestSystem system) - { - } - public void Draw(ISystem system) - { - } - public void Init(TestSystem system, IRenderContext renderContext) - { - } - public void Init(ISystem system, IRenderContext renderContext) - { - } - public void Cleanup() - { - CleanUpWasCalled = true; - } - public Instrumentations Instrumentations { get; } = new(); -} -public class TestRendererNonGeneric : IRenderer -{ - public void Draw(ISystem system) - { - } - public void Init(ISystem system, IRenderContext renderContext) - { - } - public void Cleanup() - { - } - public Instrumentations Instrumentations { get; } = new(); -} - -public class TestInputHandler : IInputHandler -{ - public void Init(TestSystem system, IInputHandlerContext inputContext) - { - } - public void Init(ISystem system, IInputHandlerContext inputContext) - { - } - public void ProcessInput(TestSystem system) - { - } - public void ProcessInput(ISystem system) - { - } - - public List GetDebugInfo() => new(); - - public Instrumentations Instrumentations { get; } = new(); -} - -public class TestInputHandlerNonGeneric : IInputHandler -{ - - public void Init(ISystem system, IInputHandlerContext inputContext) - { - } - public void ProcessInput(ISystem system) - { - } - public List GetDebugInfo() => new List(); - public Instrumentations Instrumentations { get; } = new(); -} - -public class TestAudioHandler : IAudioHandler -{ - public bool StopPlayingWasCalled = false; - - public void GenerateAudio(TestSystem system) - { - } - public void GenerateAudio(ISystem system) - { - } - public void Init(TestSystem system, IAudioHandlerContext audioHandlerContext) - { - } - public void Init(ISystem system, IAudioHandlerContext audioHandlerContext) - { - } - public void PausePlaying() - { - } - public void StartPlaying() - { - } - public void StopPlaying() - { - StopPlayingWasCalled = true; - } - public List GetDebugInfo() => new(); - - public Instrumentations Instrumentations { get; } = new(); - -} - -public class TestAudioHandlerNonGeneric : IAudioHandler -{ - public void GenerateAudio(ISystem system) - { - } - public void Init(ISystem system, IAudioHandlerContext audioHandlerContext) - { - } - public void PausePlaying() - { - } - public void StartPlaying() - { - } - public void StopPlaying() - { - } - public List GetDebugInfo() => new(); - - public Instrumentations Instrumentations { get; } = new(); - -} diff --git a/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerTests.cs b/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerTests.cs index 6cc659b4..8cf49c66 100644 --- a/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerTests.cs +++ b/tests/Highbyte.DotNet6502.Tests/Systems/SystemRunnerTests.cs @@ -1,3 +1,4 @@ +using Highbyte.DotNet6502.Instrumentation; using Highbyte.DotNet6502.Systems; namespace Highbyte.DotNet6502.Tests.Systems; @@ -5,37 +6,284 @@ namespace Highbyte.DotNet6502.Tests.Systems; public class SystemRunnerTests { [Fact] - public void CallingCleanUpWillCleanUpRenderer() + public void CreatingWithRendererAndInputHandlerAndAudioHandlerWorks() { // Arrange var system = new TestSystem(); - var systemRunner = new SystemRunner(system); - var renderer = new TestRenderer(); - systemRunner.Renderer = renderer; + var renderer = new TestRenderer(system, new NullRenderContext()); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var audioHandler = new TestAudioHandler(system, new NullAudioHandlerContext()); + var systemRunner = new SystemRunner(system, renderer, inputHandler, audioHandler); // Act - systemRunner.Cleanup(); + systemRunner.Init(); // Assert - Assert.True(renderer.CleanUpWasCalled); + Assert.Equal(system, systemRunner.System); + Assert.Equal(renderer, systemRunner.Renderer); + Assert.Equal(inputHandler, systemRunner.InputHandler); + Assert.Equal(audioHandler, systemRunner.AudioHandler); + } + + [Fact] + public void CreatingWithOnlyRendererWorksAndSetsOthersToNullImplementations() + { + // Arrange + var system = new TestSystem(); + var renderer = new TestRenderer(system, new NullRenderContext()); + var systemRunner = new SystemRunner(system, renderer); + + // Act + systemRunner.Init(); + + // Assert + Assert.Equal(system, systemRunner.System); + Assert.Equal(renderer, systemRunner.Renderer); + Assert.Equal(typeof(NullInputHandler), systemRunner.InputHandler.GetType()); + Assert.Equal(typeof(NullAudioHandler), systemRunner.AudioHandler.GetType()); + } + + [Fact] + public void CreatingWithOnlyInputHandlerWorksAndSetsOthersToNullImplementations() + { + // Arrange + var system = new TestSystem(); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var systemRunner = new SystemRunner(system, inputHandler); + + // Act + systemRunner.Init(); + + // Assert + Assert.Equal(system, systemRunner.System); + Assert.Equal(typeof(NullRenderer), systemRunner.Renderer.GetType()); + Assert.Equal(inputHandler, systemRunner.InputHandler); + Assert.Equal(typeof(NullAudioHandler), systemRunner.AudioHandler.GetType()); + } + + [Fact] + public void CreatingWithOnlyAudioHandlerWorksAndSetsOthersToNullImplementations() + { + // Arrange + var system = new TestSystem(); + var audioHandler = new TestAudioHandler(system, new NullAudioHandlerContext()); + var systemRunner = new SystemRunner(system, audioHandler); + + // Act + systemRunner.Init(); + + // Assert + Assert.Equal(system, systemRunner.System); + Assert.Equal(typeof(NullRenderer), systemRunner.Renderer.GetType()); + Assert.Equal(typeof(NullInputHandler), systemRunner.InputHandler.GetType()); + Assert.Equal(audioHandler, systemRunner.AudioHandler); } [Fact] - public void CallingCleanUpWillStopPlayingAndCleanUpAudioHandler() + public void CreatingWithOnlyRendererAndInputHandlersWorksAndSetsOthersToNullImplementations() { // Arrange var system = new TestSystem(); - var systemRunner = new SystemRunner(system); + var renderer = new TestRenderer(system, new NullRenderContext()); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var systemRunner = new SystemRunner(system, renderer, inputHandler); - var audioHandler = new TestAudioHandler(); - systemRunner.AudioHandler = audioHandler; + // Act + systemRunner.Init(); + + // Assert + Assert.Equal(system, systemRunner.System); + Assert.Equal(renderer, systemRunner.Renderer); + Assert.Equal(inputHandler, systemRunner.InputHandler); + Assert.Equal(typeof(NullAudioHandler), systemRunner.AudioHandler.GetType()); + } + + [Fact] + public void CreatingWithDifferentSystemInRendererFails() + { + // Arrange + var system = new TestSystem(); + var system2 = new TestSystem(); + var renderer = new TestRenderer(system2, new NullRenderContext()); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var audioHandler = new TestAudioHandler(system, new NullAudioHandlerContext()); + + // Act / Assert + var ex = Assert.Throws(() => new SystemRunner(system, renderer, inputHandler, audioHandler)); + Assert.Contains("Renderer must be for the same system as the SystemRunner", ex.Message); + } + + [Fact] + public void CreatingWithDifferentSystemInInputHandlerFails() + { + // Arrange + var system = new TestSystem(); + var system2 = new TestSystem(); + var renderer = new TestRenderer(system, new NullRenderContext()); + var inputHandler = new TestInputHandler(system2, new NullInputHandlerContext()); + var audioHandler = new TestAudioHandler(system, new NullAudioHandlerContext()); + + // Act / Assert + var ex = Assert.Throws(() => new SystemRunner(system, renderer, inputHandler, audioHandler)); + Assert.Contains("InputHandler must be for the same system as the SystemRunner", ex.Message); + } + + [Fact] + public void CreatingWithDifferentSystemInAudioHandlerFails() + { + // Arrange + var system = new TestSystem(); + var system2 = new TestSystem(); + var renderer = new TestRenderer(system, new NullRenderContext()); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var audioHandler = new TestAudioHandler(system2, new NullAudioHandlerContext()); + + // Act / Assert + var ex = Assert.Throws(() => new SystemRunner(system, renderer, inputHandler, audioHandler)); + Assert.Contains("AudioHandler must be for the same system as the SystemRunner", ex.Message); + } + + [Fact] + public void CallingCleanUpWillCleanUpRendererAndInputHandlerAndAudioHandler() + { + // Arrange + var system = new TestSystem(); + var renderer = new TestRenderer(system, new NullRenderContext()); + var inputHandler = new TestInputHandler(system, new NullInputHandlerContext()); + var audioHandler = new TestAudioHandler(system, new NullAudioHandlerContext()); + var systemRunner = new SystemRunner(system, renderer, inputHandler, audioHandler); + systemRunner.Init(); // Act systemRunner.Cleanup(); // Assert - Assert.True(audioHandler.StopPlayingWasCalled); - //Assert.True(audioHandler.CleanupWasCalled); + Assert.True(renderer.CleanUpWasCalled); + Assert.True(inputHandler.CleanUpWasCalled); + Assert.True(audioHandler.CleanUpWasCalled); + } + + +} + +public class TestSystem : ISystem +{ + public string Name => "Test"; + + public List SystemInfo => new List(); + + public CPU CPU => throw new NotImplementedException(); + + public Memory Mem => throw new NotImplementedException(); + + public IScreen Screen => throw new NotImplementedException(); + + public bool InstrumentationEnabled { get; set; } = false; + + public Instrumentations Instrumentations { get; } = new(); + + public ExecEvaluatorTriggerResult ExecuteOneFrame(SystemRunner systemRunner, IExecEvaluator? execEvaluator = null) + { + return new ExecEvaluatorTriggerResult(); + } + + public ExecEvaluatorTriggerResult ExecuteOneInstruction(SystemRunner systemRunner, out InstructionExecResult instructionExecResult, IExecEvaluator? execEvaluator = null) + { + instructionExecResult = new InstructionExecResult(); + return new ExecEvaluatorTriggerResult(); + } +} + +public class TestRenderer : IRenderer +{ + private readonly TestSystem _system; + public ISystem System => _system; + + private readonly IRenderContext _renderContext; + public bool CleanUpWasCalled = false; + + public TestRenderer(TestSystem system, IRenderContext renderContext) + { + _system = system; + _renderContext = renderContext; + } + public void Init() + { + } + public void DrawFrame() + { + } + public void Cleanup() + { + CleanUpWasCalled = true; + } + public Instrumentations Instrumentations { get; } = new(); +} + +public class TestInputHandler : IInputHandler +{ + private readonly TestSystem _system; + public ISystem System => _system; + + private readonly IInputHandlerContext _inputContext; + public bool CleanUpWasCalled = false; + + + public TestInputHandler(TestSystem system, IInputHandlerContext inputContext) + { + _system = system; + _inputContext = inputContext; + } + public void Init() + { + } + public void BeforeFrame() + { + } + public void Cleanup() + { + CleanUpWasCalled = true; + } + public List GetDebugInfo() => new(); + + public Instrumentations Instrumentations { get; } = new(); +} + +public class TestAudioHandler : IAudioHandler +{ + private readonly TestSystem _system; + public ISystem System => _system; + + private readonly IAudioHandlerContext _audioHandlerContext; + public bool CleanUpWasCalled = false; + + public TestAudioHandler(TestSystem system, IAudioHandlerContext audioHandlerContext) + { + _system = system; + _audioHandlerContext = audioHandlerContext; + } + + public void Init() + { + } + public void AfterFrame() + { + } + public void PausePlaying() + { + } + public void StartPlaying() + { + } + public void StopPlaying() + { + } + public void Cleanup() + { + CleanUpWasCalled = true; + StopPlaying(); } + public List GetDebugInfo() => new(); + public Instrumentations Instrumentations { get; } = new(); }