Add sample/tests project

Just a playground to see how the string/type/array friendly overloads work.
You'll have to provide the SDL3 library manually.
This commit is contained in:
Susko3 2024-04-06 14:47:39 +02:00
parent e85349e3fa
commit bb9834d0a9
5 changed files with 419 additions and 0 deletions

238
SDL3-CS.Tests/MyWindow.cs Normal file
View File

@ -0,0 +1,238 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using SDL;
using static SDL.SDL3;
namespace SDL3.Tests
{
public sealed unsafe class MyWindow : IDisposable
{
private bool flash;
private ObjectHandle<MyWindow> objectHandle { get; }
private SDL_Window* SDLWindowHandle;
private SDL_Renderer* Renderer;
private readonly bool initSuccess;
private const SDL_InitFlags initFlags = SDL_InitFlags.SDL_INIT_VIDEO | SDL_InitFlags.SDL_INIT_GAMEPAD;
public MyWindow()
{
if (SDL_InitSubSystem(initFlags) < 0)
throw new InvalidOperationException($"failed to initialise SDL. Error: {SDL_GetError()}");
initSuccess = true;
objectHandle = new ObjectHandle<MyWindow>(this, GCHandleType.Normal);
}
public void Setup()
{
SDL_SetGamepadEventsEnabled(SDL_bool.SDL_TRUE);
SDL_SetEventFilter(&nativeFilter, objectHandle.Handle);
if (OperatingSystem.IsWindows())
SDL_SetWindowsMessageHook(&wndProc, objectHandle.Handle);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static SDL_bool wndProc(IntPtr userdata, MSG* message)
{
var handle = new ObjectHandle<MyWindow>(userdata);
if (handle.GetTarget(out var window))
{
Console.WriteLine($"from {window}, message: {message->message}");
}
return SDL_TRUE; // sample use of definition from SDL3 class, not SDL_bool enum
}
// ReSharper disable once UseCollectionExpression
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static int nativeFilter(IntPtr userdata, SDL_Event* e)
{
var handle = new ObjectHandle<MyWindow>(userdata);
if (handle.GetTarget(out var window))
return window.handleEventFromFilter(e);
return 1;
}
public Action<SDL_Event>? EventFilter;
private int handleEventFromFilter(SDL_Event* e)
{
switch (e->type)
{
case SDL_EventType.SDL_EVENT_KEY_UP:
case SDL_EventType.SDL_EVENT_KEY_DOWN:
handleKeyFromFilter(e->key);
break;
default:
EventFilter?.Invoke(*e);
break;
}
return 1;
}
private void handleKeyFromFilter(SDL_KeyboardEvent e)
{
if (e.keysym.sym == SDL_Keycode.SDLK_f)
{
flash = true;
}
}
public void Create()
{
SDLWindowHandle = SDL_CreateWindow("hello"u8, 800, 600, SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY);
Renderer = SDL_CreateRenderer(SDLWindowHandle, (byte*)null, SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);
}
private void handleEvent(SDL_Event e)
{
switch (e.type)
{
case SDL_EventType.SDL_EVENT_QUIT:
run = false;
break;
case SDL_EventType.SDL_EVENT_KEY_DOWN:
switch (e.key.keysym.sym)
{
case SDL_Keycode.SDLK_r:
bool old = SDL_GetRelativeMouseMode() == SDL_bool.SDL_TRUE;
SDL_SetRelativeMouseMode(old ? SDL_bool.SDL_FALSE : SDL_bool.SDL_TRUE);
break;
case SDL_Keycode.SDLK_v:
string? text = SDL_GetClipboardText();
Console.WriteLine($"clipboard: {text}");
break;
case SDL_Keycode.SDLK_F10:
SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_FALSE);
break;
case SDL_Keycode.SDLK_F11:
SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_TRUE);
break;
case SDL_Keycode.SDLK_j:
{
using var gamepads = SDL_GetGamepads();
if (gamepads == null || gamepads.Count == 0)
break;
var gamepad = SDL_OpenGamepad(gamepads[0]);
int count;
var bindings = SDL_GetGamepadBindings(gamepad, &count);
for (int i = 0; i < count; i++)
{
var binding = *bindings[i];
Console.WriteLine(binding.input_type);
Console.WriteLine(binding.output_type);
Console.WriteLine();
}
SDL_CloseGamepad(gamepad);
break;
}
case SDL_Keycode.SDLK_F1:
SDL_StartTextInput();
break;
case SDL_Keycode.SDLK_F2:
SDL_StopTextInput();
break;
case SDL_Keycode.SDLK_m:
SDL_Keymod mod = e.key.keysym.Mod;
Console.WriteLine(mod);
break;
}
break;
case SDL_EventType.SDL_EVENT_TEXT_INPUT:
Console.WriteLine(e.text.GetText());
break;
case SDL_EventType.SDL_EVENT_GAMEPAD_ADDED:
Console.WriteLine($"gamepad added: {e.gdevice.which}");
break;
case SDL_EventType.SDL_EVENT_WINDOW_PEN_ENTER:
SDL_PenCapabilityInfo info;
var cap = (SDL_PEN_CAPABILITIES)SDL_GetPenCapabilities((SDL_PenID)e.window.data1, &info);
if (cap.HasFlag(SDL_PEN_CAPABILITIES.SDL_PEN_AXIS_XTILT_MASK))
Console.WriteLine("has pen xtilt axis");
Console.WriteLine(info.max_tilt);
break;
}
}
private bool run = true;
private const int events_per_peep = 64;
private readonly SDL_Event[] events = new SDL_Event[events_per_peep];
private void pollEvents()
{
SDL_PumpEvents();
int eventsRead;
do
{
fixed (SDL_Event* buf = events)
eventsRead = SDL_PeepEvents(buf, events_per_peep, SDL_eventaction.SDL_GETEVENT, SDL_EventType.SDL_EVENT_FIRST, SDL_EventType.SDL_EVENT_LAST);
for (int i = 0; i < eventsRead; i++)
handleEvent(events[i]);
} while (eventsRead == events_per_peep);
}
private float frame;
public void Run()
{
while (run)
{
if (flash)
{
flash = false;
Console.WriteLine("flash!");
}
pollEvents();
SDL_SetRenderDrawColorFloat(Renderer, SDL_sinf(frame) / 2 + 0.5f, SDL_cosf(frame) / 2 + 0.5f, 0.3f, 1.0f);
SDL_RenderClear(Renderer);
SDL_RenderPresent(Renderer);
frame += 0.015f;
Thread.Sleep(10);
}
}
public void Dispose()
{
if (initSuccess)
SDL_QuitSubSystem(initFlags);
objectHandle.Dispose();
}
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Runtime.InteropServices;
namespace osu.Framework.Allocation
{
/// <summary>
/// Wrapper on <see cref="GCHandle" /> that supports the <see cref="IDisposable" /> pattern.
/// </summary>
public struct ObjectHandle<T> : IDisposable
where T : class
{
/// <summary>
/// The pointer from the <see cref="GCHandle" />, if it is allocated. Otherwise <see cref="IntPtr.Zero" />.
/// </summary>
public IntPtr Handle => handle.IsAllocated ? GCHandle.ToIntPtr(handle) : IntPtr.Zero;
/// <summary>
/// The address of target object, if it is allocated and pinned. Otherwise <see cref="IntPtr.Zero" />.
/// Note: This is not the same as the <see cref="Handle" />.
/// </summary>
public IntPtr Address => handle.IsAllocated ? handle.AddrOfPinnedObject() : IntPtr.Zero;
public bool IsAllocated => handle.IsAllocated;
private GCHandle handle;
private readonly bool fromPointer;
/// <summary>
/// Wraps the provided object with a <see cref="GCHandle" />, using the given <see cref="GCHandleType" />.
/// </summary>
/// <param name="target">The target object to wrap.</param>
/// <param name="handleType">The handle type to use.</param>
public ObjectHandle(T target, GCHandleType handleType)
{
handle = GCHandle.Alloc(target, handleType);
fromPointer = false;
}
/// <summary>
/// Recreates an <see cref="ObjectHandle{T}" /> based on the passed <see cref="IntPtr" />.
/// Disposing this object will not free the handle, the original object must be disposed instead.
/// </summary>
/// <param name="handle">Handle.</param>
public ObjectHandle(IntPtr handle)
{
this.handle = GCHandle.FromIntPtr(handle);
fromPointer = true;
}
/// <summary>
/// Gets the object being referenced.
/// Returns true if successful and populates <paramref name="target"/> with the referenced object.
/// Returns false If the handle is not allocated or the target is not of type <typeparamref name="T"/>.
/// </summary>
/// <param name="target">Populates this parameter with the targeted object.</param>
public bool GetTarget(out T target)
{
if (!handle.IsAllocated)
{
target = default;
return false;
}
try
{
if (handle.Target is T value)
{
target = value;
return true;
}
}
catch (InvalidOperationException)
{
}
target = default;
return false;
}
#region IDisposable Support
public void Dispose()
{
if (!fromPointer && handle.IsAllocated)
handle.Free();
}
#endregion
}
}

64
SDL3-CS.Tests/Program.cs Normal file
View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using System.Text;
using SDL;
using static SDL.SDL3;
namespace SDL3.Tests
{
public static class Program
{
private static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "null byte \0 in string"u8);
Debug.Assert(SDL_GetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4) == "null byte ");
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1"u8);
using (var window = new MyWindow())
{
Console.WriteLine($"SDL revision: {SDL_GetRevision()}");
printDisplays();
window.Setup();
window.Create();
const SDL_Keymod state = SDL_Keymod.SDL_KMOD_CAPS | SDL_Keymod.SDL_KMOD_ALT;
SDL_SetModState(state);
Debug.Assert(SDL_GetModState() == state);
window.Run();
}
SDL_Quit();
}
private static void printDisplays()
{
using var displays = SDL_GetDisplays();
if (displays == null)
return;
for (int i = 0; i < displays.Count; i++)
{
SDL_DisplayID id = displays[i];
Console.WriteLine(id);
using var modes = SDL_GetFullscreenDisplayModes(id);
if (modes == null)
continue;
for (int j = 0; j < modes.Count; j++)
{
SDL_DisplayMode mode = modes[j];
Console.WriteLine($"{mode.w}x{mode.h}@{mode.refresh_rate}");
}
}
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>SDL3.Tests</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SDL3-CS\SDL3-CS.csproj"/>
</ItemGroup>
</Project>

View File

@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS", "SDL3-CS\SDL3-CS.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.SourceGeneration", "SDL3-CS.SourceGeneration\SDL3-CS.SourceGeneration.csproj", "{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.Tests", "SDL3-CS.Tests\SDL3-CS.Tests.csproj", "{CF980481-8227-4BED-970E-6433C83F64CB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -34,5 +36,9 @@ Global
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Release|Any CPU.Build.0 = Release|Any CPU
{CF980481-8227-4BED-970E-6433C83F64CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF980481-8227-4BED-970E-6433C83F64CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF980481-8227-4BED-970E-6433C83F64CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF980481-8227-4BED-970E-6433C83F64CB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal