Skip to content

Commit

Permalink
Feature/gamepad c64 (#89)
Browse files Browse the repository at this point in the history
* Native Silk.Net Gamepad to control C64 Joystick.

* Web WASM Gamepad to control C64 Joystick.
  • Loading branch information
highbyte authored Oct 12, 2023
1 parent df8b1fa commit acc0237
Show file tree
Hide file tree
Showing 23 changed files with 435 additions and 123 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-push-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
# PROJECT_FILE6: "src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Highbyte.DotNet6502.Impl.SilkNet.csproj"
# PROJECT_FILE7: "src/libraries/Highbyte.DotNet6502.Impl.Skia/Highbyte.DotNet6502.Impl.Skia.csproj"
CONFIGURATION: "Release"
VERSION: "0.12.0" #Major.Minor[.Rev]
VERSION: "0.12.1" #Major.Minor[.Rev]
VERSION_SUFFIX: "-alpha"
PACKAGE_REPO: "https://nuget.pkg.github.com/highbyte/index.json"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sonarscan-dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

# Runs a single command using the runners shell
- name: SonarScanner for .NET 7 with pull request decoration support
uses: highbyte/[email protected].4
uses: highbyte/[email protected].6
with:
# The key of the SonarQube project
sonarProjectKey: highbyte_dotnet-6502
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Linq;
using System.Numerics;
using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Input;
using Highbyte.DotNet6502.Systems.Commodore64.Config;
using static Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral.C64Joystick;
using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral;

namespace Highbyte.DotNet6502.App.SkiaNative.ConfigUI;

Expand All @@ -20,13 +20,14 @@ public class SilkNetImGuiC64Config
public bool Cancel { get; set; }

private bool _isValidConfig = true;

public bool IsValidConfig => _isValidConfig;
private List<string>? _validationErrors;

private const int POS_X = 50;
private const int POS_Y = 50;
private const int WIDTH = 400;
private const int HEIGHT = 380;
private const int HEIGHT = 550;
//private static Vector4 s_informationColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f);
private static Vector4 s_errorColor = new Vector4(1.0f, 0.0f, 0.0f, 1.0f);
//private static Vector4 s_warningColor = new Vector4(0.5f, 0.8f, 0.8f, 1);
Expand Down Expand Up @@ -91,15 +92,22 @@ public void PostOnRender()
_config!.SetROM(C64Config.CHARGEN_ROM_NAME, _chargenRomFile);
}

ImGui.Text("Keyboard joystick 2");
var keyToJoystickMap = _config!.KeyboardJoystickMap;
int joystick = 2;
ImGui.Text($"Joystick {joystick}");
ImGui.BeginDisabled(disabled: true);
foreach (var mapKey in C64SilkNetGamepad.SilkNetGamePadToC64JoystickMap)
{
ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}");
}
ImGui.EndDisabled();

ImGui.Text($"Keyboard Joystick {joystick}");
var keyToJoystickMap = _config!.KeyboardJoystickMap;
ImGui.BeginDisabled(disabled: true);
ImGui.LabelText("Up", $"{string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Up))}");
ImGui.LabelText("Down", $"{string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Down))}");
ImGui.LabelText("Left", $"{string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Left))}");
ImGui.LabelText("Right", $"{string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Right))}");
ImGui.LabelText("Fire", $"{string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Fire)).Replace(" ", "SPACE")}");
foreach (var mapKey in keyToJoystickMap.GetMap(joystick))
{
ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}");
}
ImGui.EndDisabled();

if (_config!.IsDirty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class SilkNetImGuiLogsPanel : ISilkNetImGuiWindow

private const int POS_X = 300;
private const int POS_Y = 2;
private const int WIDTH = 800;
private const int WIDTH = 950;
private const int HEIGHT = 600;
private static Vector4 s_labelColor = new Vector4(0.7f, 0.7f, 0.7f, 1.0f);
private static Vector4 s_informationColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Numerics;
using Highbyte.DotNet6502.App.SkiaNative.ConfigUI;
using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Input;
using Highbyte.DotNet6502.Systems;
using Highbyte.DotNet6502.Systems.Commodore64;
using Highbyte.DotNet6502.Systems.Commodore64.Config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<PackageReference Include="SkiaSharp.Views.Blazor" Version="2.88.6" />
<PackageReference Include="PublishSPAforGitHubPages.Build" Version="2.1.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Toolbelt.Blazor.Gamepad" Version="8.0.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@using Highbyte.DotNet6502.App.SkiaWASM.Skia
@using Highbyte.DotNet6502.Impl.AspNet.Commodore64.Input;
@using Highbyte.DotNet6502.Systems;
@using Highbyte.DotNet6502.Systems.Commodore64.Config
@using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral;
@using static Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral.C64Joystick;
@using static Highbyte.DotNet6502.App.SkiaWASM.Pages.Index

Expand Down Expand Up @@ -65,36 +67,50 @@ else
</div>
</div>

<h2>Joystick #2 keyboard map</h2>

<h2>Joystick</h2>
@{
var gampadToJoystickMap = C64AspNetGamepad.AspNetGamePadToC64JoystickMap;
int joystick = 2;
}
<div class="table">
<div class="table-row">
<div class="table-cell twocol">Joystick @joystick action</div>
<div class="table-cell twocol">Gampad button</div>
</div>
@{
foreach (var mapKey in gampadToJoystickMap)
{
<div class="table-row">
<div class="table-cell twocol">@string.Join(",", mapKey.Value)</div>
<div class="table-cell twocol">@string.Join(",", mapKey.Key)</div>
</div>
}
}
</div>

<h2>Joystick keyboard</h2>
@{
var keyToJoystickMap = C64Config.KeyboardJoystickMap;
var joystick = 2;
}
<div class="table">
<div class="table-row">
<div class="table-cell twocol">Enabled</div>
<div class="table-cell twocol"><input @bind="C64Config.KeyboardJoystickEnabled" @bind:event="oninput" type="checkbox" id="keyboardJoystickEnabled" title="Enable Joystick Keyboard" /></div>
</div>
<div class="table-row">
<div class="table-cell twocol">Up:</div>
<div class="table-cell twocol">@string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Up))</div>
</div>
<div class="table-row">
<div class="table-cell twocol">Down:</div>
<div class="table-cell twocol">@string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Down))</div>
</div>
<div class="table-row">
<div class="table-cell twocol">Left:</div>
<div class="table-cell twocol">@string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Left))</div>
</div>
<div class="table-row">
<div class="table-cell twocol">Right:</div>
<div class="table-cell twocol">@string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Right))</div>
</div>
<div class="table-row">
<div class="table-cell twocol">Fire:</div>
<div class="table-cell twocol">@string.Join(",", keyToJoystickMap.GetMappedKeysForJoystickAction(joystick, C64JoystickAction.Fire)).Replace(" ", "SPACE")</div>
<div class="table-cell twocol">Joystick @joystick action</div>
<div class="table-cell twocol">Key</div>
</div>
@{
foreach (var mapKey in keyToJoystickMap.GetMap(joystick))
{
<div class="table-row">
<div class="table-cell twocol">@string.Join(",", mapKey.Value)</div>
<div class="table-cell twocol">@string.Join(",", mapKey.Key)</div>
</div>
}
}
</div>

<p></p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,21 @@
private string SYSTEM_NAME = C64.SystemName;

// Note: The current config object (reference) is stored in this variable so that the UI can bind it's properties (not possible to use async call to _systemList.GetSystemConfig() in property )
private C64Config _c64Config => (C64Config)Parent.SystemConfig;
private C64Config? _c64Config => Parent.SystemConfig as C64Config;


private bool JoystickKeyboardEnabled
{
get
{
if (_c64Config == null)
return false;
return _c64Config?.KeyboardJoystickEnabled ?? false;
}
set
{
if (_c64Config == null)
return;
_c64Config.KeyboardJoystickEnabled = value;
if (Parent.CurrentEmulatorState != EmulatorState.Uninitialized)
{
Expand Down Expand Up @@ -316,13 +321,12 @@

// Send "list" + Enter to the keyboard buffer to immediately list the loaded program
var c64Keyboard = c64.Cia.Keyboard;
// TODO after implementing C64 keyboard matrix scanning
// TODO: Bypass keyboard matrix scanning and send directly to keyboard buffer?
// c64Keyboard.KeyPressed(Petscii.CharToPetscii['l']);
// c64Keyboard.KeyPressed(Petscii.CharToPetscii['i']);
// c64Keyboard.KeyPressed(Petscii.CharToPetscii['s']);
// c64Keyboard.KeyPressed(Petscii.CharToPetscii['t']);
// c64Keyboard.KeyPressed(Petscii.CharToPetscii[(char)13]);
// Bypass keyboard matrix scanning and send directly to keyboard buffer?
c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['l']);
c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['i']);
c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['s']);
c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii['t']);
c64Keyboard.InsertPetsciiCharIntoBuffer(Petscii.CharToPetscii[(char)13]);

await Parent.OnStart(new());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Components.Forms;
using Blazored.LocalStorage;
using Highbyte.DotNet6502.Logging.Console;
using Toolbelt.Blazor.Gamepad;

namespace Highbyte.DotNet6502.App.SkiaWASM.Pages;

Expand Down Expand Up @@ -121,6 +122,9 @@ private double Scale
[Inject]
public DotNet6502ConsoleLoggerConfiguration LoggerConfiguration { get; set; }

[Inject]
public GamepadList GamepadList { get; set; }

private ILogger<Index> _logger;

protected override async Task OnInitializedAsync()
Expand Down Expand Up @@ -268,7 +272,7 @@ protected async void OnPaintSurface(SKPaintGLSurfaceEventArgs e)

if (!_wasmHost.Initialized)
{
await _wasmHost.Init(e.Surface.Canvas, grContext, _audioContext, Js!);
await _wasmHost.Init(e.Surface.Canvas, grContext, _audioContext, GamepadList, Js!);
}

