diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58221bc..18602b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,9 @@ jobs: - name: Generate Bindings run: dotnet build -c Release src/Raylib.NET.Bindgen + - name: Test + run: dotnet test -p:SkipNatives=true -p:SkipBindgen=true + - name: Build run: dotnet build -c Release -p:SkipNatives=true -p:SkipBindgen=true diff --git a/Raylib.NET.sln b/Raylib.NET.sln index e7e0f36..c35390c 100644 --- a/Raylib.NET.sln +++ b/Raylib.NET.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raylib.NET.Bindgen", "src\R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raylib.NET.Native", "src\Raylib.NET.Native\Raylib.NET.Native.csproj", "{0EE0C0FD-F7B7-42C9-8B63-095A066C0800}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raylib.NET.Test", "src\Raylib.NET.Test\Raylib.NET.Test.csproj", "{29E68756-5677-461E-8E54-8017E9FBFEED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,11 +40,16 @@ Global {0EE0C0FD-F7B7-42C9-8B63-095A066C0800}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EE0C0FD-F7B7-42C9-8B63-095A066C0800}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EE0C0FD-F7B7-42C9-8B63-095A066C0800}.Release|Any CPU.Build.0 = Release|Any CPU + {29E68756-5677-461E-8E54-8017E9FBFEED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29E68756-5677-461E-8E54-8017E9FBFEED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29E68756-5677-461E-8E54-8017E9FBFEED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29E68756-5677-461E-8E54-8017E9FBFEED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0A7DD14F-8EED-4688-8931-38AA1B940DCB} = {DA7DD588-77B2-465A-8BCF-BE3295552258} {5A3CE360-CC8F-4ADD-B342-FD6A3246C7B9} = {DA7DD588-77B2-465A-8BCF-BE3295552258} {DE44EB33-70C2-402D-9FBB-36A6CD713B4F} = {DA7DD588-77B2-465A-8BCF-BE3295552258} {0EE0C0FD-F7B7-42C9-8B63-095A066C0800} = {DA7DD588-77B2-465A-8BCF-BE3295552258} + {29E68756-5677-461E-8E54-8017E9FBFEED} = {DA7DD588-77B2-465A-8BCF-BE3295552258} EndGlobalSection EndGlobal diff --git a/src/Bindgen/Generator.cs b/src/Bindgen/Generator.cs index 80d52b0..3ba0633 100644 --- a/src/Bindgen/Generator.cs +++ b/src/Bindgen/Generator.cs @@ -49,6 +49,7 @@ public void Generate() Console.WriteLine(message); } + var types = new List(); var builder = new StringBuilder(); builder.AppendLine("using System.Runtime.CompilerServices;"); @@ -109,6 +110,7 @@ public void Generate() continue; } + types.Add(name); generated = "namespace " + options.GeneratedNamespace + ";\n\n" + generated; var path = $"{options.OutputPath}/Enums/{name}.cs"; Console.WriteLine($"- Generated: {name} - {path}"); @@ -125,6 +127,7 @@ public void Generate() continue; } + types.Add(name); var generate = ""; generate += "using System.Runtime.InteropServices;\n"; generate += "using Bindgen.Interop;\n"; @@ -148,6 +151,48 @@ public void Generate() outPath = $"{options.OutputPath}/Interop.cs"; Console.WriteLine($"> Generated: Interop - {outPath}"); WriteFile(outPath, Interop.Generate()); + + if (!string.IsNullOrEmpty(options.TestPath)) + { + outPath = $"{options.TestPath}/Test.cs"; + Console.WriteLine($"> Generated: Test - {outPath}"); + WriteFile(outPath, Test.Generate()); + + + var testBuilder = new StringBuilder(); + testBuilder.AppendLine("using Xunit;"); + testBuilder.AppendLine("using Bindgen.Test;"); + testBuilder.AppendLine(); + testBuilder.AppendLine($"namespace {options.GeneratedNamespace}.Test;"); + testBuilder.AppendLine(); + testBuilder.AppendLine($"public class {options.GeneratedClass}Test"); + testBuilder.AppendLine("{"); + + testBuilder.Append($$""" + private unsafe void CheckType() where T : unmanaged + { + Assert.True(BlittableHelper.IsBlittable()); + } + """); + + testBuilder.AppendLine(); + testBuilder.AppendLine(); + testBuilder.AppendLine(" [Fact]"); + testBuilder.AppendLine(" public void CheckTypes()"); + testBuilder.AppendLine(" {"); + + foreach (var type in types) + { + testBuilder.AppendLine($" CheckType<{type}>();"); + } + + testBuilder.AppendLine(" }"); + testBuilder.AppendLine("}"); + + outPath = $"{options.TestPath}/{options.GeneratedClass}Test.cs"; + Console.WriteLine($"> Generated: {options.GeneratedClass}Test - {outPath}"); + WriteFile(outPath, testBuilder.ToString()); + } } private String GenerateConstant(CppMacro macro, out String output) diff --git a/src/Bindgen/GeneratorOptions.cs b/src/Bindgen/GeneratorOptions.cs index 17cb98e..b9b505a 100644 --- a/src/Bindgen/GeneratorOptions.cs +++ b/src/Bindgen/GeneratorOptions.cs @@ -5,6 +5,7 @@ public struct GeneratorOptions public string GeneratedNamespace = ""; public string GeneratedClass = ""; public string OutputPath = ""; + public string TestPath = ""; public string LibraryName = ""; public string FilePath = ""; public string[] SystemIncludeFolders = { }; diff --git a/src/Bindgen/Test.cs b/src/Bindgen/Test.cs new file mode 100644 index 0000000..b70f2f0 --- /dev/null +++ b/src/Bindgen/Test.cs @@ -0,0 +1,79 @@ +namespace Bindgen; + +public class Test +{ + public static string Generate() + { + return $$""" + // For more information see the blog post: https://aakinshin.net/posts/blittable/ + // Original code derived from: https://github.com/AndreyAkinshin/BlittableStructs/blob/master/BlittableStructs/BlittableHelper.cs + + /* + The MIT License + + Copyright (c) 2015 Andrey Akinshin + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + using System; + using System.Runtime.InteropServices; + using System.Runtime.Serialization; + + namespace Bindgen.Test; + + public static class BlittableHelper + { + public static bool IsBlittable() + { + return IsBlittableCache.VALUE; + } + + public static bool IsBlittable(this Type type) + { + if (type == typeof(decimal)) + { + return false; + } + if (type.IsArray) + { + var elementType = type.GetElementType(); + return elementType != null && elementType.IsValueType && IsBlittable(elementType); + } + try + { + var instance = FormatterServices.GetUninitializedObject(type); + GCHandle.Alloc(instance, GCHandleType.Pinned).Free(); + return true; + } + catch + { + return false; + } + } + + private static class IsBlittableCache + { + public static readonly bool VALUE = IsBlittable(typeof(T)); + } + } + """; + } +} diff --git a/src/Raylib.NET.Bindgen/Program.cs b/src/Raylib.NET.Bindgen/Program.cs index 509ad5b..88dfe07 100644 --- a/src/Raylib.NET.Bindgen/Program.cs +++ b/src/Raylib.NET.Bindgen/Program.cs @@ -22,6 +22,7 @@ { "Matrix4x4", "System.Numerics" }, }, OutputPath = "../../src/Raylib.NET", + TestPath = "../../src/Raylib.NET.Test", GeneratedNamespace = "RaylibNET", LibraryName = "raylib", SystemIncludeFolders = new[] diff --git a/src/Raylib.NET.Test/RayguiTest.cs b/src/Raylib.NET.Test/RayguiTest.cs new file mode 100644 index 0000000..d6204b2 --- /dev/null +++ b/src/Raylib.NET.Test/RayguiTest.cs @@ -0,0 +1,89 @@ +using Xunit; +using Bindgen.Test; + +namespace RaylibNET.Test; + +public class RayguiTest +{ + private unsafe void CheckType() where T : unmanaged + { + Assert.True(BlittableHelper.IsBlittable()); + } + + [Fact] + public void CheckTypes() + { + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + } +} diff --git a/src/Raylib.NET.Test/Raylib.NET.Test.csproj b/src/Raylib.NET.Test/Raylib.NET.Test.csproj new file mode 100644 index 0000000..35e941f --- /dev/null +++ b/src/Raylib.NET.Test/Raylib.NET.Test.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + false + true + + + + + + + + + + + + + + diff --git a/src/Raylib.NET.Test/RaylibTest.cs b/src/Raylib.NET.Test/RaylibTest.cs new file mode 100644 index 0000000..2e94363 --- /dev/null +++ b/src/Raylib.NET.Test/RaylibTest.cs @@ -0,0 +1,69 @@ +using Xunit; +using Bindgen.Test; + +namespace RaylibNET.Test; + +public class RaylibTest +{ + private unsafe void CheckType() where T : unmanaged + { + Assert.True(BlittableHelper.IsBlittable()); + } + + [Fact] + public void CheckTypes() + { + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + } +} diff --git a/src/Raylib.NET.Test/RaymathTest.cs b/src/Raylib.NET.Test/RaymathTest.cs new file mode 100644 index 0000000..7e519fd --- /dev/null +++ b/src/Raylib.NET.Test/RaymathTest.cs @@ -0,0 +1,19 @@ +using Xunit; +using Bindgen.Test; + +namespace RaylibNET.Test; + +public class RaymathTest +{ + private unsafe void CheckType() where T : unmanaged + { + Assert.True(BlittableHelper.IsBlittable()); + } + + [Fact] + public void CheckTypes() + { + CheckType(); + CheckType(); + } +} diff --git a/src/Raylib.NET.Test/RlglTest.cs b/src/Raylib.NET.Test/RlglTest.cs new file mode 100644 index 0000000..c5524b5 --- /dev/null +++ b/src/Raylib.NET.Test/RlglTest.cs @@ -0,0 +1,31 @@ +using Xunit; +using Bindgen.Test; + +namespace RaylibNET.Test; + +public class RlglTest +{ + private unsafe void CheckType() where T : unmanaged + { + Assert.True(BlittableHelper.IsBlittable()); + } + + [Fact] + public void CheckTypes() + { + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + CheckType(); + } +} diff --git a/src/Raylib.NET.Test/Test.cs b/src/Raylib.NET.Test/Test.cs new file mode 100644 index 0000000..27c6b60 --- /dev/null +++ b/src/Raylib.NET.Test/Test.cs @@ -0,0 +1,69 @@ + // For more information see the blog post: https://aakinshin.net/posts/blittable/ + // Original code derived from: https://github.com/AndreyAkinshin/BlittableStructs/blob/master/BlittableStructs/BlittableHelper.cs + + /* + The MIT License + + Copyright (c) 2015 Andrey Akinshin + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + using System; + using System.Runtime.InteropServices; + using System.Runtime.Serialization; + + namespace Bindgen.Test; + + public static class BlittableHelper + { + public static bool IsBlittable() + { + return IsBlittableCache.VALUE; + } + + public static bool IsBlittable(this Type type) + { + if (type == typeof(decimal)) + { + return false; + } + if (type.IsArray) + { + var elementType = type.GetElementType(); + return elementType != null && elementType.IsValueType && IsBlittable(elementType); + } + try + { + var instance = FormatterServices.GetUninitializedObject(type); + GCHandle.Alloc(instance, GCHandleType.Pinned).Free(); + return true; + } + catch + { + return false; + } + } + + private static class IsBlittableCache + { + public static readonly bool VALUE = IsBlittable(typeof(T)); + } + } \ No newline at end of file