//_emulatorRenderer.SetSize(e.Info.Width, e.Info.Height);
Expand Down
2 changes: 2 additions & 0 deletions src/apps/Highbyte.DotNet6502.App.SkiaWASM/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using Highbyte.DotNet6502.App.SkiaWASM;
using Highbyte.DotNet6502.Logging.Console;
using Blazored.LocalStorage;
using Toolbelt.Blazor.Extensions.DependencyInjection;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddBlazoredModal();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddGamepadList();

builder.Logging.ClearProviders();
builder.Logging.AddDotNet6502Console();
Expand Down
5 changes: 3 additions & 2 deletions src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Highbyte.DotNet6502.Impl.Skia;
using Highbyte.DotNet6502.Monitor;
using Highbyte.DotNet6502.Systems;
using Toolbelt.Blazor.Gamepad;

namespace Highbyte.DotNet6502.App.SkiaWASM.Skia;

Expand Down Expand Up @@ -97,13 +98,13 @@ public WasmHost(
Initialized = false;
}

public async Task Init(SKCanvas canvas, GRContext grContext, AudioContextSync audioContext, IJSRuntime jsRuntime)
public async Task Init(SKCanvas canvas, GRContext grContext, AudioContextSync audioContext, GamepadList gamepadList, IJSRuntime jsRuntime)
{
_skCanvas = canvas;
_grContext = grContext;

_skiaRenderContext = new SkiaRenderContext(GetCanvas, GetGRContext);
InputHandlerContext = new AspNetInputHandlerContext(_loggerFactory);
InputHandlerContext = new AspNetInputHandlerContext(_loggerFactory, gamepadList);
AudioHandlerContext = new WASMAudioHandlerContext(audioContext, jsRuntime, _initialMasterVolume);

_systemList.InitContext(() => _skiaRenderContext, () => InputHandlerContext, () => AudioHandlerContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
using Highbyte.DotNet6502.Instructions;
using Highbyte.DotNet6502.Systems;
using Microsoft.Extensions.Logging;
using Toolbelt.Blazor.Gamepad;

namespace Highbyte.DotNet6502.Impl.AspNet;

public class AspNetInputHandlerContext : IInputHandlerContext
{
private readonly ILogger<AspNetInputHandlerContext> _logger;

// Keyboard
public HashSet<string> KeysDown = new();

private bool _capsLockKeyDownCaptured;
private bool _capsLockOn;

public AspNetInputHandlerContext(ILoggerFactory loggerFactory)
// Gamepad
private readonly GamepadList _gamepadList;
private readonly System.Timers.Timer _gamepadUpdateTimer = new System.Timers.Timer(50) { Enabled = true };
private Gamepad? _currentGamepad;
public HashSet<int> GamepadButtonsDown = new();

public AspNetInputHandlerContext(ILoggerFactory loggerFactory, GamepadList gamepadList)
{
_logger = loggerFactory.CreateLogger<AspNetInputHandlerContext>();
_gamepadList = gamepadList;
}

public void Init()
{
_gamepadUpdateTimer.Elapsed += GamepadUpdateTimer_Elapsed;
}

private async void GamepadUpdateTimer_Elapsed(object sender, EventArgs args)
{
try
{
var gamepads = await _gamepadList.GetGamepadsAsync();

var gamePad = gamepads.LastOrDefault();
if (gamePad != _currentGamepad)
{
_currentGamepad = gamePad;
if (_currentGamepad != null && _currentGamepad.Connected)
_logger.LogInformation($"Current gamepad changed to: {_currentGamepad.Id} ({_currentGamepad.Index})");
else
_logger.LogInformation($"Gamepad disconnected");
}

GamepadButtonsDown.Clear();

if (_currentGamepad != null && _currentGamepad.Connected)
{
for (int buttonIndex = 0; buttonIndex < _currentGamepad.Buttons.Count; buttonIndex++)
{
var button = _currentGamepad.Buttons[buttonIndex];
if (!button.Pressed)
continue;
GamepadButtonsDown.Add(buttonIndex);
_logger.LogInformation($"Gamepad button pressed: {buttonIndex} ({button.Pressed})");
}
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
throw;
}
}

public void KeyUp(KeyboardEventArgs e)
Expand Down Expand Up @@ -66,4 +113,9 @@ public bool GetCapsLockState()
public void Cleanup()
{
}

public void Dispose()
{
_gamepadUpdateTimer.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral;

namespace Highbyte.DotNet6502.Impl.AspNet.Commodore64.Input;

public static class C64AspNetGamepad
{
public static Dictionary<int[], C64JoystickAction[]> AspNetGamePadToC64JoystickMap = new()
{
{ new[] { 0 }, new[] { C64JoystickAction.Fire } },
{ new[] { 12 }, new[] { C64JoystickAction.Up} },
{ new[] { 13 }, new[] { C64JoystickAction.Down } },
{ new[] { 14 }, new[] { C64JoystickAction.Left } },
{ new[] { 15 }, new[] { C64JoystickAction.Right } },
};
}
Loading

0 comments on commit acc0237

Please sign in to comment.