initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# This file should only contain severity override to diagnostics, in order to make generated and
# interop code compilation readable. We want to limit the scope of suppression as much as possible.
[**/Generated/**.cs]
# IDE1006: Naming rule violation
dotnet_diagnostic.IDE1006.severity = none
# CA1062: Validate parameter is non-null before using it
# Useful for generated code, as it disables nullable
dotnet_diagnostic.CA1062.severity = error
# CA1069: Enums should not have duplicate values
dotnet_diagnostic.CA1069.severity = none
# CA1707: Identifiers should not contain underscores
dotnet_diagnostic.CA1707.severity = none
# CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1708.severity = none
# CA1711: Identifiers should not have incorrect suffix
# Disable warning for suffixes like EventHandler, Flags, Enum, etc.
dotnet_diagnostic.CA1711.severity = none
# CA1716: Identifiers should not match keywords
# This is suppressed, because it will report `@event` as well as `event`
dotnet_diagnostic.CA1716.severity = none
# CA1720: Identifiers should not contain type names
dotnet_diagnostic.CA1720.severity = none
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none
# CS1573: Parameter has no matching param tag in the XML comment
dotnet_diagnostic.CS1573.severity = none
# TODO: Temporary change to not pollute the warnings, but this denotes with ou doc generation
# CS1734: XML comment on '' has a paramref tag for '', but there is no parameter by that name
dotnet_diagnostic.CS1734.severity = none
[GodotSharp/Core/NativeInterop/**.cs]
# CA1720: Identifiers should not contain type names
dotnet_diagnostic.CA1720.severity = none
[GodotSharp/Core/**.cs]
# CS1591: Missing XML comment for publicly visible type or member
# TODO: Temporary change to not pollute the warnings, but we need to document public APIs
dotnet_diagnostic.CS1591.severity = suggestion

View File

@@ -0,0 +1,3 @@
# Generated Godot API sources directories
GodotSharp/Generated
GodotSharpEditor/Generated

View File

@@ -0,0 +1,5 @@
<assembly name="System.Runtime.InteropServices">
<member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
</assembly>

View File

@@ -0,0 +1,24 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators.Internal;
internal readonly struct CallbacksData
{
public CallbacksData(INamedTypeSymbol nativeTypeSymbol, INamedTypeSymbol funcStructSymbol)
{
NativeTypeSymbol = nativeTypeSymbol;
FuncStructSymbol = funcStructSymbol;
Methods = NativeTypeSymbol.GetMembers()
.Where(symbol => symbol is IMethodSymbol { IsPartialDefinition: true })
.Cast<IMethodSymbol>()
.ToImmutableArray();
}
public INamedTypeSymbol NativeTypeSymbol { get; }
public INamedTypeSymbol FuncStructSymbol { get; }
public ImmutableArray<IMethodSymbol> Methods { get; }
}

View File

@@ -0,0 +1,65 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Godot.SourceGenerators.Internal;
internal static class Common
{
public static void ReportNonPartialUnmanagedCallbacksClass(
GeneratorExecutionContext context,
ClassDeclarationSyntax cds, INamedTypeSymbol symbol
)
{
string message =
"Missing partial modifier on declaration of type '" +
$"{symbol.FullQualifiedNameOmitGlobal()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'";
string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " +
"must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0001",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
cds.GetLocation(),
cds.SyntaxTree.FilePath));
}
public static void ReportNonPartialUnmanagedCallbacksOuterClass(
GeneratorExecutionContext context,
TypeDeclarationSyntax outerTypeDeclSyntax
)
{
var outerSymbol = context.Compilation
.GetSemanticModel(outerTypeDeclSyntax.SyntaxTree)
.GetDeclaredSymbol(outerTypeDeclSyntax);
string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ?
namedTypeSymbol.FullQualifiedNameOmitGlobal() :
"type not found";
string message =
$"Missing partial modifier on declaration of type '{fullQualifiedName}', " +
$"which contains one or more subclasses with attribute " +
$"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'";
string description = $"{message}. Classes with attribute " +
$"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' and their " +
"containing types must be declared with the partial modifier.";
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0002",
title: message,
messageFormat: message,
category: "Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description),
outerTypeDeclSyntax.GetLocation(),
outerTypeDeclSyntax.SyntaxTree.FilePath));
}
}

View File

@@ -0,0 +1,124 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Godot.SourceGenerators.Internal;
internal static class ExtensionMethods
{
public static AttributeData? GetGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol)
=> symbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false);
private static bool HasGenerateUnmanagedCallbacksAttribute(
this ClassDeclarationSyntax cds, Compilation compilation,
out INamedTypeSymbol? symbol
)
{
var sm = compilation.GetSemanticModel(cds.SyntaxTree);
var classTypeSymbol = sm.GetDeclaredSymbol(cds);
if (classTypeSymbol == null)
{
symbol = null;
return false;
}
if (!classTypeSymbol.GetAttributes()
.Any(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false))
{
symbol = null;
return false;
}
symbol = classTypeSymbol;
return true;
}
private static bool IsGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol)
=> symbol.FullQualifiedNameOmitGlobal() == GeneratorClasses.GenerateUnmanagedCallbacksAttr;
public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectUnmanagedCallbacksClasses(
this IEnumerable<ClassDeclarationSyntax> source,
Compilation compilation
)
{
foreach (var cds in source)
{
if (cds.HasGenerateUnmanagedCallbacksAttribute(compilation, out var symbol))
yield return (cds, symbol!);
}
}
public static bool IsNested(this TypeDeclarationSyntax cds)
=> cds.Parent is TypeDeclarationSyntax;
public static bool IsPartial(this TypeDeclarationSyntax cds)
=> cds.Modifiers.Any(SyntaxKind.PartialKeyword);
public static bool AreAllOuterTypesPartial(
this TypeDeclarationSyntax cds,
out TypeDeclarationSyntax? typeMissingPartial
)
{
SyntaxNode? outerSyntaxNode = cds.Parent;
while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax)
{
if (!outerTypeDeclSyntax.IsPartial())
{
typeMissingPartial = outerTypeDeclSyntax;
return false;
}
outerSyntaxNode = outerSyntaxNode.Parent;
}
typeMissingPartial = null;
return true;
}
public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol)
{
string? keyword = namedTypeSymbol.DeclaringSyntaxReferences
.OfType<TypeDeclarationSyntax>().FirstOrDefault()?
.Keyword.Text;
return keyword ?? namedTypeSymbol.TypeKind switch
{
TypeKind.Interface => "interface",
TypeKind.Struct => "struct",
_ => "class"
};
}
private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } =
SymbolDisplayFormat.FullyQualifiedFormat
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included);
public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol)
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol)
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol)
=> symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal);
public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol)
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal);
public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName)
=> qualifiedName
// AddSource() doesn't support @ prefix
.Replace("@", "")
// AddSource() doesn't support angle brackets
.Replace("<", "(Of ")
.Replace(">", ")");
}

View File

@@ -0,0 +1,6 @@
namespace Godot.SourceGenerators.Internal;
internal static class GeneratorClasses
{
public const string GenerateUnmanagedCallbacksAttr = "Godot.SourceGenerators.Internal.GenerateUnmanagedCallbacksAttribute";
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,478 @@
using System.Text;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Godot.SourceGenerators.Internal;
[Generator]
public class UnmanagedCallbacksGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForPostInitialization(ctx => { GenerateAttribute(ctx); });
}
public void Execute(GeneratorExecutionContext context)
{
INamedTypeSymbol[] unmanagedCallbacksClasses = context
.Compilation.SyntaxTrees
.SelectMany(tree =>
tree.GetRoot().DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.SelectUnmanagedCallbacksClasses(context.Compilation)
// Report and skip non-partial classes
.Where(x =>
{
if (x.cds.IsPartial())
{
if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
{
Common.ReportNonPartialUnmanagedCallbacksOuterClass(context, typeMissingPartial!);
return false;
}
return true;
}
Common.ReportNonPartialUnmanagedCallbacksClass(context, x.cds, x.symbol);
return false;
})
.Select(x => x.symbol)
)
.Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
.ToArray();
foreach (var symbol in unmanagedCallbacksClasses)
{
var attr = symbol.GetGenerateUnmanagedCallbacksAttribute();
if (attr == null || attr.ConstructorArguments.Length != 1)
{
// TODO: Report error or throw exception, this is an invalid case and should never be reached
System.Diagnostics.Debug.Fail("FAILED!");
continue;
}
var funcStructType = (INamedTypeSymbol?)attr.ConstructorArguments[0].Value;
if (funcStructType == null)
{
// TODO: Report error or throw exception, this is an invalid case and should never be reached
System.Diagnostics.Debug.Fail("FAILED!");
continue;
}
var data = new CallbacksData(symbol, funcStructType);
GenerateInteropMethodImplementations(context, data);
GenerateUnmanagedCallbacksStruct(context, data);
}
}
private void GenerateAttribute(GeneratorPostInitializationContext context)
{
string source = @"using System;
namespace Godot.SourceGenerators.Internal
{
internal class GenerateUnmanagedCallbacksAttribute : Attribute
{
public Type FuncStructType { get; }
public GenerateUnmanagedCallbacksAttribute(Type funcStructType)
{
FuncStructType = funcStructType;
}
}
}";
context.AddSource("GenerateUnmanagedCallbacksAttribute.generated",
SourceText.From(source, Encoding.UTF8));
}
private void GenerateInteropMethodImplementations(GeneratorExecutionContext context, CallbacksData data)
{
var symbol = data.NativeTypeSymbol;
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedNameOmitGlobal() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
var source = new StringBuilder();
var methodSource = new StringBuilder();
var methodCallArguments = new StringBuilder();
var methodSourceAfterCall = new StringBuilder();
source.Append(
@"using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot.Bridge;
using Godot.NativeInterop;
#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores
");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
source.Append("\n{\n");
}
if (isInnerClass)
{
var containingType = symbol.ContainingType;
AppendPartialContainingTypeDeclarations(containingType);
void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
{
if (containingType == null)
return;
AppendPartialContainingTypeDeclarations(containingType.ContainingType);
source.Append("partial ");
source.Append(containingType.GetDeclarationKeyword());
source.Append(" ");
source.Append(containingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
source.Append("\n{\n");
}
}
source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");
source.Append($"unsafe partial class {symbol.Name}\n");
source.Append("{\n");
source.Append($" private static {data.FuncStructSymbol.FullQualifiedNameIncludeGlobal()} _unmanagedCallbacks;\n\n");
foreach (var callback in data.Methods)
{
methodSource.Clear();
methodCallArguments.Clear();
methodSourceAfterCall.Clear();
source.Append(" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]\n");
source.Append($" {SyntaxFacts.GetText(callback.DeclaredAccessibility)} ");
if (callback.IsStatic)
source.Append("static ");
source.Append("partial ");
source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal());
source.Append(' ');
source.Append(callback.Name);
source.Append('(');
for (int i = 0; i < callback.Parameters.Length; i++)
{
var parameter = callback.Parameters[i];
AppendRefKind(source, parameter.RefKind, parameter.ScopedKind);
source.Append(' ');
source.Append(parameter.Type.FullQualifiedNameIncludeGlobal());
source.Append(' ');
source.Append(parameter.Name);
if (parameter.RefKind == RefKind.Out)
{
// Only assign default if the parameter won't be passed by-ref or copied later.
if (IsGodotInteropStruct(parameter.Type))
methodSource.Append($" {parameter.Name} = default;\n");
}
if (IsByRefParameter(parameter))
{
if (IsGodotInteropStruct(parameter.Type))
{
methodSource.Append(" ");
AppendCustomUnsafeAsPointer(methodSource, parameter, out string varName);
methodCallArguments.Append(varName);
}
else if (parameter.Type.IsValueType)
{
methodSource.Append(" ");
AppendCopyToStackAndGetPointer(methodSource, parameter, out string varName);
methodCallArguments.Append($"&{varName}");
if (parameter.RefKind is RefKind.Out or RefKind.Ref)
{
methodSourceAfterCall.Append($" {parameter.Name} = {varName};\n");
}
}
else
{
// If it's a by-ref param and we can't get the pointer
// just pass it by-ref and let it be pinned.
AppendRefKind(methodCallArguments, parameter.RefKind, parameter.ScopedKind)
.Append(' ')
.Append(parameter.Name);
}
}
else
{
methodCallArguments.Append(parameter.Name);
}
if (i < callback.Parameters.Length - 1)
{
source.Append(", ");
methodCallArguments.Append(", ");
}
}
source.Append(")\n");
source.Append(" {\n");
source.Append(methodSource);
source.Append(" ");
if (!callback.ReturnsVoid)
{
if (methodSourceAfterCall.Length != 0)
source.Append($"{callback.ReturnType.FullQualifiedNameIncludeGlobal()} ret = ");
else
source.Append("return ");
}
source.Append($"_unmanagedCallbacks.{callback.Name}(");
source.Append(methodCallArguments);
source.Append(");\n");
if (methodSourceAfterCall.Length != 0)
{
source.Append(methodSourceAfterCall);
if (!callback.ReturnsVoid)
source.Append(" return ret;\n");
}
source.Append(" }\n\n");
}
source.Append("}\n");
if (isInnerClass)
{
var containingType = symbol.ContainingType;
while (containingType != null)
{
source.Append("}\n"); // outer class
containingType = containingType.ContainingType;
}
}
if (hasNamespace)
source.Append("\n}");
source.Append("\n\n#pragma warning restore CA1707\n");
context.AddSource($"{data.NativeTypeSymbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated",
SourceText.From(source.ToString(), Encoding.UTF8));
}
private void GenerateUnmanagedCallbacksStruct(GeneratorExecutionContext context, CallbacksData data)
{
var symbol = data.FuncStructSymbol;
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedNameOmitGlobal() :
string.Empty;
bool hasNamespace = classNs.Length != 0;
bool isInnerClass = symbol.ContainingType != null;
var source = new StringBuilder();
source.Append(
@"using System.Runtime.InteropServices;
using Godot.NativeInterop;
#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores
");
if (hasNamespace)
{
source.Append("namespace ");
source.Append(classNs);
source.Append("\n{\n");
}
if (isInnerClass)
{
var containingType = symbol.ContainingType;
AppendPartialContainingTypeDeclarations(containingType);
void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
{
if (containingType == null)
return;
AppendPartialContainingTypeDeclarations(containingType.ContainingType);
source.Append("partial ");
source.Append(containingType.GetDeclarationKeyword());
source.Append(" ");
source.Append(containingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
source.Append("\n{\n");
}
}
source.Append("[StructLayout(LayoutKind.Sequential)]\n");
source.Append($"unsafe partial struct {symbol.Name}\n{{\n");
foreach (var callback in data.Methods)
{
source.Append(" ");
source.Append(callback.DeclaredAccessibility == Accessibility.Public ? "public " : "internal ");
source.Append("delegate* unmanaged<");
foreach (var parameter in callback.Parameters)
{
if (IsByRefParameter(parameter))
{
if (IsGodotInteropStruct(parameter.Type) || parameter.Type.IsValueType)
{
AppendPointerType(source, parameter.Type);
}
else
{
// If it's a by-ref param and we can't get the pointer
// just pass it by-ref and let it be pinned.
AppendRefKind(source, parameter.RefKind, parameter.ScopedKind)
.Append(' ')
.Append(parameter.Type.FullQualifiedNameIncludeGlobal());
}
}
else
{
source.Append(parameter.Type.FullQualifiedNameIncludeGlobal());
}
source.Append(", ");
}
source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal());
source.Append($"> {callback.Name};\n");
}
source.Append("}\n");
if (isInnerClass)
{
var containingType = symbol.ContainingType;
while (containingType != null)
{
source.Append("}\n"); // outer class
containingType = containingType.ContainingType;
}
}
if (hasNamespace)
source.Append("}\n");
source.Append("\n#pragma warning restore CA1707\n");
context.AddSource($"{symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated",
SourceText.From(source.ToString(), Encoding.UTF8));
}
private static bool IsGodotInteropStruct(ITypeSymbol type) =>
_godotInteropStructs.Contains(type.FullQualifiedNameOmitGlobal());
private static bool IsByRefParameter(IParameterSymbol parameter) =>
parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref;
private static StringBuilder AppendRefKind(StringBuilder source, RefKind refKind, ScopedKind scopedKind)
{
return (refKind, scopedKind) switch
{
(RefKind.Out, _) => source.Append("out"),
(RefKind.In, ScopedKind.ScopedRef) => source.Append("scoped in"),
(RefKind.In, _) => source.Append("in"),
(RefKind.Ref, ScopedKind.ScopedRef) => source.Append("scoped ref"),
(RefKind.Ref, _) => source.Append("ref"),
_ => source,
};
}
private static void AppendPointerType(StringBuilder source, ITypeSymbol type)
{
source.Append(type.FullQualifiedNameIncludeGlobal());
source.Append('*');
}
private static void AppendCustomUnsafeAsPointer(StringBuilder source, IParameterSymbol parameter,
out string varName)
{
varName = $"{parameter.Name}_ptr";
AppendPointerType(source, parameter.Type);
source.Append(' ');
source.Append(varName);
source.Append(" = ");
source.Append('(');
AppendPointerType(source, parameter.Type);
source.Append(')');
if (parameter.RefKind == RefKind.In)
source.Append("CustomUnsafe.ReadOnlyRefAsPointer(in ");
else
source.Append("CustomUnsafe.AsPointer(ref ");
source.Append(parameter.Name);
source.Append(");\n");
}
private static void AppendCopyToStackAndGetPointer(StringBuilder source, IParameterSymbol parameter,
out string varName)
{
varName = $"{parameter.Name}_copy";
source.Append(parameter.Type.FullQualifiedNameIncludeGlobal());
source.Append(' ');
source.Append(varName);
if (parameter.RefKind is RefKind.In or RefKind.Ref)
{
source.Append(" = ");
source.Append(parameter.Name);
}
source.Append(";\n");
}
private static readonly string[] _godotInteropStructs =
{
"Godot.NativeInterop.godot_ref",
"Godot.NativeInterop.godot_variant_call_error",
"Godot.NativeInterop.godot_variant",
"Godot.NativeInterop.godot_string",
"Godot.NativeInterop.godot_string_name",
"Godot.NativeInterop.godot_node_path",
"Godot.NativeInterop.godot_signal",
"Godot.NativeInterop.godot_callable",
"Godot.NativeInterop.godot_array",
"Godot.NativeInterop.godot_dictionary",
"Godot.NativeInterop.godot_packed_byte_array",
"Godot.NativeInterop.godot_packed_int32_array",
"Godot.NativeInterop.godot_packed_int64_array",
"Godot.NativeInterop.godot_packed_float32_array",
"Godot.NativeInterop.godot_packed_float64_array",
"Godot.NativeInterop.godot_packed_string_array",
"Godot.NativeInterop.godot_packed_vector2_array",
"Godot.NativeInterop.godot_packed_vector3_array",
"Godot.NativeInterop.godot_packed_vector4_array",
"Godot.NativeInterop.godot_packed_color_array",
};
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- To generate the .runtimeconfig.json file-->
<EnableDynamicLoading>true</EnableDynamicLoading>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\GodotSharp\GodotSharp.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Godot.Bridge;
using Godot.NativeInterop;
namespace GodotPlugins
{
public static class Main
{
// IMPORTANT:
// Keeping strong references to the AssemblyLoadContext (our PluginLoadContext) prevents
// it from being unloaded. To avoid issues, we wrap the reference in this class, and mark
// all the methods that access it as non-inlineable. This way we prevent local references
// (either real or introduced by the JIT) to escape the scope of these methods due to
// inlining, which could keep the AssemblyLoadContext alive while trying to unload.
private sealed class PluginLoadContextWrapper
{
private PluginLoadContext? _pluginLoadContext;
private readonly WeakReference _weakReference;
private PluginLoadContextWrapper(PluginLoadContext pluginLoadContext, WeakReference weakReference)
{
_pluginLoadContext = pluginLoadContext;
_weakReference = weakReference;
}
public string? AssemblyLoadedPath
{
[MethodImpl(MethodImplOptions.NoInlining)]
get => _pluginLoadContext?.AssemblyLoadedPath;
}
public bool IsCollectible
{
[MethodImpl(MethodImplOptions.NoInlining)]
// if _pluginLoadContext is null we already started unloading, so it was collectible
get => _pluginLoadContext?.IsCollectible ?? true;
}
public bool IsAlive
{
[MethodImpl(MethodImplOptions.NoInlining)]
get => _weakReference.IsAlive;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName(
AssemblyName assemblyName,
string pluginPath,
ICollection<string> sharedAssemblies,
AssemblyLoadContext mainLoadContext,
bool isCollectible
)
{
var context = new PluginLoadContext(pluginPath, sharedAssemblies, mainLoadContext, isCollectible);
var reference = new WeakReference(context, trackResurrection: true);
var wrapper = new PluginLoadContextWrapper(context, reference);
var assembly = context.LoadFromAssemblyName(assemblyName);
return (assembly, wrapper);
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal void Unload()
{
_pluginLoadContext?.Unload();
_pluginLoadContext = null;
}
}
private static readonly List<AssemblyName> SharedAssemblies = new();
private static readonly Assembly CoreApiAssembly = typeof(global::Godot.GodotObject).Assembly;
private static Assembly? _editorApiAssembly;
private static PluginLoadContextWrapper? _projectLoadContext;
private static bool _editorHint = false;
private static readonly AssemblyLoadContext MainLoadContext =
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
AssemblyLoadContext.Default;
private static DllImportResolver? _dllImportResolver;
// Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later.
[UnmanagedCallersOnly]
// ReSharper disable once UnusedMember.Local
private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, godot_bool editorHint,
PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks,
IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
try
{
_editorHint = editorHint.ToBool();
_dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
SharedAssemblies.Add(CoreApiAssembly.GetName());
NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
AlcReloadCfg.Configure(alcReloadEnabled: _editorHint);
NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize);
if (_editorHint)
{
_editorApiAssembly = Assembly.Load("GodotSharpEditor");
SharedAssemblies.Add(_editorApiAssembly.GetName());
NativeLibrary.SetDllImportResolver(_editorApiAssembly, _dllImportResolver);
}
*pluginsCallbacks = new()
{
LoadProjectAssemblyCallback = &LoadProjectAssembly,
LoadToolsAssemblyCallback = &LoadToolsAssembly,
UnloadProjectPluginCallback = &UnloadProjectPlugin,
};
*managedCallbacks = ManagedCallbacks.Create();
return godot_bool.True;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
return godot_bool.False;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct PluginsCallbacks
{
public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback;
public unsafe delegate* unmanaged<char*, IntPtr, int, IntPtr> LoadToolsAssemblyCallback;
public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback;
}
[UnmanagedCallersOnly]
private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath)
{
try
{
if (_projectLoadContext != null)
return godot_bool.True; // Already loaded
string assemblyPath = new(nAssemblyPath);
(var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath, isCollectible: _editorHint);
string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath;
*outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath);
ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly);
return godot_bool.True;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
private static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath,
IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
try
{
string assemblyPath = new(nAssemblyPath);
if (_editorApiAssembly == null)
throw new InvalidOperationException("The Godot editor API assembly is not loaded.");
var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: false);
NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
var method = assembly.GetType("GodotTools.GodotSharpEditor")?
.GetMethod("InternalCreateInstance",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (method == null)
{
throw new MissingMethodException("GodotTools.GodotSharpEditor",
"InternalCreateInstance");
}
return (IntPtr?)method
.Invoke(null, new object[] { unmanagedCallbacks, unmanagedCallbacksSize })
?? IntPtr.Zero;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
return IntPtr.Zero;
}
}
private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath, bool isCollectible)
{
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
var sharedAssemblies = new List<string>();
foreach (var sharedAssembly in SharedAssemblies)
{
string? sharedAssemblyName = sharedAssembly.Name;
if (sharedAssemblyName != null)
sharedAssemblies.Add(sharedAssemblyName);
}
return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName(
new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext, isCollectible);
}
[UnmanagedCallersOnly]
private static godot_bool UnloadProjectPlugin()
{
try
{
return UnloadPlugin(ref _projectLoadContext).ToGodotBool();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
return godot_bool.False;
}
}
private static bool UnloadPlugin(ref PluginLoadContextWrapper? pluginLoadContext)
{
try
{
if (pluginLoadContext == null)
return true;
if (!pluginLoadContext.IsCollectible)
{
Console.Error.WriteLine("Cannot unload a non-collectible assembly load context.");
return false;
}
Console.WriteLine("Unloading assembly load context...");
pluginLoadContext.Unload();
int startTimeMs = Environment.TickCount;
bool takingTooLong = false;
while (pluginLoadContext.IsAlive)
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
if (!pluginLoadContext.IsAlive)
break;
int elapsedTimeMs = Environment.TickCount - startTimeMs;
if (!takingTooLong && elapsedTimeMs >= 200)
{
takingTooLong = true;
// TODO: How to log from GodotPlugins? (delegate pointer?)
Console.Error.WriteLine("Assembly unloading is taking longer than expected...");
}
else if (elapsedTimeMs >= 1000)
{
// TODO: How to log from GodotPlugins? (delegate pointer?)
Console.Error.WriteLine(
"Failed to unload assemblies. Possible causes: Strong GC handles, running threads, etc.");
return false;
}
}
Console.WriteLine("Assembly load context unloaded successfully.");
pluginLoadContext = null;
return true;
}
catch (Exception e)
{
// TODO: How to log exceptions from GodotPlugins? (delegate pointer?)
Console.Error.WriteLine(e);
return false;
}
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
namespace GodotPlugins
{
public class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
private readonly ICollection<string> _sharedAssemblies;
private readonly AssemblyLoadContext _mainLoadContext;
public string? AssemblyLoadedPath { get; private set; }
public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
AssemblyLoadContext mainLoadContext, bool isCollectible)
: base(isCollectible)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
_sharedAssemblies = sharedAssemblies;
_mainLoadContext = mainLoadContext;
if (string.IsNullOrEmpty(AppContext.BaseDirectory))
{
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs#L17-L35
// but Assembly.Location is unavailable, because we load assemblies from memory.
string? baseDirectory = Path.GetDirectoryName(pluginPath);
if (baseDirectory != null)
{
if (!Path.EndsInDirectorySeparator(baseDirectory))
baseDirectory += Path.DirectorySeparatorChar;
// This SetData call effectively sets AppContext.BaseDirectory
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25
AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory);
}
else
{
// TODO: How to log from GodotPlugins? (delegate pointer?)
Console.Error.WriteLine("Failed to set AppContext.BaseDirectory. Dynamic loading of libraries may fail.");
}
}
}
protected override Assembly? Load(AssemblyName assemblyName)
{
if (assemblyName.Name == null)
return null;
if (_sharedAssemblies.Contains(assemblyName.Name))
return _mainLoadContext.LoadFromAssemblyName(assemblyName);
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
AssemblyLoadedPath = assemblyPath;
// Load in memory to prevent locking the file
using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
if (File.Exists(pdbPath))
{
using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read);
return LoadFromStream(assemblyFile, pdbFile);
}
return LoadFromStream(assemblyFile);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
return LoadUnmanagedDllFromPath(libraryPath);
return IntPtr.Zero;
}
}
}

View File

@@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpEditor", "GodotSharpEditor\GodotSharpEditor.csproj", "{8FBEC238-D944-4074-8548-B3B524305905}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotPlugins", "GodotPlugins\GodotPlugins.csproj", "{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{7749662B-E30C-419A-B745-13852573360A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU
{8FBEC238-D944-4074-8548-B3B524305905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FBEC238-D944-4074-8548-B3B524305905}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.Build.0 = Release|Any CPU
{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.Build.0 = Release|Any CPU
{7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {371B0F03-042D-45FD-A270-F3141F2480CD}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=alcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gdextension/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=icall/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=quat/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=vcall/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -0,0 +1,248 @@
// This file contains methods that existed in a previous version of Godot in ClassDB but were removed
// or their method signature has changed so they are no longer generated by bindings_generator.
// These methods are provided to avoid breaking binary compatibility.
using System;
using System.ComponentModel;
namespace Godot;
#pragma warning disable CS1734 // XML comment on 'X' has a paramref tag for 'Y', but there is no parameter by that name.
// TODO: This is currently disabled because of https://github.com/dotnet/roslyn/issues/52904
#pragma warning disable IDE0040 // Add accessibility modifiers.
partial class AnimationNode
{
/// <inheritdoc cref="BlendInput(int, double, bool, bool, float, FilterAction, bool, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public double BlendInput(int inputIndex, double time, bool seek, bool isExternalSeeking, float blend, FilterAction filter, bool sync)
{
return BlendInput(inputIndex, time, seek, isExternalSeeking, blend, filter, sync, testOnly: false);
}
/// <inheritdoc cref="BlendNode(StringName, AnimationNode, double, bool, bool, float, FilterAction, bool, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public double BlendNode(StringName name, AnimationNode node, double time, bool seek, bool isExternalSeeking, float blend, FilterAction filter, bool sync)
{
return BlendNode(name, node, time, seek, isExternalSeeking, blend, filter, sync, testOnly: false);
}
}
partial class AnimationPlayer
{
/// <inheritdoc cref="AnimationMixer.CallbackModeMethod"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationMethodCallMode MethodCallMode
{
get => (AnimationMethodCallMode)CallbackModeMethod;
set => CallbackModeMethod = (AnimationCallbackModeMethod)value;
}
/// <inheritdoc cref="AnimationMixer.Active"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool PlaybackActive
{
get => Active;
set => Active = value;
}
/// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationProcessCallback PlaybackProcessMode
{
get => (AnimationProcessCallback)CallbackModeProcess;
set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
}
}
partial class AnimationTree
{
/// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationProcessCallback ProcessCallback
{
get => (AnimationProcessCallback)CallbackModeProcess;
set => CallbackModeProcess = (AnimationCallbackModeProcess)value;
}
}
partial class CodeEdit
{
/// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Variant, int)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void AddCodeCompletionOption(CodeCompletionKind type, string displayText, string insertText, Nullable<Color> textColor, Resource icon, Nullable<Variant> value)
{
AddCodeCompletionOption(type, displayText, insertText, textColor, icon, value, location: 1024);
}
}
partial class Geometry3D
{
/// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Godot.Collections.Array{Plane})"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static Vector3[] SegmentIntersectsConvex(Vector3 from, Vector3 to, Godot.Collections.Array planes)
{
return SegmentIntersectsConvex(from, to, new Godot.Collections.Array<Plane>(planes));
}
}
partial class GraphEdit
{
/// <inheritdoc cref="ShowArrangeButton"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool ArrangeNodesButtonHidden
{
get => !ShowArrangeButton;
set => ShowArrangeButton = !value;
}
/// <inheritdoc cref="GetMenuHBox()"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public HBoxContainer GetZoomHBox()
{
return GetMenuHBox();
}
/// <inheritdoc cref="SnappingDistance"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public int SnapDistance
{
get => SnappingDistance;
set => SnappingDistance = value;
}
/// <inheritdoc cref="SnappingEnabled"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool UseSnap
{
get => SnappingEnabled;
set => SnappingEnabled = value;
}
}
partial class GraphNode
{
/// <inheritdoc cref="GraphElement.DeleteRequest"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public event Action CloseRequest
{
add => DeleteRequest += value;
remove => DeleteRequest -= value;
}
}
partial class ImporterMesh
{
/// <inheritdoc cref="AddSurface(Mesh.PrimitiveType, Godot.Collections.Array, Godot.Collections.Array{Godot.Collections.Array}, Godot.Collections.Dictionary, Material, string, ulong)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void AddSurface(Mesh.PrimitiveType primitive, Godot.Collections.Array arrays, Godot.Collections.Array<Godot.Collections.Array> blendShapes, Godot.Collections.Dictionary lods, Material material, string name, uint flags)
{
AddSurface(primitive, arrays, blendShapes, lods, material, name, (ulong)flags);
}
}
partial class MeshInstance3D
{
/// <inheritdoc cref="CreateMultipleConvexCollisions(MeshConvexDecompositionSettings)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void CreateMultipleConvexCollisions()
{
CreateMultipleConvexCollisions(settings: null);
}
}
partial class Node3D
{
/// <inheritdoc cref="LookAt(Vector3, Nullable{Vector3}, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void LookAt(Vector3 target, Nullable<Vector3> up)
{
LookAt(target, up, useModelFront: false);
}
/// <inheritdoc cref="LookAtFromPosition(Vector3, Vector3, Nullable{Vector3}, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void LookAtFromPosition(Vector3 position, Vector3 target, Nullable<Vector3> up)
{
LookAtFromPosition(position, target, up, useModelFront: false);
}
}
partial class RenderingDevice
{
/// <inheritdoc cref="DrawListBegin(Rid, InitialAction, FinalAction, InitialAction, FinalAction, Color[], float, uint, Nullable{Rect2}, Godot.Collections.Array{Rid})"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public long DrawListBegin(Rid framebuffer, InitialAction initialColorAction, FinalAction finalColorAction, InitialAction initialDepthAction, FinalAction finalDepthAction, Color[] clearColorValues, float clearDepth, uint clearStencil, Nullable<Rect2> region, Godot.Collections.Array storageTextures)
{
return DrawListBegin(framebuffer, initialColorAction, finalColorAction, initialDepthAction, finalDepthAction, clearColorValues, clearDepth, clearStencil, region, new Godot.Collections.Array<Rid>(storageTextures));
}
}
partial class RichTextLabel
{
/// <inheritdoc cref="PushList(int, ListType, bool, string)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void PushList(int level, ListType type, bool capitalize)
{
PushList(level, type, capitalize, bullet: "•");
}
/// <inheritdoc cref="PushParagraph(HorizontalAlignment, TextDirection, string, TextServer.StructuredTextParser, TextServer.JustificationFlag, float[])"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void PushParagraph(HorizontalAlignment alignment, TextDirection baseDirection, string language, TextServer.StructuredTextParser stParser)
{
PushParagraph(alignment, baseDirection, language, stParser, TextServer.JustificationFlag.WordBound | TextServer.JustificationFlag.Kashida | TextServer.JustificationFlag.SkipLastLine | TextServer.JustificationFlag.DoNotSkipSingleLine);
}
}
partial class SurfaceTool
{
/// <inheritdoc cref="AddTriangleFan(Vector3[], Vector2[], Color[], Vector2[], Vector3[], Godot.Collections.Array{Plane})"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void AddTriangleFan(Vector3[] vertices, Vector2[] uvs, Color[] colors, Vector2[] uv2S, Vector3[] normals, Godot.Collections.Array tangents)
{
AddTriangleFan(vertices, uvs, colors, uv2S, normals, new Godot.Collections.Array<Plane>(tangents));
}
/// <inheritdoc cref="Commit(ArrayMesh, ulong)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public ArrayMesh Commit(ArrayMesh existing, uint flags)
{
return Commit(existing, (ulong)flags);
}
}
partial class TileMap
{
/// <summary>
/// The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public int CellQuadrantSize
{
get => RenderingQuadrantSize;
set => RenderingQuadrantSize = value;
}
}
partial class Tree
{
/// <inheritdoc cref="EditSelected(bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool EditSelected()
{
return EditSelected(forceEdit: false);
}
}
partial class UndoRedo
{
/// <inheritdoc cref="CreateAction(string, MergeMode, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public void CreateAction(string name, MergeMode mergeMode)
{
CreateAction(name, mergeMode, backwardUndoOps: false);
}
}
#pragma warning restore CS1734

View File

@@ -0,0 +1,754 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// Axis-Aligned Bounding Box. AABB consists of a position, a size, and
/// several utility functions. It is typically used for fast overlap tests.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Aabb : IEquatable<Aabb>
{
private Vector3 _position;
private Vector3 _size;
/// <summary>
/// Beginning corner. Typically has values lower than <see cref="End"/>.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector3 Position
{
readonly get { return _position; }
set { _position = value; }
}
/// <summary>
/// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive.
/// If the size is negative, you can use <see cref="Abs"/> to fix it.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector3 Size
{
readonly get { return _size; }
set { _size = value; }
}
/// <summary>
/// Ending corner. This is calculated as <see cref="Position"/> plus
/// <see cref="Size"/>. Setting this value will change the size.
/// </summary>
/// <value>
/// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>,
/// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/>
/// </value>
public Vector3 End
{
readonly get { return _position + _size; }
set { _size = value - _position; }
}
/// <summary>
/// The volume of this <see cref="Aabb"/>.
/// See also <see cref="HasVolume"/>.
/// </summary>
public readonly real_t Volume
{
get { return _size.X * _size.Y * _size.Z; }
}
/// <summary>
/// Returns an <see cref="Aabb"/> with equivalent position and size, modified so that
/// the most-negative corner is the origin and the size is positive.
/// </summary>
/// <returns>The modified <see cref="Aabb"/>.</returns>
public readonly Aabb Abs()
{
Vector3 end = End;
Vector3 topLeft = end.Min(_position);
return new Aabb(topLeft, _size.Abs());
}
/// <summary>
/// Returns the center of the <see cref="Aabb"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// </summary>
/// <returns>The center.</returns>
public readonly Vector3 GetCenter()
{
return _position + (_size * 0.5f);
}
/// <summary>
/// Returns <see langword="true"/> if this <see cref="Aabb"/> completely encloses another one.
/// </summary>
/// <param name="with">The other <see cref="Aabb"/> that may be enclosed.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not this <see cref="Aabb"/> encloses <paramref name="with"/>.
/// </returns>
public readonly bool Encloses(Aabb with)
{
Vector3 srcMin = _position;
Vector3 srcMax = _position + _size;
Vector3 dstMin = with._position;
Vector3 dstMax = with._position + with._size;
return srcMin.X <= dstMin.X &&
srcMax.X >= dstMax.X &&
srcMin.Y <= dstMin.Y &&
srcMax.Y >= dstMax.Y &&
srcMin.Z <= dstMin.Z &&
srcMax.Z >= dstMax.Z;
}
/// <summary>
/// Returns this <see cref="Aabb"/> expanded to include a given point.
/// </summary>
/// <param name="point">The point to include.</param>
/// <returns>The expanded <see cref="Aabb"/>.</returns>
public readonly Aabb Expand(Vector3 point)
{
Vector3 begin = _position;
Vector3 end = _position + _size;
if (point.X < begin.X)
{
begin.X = point.X;
}
if (point.Y < begin.Y)
{
begin.Y = point.Y;
}
if (point.Z < begin.Z)
{
begin.Z = point.Z;
}
if (point.X > end.X)
{
end.X = point.X;
}
if (point.Y > end.Y)
{
end.Y = point.Y;
}
if (point.Z > end.Z)
{
end.Z = point.Z;
}
return new Aabb(begin, end - begin);
}
/// <summary>
/// Gets the position of one of the 8 endpoints of the <see cref="Aabb"/>.
/// </summary>
/// <param name="idx">Which endpoint to get.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="idx"/> is less than 0 or greater than 7.
/// </exception>
/// <returns>An endpoint of the <see cref="Aabb"/>.</returns>
public readonly Vector3 GetEndpoint(int idx)
{
switch (idx)
{
case 0:
return new Vector3(_position.X, _position.Y, _position.Z);
case 1:
return new Vector3(_position.X, _position.Y, _position.Z + _size.Z);
case 2:
return new Vector3(_position.X, _position.Y + _size.Y, _position.Z);
case 3:
return new Vector3(_position.X, _position.Y + _size.Y, _position.Z + _size.Z);
case 4:
return new Vector3(_position.X + _size.X, _position.Y, _position.Z);
case 5:
return new Vector3(_position.X + _size.X, _position.Y, _position.Z + _size.Z);
case 6:
return new Vector3(_position.X + _size.X, _position.Y + _size.Y, _position.Z);
case 7:
return new Vector3(_position.X + _size.X, _position.Y + _size.Y, _position.Z + _size.Z);
default:
{
throw new ArgumentOutOfRangeException(nameof(idx),
$"Index is {idx}, but a value from 0 to 7 is expected.");
}
}
}
/// <summary>
/// Returns the normalized longest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>A vector representing the normalized longest axis of the <see cref="Aabb"/>.</returns>
public readonly Vector3 GetLongestAxis()
{
var axis = new Vector3(1f, 0f, 0f);
real_t maxSize = _size.X;
if (_size.Y > maxSize)
{
axis = new Vector3(0f, 1f, 0f);
maxSize = _size.Y;
}
if (_size.Z > maxSize)
{
axis = new Vector3(0f, 0f, 1f);
}
return axis;
}
/// <summary>
/// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns>
public readonly Vector3.Axis GetLongestAxisIndex()
{
var axis = Vector3.Axis.X;
real_t maxSize = _size.X;
if (_size.Y > maxSize)
{
axis = Vector3.Axis.Y;
maxSize = _size.Y;
}
if (_size.Z > maxSize)
{
axis = Vector3.Axis.Z;
}
return axis;
}
/// <summary>
/// Returns the scalar length of the longest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>The scalar length of the longest axis of the <see cref="Aabb"/>.</returns>
public readonly real_t GetLongestAxisSize()
{
real_t maxSize = _size.X;
if (_size.Y > maxSize)
maxSize = _size.Y;
if (_size.Z > maxSize)
maxSize = _size.Z;
return maxSize;
}
/// <summary>
/// Returns the normalized shortest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>A vector representing the normalized shortest axis of the <see cref="Aabb"/>.</returns>
public readonly Vector3 GetShortestAxis()
{
var axis = new Vector3(1f, 0f, 0f);
real_t maxSize = _size.X;
if (_size.Y < maxSize)
{
axis = new Vector3(0f, 1f, 0f);
maxSize = _size.Y;
}
if (_size.Z < maxSize)
{
axis = new Vector3(0f, 0f, 1f);
}
return axis;
}
/// <summary>
/// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns>
public readonly Vector3.Axis GetShortestAxisIndex()
{
var axis = Vector3.Axis.X;
real_t maxSize = _size.X;
if (_size.Y < maxSize)
{
axis = Vector3.Axis.Y;
maxSize = _size.Y;
}
if (_size.Z < maxSize)
{
axis = Vector3.Axis.Z;
}
return axis;
}
/// <summary>
/// Returns the scalar length of the shortest axis of the <see cref="Aabb"/>.
/// </summary>
/// <returns>The scalar length of the shortest axis of the <see cref="Aabb"/>.</returns>
public readonly real_t GetShortestAxisSize()
{
real_t maxSize = _size.X;
if (_size.Y < maxSize)
maxSize = _size.Y;
if (_size.Z < maxSize)
maxSize = _size.Z;
return maxSize;
}
/// <summary>
/// Returns the support point in a given direction.
/// This is useful for collision detection algorithms.
/// </summary>
/// <param name="dir">The direction to find support for.</param>
/// <returns>A vector representing the support.</returns>
public readonly Vector3 GetSupport(Vector3 dir)
{
Vector3 support = _position;
if (dir.X > 0.0f)
{
support.X += _size.X;
}
if (dir.Y > 0.0f)
{
support.Y += _size.Y;
}
if (dir.Z > 0.0f)
{
support.Z += _size.Z;
}
return support;
}
/// <summary>
/// Returns a copy of the <see cref="Aabb"/> grown a given amount of units towards all the sides.
/// </summary>
/// <param name="by">The amount to grow by.</param>
/// <returns>The grown <see cref="Aabb"/>.</returns>
public readonly Aabb Grow(real_t by)
{
Aabb res = this;
res._position.X -= by;
res._position.Y -= by;
res._position.Z -= by;
res._size.X += 2.0f * by;
res._size.Y += 2.0f * by;
res._size.Z += 2.0f * by;
return res;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/> contains a point,
/// or <see langword="false"/> otherwise.
/// </summary>
/// <param name="point">The point to check.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> contains <paramref name="point"/>.
/// </returns>
public readonly bool HasPoint(Vector3 point)
{
if (point.X < _position.X)
return false;
if (point.Y < _position.Y)
return false;
if (point.Z < _position.Z)
return false;
if (point.X > _position.X + _size.X)
return false;
if (point.Y > _position.Y + _size.Y)
return false;
if (point.Z > _position.Z + _size.Z)
return false;
return true;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/>
/// has a surface or a length, and <see langword="false"/>
/// if the <see cref="Aabb"/> is empty (all components
/// of <see cref="Size"/> are zero or negative).
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> has surface.
/// </returns>
public readonly bool HasSurface()
{
return _size.X > 0.0f || _size.Y > 0.0f || _size.Z > 0.0f;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/> has
/// area, and <see langword="false"/> if the <see cref="Aabb"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
/// See also <see cref="Volume"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> has volume.
/// </returns>
public readonly bool HasVolume()
{
return _size.X > 0.0f && _size.Y > 0.0f && _size.Z > 0.0f;
}
/// <summary>
/// Returns the intersection of this <see cref="Aabb"/> and <paramref name="with"/>.
/// </summary>
/// <param name="with">The other <see cref="Aabb"/>.</param>
/// <returns>The clipped <see cref="Aabb"/>.</returns>
public readonly Aabb Intersection(Aabb with)
{
Vector3 srcMin = _position;
Vector3 srcMax = _position + _size;
Vector3 dstMin = with._position;
Vector3 dstMax = with._position + with._size;
Vector3 min, max;
if (srcMin.X > dstMax.X || srcMax.X < dstMin.X)
{
return new Aabb();
}
min.X = srcMin.X > dstMin.X ? srcMin.X : dstMin.X;
max.X = srcMax.X < dstMax.X ? srcMax.X : dstMax.X;
if (srcMin.Y > dstMax.Y || srcMax.Y < dstMin.Y)
{
return new Aabb();
}
min.Y = srcMin.Y > dstMin.Y ? srcMin.Y : dstMin.Y;
max.Y = srcMax.Y < dstMax.Y ? srcMax.Y : dstMax.Y;
if (srcMin.Z > dstMax.Z || srcMax.Z < dstMin.Z)
{
return new Aabb();
}
min.Z = srcMin.Z > dstMin.Z ? srcMin.Z : dstMin.Z;
max.Z = srcMax.Z < dstMax.Z ? srcMax.Z : dstMax.Z;
return new Aabb(min, max - min);
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/> overlaps with <paramref name="with"/>
/// (i.e. they have at least one point in common).
/// </summary>
/// <param name="with">The other <see cref="Aabb"/> to check for intersections with.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not they are intersecting.
/// </returns>
public readonly bool Intersects(Aabb with)
{
if (_position.X >= with._position.X + with._size.X)
return false;
if (_position.X + _size.X <= with._position.X)
return false;
if (_position.Y >= with._position.Y + with._size.Y)
return false;
if (_position.Y + _size.Y <= with._position.Y)
return false;
if (_position.Z >= with._position.Z + with._size.Z)
return false;
if (_position.Z + _size.Z <= with._position.Z)
return false;
return true;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/> is on both sides of <paramref name="plane"/>.
/// </summary>
/// <param name="plane">The <see cref="Plane"/> to check for intersection.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> intersects the <see cref="Plane"/>.
/// </returns>
public readonly bool IntersectsPlane(Plane plane)
{
ReadOnlySpan<Vector3> points =
[
new Vector3(_position.X, _position.Y, _position.Z),
new Vector3(_position.X, _position.Y, _position.Z + _size.Z),
new Vector3(_position.X, _position.Y + _size.Y, _position.Z),
new Vector3(_position.X, _position.Y + _size.Y, _position.Z + _size.Z),
new Vector3(_position.X + _size.X, _position.Y, _position.Z),
new Vector3(_position.X + _size.X, _position.Y, _position.Z + _size.Z),
new Vector3(_position.X + _size.X, _position.Y + _size.Y, _position.Z),
new Vector3(_position.X + _size.X, _position.Y + _size.Y, _position.Z + _size.Z)
];
bool over = false;
bool under = false;
for (int i = 0; i < 8; i++)
{
if (plane.DistanceTo(points[i]) > 0)
{
over = true;
}
else
{
under = true;
}
}
return under && over;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Aabb"/> intersects
/// the line segment between <paramref name="from"/> and <paramref name="to"/>.
/// </summary>
/// <param name="from">The start of the line segment.</param>
/// <param name="to">The end of the line segment.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> intersects the line segment.
/// </returns>
public readonly bool IntersectsSegment(Vector3 from, Vector3 to)
{
real_t min = 0f;
real_t max = 1f;
for (int i = 0; i < 3; i++)
{
real_t segFrom = from[i];
real_t segTo = to[i];
real_t boxBegin = _position[i];
real_t boxEnd = boxBegin + _size[i];
real_t cmin, cmax;
if (segFrom < segTo)
{
if (segFrom > boxEnd || segTo < boxBegin)
{
return false;
}
real_t length = segTo - segFrom;
cmin = segFrom < boxBegin ? (boxBegin - segFrom) / length : 0f;
cmax = segTo > boxEnd ? (boxEnd - segFrom) / length : 1f;
}
else
{
if (segTo > boxEnd || segFrom < boxBegin)
{
return false;
}
real_t length = segTo - segFrom;
cmin = segFrom > boxEnd ? (boxEnd - segFrom) / length : 0f;
cmax = segTo < boxBegin ? (boxBegin - segFrom) / length : 1f;
}
if (cmin > min)
{
min = cmin;
}
if (cmax < max)
{
max = cmax;
}
if (max < min)
{
return false;
}
}
return true;
}
/// <summary>
/// Returns <see langword="true"/> if this <see cref="Aabb"/> is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return _position.IsFinite() && _size.IsFinite();
}
/// <summary>
/// Returns a larger <see cref="Aabb"/> that contains this <see cref="Aabb"/> and <paramref name="with"/>.
/// </summary>
/// <param name="with">The other <see cref="Aabb"/>.</param>
/// <returns>The merged <see cref="Aabb"/>.</returns>
public readonly Aabb Merge(Aabb with)
{
Vector3 beg1 = _position;
Vector3 beg2 = with._position;
var end1 = new Vector3(_size.X, _size.Y, _size.Z) + beg1;
var end2 = new Vector3(with._size.X, with._size.Y, with._size.Z) + beg2;
var min = new Vector3(
beg1.X < beg2.X ? beg1.X : beg2.X,
beg1.Y < beg2.Y ? beg1.Y : beg2.Y,
beg1.Z < beg2.Z ? beg1.Z : beg2.Z
);
var max = new Vector3(
end1.X > end2.X ? end1.X : end2.X,
end1.Y > end2.Y ? end1.Y : end2.Y,
end1.Z > end2.Z ? end1.Z : end2.Z
);
return new Aabb(min, max - min);
}
/// <summary>
/// Constructs an <see cref="Aabb"/> from a position and size.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="size">The size, typically positive.</param>
public Aabb(Vector3 position, Vector3 size)
{
_position = position;
_size = size;
}
/// <summary>
/// Constructs an <see cref="Aabb"/> from a <paramref name="position"/>,
/// <paramref name="width"/>, <paramref name="height"/>, and <paramref name="depth"/>.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="width">The width, typically positive.</param>
/// <param name="height">The height, typically positive.</param>
/// <param name="depth">The depth, typically positive.</param>
public Aabb(Vector3 position, real_t width, real_t height, real_t depth)
{
_position = position;
_size = new Vector3(width, height, depth);
}
/// <summary>
/// Constructs an <see cref="Aabb"/> from <paramref name="x"/>,
/// <paramref name="y"/>, <paramref name="z"/>, and <paramref name="size"/>.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="z">The position's Z coordinate.</param>
/// <param name="size">The size, typically positive.</param>
public Aabb(real_t x, real_t y, real_t z, Vector3 size)
{
_position = new Vector3(x, y, z);
_size = size;
}
/// <summary>
/// Constructs an <see cref="Aabb"/> from <paramref name="x"/>,
/// <paramref name="y"/>, <paramref name="z"/>, <paramref name="width"/>,
/// <paramref name="height"/>, and <paramref name="depth"/>.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="z">The position's Z coordinate.</param>
/// <param name="width">The width, typically positive.</param>
/// <param name="height">The height, typically positive.</param>
/// <param name="depth">The depth, typically positive.</param>
public Aabb(real_t x, real_t y, real_t z, real_t width, real_t height, real_t depth)
{
_position = new Vector3(x, y, z);
_size = new Vector3(width, height, depth);
}
/// <summary>
/// Returns <see langword="true"/> if the AABBs are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left AABB.</param>
/// <param name="right">The right AABB.</param>
/// <returns>Whether or not the AABBs are exactly equal.</returns>
public static bool operator ==(Aabb left, Aabb right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the AABBs are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left AABB.</param>
/// <param name="right">The right AABB.</param>
/// <returns>Whether or not the AABBs are not equal.</returns>
public static bool operator !=(Aabb left, Aabb right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the AABB is exactly equal
/// to the given object (<paramref name="obj"/>).
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the AABB and the object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Aabb other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the AABBs are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="other">The other AABB.</param>
/// <returns>Whether or not the AABBs are exactly equal.</returns>
public readonly bool Equals(Aabb other)
{
return _position == other._position && _size == other._size;
}
/// <summary>
/// Returns <see langword="true"/> if this AABB and <paramref name="other"/> are approximately equal,
/// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component.
/// </summary>
/// <param name="other">The other AABB to compare.</param>
/// <returns>Whether or not the AABBs structures are approximately equal.</returns>
public readonly bool IsEqualApprox(Aabb other)
{
return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size);
}
/// <summary>
/// Serves as the hash function for <see cref="Aabb"/>.
/// </summary>
/// <returns>A hash code for this AABB.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_position, _size);
}
/// <summary>
/// Converts this <see cref="Aabb"/> to a string.
/// </summary>
/// <returns>A string representation of this AABB.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Aabb"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this AABB.</returns>
public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
using System;
using System.Diagnostics.CodeAnalysis;
#nullable enable
namespace Godot
{
/// <summary>
/// Attribute that determines that the assembly contains Godot scripts and, optionally, the
/// collection of types that implement scripts; otherwise, retrieving the types requires lookup.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class AssemblyHasScriptsAttribute : Attribute
{
/// <summary>
/// If the Godot scripts contained in the assembly require lookup
/// and can't rely on <see cref="ScriptTypes"/>.
/// </summary>
[MemberNotNullWhen(false, nameof(ScriptTypes))]
public bool RequiresLookup { get; }
/// <summary>
/// The collection of types that implement a Godot script.
/// </summary>
public Type[]? ScriptTypes { get; }
/// <summary>
/// Constructs a new AssemblyHasScriptsAttribute instance
/// that requires lookup to get the Godot scripts.
/// </summary>
public AssemblyHasScriptsAttribute()
{
RequiresLookup = true;
ScriptTypes = null;
}
/// <summary>
/// Constructs a new AssemblyHasScriptsAttribute instance
/// that includes the Godot script types and requires no lookup.
/// </summary>
/// <param name="scriptTypes">The collection of types that implement a Godot script.</param>
public AssemblyHasScriptsAttribute(Type[] scriptTypes)
{
RequiresLookup = false;
ScriptTypes = scriptTypes;
}
}
}
#nullable restore

View File

@@ -0,0 +1,32 @@
using System;
namespace Godot
{
/// <summary>
/// Exports the annotated member as a property of the Godot Object.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ExportAttribute : Attribute
{
/// <summary>
/// Optional hint that determines how the property should be handled by the editor.
/// </summary>
public PropertyHint Hint { get; }
/// <summary>
/// Optional string that can contain additional metadata for the <see cref="Hint"/>.
/// </summary>
public string HintString { get; }
/// <summary>
/// Constructs a new ExportAttribute Instance.
/// </summary>
/// <param name="hint">The hint for the exported property.</param>
/// <param name="hintString">A string that may contain additional metadata for the hint.</param>
public ExportAttribute(PropertyHint hint = PropertyHint.None, string hintString = "")
{
Hint = hint;
HintString = hintString;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Godot
{
/// <summary>
/// Define a new category for the following exported properties. This helps to organize properties in the Inspector dock.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ExportCategoryAttribute : Attribute
{
/// <summary>
/// Name of the category.
/// </summary>
public string Name { get; }
/// <summary>
/// Define a new category for the following exported properties.
/// </summary>
/// <param name="name">The name of the category.</param>
public ExportCategoryAttribute(string name)
{
Name = name;
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
#nullable enable
namespace Godot
{
/// <summary>
/// Define a new group for the following exported properties. This helps to organize properties in the Inspector dock.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ExportGroupAttribute : Attribute
{
/// <summary>
/// Name of the group.
/// </summary>
public string Name { get; }
/// <summary>
/// If provided, the prefix that all properties must have to be considered part of the group.
/// </summary>
public string? Prefix { get; }
/// <summary>
/// Define a new group for the following exported properties.
/// </summary>
/// <param name="name">The name of the group.</param>
/// <param name="prefix">If provided, the group would make group to only consider properties that have this prefix.</param>
public ExportGroupAttribute(string name, string prefix = "")
{
Name = name;
Prefix = prefix;
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
#nullable enable
namespace Godot
{
/// <summary>
/// Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ExportSubgroupAttribute : Attribute
{
/// <summary>
/// Name of the subgroup.
/// </summary>
public string Name { get; }
/// <summary>
/// If provided, the prefix that all properties must have to be considered part of the subgroup.
/// </summary>
public string? Prefix { get; }
/// <summary>
/// Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock.
/// </summary>
/// <param name="name">The name of the subgroup.</param>
/// <param name="prefix">If provided, the subgroup would make group to only consider properties that have this prefix.</param>
public ExportSubgroupAttribute(string name, string prefix = "")
{
Name = name;
Prefix = prefix;
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
#nullable enable
namespace Godot
{
/// <summary>
/// Exports the annotated <see cref="Callable"/> as a clickable button.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ExportToolButtonAttribute : Attribute
{
/// <summary>
/// The label of the button.
/// </summary>
public string Text { get; }
/// <summary>
/// If defined, used to fetch an icon for the button via <see cref="Control.GetThemeIcon"/>,
/// from the <code>EditorIcons</code> theme type.
/// </summary>
public string? Icon { get; init; }
/// <summary>
/// Exports the annotated <see cref="Callable"/> as a clickable button.
/// </summary>
/// <param name="text">The label of the button.</param>
public ExportToolButtonAttribute(string text)
{
Text = text;
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
#nullable enable
namespace Godot
{
/// <summary>
/// Exposes the target class as a global script class to Godot Engine.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class GlobalClassAttribute : Attribute { }
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Godot
{
/// <summary>
/// Attribute that specifies the engine class name when it's not the same
/// as the generated C# class name. This allows introspection code to find
/// the name associated with the class. If the attribute is not present,
/// the C# class name can be used instead.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class GodotClassNameAttribute : Attribute
{
/// <summary>
/// Original engine class name.
/// </summary>
public string Name { get; }
public GodotClassNameAttribute(string name)
{
Name = name;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Godot
{
/// <summary>
/// Specifies a custom icon for representing this class in the Godot Editor.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class IconAttribute : Attribute
{
/// <summary>
/// File path to a custom icon for representing this class in the Godot Editor.
/// </summary>
public string Path { get; }
/// <summary>
/// Specify the custom icon that represents the class.
/// </summary>
/// <param name="path">File path to the custom icon.</param>
public IconAttribute(string path)
{
Path = path;
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Godot
{
/// <summary>
/// Attribute that restricts generic type parameters to be only types
/// that can be marshaled from/to a <see cref="Variant"/>.
/// </summary>
[AttributeUsage(AttributeTargets.GenericParameter)]
public sealed class MustBeVariantAttribute : Attribute { }
}

View File

@@ -0,0 +1,43 @@
using System;
namespace Godot
{
/// <summary>
/// Attribute that changes the RPC mode for the annotated <c>method</c> to the given <see cref="Mode"/>,
/// optionally specifying the <see cref="TransferMode"/> and <see cref="TransferChannel"/> (on supported peers).
/// See <see cref="MultiplayerApi.RpcMode"/> and <see cref="MultiplayerPeer.TransferModeEnum"/>.
/// By default, methods are not exposed to networking (and RPCs).
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class RpcAttribute : Attribute
{
/// <summary>
/// RPC mode for the annotated method.
/// </summary>
public MultiplayerApi.RpcMode Mode { get; } = MultiplayerApi.RpcMode.Disabled;
/// <summary>
/// If the method will also be called locally; otherwise, it is only called remotely.
/// </summary>
public bool CallLocal { get; init; }
/// <summary>
/// Transfer mode for the annotated method.
/// </summary>
public MultiplayerPeer.TransferModeEnum TransferMode { get; init; } = MultiplayerPeer.TransferModeEnum.Reliable;
/// <summary>
/// Transfer channel for the annotated mode.
/// </summary>
public int TransferChannel { get; init; }
/// <summary>
/// Constructs a <see cref="RpcAttribute"/> instance.
/// </summary>
/// <param name="mode">The RPC mode to use.</param>
public RpcAttribute(MultiplayerApi.RpcMode mode = MultiplayerApi.RpcMode.Authority)
{
Mode = mode;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Godot
{
/// <summary>
/// An attribute that contains the path to the object's script.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ScriptPathAttribute : Attribute
{
/// <summary>
/// File path to the script.
/// </summary>
public string Path { get; }
/// <summary>
/// Constructs a new ScriptPathAttribute instance.
/// </summary>
/// <param name="path">The file path to the script</param>
public ScriptPathAttribute(string path)
{
Path = path;
}
}
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Delegate)]
public sealed class SignalAttribute : Attribute { }
}

View File

@@ -0,0 +1,7 @@
using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class ToolAttribute : Attribute { }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
namespace Godot.Bridge;
public static class AlcReloadCfg
{
private static bool _configured;
public static void Configure(bool alcReloadEnabled)
{
if (_configured)
return;
_configured = true;
IsAlcReloadingEnabled = alcReloadEnabled;
}
internal static bool IsAlcReloadingEnabled;
}

View File

@@ -0,0 +1,279 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot.Bridge
{
internal static class CSharpInstanceBridge
{
[UnmanagedCallersOnly]
internal static unsafe godot_bool Call(IntPtr godotObjectGCHandle, godot_string_name* method,
godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
{
*ret = default;
(*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL;
return godot_bool.False;
}
bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
new NativeVariantPtrArgs(args, argCount), out godot_variant retValue);
if (!methodInvoked)
{
*ret = default;
// This is important, as it tells Object::call that no method was called.
// Otherwise, it would prevent Object::call from calling native methods.
(*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
return godot_bool.False;
}
*ret = retValue;
return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*ret = default;
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static unsafe godot_bool Set(IntPtr godotObjectGCHandle, godot_string_name* name, godot_variant* value)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
throw new InvalidOperationException();
if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value)))
{
return godot_bool.True;
}
if (!godotObject.HasGodotClassMethod(GodotObject.MethodName._Set.NativeValue.DangerousSelfRef))
{
return godot_bool.False;
}
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
Variant valueManaged = Variant.CreateCopyingBorrowed(*value);
return godotObject._Set(nameManaged, valueManaged).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static unsafe godot_bool Get(IntPtr godotObjectGCHandle, godot_string_name* name,
godot_variant* outRet)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
throw new InvalidOperationException();
// Properties
if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue))
{
*outRet = outRetValue;
return godot_bool.True;
}
// Signals
if (godotObject.HasGodotClassSignal(CustomUnsafe.AsRef(name)))
{
godot_signal signal = new godot_signal(NativeFuncs.godotsharp_string_name_new_copy(*name), godotObject.GetInstanceId());
*outRet = VariantUtils.CreateFromSignalTakingOwnershipOfDisposableValue(signal);
return godot_bool.True;
}
// Methods
if (godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(name)))
{
godot_callable method = new godot_callable(NativeFuncs.godotsharp_string_name_new_copy(*name), godotObject.GetInstanceId());
*outRet = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(method);
return godot_bool.True;
}
if (!godotObject.HasGodotClassMethod(GodotObject.MethodName._Get.NativeValue.DangerousSelfRef))
{
return godot_bool.False;
}
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name)));
Variant ret = godotObject._Get(nameManaged);
if (ret.VariantType == Variant.Type.Nil)
{
*outRet = default;
return godot_bool.False;
}
*outRet = ret.CopyNativeVariant();
return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outRet = default;
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static void CallDispose(IntPtr godotObjectGCHandle, godot_bool okIfNull)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (okIfNull.ToBool())
godotObject?.Dispose();
else
godotObject!.Dispose();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
[UnmanagedCallersOnly]
internal static unsafe void CallToString(IntPtr godotObjectGCHandle, godot_string* outRes, godot_bool* outValid)
{
try
{
var self = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (self == null)
{
*outRes = default;
*outValid = godot_bool.False;
return;
}
var resultStr = self.ToString();
if (resultStr == null)
{
*outRes = default;
*outValid = godot_bool.False;
return;
}
*outRes = Marshaling.ConvertStringToNative(resultStr);
*outValid = godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outRes = default;
*outValid = godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static unsafe godot_bool HasMethodUnknownParams(IntPtr godotObjectGCHandle, godot_string_name* method)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
return godot_bool.False;
return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static unsafe void SerializeState(
IntPtr godotObjectGCHandle,
godot_dictionary* propertiesState,
godot_dictionary* signalEventsState
)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
return;
// Call OnBeforeSerialize
// ReSharper disable once SuspiciousTypeConversion.Global
if (godotObject is ISerializationListener serializationListener)
serializationListener.OnBeforeSerialize();
// Save instance state
using var info = GodotSerializationInfo.CreateCopyingBorrowed(
*propertiesState, *signalEventsState);
godotObject.SaveGodotObjectData(info);
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
[UnmanagedCallersOnly]
internal static unsafe void DeserializeState(
IntPtr godotObjectGCHandle,
godot_dictionary* propertiesState,
godot_dictionary* signalEventsState
)
{
try
{
var godotObject = (GodotObject)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
return;
// Restore instance state
using var info = GodotSerializationInfo.CreateCopyingBorrowed(
*propertiesState, *signalEventsState);
godotObject.RestoreGodotObjectData(info);
// Call OnAfterDeserialize
// ReSharper disable once SuspiciousTypeConversion.Global
if (godotObject is ISerializationListener serializationListener)
serializationListener.OnAfterDeserialize();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot.Bridge
{
internal static class GCHandleBridge
{
[UnmanagedCallersOnly]
internal static void FreeGCHandle(IntPtr gcHandlePtr)
{
try
{
CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
// Returns true, if releasing the provided handle is necessary for assembly unloading to succeed.
// This check is not perfect and only intended to prevent things in GodotTools from being reloaded.
[UnmanagedCallersOnly]
internal static godot_bool GCHandleIsTargetCollectible(IntPtr gcHandlePtr)
{
try
{
var target = GCHandle.FromIntPtr(gcHandlePtr).Target;
if (target is Delegate @delegate)
return DelegateUtils.IsDelegateCollectible(@delegate).ToGodotBool();
return target.GetType().IsCollectible.ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return godot_bool.True;
}
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
namespace Godot.Bridge;
public sealed class GodotSerializationInfo : IDisposable
{
private readonly Collections.Dictionary _properties;
private readonly Collections.Dictionary _signalEvents;
public void Dispose()
{
_properties?.Dispose();
_signalEvents?.Dispose();
GC.SuppressFinalize(this);
}
private GodotSerializationInfo(in godot_dictionary properties, in godot_dictionary signalEvents)
{
_properties = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(properties);
_signalEvents = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(signalEvents);
}
internal static GodotSerializationInfo CreateCopyingBorrowed(
in godot_dictionary properties, in godot_dictionary signalEvents)
{
return new(NativeFuncs.godotsharp_dictionary_new_copy(properties),
NativeFuncs.godotsharp_dictionary_new_copy(signalEvents));
}
public void AddProperty(StringName name, Variant value)
{
_properties[name] = value;
}
public bool TryGetProperty(StringName name, out Variant value)
{
return _properties.TryGetValue(name, out value);
}
public void AddSignalEventDelegate(StringName name, Delegate eventDelegate)
{
var serializedData = new Collections.Array();
if (DelegateUtils.TrySerializeDelegate(eventDelegate, serializedData))
{
_signalEvents[name] = serializedData;
}
else if (OS.IsStdOutVerbose())
{
Console.WriteLine($"Failed to serialize event signal delegate: {name}");
}
}
public bool TryGetSignalEventDelegate<T>(StringName name, [MaybeNullWhen(false)] out T value)
where T : Delegate
{
if (_signalEvents.TryGetValue(name, out Variant serializedData))
{
if (DelegateUtils.TryDeserializeDelegate(serializedData.AsGodotArray(), out var eventDelegate))
{
value = eventDelegate as T;
if (value == null)
{
Console.WriteLine($"Cannot cast the deserialized event signal delegate: {name}. " +
$"Expected '{typeof(T).FullName}'; got '{eventDelegate.GetType().FullName}'.");
return false;
}
return true;
}
else if (OS.IsStdOutVerbose())
{
Console.WriteLine($"Failed to deserialize event signal delegate: {name}");
}
value = null;
return false;
}
value = null;
return false;
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot.Bridge
{
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ManagedCallbacks
{
// @formatter:off
public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
public delegate* unmanaged<IntPtr, int> DelegateUtils_DelegateHash;
public delegate* unmanaged<IntPtr, godot_bool*, int> DelegateUtils_GetArgumentCount;
public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;
public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback;
public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName;
public delegate* unmanaged<godot_string*, godot_string*, godot_string*, godot_bool*, godot_bool*, godot_string*, void> ScriptManagerBridge_GetGlobalClassName;
public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
public delegate* unmanaged<IntPtr, godot_csharp_type_info*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> ScriptManagerBridge_CallStatic;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get;
public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams;
public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState;
public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState;
public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle;
public delegate* unmanaged<IntPtr, godot_bool> GCHandleBridge_GCHandleIsTargetCollectible;
public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo;
public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown;
public delegate* unmanaged<godot_bool, void> GD_OnCoreApiAssemblyLoaded;
// @formatter:on
public static ManagedCallbacks Create()
{
return new()
{
// @formatter:off
SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback,
DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
DelegateUtils_DelegateHash = &DelegateUtils.DelegateHash,
DelegateUtils_GetArgumentCount = &DelegateUtils.GetArgumentCount,
DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle,
DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle,
ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,
ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName,
ScriptManagerBridge_GetGlobalClassName = &ScriptManagerBridge.GetGlobalClassName,
ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr,
ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal,
ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits,
ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass,
ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo,
ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues,
ScriptManagerBridge_CallStatic = &ScriptManagerBridge.CallStatic,
CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call,
CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set,
CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get,
CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState,
CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState,
GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
GCHandleBridge_GCHandleIsTargetCollectible = &GCHandleBridge.GCHandleIsTargetCollectible,
DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo,
DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown,
GD_OnCoreApiAssemblyLoaded = &GD.OnCoreApiAssemblyLoaded,
// @formatter:on
};
}
public static void Create(IntPtr outManagedCallbacks)
=> *(ManagedCallbacks*)outManagedCallbacks = Create();
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Godot.Bridge;
#nullable enable
public readonly struct MethodInfo
{
public StringName Name { get; init; }
public PropertyInfo ReturnVal { get; init; }
public MethodFlags Flags { get; init; }
public int Id { get; init; } = 0;
public List<PropertyInfo>? Arguments { get; init; }
public List<Variant>? DefaultArguments { get; init; }
public MethodInfo(StringName name, PropertyInfo returnVal, MethodFlags flags,
List<PropertyInfo>? arguments, List<Variant>? defaultArguments)
{
Name = name;
ReturnVal = returnVal;
Flags = flags;
Arguments = arguments;
DefaultArguments = defaultArguments;
}
}

View File

@@ -0,0 +1,30 @@
namespace Godot.Bridge;
#nullable enable
public readonly struct PropertyInfo
{
public Variant.Type Type { get; init; }
public StringName Name { get; init; }
public PropertyHint Hint { get; init; }
public string HintString { get; init; }
public PropertyUsageFlags Usage { get; init; }
public StringName? ClassName { get; init; }
public bool Exported { get; init; }
public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString,
PropertyUsageFlags usage, bool exported)
: this(type, name, hint, hintString, usage, className: null, exported) { }
public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString,
PropertyUsageFlags usage, StringName? className, bool exported)
{
Type = type;
Name = name;
Hint = hint;
HintString = hintString;
Usage = usage;
ClassName = className;
Exported = exported;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Godot.Bridge;
#nullable enable
public static partial class ScriptManagerBridge
{
[SuppressMessage("Design", "CA1001", MessageId = "Types that own disposable fields should be disposable",
Justification = "Not applicable. The class functions as a persistent singleton.")]
private class ScriptTypeBiMap
{
public readonly ReaderWriterLockSlim ReadWriteLock = new(LockRecursionPolicy.SupportsRecursion);
private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
public void Add(IntPtr scriptPtr, Type scriptType)
{
// TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
Debug.Assert(!scriptType.IsGenericTypeDefinition, $"A generic type definition must never be added to the script type map. Type: {scriptType}.");
_scriptTypeMap.Add(scriptPtr, scriptType);
_typeScriptMap.Add(scriptType, scriptPtr);
if (AlcReloadCfg.IsAlcReloadingEnabled)
{
AddTypeForAlcReloading(scriptType);
}
}
public void Remove(IntPtr scriptPtr)
{
if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType))
_ = _typeScriptMap.Remove(scriptType);
}
public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr)
{
if (_typeScriptMap.Remove(scriptType, out scriptPtr))
return _scriptTypeMap.Remove(scriptPtr);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) =>
_scriptTypeMap.TryGetValue(scriptPtr, out scriptType);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) =>
_typeScriptMap.TryGetValue(scriptType, out scriptPtr);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr);
}
private class PathScriptTypeBiMap
{
private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new();
private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new();
public System.Collections.Generic.Dictionary<string, Type>.KeyCollection Paths => _pathTypeMap.Keys;
public void Add(string scriptPath, Type scriptType)
{
_pathTypeMap.Add(scriptPath, scriptType);
// Due to partial classes, more than one file can point to the same type, so
// there could be duplicate keys in this case. We only add a type as key once.
_typePathMap.TryAdd(scriptType, scriptPath);
}
public void RemoveByScriptType(Type scriptType)
{
foreach (var pair in _pathTypeMap
.Where(p => p.Value == scriptType).ToArray())
{
_pathTypeMap.Remove(pair.Key);
}
_typePathMap.Remove(scriptType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
// This must never return true for a generic type definition, we only consider script types
// the types that can be attached to a Node/Resource (non-generic or constructed generic types).
_pathTypeMap.TryGetValue(scriptPath, out scriptType) && !scriptType.IsGenericTypeDefinition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath)
{
if (scriptType.IsGenericTypeDefinition)
{
// This must never return true for a generic type definition, we only consider script types
// the types that can be attached to a Node/Resource (non-generic or constructed generic types).
scriptPath = null;
return false;
}
return _typePathMap.TryGetValue(scriptType, out scriptPath);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetGenericTypeDefinitionPath(Type genericTypeDefinition, [MaybeNullWhen(false)] out string scriptPath)
{
Debug.Assert(genericTypeDefinition.IsGenericTypeDefinition);
return _typePathMap.TryGetValue(genericTypeDefinition, out scriptPath);
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
{
/// <summary>
/// Callable is a first class object which can be held in variables and passed to functions.
/// It represents a given method in an Object, and is typically used for signal callbacks.
/// </summary>
/// <example>
/// <code>
/// public void PrintArgs(object ar1, object arg2, object arg3 = null)
/// {
/// GD.PrintS(arg1, arg2, arg3);
/// }
///
/// public void Test()
/// {
/// // This Callable object will call the PrintArgs method defined above.
/// Callable callable = new Callable(this, nameof(PrintArgs));
/// callable.Call("hello", "world"); // Prints "hello world null".
/// callable.Call(Vector2.Up, 42, callable); // Prints "(0, -1) 42 Node(Node.cs)::PrintArgs".
/// callable.Call("invalid"); // Invalid call, should have at least 2 arguments.
/// }
/// </code>
/// </example>
public readonly partial struct Callable
{
private readonly GodotObject _target;
private readonly StringName _method;
private readonly Delegate _delegate;
private readonly unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> _trampoline;
/// <summary>
/// Object that contains the method.
/// </summary>
public GodotObject Target => _target;
/// <summary>
/// Name of the method that will be called.
/// </summary>
public StringName Method => _method;
/// <summary>
/// Delegate of the method that will be called.
/// </summary>
public Delegate Delegate => _delegate;
/// <summary>
/// Trampoline function pointer for dynamically invoking <see cref="Callable.Delegate"/>.
/// </summary>
public unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> Trampoline
=> _trampoline;
/// <summary>
/// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/>
/// in the specified <paramref name="target"/>.
/// </summary>
/// <param name="target">Object that contains the method.</param>
/// <param name="method">Name of the method that will be called.</param>
public unsafe Callable(GodotObject target, StringName method)
{
_target = target;
_method = method;
_delegate = null;
_trampoline = null;
}
private unsafe Callable(Delegate @delegate,
delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
{
_target = @delegate?.Target as GodotObject;
_method = null;
_delegate = @delegate;
_trampoline = trampoline;
}
private const int VarArgsSpanThreshold = 10;
/// <summary>
/// Calls the method represented by this <see cref="Callable"/>.
/// Arguments can be passed and should match the method's signature.
/// </summary>
/// <param name="args">Arguments that will be passed to the method call.</param>
/// <returns>The value returned by the method.</returns>
public unsafe Variant Call(params Variant[] args)
{
using godot_callable callable = Marshaling.ConvertCallableToNative(this);
int argc = args.Length;
Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ?
stackalloc godot_variant.movable[VarArgsSpanThreshold] :
new godot_variant.movable[argc];
Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ?
stackalloc IntPtr[VarArgsSpanThreshold] :
new IntPtr[argc];
fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef)
fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan))
{
for (int i = 0; i < argc; i++)
{
varargs[i] = (godot_variant)args[i].NativeVar;
argsPtr[i] = new IntPtr(&varargs[i]);
}
godot_variant ret = NativeFuncs.godotsharp_callable_call(callable,
(godot_variant**)argsPtr, argc, out godot_variant_call_error vcall_error);
ExceptionUtils.DebugCheckCallError(callable, (godot_variant**)argsPtr, argc, vcall_error);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
}
/// <summary>
/// Calls the method represented by this <see cref="Callable"/> in deferred mode, i.e. during the idle frame.
/// Arguments can be passed and should match the method's signature.
/// </summary>
/// <param name="args">Arguments that will be passed to the method call.</param>
public unsafe void CallDeferred(params Variant[] args)
{
using godot_callable callable = Marshaling.ConvertCallableToNative(this);
int argc = args.Length;
Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ?
stackalloc godot_variant.movable[VarArgsSpanThreshold] :
new godot_variant.movable[argc];
Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ?
stackalloc IntPtr[VarArgsSpanThreshold] :
new IntPtr[argc];
fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef)
fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan))
{
for (int i = 0; i < argc; i++)
{
varargs[i] = (godot_variant)args[i].NativeVar;
argsPtr[i] = new IntPtr(&varargs[i]);
}
NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc);
}
}
/// <summary>
/// <para>
/// Constructs a new <see cref="Callable"/> using the <paramref name="trampoline"/>
/// function pointer to dynamically invoke the given <paramref name="delegate"/>.
/// </para>
/// <para>
/// The parameters passed to the <paramref name="trampoline"/> function are:
/// </para>
/// <list type="number">
/// <item>
/// <term>delegateObj</term>
/// <description>The given <paramref name="delegate"/>, upcast to <see cref="object"/>.</description>
/// </item>
/// <item>
/// <term>args</term>
/// <description>Array of <see cref="godot_variant"/> arguments.</description>
/// </item>
/// <item>
/// <term>ret</term>
/// <description>Return value of type <see cref="godot_variant"/>.</description>
/// </item>
///</list>
/// <para>
/// The delegate should be downcast to a more specific delegate type before invoking.
/// </para>
/// </summary>
/// <example>
/// Usage example:
///
/// <code>
/// static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
/// {
/// if (args.Count != 1)
/// throw new ArgumentException($&quot;Callable expected {1} argument but received {args.Count}.&quot;);
///
/// TResult res = ((Func&lt;int, string&gt;)delegateObj)(
/// VariantConversionCallbacks.GetToManagedCallback&lt;int&gt;()(args[0])
/// );
///
/// ret = VariantConversionCallbacks.GetToVariantCallback&lt;string&gt;()(res);
/// }
///
/// var callable = Callable.CreateWithUnsafeTrampoline((int num) =&gt; &quot;foo&quot; + num.ToString(), &amp;Trampoline);
/// var res = (string)callable.Call(10);
/// Console.WriteLine(res);
/// </code>
/// </example>
/// <param name="delegate">Delegate method that will be called.</param>
/// <param name="trampoline">Trampoline function pointer for invoking the delegate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe Callable CreateWithUnsafeTrampoline(Delegate @delegate,
delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline)
=> new(@delegate, trampoline);
}
}

View File

@@ -0,0 +1,480 @@
using System;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
namespace Godot;
#nullable enable
public readonly partial struct Callable
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ThrowIfArgCountMismatch(NativeVariantPtrArgs args, int countExpected,
[CallerArgumentExpression("args")] string? paramName = null)
{
if (countExpected != args.Count)
ThrowArgCountMismatch(countExpected, args.Count, paramName);
static void ThrowArgCountMismatch(int countExpected, int countReceived, string? paramName)
{
throw new ArgumentException(
"Invalid argument count for invoking callable." +
$" Expected {countExpected} argument(s), received {countReceived}.",
paramName);
}
}
/// <summary>
/// Constructs a new <see cref="Callable"/> for the given <paramref name="action"/>.
/// </summary>
/// <param name="action">Action method that will be called.</param>
public static unsafe Callable From(
Action action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 0);
((Action)delegateObj)();
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0>(
Action<T0> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 1);
((Action<T0>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1>(
Action<T0, T1> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 2);
((Action<T0, T1>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2>(
Action<T0, T1, T2> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 3);
((Action<T0, T1, T2>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3>(
Action<T0, T1, T2, T3> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 4);
((Action<T0, T1, T2, T3>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4>(
Action<T0, T1, T2, T3, T4> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 5);
((Action<T0, T1, T2, T3, T4>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5>(
Action<T0, T1, T2, T3, T4, T5> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 6);
((Action<T0, T1, T2, T3, T4, T5>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6>(
Action<T0, T1, T2, T3, T4, T5, T6> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 7);
((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6, [MustBeVariant] T7>(
Action<T0, T1, T2, T3, T4, T5, T6, T7> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 8);
((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <inheritdoc cref="From(Action)"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6, [MustBeVariant] T7, [MustBeVariant] T8>(
Action<T0, T1, T2, T3, T4, T5, T6, T7, T8> action
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 9);
((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7]),
VariantUtils.ConvertTo<T8>(args[8])
);
ret = default;
}
return CreateWithUnsafeTrampoline(action, &Trampoline);
}
/// <summary>
/// Constructs a new <see cref="Callable"/> for the given <paramref name="func"/>.
/// </summary>
/// <param name="func">Action method that will be called.</param>
public static unsafe Callable From<[MustBeVariant] TResult>(
Func<TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 0);
TResult res = ((Func<TResult>)delegateObj)();
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] TResult>(
Func<T0, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 1);
TResult res = ((Func<T0, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] TResult>(
Func<T0, T1, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 2);
TResult res = ((Func<T0, T1, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] TResult>(
Func<T0, T1, T2, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 3);
TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 4);
TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, T4, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 5);
TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, T4, T5, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 6);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, T4, T5, T6, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 7);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6, [MustBeVariant] T7, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 8);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
/// <inheritdoc cref="From{TResult}(Func{TResult})"/>
public static unsafe Callable From<[MustBeVariant] T0, [MustBeVariant] T1, [MustBeVariant] T2, [MustBeVariant] T3, [MustBeVariant] T4, [MustBeVariant] T5, [MustBeVariant] T6, [MustBeVariant] T7, [MustBeVariant] T8, [MustBeVariant] TResult>(
Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult> func
)
{
static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)
{
ThrowIfArgCountMismatch(args, 9);
TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)(
VariantUtils.ConvertTo<T0>(args[0]),
VariantUtils.ConvertTo<T1>(args[1]),
VariantUtils.ConvertTo<T2>(args[2]),
VariantUtils.ConvertTo<T3>(args[3]),
VariantUtils.ConvertTo<T4>(args[4]),
VariantUtils.ConvertTo<T5>(args[5]),
VariantUtils.ConvertTo<T6>(args[6]),
VariantUtils.ConvertTo<T7>(args[7]),
VariantUtils.ConvertTo<T8>(args[8])
);
ret = VariantUtils.CreateFrom(res);
}
return CreateWithUnsafeTrampoline(func, &Trampoline);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,311 @@
using System.Collections.Generic;
using System.Collections.Frozen;
namespace Godot
{
/// <summary>
/// This class contains color constants created from standardized color names.
/// The standardized color set is based on the X11 and .NET color names.
/// </summary>
public static class Colors
{
// Color names and values are derived from core/math/color_names.inc
internal static readonly FrozenDictionary<string, Color> NamedColors = new Dictionary<string, Color> {
{ "ALICEBLUE", Colors.AliceBlue },
{ "ANTIQUEWHITE", Colors.AntiqueWhite },
{ "AQUA", Colors.Aqua },
{ "AQUAMARINE", Colors.Aquamarine },
{ "AZURE", Colors.Azure },
{ "BEIGE", Colors.Beige },
{ "BISQUE", Colors.Bisque },
{ "BLACK", Colors.Black },
{ "BLANCHEDALMOND", Colors.BlanchedAlmond },
{ "BLUE", Colors.Blue },
{ "BLUEVIOLET", Colors.BlueViolet },
{ "BROWN", Colors.Brown },
{ "BURLYWOOD", Colors.Burlywood },
{ "CADETBLUE", Colors.CadetBlue },
{ "CHARTREUSE", Colors.Chartreuse },
{ "CHOCOLATE", Colors.Chocolate },
{ "CORAL", Colors.Coral },
{ "CORNFLOWERBLUE", Colors.CornflowerBlue },
{ "CORNSILK", Colors.Cornsilk },
{ "CRIMSON", Colors.Crimson },
{ "CYAN", Colors.Cyan },
{ "DARKBLUE", Colors.DarkBlue },
{ "DARKCYAN", Colors.DarkCyan },
{ "DARKGOLDENROD", Colors.DarkGoldenrod },
{ "DARKGRAY", Colors.DarkGray },
{ "DARKGREEN", Colors.DarkGreen },
{ "DARKKHAKI", Colors.DarkKhaki },
{ "DARKMAGENTA", Colors.DarkMagenta },
{ "DARKOLIVEGREEN", Colors.DarkOliveGreen },
{ "DARKORANGE", Colors.DarkOrange },
{ "DARKORCHID", Colors.DarkOrchid },
{ "DARKRED", Colors.DarkRed },
{ "DARKSALMON", Colors.DarkSalmon },
{ "DARKSEAGREEN", Colors.DarkSeaGreen },
{ "DARKSLATEBLUE", Colors.DarkSlateBlue },
{ "DARKSLATEGRAY", Colors.DarkSlateGray },
{ "DARKTURQUOISE", Colors.DarkTurquoise },
{ "DARKVIOLET", Colors.DarkViolet },
{ "DEEPPINK", Colors.DeepPink },
{ "DEEPSKYBLUE", Colors.DeepSkyBlue },
{ "DIMGRAY", Colors.DimGray },
{ "DODGERBLUE", Colors.DodgerBlue },
{ "FIREBRICK", Colors.Firebrick },
{ "FLORALWHITE", Colors.FloralWhite },
{ "FORESTGREEN", Colors.ForestGreen },
{ "FUCHSIA", Colors.Fuchsia },
{ "GAINSBORO", Colors.Gainsboro },
{ "GHOSTWHITE", Colors.GhostWhite },
{ "GOLD", Colors.Gold },
{ "GOLDENROD", Colors.Goldenrod },
{ "GRAY", Colors.Gray },
{ "GREEN", Colors.Green },
{ "GREENYELLOW", Colors.GreenYellow },
{ "HONEYDEW", Colors.Honeydew },
{ "HOTPINK", Colors.HotPink },
{ "INDIANRED", Colors.IndianRed },
{ "INDIGO", Colors.Indigo },
{ "IVORY", Colors.Ivory },
{ "KHAKI", Colors.Khaki },
{ "LAVENDER", Colors.Lavender },
{ "LAVENDERBLUSH", Colors.LavenderBlush },
{ "LAWNGREEN", Colors.LawnGreen },
{ "LEMONCHIFFON", Colors.LemonChiffon },
{ "LIGHTBLUE", Colors.LightBlue },
{ "LIGHTCORAL", Colors.LightCoral },
{ "LIGHTCYAN", Colors.LightCyan },
{ "LIGHTGOLDENROD", Colors.LightGoldenrod },
{ "LIGHTGRAY", Colors.LightGray },
{ "LIGHTGREEN", Colors.LightGreen },
{ "LIGHTPINK", Colors.LightPink },
{ "LIGHTSALMON", Colors.LightSalmon },
{ "LIGHTSEAGREEN", Colors.LightSeaGreen },
{ "LIGHTSKYBLUE", Colors.LightSkyBlue },
{ "LIGHTSLATEGRAY", Colors.LightSlateGray },
{ "LIGHTSTEELBLUE", Colors.LightSteelBlue },
{ "LIGHTYELLOW", Colors.LightYellow },
{ "LIME", Colors.Lime },
{ "LIMEGREEN", Colors.LimeGreen },
{ "LINEN", Colors.Linen },
{ "MAGENTA", Colors.Magenta },
{ "MAROON", Colors.Maroon },
{ "MEDIUMAQUAMARINE", Colors.MediumAquamarine },
{ "MEDIUMBLUE", Colors.MediumBlue },
{ "MEDIUMORCHID", Colors.MediumOrchid },
{ "MEDIUMPURPLE", Colors.MediumPurple },
{ "MEDIUMSEAGREEN", Colors.MediumSeaGreen },
{ "MEDIUMSLATEBLUE", Colors.MediumSlateBlue },
{ "MEDIUMSPRINGGREEN", Colors.MediumSpringGreen },
{ "MEDIUMTURQUOISE", Colors.MediumTurquoise },
{ "MEDIUMVIOLETRED", Colors.MediumVioletRed },
{ "MIDNIGHTBLUE", Colors.MidnightBlue },
{ "MINTCREAM", Colors.MintCream },
{ "MISTYROSE", Colors.MistyRose },
{ "MOCCASIN", Colors.Moccasin },
{ "NAVAJOWHITE", Colors.NavajoWhite },
{ "NAVYBLUE", Colors.NavyBlue },
{ "OLDLACE", Colors.OldLace },
{ "OLIVE", Colors.Olive },
{ "OLIVEDRAB", Colors.OliveDrab },
{ "ORANGE", Colors.Orange },
{ "ORANGERED", Colors.OrangeRed },
{ "ORCHID", Colors.Orchid },
{ "PALEGOLDENROD", Colors.PaleGoldenrod },
{ "PALEGREEN", Colors.PaleGreen },
{ "PALETURQUOISE", Colors.PaleTurquoise },
{ "PALEVIOLETRED", Colors.PaleVioletRed },
{ "PAPAYAWHIP", Colors.PapayaWhip },
{ "PEACHPUFF", Colors.PeachPuff },
{ "PERU", Colors.Peru },
{ "PINK", Colors.Pink },
{ "PLUM", Colors.Plum },
{ "POWDERBLUE", Colors.PowderBlue },
{ "PURPLE", Colors.Purple },
{ "REBECCAPURPLE", Colors.RebeccaPurple },
{ "RED", Colors.Red },
{ "ROSYBROWN", Colors.RosyBrown },
{ "ROYALBLUE", Colors.RoyalBlue },
{ "SADDLEBROWN", Colors.SaddleBrown },
{ "SALMON", Colors.Salmon },
{ "SANDYBROWN", Colors.SandyBrown },
{ "SEAGREEN", Colors.SeaGreen },
{ "SEASHELL", Colors.Seashell },
{ "SIENNA", Colors.Sienna },
{ "SILVER", Colors.Silver },
{ "SKYBLUE", Colors.SkyBlue },
{ "SLATEBLUE", Colors.SlateBlue },
{ "SLATEGRAY", Colors.SlateGray },
{ "SNOW", Colors.Snow },
{ "SPRINGGREEN", Colors.SpringGreen },
{ "STEELBLUE", Colors.SteelBlue },
{ "TAN", Colors.Tan },
{ "TEAL", Colors.Teal },
{ "THISTLE", Colors.Thistle },
{ "TOMATO", Colors.Tomato },
{ "TRANSPARENT", Colors.Transparent },
{ "TURQUOISE", Colors.Turquoise },
{ "VIOLET", Colors.Violet },
{ "WEBGRAY", Colors.WebGray },
{ "WEBGREEN", Colors.WebGreen },
{ "WEBMAROON", Colors.WebMaroon },
{ "WEBPURPLE", Colors.WebPurple },
{ "WHEAT", Colors.Wheat },
{ "WHITE", Colors.White },
{ "WHITESMOKE", Colors.WhiteSmoke },
{ "YELLOW", Colors.Yellow },
{ "YELLOWGREEN", Colors.YellowGreen },
}.ToFrozenDictionary();
#pragma warning disable CS1591 // Disable warning: "Missing XML comment for publicly visible type or member"
public static Color AliceBlue => new Color(0xF0F8FFFF);
public static Color AntiqueWhite => new Color(0xFAEBD7FF);
public static Color Aqua => new Color(0x00FFFFFF);
public static Color Aquamarine => new Color(0x7FFFD4FF);
public static Color Azure => new Color(0xF0FFFFFF);
public static Color Beige => new Color(0xF5F5DCFF);
public static Color Bisque => new Color(0xFFE4C4FF);
public static Color Black => new Color(0x000000FF);
public static Color BlanchedAlmond => new Color(0xFFEBCDFF);
public static Color Blue => new Color(0x0000FFFF);
public static Color BlueViolet => new Color(0x8A2BE2FF);
public static Color Brown => new Color(0xA52A2AFF);
public static Color Burlywood => new Color(0xDEB887FF);
public static Color CadetBlue => new Color(0x5F9EA0FF);
public static Color Chartreuse => new Color(0x7FFF00FF);
public static Color Chocolate => new Color(0xD2691EFF);
public static Color Coral => new Color(0xFF7F50FF);
public static Color CornflowerBlue => new Color(0x6495EDFF);
public static Color Cornsilk => new Color(0xFFF8DCFF);
public static Color Crimson => new Color(0xDC143CFF);
public static Color Cyan => new Color(0x00FFFFFF);
public static Color DarkBlue => new Color(0x00008BFF);
public static Color DarkCyan => new Color(0x008B8BFF);
public static Color DarkGoldenrod => new Color(0xB8860BFF);
public static Color DarkGray => new Color(0xA9A9A9FF);
public static Color DarkGreen => new Color(0x006400FF);
public static Color DarkKhaki => new Color(0xBDB76BFF);
public static Color DarkMagenta => new Color(0x8B008BFF);
public static Color DarkOliveGreen => new Color(0x556B2FFF);
public static Color DarkOrange => new Color(0xFF8C00FF);
public static Color DarkOrchid => new Color(0x9932CCFF);
public static Color DarkRed => new Color(0x8B0000FF);
public static Color DarkSalmon => new Color(0xE9967AFF);
public static Color DarkSeaGreen => new Color(0x8FBC8FFF);
public static Color DarkSlateBlue => new Color(0x483D8BFF);
public static Color DarkSlateGray => new Color(0x2F4F4FFF);
public static Color DarkTurquoise => new Color(0x00CED1FF);
public static Color DarkViolet => new Color(0x9400D3FF);
public static Color DeepPink => new Color(0xFF1493FF);
public static Color DeepSkyBlue => new Color(0x00BFFFFF);
public static Color DimGray => new Color(0x696969FF);
public static Color DodgerBlue => new Color(0x1E90FFFF);
public static Color Firebrick => new Color(0xB22222FF);
public static Color FloralWhite => new Color(0xFFFAF0FF);
public static Color ForestGreen => new Color(0x228B22FF);
public static Color Fuchsia => new Color(0xFF00FFFF);
public static Color Gainsboro => new Color(0xDCDCDCFF);
public static Color GhostWhite => new Color(0xF8F8FFFF);
public static Color Gold => new Color(0xFFD700FF);
public static Color Goldenrod => new Color(0xDAA520FF);
public static Color Gray => new Color(0xBEBEBEFF);
public static Color Green => new Color(0x00FF00FF);
public static Color GreenYellow => new Color(0xADFF2FFF);
public static Color Honeydew => new Color(0xF0FFF0FF);
public static Color HotPink => new Color(0xFF69B4FF);
public static Color IndianRed => new Color(0xCD5C5CFF);
public static Color Indigo => new Color(0x4B0082FF);
public static Color Ivory => new Color(0xFFFFF0FF);
public static Color Khaki => new Color(0xF0E68CFF);
public static Color Lavender => new Color(0xE6E6FAFF);
public static Color LavenderBlush => new Color(0xFFF0F5FF);
public static Color LawnGreen => new Color(0x7CFC00FF);
public static Color LemonChiffon => new Color(0xFFFACDFF);
public static Color LightBlue => new Color(0xADD8E6FF);
public static Color LightCoral => new Color(0xF08080FF);
public static Color LightCyan => new Color(0xE0FFFFFF);
public static Color LightGoldenrod => new Color(0xFAFAD2FF);
public static Color LightGray => new Color(0xD3D3D3FF);
public static Color LightGreen => new Color(0x90EE90FF);
public static Color LightPink => new Color(0xFFB6C1FF);
public static Color LightSalmon => new Color(0xFFA07AFF);
public static Color LightSeaGreen => new Color(0x20B2AAFF);
public static Color LightSkyBlue => new Color(0x87CEFAFF);
public static Color LightSlateGray => new Color(0x778899FF);
public static Color LightSteelBlue => new Color(0xB0C4DEFF);
public static Color LightYellow => new Color(0xFFFFE0FF);
public static Color Lime => new Color(0x00FF00FF);
public static Color LimeGreen => new Color(0x32CD32FF);
public static Color Linen => new Color(0xFAF0E6FF);
public static Color Magenta => new Color(0xFF00FFFF);
public static Color Maroon => new Color(0xB03060FF);
public static Color MediumAquamarine => new Color(0x66CDAAFF);
public static Color MediumBlue => new Color(0x0000CDFF);
public static Color MediumOrchid => new Color(0xBA55D3FF);
public static Color MediumPurple => new Color(0x9370DBFF);
public static Color MediumSeaGreen => new Color(0x3CB371FF);
public static Color MediumSlateBlue => new Color(0x7B68EEFF);
public static Color MediumSpringGreen => new Color(0x00FA9AFF);
public static Color MediumTurquoise => new Color(0x48D1CCFF);
public static Color MediumVioletRed => new Color(0xC71585FF);
public static Color MidnightBlue => new Color(0x191970FF);
public static Color MintCream => new Color(0xF5FFFAFF);
public static Color MistyRose => new Color(0xFFE4E1FF);
public static Color Moccasin => new Color(0xFFE4B5FF);
public static Color NavajoWhite => new Color(0xFFDEADFF);
public static Color NavyBlue => new Color(0x000080FF);
public static Color OldLace => new Color(0xFDF5E6FF);
public static Color Olive => new Color(0x808000FF);
public static Color OliveDrab => new Color(0x6B8E23FF);
public static Color Orange => new Color(0xFFA500FF);
public static Color OrangeRed => new Color(0xFF4500FF);
public static Color Orchid => new Color(0xDA70D6FF);
public static Color PaleGoldenrod => new Color(0xEEE8AAFF);
public static Color PaleGreen => new Color(0x98FB98FF);
public static Color PaleTurquoise => new Color(0xAFEEEEFF);
public static Color PaleVioletRed => new Color(0xDB7093FF);
public static Color PapayaWhip => new Color(0xFFEFD5FF);
public static Color PeachPuff => new Color(0xFFDAB9FF);
public static Color Peru => new Color(0xCD853FFF);
public static Color Pink => new Color(0xFFC0CBFF);
public static Color Plum => new Color(0xDDA0DDFF);
public static Color PowderBlue => new Color(0xB0E0E6FF);
public static Color Purple => new Color(0xA020F0FF);
public static Color RebeccaPurple => new Color(0x663399FF);
public static Color Red => new Color(0xFF0000FF);
public static Color RosyBrown => new Color(0xBC8F8FFF);
public static Color RoyalBlue => new Color(0x4169E1FF);
public static Color SaddleBrown => new Color(0x8B4513FF);
public static Color Salmon => new Color(0xFA8072FF);
public static Color SandyBrown => new Color(0xF4A460FF);
public static Color SeaGreen => new Color(0x2E8B57FF);
public static Color Seashell => new Color(0xFFF5EEFF);
public static Color Sienna => new Color(0xA0522DFF);
public static Color Silver => new Color(0xC0C0C0FF);
public static Color SkyBlue => new Color(0x87CEEBFF);
public static Color SlateBlue => new Color(0x6A5ACDFF);
public static Color SlateGray => new Color(0x708090FF);
public static Color Snow => new Color(0xFFFAFAFF);
public static Color SpringGreen => new Color(0x00FF7FFF);
public static Color SteelBlue => new Color(0x4682B4FF);
public static Color Tan => new Color(0xD2B48CFF);
public static Color Teal => new Color(0x008080FF);
public static Color Thistle => new Color(0xD8BFD8FF);
public static Color Tomato => new Color(0xFF6347FF);
public static Color Transparent => new Color(0xFFFFFF00);
public static Color Turquoise => new Color(0x40E0D0FF);
public static Color Violet => new Color(0xEE82EEFF);
public static Color WebGray => new Color(0x808080FF);
public static Color WebGreen => new Color(0x008000FF);
public static Color WebMaroon => new Color(0x800000FF);
public static Color WebPurple => new Color(0x800080FF);
public static Color Wheat => new Color(0xF5DEB3FF);
public static Color White => new Color(0xFFFFFFFF);
public static Color WhiteSmoke => new Color(0xF5F5F5FF);
public static Color Yellow => new Color(0xFFFF00FF);
public static Color YellowGreen => new Color(0x9ACD32FF);
#pragma warning restore CS1591
}
}

View File

@@ -0,0 +1,97 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Godot.Bridge;
namespace Godot;
/// <summary>
/// Provides a GCHandle that becomes weak when unloading the assembly load context, without having
/// to manually replace the GCHandle. This hides all the complexity of releasing strong GC handles
/// to allow the assembly load context to unload properly.
///
/// Internally, a strong CustomGCHandle actually contains a weak GCHandle, while the actual strong
/// reference is stored in a static table.
/// </summary>
public static class CustomGCHandle
{
// ConditionalWeakTable uses DependentHandle, so it stores weak references.
// Having the assembly load context as key won't prevent it from unloading.
private static ConditionalWeakTable<AssemblyLoadContext, object?> _alcsBeingUnloaded = new();
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsAlcBeingUnloaded(AssemblyLoadContext alc) => _alcsBeingUnloaded.TryGetValue(alc, out _);
private static ConcurrentDictionary<
AssemblyLoadContext,
ConcurrentDictionary<GCHandle, object>
> _strongReferencesByAlc = new();
[MethodImpl(MethodImplOptions.NoInlining)]
private static void OnAlcUnloading(AssemblyLoadContext alc)
{
_alcsBeingUnloaded.Add(alc, null);
if (_strongReferencesByAlc.TryRemove(alc, out var strongReferences))
{
strongReferences.Clear();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GCHandle AllocStrong(object value)
=> AllocStrong(value, value.GetType());
public static GCHandle AllocStrong(object value, Type valueType)
{
if (AlcReloadCfg.IsAlcReloadingEnabled)
{
var alc = AssemblyLoadContext.GetLoadContext(valueType.Assembly);
if (alc != null)
{
var weakHandle = GCHandle.Alloc(value, GCHandleType.Weak);
if (!IsAlcBeingUnloaded(alc))
{
var strongReferences = _strongReferencesByAlc.GetOrAdd(alc,
static alc =>
{
alc.Unloading += OnAlcUnloading;
return new();
});
strongReferences.TryAdd(weakHandle, value);
}
return weakHandle;
}
}
return GCHandle.Alloc(value, GCHandleType.Normal);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GCHandle AllocWeak(object value) => GCHandle.Alloc(value, GCHandleType.Weak);
public static void Free(GCHandle handle)
{
if (AlcReloadCfg.IsAlcReloadingEnabled)
{
var target = handle.Target;
if (target != null)
{
var alc = AssemblyLoadContext.GetLoadContext(target.GetType().Assembly);
if (alc != null && _strongReferencesByAlc.TryGetValue(alc, out var strongReferences))
_ = strongReferences.TryRemove(handle, out _);
}
}
handle.Free();
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Godot.Collections
{
internal sealed class ArrayDebugView<T>
{
private readonly IList<T> _array;
public ArrayDebugView(IList<T> array)
{
ArgumentNullException.ThrowIfNull(array);
_array = array;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var items = new T[_array.Count];
_array.CopyTo(items, 0);
return items;
}
}
}
internal sealed class DictionaryDebugView<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _dictionary;
public DictionaryDebugView(IDictionary<TKey, TValue> dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);
_dictionary = dictionary;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public DictionaryKeyItemDebugView<TKey, TValue>[] Items
{
get
{
var items = new KeyValuePair<TKey, TValue>[_dictionary.Count];
var views = new DictionaryKeyItemDebugView<TKey, TValue>[_dictionary.Count];
_dictionary.CopyTo(items, 0);
for (int i = 0; i < items.Length; i++)
{
views[i] = new DictionaryKeyItemDebugView<TKey, TValue>(items[i]);
}
return views;
}
}
}
[DebuggerDisplay("{Value}", Name = "[{Key}]")]
internal readonly struct DictionaryKeyItemDebugView<TKey, TValue>
{
public DictionaryKeyItemDebugView(KeyValuePair<TKey, TValue> keyValue)
{
Key = keyValue.Key;
Value = keyValue.Value;
}
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public TKey Key { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public TValue Value { get; }
}
}

View File

@@ -0,0 +1,231 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
internal static class DebuggingUtils
{
private static void AppendTypeName(this StringBuilder sb, Type type)
{
// Use the C# type keyword for built-in types.
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types
if (type == typeof(void))
sb.Append("void");
else if (type == typeof(bool))
sb.Append("bool");
else if (type == typeof(byte))
sb.Append("byte");
else if (type == typeof(sbyte))
sb.Append("sbyte");
else if (type == typeof(char))
sb.Append("char");
else if (type == typeof(decimal))
sb.Append("decimal");
else if (type == typeof(double))
sb.Append("double");
else if (type == typeof(float))
sb.Append("float");
else if (type == typeof(int))
sb.Append("int");
else if (type == typeof(uint))
sb.Append("uint");
else if (type == typeof(nint))
sb.Append("nint");
else if (type == typeof(nuint))
sb.Append("nuint");
else if (type == typeof(long))
sb.Append("long");
else if (type == typeof(ulong))
sb.Append("ulong");
else if (type == typeof(short))
sb.Append("short");
else if (type == typeof(ushort))
sb.Append("ushort");
else if (type == typeof(object))
sb.Append("object");
else if (type == typeof(string))
sb.Append("string");
else
sb.Append(type);
}
internal static void InstallTraceListener()
{
Trace.Listeners.Clear();
Trace.Listeners.Add(new GodotTraceListener());
}
#pragma warning disable IDE1006 // Naming rule violation
// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Sequential)]
internal ref struct godot_stack_info
{
public godot_string File;
public godot_string Func;
public int Line;
}
// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Sequential)]
internal ref struct godot_stack_info_vector
{
private IntPtr _writeProxy;
private unsafe godot_stack_info* _ptr;
public readonly unsafe godot_stack_info* Elements
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _ptr;
}
public void Resize(int size)
{
ArgumentOutOfRangeException.ThrowIfNegative(size);
var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size);
if (err != Error.Ok)
throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString());
}
public unsafe void Dispose()
{
if (_ptr == null)
return;
NativeFuncs.godotsharp_stack_info_vector_destroy(ref this);
_ptr = null;
}
}
#pragma warning restore IDE1006
internal static unsafe StackFrame? GetCurrentStackFrame(int skipFrames = 0)
{
// We skip 2 frames:
// The first skipped frame is the current method.
// The second skipped frame is a method in NativeInterop.NativeFuncs.
var stackTrace = new StackTrace(skipFrames: 2 + skipFrames, fNeedFileInfo: true);
return stackTrace.GetFrame(0);
}
[UnmanagedCallersOnly]
internal static unsafe void GetCurrentStackInfo(void* destVector)
{
try
{
var vector = (godot_stack_info_vector*)destVector;
// We skip 2 frames:
// The first skipped frame is the current method.
// The second skipped frame is a method in NativeInterop.NativeFuncs.
var stackTrace = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
int frameCount = stackTrace.FrameCount;
if (frameCount == 0)
return;
vector->Resize(frameCount);
int i = 0;
foreach (StackFrame frame in stackTrace.GetFrames())
{
var method = frame.GetMethod();
if (method is MethodInfo methodInfo && methodInfo.IsDefined(typeof(StackTraceHiddenAttribute)))
{
// Skip methods marked hidden from the stack trace.
continue;
}
string? fileName = frame.GetFileName();
int fileLineNumber = frame.GetFileLineNumber();
GetStackFrameMethodDecl(frame, out string methodDecl);
godot_stack_info* stackInfo = &vector->Elements[i];
// Assign directly to element in Vector. This way we don't need to worry
// about disposal if an exception is thrown. The Vector takes care of it.
stackInfo->File = Marshaling.ConvertStringToNative(fileName);
stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl);
stackInfo->Line = fileLineNumber;
i++;
}
// Resize the vector again in case we skipped some frames.
vector->Resize(i);
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl)
{
MethodBase? methodBase = frame.GetMethod();
if (methodBase == null)
{
methodDecl = string.Empty;
return;
}
var sb = new StringBuilder();
if (methodBase is MethodInfo methodInfo)
{
sb.AppendTypeName(methodInfo.ReturnType);
sb.Append(' ');
}
sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>");
sb.Append('.');
sb.Append(methodBase.Name);
if (methodBase.IsGenericMethod)
{
Type[] genericParams = methodBase.GetGenericArguments();
sb.Append('<');
for (int j = 0; j < genericParams.Length; j++)
{
if (j > 0)
sb.Append(", ");
sb.AppendTypeName(genericParams[j]);
}
sb.Append('>');
}
sb.Append('(');
bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0;
ParameterInfo[] parameter = methodBase.GetParameters();
for (int i = 0; i < parameter.Length; i++)
{
if (i > 0)
sb.Append(", ");
if (i == parameter.Length - 1 && varArgs)
sb.Append("params ");
sb.AppendTypeName(parameter[i].ParameterType);
}
sb.Append(')');
methodDecl = sb.ToString();
}
}
}

View File

@@ -0,0 +1,898 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
{
internal static class DelegateUtils
{
[UnmanagedCallersOnly]
internal static godot_bool DelegateEquals(IntPtr delegateGCHandleA, IntPtr delegateGCHandleB)
{
try
{
var @delegateA = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleA).Target;
var @delegateB = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleB).Target;
return (@delegateA! == @delegateB!).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static int DelegateHash(IntPtr delegateGCHandle)
{
try
{
var @delegate = (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target;
return @delegate?.GetHashCode() ?? 0;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return 0;
}
}
[UnmanagedCallersOnly]
internal static unsafe int GetArgumentCount(IntPtr delegateGCHandle, godot_bool* outIsValid)
{
try
{
var @delegate = (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target;
int? argCount = @delegate?.Method?.GetParameters().Length;
if (argCount is null)
{
*outIsValid = godot_bool.False;
return 0;
}
*outIsValid = godot_bool.True;
return argCount.Value;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outIsValid = godot_bool.False;
return 0;
}
}
[UnmanagedCallersOnly]
internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline,
godot_variant** args, int argc, godot_variant* outRet)
{
try
{
if (trampoline == null)
{
throw new ArgumentNullException(nameof(trampoline),
"Cannot dynamically invoke delegate because the trampoline is null.");
}
var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
var trampolineFn = (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline;
trampolineFn(@delegate, new NativeVariantPtrArgs(args, argc), out godot_variant ret);
*outRet = ret;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outRet = default;
}
}
// TODO: Check if we should be using BindingFlags.DeclaredOnly (would give better reflection performance).
private enum TargetKind : uint
{
Static,
GodotObject,
CompilerGenerated
}
internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
{
if (@delegate is null)
{
return false;
}
if (@delegate is MulticastDelegate multicastDelegate)
{
bool someDelegatesSerialized = false;
Delegate[] invocationList = multicastDelegate.GetInvocationList();
if (invocationList.Length > 1)
{
var multiCastData = new Collections.Array();
foreach (Delegate oneDelegate in invocationList)
someDelegatesSerialized |= TrySerializeDelegate(oneDelegate, multiCastData);
if (!someDelegatesSerialized)
return false;
serializedData.Add(multiCastData);
return true;
}
}
if (TrySerializeSingleDelegate(@delegate, out byte[]? buffer))
{
serializedData.Add((Span<byte>)buffer);
return true;
}
return false;
}
private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer)
{
buffer = null;
object? target = @delegate.Target;
switch (target)
{
case null:
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write((ulong)TargetKind.Static);
SerializeType(writer, @delegate.GetType());
if (!TrySerializeMethodInfo(writer, @delegate.Method))
return false;
buffer = stream.ToArray();
return true;
}
}
case GodotObject godotObject:
{
if (!GodotObject.IsInstanceValid(godotObject))
{
// If the delegate's target has been freed we can't serialize it.
return false;
}
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write((ulong)TargetKind.GodotObject);
// ReSharper disable once RedundantCast
writer.Write((ulong)godotObject.GetInstanceId());
SerializeType(writer, @delegate.GetType());
if (!TrySerializeMethodInfo(writer, @delegate.Method))
return false;
buffer = stream.ToArray();
return true;
}
}
default:
{
Type targetType = target.GetType();
if (targetType.IsDefined(typeof(CompilerGeneratedAttribute), true))
{
// Compiler generated. Probably a closure. Try to serialize it.
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
writer.Write((ulong)TargetKind.CompilerGenerated);
SerializeType(writer, targetType);
SerializeType(writer, @delegate.GetType());
if (!TrySerializeMethodInfo(writer, @delegate.Method))
return false;
FieldInfo[] fields = targetType.GetFields(BindingFlags.Instance | BindingFlags.Public);
writer.Write(fields.Length);
foreach (FieldInfo field in fields)
{
Type fieldType = field.FieldType;
Variant.Type variantType = GD.TypeToVariantType(fieldType);
if (variantType == Variant.Type.Nil)
return false;
static byte[] VarToBytes(in godot_variant var)
{
NativeFuncs.godotsharp_var_to_bytes(var, godot_bool.True, out var varBytes);
using (varBytes)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes);
}
writer.Write(field.Name);
var fieldValue = field.GetValue(target);
using var fieldValueVariant = RuntimeTypeConversionHelper.ConvertToVariant(fieldValue);
byte[] valueBuffer = VarToBytes(fieldValueVariant);
writer.Write(valueBuffer.Length);
writer.Write(valueBuffer);
}
buffer = stream.ToArray();
return true;
}
}
return false;
}
}
}
private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
{
SerializeType(writer, methodInfo.DeclaringType);
writer.Write(methodInfo.Name);
int flags = 0;
if (methodInfo.IsPublic)
flags |= (int)BindingFlags.Public;
else
flags |= (int)BindingFlags.NonPublic;
if (methodInfo.IsStatic)
flags |= (int)BindingFlags.Static;
else
flags |= (int)BindingFlags.Instance;
writer.Write(flags);
Type returnType = methodInfo.ReturnType;
bool hasReturn = methodInfo.ReturnType != typeof(void);
writer.Write(hasReturn);
if (hasReturn)
SerializeType(writer, returnType);
ParameterInfo[] parameters = methodInfo.GetParameters();
writer.Write(parameters.Length);
if (parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
SerializeType(writer, parameters[i].ParameterType);
}
return true;
}
private static void SerializeType(BinaryWriter writer, Type? type)
{
if (type == null)
{
int genericArgumentsCount = -1;
writer.Write(genericArgumentsCount);
}
else if (type.IsGenericType)
{
Type genericTypeDef = type.GetGenericTypeDefinition();
Type[] genericArgs = type.GetGenericArguments();
int genericArgumentsCount = genericArgs.Length;
writer.Write(genericArgumentsCount);
writer.Write(genericTypeDef.Assembly.GetName().Name ?? "");
writer.Write(genericTypeDef.FullName ?? genericTypeDef.ToString());
for (int i = 0; i < genericArgs.Length; i++)
SerializeType(writer, genericArgs[i]);
}
else
{
int genericArgumentsCount = 0;
writer.Write(genericArgumentsCount);
writer.Write(type.Assembly.GetName().Name ?? "");
writer.Write(type.FullName ?? type.ToString());
}
}
[UnmanagedCallersOnly]
internal static unsafe godot_bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle,
godot_array* nSerializedData)
{
try
{
var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
return TrySerializeDelegate(@delegate, serializedData)
.ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
return godot_bool.False;
}
}
[UnmanagedCallersOnly]
internal static unsafe godot_bool TryDeserializeDelegateWithGCHandle(godot_array* nSerializedData,
IntPtr* delegateGCHandle)
{
try
{
var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
if (TryDeserializeDelegate(serializedData, out Delegate? @delegate))
{
*delegateGCHandle = GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(@delegate));
return godot_bool.True;
}
else
{
*delegateGCHandle = IntPtr.Zero;
return godot_bool.False;
}
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*delegateGCHandle = default;
return godot_bool.False;
}
}
internal static bool TryDeserializeDelegate(Collections.Array serializedData,
[MaybeNullWhen(false)] out Delegate @delegate)
{
@delegate = null;
if (serializedData.Count == 1)
{
var elem = serializedData[0].Obj;
if (elem == null)
return false;
if (elem is Collections.Array multiCastData)
return TryDeserializeDelegate(multiCastData, out @delegate);
return TryDeserializeSingleDelegate((byte[])elem, out @delegate);
}
var delegates = new List<Delegate>(serializedData.Count);
foreach (Variant variantElem in serializedData)
{
var elem = variantElem.Obj;
if (elem == null)
continue;
if (elem is Collections.Array multiCastData)
{
if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate))
delegates.Add(oneDelegate);
}
else
{
if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate))
delegates.Add(oneDelegate);
}
}
if (delegates.Count <= 0)
return false;
@delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!;
return true;
}
private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate)
{
@delegate = null;
using (var stream = new MemoryStream(buffer, writable: false))
using (var reader = new BinaryReader(stream))
{
var targetKind = (TargetKind)reader.ReadUInt64();
switch (targetKind)
{
case TargetKind.Static:
{
Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
return false;
@delegate = Delegate.CreateDelegate(delegateType, null, methodInfo, throwOnBindFailure: false);
if (@delegate == null)
return false;
return true;
}
case TargetKind.GodotObject:
{
ulong objectId = reader.ReadUInt64();
GodotObject? godotObject = GodotObject.InstanceFromId(objectId);
if (godotObject == null)
return false;
Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
return false;
@delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo,
throwOnBindFailure: false);
if (@delegate == null)
return false;
return true;
}
case TargetKind.CompilerGenerated:
{
Type? targetType = DeserializeType(reader);
if (targetType == null)
return false;
Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
return false;
int fieldCount = reader.ReadInt32();
object recreatedTarget = Activator.CreateInstance(targetType)!;
for (int i = 0; i < fieldCount; i++)
{
string name = reader.ReadString();
int valueBufferLength = reader.ReadInt32();
byte[] valueBuffer = reader.ReadBytes(valueBufferLength);
FieldInfo? fieldInfo = targetType.GetField(name,
BindingFlags.Instance | BindingFlags.Public);
if (fieldInfo != null)
{
var variantValue = GD.BytesToVarWithObjects(valueBuffer);
object? managedValue = RuntimeTypeConversionHelper.ConvertToObjectOfType(
(godot_variant)variantValue.NativeVar, fieldInfo.FieldType);
fieldInfo.SetValue(recreatedTarget, managedValue);
}
}
@delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo,
throwOnBindFailure: false);
if (@delegate == null)
return false;
return true;
}
default:
return false;
}
}
}
private static bool TryDeserializeMethodInfo(BinaryReader reader,
[MaybeNullWhen(false)] out MethodInfo methodInfo)
{
methodInfo = null;
Type? declaringType = DeserializeType(reader);
if (declaringType == null)
return false;
string methodName = reader.ReadString();
BindingFlags flags = (BindingFlags)reader.ReadInt32();
bool hasReturn = reader.ReadBoolean();
Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void);
int parametersCount = reader.ReadInt32();
var parameterTypes = parametersCount == 0 ? Type.EmptyTypes : new Type[parametersCount];
for (int i = 0; i < parametersCount; i++)
{
Type? parameterType = DeserializeType(reader);
if (parameterType == null)
return false;
parameterTypes[i] = parameterType;
}
#pragma warning disable REFL045 // These flags are insufficient to match any members
// TODO: Suppressing invalid warning, remove when issue is fixed
// https://github.com/DotNetAnalyzers/ReflectionAnalyzers/issues/209
methodInfo = declaringType.GetMethod(methodName, flags, null, parameterTypes, null);
#pragma warning restore REFL045
return methodInfo != null && methodInfo.ReturnType == returnType;
}
private static Type? DeserializeType(BinaryReader reader)
{
int genericArgumentsCount = reader.ReadInt32();
if (genericArgumentsCount == -1)
return null;
string assemblyName = reader.ReadString();
if (assemblyName.Length == 0)
{
GD.PushError($"Missing assembly name of type when attempting to deserialize delegate");
return null;
}
string typeFullName = reader.ReadString();
var type = ReflectionUtils.FindTypeInLoadedAssemblies(assemblyName, typeFullName);
if (type == null)
return null; // Type not found
if (genericArgumentsCount != 0)
{
var genericArgumentTypes = new Type[genericArgumentsCount];
for (int i = 0; i < genericArgumentsCount; i++)
{
Type? genericArgumentType = DeserializeType(reader);
if (genericArgumentType == null)
return null;
genericArgumentTypes[i] = genericArgumentType;
}
type = type.MakeGenericType(genericArgumentTypes);
}
return type;
}
// Returns true, if unloading the delegate is necessary for assembly unloading to succeed.
// This check is not perfect and only intended to prevent things in GodotTools from being reloaded.
internal static bool IsDelegateCollectible(Delegate @delegate)
{
if (@delegate.GetType().IsCollectible)
return true;
if (@delegate is MulticastDelegate multicastDelegate)
{
Delegate[] invocationList = multicastDelegate.GetInvocationList();
if (invocationList.Length > 1)
{
foreach (Delegate oneDelegate in invocationList)
if (IsDelegateCollectible(oneDelegate))
return true;
return false;
}
}
if (@delegate.Method.IsCollectible)
return true;
object? target = @delegate.Target;
if (target is not null && target.GetType().IsCollectible)
return true;
return false;
}
internal static class RuntimeTypeConversionHelper
{
public static godot_variant ConvertToVariant(object? obj)
{
if (obj == null)
return default;
switch (obj)
{
case bool @bool:
return VariantUtils.CreateFrom(@bool);
case char @char:
return VariantUtils.CreateFrom(@char);
case sbyte int8:
return VariantUtils.CreateFrom(int8);
case short int16:
return VariantUtils.CreateFrom(int16);
case int int32:
return VariantUtils.CreateFrom(int32);
case long int64:
return VariantUtils.CreateFrom(int64);
case byte uint8:
return VariantUtils.CreateFrom(uint8);
case ushort uint16:
return VariantUtils.CreateFrom(uint16);
case uint uint32:
return VariantUtils.CreateFrom(uint32);
case ulong uint64:
return VariantUtils.CreateFrom(uint64);
case float @float:
return VariantUtils.CreateFrom(@float);
case double @double:
return VariantUtils.CreateFrom(@double);
case Vector2 vector2:
return VariantUtils.CreateFrom(vector2);
case Vector2I vector2I:
return VariantUtils.CreateFrom(vector2I);
case Rect2 rect2:
return VariantUtils.CreateFrom(rect2);
case Rect2I rect2I:
return VariantUtils.CreateFrom(rect2I);
case Transform2D transform2D:
return VariantUtils.CreateFrom(transform2D);
case Vector3 vector3:
return VariantUtils.CreateFrom(vector3);
case Vector3I vector3I:
return VariantUtils.CreateFrom(vector3I);
case Vector4 vector4:
return VariantUtils.CreateFrom(vector4);
case Vector4I vector4I:
return VariantUtils.CreateFrom(vector4I);
case Basis basis:
return VariantUtils.CreateFrom(basis);
case Quaternion quaternion:
return VariantUtils.CreateFrom(quaternion);
case Transform3D transform3D:
return VariantUtils.CreateFrom(transform3D);
case Projection projection:
return VariantUtils.CreateFrom(projection);
case Aabb aabb:
return VariantUtils.CreateFrom(aabb);
case Color color:
return VariantUtils.CreateFrom(color);
case Plane plane:
return VariantUtils.CreateFrom(plane);
case Callable callable:
return VariantUtils.CreateFrom(callable);
case Signal signal:
return VariantUtils.CreateFrom(signal);
case string @string:
return VariantUtils.CreateFrom(@string);
case byte[] byteArray:
return VariantUtils.CreateFrom(byteArray);
case int[] int32Array:
return VariantUtils.CreateFrom(int32Array);
case long[] int64Array:
return VariantUtils.CreateFrom(int64Array);
case float[] floatArray:
return VariantUtils.CreateFrom(floatArray);
case double[] doubleArray:
return VariantUtils.CreateFrom(doubleArray);
case string[] stringArray:
return VariantUtils.CreateFrom(stringArray);
case Vector2[] vector2Array:
return VariantUtils.CreateFrom(vector2Array);
case Vector3[] vector3Array:
return VariantUtils.CreateFrom(vector3Array);
case Color[] colorArray:
return VariantUtils.CreateFrom(colorArray);
case StringName[] stringNameArray:
return VariantUtils.CreateFrom(stringNameArray);
case NodePath[] nodePathArray:
return VariantUtils.CreateFrom(nodePathArray);
case Rid[] ridArray:
return VariantUtils.CreateFrom(ridArray);
case GodotObject[] godotObjectArray:
return VariantUtils.CreateFrom(godotObjectArray);
case StringName stringName:
return VariantUtils.CreateFrom(stringName);
case NodePath nodePath:
return VariantUtils.CreateFrom(nodePath);
case Rid rid:
return VariantUtils.CreateFrom(rid);
case Collections.Dictionary godotDictionary:
return VariantUtils.CreateFrom(godotDictionary);
case Collections.Array godotArray:
return VariantUtils.CreateFrom(godotArray);
case Variant variant:
return VariantUtils.CreateFrom(variant);
case GodotObject godotObject:
return VariantUtils.CreateFrom(godotObject);
case Enum @enum:
return VariantUtils.CreateFrom(Convert.ToInt64(@enum, CultureInfo.InvariantCulture));
case Collections.IGenericGodotDictionary godotDictionary:
return VariantUtils.CreateFrom(godotDictionary.UnderlyingDictionary);
case Collections.IGenericGodotArray godotArray:
return VariantUtils.CreateFrom(godotArray.UnderlyingArray);
}
GD.PushError("Attempted to convert an unmarshallable managed type to Variant. Name: '" +
obj.GetType().FullName + ".");
return new godot_variant();
}
private delegate object? ConvertToSystemObjectFunc(in godot_variant managed);
private static readonly System.Collections.Generic.Dictionary<Type, ConvertToSystemObjectFunc>
_toSystemObjectFuncByType = new()
{
[typeof(bool)] = (in godot_variant variant) => VariantUtils.ConvertTo<bool>(variant),
[typeof(char)] = (in godot_variant variant) => VariantUtils.ConvertTo<char>(variant),
[typeof(sbyte)] = (in godot_variant variant) => VariantUtils.ConvertTo<sbyte>(variant),
[typeof(short)] = (in godot_variant variant) => VariantUtils.ConvertTo<short>(variant),
[typeof(int)] = (in godot_variant variant) => VariantUtils.ConvertTo<int>(variant),
[typeof(long)] = (in godot_variant variant) => VariantUtils.ConvertTo<long>(variant),
[typeof(byte)] = (in godot_variant variant) => VariantUtils.ConvertTo<byte>(variant),
[typeof(ushort)] = (in godot_variant variant) => VariantUtils.ConvertTo<ushort>(variant),
[typeof(uint)] = (in godot_variant variant) => VariantUtils.ConvertTo<uint>(variant),
[typeof(ulong)] = (in godot_variant variant) => VariantUtils.ConvertTo<ulong>(variant),
[typeof(float)] = (in godot_variant variant) => VariantUtils.ConvertTo<float>(variant),
[typeof(double)] = (in godot_variant variant) => VariantUtils.ConvertTo<double>(variant),
[typeof(Vector2)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2>(variant),
[typeof(Vector2I)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2I>(variant),
[typeof(Rect2)] = (in godot_variant variant) => VariantUtils.ConvertTo<Rect2>(variant),
[typeof(Rect2I)] = (in godot_variant variant) => VariantUtils.ConvertTo<Rect2I>(variant),
[typeof(Transform2D)] = (in godot_variant variant) => VariantUtils.ConvertTo<Transform2D>(variant),
[typeof(Vector3)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3>(variant),
[typeof(Vector3I)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3I>(variant),
[typeof(Basis)] = (in godot_variant variant) => VariantUtils.ConvertTo<Basis>(variant),
[typeof(Quaternion)] = (in godot_variant variant) => VariantUtils.ConvertTo<Quaternion>(variant),
[typeof(Transform3D)] = (in godot_variant variant) => VariantUtils.ConvertTo<Transform3D>(variant),
[typeof(Vector4)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector4>(variant),
[typeof(Vector4I)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector4I>(variant),
[typeof(Aabb)] = (in godot_variant variant) => VariantUtils.ConvertTo<Aabb>(variant),
[typeof(Color)] = (in godot_variant variant) => VariantUtils.ConvertTo<Color>(variant),
[typeof(Plane)] = (in godot_variant variant) => VariantUtils.ConvertTo<Plane>(variant),
[typeof(Callable)] = (in godot_variant variant) => VariantUtils.ConvertTo<Callable>(variant),
[typeof(Signal)] = (in godot_variant variant) => VariantUtils.ConvertTo<Signal>(variant),
[typeof(string)] = (in godot_variant variant) => VariantUtils.ConvertTo<string>(variant),
[typeof(byte[])] = (in godot_variant variant) => VariantUtils.ConvertTo<byte[]>(variant),
[typeof(int[])] = (in godot_variant variant) => VariantUtils.ConvertTo<int[]>(variant),
[typeof(long[])] = (in godot_variant variant) => VariantUtils.ConvertTo<long[]>(variant),
[typeof(float[])] = (in godot_variant variant) => VariantUtils.ConvertTo<float[]>(variant),
[typeof(double[])] = (in godot_variant variant) => VariantUtils.ConvertTo<double[]>(variant),
[typeof(string[])] = (in godot_variant variant) => VariantUtils.ConvertTo<string[]>(variant),
[typeof(Vector2[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2[]>(variant),
[typeof(Vector3[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3[]>(variant),
[typeof(Color[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Color[]>(variant),
[typeof(StringName[])] =
(in godot_variant variant) => VariantUtils.ConvertTo<StringName[]>(variant),
[typeof(NodePath[])] = (in godot_variant variant) => VariantUtils.ConvertTo<NodePath[]>(variant),
[typeof(Rid[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Rid[]>(variant),
[typeof(StringName)] = (in godot_variant variant) => VariantUtils.ConvertTo<StringName>(variant),
[typeof(NodePath)] = (in godot_variant variant) => VariantUtils.ConvertTo<NodePath>(variant),
[typeof(Rid)] = (in godot_variant variant) => VariantUtils.ConvertTo<Rid>(variant),
[typeof(Godot.Collections.Dictionary)] = (in godot_variant variant) =>
VariantUtils.ConvertTo<Godot.Collections.Dictionary>(variant),
[typeof(Godot.Collections.Array)] =
(in godot_variant variant) => VariantUtils.ConvertTo<Godot.Collections.Array>(variant),
[typeof(Variant)] = (in godot_variant variant) => VariantUtils.ConvertTo<Variant>(variant),
};
public static object? ConvertToObjectOfType(in godot_variant variant, Type type)
{
if (_toSystemObjectFuncByType.TryGetValue(type, out var func))
return func(variant);
if (typeof(GodotObject).IsAssignableFrom(type))
return VariantUtils.ConvertTo<GodotObject>(variant);
if (typeof(GodotObject[]).IsAssignableFrom(type))
{
static GodotObject[] ConvertToSystemArrayOfGodotObject(in godot_array nativeArray, Type type)
{
var array = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(nativeArray));
int length = array.Count;
var ret = (GodotObject[])Activator.CreateInstance(type, length)!;
for (int i = 0; i < length; i++)
ret[i] = array[i].AsGodotObject();
return ret;
}
using var godotArray = NativeFuncs.godotsharp_variant_as_array(variant);
return ConvertToSystemArrayOfGodotObject(godotArray, type);
}
if (type.IsEnum)
{
var enumUnderlyingType = type.GetEnumUnderlyingType();
switch (Type.GetTypeCode(enumUnderlyingType))
{
case TypeCode.SByte:
return Enum.ToObject(type, VariantUtils.ConvertToInt8(variant));
case TypeCode.Int16:
return Enum.ToObject(type, VariantUtils.ConvertToInt16(variant));
case TypeCode.Int32:
return Enum.ToObject(type, VariantUtils.ConvertToInt32(variant));
case TypeCode.Int64:
return Enum.ToObject(type, VariantUtils.ConvertToInt64(variant));
case TypeCode.Byte:
return Enum.ToObject(type, VariantUtils.ConvertToUInt8(variant));
case TypeCode.UInt16:
return Enum.ToObject(type, VariantUtils.ConvertToUInt16(variant));
case TypeCode.UInt32:
return Enum.ToObject(type, VariantUtils.ConvertToUInt32(variant));
case TypeCode.UInt64:
return Enum.ToObject(type, VariantUtils.ConvertToUInt64(variant));
default:
{
GD.PushError(
"Attempted to convert Variant to enum value of unsupported underlying type. Name: " +
type.FullName + " : " + enumUnderlyingType.FullName + ".");
return null;
}
}
}
if (type.IsGenericType)
{
var genericTypeDef = type.GetGenericTypeDefinition();
if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>))
{
var ctor = type.GetConstructor(new[] { typeof(Godot.Collections.Dictionary) });
if (ctor == null)
throw new InvalidOperationException("Dictionary constructor not found");
return ctor.Invoke(new object?[]
{
VariantUtils.ConvertTo<Godot.Collections.Dictionary>(variant)
});
}
if (genericTypeDef == typeof(Godot.Collections.Array<>))
{
var ctor = type.GetConstructor(new[] { typeof(Godot.Collections.Array) });
if (ctor == null)
throw new InvalidOperationException("Array constructor not found");
return ctor.Invoke(new object?[]
{
VariantUtils.ConvertTo<Godot.Collections.Array>(variant)
});
}
}
GD.PushError($"Attempted to convert Variant to unsupported type. Name: {type.FullName}.");
return null;
}
}
}
}

View File

@@ -0,0 +1,969 @@
using System;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
using System.Diagnostics;
#nullable enable
namespace Godot.Collections
{
/// <summary>
/// Wrapper around Godot's Dictionary class, a dictionary of Variant
/// typed elements allocated in the engine in C++. Useful when
/// interfacing with the engine.
/// </summary>
[DebuggerTypeProxy(typeof(DictionaryDebugView<Variant, Variant>))]
[DebuggerDisplay("Count = {Count}")]
public sealed class Dictionary :
IDictionary<Variant, Variant>,
IReadOnlyDictionary<Variant, Variant>,
IDisposable
{
internal godot_dictionary.movable NativeValue;
private WeakReference<IDisposable>? _weakReferenceToSelf;
/// <summary>
/// Constructs a new empty <see cref="Dictionary"/>.
/// </summary>
public Dictionary()
{
NativeValue = (godot_dictionary.movable)NativeFuncs.godotsharp_dictionary_new();
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
private Dictionary(godot_dictionary nativeValueToOwn)
{
NativeValue = (godot_dictionary.movable)(nativeValueToOwn.IsAllocated ?
nativeValueToOwn :
NativeFuncs.godotsharp_dictionary_new());
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
internal static Dictionary CreateTakingOwnershipOfDisposableValue(godot_dictionary nativeValueToOwn)
=> new Dictionary(nativeValueToOwn);
~Dictionary()
{
Dispose(false);
}
/// <summary>
/// Disposes of this <see cref="Dictionary"/>.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
}
/// <summary>
/// Returns a copy of the <see cref="Dictionary"/>.
/// If <paramref name="deep"/> is <see langword="true"/>, a deep copy is performed:
/// all nested arrays and dictionaries are duplicated and will not be shared with
/// the original dictionary. If <see langword="false"/>, a shallow copy is made and
/// references to the original nested arrays and dictionaries are kept, so that
/// modifying a sub-array or dictionary in the copy will also impact those
/// referenced in the source dictionary. Note that any <see cref="GodotObject"/> derived
/// elements will be shallow copied regardless of the <paramref name="deep"/>
/// setting.
/// </summary>
/// <param name="deep">If <see langword="true"/>, performs a deep copy.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary Duplicate(bool deep = false)
{
godot_dictionary newDictionary;
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_duplicate(ref self, deep.ToGodotBool(), out newDictionary);
return CreateTakingOwnershipOfDisposableValue(newDictionary);
}
/// <summary>
/// Adds entries from <paramref name="dictionary"/> to this dictionary.
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary dictionary, bool overwrite = false)
{
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue;
var other = (godot_dictionary)dictionary.NativeValue;
NativeFuncs.godotsharp_dictionary_merge(ref self, in other, overwrite.ToGodotBool());
}
/// <summary>
/// Compares this <see cref="Dictionary"/> against the <paramref name="other"/>
/// <see cref="Dictionary"/> recursively. Returns <see langword="true"/> if the
/// two dictionaries contain the same keys and values. The order of the entries
/// does not matter.
/// otherwise.
/// </summary>
/// <param name="other">The other dictionary to compare against.</param>
/// <returns>
/// <see langword="true"/> if the dictionaries contain the same keys and values,
/// <see langword="false"/> otherwise.
/// </returns>
public bool RecursiveEqual(Dictionary other)
{
var self = (godot_dictionary)NativeValue;
var otherVariant = (godot_dictionary)other.NativeValue;
return NativeFuncs.godotsharp_dictionary_recursive_equal(ref self, otherVariant).ToBool();
}
// IDictionary
/// <summary>
/// Gets the collection of keys in this <see cref="Dictionary"/>.
/// </summary>
public ICollection<Variant> Keys
{
get
{
godot_array keysArray;
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray);
return Array.CreateTakingOwnershipOfDisposableValue(keysArray);
}
}
/// <summary>
/// Gets the collection of elements in this <see cref="Dictionary"/>.
/// </summary>
public ICollection<Variant> Values
{
get
{
godot_array valuesArray;
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray);
return Array.CreateTakingOwnershipOfDisposableValue(valuesArray);
}
}
IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Keys => Keys;
IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Values => Values;
private (Array keys, Array values, int count) GetKeyValuePairs()
{
var self = (godot_dictionary)NativeValue;
godot_array keysArray;
NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray);
var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray);
godot_array valuesArray;
NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray);
var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray);
int count = NativeFuncs.godotsharp_dictionary_count(ref self);
return (keys, values, count);
}
/// <summary>
/// Returns the value at the given <paramref name="key"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the dictionary is read-only.
/// </exception>
/// <exception cref="KeyNotFoundException">
/// The property is retrieved and an entry for <paramref name="key"/>
/// does not exist in the dictionary.
/// </exception>
/// <value>The value at the given <paramref name="key"/>.</value>
public Variant this[Variant key]
{
get
{
var self = (godot_dictionary)NativeValue;
if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
(godot_variant)key.NativeVar, out godot_variant value).ToBool())
{
return Variant.CreateTakingOwnershipOfDisposableValue(value);
}
else
{
throw new KeyNotFoundException();
}
}
set
{
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self,
(godot_variant)key.NativeVar, (godot_variant)value.NativeVar);
}
}
/// <summary>
/// Adds an value <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <exception cref="ArgumentException">
/// An entry for <paramref name="key"/> already exists in the dictionary.
/// </exception>
/// <param name="key">The key at which to add the value.</param>
/// <param name="value">The value to add.</param>
public void Add(Variant key, Variant value)
{
ThrowIfReadOnly();
var variantKey = (godot_variant)key.NativeVar;
var self = (godot_dictionary)NativeValue;
if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool())
throw new ArgumentException("An element with the same key already exists.", nameof(key));
godot_variant variantValue = (godot_variant)value.NativeVar;
NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue);
}
void ICollection<KeyValuePair<Variant, Variant>>.Add(KeyValuePair<Variant, Variant> item)
=> Add(item.Key, item.Value);
/// <summary>
/// Clears the dictionary, removing all entries from it.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
public void Clear()
{
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_clear(ref self);
}
/// <summary>
/// Checks if this <see cref="Dictionary"/> contains the given key.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>Whether or not this dictionary contains the given key.</returns>
public bool ContainsKey(Variant key)
{
var self = (godot_dictionary)NativeValue;
return NativeFuncs.godotsharp_dictionary_contains_key(ref self, (godot_variant)key.NativeVar).ToBool();
}
bool ICollection<KeyValuePair<Variant, Variant>>.Contains(KeyValuePair<Variant, Variant> item)
{
godot_variant variantKey = (godot_variant)item.Key.NativeVar;
var self = (godot_dictionary)NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
{
if (!found)
return false;
godot_variant variantValue = (godot_variant)item.Value.NativeVar;
return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool();
}
}
/// <summary>
/// Removes an element from this <see cref="Dictionary"/> by key.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="key">The key of the element to remove.</param>
public bool Remove(Variant key)
{
ThrowIfReadOnly();
var self = (godot_dictionary)NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool();
}
bool ICollection<KeyValuePair<Variant, Variant>>.Remove(KeyValuePair<Variant, Variant> item)
{
ThrowIfReadOnly();
godot_variant variantKey = (godot_variant)item.Key.NativeVar;
var self = (godot_dictionary)NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
{
if (!found)
return false;
godot_variant variantValue = (godot_variant)item.Value.NativeVar;
if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool())
{
return NativeFuncs.godotsharp_dictionary_remove_key(
ref self, variantKey).ToBool();
}
return false;
}
}
/// <summary>
/// Returns the number of elements in this <see cref="Dictionary"/>.
/// This is also known as the size or length of the dictionary.
/// </summary>
/// <returns>The number of elements.</returns>
public int Count
{
get
{
var self = (godot_dictionary)NativeValue;
return NativeFuncs.godotsharp_dictionary_count(ref self);
}
}
/// <summary>
/// Returns <see langword="true"/> if the dictionary is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => NativeValue.DangerousSelfRef.IsReadOnly;
/// <summary>
/// Makes the <see cref="Dictionary"/> read-only, i.e. disabled modying of the
/// dictionary's elements. Does not apply to nested content, e.g. content of
/// nested dictionaries.
/// </summary>
public void MakeReadOnly()
{
if (IsReadOnly)
{
// Avoid interop call when the dictionary is already read-only.
return;
}
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_make_read_only(ref self);
}
/// <summary>
/// Gets the value for the given <paramref name="key"/> in the dictionary.
/// Returns <see langword="true"/> if an entry for the given key exists in
/// the dictionary; otherwise, returns <see langword="false"/>.
/// </summary>
/// <param name="key">The key of the element to get.</param>
/// <param name="value">The value at the given <paramref name="key"/>.</param>
/// <returns>If an entry was found for the given <paramref name="key"/>.</returns>
public bool TryGetValue(Variant key, out Variant value)
{
var self = (godot_dictionary)NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
(godot_variant)key.NativeVar, out godot_variant retValue).ToBool();
value = found ? Variant.CreateTakingOwnershipOfDisposableValue(retValue) : default;
return found;
}
/// <summary>
/// Copies the elements of this <see cref="Dictionary"/> to the given untyped
/// <see cref="KeyValuePair{TKey, TValue}"/> array, starting at the given index.
/// </summary>
/// <exception cref="ArgumentNullException">
/// The <paramref name="array"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
/// </exception>
/// <exception cref="ArgumentException">
/// The destination array was not long enough.
/// </exception>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
void ICollection<KeyValuePair<Variant, Variant>>.CopyTo(KeyValuePair<Variant, Variant>[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException(nameof(array), "Value cannot be null.");
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException(nameof(arrayIndex),
"Number was less than the array's lower bound in the first dimension.");
var (keys, values, count) = GetKeyValuePairs();
if (array.Length < (arrayIndex + count))
throw new ArgumentException(
"Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
for (int i = 0; i < count; i++)
{
array[arrayIndex] = new(keys[i], values[i]);
arrayIndex++;
}
}
// IEnumerable
/// <summary>
/// Gets an enumerator for this <see cref="Dictionary"/>.
/// </summary>
/// <returns>An enumerator.</returns>
public IEnumerator<KeyValuePair<Variant, Variant>> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return GetKeyValuePair(i);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private KeyValuePair<Variant, Variant> GetKeyValuePair(int index)
{
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index,
out godot_variant key,
out godot_variant value);
return new KeyValuePair<Variant, Variant>(Variant.CreateTakingOwnershipOfDisposableValue(key),
Variant.CreateTakingOwnershipOfDisposableValue(value));
}
/// <summary>
/// Converts this <see cref="Dictionary"/> to a string.
/// </summary>
/// <returns>A string representation of this dictionary.</returns>
public override string ToString()
{
var self = (godot_dictionary)NativeValue;
NativeFuncs.godotsharp_dictionary_to_string(ref self, out godot_string str);
using (str)
return Marshaling.ConvertStringToManaged(str);
}
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Dictionary instance is read-only.");
}
}
}
internal interface IGenericGodotDictionary
{
public Dictionary UnderlyingDictionary { get; }
}
/// <summary>
/// Typed wrapper around Godot's Dictionary class, a dictionary of <typeparamref name="TKey"/>
/// and <typeparamref name="TValue"/> annotated, Variant typed elements allocated in the engine in C++.
/// Useful when interfacing with the engine. Otherwise prefer .NET collections
/// such as <see cref="System.Collections.Generic.Dictionary{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TKey">The type of the dictionary's keys.</typeparam>
/// <typeparam name="TValue">The type of the dictionary's values.</typeparam>
/// <remarks>
/// While the elements are statically annotated to <typeparamref name="TKey"/> and <typeparamref name="TValue"/>,
/// the underlying dictionary still stores <see cref="Variant"/>, which has the same memory footprint per element
/// as an untyped <see cref="Dictionary"/>.
/// </remarks>
[DebuggerTypeProxy(typeof(DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[SuppressMessage("Design", "CA1001", MessageId = "Types that own disposable fields should be disposable",
Justification = "Known issue. Requires explicit refcount management to not dispose untyped collections.")]
public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> :
IDictionary<TKey, TValue>,
IReadOnlyDictionary<TKey, TValue>,
IGenericGodotDictionary
{
private static godot_variant ToVariantFunc(scoped in Dictionary<TKey, TValue> godotDictionary) =>
VariantUtils.CreateFromDictionary(godotDictionary);
private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
VariantUtils.ConvertToDictionary<TKey, TValue>(variant);
private void SetTypedForUnderlyingDictionary()
{
Marshaling.GetTypedCollectionParameterInfo<TKey>(out var keyVariantType, out var keyClassName, out var keyScriptRef);
Marshaling.GetTypedCollectionParameterInfo<TValue>(out var valueVariantType, out var valueClassName, out var valueScriptRef);
var self = (godot_dictionary)NativeValue;
using (keyScriptRef)
using (valueScriptRef)
{
NativeFuncs.godotsharp_dictionary_set_typed(
ref self,
(uint)keyVariantType,
keyClassName,
keyScriptRef,
(uint)valueVariantType,
valueClassName,
valueScriptRef);
}
}
static unsafe Dictionary()
{
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = ToVariantFunc;
VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.FromVariantCb = FromVariantFunc;
}
private readonly Dictionary _underlyingDict;
Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict;
internal ref godot_dictionary.movable NativeValue
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _underlyingDict.NativeValue;
}
/// <summary>
/// Constructs a new empty <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary()
{
_underlyingDict = new Dictionary();
SetTypedForUnderlyingDictionary();
}
/// <summary>
/// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements.
/// </summary>
/// <exception cref="ArgumentNullException">
/// The <paramref name="dictionary"/> is <see langword="null"/>.
/// </exception>
/// <param name="dictionary">The dictionary to construct from.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(IDictionary<TKey, TValue> dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);
_underlyingDict = new Dictionary();
SetTypedForUnderlyingDictionary();
foreach (KeyValuePair<TKey, TValue> entry in dictionary)
Add(entry.Key, entry.Value);
}
/// <summary>
/// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements.
/// </summary>
/// <exception cref="ArgumentNullException">
/// The <paramref name="dictionary"/> is <see langword="null"/>.
/// </exception>
/// <param name="dictionary">The dictionary to construct from.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(Dictionary dictionary)
{
ArgumentNullException.ThrowIfNull(dictionary);
_underlyingDict = dictionary;
}
// Explicit name to make it very clear
internal static Dictionary<TKey, TValue> CreateTakingOwnershipOfDisposableValue(
godot_dictionary nativeValueToOwn)
=> new Dictionary<TKey, TValue>(Dictionary.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn));
/// <summary>
/// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>.
/// </summary>
/// <param name="from">The typed dictionary to convert.</param>
/// <returns>A new Godot Dictionary, or <see langword="null"/> if <see paramref="from"/> was null.</returns>
[return: NotNullIfNotNull("from")]
public static explicit operator Dictionary?(Dictionary<TKey, TValue>? from)
{
return from?._underlyingDict;
}
/// <summary>
/// Returns a copy of the <see cref="Dictionary{TKey, TValue}"/>.
/// If <paramref name="deep"/> is <see langword="true"/>, a deep copy is performed:
/// all nested arrays and dictionaries are duplicated and will not be shared with
/// the original dictionary. If <see langword="false"/>, a shallow copy is made and
/// references to the original nested arrays and dictionaries are kept, so that
/// modifying a sub-array or dictionary in the copy will also impact those
/// referenced in the source dictionary. Note that any <see cref="GodotObject"/> derived
/// elements will be shallow copied regardless of the <paramref name="deep"/>
/// setting.
/// </summary>
/// <param name="deep">If <see langword="true"/>, performs a deep copy.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary<TKey, TValue> Duplicate(bool deep = false)
{
return new Dictionary<TKey, TValue>(_underlyingDict.Duplicate(deep));
}
/// <summary>
/// Adds entries from <paramref name="dictionary"/> to this dictionary.
/// By default, duplicate keys are not copied over, unless <paramref name="overwrite"/>
/// is <see langword="true"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="dictionary">Dictionary to copy entries from.</param>
/// <param name="overwrite">If duplicate keys should be copied over as well.</param>
public void Merge(Dictionary<TKey, TValue> dictionary, bool overwrite = false)
{
_underlyingDict.Merge(dictionary._underlyingDict, overwrite);
}
/// <summary>
/// Compares this <see cref="Dictionary{TKey, TValue}"/> against the <paramref name="other"/>
/// <see cref="Dictionary{TKey, TValue}"/> recursively. Returns <see langword="true"/> if the
/// two dictionaries contain the same keys and values. The order of the entries does not matter.
/// otherwise.
/// </summary>
/// <param name="other">The other dictionary to compare against.</param>
/// <returns>
/// <see langword="true"/> if the dictionaries contain the same keys and values,
/// <see langword="false"/> otherwise.
/// </returns>
public bool RecursiveEqual(Dictionary<TKey, TValue> other)
{
return _underlyingDict.RecursiveEqual(other._underlyingDict);
}
// IDictionary<TKey, TValue>
/// <summary>
/// Returns the value at the given <paramref name="key"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The property is assigned and the dictionary is read-only.
/// </exception>
/// <exception cref="KeyNotFoundException">
/// The property is retrieved and an entry for <paramref name="key"/>
/// does not exist in the dictionary.
/// </exception>
/// <value>The value at the given <paramref name="key"/>.</value>
public TValue this[TKey key]
{
get
{
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant value).ToBool())
{
using (value)
return VariantUtils.ConvertTo<TValue>(value);
}
else
{
throw new KeyNotFoundException();
}
}
set
{
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key);
using var variantValue = VariantUtils.CreateFrom(value);
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_set_value(ref self,
variantKey, variantValue);
}
}
/// <summary>
/// Gets the collection of keys in this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
public ICollection<TKey> Keys
{
get
{
godot_array keyArray;
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_keys(ref self, out keyArray);
return Array<TKey>.CreateTakingOwnershipOfDisposableValue(keyArray);
}
}
/// <summary>
/// Gets the collection of elements in this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
public ICollection<TValue> Values
{
get
{
godot_array valuesArray;
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray);
return Array<TValue>.CreateTakingOwnershipOfDisposableValue(valuesArray);
}
}
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
private KeyValuePair<TKey, TValue> GetKeyValuePair(int index)
{
var self = (godot_dictionary)_underlyingDict.NativeValue;
NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index,
out godot_variant key,
out godot_variant value);
using (key)
using (value)
{
return new KeyValuePair<TKey, TValue>(
VariantUtils.ConvertTo<TKey>(key),
VariantUtils.ConvertTo<TValue>(value));
}
}
/// <summary>
/// Adds an object <paramref name="value"/> at key <paramref name="key"/>
/// to this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <exception cref="ArgumentException">
/// An element with the same <paramref name="key"/> already exists.
/// </exception>
/// <param name="key">The key at which to add the object.</param>
/// <param name="value">The object to add.</param>
public void Add(TKey key, TValue value)
{
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool())
throw new ArgumentException("An element with the same key already exists.", nameof(key));
using var variantValue = VariantUtils.CreateFrom(value);
NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue);
}
/// <summary>
/// Checks if this <see cref="Dictionary{TKey, TValue}"/> contains the given key.
/// </summary>
/// <param name="key">The key to look for.</param>
/// <returns>Whether or not this dictionary contains the given key.</returns>
public bool ContainsKey(TKey key)
{
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool();
}
/// <summary>
/// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
/// <param name="key">The key of the element to remove.</param>
public bool Remove(TKey key)
{
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool();
}
/// <summary>
/// Gets the value for the given <paramref name="key"/> in the dictionary.
/// Returns <see langword="true"/> if an entry for the given key exists in
/// the dictionary; otherwise, returns <see langword="false"/>.
/// </summary>
/// <param name="key">The key of the element to get.</param>
/// <param name="value">The value at the given <paramref name="key"/>.</param>
/// <returns>If an entry was found for the given <paramref name="key"/>.</returns>
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
using var variantKey = VariantUtils.CreateFrom(key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
value = found ? VariantUtils.ConvertTo<TValue>(retValue) : default;
return found;
}
// ICollection<KeyValuePair<TKey, TValue>>
/// <summary>
/// Returns the number of elements in this <see cref="Dictionary{TKey, TValue}"/>.
/// This is also known as the size or length of the dictionary.
/// </summary>
/// <returns>The number of elements.</returns>
public int Count => _underlyingDict.Count;
/// <summary>
/// Returns <see langword="true"/> if the dictionary is read-only.
/// See <see cref="MakeReadOnly"/>.
/// </summary>
public bool IsReadOnly => _underlyingDict.IsReadOnly;
/// <summary>
/// Makes the <see cref="Dictionary{TKey, TValue}"/> read-only, i.e. disabled
/// modying of the dictionary's elements. Does not apply to nested content,
/// e.g. content of nested dictionaries.
/// </summary>
public void MakeReadOnly()
{
_underlyingDict.MakeReadOnly();
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
=> Add(item.Key, item.Value);
/// <summary>
/// Clears the dictionary, removing all entries from it.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
public void Clear() => _underlyingDict.Clear();
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
{
if (!found)
return false;
using var variantValue = VariantUtils.CreateFrom(item.Value);
return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool();
}
}
/// <summary>
/// Copies the elements of this <see cref="Dictionary{TKey, TValue}"/> to the given
/// untyped C# array, starting at the given index.
/// </summary>
/// <exception cref="ArgumentNullException">
/// The <paramref name="array"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
/// </exception>
/// <exception cref="ArgumentException">
/// The destination array was not long enough.
/// </exception>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (array == null)
throw new ArgumentNullException(nameof(array), "Value cannot be null.");
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException(nameof(arrayIndex),
"Number was less than the array's lower bound in the first dimension.");
int count = Count;
if (array.Length < (arrayIndex + count))
throw new ArgumentException(
"Destination array was not long enough. Check destIndex and length, and the array's lower bounds.");
for (int i = 0; i < count; i++)
{
array[arrayIndex] = GetKeyValuePair(i);
arrayIndex++;
}
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
ThrowIfReadOnly();
using var variantKey = VariantUtils.CreateFrom(item.Key);
var self = (godot_dictionary)_underlyingDict.NativeValue;
bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
variantKey, out godot_variant retValue).ToBool();
using (retValue)
{
if (!found)
return false;
using var variantValue = VariantUtils.CreateFrom(item.Value);
if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool())
{
return NativeFuncs.godotsharp_dictionary_remove_key(
ref self, variantKey).ToBool();
}
return false;
}
}
// IEnumerable<KeyValuePair<TKey, TValue>>
/// <summary>
/// Gets an enumerator for this <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <returns>An enumerator.</returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return GetKeyValuePair(i);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string.
/// </summary>
/// <returns>A string representation of this dictionary.</returns>
public override string ToString() => _underlyingDict.ToString();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Dictionary<TKey, TValue>(Variant from) =>
from.AsGodotDictionary<TKey, TValue>();
private void ThrowIfReadOnly()
{
if (IsReadOnly)
{
throw new InvalidOperationException("Dictionary instance is read-only.");
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
{
public static class Dispatcher
{
internal static GodotTaskScheduler DefaultGodotTaskScheduler;
internal static void InitializeDefaultGodotTaskScheduler()
{
DefaultGodotTaskScheduler?.Dispose();
DefaultGodotTaskScheduler = new GodotTaskScheduler();
}
public static GodotSynchronizationContext SynchronizationContext => DefaultGodotTaskScheduler.Context;
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
internal static class DisposablesTracker
{
[UnmanagedCallersOnly]
internal static void OnGodotShuttingDown()
{
try
{
OnGodotShuttingDownImpl();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
private static void OnGodotShuttingDownImpl()
{
bool isStdoutVerbose;
try
{
isStdoutVerbose = OS.IsStdOutVerbose();
}
catch (ObjectDisposedException)
{
// OS singleton already disposed. Maybe OnUnloading was called twice.
isStdoutVerbose = false;
}
if (isStdoutVerbose)
GD.Print("Unloading: Disposing tracked instances...");
// Dispose Godot Objects first, and only then dispose other disposables
// like StringName, NodePath, Godot.Collections.Array/Dictionary, etc.
// The Godot Object Dispose() method may need any of the later instances.
foreach (WeakReference<GodotObject> item in GodotObjectInstances.Keys)
{
if (item.TryGetTarget(out GodotObject? self))
self.Dispose();
}
foreach (WeakReference<IDisposable> item in OtherInstances.Keys)
{
if (item.TryGetTarget(out IDisposable? self))
self.Dispose();
}
if (isStdoutVerbose)
GD.Print("Unloading: Finished disposing tracked instances.");
}
private static ConcurrentDictionary<WeakReference<GodotObject>, byte> GodotObjectInstances { get; } =
new();
private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } =
new();
public static WeakReference<GodotObject> RegisterGodotObject(GodotObject godotObject)
{
var weakReferenceToSelf = new WeakReference<GodotObject>(godotObject);
GodotObjectInstances.TryAdd(weakReferenceToSelf, 0);
return weakReferenceToSelf;
}
public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable)
{
var weakReferenceToSelf = new WeakReference<IDisposable>(disposable);
OtherInstances.TryAdd(weakReferenceToSelf, 0);
return weakReferenceToSelf;
}
public static void UnregisterGodotObject(GodotObject godotObject, WeakReference<GodotObject> weakReferenceToSelf)
{
if (!GodotObjectInstances.TryRemove(weakReferenceToSelf, out _))
throw new ArgumentException("Godot Object not registered.", nameof(weakReferenceToSelf));
}
public static void UnregisterDisposable(WeakReference<IDisposable> weakReference)
{
if (!OtherInstances.TryRemove(weakReference, out _))
throw new ArgumentException("Disposable not registered.", nameof(weakReference));
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
public partial class GodotObject
{
/// <summary>
/// Returns the <see cref="GodotObject"/> that corresponds to <paramref name="instanceId"/>.
/// All Objects have a unique instance ID. See also <see cref="GetInstanceId"/>.
/// </summary>
/// <example>
/// <code>
/// public partial class MyNode : Node
/// {
/// public string Foo { get; set; } = "bar";
///
/// public override void _Ready()
/// {
/// ulong id = GetInstanceId();
/// var inst = (MyNode)InstanceFromId(Id);
/// GD.Print(inst.Foo); // Prints bar
/// }
/// }
/// </code>
/// </example>
/// <param name="instanceId">Instance ID of the Object to retrieve.</param>
/// <returns>The <see cref="GodotObject"/> instance.</returns>
public static GodotObject? InstanceFromId(ulong instanceId)
{
return InteropUtils.UnmanagedGetManaged(NativeFuncs.godotsharp_instance_from_id(instanceId));
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="GodotObject"/> that corresponds
/// to <paramref name="id"/> is a valid object (e.g. has not been deleted from
/// memory). All Objects have a unique instance ID.
/// </summary>
/// <param name="id">The Object ID to check.</param>
/// <returns>If the instance with the given ID is a valid object.</returns>
public static bool IsInstanceIdValid(ulong id)
{
return IsInstanceValid(InstanceFromId(id));
}
/// <summary>
/// Returns <see langword="true"/> if <paramref name="instance"/> is a
/// valid <see cref="GodotObject"/> (e.g. has not been deleted from memory).
/// </summary>
/// <param name="instance">The instance to check.</param>
/// <returns>If the instance is a valid object.</returns>
public static bool IsInstanceValid([NotNullWhen(true)] GodotObject? instance)
{
return instance != null && instance.NativeInstance != IntPtr.Zero;
}
/// <summary>
/// Returns a weak reference to an object, or <see langword="null"/>
/// if the argument is invalid.
/// A weak reference to an object is not enough to keep the object alive:
/// when the only remaining references to a referent are weak references,
/// garbage collection is free to destroy the referent and reuse its memory
/// for something else. However, until the object is actually destroyed the
/// weak reference may return the object even if there are no strong references
/// to it.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// The <see cref="Godot.WeakRef"/> reference to the object or <see langword="null"/>.
/// </returns>
public static WeakRef? WeakRef(GodotObject? obj)
{
if (!IsInstanceValid(obj))
return null;
NativeFuncs.godotsharp_weakref(GetPtr(obj), out godot_ref weakRef);
using (weakRef)
{
if (weakRef.IsNull)
return null;
return (WeakRef)InteropUtils.UnmanagedGetManaged(weakRef.Reference);
}
}
}
}

View File

@@ -0,0 +1,199 @@
using System;
namespace Godot
{
public partial class Node
{
/// <summary>
/// Fetches a node. The <see cref="NodePath"/> can be either a relative path (from
/// the current node) or an absolute path (in the scene tree) to a node. If the path
/// does not exist, a <see langword="null"/> instance is returned and an error
/// is logged. Attempts to access methods on the return value will result in an
/// "Attempt to call &lt;method&gt; on a null instance." error.
/// Note: Fetching absolute paths only works when the node is inside the scene tree
/// (see <see cref="IsInsideTree"/>).
/// </summary>
/// <example>
/// Example: Assume your current node is Character and the following tree:
/// <code>
/// /root
/// /root/Character
/// /root/Character/Sword
/// /root/Character/Backpack/Dagger
/// /root/MyGame
/// /root/Swamp/Alligator
/// /root/Swamp/Mosquito
/// /root/Swamp/Goblin
/// </code>
/// Possible paths are:
/// <code>
/// GetNode("Sword");
/// GetNode("Backpack/Dagger");
/// GetNode("../Swamp/Alligator");
/// GetNode("/root/MyGame");
/// </code>
/// </example>
/// <seealso cref="GetNodeOrNull{T}(NodePath)"/>
/// <param name="path">The path to the node to fetch.</param>
/// <exception cref="InvalidCastException">
/// The fetched node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The <see cref="Node"/> at the given <paramref name="path"/>.
/// </returns>
public T GetNode<T>(NodePath path) where T : class
{
return (T)(object)GetNode(path);
}
/// <summary>
/// Similar to <see cref="GetNode"/>, but does not log an error if <paramref name="path"/>
/// does not point to a valid <see cref="Node"/>.
/// </summary>
/// <example>
/// Example: Assume your current node is Character and the following tree:
/// <code>
/// /root
/// /root/Character
/// /root/Character/Sword
/// /root/Character/Backpack/Dagger
/// /root/MyGame
/// /root/Swamp/Alligator
/// /root/Swamp/Mosquito
/// /root/Swamp/Goblin
/// </code>
/// Possible paths are:
/// <code>
/// GetNode("Sword");
/// GetNode("Backpack/Dagger");
/// GetNode("../Swamp/Alligator");
/// GetNode("/root/MyGame");
/// </code>
/// </example>
/// <seealso cref="GetNode{T}(NodePath)"/>
/// <param name="path">The path to the node to fetch.</param>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The <see cref="Node"/> at the given <paramref name="path"/>, or <see langword="null"/> if not found.
/// </returns>
public T GetNodeOrNull<T>(NodePath path) where T : class
{
return GetNodeOrNull(path) as T;
}
/// <summary>
/// Returns a child node by its index (see <see cref="GetChildCount"/>).
/// This method is often used for iterating all children of a node.
/// Negative indices access the children from the last one.
/// To access a child node via its name, use <see cref="GetNode"/>.
/// </summary>
/// <seealso cref="GetChildOrNull{T}(int, bool)"/>
/// <param name="idx">Child index.</param>
/// <param name="includeInternal">
/// If <see langword="false"/>, internal children are skipped (see <c>internal</c>
/// parameter in <see cref="AddChild(Node, bool, InternalMode)"/>).
/// </param>
/// <exception cref="InvalidCastException">
/// The fetched node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The child <see cref="Node"/> at the given index <paramref name="idx"/>.
/// </returns>
public T GetChild<T>(int idx, bool includeInternal = false) where T : class
{
return (T)(object)GetChild(idx, includeInternal);
}
/// <summary>
/// Returns a child node by its index (see <see cref="GetChildCount"/>).
/// This method is often used for iterating all children of a node.
/// Negative indices access the children from the last one.
/// To access a child node via its name, use <see cref="GetNode"/>.
/// </summary>
/// <seealso cref="GetChild{T}(int, bool)"/>
/// <param name="idx">Child index.</param>
/// <param name="includeInternal">
/// If <see langword="false"/>, internal children are skipped (see <c>internal</c>
/// parameter in <see cref="AddChild(Node, bool, InternalMode)"/>).
/// </param>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The child <see cref="Node"/> at the given index <paramref name="idx"/>, or <see langword="null"/> if not found.
/// </returns>
public T GetChildOrNull<T>(int idx, bool includeInternal = false) where T : class
{
int count = GetChildCount(includeInternal);
return idx >= -count && idx < count ? GetChild(idx, includeInternal) as T : null;
}
/// <summary>
/// The node owner. A node can have any other node as owner (as long as it is
/// a valid parent, grandparent, etc. ascending in the tree). When saving a
/// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved
/// with it. This allows for the creation of complex <see cref="SceneTree"/>s,
/// with instancing and subinstancing.
/// </summary>
/// <seealso cref="GetOwnerOrNull{T}"/>
/// <exception cref="InvalidCastException">
/// The fetched node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The owner <see cref="Node"/>.
/// </returns>
public T GetOwner<T>() where T : class
{
return (T)(object)Owner;
}
/// <summary>
/// The node owner. A node can have any other node as owner (as long as it is
/// a valid parent, grandparent, etc. ascending in the tree). When saving a
/// node (using <see cref="PackedScene"/>), all the nodes it owns will be saved
/// with it. This allows for the creation of complex <see cref="SceneTree"/>s,
/// with instancing and subinstancing.
/// </summary>
/// <seealso cref="GetOwner{T}"/>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The owner <see cref="Node"/>, or <see langword="null"/> if there is no owner.
/// </returns>
public T GetOwnerOrNull<T>() where T : class
{
return Owner as T;
}
/// <summary>
/// Returns the parent node of the current node, or a <see langword="null"/> instance
/// if the node lacks a parent.
/// </summary>
/// <seealso cref="GetParentOrNull{T}"/>
/// <exception cref="InvalidCastException">
/// The fetched node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The parent <see cref="Node"/>.
/// </returns>
public T GetParent<T>() where T : class
{
return (T)(object)GetParent();
}
/// <summary>
/// Returns the parent node of the current node, or a <see langword="null"/> instance
/// if the node lacks a parent.
/// </summary>
/// <seealso cref="GetParent{T}"/>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>
/// The parent <see cref="Node"/>, or <see langword="null"/> if the node has no parent.
/// </returns>
public T GetParentOrNull<T>() where T : class
{
return GetParent() as T;
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
namespace Godot
{
public partial class PackedScene
{
/// <summary>
/// Instantiates the scene's node hierarchy, erroring on failure.
/// Triggers child scene instantiation(s). Triggers a
/// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node.
/// </summary>
/// <seealso cref="InstantiateOrNull{T}(GenEditState)"/>
/// <exception cref="InvalidCastException">
/// The instantiated node can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>The instantiated scene.</returns>
public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
{
return (T)(object)Instantiate(editState);
}
/// <summary>
/// Instantiates the scene's node hierarchy, returning <see langword="null"/> on failure.
/// Triggers child scene instantiation(s). Triggers a
/// <see cref="Node.NotificationSceneInstantiated"/> notification on the root node.
/// </summary>
/// <seealso cref="Instantiate{T}(GenEditState)"/>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam>
/// <returns>The instantiated scene.</returns>
public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
{
return Instantiate(editState) as T;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace Godot
{
public static partial class ResourceLoader
{
/// <summary>
/// Loads a resource at the given <paramref name="path"/>, caching the result
/// for further access.
/// The registered <see cref="ResourceFormatLoader"/> instances are queried sequentially
/// to find the first one which can handle the file's extension, and then attempt
/// loading. If loading fails, the remaining ResourceFormatLoaders are also attempted.
/// An optional <paramref name="typeHint"/> can be used to further specify the
/// <see cref="Resource"/> type that should be handled by the <see cref="ResourceFormatLoader"/>.
/// Anything that inherits from <see cref="Resource"/> can be used as a type hint,
/// for example <see cref="Image"/>.
/// The <paramref name="cacheMode"/> property defines whether and how the cache should
/// be used or updated when loading the resource. See <see cref="CacheMode"/> for details.
/// Returns an empty resource if no <see cref="ResourceFormatLoader"/> could handle the file.
/// </summary>
/// <exception cref="InvalidCastException">
/// The loaded resource can't be casted to the given type <typeparamref name="T"/>.
/// </exception>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam>
public static T Load<T>(string path, string typeHint = null, CacheMode cacheMode = CacheMode.Reuse) where T : class
{
return (T)(object)Load(path, typeHint, cacheMode);
}
}
}

View File

@@ -0,0 +1,734 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Godot.NativeInterop;
namespace Godot
{
/// <summary>
/// Godot's global functions.
/// </summary>
public static partial class GD
{
/// <summary>
/// Decodes a byte array back to a <see cref="Variant"/> value, without decoding objects.
/// Note: If you need object deserialization, see <see cref="BytesToVarWithObjects"/>.
/// </summary>
/// <param name="bytes">Byte array that will be decoded to a <see cref="Variant"/>.</param>
/// <returns>The decoded <see cref="Variant"/>.</returns>
public static Variant BytesToVar(Span<byte> bytes)
{
using var varBytes = Marshaling.ConvertSystemArrayToNativePackedByteArray(bytes);
NativeFuncs.godotsharp_bytes_to_var(varBytes, godot_bool.False, out godot_variant ret);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
/// <summary>
/// Decodes a byte array back to a <see cref="Variant"/> value. Decoding objects is allowed.
/// Warning: Deserialized object can contain code which gets executed. Do not use this
/// option if the serialized object comes from untrusted sources to avoid potential security
/// threats (remote code execution).
/// </summary>
/// <param name="bytes">Byte array that will be decoded to a <see cref="Variant"/>.</param>
/// <returns>The decoded <see cref="Variant"/>.</returns>
public static Variant BytesToVarWithObjects(Span<byte> bytes)
{
using var varBytes = Marshaling.ConvertSystemArrayToNativePackedByteArray(bytes);
NativeFuncs.godotsharp_bytes_to_var(varBytes, godot_bool.True, out godot_variant ret);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
/// <summary>
/// Converts <paramref name="what"/> to <paramref name="type"/> in the best way possible.
/// The <paramref name="type"/> parameter uses the <see cref="Variant.Type"/> values.
/// </summary>
/// <example>
/// <code>
/// Variant a = new Godot.Collections.Array { 4, 2.5, 1.2 };
/// GD.Print(a.VariantType == Variant.Type.Array); // Prints true
///
/// var b = GD.Convert(a, Variant.Type.PackedByteArray);
/// GD.Print(b); // Prints [4, 2, 1]
/// GD.Print(b.VariantType == Variant.Type.Array); // Prints false
/// </code>
/// </example>
/// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns>
public static Variant Convert(Variant what, Variant.Type type)
{
NativeFuncs.godotsharp_convert((godot_variant)what.NativeVar, (int)type, out godot_variant ret);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
/// <summary>
/// Returns the integer hash of the passed <paramref name="var"/>.
/// </summary>
/// <example>
/// <code>
/// GD.Print(GD.Hash("a")); // Prints 177670
/// </code>
/// </example>
/// <param name="var">Variable that will be hashed.</param>
/// <returns>Hash of the variable passed.</returns>
public static int Hash(Variant var)
{
return NativeFuncs.godotsharp_hash((godot_variant)var.NativeVar);
}
/// <summary>
/// Loads a resource from the filesystem located at <paramref name="path"/>.
/// The resource is loaded on the method call (unless it's referenced already
/// elsewhere, e.g. in another script or in the scene), which might cause slight delay,
/// especially when loading scenes. To avoid unnecessary delays when loading something
/// multiple times, either store the resource in a variable.
///
/// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem
/// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
///
/// Important: The path must be absolute, a local path will just return <see langword="null"/>.
/// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used
/// for more advanced scenarios.
/// </summary>
/// <example>
/// <code>
/// // Load a scene called main located in the root of the project directory and cache it in a variable.
/// var main = GD.Load("res://main.tscn"); // main will contain a PackedScene resource.
/// </code>
/// </example>
/// <param name="path">Path of the <see cref="Resource"/> to load.</param>
/// <returns>The loaded <see cref="Resource"/>.</returns>
public static Resource Load(string path)
{
return ResourceLoader.Load(path);
}
/// <summary>
/// Loads a resource from the filesystem located at <paramref name="path"/>.
/// The resource is loaded on the method call (unless it's referenced already
/// elsewhere, e.g. in another script or in the scene), which might cause slight delay,
/// especially when loading scenes. To avoid unnecessary delays when loading something
/// multiple times, either store the resource in a variable.
///
/// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem
/// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
///
/// Important: The path must be absolute, a local path will just return <see langword="null"/>.
/// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used
/// for more advanced scenarios.
/// </summary>
/// <example>
/// <code>
/// // Load a scene called main located in the root of the project directory and cache it in a variable.
/// var main = GD.Load&lt;PackedScene&gt;("res://main.tscn"); // main will contain a PackedScene resource.
/// </code>
/// </example>
/// <param name="path">Path of the <see cref="Resource"/> to load.</param>
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam>
public static T Load<T>(string path) where T : class
{
return ResourceLoader.Load<T>(path);
}
private static string AppendPrintParams(object[] parameters)
{
if (parameters == null)
{
return "null";
}
var sb = new StringBuilder();
for (int i = 0; i < parameters.Length; i++)
{
sb.Append(parameters[i]?.ToString() ?? "null");
}
return sb.ToString();
}
private static string AppendPrintParams(char separator, object[] parameters)
{
if (parameters == null)
{
return "null";
}
var sb = new StringBuilder();
for (int i = 0; i < parameters.Length; i++)
{
if (i != 0)
sb.Append(separator);
sb.Append(parameters[i]?.ToString() ?? "null");
}
return sb.ToString();
}
/// <summary>
/// Prints a message to the console.
///
/// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/>
/// to print error and warning messages instead of <see cref="Print(string)"/>.
/// This distinguishes them from print messages used for debugging purposes,
/// while also displaying a stack trace when an error or warning is printed.
/// </summary>
/// <param name="what">Message that will be printed.</param>
public static void Print(string what)
{
using var godotStr = Marshaling.ConvertStringToNative(what);
NativeFuncs.godotsharp_print(godotStr);
}
/// <summary>
/// Converts one or more arguments of any type to string in the best way possible
/// and prints them to the console.
///
/// Note: Consider using <see cref="PushError(object[])"/> and <see cref="PushWarning(object[])"/>
/// to print error and warning messages instead of <see cref="Print(object[])"/>.
/// This distinguishes them from print messages used for debugging purposes,
/// while also displaying a stack trace when an error or warning is printed.
/// </summary>
/// <example>
/// <code>
/// var a = new Godot.Collections.Array { 1, 2, 3 };
/// GD.Print("a", "b", a); // Prints ab[1, 2, 3]
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void Print(params object[] what)
{
Print(AppendPrintParams(what));
}
/// <summary>
/// Prints a message to the console.
/// The following BBCode tags are supported: b, i, u, s, indent, code, url, center,
/// right, color, bgcolor, fgcolor.
/// Color tags only support named colors such as <c>red</c>, not hexadecimal color codes.
/// Unsupported tags will be left as-is in standard output.
/// When printing to standard output, the supported subset of BBCode is converted to
/// ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes
/// is currently only supported on Linux and macOS. Support for ANSI escape codes may vary
/// across terminal emulators, especially for italic and strikethrough.
///
/// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/>
/// to print error and warning messages instead of <see cref="Print(string)"/> or
/// <see cref="PrintRich(string)"/>.
/// This distinguishes them from print messages used for debugging purposes,
/// while also displaying a stack trace when an error or warning is printed.
/// </summary>
/// <param name="what">Message that will be printed.</param>
public static void PrintRich(string what)
{
using var godotStr = Marshaling.ConvertStringToNative(what);
NativeFuncs.godotsharp_print_rich(godotStr);
}
/// <summary>
/// Converts one or more arguments of any type to string in the best way possible
/// and prints them to the console.
/// The following BBCode tags are supported: b, i, u, s, indent, code, url, center,
/// right, color, bgcolor, fgcolor.
/// Color tags only support named colors such as <c>red</c>, not hexadecimal color codes.
/// Unsupported tags will be left as-is in standard output.
/// When printing to standard output, the supported subset of BBCode is converted to
/// ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes
/// is currently only supported on Linux and macOS. Support for ANSI escape codes may vary
/// across terminal emulators, especially for italic and strikethrough.
///
/// Note: Consider using <see cref="PushError(object[])"/> and <see cref="PushWarning(object[])"/>
/// to print error and warning messages instead of <see cref="Print(object[])"/> or
/// <see cref="PrintRich(object[])"/>.
/// This distinguishes them from print messages used for debugging purposes,
/// while also displaying a stack trace when an error or warning is printed.
/// </summary>
/// <example>
/// <code>
/// GD.PrintRich("[code][b]Hello world![/b][/code]"); // Prints out: [b]Hello world![/b]
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void PrintRich(params object[] what)
{
PrintRich(AppendPrintParams(what));
}
/// <summary>
/// Prints a message to standard error line.
/// </summary>
/// <param name="what">Message that will be printed.</param>
public static void PrintErr(string what)
{
using var godotStr = Marshaling.ConvertStringToNative(what);
NativeFuncs.godotsharp_printerr(godotStr);
}
/// <summary>
/// Prints one or more arguments to strings in the best way possible to standard error line.
/// </summary>
/// <example>
/// <code>
/// GD.PrintErr("prints to stderr");
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void PrintErr(params object[] what)
{
PrintErr(AppendPrintParams(what));
}
/// <summary>
/// Prints a message to the OS terminal.
/// Unlike <see cref="Print(string)"/>, no newline is added at the end.
/// </summary>
/// <param name="what">Message that will be printed.</param>
public static void PrintRaw(string what)
{
using var godotStr = Marshaling.ConvertStringToNative(what);
NativeFuncs.godotsharp_printraw(godotStr);
}
/// <summary>
/// Prints one or more arguments to strings in the best way possible to the OS terminal.
/// Unlike <see cref="Print(object[])"/>, no newline is added at the end.
/// </summary>
/// <example>
/// <code>
/// GD.PrintRaw("A");
/// GD.PrintRaw("B");
/// GD.PrintRaw("C");
/// // Prints ABC to terminal
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void PrintRaw(params object[] what)
{
PrintRaw(AppendPrintParams(what));
}
/// <summary>
/// Prints one or more arguments to the console with a space between each argument.
/// </summary>
/// <example>
/// <code>
/// GD.PrintS("A", "B", "C"); // Prints A B C
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void PrintS(params object[] what)
{
string message = AppendPrintParams(' ', what);
using var godotStr = Marshaling.ConvertStringToNative(message);
NativeFuncs.godotsharp_prints(godotStr);
}
/// <summary>
/// Prints one or more arguments to the console with a tab between each argument.
/// </summary>
/// <example>
/// <code>
/// GD.PrintT("A", "B", "C"); // Prints A B C
/// </code>
/// </example>
/// <param name="what">Arguments that will be printed.</param>
public static void PrintT(params object[] what)
{
string message = AppendPrintParams('\t', what);
using var godotStr = Marshaling.ConvertStringToNative(message);
NativeFuncs.godotsharp_printt(godotStr);
}
[StackTraceHidden]
private static void ErrPrintError(string message, godot_error_handler_type type = godot_error_handler_type.ERR_HANDLER_ERROR)
{
// Skip 1 frame to avoid current method.
var stackFrame = DebuggingUtils.GetCurrentStackFrame(skipFrames: 1);
string callerFilePath = ProjectSettings.LocalizePath(stackFrame.GetFileName());
DebuggingUtils.GetStackFrameMethodDecl(stackFrame, out string callerName);
int callerLineNumber = stackFrame.GetFileLineNumber();
using godot_string messageStr = Marshaling.ConvertStringToNative(message);
using godot_string callerNameStr = Marshaling.ConvertStringToNative(callerName);
using godot_string callerFilePathStr = Marshaling.ConvertStringToNative(callerFilePath);
NativeFuncs.godotsharp_err_print_error(callerNameStr, callerFilePathStr, callerLineNumber, messageStr, p_type: type);
}
/// <summary>
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
///
/// Note: Errors printed this way will not pause project execution.
/// </summary>
/// <example>
/// <code>
/// GD.PushError("test error"); // Prints "test error" to debugger and terminal as error call
/// </code>
/// </example>
/// <param name="message">Error message.</param>
public static void PushError(string message)
{
ErrPrintError(message);
}
/// <summary>
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
///
/// Note: Errors printed this way will not pause project execution.
/// </summary>
/// <example>
/// <code>
/// GD.PushError("test_error"); // Prints "test error" to debugger and terminal as error call
/// </code>
/// </example>
/// <param name="what">Arguments that form the error message.</param>
public static void PushError(params object[] what)
{
ErrPrintError(AppendPrintParams(what));
}
/// <summary>
/// Pushes a warning message to Godot's built-in debugger and to the OS terminal.
/// </summary>
/// <example>
/// <code>
/// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call
/// </code>
/// </example>
/// <param name="message">Warning message.</param>
public static void PushWarning(string message)
{
ErrPrintError(message, type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
/// Pushes a warning message to Godot's built-in debugger and to the OS terminal.
/// </summary>
/// <example>
/// <code>
/// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call
/// </code>
/// </example>
/// <param name="what">Arguments that form the warning message.</param>
public static void PushWarning(params object[] what)
{
ErrPrintError(AppendPrintParams(what), type: godot_error_handler_type.ERR_HANDLER_WARNING);
}
/// <summary>
/// Returns a random floating point value between <c>0.0</c> and <c>1.0</c> (inclusive).
/// </summary>
/// <example>
/// <code>
/// GD.Randf(); // Returns e.g. 0.375671
/// </code>
/// </example>
/// <returns>A random <see langword="float"/> number.</returns>
public static float Randf()
{
return NativeFuncs.godotsharp_randf();
}
/// <summary>
/// Returns a normally-distributed pseudo-random floating point value
/// using Box-Muller transform with the specified <pararmref name="mean"/>
/// and a standard <paramref name="deviation"/>.
/// This is also called Gaussian distribution.
/// </summary>
/// <returns>A random normally-distributed <see langword="float"/> number.</returns>
public static double Randfn(double mean, double deviation)
{
return NativeFuncs.godotsharp_randfn(mean, deviation);
}
/// <summary>
/// Returns a random unsigned 32-bit integer.
/// Use remainder to obtain a random value in the interval <c>[0, N - 1]</c>
/// (where N is smaller than 2^32).
/// </summary>
/// <example>
/// <code>
/// GD.Randi(); // Returns random integer between 0 and 2^32 - 1
/// GD.Randi() % 20; // Returns random integer between 0 and 19
/// GD.Randi() % 100; // Returns random integer between 0 and 99
/// GD.Randi() % 100 + 1; // Returns random integer between 1 and 100
/// </code>
/// </example>
/// <returns>A random <see langword="uint"/> number.</returns>
public static uint Randi()
{
return NativeFuncs.godotsharp_randi();
}
/// <summary>
/// Randomizes the seed (or the internal state) of the random number generator.
/// The current implementation uses a number based on the device's time.
///
/// Note: This method is called automatically when the project is run.
/// If you need to fix the seed to have consistent, reproducible results,
/// use <see cref="Seed(ulong)"/> to initialize the random number generator.
/// </summary>
public static void Randomize()
{
NativeFuncs.godotsharp_randomize();
}
/// <summary>
/// Returns a random floating point value between <paramref name="from"/>
/// and <paramref name="to"/> (inclusive).
/// </summary>
/// <example>
/// <code>
/// GD.RandRange(0.0, 20.5); // Returns e.g. 7.45315
/// GD.RandRange(-10.0, 10.0); // Returns e.g. -3.844535
/// </code>
/// </example>
/// <returns>A random <see langword="double"/> number inside the given range.</returns>
public static double RandRange(double from, double to)
{
return NativeFuncs.godotsharp_randf_range(from, to);
}
/// <summary>
/// Returns a random signed 32-bit integer between <paramref name="from"/>
/// and <paramref name="to"/> (inclusive). If <paramref name="to"/> is lesser than
/// <paramref name="from"/>, they are swapped.
/// </summary>
/// <example>
/// <code>
/// GD.RandRange(0, 1); // Returns either 0 or 1
/// GD.RandRange(-10, 1000); // Returns random integer between -10 and 1000
/// </code>
/// </example>
/// <returns>A random <see langword="int"/> number inside the given range.</returns>
public static int RandRange(int from, int to)
{
return NativeFuncs.godotsharp_randi_range(from, to);
}
/// <summary>
/// Given a <paramref name="seed"/>, returns a randomized <see langword="uint"/>
/// value. The <paramref name="seed"/> may be modified.
/// Passing the same <paramref name="seed"/> consistently returns the same value.
///
/// Note: "Seed" here refers to the internal state of the pseudo random number
/// generator, currently implemented as a 64 bit integer.
/// </summary>
/// <example>
/// <code>
/// var a = GD.RandFromSeed(4);
/// </code>
/// </example>
/// <param name="seed">
/// Seed to use to generate the random number.
/// If a different seed is used, its value will be modified.
/// </param>
/// <returns>A random <see langword="uint"/> number.</returns>
public static uint RandFromSeed(ref ulong seed)
{
return NativeFuncs.godotsharp_rand_from_seed(seed, out seed);
}
/// <summary>
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
/// <c>0</c> (inclusive) to <paramref name="end"/> (exclusive)
/// in steps of <c>1</c>.
/// </summary>
/// <param name="end">The last index.</param>
public static IEnumerable<int> Range(int end)
{
return Range(0, end, 1);
}
/// <summary>
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
/// <paramref name="start"/> (inclusive) to <paramref name="end"/> (exclusive)
/// in steps of <c>1</c>.
/// </summary>
/// <param name="start">The first index.</param>
/// <param name="end">The last index.</param>
public static IEnumerable<int> Range(int start, int end)
{
return Range(start, end, 1);
}
/// <summary>
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
/// <paramref name="start"/> (inclusive) to <paramref name="end"/> (exclusive)
/// in steps of <paramref name="step"/>.
/// The argument <paramref name="step"/> can be negative, but not <c>0</c>.
/// </summary>
/// <exception cref="ArgumentException">
/// <paramref name="step"/> is 0.
/// </exception>
/// <param name="start">The first index.</param>
/// <param name="end">The last index.</param>
/// <param name="step">The amount by which to increment the index on each iteration.</param>
public static IEnumerable<int> Range(int start, int end, int step)
{
if (step == 0)
throw new ArgumentException("step cannot be 0.", nameof(step));
if (end < start && step > 0)
yield break;
if (end > start && step < 0)
yield break;
if (step > 0)
{
for (int i = start; i < end; i += step)
yield return i;
}
else
{
for (int i = start; i > end; i += step)
yield return i;
}
}
/// <summary>
/// Sets seed for the random number generator to <paramref name="seed"/>.
/// Setting the seed manually can ensure consistent, repeatable results for
/// most random functions.
/// </summary>
/// <example>
/// <code>
/// ulong mySeed = (ulong)GD.Hash("Godot Rocks");
/// GD.Seed(mySeed);
/// var a = GD.Randf() + GD.Randi();
/// GD.Seed(mySeed);
/// var b = GD.Randf() + GD.Randi();
/// // a and b are now identical
/// </code>
/// </example>
/// <param name="seed">Seed that will be used.</param>
public static void Seed(ulong seed)
{
NativeFuncs.godotsharp_seed(seed);
}
/// <summary>
/// Converts a formatted string that was returned by <see cref="VarToStr(Variant)"/>
/// to the original value.
/// </summary>
/// <example>
/// <code>
/// string a = "{ \"a\": 1, \"b\": 2 }"; // a is a string
/// var b = GD.StrToVar(a).AsGodotDictionary(); // b is a Dictionary
/// GD.Print(b["a"]); // Prints 1
/// </code>
/// </example>
/// <param name="str">String that will be converted to Variant.</param>
/// <returns>The decoded <c>Variant</c>.</returns>
public static Variant StrToVar(string str)
{
using var godotStr = Marshaling.ConvertStringToNative(str);
NativeFuncs.godotsharp_str_to_var(godotStr, out godot_variant ret);
return Variant.CreateTakingOwnershipOfDisposableValue(ret);
}
/// <summary>
/// Encodes a <see cref="Variant"/> value to a byte array, without encoding objects.
/// Deserialization can be done with <see cref="BytesToVar"/>.
/// Note: If you need object serialization, see <see cref="VarToBytesWithObjects"/>.
/// </summary>
/// <param name="var"><see cref="Variant"/> that will be encoded.</param>
/// <returns>The <see cref="Variant"/> encoded as an array of bytes.</returns>
public static byte[] VarToBytes(Variant var)
{
NativeFuncs.godotsharp_var_to_bytes((godot_variant)var.NativeVar, godot_bool.False, out var varBytes);
using (varBytes)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes);
}
/// <summary>
/// Encodes a <see cref="Variant"/>. Encoding objects is allowed (and can potentially
/// include executable code). Deserialization can be done with <see cref="BytesToVarWithObjects"/>.
/// </summary>
/// <param name="var"><see cref="Variant"/> that will be encoded.</param>
/// <returns>The <see cref="Variant"/> encoded as an array of bytes.</returns>
public static byte[] VarToBytesWithObjects(Variant var)
{
NativeFuncs.godotsharp_var_to_bytes((godot_variant)var.NativeVar, godot_bool.True, out var varBytes);
using (varBytes)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes);
}
/// <summary>
/// Converts a <see cref="Variant"/> <paramref name="var"/> to a formatted string that
/// can later be parsed using <see cref="StrToVar(string)"/>.
/// </summary>
/// <example>
/// <code>
/// var a = new Godot.Collections.Dictionary { ["a"] = 1, ["b"] = 2 };
/// GD.Print(GD.VarToStr(a));
/// // Prints:
/// // {
/// // "a": 1,
/// // "b": 2
/// // }
/// </code>
/// </example>
/// <param name="var">Variant that will be converted to string.</param>
/// <returns>The <see cref="Variant"/> encoded as a string.</returns>
public static string VarToStr(Variant var)
{
NativeFuncs.godotsharp_var_to_str((godot_variant)var.NativeVar, out godot_string ret);
using (ret)
return Marshaling.ConvertStringToManaged(ret);
}
/// <summary>
/// Get the <see cref="Variant.Type"/> that corresponds for the given <see cref="Type"/>.
/// </summary>
/// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns>
public static Variant.Type TypeToVariantType(Type type)
{
return Marshaling.ConvertManagedTypeToVariantType(type, out bool _);
}
/// <summary>
/// Returns a new byte array with the data compressed.
/// </summary>
/// <param name="instance">The byte array to compress.</param>
/// <param name="compressionMode">The compression mode, one of <see cref="FileAccess.CompressionMode"/></param>
/// <returns>The compressed byte array.</returns>
public static byte[] Compress(this byte[] instance, FileAccess.CompressionMode compressionMode = 0)
{
using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance);
NativeFuncs.godotsharp_packed_byte_array_compress(src, (int)compressionMode, out var ret);
using (ret)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret);
}
/// <summary>
/// Returns a new byte array with the data decompressed.
/// <para>Note: Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header.</para>
/// </summary>
/// <param name="instance">The byte array to decompress.</param>
/// <param name="bufferSize">The size of the uncompressed data.</param>
/// <param name="compressionMode">The compression mode, one of <see cref="FileAccess.CompressionMode"/></param>
/// <returns>The decompressed byte array.</returns>
public static byte[] Decompress(this byte[] instance, long bufferSize, FileAccess.CompressionMode compressionMode = 0)
{
using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance);
NativeFuncs.godotsharp_packed_byte_array_decompress(src, bufferSize, (int)compressionMode, out var ret);
using (ret)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret);
}
/// <summary>
/// Returns a new byte array with the data decompressed. <b>This method only accepts brotli, gzip, and deflate compression modes</b>.
/// <para>This method is potentially slower than <see cref="Decompress"/>, as it may have to re-allocate its output buffer multiple times while decompressing, whereas <see cref="Decompress"/> knows it's output buffer size from the beginning.</para>
/// <para>GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via [param max_output_size]. Passing -1 will allow for unbounded output. If any positive value is passed, and the decompression exceeds that amount in bytes, then an error will be returned.</para>
/// <para>Note: Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate compression mode lacks a checksum or header.</para>
/// </summary>
/// <param name="instance">The byte array to decompress.</param>
/// <param name="maxOutputSize">The maximum size this function is allowed to allocate in bytes.</param>
/// <param name="compressionMode">The compression mode, one of <see cref="FileAccess.CompressionMode"/></param>
/// <returns>The decompressed byte array.</returns>
public static byte[] DecompressDynamic(this byte[] instance, long maxOutputSize, FileAccess.CompressionMode compressionMode = 0)
{
using godot_packed_byte_array src = Marshaling.ConvertSystemArrayToNativePackedByteArray(instance);
NativeFuncs.godotsharp_packed_byte_array_decompress_dynamic(src, maxOutputSize, (int)compressionMode, out var ret);
using (ret)
return Marshaling.ConvertNativePackedByteArrayToSystemArray(ret);
}
}
}

View File

@@ -0,0 +1,353 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using Godot.Bridge;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
public partial class GodotObject : IDisposable
{
private bool _disposed;
private static readonly Type _cachedType = typeof(GodotObject);
private static readonly Dictionary<Type, StringName?> _nativeNames = new Dictionary<Type, StringName?>();
internal IntPtr NativePtr;
private bool _memoryOwn;
private WeakReference<GodotObject>? _weakReferenceToSelf;
/// <summary>
/// Constructs a new <see cref="GodotObject"/>.
/// </summary>
public GodotObject() : this(false)
{
unsafe
{
ConstructAndInitialize(NativeCtor, NativeName, _cachedType, refCounted: false);
}
}
internal GodotObject(IntPtr nativePtr) : this(false)
{
// NativePtr must be non-zero before calling ConstructAndInitialize to avoid invoking the constructor NativeCtor.
// We don't want to invoke the constructor, because we already have a constructed instance in nativePtr.
NativePtr = nativePtr;
unsafe
{
ConstructAndInitialize(NativeCtor, NativeName, _cachedType, refCounted: false);
}
}
internal unsafe void ConstructAndInitialize(
delegate* unmanaged<godot_bool, IntPtr> nativeCtor,
StringName nativeName,
Type cachedType,
bool refCounted
)
{
if (NativePtr == IntPtr.Zero)
{
Debug.Assert(nativeCtor != null);
// Need postinitialization.
NativePtr = nativeCtor(godot_bool.True);
InteropUtils.TieManagedToUnmanaged(this, NativePtr,
nativeName, refCounted, GetType(), cachedType);
}
else
{
InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr,
GetType(), cachedType);
}
_weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this);
}
internal GodotObject(bool memoryOwn)
{
_memoryOwn = memoryOwn;
}
/// <summary>
/// The pointer to the native instance of this <see cref="GodotObject"/>.
/// </summary>
public IntPtr NativeInstance => NativePtr;
internal static IntPtr GetPtr(GodotObject? instance)
{
if (instance == null)
return IntPtr.Zero;
// We check if NativePtr is null because this may be called by the debugger.
// If the debugger puts a breakpoint in one of the base constructors, before
// NativePtr is assigned, that would result in UB or crashes when calling
// native functions that receive the pointer, which can happen because the
// debugger calls ToString() and tries to get the value of properties.
ObjectDisposedException.ThrowIf(instance._disposed || instance.NativePtr == IntPtr.Zero, instance);
return instance.NativePtr;
}
~GodotObject()
{
Dispose(false);
}
/// <summary>
/// Disposes of this <see cref="GodotObject"/>.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes implementation of this <see cref="GodotObject"/>.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (NativePtr != IntPtr.Zero)
{
IntPtr gcHandleToFree = NativeFuncs.godotsharp_internal_object_get_associated_gchandle(NativePtr);
if (gcHandleToFree != IntPtr.Zero)
{
object? target = GCHandle.FromIntPtr(gcHandleToFree).Target;
// The GC handle may have been replaced in another thread. Release it only if
// it's associated to this managed instance, or if the target is no longer alive.
if (target != this && target != null)
gcHandleToFree = IntPtr.Zero;
}
if (_memoryOwn)
{
NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, gcHandleToFree,
(!disposing).ToGodotBool());
}
else
{
NativeFuncs.godotsharp_internal_object_disposed(NativePtr, gcHandleToFree);
}
NativePtr = IntPtr.Zero;
}
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
}
}
/// <summary>
/// Converts this <see cref="GodotObject"/> to a string.
/// </summary>
/// <returns>A string representation of this object.</returns>
public override string ToString()
{
NativeFuncs.godotsharp_object_to_string(GetPtr(this), out godot_string str);
using (str)
return Marshaling.ConvertStringToManaged(str);
}
/// <summary>
/// Returns a new <see cref="SignalAwaiter"/> awaiter configured to complete when the instance
/// <paramref name="source"/> emits the signal specified by the <paramref name="signal"/> parameter.
/// </summary>
/// <param name="source">
/// The instance the awaiter will be listening to.
/// </param>
/// <param name="signal">
/// The signal the awaiter will be waiting for.
/// </param>
/// <example>
/// This sample prints a message once every frame up to 100 times.
/// <code>
/// public override void _Ready()
/// {
/// for (int i = 0; i &lt; 100; i++)
/// {
/// await ToSignal(GetTree(), "process_frame");
/// GD.Print($"Frame {i}");
/// }
/// }
/// </code>
/// </example>
/// <returns>
/// A <see cref="SignalAwaiter"/> that completes when
/// <paramref name="source"/> emits the <paramref name="signal"/>.
/// </returns>
public SignalAwaiter ToSignal(GodotObject source, StringName signal)
{
return new SignalAwaiter(source, signal, this);
}
internal static bool IsNativeClass(Type t)
{
if (ReferenceEquals(t.Assembly, typeof(GodotObject).Assembly))
{
return true;
}
if (ReflectionUtils.IsEditorHintCached)
{
return t.Assembly.GetName().Name == "GodotSharpEditor";
}
return false;
}
internal static Type InternalGetClassNativeBase(Type t)
{
while (!IsNativeClass(t))
{
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
t = t.BaseType;
}
return t;
}
internal static StringName? InternalGetClassNativeBaseName(Type t)
{
if (_nativeNames.TryGetValue(t, out var name))
{
return name;
}
var baseType = InternalGetClassNativeBase(t);
if (_nativeNames.TryGetValue(baseType, out name))
{
return name;
}
var field = baseType.GetField("NativeName",
BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.Public | BindingFlags.NonPublic);
name = field?.GetValue(null) as StringName;
_nativeNames[baseType] = name;
return name;
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
/// <summary>
/// Set the value of a property contained in this class.
/// This method is used by Godot to assign property values.
/// Do not call or override this method.
/// </summary>
/// <param name="name">Name of the property to set.</param>
/// <param name="value">Value to set the property to if it was found.</param>
/// <returns><see langword="true"/> if a property with the given name was found.</returns>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value)
{
return false;
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
/// <summary>
/// Get the value of a property contained in this class.
/// This method is used by Godot to retrieve property values.
/// Do not call or override this method.
/// </summary>
/// <param name="name">Name of the property to get.</param>
/// <param name="value">Value of the property if it was found.</param>
/// <returns><see langword="true"/> if a property with the given name was found.</returns>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value)
{
value = default;
return false;
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
/// <summary>
/// Raises the signal with the given name, using the given arguments.
/// This method is used by Godot to raise signals from the engine side.\n"
/// Do not call or override this method.
/// </summary>
/// <param name="signal">Name of the signal to raise.</param>
/// <param name="args">Arguments to use with the raised signal.</param>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal,
NativeVariantPtrArgs args)
{
}
internal static IntPtr ClassDB_get_method(StringName type, StringName method)
{
var typeSelf = (godot_string_name)type.NativeValue;
var methodSelf = (godot_string_name)method.NativeValue;
IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodSelf);
if (methodBind == IntPtr.Zero)
throw new NativeMethodBindNotFoundException(type + "." + method);
return methodBind;
}
internal static IntPtr ClassDB_get_method_with_compatibility(StringName type, StringName method, ulong hash)
{
var typeSelf = (godot_string_name)type.NativeValue;
var methodSelf = (godot_string_name)method.NativeValue;
IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method_with_compatibility(typeSelf, methodSelf, hash);
if (methodBind == IntPtr.Zero)
throw new NativeMethodBindNotFoundException(type + "." + method);
return methodBind;
}
internal static unsafe delegate* unmanaged<godot_bool, IntPtr> ClassDB_get_constructor(StringName type)
{
// for some reason the '??' operator doesn't support 'delegate*'
var typeSelf = (godot_string_name)type.NativeValue;
var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(typeSelf);
if (nativeConstructor == null)
throw new NativeConstructorNotFoundException(type);
return nativeConstructor;
}
/// <summary>
/// Saves this instance's state to be restored when reloading assemblies.
/// Do not call or override this method.
/// To add data to be saved and restored, implement <see cref="ISerializationListener"/>.
/// </summary>
/// <param name="info">Object used to save the data.</param>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info)
{
}
// TODO: Should this be a constructor overload?
/// <summary>
/// Restores this instance's state after reloading assemblies.
/// Do not call or override this method.
/// To add data to be saved and restored, implement <see cref="ISerializationListener"/>.
/// </summary>
/// <param name="info">Object that contains the previously saved data.</param>
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info)
{
}
}
}

View File

@@ -0,0 +1,143 @@
using System;
using System.Globalization;
using System.Text;
#nullable enable
namespace Godot
{
public partial class GodotObject
{
public class NativeMemberNotFoundException : Exception
{
public NativeMemberNotFoundException()
{
}
public NativeMemberNotFoundException(string? message) : base(message)
{
}
public NativeMemberNotFoundException(string? message, Exception? innerException)
: base(message, innerException)
{
}
}
public class NativeConstructorNotFoundException : NativeMemberNotFoundException
{
private readonly string? _nativeClassName;
// ReSharper disable once InconsistentNaming
private const string Arg_NativeConstructorNotFoundException = "Unable to find the native constructor.";
public NativeConstructorNotFoundException()
: base(Arg_NativeConstructorNotFoundException)
{
}
public NativeConstructorNotFoundException(string? nativeClassName)
: this(Arg_NativeConstructorNotFoundException, nativeClassName)
{
}
public NativeConstructorNotFoundException(string? message, Exception? innerException)
: base(message, innerException)
{
}
public NativeConstructorNotFoundException(string? message, string? nativeClassName)
: base(message)
{
_nativeClassName = nativeClassName;
}
public NativeConstructorNotFoundException(string? message, string? nativeClassName, Exception? innerException)
: base(message, innerException)
{
_nativeClassName = nativeClassName;
}
public override string Message
{
get
{
StringBuilder sb;
if (string.IsNullOrEmpty(base.Message))
{
sb = new(Arg_NativeConstructorNotFoundException);
}
else
{
sb = new(base.Message);
}
if (!string.IsNullOrEmpty(_nativeClassName))
{
sb.Append(CultureInfo.InvariantCulture, $" (Method '{_nativeClassName}')");
}
return sb.ToString();
}
}
}
public class NativeMethodBindNotFoundException : NativeMemberNotFoundException
{
private readonly string? _nativeMethodName;
// ReSharper disable once InconsistentNaming
private const string Arg_NativeMethodBindNotFoundException = "Unable to find the native method bind.";
public NativeMethodBindNotFoundException()
: base(Arg_NativeMethodBindNotFoundException)
{
}
public NativeMethodBindNotFoundException(string? nativeMethodName)
: this(Arg_NativeMethodBindNotFoundException, nativeMethodName)
{
}
public NativeMethodBindNotFoundException(string? message, Exception? innerException)
: base(message, innerException)
{
}
public NativeMethodBindNotFoundException(string? message, string? nativeMethodName)
: base(message)
{
_nativeMethodName = nativeMethodName;
}
public NativeMethodBindNotFoundException(string? message, string? nativeMethodName, Exception? innerException)
: base(message, innerException)
{
_nativeMethodName = nativeMethodName;
}
public override string Message
{
get
{
StringBuilder sb;
if (string.IsNullOrEmpty(base.Message))
{
sb = new(Arg_NativeMethodBindNotFoundException);
}
else
{
sb = new(base.Message);
}
if (!string.IsNullOrEmpty(_nativeMethodName))
{
sb.Append(CultureInfo.InvariantCulture, $" (Method '{_nativeMethodName}')");
}
return sb.ToString();
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace Godot
{
public sealed class GodotSynchronizationContext : SynchronizationContext, IDisposable
{
private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new();
public override void Send(SendOrPostCallback d, object state)
{
// Shortcut if we're already on this context
// Also necessary to avoid a deadlock, since Send is blocking
if (Current == this)
{
d(state);
return;
}
var source = new TaskCompletionSource();
_queue.Add((st =>
{
try
{
d(st);
}
finally
{
source.SetResult();
}
}, state));
source.Task.Wait();
}
public override void Post(SendOrPostCallback d, object state)
{
_queue.Add((d, state));
}
/// <summary>
/// Calls the Key method on each workItem object in the _queue to activate their callbacks.
/// </summary>
public void ExecutePendingContinuations()
{
while (_queue.TryTake(out var workItem))
{
workItem.Callback(workItem.State);
}
}
public void Dispose()
{
_queue.Dispose();
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Godot
{
/// <summary>
/// GodotTaskScheduler contains a linked list of tasks to perform as a queue. Methods
/// within the class are used to control the queue and perform the contained tasks.
/// </summary>
public sealed class GodotTaskScheduler : TaskScheduler, IDisposable
{
/// <summary>
/// The current synchronization context.
/// </summary>
internal GodotSynchronizationContext Context { get; }
/// <summary>
/// The queue of tasks for the task scheduler.
/// </summary>
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
/// <summary>
/// Constructs a new GodotTaskScheduler instance.
/// </summary>
public GodotTaskScheduler()
{
Context = new GodotSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(Context);
}
protected sealed override void QueueTask(Task task)
{
lock (_tasks)
{
_tasks.AddLast(task);
}
}
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (SynchronizationContext.Current != Context)
return false;
if (taskWasPreviouslyQueued)
TryDequeue(task);
return TryExecuteTask(task);
}
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks)
{
return _tasks.Remove(task);
}
}
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
lock (_tasks)
{
foreach (Task task in _tasks)
yield return task;
}
}
/// <summary>
/// Executes all queued tasks and pending tasks from the current context.
/// </summary>
public void Activate()
{
ExecuteQueuedTasks();
Context.ExecutePendingContinuations();
}
/// <summary>
/// Loops through and attempts to execute each task in _tasks.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
private void ExecuteQueuedTasks()
{
while (true)
{
Task task;
lock (_tasks)
{
if (_tasks.Count > 0)
{
task = _tasks.First.Value;
_tasks.RemoveFirst();
}
else
{
break;
}
}
if (task != null)
{
if (!TryExecuteTask(task))
{
throw new InvalidOperationException();
}
}
}
}
public void Dispose()
{
Context.Dispose();
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Diagnostics;
namespace Godot
{
internal class GodotTraceListener : TraceListener
{
public override void Write(string message)
{
GD.PrintRaw(message);
}
public override void WriteLine(string message)
{
GD.Print(message);
}
public override void Fail(string message, string detailMessage)
{
GD.PrintErr("Assertion failed: ", message);
GD.PrintErr(" Details: ", detailMessage);
try
{
string stackTrace = new StackTrace(true).ToString();
GD.PrintErr(stackTrace);
}
catch
{
// ignored
}
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
{
public static partial class GD
{
[UnmanagedCallersOnly]
internal static void OnCoreApiAssemblyLoaded(godot_bool isDebug)
{
try
{
Dispatcher.InitializeDefaultGodotTaskScheduler();
if (isDebug.ToBool())
{
DebuggingUtils.InstallTraceListener();
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
// Exception.ToString() includes the inner exception
ExceptionUtils.LogUnhandledException((Exception)e.ExceptionObject);
};
}
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
}
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Godot
{
/// <summary>
/// An interface that requires a GetAwaiter() method to get a reference to the Awaiter.
/// </summary>
public interface IAwaitable
{
IAwaiter GetAwaiter();
}
/// <summary>
/// A templated interface that requires a GetAwaiter() method to get a reference to the Awaiter.
/// </summary>
/// <typeparam name="TResult">A reference to the result to be passed out.</typeparam>
public interface IAwaitable<out TResult>
{
IAwaiter<TResult> GetAwaiter();
}
}

View File

@@ -0,0 +1,25 @@
using System.Runtime.CompilerServices;
namespace Godot
{
/// <summary>
/// An interface that requires a boolean for completion status and a method that gets the result of completion.
/// </summary>
public interface IAwaiter : INotifyCompletion
{
bool IsCompleted { get; }
void GetResult();
}
/// <summary>
/// A templated interface that requires a boolean for completion status and a method that gets the result of completion and returns it.
/// </summary>
/// <typeparam name="TResult">A reference to the result to be passed out.</typeparam>
public interface IAwaiter<out TResult> : INotifyCompletion
{
bool IsCompleted { get; }
TResult GetResult();
}
}

View File

@@ -0,0 +1,21 @@
namespace Godot
{
/// <summary>
/// Allows a GodotObject to react to the serialization/deserialization
/// that occurs when Godot reloads assemblies.
/// </summary>
public interface ISerializationListener
{
/// <summary>
/// Executed before serializing this instance's state when reloading assemblies.
/// Clear any data that should not be serialized.
/// </summary>
void OnBeforeSerialize();
/// <summary>
/// Executed after deserializing this instance's state after reloading assemblies.
/// Restore any state that has been lost.
/// </summary>
void OnAfterDeserialize();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,199 @@
using System;
using System.Runtime.CompilerServices;
// This file contains extra members for the Mathf class that aren't part of Godot's Core API.
// Math API that is also part of Core should go into Mathf.cs.
namespace Godot
{
public static partial class Mathf
{
// Define constants with Decimal precision and cast down to double or float.
/// <summary>
/// The natural number <c>e</c>.
/// </summary>
public const real_t E = (real_t)2.7182818284590452353602874714M; // 2.7182817f and 2.718281828459045
/// <summary>
/// The square root of 2.
/// </summary>
public const real_t Sqrt2 = (real_t)1.4142135623730950488016887242M; // 1.4142136f and 1.414213562373095
// Epsilon size should depend on the precision used.
private const float EpsilonF = 1e-06f;
private const double EpsilonD = 1e-14;
/// <summary>
/// A very small number used for float comparison with error tolerance.
/// 1e-06 with single-precision floats, but 1e-14 if <c>REAL_T_IS_DOUBLE</c>.
/// </summary>
#if REAL_T_IS_DOUBLE
public const real_t Epsilon = EpsilonD;
#else
public const real_t Epsilon = EpsilonF;
#endif
/// <summary>
/// Returns the amount of digits after the decimal place.
/// </summary>
/// <param name="s">The input value.</param>
/// <returns>The amount of digits.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int DecimalCount(double s)
{
return DecimalCount((decimal)s);
}
/// <summary>
/// Returns the amount of digits after the decimal place.
/// </summary>
/// <param name="s">The input <see langword="decimal"/> value.</param>
/// <returns>The amount of digits.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int DecimalCount(decimal s)
{
return BitConverter.GetBytes(decimal.GetBits(s)[3])[2];
}
/// <summary>
/// Rounds <paramref name="s"/> upward (towards positive infinity).
///
/// This is the same as <see cref="Ceil(float)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to ceil.</param>
/// <returns>The smallest whole number that is not less than <paramref name="s"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CeilToInt(float s)
{
return (int)MathF.Ceiling(s);
}
/// <summary>
/// Rounds <paramref name="s"/> upward (towards positive infinity).
///
/// This is the same as <see cref="Ceil(double)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to ceil.</param>
/// <returns>The smallest whole number that is not less than <paramref name="s"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CeilToInt(double s)
{
return (int)Math.Ceiling(s);
}
/// <summary>
/// Rounds <paramref name="s"/> downward (towards negative infinity).
///
/// This is the same as <see cref="Floor(float)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to floor.</param>
/// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FloorToInt(float s)
{
return (int)MathF.Floor(s);
}
/// <summary>
/// Rounds <paramref name="s"/> downward (towards negative infinity).
///
/// This is the same as <see cref="Floor(double)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to floor.</param>
/// <returns>The largest whole number that is not more than <paramref name="s"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FloorToInt(double s)
{
return (int)Math.Floor(s);
}
/// <summary>
/// Rounds <paramref name="s"/> to the nearest whole number.
///
/// This is the same as <see cref="Round(float)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to round.</param>
/// <returns>The rounded number.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(float s)
{
return (int)MathF.Round(s);
}
/// <summary>
/// Rounds <paramref name="s"/> to the nearest whole number.
///
/// This is the same as <see cref="Round(double)"/>, but returns an <see langword="int"/>.
/// </summary>
/// <param name="s">The number to round.</param>
/// <returns>The rounded number.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(double s)
{
return (int)Math.Round(s);
}
/// <summary>
/// Returns the sine and cosine of angle <paramref name="s"/> in radians.
/// </summary>
/// <param name="s">The angle in radians.</param>
/// <returns>The sine and cosine of that angle.</returns>
public static (float Sin, float Cos) SinCos(float s)
{
return MathF.SinCos(s);
}
/// <summary>
/// Returns the sine and cosine of angle <paramref name="s"/> in radians.
/// </summary>
/// <param name="s">The angle in radians.</param>
/// <returns>The sine and cosine of that angle.</returns>
public static (double Sin, double Cos) SinCos(double s)
{
return Math.SinCos(s);
}
/// <summary>
/// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="b"/> are approximately
/// equal to each other.
/// The comparison is done using the provided tolerance value.
/// If you want the tolerance to be calculated for you, use <see cref="IsEqualApprox(float, float)"/>.
/// </summary>
/// <param name="a">One of the values.</param>
/// <param name="b">The other value.</param>
/// <param name="tolerance">The pre-calculated tolerance value.</param>
/// <returns>A <see langword="bool"/> for whether or not the two values are equal.</returns>
public static bool IsEqualApprox(float a, float b, float tolerance)
{
// Check for exact equality first, required to handle "infinity" values.
if (a == b)
{
return true;
}
// Then check for approximate equality.
return Math.Abs(a - b) < tolerance;
}
/// <summary>
/// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="b"/> are approximately
/// equal to each other.
/// The comparison is done using the provided tolerance value.
/// If you want the tolerance to be calculated for you, use <see cref="IsEqualApprox(double, double)"/>.
/// </summary>
/// <param name="a">One of the values.</param>
/// <param name="b">The other value.</param>
/// <param name="tolerance">The pre-calculated tolerance value.</param>
/// <returns>A <see langword="bool"/> for whether or not the two values are equal.</returns>
public static bool IsEqualApprox(double a, double b, double tolerance)
{
// Check for exact equality first, required to handle "infinity" values.
if (a == b)
{
return true;
}
// Then check for approximate equality.
return Math.Abs(a - b) < tolerance;
}
}
}

View File

@@ -0,0 +1,329 @@
using System.Runtime.CompilerServices;
namespace Godot.NativeInterop;
// Ref structs are not allowed as generic type parameters, so we can't use Unsafe.AsPointer<T>/AsRef<T>.
// As a workaround we create our own overloads for our structs with some tricks under the hood.
public static class CustomUnsafe
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_ref* AsPointer(ref godot_ref value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_ref* ReadOnlyRefAsPointer(in godot_ref value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_ref AsRef(godot_ref* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_ref AsRef(in godot_ref source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_variant_call_error* AsPointer(ref godot_variant_call_error value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_variant_call_error* ReadOnlyRefAsPointer(in godot_variant_call_error value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_variant_call_error AsRef(godot_variant_call_error* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_variant_call_error AsRef(in godot_variant_call_error source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_variant* AsPointer(ref godot_variant value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_variant* ReadOnlyRefAsPointer(in godot_variant value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_variant AsRef(godot_variant* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_variant AsRef(in godot_variant source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_string* AsPointer(ref godot_string value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_string* ReadOnlyRefAsPointer(in godot_string value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_string AsRef(godot_string* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_string AsRef(in godot_string source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_string_name* AsPointer(ref godot_string_name value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_string_name* ReadOnlyRefAsPointer(in godot_string_name value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_string_name AsRef(godot_string_name* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_string_name AsRef(in godot_string_name source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_node_path* AsPointer(ref godot_node_path value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_node_path* ReadOnlyRefAsPointer(in godot_node_path value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_node_path AsRef(godot_node_path* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_node_path AsRef(in godot_node_path source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_signal* AsPointer(ref godot_signal value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_signal* ReadOnlyRefAsPointer(in godot_signal value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_signal AsRef(godot_signal* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_signal AsRef(in godot_signal source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_callable* AsPointer(ref godot_callable value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_callable* ReadOnlyRefAsPointer(in godot_callable value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_callable AsRef(godot_callable* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_callable AsRef(in godot_callable source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_array* AsPointer(ref godot_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_array* ReadOnlyRefAsPointer(in godot_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_array AsRef(godot_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_array AsRef(in godot_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_dictionary* AsPointer(ref godot_dictionary value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_dictionary* ReadOnlyRefAsPointer(in godot_dictionary value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_dictionary AsRef(godot_dictionary* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_dictionary AsRef(in godot_dictionary source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_byte_array* AsPointer(ref godot_packed_byte_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_byte_array* ReadOnlyRefAsPointer(in godot_packed_byte_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_byte_array AsRef(godot_packed_byte_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_byte_array AsRef(in godot_packed_byte_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_int32_array* AsPointer(ref godot_packed_int32_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_int32_array* ReadOnlyRefAsPointer(in godot_packed_int32_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_int32_array AsRef(godot_packed_int32_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_int32_array AsRef(in godot_packed_int32_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_int64_array* AsPointer(ref godot_packed_int64_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_int64_array* ReadOnlyRefAsPointer(in godot_packed_int64_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_int64_array AsRef(godot_packed_int64_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_int64_array AsRef(in godot_packed_int64_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_float32_array* AsPointer(ref godot_packed_float32_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_float32_array* ReadOnlyRefAsPointer(in godot_packed_float32_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_float32_array AsRef(godot_packed_float32_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_float32_array AsRef(in godot_packed_float32_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_float64_array* AsPointer(ref godot_packed_float64_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_float64_array* ReadOnlyRefAsPointer(in godot_packed_float64_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_float64_array AsRef(godot_packed_float64_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_float64_array AsRef(in godot_packed_float64_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_string_array* AsPointer(ref godot_packed_string_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_string_array* ReadOnlyRefAsPointer(in godot_packed_string_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_string_array AsRef(godot_packed_string_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_string_array AsRef(in godot_packed_string_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector2_array* AsPointer(ref godot_packed_vector2_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector2_array* ReadOnlyRefAsPointer(in godot_packed_vector2_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector2_array AsRef(godot_packed_vector2_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector2_array AsRef(in godot_packed_vector2_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector3_array* AsPointer(ref godot_packed_vector3_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector3_array* ReadOnlyRefAsPointer(in godot_packed_vector3_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector3_array AsRef(godot_packed_vector3_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector3_array AsRef(in godot_packed_vector3_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector4_array* AsPointer(ref godot_packed_vector4_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_vector4_array* ReadOnlyRefAsPointer(in godot_packed_vector4_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector4_array AsRef(godot_packed_vector4_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_vector4_array AsRef(in godot_packed_vector4_array source)
=> ref *ReadOnlyRefAsPointer(in source);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_color_array* AsPointer(ref godot_packed_color_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe godot_packed_color_array* ReadOnlyRefAsPointer(in godot_packed_color_array value)
=> value.GetUnsafeAddress();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_color_array AsRef(godot_packed_color_array* source)
=> ref *source;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref godot_packed_color_array AsRef(in godot_packed_color_array source)
=> ref *ReadOnlyRefAsPointer(in source);
}

View File

@@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
#nullable enable
namespace Godot.NativeInterop
{
internal static class ExceptionUtils
{
public static void PushError(string message)
{
GD.PushError(message);
}
private static void OnExceptionLoggerException(Exception loggerException, Exception exceptionToLog)
{
try
{
// This better not throw
PushError(string.Concat("Exception thrown while trying to log another exception...",
"\n### Exception ###\n", exceptionToLog.ToString(),
"\n### Logger exception ###\n", loggerException.ToString()));
}
catch (Exception)
{
// Well, too bad...
}
}
private record struct StackInfoTuple(string? File, string Func, int Line);
private static void CollectExceptionInfo(Exception exception, List<StackInfoTuple> globalFrames,
StringBuilder excMsg)
{
if (excMsg.Length > 0)
excMsg.Append(" ---> ");
excMsg.Append(exception.GetType().FullName);
excMsg.Append(": ");
excMsg.Append(exception.Message);
var innerExc = exception.InnerException;
if (innerExc != null)
{
CollectExceptionInfo(innerExc, globalFrames, excMsg);
globalFrames.Add(new("", "--- End of inner exception stack trace ---", 0));
}
var stackTrace = new StackTrace(exception, fNeedFileInfo: true);
foreach (StackFrame frame in stackTrace.GetFrames())
{
DebuggingUtils.GetStackFrameMethodDecl(frame, out string methodDecl);
globalFrames.Add(new(frame.GetFileName(), methodDecl, frame.GetFileLineNumber()));
}
}
private static void SendToScriptDebugger(Exception e)
{
var globalFrames = new List<StackInfoTuple>();
var excMsg = new StringBuilder();
CollectExceptionInfo(e, globalFrames, excMsg);
string file = globalFrames.Count > 0 ? globalFrames[0].File ?? "" : "";
string func = globalFrames.Count > 0 ? globalFrames[0].Func : "";
int line = globalFrames.Count > 0 ? globalFrames[0].Line : 0;
string errorMsg = e.GetType().FullName ?? "";
using godot_string nFile = Marshaling.ConvertStringToNative(file);
using godot_string nFunc = Marshaling.ConvertStringToNative(func);
using godot_string nErrorMsg = Marshaling.ConvertStringToNative(errorMsg);
using godot_string nExcMsg = Marshaling.ConvertStringToNative(excMsg.ToString());
using DebuggingUtils.godot_stack_info_vector stackInfoVector = default;
stackInfoVector.Resize(globalFrames.Count);
unsafe
{
for (int i = 0; i < globalFrames.Count; i++)
{
DebuggingUtils.godot_stack_info* stackInfo = &stackInfoVector.Elements[i];
var globalFrame = globalFrames[i];
// Assign directly to element in Vector. This way we don't need to worry
// about disposal if an exception is thrown. The Vector takes care of it.
stackInfo->File = Marshaling.ConvertStringToNative(globalFrame.File);
stackInfo->Func = Marshaling.ConvertStringToNative(globalFrame.Func);
stackInfo->Line = globalFrame.Line;
}
NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line,
nErrorMsg, nExcMsg, godot_error_handler_type.ERR_HANDLER_ERROR, stackInfoVector);
}
}
public static void LogException(Exception e)
{
try
{
if (NativeFuncs.godotsharp_internal_script_debugger_is_active().ToBool())
{
SendToScriptDebugger(e);
}
else
{
GD.PushError(e.ToString());
}
}
catch (Exception unexpected)
{
OnExceptionLoggerException(unexpected, e);
}
}
public static void LogUnhandledException(Exception e)
{
try
{
if (NativeFuncs.godotsharp_internal_script_debugger_is_active().ToBool())
{
SendToScriptDebugger(e);
}
// In this case, print it as well in addition to sending it to the script debugger
GD.PushError("Unhandled exception\n" + e);
}
catch (Exception unexpected)
{
OnExceptionLoggerException(unexpected, e);
}
}
[Conditional("DEBUG")]
public unsafe static void DebugCheckCallError(godot_string_name method, IntPtr instance, godot_variant** args, int argCount, godot_variant_call_error error)
{
if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
{
using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance);
string where = GetCallErrorWhere(ref error, method, &instanceVariant, args, argCount);
string errorText = GetCallErrorMessage(error, where, args);
GD.PushError(errorText);
}
}
[Conditional("DEBUG")]
public unsafe static void DebugCheckCallError(in godot_callable callable, godot_variant** args, int argCount, godot_variant_call_error error)
{
if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK)
{
using godot_variant callableVariant = VariantUtils.CreateFromCallableTakingOwnershipOfDisposableValue(callable);
string where = $"callable '{VariantUtils.ConvertToString(callableVariant)}'";
string errorText = GetCallErrorMessage(error, where, args);
GD.PushError(errorText);
}
}
private unsafe static string GetCallErrorWhere(ref godot_variant_call_error error, godot_string_name method, godot_variant* instance, godot_variant** args, int argCount)
{
string? methodstr = null;
string basestr = GetVariantTypeName(instance);
if (method == GodotObject.MethodName.Call || (basestr == "Godot.TreeItem" && method == TreeItem.MethodName.CallRecursive))
{
if (argCount >= 1)
{
methodstr = VariantUtils.ConvertToString(*args[0]);
if (error.Error == godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT)
{
error.Argument += 1;
}
}
}
if (string.IsNullOrEmpty(methodstr))
{
methodstr = StringName.CreateTakingOwnershipOfDisposableValue(method);
}
return $"function '{methodstr}' in base '{basestr}'";
}
private unsafe static string GetCallErrorMessage(godot_variant_call_error error, string where, godot_variant** args)
{
switch (error.Error)
{
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT:
{
int errorarg = error.Argument;
// Handle the Object to Object case separately as we don't have further class details.
#if DEBUG
if (error.Expected == Variant.Type.Object && args[errorarg]->Type == error.Expected)
{
return $"Invalid type in {where}. The Object-derived class of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") is not a subclass of the expected argument class.";
}
else if (error.Expected == Variant.Type.Array && args[errorarg]->Type == error.Expected)
{
return $"Invalid type in {where}. The array of argument {errorarg + 1} (" + GetVariantTypeName(args[errorarg]) + ") does not have the same element type as the expected typed array argument.";
}
else
#endif
{
return $"Invalid type in {where}. Cannot convert argument {errorarg + 1} from {args[errorarg]->Type} to {error.Expected}.";
}
}
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS:
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS:
return $"Invalid call to {where}. Expected {error.Expected} argument(s).";
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD:
return $"Invalid call. Nonexistent {where}.";
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL:
return $"Attempt to call {where} on a null instance.";
case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_METHOD_NOT_CONST:
return $"Attempt to call {where} on a const instance.";
default:
return $"Bug, call error: #{error.Error}";
}
}
private unsafe static string GetVariantTypeName(godot_variant* variant)
{
if (variant->Type == Variant.Type.Object)
{
GodotObject obj = VariantUtils.ConvertToGodotObject(*variant);
if (obj == null)
{
return "null instance";
}
else if (!GodotObject.IsInstanceValid(obj))
{
return "previously freed";
}
else
{
return obj.GetType().ToString();
}
}
return variant->Type.ToString();
}
internal static void ThrowIfNullPtr(IntPtr ptr, [CallerArgumentExpression("ptr")] string? paramName = null)
{
if (ptr == IntPtr.Zero)
{
throw new ArgumentNullException(paramName);
}
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot.NativeInterop
{
public class GodotDllImportResolver
{
private IntPtr _internalHandle;
public GodotDllImportResolver(IntPtr internalHandle)
{
_internalHandle = internalHandle;
}
public IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName == "__Internal")
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Win32.GetModuleHandle(IntPtr.Zero);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return _internalHandle;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY);
}
}
return IntPtr.Zero;
}
// ReSharper disable InconsistentNaming
private static class MacOS
{
private const string SystemLibrary = "/usr/lib/libSystem.dylib";
public const int RTLD_LAZY = 1;
[DllImport(SystemLibrary)]
public static extern IntPtr dlopen(IntPtr path, int mode);
}
private static class Win32
{
private const string SystemLibrary = "Kernel32.dll";
[DllImport(SystemLibrary)]
public static extern IntPtr GetModuleHandle(IntPtr lpModuleName);
}
// ReSharper restore InconsistentNaming
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
using System;
using System.Runtime.InteropServices;
using Godot.Bridge;
// ReSharper disable InconsistentNaming
namespace Godot.NativeInterop
{
internal static class InteropUtils
{
public static GodotObject UnmanagedGetManaged(IntPtr unmanaged)
{
// The native pointer may be null
if (unmanaged == IntPtr.Zero)
return null;
IntPtr gcHandlePtr;
godot_bool hasCsScriptInstance;
// First try to get the tied managed instance from a CSharpInstance script instance
gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_script_instance_managed(
unmanaged, out hasCsScriptInstance);
if (gcHandlePtr != IntPtr.Zero)
return (GodotObject)GCHandle.FromIntPtr(gcHandlePtr).Target;
// Otherwise, if the object has a CSharpInstance script instance, return null
if (hasCsScriptInstance.ToBool())
return null;
// If it doesn't have a CSharpInstance script instance, try with native instance bindings
gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_instance_binding_managed(unmanaged);
object target = gcHandlePtr != IntPtr.Zero ? GCHandle.FromIntPtr(gcHandlePtr).Target : null;
if (target != null)
return (GodotObject)target;
// If the native instance binding GC handle target was collected, create a new one
gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_instance_binding_create_managed(
unmanaged, gcHandlePtr);
return gcHandlePtr != IntPtr.Zero ? (GodotObject)GCHandle.FromIntPtr(gcHandlePtr).Target : null;
}
public static void TieManagedToUnmanaged(GodotObject managed, IntPtr unmanaged,
StringName nativeName, bool refCounted, Type type, Type nativeType)
{
var gcHandle = refCounted ?
CustomGCHandle.AllocWeak(managed) :
CustomGCHandle.AllocStrong(managed, type);
if (type == nativeType)
{
var nativeNameSelf = (godot_string_name)nativeName.NativeValue;
NativeFuncs.godotsharp_internal_tie_native_managed_to_unmanaged(
GCHandle.ToIntPtr(gcHandle), unmanaged, nativeNameSelf, refCounted.ToGodotBool());
}
else
{
unsafe
{
// We don't dispose `script` ourselves here.
// `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call.
godot_ref script;
ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script);
// IMPORTANT: This must be called after GetOrCreateScriptBridgeForType
NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged(
GCHandle.ToIntPtr(gcHandle), unmanaged, &script, refCounted.ToGodotBool());
}
}
}
public static void TieManagedToUnmanagedWithPreSetup(GodotObject managed, IntPtr unmanaged,
Type type, Type nativeType)
{
if (type == nativeType)
return;
var strongGCHandle = CustomGCHandle.AllocStrong(managed);
NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
GCHandle.ToIntPtr(strongGCHandle), unmanaged);
}
public static GodotObject EngineGetSingleton(string name)
{
using godot_string src = Marshaling.ConvertStringToNative(name);
return UnmanagedGetManaged(NativeFuncs.godotsharp_engine_get_singleton(src));
}
}
}

View File

@@ -0,0 +1,710 @@
#pragma warning disable CA1707 // Identifiers should not contain underscores
using System;
using System.Runtime.InteropServices;
using Godot.Collections;
using Array = System.Array;
// ReSharper disable InconsistentNaming
// We want to use full name qualifiers here even if redundant for clarity
// ReSharper disable RedundantNameQualifier
#nullable enable
namespace Godot.NativeInterop
{
public static class Marshaling
{
internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool r_nil_is_variant)
{
r_nil_is_variant = false;
switch (Type.GetTypeCode(type))
{
case TypeCode.Boolean:
return Variant.Type.Bool;
case TypeCode.Char:
return Variant.Type.Int;
case TypeCode.SByte:
return Variant.Type.Int;
case TypeCode.Int16:
return Variant.Type.Int;
case TypeCode.Int32:
return Variant.Type.Int;
case TypeCode.Int64:
return Variant.Type.Int;
case TypeCode.Byte:
return Variant.Type.Int;
case TypeCode.UInt16:
return Variant.Type.Int;
case TypeCode.UInt32:
return Variant.Type.Int;
case TypeCode.UInt64:
return Variant.Type.Int;
case TypeCode.Single:
return Variant.Type.Float;
case TypeCode.Double:
return Variant.Type.Float;
case TypeCode.String:
return Variant.Type.String;
default:
{
if (type == typeof(Vector2))
return Variant.Type.Vector2;
if (type == typeof(Vector2I))
return Variant.Type.Vector2I;
if (type == typeof(Rect2))
return Variant.Type.Rect2;
if (type == typeof(Rect2I))
return Variant.Type.Rect2I;
if (type == typeof(Transform2D))
return Variant.Type.Transform2D;
if (type == typeof(Vector3))
return Variant.Type.Vector3;
if (type == typeof(Vector3I))
return Variant.Type.Vector3I;
if (type == typeof(Vector4))
return Variant.Type.Vector4;
if (type == typeof(Vector4I))
return Variant.Type.Vector4I;
if (type == typeof(Basis))
return Variant.Type.Basis;
if (type == typeof(Quaternion))
return Variant.Type.Quaternion;
if (type == typeof(Transform3D))
return Variant.Type.Transform3D;
if (type == typeof(Projection))
return Variant.Type.Projection;
if (type == typeof(Aabb))
return Variant.Type.Aabb;
if (type == typeof(Color))
return Variant.Type.Color;
if (type == typeof(Plane))
return Variant.Type.Plane;
if (type == typeof(Callable))
return Variant.Type.Callable;
if (type == typeof(Signal))
return Variant.Type.Signal;
if (type.IsEnum)
return Variant.Type.Int;
if (type.IsArray || type.IsSZArray)
{
if (type == typeof(byte[]))
return Variant.Type.PackedByteArray;
if (type == typeof(int[]))
return Variant.Type.PackedInt32Array;
if (type == typeof(long[]))
return Variant.Type.PackedInt64Array;
if (type == typeof(float[]))
return Variant.Type.PackedFloat32Array;
if (type == typeof(double[]))
return Variant.Type.PackedFloat64Array;
if (type == typeof(string[]))
return Variant.Type.PackedStringArray;
if (type == typeof(Vector2[]))
return Variant.Type.PackedVector2Array;
if (type == typeof(Vector3[]))
return Variant.Type.PackedVector3Array;
if (type == typeof(Vector4[]))
return Variant.Type.PackedVector4Array;
if (type == typeof(Color[]))
return Variant.Type.PackedColorArray;
if (type == typeof(StringName[]))
return Variant.Type.Array;
if (type == typeof(NodePath[]))
return Variant.Type.Array;
if (type == typeof(Rid[]))
return Variant.Type.Array;
if (typeof(GodotObject[]).IsAssignableFrom(type))
return Variant.Type.Array;
}
else if (type.IsGenericType)
{
if (typeof(GodotObject).IsAssignableFrom(type))
return Variant.Type.Object;
// We use `IsAssignableFrom` with our helper interfaces to detect generic Godot collections
// because `GetGenericTypeDefinition` is not supported in NativeAOT reflection-free mode.
if (typeof(IGenericGodotDictionary).IsAssignableFrom(type))
return Variant.Type.Dictionary;
if (typeof(IGenericGodotArray).IsAssignableFrom(type))
return Variant.Type.Array;
}
else if (type == typeof(Variant))
{
r_nil_is_variant = true;
return Variant.Type.Nil;
}
else
{
if (typeof(GodotObject).IsAssignableFrom(type))
return Variant.Type.Object;
if (typeof(StringName) == type)
return Variant.Type.StringName;
if (typeof(NodePath) == type)
return Variant.Type.NodePath;
if (typeof(Rid) == type)
return Variant.Type.Rid;
if (typeof(Collections.Dictionary) == type)
return Variant.Type.Dictionary;
if (typeof(Collections.Array) == type)
return Variant.Type.Array;
}
break;
}
}
// Unknown
return Variant.Type.Nil;
}
internal static void GetTypedCollectionParameterInfo<T>(
out Variant.Type variantType,
out godot_string_name className,
out godot_ref script)
{
variantType = ConvertManagedTypeToVariantType(typeof(T), out _);
if (variantType != Variant.Type.Object)
{
className = default;
script = default;
return;
}
godot_ref scriptRef = default;
if (!GodotObject.IsNativeClass(typeof(T)))
{
unsafe
{
Godot.Bridge.ScriptManagerBridge.GetOrLoadOrCreateScriptForType(typeof(T), &scriptRef);
}
// Don't call GodotObject.InternalGetClassNativeBaseName here!
// godot_dictionary_set_typed and godot_array_set_typed will call CSharpScript::get_instance_base_type
// when a script is passed, because this is better for performance than using reflection to find the
// native base type.
className = default;
}
else
{
StringName? nativeBaseName = GodotObject.InternalGetClassNativeBaseName(typeof(T));
className = nativeBaseName != null ? (godot_string_name)nativeBaseName.NativeValue : default;
}
script = scriptRef;
}
// String
public static unsafe godot_string ConvertStringToNative(string? p_mono_string)
{
if (p_mono_string == null)
return new godot_string();
fixed (char* methodChars = p_mono_string)
{
NativeFuncs.godotsharp_string_new_with_utf16_chars(out godot_string dest, methodChars);
return dest;
}
}
public static unsafe string ConvertStringToManaged(in godot_string p_string)
{
if (p_string.Buffer == IntPtr.Zero)
return string.Empty;
const int SizeOfChar32 = 4;
byte* bytes = (byte*)p_string.Buffer;
int size = p_string.Size;
if (size == 0)
return string.Empty;
size -= 1; // zero at the end
int sizeInBytes = size * SizeOfChar32;
return System.Text.Encoding.UTF32.GetString(bytes, sizeInBytes);
}
// Callable
public static godot_callable ConvertCallableToNative(scoped in Callable p_managed_callable)
{
if (p_managed_callable.Delegate != null)
{
var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
IntPtr objectPtr = p_managed_callable.Target != null ?
GodotObject.GetPtr(p_managed_callable.Target) :
IntPtr.Zero;
unsafe
{
NativeFuncs.godotsharp_callable_new_with_delegate(
GCHandle.ToIntPtr(gcHandle), (IntPtr)p_managed_callable.Trampoline,
objectPtr, out godot_callable callable);
return callable;
}
}
else
{
godot_string_name method;
if (p_managed_callable.Method != null && !p_managed_callable.Method.IsEmpty)
{
var src = (godot_string_name)p_managed_callable.Method.NativeValue;
method = NativeFuncs.godotsharp_string_name_new_copy(src);
}
else
{
method = default;
}
return new godot_callable(method /* Takes ownership of disposable */,
p_managed_callable.Target?.GetInstanceId() ?? 0);
}
}
public static Callable ConvertCallableToManaged(in godot_callable p_callable)
{
if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable,
out IntPtr delegateGCHandle, out IntPtr trampoline,
out IntPtr godotObject, out godot_string_name name).ToBool())
{
if (delegateGCHandle != IntPtr.Zero)
{
unsafe
{
return Callable.CreateWithUnsafeTrampoline(
(Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target,
(delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline);
}
}
return new Callable(
InteropUtils.UnmanagedGetManaged(godotObject),
StringName.CreateTakingOwnershipOfDisposableValue(name));
}
// Some other unsupported callable
return new Callable();
}
// Signal
public static godot_signal ConvertSignalToNative(scoped in Signal p_managed_signal)
{
ulong ownerId = p_managed_signal.Owner.GetInstanceId();
godot_string_name name;
if (p_managed_signal.Name != null && !p_managed_signal.Name.IsEmpty)
{
var src = (godot_string_name)p_managed_signal.Name.NativeValue;
name = NativeFuncs.godotsharp_string_name_new_copy(src);
}
else
{
name = default;
}
return new godot_signal(name, ownerId);
}
public static Signal ConvertSignalToManaged(in godot_signal p_signal)
{
var owner = GodotObject.InstanceFromId(p_signal.ObjectId);
var name = StringName.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_string_name_new_copy(p_signal.Name));
return new Signal(owner, name);
}
// Array
internal static T[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(in godot_array p_array)
where T : GodotObject
{
var array = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(p_array));
int length = array.Count;
var ret = new T[length];
for (int i = 0; i < length; i++)
ret[i] = (T)array[i].AsGodotObject();
return ret;
}
internal static StringName[] ConvertNativeGodotArrayToSystemArrayOfStringName(in godot_array p_array)
{
var array = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(p_array));
int length = array.Count;
var ret = new StringName[length];
for (int i = 0; i < length; i++)
ret[i] = array[i].AsStringName();
return ret;
}
internal static NodePath[] ConvertNativeGodotArrayToSystemArrayOfNodePath(in godot_array p_array)
{
var array = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(p_array));
int length = array.Count;
var ret = new NodePath[length];
for (int i = 0; i < length; i++)
ret[i] = array[i].AsNodePath();
return ret;
}
internal static Rid[] ConvertNativeGodotArrayToSystemArrayOfRid(in godot_array p_array)
{
var array = Collections.Array.CreateTakingOwnershipOfDisposableValue(
NativeFuncs.godotsharp_array_new_copy(p_array));
int length = array.Count;
var ret = new Rid[length];
for (int i = 0; i < length; i++)
ret[i] = array[i].AsRid();
return ret;
}
// PackedByteArray
public static unsafe byte[] ConvertNativePackedByteArrayToSystemArray(in godot_packed_byte_array p_array)
{
byte* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<byte>();
var array = new byte[size];
fixed (byte* dest = array)
Buffer.MemoryCopy(buffer, dest, size, size);
return array;
}
public static godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(scoped Span<byte> p_array)
{
return ConvertSystemArrayToNativePackedByteArray((ReadOnlySpan<byte>)p_array);
}
public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(scoped ReadOnlySpan<byte> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_byte_array();
fixed (byte* src = p_array)
return NativeFuncs.godotsharp_packed_byte_array_new_mem_copy(src, p_array.Length);
}
// PackedInt32Array
public static unsafe int[] ConvertNativePackedInt32ArrayToSystemArray(godot_packed_int32_array p_array)
{
int* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<int>();
int sizeInBytes = size * sizeof(int);
var array = new int[size];
fixed (int* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(scoped Span<int> p_array)
{
return ConvertSystemArrayToNativePackedInt32Array((ReadOnlySpan<int>)p_array);
}
public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(scoped ReadOnlySpan<int> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int32_array();
fixed (int* src = p_array)
return NativeFuncs.godotsharp_packed_int32_array_new_mem_copy(src, p_array.Length);
}
// PackedInt64Array
public static unsafe long[] ConvertNativePackedInt64ArrayToSystemArray(godot_packed_int64_array p_array)
{
long* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<long>();
int sizeInBytes = size * sizeof(long);
var array = new long[size];
fixed (long* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(scoped Span<long> p_array)
{
return ConvertSystemArrayToNativePackedInt64Array((ReadOnlySpan<long>)p_array);
}
public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(scoped ReadOnlySpan<long> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int64_array();
fixed (long* src = p_array)
return NativeFuncs.godotsharp_packed_int64_array_new_mem_copy(src, p_array.Length);
}
// PackedFloat32Array
public static unsafe float[] ConvertNativePackedFloat32ArrayToSystemArray(godot_packed_float32_array p_array)
{
float* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<float>();
int sizeInBytes = size * sizeof(float);
var array = new float[size];
fixed (float* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(scoped Span<float> p_array)
{
return ConvertSystemArrayToNativePackedFloat32Array((ReadOnlySpan<float>)p_array);
}
public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(scoped ReadOnlySpan<float> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float32_array();
fixed (float* src = p_array)
return NativeFuncs.godotsharp_packed_float32_array_new_mem_copy(src, p_array.Length);
}
// PackedFloat64Array
public static unsafe double[] ConvertNativePackedFloat64ArrayToSystemArray(godot_packed_float64_array p_array)
{
double* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<double>();
int sizeInBytes = size * sizeof(double);
var array = new double[size];
fixed (double* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(scoped Span<double> p_array)
{
return ConvertSystemArrayToNativePackedFloat64Array((ReadOnlySpan<double>)p_array);
}
public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(scoped ReadOnlySpan<double> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float64_array();
fixed (double* src = p_array)
return NativeFuncs.godotsharp_packed_float64_array_new_mem_copy(src, p_array.Length);
}
// PackedStringArray
public static unsafe string[] ConvertNativePackedStringArrayToSystemArray(godot_packed_string_array p_array)
{
godot_string* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<string>();
var array = new string[size];
for (int i = 0; i < size; i++)
array[i] = ConvertStringToManaged(buffer[i]);
return array;
}
public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(scoped Span<string> p_array)
{
return ConvertSystemArrayToNativePackedStringArray((ReadOnlySpan<string>)p_array);
}
public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(scoped ReadOnlySpan<string> p_array)
{
godot_packed_string_array dest = new godot_packed_string_array();
if (p_array.IsEmpty)
return dest;
/* TODO: Replace godotsharp_packed_string_array_add with a single internal call to
get the write address. We can't use `dest._ptr` directly for writing due to COW. */
for (int i = 0; i < p_array.Length; i++)
{
using godot_string godotStrElem = ConvertStringToNative(p_array[i]);
NativeFuncs.godotsharp_packed_string_array_add(ref dest, godotStrElem);
}
return dest;
}
// PackedVector2Array
public static unsafe Vector2[] ConvertNativePackedVector2ArrayToSystemArray(godot_packed_vector2_array p_array)
{
Vector2* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<Vector2>();
int sizeInBytes = size * sizeof(Vector2);
var array = new Vector2[size];
fixed (Vector2* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(scoped Span<Vector2> p_array)
{
return ConvertSystemArrayToNativePackedVector2Array((ReadOnlySpan<Vector2>)p_array);
}
public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(scoped ReadOnlySpan<Vector2> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector2_array();
fixed (Vector2* src = p_array)
return NativeFuncs.godotsharp_packed_vector2_array_new_mem_copy(src, p_array.Length);
}
// PackedVector3Array
public static unsafe Vector3[] ConvertNativePackedVector3ArrayToSystemArray(godot_packed_vector3_array p_array)
{
Vector3* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<Vector3>();
int sizeInBytes = size * sizeof(Vector3);
var array = new Vector3[size];
fixed (Vector3* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(scoped Span<Vector3> p_array)
{
return ConvertSystemArrayToNativePackedVector3Array((ReadOnlySpan<Vector3>)p_array);
}
public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(scoped ReadOnlySpan<Vector3> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector3_array();
fixed (Vector3* src = p_array)
return NativeFuncs.godotsharp_packed_vector3_array_new_mem_copy(src, p_array.Length);
}
// PackedVector4Array
public static unsafe Vector4[] ConvertNativePackedVector4ArrayToSystemArray(godot_packed_vector4_array p_array)
{
Vector4* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<Vector4>();
int sizeInBytes = size * sizeof(Vector4);
var array = new Vector4[size];
fixed (Vector4* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(scoped Span<Vector4> p_array)
{
return ConvertSystemArrayToNativePackedVector4Array((ReadOnlySpan<Vector4>)p_array);
}
public static unsafe godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(scoped ReadOnlySpan<Vector4> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector4_array();
fixed (Vector4* src = p_array)
return NativeFuncs.godotsharp_packed_vector4_array_new_mem_copy(src, p_array.Length);
}
// PackedColorArray
public static unsafe Color[] ConvertNativePackedColorArrayToSystemArray(godot_packed_color_array p_array)
{
Color* buffer = p_array.Buffer;
int size = p_array.Size;
if (size == 0)
return Array.Empty<Color>();
int sizeInBytes = size * sizeof(Color);
var array = new Color[size];
fixed (Color* dest = array)
Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes);
return array;
}
public static godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(scoped Span<Color> p_array)
{
return ConvertSystemArrayToNativePackedColorArray((ReadOnlySpan<Color>)p_array);
}
public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(scoped ReadOnlySpan<Color> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_color_array();
fixed (Color* src = p_array)
return NativeFuncs.godotsharp_packed_color_array_new_mem_copy(src, p_array.Length);
}
}
}

View File

@@ -0,0 +1,610 @@
#pragma warning disable CA1707 // Identifiers should not contain underscores
#pragma warning disable IDE1006 // Naming rule violation
// ReSharper disable InconsistentNaming
using System;
using System.Runtime.CompilerServices;
using Godot.SourceGenerators.Internal;
namespace Godot.NativeInterop
{
/*
* IMPORTANT:
* The order of the methods defined in NativeFuncs must match the order
* in the array defined at the bottom of 'glue/runtime_interop.cpp'.
*/
[GenerateUnmanagedCallbacks(typeof(UnmanagedCallbacks))]
public static unsafe partial class NativeFuncs
{
private static bool initialized;
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global
public static void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)
{
if (initialized)
throw new InvalidOperationException("Already initialized.");
initialized = true;
if (unmanagedCallbacksSize != sizeof(UnmanagedCallbacks))
throw new ArgumentException("Unmanaged callbacks size mismatch.", nameof(unmanagedCallbacksSize));
_unmanagedCallbacks = Unsafe.AsRef<UnmanagedCallbacks>((void*)unmanagedCallbacks);
}
private partial struct UnmanagedCallbacks
{
}
// Custom functions
internal static partial godot_bool godotsharp_dotnet_module_is_initialized();
public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname,
in godot_string_name p_methodname);
public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility(
in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash);
public static partial delegate* unmanaged<godot_bool, IntPtr> godotsharp_get_class_constructor(
in godot_string_name p_classname);
public static partial IntPtr godotsharp_engine_get_singleton(in godot_string p_name);
internal static partial Error godotsharp_stack_info_vector_resize(
ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector, int p_size);
internal static partial void godotsharp_stack_info_vector_destroy(
ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
internal static partial void godotsharp_internal_editor_file_system_update_files(in godot_packed_string_array p_script_paths);
internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func,
in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr,
godot_error_handler_type p_type, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector);
internal static partial godot_bool godotsharp_internal_script_debugger_is_active();
internal static partial IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr);
internal static partial void godotsharp_internal_object_disposed(IntPtr ptr, IntPtr gcHandleToFree);
internal static partial void godotsharp_internal_refcounted_disposed(IntPtr ptr, IntPtr gcHandleToFree,
godot_bool isFinalizer);
internal static partial Error godotsharp_internal_signal_awaiter_connect(IntPtr source,
in godot_string_name signal,
IntPtr target, IntPtr awaiterHandlePtr);
internal static partial void godotsharp_internal_tie_native_managed_to_unmanaged(IntPtr gcHandleIntPtr,
IntPtr unmanaged, in godot_string_name nativeName, godot_bool refCounted);
internal static partial void godotsharp_internal_tie_user_managed_to_unmanaged(IntPtr gcHandleIntPtr,
IntPtr unmanaged, godot_ref* scriptPtr, godot_bool refCounted);
internal static partial void godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
IntPtr gcHandleIntPtr, IntPtr unmanaged);
internal static partial IntPtr godotsharp_internal_unmanaged_get_script_instance_managed(IntPtr p_unmanaged,
out godot_bool r_has_cs_script_instance);
internal static partial IntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(IntPtr p_unmanaged);
internal static partial IntPtr godotsharp_internal_unmanaged_instance_binding_create_managed(IntPtr p_unmanaged,
IntPtr oldGCHandlePtr);
internal static partial void godotsharp_internal_new_csharp_script(godot_ref* r_dest);
internal static partial godot_bool godotsharp_internal_script_load(in godot_string p_path, godot_ref* r_dest);
internal static partial void godotsharp_internal_reload_registered_script(IntPtr scriptPtr);
internal static partial void godotsharp_array_filter_godot_objects_by_native(scoped in godot_string_name p_native_name,
scoped in godot_array p_input, out godot_array r_output);
internal static partial void godotsharp_array_filter_godot_objects_by_non_native(scoped in godot_array p_input,
out godot_array r_output);
public static partial void godotsharp_ref_new_from_ref_counted_ptr(out godot_ref r_dest,
IntPtr p_ref_counted_ptr);
public static partial void godotsharp_ref_destroy(ref godot_ref p_instance);
public static partial void godotsharp_string_name_new_from_string(out godot_string_name r_dest,
scoped in godot_string p_name);
public static partial void godotsharp_node_path_new_from_string(out godot_node_path r_dest,
scoped in godot_string p_name);
public static partial void
godotsharp_string_name_as_string(out godot_string r_dest, scoped in godot_string_name p_name);
public static partial void godotsharp_node_path_as_string(out godot_string r_dest, scoped in godot_node_path p_np);
public static partial godot_packed_byte_array godotsharp_packed_byte_array_new_mem_copy(byte* p_src,
int p_length);
public static partial godot_packed_int32_array godotsharp_packed_int32_array_new_mem_copy(int* p_src,
int p_length);
public static partial godot_packed_int64_array godotsharp_packed_int64_array_new_mem_copy(long* p_src,
int p_length);
public static partial godot_packed_float32_array godotsharp_packed_float32_array_new_mem_copy(float* p_src,
int p_length);
public static partial godot_packed_float64_array godotsharp_packed_float64_array_new_mem_copy(double* p_src,
int p_length);
public static partial godot_packed_vector2_array godotsharp_packed_vector2_array_new_mem_copy(Vector2* p_src,
int p_length);
public static partial godot_packed_vector3_array godotsharp_packed_vector3_array_new_mem_copy(Vector3* p_src,
int p_length);
public static partial godot_packed_vector4_array godotsharp_packed_vector4_array_new_mem_copy(Vector4* p_src,
int p_length);
public static partial godot_packed_color_array godotsharp_packed_color_array_new_mem_copy(Color* p_src,
int p_length);
public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest,
in godot_string p_element);
public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_trampoline,
IntPtr p_object, out godot_callable r_callable);
internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(scoped in godot_callable p_callable,
out IntPtr r_delegate_handle, out IntPtr r_trampoline, out IntPtr r_object, out godot_string_name r_name);
internal static partial godot_variant godotsharp_callable_call(scoped in godot_callable p_callable,
godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error);
internal static partial void godotsharp_callable_call_deferred(in godot_callable p_callable,
godot_variant** p_args, int p_arg_count);
internal static partial Color godotsharp_color_from_ok_hsl(float p_h, float p_s, float p_l, float p_alpha);
internal static partial float godotsharp_color_get_ok_hsl_h(in Color p_self);
internal static partial float godotsharp_color_get_ok_hsl_s(in Color p_self);
internal static partial float godotsharp_color_get_ok_hsl_l(in Color p_self);
// GDNative functions
// gdnative.h
public static partial void godotsharp_method_bind_ptrcall(IntPtr p_method_bind, IntPtr p_instance, void** p_args,
void* p_ret);
public static partial godot_variant godotsharp_method_bind_call(IntPtr p_method_bind, IntPtr p_instance,
godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error);
// variant.h
public static partial void
godotsharp_variant_new_string_name(out godot_variant r_dest, scoped in godot_string_name p_s);
public static partial void godotsharp_variant_new_copy(out godot_variant r_dest, scoped in godot_variant p_src);
public static partial void godotsharp_variant_new_node_path(out godot_variant r_dest, scoped in godot_node_path p_np);
public static partial void godotsharp_variant_new_object(out godot_variant r_dest, IntPtr p_obj);
public static partial void godotsharp_variant_new_transform2d(out godot_variant r_dest, scoped in Transform2D p_t2d);
public static partial void godotsharp_variant_new_basis(out godot_variant r_dest, scoped in Basis p_basis);
public static partial void godotsharp_variant_new_transform3d(out godot_variant r_dest, scoped in Transform3D p_trans);
public static partial void godotsharp_variant_new_projection(out godot_variant r_dest, scoped in Projection p_proj);
public static partial void godotsharp_variant_new_aabb(out godot_variant r_dest, scoped in Aabb p_aabb);
public static partial void godotsharp_variant_new_dictionary(out godot_variant r_dest,
scoped in godot_dictionary p_dict);
public static partial void godotsharp_variant_new_array(out godot_variant r_dest, scoped in godot_array p_arr);
public static partial void godotsharp_variant_new_packed_byte_array(out godot_variant r_dest,
scoped in godot_packed_byte_array p_pba);
public static partial void godotsharp_variant_new_packed_int32_array(out godot_variant r_dest,
scoped in godot_packed_int32_array p_pia);
public static partial void godotsharp_variant_new_packed_int64_array(out godot_variant r_dest,
scoped in godot_packed_int64_array p_pia);
public static partial void godotsharp_variant_new_packed_float32_array(out godot_variant r_dest,
scoped in godot_packed_float32_array p_pra);
public static partial void godotsharp_variant_new_packed_float64_array(out godot_variant r_dest,
scoped in godot_packed_float64_array p_pra);
public static partial void godotsharp_variant_new_packed_string_array(out godot_variant r_dest,
scoped in godot_packed_string_array p_psa);
public static partial void godotsharp_variant_new_packed_vector2_array(out godot_variant r_dest,
scoped in godot_packed_vector2_array p_pv2a);
public static partial void godotsharp_variant_new_packed_vector3_array(out godot_variant r_dest,
scoped in godot_packed_vector3_array p_pv3a);
public static partial void godotsharp_variant_new_packed_vector4_array(out godot_variant r_dest,
scoped in godot_packed_vector4_array p_pv4a);
public static partial void godotsharp_variant_new_packed_color_array(out godot_variant r_dest,
scoped in godot_packed_color_array p_pca);
public static partial godot_bool godotsharp_variant_as_bool(scoped in godot_variant p_self);
public static partial Int64 godotsharp_variant_as_int(scoped in godot_variant p_self);
public static partial double godotsharp_variant_as_float(scoped in godot_variant p_self);
public static partial godot_string godotsharp_variant_as_string(scoped in godot_variant p_self);
public static partial Vector2 godotsharp_variant_as_vector2(scoped in godot_variant p_self);
public static partial Vector2I godotsharp_variant_as_vector2i(scoped in godot_variant p_self);
public static partial Rect2 godotsharp_variant_as_rect2(scoped in godot_variant p_self);
public static partial Rect2I godotsharp_variant_as_rect2i(scoped in godot_variant p_self);
public static partial Vector3 godotsharp_variant_as_vector3(scoped in godot_variant p_self);
public static partial Vector3I godotsharp_variant_as_vector3i(scoped in godot_variant p_self);
public static partial Transform2D godotsharp_variant_as_transform2d(scoped in godot_variant p_self);
public static partial Vector4 godotsharp_variant_as_vector4(scoped in godot_variant p_self);
public static partial Vector4I godotsharp_variant_as_vector4i(scoped in godot_variant p_self);
public static partial Plane godotsharp_variant_as_plane(scoped in godot_variant p_self);
public static partial Quaternion godotsharp_variant_as_quaternion(scoped in godot_variant p_self);
public static partial Aabb godotsharp_variant_as_aabb(scoped in godot_variant p_self);
public static partial Basis godotsharp_variant_as_basis(scoped in godot_variant p_self);
public static partial Transform3D godotsharp_variant_as_transform3d(scoped in godot_variant p_self);
public static partial Projection godotsharp_variant_as_projection(scoped in godot_variant p_self);
public static partial Color godotsharp_variant_as_color(scoped in godot_variant p_self);
public static partial godot_string_name godotsharp_variant_as_string_name(scoped in godot_variant p_self);
public static partial godot_node_path godotsharp_variant_as_node_path(scoped in godot_variant p_self);
public static partial Rid godotsharp_variant_as_rid(scoped in godot_variant p_self);
public static partial godot_callable godotsharp_variant_as_callable(scoped in godot_variant p_self);
public static partial godot_signal godotsharp_variant_as_signal(scoped in godot_variant p_self);
public static partial godot_dictionary godotsharp_variant_as_dictionary(scoped in godot_variant p_self);
public static partial godot_array godotsharp_variant_as_array(scoped in godot_variant p_self);
public static partial godot_packed_byte_array godotsharp_variant_as_packed_byte_array(scoped in godot_variant p_self);
public static partial godot_packed_int32_array godotsharp_variant_as_packed_int32_array(scoped in godot_variant p_self);
public static partial godot_packed_int64_array godotsharp_variant_as_packed_int64_array(scoped in godot_variant p_self);
public static partial godot_packed_float32_array godotsharp_variant_as_packed_float32_array(scoped in godot_variant p_self);
public static partial godot_packed_float64_array godotsharp_variant_as_packed_float64_array(scoped in godot_variant p_self);
public static partial godot_packed_string_array godotsharp_variant_as_packed_string_array(scoped in godot_variant p_self);
public static partial godot_packed_vector2_array godotsharp_variant_as_packed_vector2_array(scoped in godot_variant p_self);
public static partial godot_packed_vector3_array godotsharp_variant_as_packed_vector3_array(scoped in godot_variant p_self);
public static partial godot_packed_vector4_array godotsharp_variant_as_packed_vector4_array(
in godot_variant p_self);
public static partial godot_packed_color_array godotsharp_variant_as_packed_color_array(scoped in godot_variant p_self);
public static partial godot_bool godotsharp_variant_equals(scoped in godot_variant p_a, scoped in godot_variant p_b);
// string.h
public static partial void godotsharp_string_new_with_utf16_chars(out godot_string r_dest, char* p_contents);
// string_name.h
public static partial void godotsharp_string_name_new_copy(out godot_string_name r_dest,
scoped in godot_string_name p_src);
// node_path.h
public static partial void godotsharp_node_path_new_copy(out godot_node_path r_dest, scoped in godot_node_path p_src);
// array.h
public static partial void godotsharp_array_new(out godot_array r_dest);
public static partial void godotsharp_array_new_copy(out godot_array r_dest, scoped in godot_array p_src);
public static partial godot_variant* godotsharp_array_ptrw(ref godot_array p_self);
// dictionary.h
public static partial void godotsharp_dictionary_new(out godot_dictionary r_dest);
public static partial void godotsharp_dictionary_new_copy(out godot_dictionary r_dest,
scoped in godot_dictionary p_src);
// destroy functions
public static partial void godotsharp_packed_byte_array_destroy(ref godot_packed_byte_array p_self);
public static partial void godotsharp_packed_int32_array_destroy(ref godot_packed_int32_array p_self);
public static partial void godotsharp_packed_int64_array_destroy(ref godot_packed_int64_array p_self);
public static partial void godotsharp_packed_float32_array_destroy(ref godot_packed_float32_array p_self);
public static partial void godotsharp_packed_float64_array_destroy(ref godot_packed_float64_array p_self);
public static partial void godotsharp_packed_string_array_destroy(ref godot_packed_string_array p_self);
public static partial void godotsharp_packed_vector2_array_destroy(ref godot_packed_vector2_array p_self);
public static partial void godotsharp_packed_vector3_array_destroy(ref godot_packed_vector3_array p_self);
public static partial void godotsharp_packed_vector4_array_destroy(ref godot_packed_vector4_array p_self);
public static partial void godotsharp_packed_color_array_destroy(ref godot_packed_color_array p_self);
public static partial void godotsharp_variant_destroy(ref godot_variant p_self);
public static partial void godotsharp_string_destroy(ref godot_string p_self);
public static partial void godotsharp_string_name_destroy(ref godot_string_name p_self);
public static partial void godotsharp_node_path_destroy(ref godot_node_path p_self);
public static partial void godotsharp_signal_destroy(ref godot_signal p_self);
public static partial void godotsharp_callable_destroy(ref godot_callable p_self);
public static partial void godotsharp_array_destroy(ref godot_array p_self);
public static partial void godotsharp_dictionary_destroy(ref godot_dictionary p_self);
// Array
public static partial int godotsharp_array_add(ref godot_array p_self, in godot_variant p_item);
public static partial int godotsharp_array_add_range(ref godot_array p_self, in godot_array p_collection);
public static partial int godotsharp_array_binary_search(ref godot_array p_self, int p_index, int p_count, in godot_variant p_value);
public static partial void godotsharp_array_duplicate(scoped ref godot_array p_self, godot_bool p_deep, out godot_array r_dest);
public static partial void godotsharp_array_fill(ref godot_array p_self, in godot_variant p_value);
public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item, int p_index = 0);
public static partial void godotsharp_array_insert(ref godot_array p_self, int p_index, in godot_variant p_item);
public static partial int godotsharp_array_last_index_of(ref godot_array p_self, in godot_variant p_item, int p_index);
public static partial void godotsharp_array_make_read_only(ref godot_array p_self);
public static partial void godotsharp_array_set_typed(
ref godot_array p_self,
uint p_elem_type,
in godot_string_name p_elem_class_name,
in godot_ref p_elem_script);
public static partial godot_bool godotsharp_array_is_typed(ref godot_array p_self);
public static partial void godotsharp_array_max(scoped ref godot_array p_self, out godot_variant r_value);
public static partial void godotsharp_array_min(scoped ref godot_array p_self, out godot_variant r_value);
public static partial void godotsharp_array_pick_random(scoped ref godot_array p_self, out godot_variant r_value);
public static partial godot_bool godotsharp_array_recursive_equal(ref godot_array p_self, in godot_array p_other);
public static partial void godotsharp_array_remove_at(ref godot_array p_self, int p_index);
public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size);
public static partial void godotsharp_array_reverse(ref godot_array p_self);
public static partial void godotsharp_array_shuffle(ref godot_array p_self);
public static partial void godotsharp_array_slice(scoped ref godot_array p_self, int p_start, int p_end,
int p_step, godot_bool p_deep, out godot_array r_dest);
public static partial void godotsharp_array_sort(ref godot_array p_self);
public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str);
public static partial void godotsharp_packed_byte_array_compress(scoped in godot_packed_byte_array p_src, int p_mode, out godot_packed_byte_array r_dst);
public static partial void godotsharp_packed_byte_array_decompress(scoped in godot_packed_byte_array p_src, long p_buffer_size, int p_mode, out godot_packed_byte_array r_dst);
public static partial void godotsharp_packed_byte_array_decompress_dynamic(scoped in godot_packed_byte_array p_src, long p_buffer_size, int p_mode, out godot_packed_byte_array r_dst);
// Dictionary
public static partial godot_bool godotsharp_dictionary_try_get_value(scoped ref godot_dictionary p_self,
scoped in godot_variant p_key,
out godot_variant r_value);
public static partial void godotsharp_dictionary_set_value(ref godot_dictionary p_self, in godot_variant p_key,
in godot_variant p_value);
public static partial void godotsharp_dictionary_keys(scoped ref godot_dictionary p_self, out godot_array r_dest);
public static partial void godotsharp_dictionary_values(scoped ref godot_dictionary p_self, out godot_array r_dest);
public static partial int godotsharp_dictionary_count(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_key_value_pair_at(scoped ref godot_dictionary p_self, int p_index,
out godot_variant r_key, out godot_variant r_value);
public static partial void godotsharp_dictionary_add(ref godot_dictionary p_self, in godot_variant p_key,
in godot_variant p_value);
public static partial void godotsharp_dictionary_clear(ref godot_dictionary p_self);
public static partial godot_bool godotsharp_dictionary_contains_key(ref godot_dictionary p_self,
in godot_variant p_key);
public static partial void godotsharp_dictionary_duplicate(scoped ref godot_dictionary p_self, godot_bool p_deep,
out godot_dictionary r_dest);
public static partial void godotsharp_dictionary_merge(ref godot_dictionary p_self, in godot_dictionary p_dictionary, godot_bool p_overwrite);
public static partial godot_bool godotsharp_dictionary_recursive_equal(ref godot_dictionary p_self, in godot_dictionary p_other);
public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self,
in godot_variant p_key);
public static partial void godotsharp_dictionary_make_read_only(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_set_typed(
ref godot_dictionary p_self,
uint p_key_type,
in godot_string_name p_key_class_name,
in godot_ref p_key_script,
uint p_value_type,
in godot_string_name p_value_class_name,
in godot_ref p_value_script);
public static partial godot_bool godotsharp_dictionary_is_typed_key(ref godot_dictionary p_self);
public static partial godot_bool godotsharp_dictionary_is_typed_value(ref godot_dictionary p_self);
public static partial uint godotsharp_dictionary_get_typed_key_builtin(ref godot_dictionary p_self);
public static partial uint godotsharp_dictionary_get_typed_value_builtin(ref godot_dictionary p_self);
public static partial void godotsharp_dictionary_get_typed_key_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
public static partial void godotsharp_dictionary_get_typed_value_class_name(ref godot_dictionary p_self, out godot_string_name r_dest);
public static partial void godotsharp_dictionary_get_typed_key_script(ref godot_dictionary p_self, out godot_variant r_dest);
public static partial void godotsharp_dictionary_get_typed_value_script(ref godot_dictionary p_self, out godot_variant r_dest);
public static partial void godotsharp_dictionary_to_string(scoped ref godot_dictionary p_self, out godot_string r_str);
// StringExtensions
public static partial void godotsharp_string_simplify_path(scoped in godot_string p_self,
out godot_string r_simplified_path);
public static partial void godotsharp_string_capitalize(scoped in godot_string p_self,
out godot_string r_capitalized);
public static partial void godotsharp_string_to_camel_case(scoped in godot_string p_self,
out godot_string r_camel_case);
public static partial void godotsharp_string_to_pascal_case(scoped in godot_string p_self,
out godot_string r_pascal_case);
public static partial void godotsharp_string_to_snake_case(scoped in godot_string p_self,
out godot_string r_snake_case);
public static partial void godotsharp_string_to_kebab_case(scoped in godot_string p_self,
out godot_string r_kebab_case);
// NodePath
public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self,
ref godot_node_path r_dest);
public static partial void godotsharp_node_path_get_concatenated_names(scoped in godot_node_path p_self,
out godot_string r_names);
public static partial void godotsharp_node_path_get_concatenated_subnames(scoped in godot_node_path p_self,
out godot_string r_subnames);
public static partial void godotsharp_node_path_get_name(scoped in godot_node_path p_self, int p_idx,
out godot_string r_name);
public static partial int godotsharp_node_path_get_name_count(in godot_node_path p_self);
public static partial void godotsharp_node_path_get_subname(scoped in godot_node_path p_self, int p_idx,
out godot_string r_subname);
public static partial int godotsharp_node_path_get_subname_count(in godot_node_path p_self);
public static partial godot_bool godotsharp_node_path_is_absolute(in godot_node_path p_self);
public static partial godot_bool godotsharp_node_path_equals(in godot_node_path p_self, in godot_node_path p_other);
public static partial int godotsharp_node_path_hash(in godot_node_path p_self);
// GD, etc
internal static partial void godotsharp_bytes_to_var(scoped in godot_packed_byte_array p_bytes,
godot_bool p_allow_objects,
out godot_variant r_ret);
internal static partial void godotsharp_convert(scoped in godot_variant p_what, int p_type,
out godot_variant r_ret);
internal static partial int godotsharp_hash(in godot_variant p_var);
internal static partial IntPtr godotsharp_instance_from_id(ulong p_instance_id);
internal static partial void godotsharp_print(in godot_string p_what);
public static partial void godotsharp_print_rich(in godot_string p_what);
internal static partial void godotsharp_printerr(in godot_string p_what);
internal static partial void godotsharp_printraw(in godot_string p_what);
internal static partial void godotsharp_prints(in godot_string p_what);
internal static partial void godotsharp_printt(in godot_string p_what);
internal static partial float godotsharp_randf();
internal static partial uint godotsharp_randi();
internal static partial void godotsharp_randomize();
internal static partial double godotsharp_randf_range(double from, double to);
internal static partial double godotsharp_randfn(double mean, double deviation);
internal static partial int godotsharp_randi_range(int from, int to);
internal static partial uint godotsharp_rand_from_seed(ulong seed, out ulong newSeed);
internal static partial void godotsharp_seed(ulong seed);
internal static partial void godotsharp_weakref(IntPtr p_obj, out godot_ref r_weak_ref);
internal static partial void godotsharp_str_to_var(scoped in godot_string p_str, out godot_variant r_ret);
internal static partial void godotsharp_var_to_bytes(scoped in godot_variant p_what, godot_bool p_full_objects,
out godot_packed_byte_array r_bytes);
internal static partial void godotsharp_var_to_str(scoped in godot_variant p_var, out godot_string r_ret);
internal static partial void godotsharp_err_print_error(in godot_string p_function, in godot_string p_file, int p_line, in godot_string p_error, in godot_string p_message = default, godot_bool p_editor_notify = godot_bool.False, godot_error_handler_type p_type = godot_error_handler_type.ERR_HANDLER_ERROR);
// Object
public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str);
}
}

View File

@@ -0,0 +1,105 @@
#pragma warning disable CA1707 // Identifiers should not contain underscores
#pragma warning disable IDE1006 // Naming rule violation
// ReSharper disable InconsistentNaming
namespace Godot.NativeInterop
{
public static partial class NativeFuncs
{
public static godot_variant godotsharp_variant_new_copy(scoped in godot_variant src)
{
switch (src.Type)
{
case Variant.Type.Nil:
return default;
case Variant.Type.Bool:
return new godot_variant() { Bool = src.Bool, Type = Variant.Type.Bool };
case Variant.Type.Int:
return new godot_variant() { Int = src.Int, Type = Variant.Type.Int };
case Variant.Type.Float:
return new godot_variant() { Float = src.Float, Type = Variant.Type.Float };
case Variant.Type.Vector2:
return new godot_variant() { Vector2 = src.Vector2, Type = Variant.Type.Vector2 };
case Variant.Type.Vector2I:
return new godot_variant() { Vector2I = src.Vector2I, Type = Variant.Type.Vector2I };
case Variant.Type.Rect2:
return new godot_variant() { Rect2 = src.Rect2, Type = Variant.Type.Rect2 };
case Variant.Type.Rect2I:
return new godot_variant() { Rect2I = src.Rect2I, Type = Variant.Type.Rect2I };
case Variant.Type.Vector3:
return new godot_variant() { Vector3 = src.Vector3, Type = Variant.Type.Vector3 };
case Variant.Type.Vector3I:
return new godot_variant() { Vector3I = src.Vector3I, Type = Variant.Type.Vector3I };
case Variant.Type.Vector4:
return new godot_variant() { Vector4 = src.Vector4, Type = Variant.Type.Vector4 };
case Variant.Type.Vector4I:
return new godot_variant() { Vector4I = src.Vector4I, Type = Variant.Type.Vector4I };
case Variant.Type.Plane:
return new godot_variant() { Plane = src.Plane, Type = Variant.Type.Plane };
case Variant.Type.Quaternion:
return new godot_variant() { Quaternion = src.Quaternion, Type = Variant.Type.Quaternion };
case Variant.Type.Color:
return new godot_variant() { Color = src.Color, Type = Variant.Type.Color };
case Variant.Type.Rid:
return new godot_variant() { Rid = src.Rid, Type = Variant.Type.Rid };
}
godotsharp_variant_new_copy(out godot_variant ret, src);
return ret;
}
public static godot_string_name godotsharp_string_name_new_copy(scoped in godot_string_name src)
{
if (src.IsEmpty)
return default;
godotsharp_string_name_new_copy(out godot_string_name ret, src);
return ret;
}
public static godot_node_path godotsharp_node_path_new_copy(scoped in godot_node_path src)
{
if (src.IsEmpty)
return default;
godotsharp_node_path_new_copy(out godot_node_path ret, src);
return ret;
}
public static godot_array godotsharp_array_new()
{
godotsharp_array_new(out godot_array ret);
return ret;
}
public static godot_array godotsharp_array_new_copy(scoped in godot_array src)
{
godotsharp_array_new_copy(out godot_array ret, src);
return ret;
}
public static godot_dictionary godotsharp_dictionary_new()
{
godotsharp_dictionary_new(out godot_dictionary ret);
return ret;
}
public static godot_dictionary godotsharp_dictionary_new_copy(scoped in godot_dictionary src)
{
godotsharp_dictionary_new_copy(out godot_dictionary ret, src);
return ret;
}
public static godot_string_name godotsharp_string_name_new_from_string(string name)
{
using godot_string src = Marshaling.ConvertStringToNative(name);
godotsharp_string_name_new_from_string(out godot_string_name ret, src);
return ret;
}
public static godot_node_path godotsharp_node_path_new_from_string(string name)
{
using godot_string src = Marshaling.ConvertStringToNative(name);
godotsharp_node_path_new_from_string(out godot_node_path ret, src);
return ret;
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Runtime.CompilerServices;
namespace Godot.NativeInterop
{
// Our source generators will add trampolines methods that access variant arguments.
// This struct makes that possible without having to enable `AllowUnsafeBlocks` in game projects.
public unsafe ref struct NativeVariantPtrArgs
{
private godot_variant** _args;
private int _argc;
internal NativeVariantPtrArgs(godot_variant** args, int argc)
{
_args = args;
_argc = argc;
}
/// <summary>
/// Returns the number of arguments.
/// </summary>
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _argc;
}
public ref godot_variant this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref *_args[index];
}
}
}

View File

@@ -0,0 +1,671 @@
#pragma warning disable CA1707 // Identifiers should not contain underscores
#pragma warning disable IDE1006 // Naming rule violation
// ReSharper disable InconsistentNaming
using System;
using System.Runtime.CompilerServices;
using Godot.Collections;
#nullable enable
namespace Godot.NativeInterop
{
public static partial class VariantUtils
{
public static godot_variant CreateFromRid(Rid from)
=> new() { Type = Variant.Type.Rid, Rid = from };
public static godot_variant CreateFromBool(bool from)
=> new() { Type = Variant.Type.Bool, Bool = from.ToGodotBool() };
public static godot_variant CreateFromInt(long from)
=> new() { Type = Variant.Type.Int, Int = from };
public static godot_variant CreateFromInt(ulong from)
=> new() { Type = Variant.Type.Int, Int = (long)from };
public static godot_variant CreateFromFloat(double from)
=> new() { Type = Variant.Type.Float, Float = from };
public static godot_variant CreateFromVector2(Vector2 from)
=> new() { Type = Variant.Type.Vector2, Vector2 = from };
public static godot_variant CreateFromVector2I(Vector2I from)
=> new() { Type = Variant.Type.Vector2I, Vector2I = from };
public static godot_variant CreateFromVector3(Vector3 from)
=> new() { Type = Variant.Type.Vector3, Vector3 = from };
public static godot_variant CreateFromVector3I(Vector3I from)
=> new() { Type = Variant.Type.Vector3I, Vector3I = from };
public static godot_variant CreateFromVector4(Vector4 from)
=> new() { Type = Variant.Type.Vector4, Vector4 = from };
public static godot_variant CreateFromVector4I(Vector4I from)
=> new() { Type = Variant.Type.Vector4I, Vector4I = from };
public static godot_variant CreateFromRect2(Rect2 from)
=> new() { Type = Variant.Type.Rect2, Rect2 = from };
public static godot_variant CreateFromRect2I(Rect2I from)
=> new() { Type = Variant.Type.Rect2I, Rect2I = from };
public static godot_variant CreateFromQuaternion(Quaternion from)
=> new() { Type = Variant.Type.Quaternion, Quaternion = from };
public static godot_variant CreateFromColor(Color from)
=> new() { Type = Variant.Type.Color, Color = from };
public static godot_variant CreateFromPlane(Plane from)
=> new() { Type = Variant.Type.Plane, Plane = from };
public static godot_variant CreateFromTransform2D(Transform2D from)
{
NativeFuncs.godotsharp_variant_new_transform2d(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromBasis(Basis from)
{
NativeFuncs.godotsharp_variant_new_basis(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromTransform3D(Transform3D from)
{
NativeFuncs.godotsharp_variant_new_transform3d(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromProjection(Projection from)
{
NativeFuncs.godotsharp_variant_new_projection(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromAabb(Aabb from)
{
NativeFuncs.godotsharp_variant_new_aabb(out godot_variant ret, from);
return ret;
}
// Explicit name to make it very clear
public static godot_variant CreateFromCallableTakingOwnershipOfDisposableValue(godot_callable from)
=> new() { Type = Variant.Type.Callable, Callable = from };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromCallable(Callable from)
=> CreateFromCallableTakingOwnershipOfDisposableValue(
Marshaling.ConvertCallableToNative(from));
// Explicit name to make it very clear
public static godot_variant CreateFromSignalTakingOwnershipOfDisposableValue(godot_signal from)
=> new() { Type = Variant.Type.Signal, Signal = from };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromSignal(Signal from)
=> CreateFromSignalTakingOwnershipOfDisposableValue(
Marshaling.ConvertSignalToNative(from));
// Explicit name to make it very clear
public static godot_variant CreateFromStringTakingOwnershipOfDisposableValue(godot_string from)
=> new() { Type = Variant.Type.String, String = from };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromString(string? from)
=> CreateFromStringTakingOwnershipOfDisposableValue(Marshaling.ConvertStringToNative(from));
public static godot_variant CreateFromPackedByteArray(scoped in godot_packed_byte_array from)
{
NativeFuncs.godotsharp_variant_new_packed_byte_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedInt32Array(scoped in godot_packed_int32_array from)
{
NativeFuncs.godotsharp_variant_new_packed_int32_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedInt64Array(scoped in godot_packed_int64_array from)
{
NativeFuncs.godotsharp_variant_new_packed_int64_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedFloat32Array(scoped in godot_packed_float32_array from)
{
NativeFuncs.godotsharp_variant_new_packed_float32_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedFloat64Array(scoped in godot_packed_float64_array from)
{
NativeFuncs.godotsharp_variant_new_packed_float64_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedStringArray(scoped in godot_packed_string_array from)
{
NativeFuncs.godotsharp_variant_new_packed_string_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedVector2Array(scoped in godot_packed_vector2_array from)
{
NativeFuncs.godotsharp_variant_new_packed_vector2_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedVector3Array(scoped in godot_packed_vector3_array from)
{
NativeFuncs.godotsharp_variant_new_packed_vector3_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedVector4Array(scoped in godot_packed_vector4_array from)
{
NativeFuncs.godotsharp_variant_new_packed_vector4_array(out godot_variant ret, from);
return ret;
}
public static godot_variant CreateFromPackedColorArray(scoped in godot_packed_color_array from)
{
NativeFuncs.godotsharp_variant_new_packed_color_array(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedByteArray(scoped Span<byte> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedByteArray(from);
return CreateFromPackedByteArray(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedInt32Array(scoped Span<int> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedInt32Array(from);
return CreateFromPackedInt32Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedInt64Array(scoped Span<long> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedInt64Array(from);
return CreateFromPackedInt64Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedFloat32Array(scoped Span<float> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedFloat32Array(from);
return CreateFromPackedFloat32Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedFloat64Array(scoped Span<double> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedFloat64Array(from);
return CreateFromPackedFloat64Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedStringArray(scoped Span<string> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedStringArray(from);
return CreateFromPackedStringArray(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector2Array(scoped Span<Vector2> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector2Array(from);
return CreateFromPackedVector2Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector3Array(scoped Span<Vector3> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector3Array(from);
return CreateFromPackedVector3Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector4Array(scoped Span<Vector4> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector4Array(from);
return CreateFromPackedVector4Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedColorArray(scoped Span<Color> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedColorArray(from);
return CreateFromPackedColorArray(nativePackedArray);
}
public static godot_variant CreateFromSystemArrayOfStringName(scoped Span<StringName> from)
{
if (from == null)
return default;
using var fromGodot = new Collections.Array(from);
return CreateFromArray((godot_array)fromGodot.NativeValue);
}
public static godot_variant CreateFromSystemArrayOfNodePath(scoped Span<NodePath> from)
{
if (from == null)
return default;
using var fromGodot = new Collections.Array(from);
return CreateFromArray((godot_array)fromGodot.NativeValue);
}
public static godot_variant CreateFromSystemArrayOfRid(scoped Span<Rid> from)
{
if (from == null)
return default;
using var fromGodot = new Collections.Array(from);
return CreateFromArray((godot_array)fromGodot.NativeValue);
}
public static godot_variant CreateFromSystemArrayOfGodotObject(GodotObject[]? from)
{
if (from == null)
return default; // Nil
using var fromGodot = new Collections.Array(from);
return CreateFromArray((godot_array)fromGodot.NativeValue);
}
public static godot_variant CreateFromArray(scoped in godot_array from)
{
NativeFuncs.godotsharp_variant_new_array(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromArray(Collections.Array? from)
=> from != null ? CreateFromArray((godot_array)from.NativeValue) : default;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromArray<[MustBeVariant] T>(Array<T>? from)
=> from != null ? CreateFromArray((godot_array)((Collections.Array)from).NativeValue) : default;
public static godot_variant CreateFromDictionary(scoped in godot_dictionary from)
{
NativeFuncs.godotsharp_variant_new_dictionary(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromDictionary(Dictionary? from)
=> from != null ? CreateFromDictionary((godot_dictionary)from.NativeValue) : default;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromDictionary<[MustBeVariant] TKey, [MustBeVariant] TValue>(Dictionary<TKey, TValue>? from)
=> from != null ? CreateFromDictionary((godot_dictionary)((Dictionary)from).NativeValue) : default;
public static godot_variant CreateFromStringName(scoped in godot_string_name from)
{
NativeFuncs.godotsharp_variant_new_string_name(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromStringName(StringName? from)
=> from != null ? CreateFromStringName((godot_string_name)from.NativeValue) : default;
public static godot_variant CreateFromNodePath(scoped in godot_node_path from)
{
NativeFuncs.godotsharp_variant_new_node_path(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromNodePath(NodePath? from)
=> from != null ? CreateFromNodePath((godot_node_path)from.NativeValue) : default;
public static godot_variant CreateFromGodotObjectPtr(IntPtr from)
{
if (from == IntPtr.Zero)
return new godot_variant();
NativeFuncs.godotsharp_variant_new_object(out godot_variant ret, from);
return ret;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromGodotObject(GodotObject? from)
=> from != null ? CreateFromGodotObjectPtr(GodotObject.GetPtr(from)) : default;
// We avoid the internal call if the stored type is the same we want.
public static bool ConvertToBool(in godot_variant p_var)
=> p_var.Type == Variant.Type.Bool ?
p_var.Bool.ToBool() :
NativeFuncs.godotsharp_variant_as_bool(p_var).ToBool();
public static char ConvertToChar(in godot_variant p_var)
=> (char)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static sbyte ConvertToInt8(in godot_variant p_var)
=> (sbyte)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static short ConvertToInt16(in godot_variant p_var)
=> (short)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static int ConvertToInt32(in godot_variant p_var)
=> (int)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static long ConvertToInt64(in godot_variant p_var)
=> p_var.Type == Variant.Type.Int ? p_var.Int : NativeFuncs.godotsharp_variant_as_int(p_var);
public static byte ConvertToUInt8(in godot_variant p_var)
=> (byte)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static ushort ConvertToUInt16(in godot_variant p_var)
=> (ushort)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static uint ConvertToUInt32(in godot_variant p_var)
=> (uint)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static ulong ConvertToUInt64(in godot_variant p_var)
=> (ulong)(p_var.Type == Variant.Type.Int ?
p_var.Int :
NativeFuncs.godotsharp_variant_as_int(p_var));
public static float ConvertToFloat32(in godot_variant p_var)
=> (float)(p_var.Type == Variant.Type.Float ?
p_var.Float :
NativeFuncs.godotsharp_variant_as_float(p_var));
public static double ConvertToFloat64(in godot_variant p_var)
=> p_var.Type == Variant.Type.Float ?
p_var.Float :
NativeFuncs.godotsharp_variant_as_float(p_var);
public static Vector2 ConvertToVector2(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector2 ?
p_var.Vector2 :
NativeFuncs.godotsharp_variant_as_vector2(p_var);
public static Vector2I ConvertToVector2I(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector2I ?
p_var.Vector2I :
NativeFuncs.godotsharp_variant_as_vector2i(p_var);
public static Rect2 ConvertToRect2(in godot_variant p_var)
=> p_var.Type == Variant.Type.Rect2 ?
p_var.Rect2 :
NativeFuncs.godotsharp_variant_as_rect2(p_var);
public static Rect2I ConvertToRect2I(in godot_variant p_var)
=> p_var.Type == Variant.Type.Rect2I ?
p_var.Rect2I :
NativeFuncs.godotsharp_variant_as_rect2i(p_var);
public static unsafe Transform2D ConvertToTransform2D(in godot_variant p_var)
=> p_var.Type == Variant.Type.Transform2D ?
*p_var.Transform2D :
NativeFuncs.godotsharp_variant_as_transform2d(p_var);
public static Vector3 ConvertToVector3(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector3 ?
p_var.Vector3 :
NativeFuncs.godotsharp_variant_as_vector3(p_var);
public static Vector3I ConvertToVector3I(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector3I ?
p_var.Vector3I :
NativeFuncs.godotsharp_variant_as_vector3i(p_var);
public static unsafe Vector4 ConvertToVector4(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector4 ?
p_var.Vector4 :
NativeFuncs.godotsharp_variant_as_vector4(p_var);
public static unsafe Vector4I ConvertToVector4I(in godot_variant p_var)
=> p_var.Type == Variant.Type.Vector4I ?
p_var.Vector4I :
NativeFuncs.godotsharp_variant_as_vector4i(p_var);
public static unsafe Basis ConvertToBasis(in godot_variant p_var)
=> p_var.Type == Variant.Type.Basis ?
*p_var.Basis :
NativeFuncs.godotsharp_variant_as_basis(p_var);
public static Quaternion ConvertToQuaternion(in godot_variant p_var)
=> p_var.Type == Variant.Type.Quaternion ?
p_var.Quaternion :
NativeFuncs.godotsharp_variant_as_quaternion(p_var);
public static unsafe Transform3D ConvertToTransform3D(in godot_variant p_var)
=> p_var.Type == Variant.Type.Transform3D ?
*p_var.Transform3D :
NativeFuncs.godotsharp_variant_as_transform3d(p_var);
public static unsafe Projection ConvertToProjection(in godot_variant p_var)
=> p_var.Type == Variant.Type.Projection ?
*p_var.Projection :
NativeFuncs.godotsharp_variant_as_projection(p_var);
public static unsafe Aabb ConvertToAabb(in godot_variant p_var)
=> p_var.Type == Variant.Type.Aabb ?
*p_var.Aabb :
NativeFuncs.godotsharp_variant_as_aabb(p_var);
public static Color ConvertToColor(in godot_variant p_var)
=> p_var.Type == Variant.Type.Color ?
p_var.Color :
NativeFuncs.godotsharp_variant_as_color(p_var);
public static Plane ConvertToPlane(in godot_variant p_var)
=> p_var.Type == Variant.Type.Plane ?
p_var.Plane :
NativeFuncs.godotsharp_variant_as_plane(p_var);
public static Rid ConvertToRid(in godot_variant p_var)
=> p_var.Type == Variant.Type.Rid ?
p_var.Rid :
NativeFuncs.godotsharp_variant_as_rid(p_var);
public static IntPtr ConvertToGodotObjectPtr(in godot_variant p_var)
{
if (p_var.Type != Variant.Type.Object || p_var.ObjectId == 0)
{
return IntPtr.Zero;
}
return NativeFuncs.godotsharp_instance_from_id(p_var.ObjectId);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GodotObject ConvertToGodotObject(in godot_variant p_var)
=> InteropUtils.UnmanagedGetManaged(ConvertToGodotObjectPtr(p_var));
public static string ConvertToString(in godot_variant p_var)
{
switch (p_var.Type)
{
case Variant.Type.Nil:
return ""; // Otherwise, Variant -> String would return the string "Null"
case Variant.Type.String:
{
// We avoid the internal call if the stored type is the same we want.
return Marshaling.ConvertStringToManaged(p_var.String);
}
default:
{
using godot_string godotString = NativeFuncs.godotsharp_variant_as_string(p_var);
return Marshaling.ConvertStringToManaged(godotString);
}
}
}
public static godot_string_name ConvertToNativeStringName(scoped in godot_variant p_var)
=> p_var.Type == Variant.Type.StringName ?
NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName) :
NativeFuncs.godotsharp_variant_as_string_name(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringName ConvertToStringName(in godot_variant p_var)
=> StringName.CreateTakingOwnershipOfDisposableValue(ConvertToNativeStringName(p_var));
public static godot_node_path ConvertToNativeNodePath(scoped in godot_variant p_var)
=> p_var.Type == Variant.Type.NodePath ?
NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath) :
NativeFuncs.godotsharp_variant_as_node_path(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static NodePath ConvertToNodePath(in godot_variant p_var)
=> NodePath.CreateTakingOwnershipOfDisposableValue(ConvertToNativeNodePath(p_var));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_callable ConvertToNativeCallable(scoped in godot_variant p_var)
=> NativeFuncs.godotsharp_variant_as_callable(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Callable ConvertToCallable(in godot_variant p_var)
{
using var callable = ConvertToNativeCallable(p_var);
return Marshaling.ConvertCallableToManaged(callable);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_signal ConvertToNativeSignal(scoped in godot_variant p_var)
=> NativeFuncs.godotsharp_variant_as_signal(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Signal ConvertToSignal(in godot_variant p_var)
{
using var signal = ConvertToNativeSignal(p_var);
return Marshaling.ConvertSignalToManaged(signal);
}
public static godot_array ConvertToNativeArray(scoped in godot_variant p_var)
=> p_var.Type == Variant.Type.Array ?
NativeFuncs.godotsharp_array_new_copy(p_var.Array) :
NativeFuncs.godotsharp_variant_as_array(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Collections.Array ConvertToArray(in godot_variant p_var)
=> Collections.Array.CreateTakingOwnershipOfDisposableValue(ConvertToNativeArray(p_var));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Array<T> ConvertToArray<[MustBeVariant] T>(in godot_variant p_var)
=> Array<T>.CreateTakingOwnershipOfDisposableValue(ConvertToNativeArray(p_var));
public static godot_dictionary ConvertToNativeDictionary(scoped in godot_variant p_var)
=> p_var.Type == Variant.Type.Dictionary ?
NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary) :
NativeFuncs.godotsharp_variant_as_dictionary(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary ConvertToDictionary(in godot_variant p_var)
=> Dictionary.CreateTakingOwnershipOfDisposableValue(ConvertToNativeDictionary(p_var));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<TKey, TValue> ConvertToDictionary<[MustBeVariant] TKey, [MustBeVariant] TValue>(in godot_variant p_var)
=> Dictionary<TKey, TValue>.CreateTakingOwnershipOfDisposableValue(ConvertToNativeDictionary(p_var));
public static byte[] ConvertAsPackedByteArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_byte_array(p_var);
return Marshaling.ConvertNativePackedByteArrayToSystemArray(packedArray);
}
public static int[] ConvertAsPackedInt32ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int32_array(p_var);
return Marshaling.ConvertNativePackedInt32ArrayToSystemArray(packedArray);
}
public static long[] ConvertAsPackedInt64ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int64_array(p_var);
return Marshaling.ConvertNativePackedInt64ArrayToSystemArray(packedArray);
}
public static float[] ConvertAsPackedFloat32ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float32_array(p_var);
return Marshaling.ConvertNativePackedFloat32ArrayToSystemArray(packedArray);
}
public static double[] ConvertAsPackedFloat64ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float64_array(p_var);
return Marshaling.ConvertNativePackedFloat64ArrayToSystemArray(packedArray);
}
public static string[] ConvertAsPackedStringArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_string_array(p_var);
return Marshaling.ConvertNativePackedStringArrayToSystemArray(packedArray);
}
public static Vector2[] ConvertAsPackedVector2ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector2_array(p_var);
return Marshaling.ConvertNativePackedVector2ArrayToSystemArray(packedArray);
}
public static Vector3[] ConvertAsPackedVector3ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector3_array(p_var);
return Marshaling.ConvertNativePackedVector3ArrayToSystemArray(packedArray);
}
public static Vector4[] ConvertAsPackedVector4ArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector4_array(p_var);
return Marshaling.ConvertNativePackedVector4ArrayToSystemArray(packedArray);
}
public static Color[] ConvertAsPackedColorArrayToSystemArray(in godot_variant p_var)
{
using var packedArray = NativeFuncs.godotsharp_variant_as_packed_color_array(p_var);
return Marshaling.ConvertNativePackedColorArrayToSystemArray(packedArray);
}
public static StringName[] ConvertToSystemArrayOfStringName(in godot_variant p_var)
{
using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var);
return Marshaling.ConvertNativeGodotArrayToSystemArrayOfStringName(godotArray);
}
public static NodePath[] ConvertToSystemArrayOfNodePath(in godot_variant p_var)
{
using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var);
return Marshaling.ConvertNativeGodotArrayToSystemArrayOfNodePath(godotArray);
}
public static Rid[] ConvertToSystemArrayOfRid(in godot_variant p_var)
{
using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var);
return Marshaling.ConvertNativeGodotArrayToSystemArrayOfRid(godotArray);
}
public static T[] ConvertToSystemArrayOfGodotObject<T>(in godot_variant p_var)
where T : GodotObject
{
using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var);
return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(godotArray);
}
}
}

View File

@@ -0,0 +1,417 @@
using System;
using System.Runtime.CompilerServices;
namespace Godot.NativeInterop;
#nullable enable
public partial class VariantUtils
{
private static InvalidOperationException UnsupportedType<T>() => new InvalidOperationException(
$"The type is not supported for conversion to/from Variant: '{typeof(T).FullName}'");
internal static class GenericConversion<T>
{
internal delegate godot_variant ToVariantConverter(scoped in T from);
internal delegate T FromVariantConverter(in godot_variant from);
public static unsafe godot_variant ToVariant(scoped in T from) =>
ToVariantCb != null ? ToVariantCb(from) : throw UnsupportedType<T>();
public static unsafe T FromVariant(in godot_variant variant) =>
FromVariantCb != null ? FromVariantCb(variant) : throw UnsupportedType<T>();
internal static ToVariantConverter? ToVariantCb;
internal static FromVariantConverter? FromVariantCb;
static GenericConversion()
{
RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static godot_variant CreateFrom<[MustBeVariant] T>(scoped in T from)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static TTo UnsafeAs<TTo>(in T f) => Unsafe.As<T, TTo>(ref Unsafe.AsRef(in f));
// `typeof(T) == typeof(X)` is optimized away. We cannot cache `typeof(T)` in a local variable, as it's not optimized when done like that.
if (typeof(T) == typeof(bool))
return CreateFromBool(UnsafeAs<bool>(from));
if (typeof(T) == typeof(char))
return CreateFromInt(UnsafeAs<char>(from));
if (typeof(T) == typeof(sbyte))
return CreateFromInt(UnsafeAs<sbyte>(from));
if (typeof(T) == typeof(short))
return CreateFromInt(UnsafeAs<short>(from));
if (typeof(T) == typeof(int))
return CreateFromInt(UnsafeAs<int>(from));
if (typeof(T) == typeof(long))
return CreateFromInt(UnsafeAs<long>(from));
if (typeof(T) == typeof(byte))
return CreateFromInt(UnsafeAs<byte>(from));
if (typeof(T) == typeof(ushort))
return CreateFromInt(UnsafeAs<ushort>(from));
if (typeof(T) == typeof(uint))
return CreateFromInt(UnsafeAs<uint>(from));
if (typeof(T) == typeof(ulong))
return CreateFromInt(UnsafeAs<ulong>(from));
if (typeof(T) == typeof(float))
return CreateFromFloat(UnsafeAs<float>(from));
if (typeof(T) == typeof(double))
return CreateFromFloat(UnsafeAs<double>(from));
if (typeof(T) == typeof(Vector2))
return CreateFromVector2(UnsafeAs<Vector2>(from));
if (typeof(T) == typeof(Vector2I))
return CreateFromVector2I(UnsafeAs<Vector2I>(from));
if (typeof(T) == typeof(Rect2))
return CreateFromRect2(UnsafeAs<Rect2>(from));
if (typeof(T) == typeof(Rect2I))
return CreateFromRect2I(UnsafeAs<Rect2I>(from));
if (typeof(T) == typeof(Transform2D))
return CreateFromTransform2D(UnsafeAs<Transform2D>(from));
if (typeof(T) == typeof(Projection))
return CreateFromProjection(UnsafeAs<Projection>(from));
if (typeof(T) == typeof(Vector3))
return CreateFromVector3(UnsafeAs<Vector3>(from));
if (typeof(T) == typeof(Vector3I))
return CreateFromVector3I(UnsafeAs<Vector3I>(from));
if (typeof(T) == typeof(Basis))
return CreateFromBasis(UnsafeAs<Basis>(from));
if (typeof(T) == typeof(Quaternion))
return CreateFromQuaternion(UnsafeAs<Quaternion>(from));
if (typeof(T) == typeof(Transform3D))
return CreateFromTransform3D(UnsafeAs<Transform3D>(from));
if (typeof(T) == typeof(Vector4))
return CreateFromVector4(UnsafeAs<Vector4>(from));
if (typeof(T) == typeof(Vector4I))
return CreateFromVector4I(UnsafeAs<Vector4I>(from));
if (typeof(T) == typeof(Aabb))
return CreateFromAabb(UnsafeAs<Aabb>(from));
if (typeof(T) == typeof(Color))
return CreateFromColor(UnsafeAs<Color>(from));
if (typeof(T) == typeof(Plane))
return CreateFromPlane(UnsafeAs<Plane>(from));
if (typeof(T) == typeof(Callable))
return CreateFromCallable(UnsafeAs<Callable>(from));
if (typeof(T) == typeof(Signal))
return CreateFromSignal(UnsafeAs<Signal>(from));
if (typeof(T) == typeof(string))
return CreateFromString(UnsafeAs<string>(from));
if (typeof(T) == typeof(byte[]))
return CreateFromPackedByteArray(UnsafeAs<byte[]>(from));
if (typeof(T) == typeof(int[]))
return CreateFromPackedInt32Array(UnsafeAs<int[]>(from));
if (typeof(T) == typeof(long[]))
return CreateFromPackedInt64Array(UnsafeAs<long[]>(from));
if (typeof(T) == typeof(float[]))
return CreateFromPackedFloat32Array(UnsafeAs<float[]>(from));
if (typeof(T) == typeof(double[]))
return CreateFromPackedFloat64Array(UnsafeAs<double[]>(from));
if (typeof(T) == typeof(string[]))
return CreateFromPackedStringArray(UnsafeAs<string[]>(from));
if (typeof(T) == typeof(Vector2[]))
return CreateFromPackedVector2Array(UnsafeAs<Vector2[]>(from));
if (typeof(T) == typeof(Vector3[]))
return CreateFromPackedVector3Array(UnsafeAs<Vector3[]>(from));
if (typeof(T) == typeof(Vector4[]))
return CreateFromPackedVector4Array(UnsafeAs<Vector4[]>(from));
if (typeof(T) == typeof(Color[]))
return CreateFromPackedColorArray(UnsafeAs<Color[]>(from));
if (typeof(T) == typeof(StringName[]))
return CreateFromSystemArrayOfStringName(UnsafeAs<StringName[]>(from));
if (typeof(T) == typeof(NodePath[]))
return CreateFromSystemArrayOfNodePath(UnsafeAs<NodePath[]>(from));
if (typeof(T) == typeof(Rid[]))
return CreateFromSystemArrayOfRid(UnsafeAs<Rid[]>(from));
if (typeof(T) == typeof(StringName))
return CreateFromStringName(UnsafeAs<StringName>(from));
if (typeof(T) == typeof(NodePath))
return CreateFromNodePath(UnsafeAs<NodePath>(from));
if (typeof(T) == typeof(Rid))
return CreateFromRid(UnsafeAs<Rid>(from));
if (typeof(T) == typeof(Godot.Collections.Dictionary))
return CreateFromDictionary(UnsafeAs<Godot.Collections.Dictionary>(from));
if (typeof(T) == typeof(Godot.Collections.Array))
return CreateFromArray(UnsafeAs<Godot.Collections.Array>(from));
if (typeof(T) == typeof(Variant))
return NativeFuncs.godotsharp_variant_new_copy((godot_variant)UnsafeAs<Variant>(from).NativeVar);
// More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away.
// `typeof(X).IsAssignableFrom(typeof(T))` is optimized away
if (typeof(GodotObject).IsAssignableFrom(typeof(T)))
return CreateFromGodotObject(UnsafeAs<GodotObject>(from));
// `typeof(T).IsValueType` is optimized away
// `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113
// Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job!
if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T)))
{
// `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away.
// Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away.
// We don't need to know whether it's signed or unsigned.
if (Unsafe.SizeOf<T>() == 1)
return CreateFromInt(UnsafeAs<sbyte>(from));
if (Unsafe.SizeOf<T>() == 2)
return CreateFromInt(UnsafeAs<short>(from));
if (Unsafe.SizeOf<T>() == 4)
return CreateFromInt(UnsafeAs<int>(from));
if (Unsafe.SizeOf<T>() == 8)
return CreateFromInt(UnsafeAs<long>(from));
throw UnsupportedType<T>();
}
return GenericConversion<T>.ToVariant(from);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static T ConvertTo<[MustBeVariant] T>(in godot_variant variant)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static T UnsafeAsT<TFrom>(TFrom f) => Unsafe.As<TFrom, T>(ref Unsafe.AsRef(in f));
if (typeof(T) == typeof(bool))
return UnsafeAsT(ConvertToBool(variant));
if (typeof(T) == typeof(char))
return UnsafeAsT(ConvertToChar(variant));
if (typeof(T) == typeof(sbyte))
return UnsafeAsT(ConvertToInt8(variant));
if (typeof(T) == typeof(short))
return UnsafeAsT(ConvertToInt16(variant));
if (typeof(T) == typeof(int))
return UnsafeAsT(ConvertToInt32(variant));
if (typeof(T) == typeof(long))
return UnsafeAsT(ConvertToInt64(variant));
if (typeof(T) == typeof(byte))
return UnsafeAsT(ConvertToUInt8(variant));
if (typeof(T) == typeof(ushort))
return UnsafeAsT(ConvertToUInt16(variant));
if (typeof(T) == typeof(uint))
return UnsafeAsT(ConvertToUInt32(variant));
if (typeof(T) == typeof(ulong))
return UnsafeAsT(ConvertToUInt64(variant));
if (typeof(T) == typeof(float))
return UnsafeAsT(ConvertToFloat32(variant));
if (typeof(T) == typeof(double))
return UnsafeAsT(ConvertToFloat64(variant));
if (typeof(T) == typeof(Vector2))
return UnsafeAsT(ConvertToVector2(variant));
if (typeof(T) == typeof(Vector2I))
return UnsafeAsT(ConvertToVector2I(variant));
if (typeof(T) == typeof(Rect2))
return UnsafeAsT(ConvertToRect2(variant));
if (typeof(T) == typeof(Rect2I))
return UnsafeAsT(ConvertToRect2I(variant));
if (typeof(T) == typeof(Transform2D))
return UnsafeAsT(ConvertToTransform2D(variant));
if (typeof(T) == typeof(Vector3))
return UnsafeAsT(ConvertToVector3(variant));
if (typeof(T) == typeof(Vector3I))
return UnsafeAsT(ConvertToVector3I(variant));
if (typeof(T) == typeof(Basis))
return UnsafeAsT(ConvertToBasis(variant));
if (typeof(T) == typeof(Quaternion))
return UnsafeAsT(ConvertToQuaternion(variant));
if (typeof(T) == typeof(Transform3D))
return UnsafeAsT(ConvertToTransform3D(variant));
if (typeof(T) == typeof(Projection))
return UnsafeAsT(ConvertToProjection(variant));
if (typeof(T) == typeof(Vector4))
return UnsafeAsT(ConvertToVector4(variant));
if (typeof(T) == typeof(Vector4I))
return UnsafeAsT(ConvertToVector4I(variant));
if (typeof(T) == typeof(Aabb))
return UnsafeAsT(ConvertToAabb(variant));
if (typeof(T) == typeof(Color))
return UnsafeAsT(ConvertToColor(variant));
if (typeof(T) == typeof(Plane))
return UnsafeAsT(ConvertToPlane(variant));
if (typeof(T) == typeof(Callable))
return UnsafeAsT(ConvertToCallable(variant));
if (typeof(T) == typeof(Signal))
return UnsafeAsT(ConvertToSignal(variant));
if (typeof(T) == typeof(string))
return UnsafeAsT(ConvertToString(variant));
if (typeof(T) == typeof(byte[]))
return UnsafeAsT(ConvertAsPackedByteArrayToSystemArray(variant));
if (typeof(T) == typeof(int[]))
return UnsafeAsT(ConvertAsPackedInt32ArrayToSystemArray(variant));
if (typeof(T) == typeof(long[]))
return UnsafeAsT(ConvertAsPackedInt64ArrayToSystemArray(variant));
if (typeof(T) == typeof(float[]))
return UnsafeAsT(ConvertAsPackedFloat32ArrayToSystemArray(variant));
if (typeof(T) == typeof(double[]))
return UnsafeAsT(ConvertAsPackedFloat64ArrayToSystemArray(variant));
if (typeof(T) == typeof(string[]))
return UnsafeAsT(ConvertAsPackedStringArrayToSystemArray(variant));
if (typeof(T) == typeof(Vector2[]))
return UnsafeAsT(ConvertAsPackedVector2ArrayToSystemArray(variant));
if (typeof(T) == typeof(Vector3[]))
return UnsafeAsT(ConvertAsPackedVector3ArrayToSystemArray(variant));
if (typeof(T) == typeof(Vector4[]))
return UnsafeAsT(ConvertAsPackedVector4ArrayToSystemArray(variant));
if (typeof(T) == typeof(Color[]))
return UnsafeAsT(ConvertAsPackedColorArrayToSystemArray(variant));
if (typeof(T) == typeof(StringName[]))
return UnsafeAsT(ConvertToSystemArrayOfStringName(variant));
if (typeof(T) == typeof(NodePath[]))
return UnsafeAsT(ConvertToSystemArrayOfNodePath(variant));
if (typeof(T) == typeof(Rid[]))
return UnsafeAsT(ConvertToSystemArrayOfRid(variant));
if (typeof(T) == typeof(StringName))
return UnsafeAsT(ConvertToStringName(variant));
if (typeof(T) == typeof(NodePath))
return UnsafeAsT(ConvertToNodePath(variant));
if (typeof(T) == typeof(Rid))
return UnsafeAsT(ConvertToRid(variant));
if (typeof(T) == typeof(Godot.Collections.Dictionary))
return UnsafeAsT(ConvertToDictionary(variant));
if (typeof(T) == typeof(Godot.Collections.Array))
return UnsafeAsT(ConvertToArray(variant));
if (typeof(T) == typeof(Variant))
return UnsafeAsT(Variant.CreateCopyingBorrowed(variant));
// More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away.
// `typeof(X).IsAssignableFrom(typeof(T))` is optimized away
if (typeof(GodotObject).IsAssignableFrom(typeof(T)))
return (T)(object)ConvertToGodotObject(variant);
// `typeof(T).IsValueType` is optimized away
// `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113
// Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job!
if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T)))
{
// `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away.
// Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away.
// We don't need to know whether it's signed or unsigned.
if (Unsafe.SizeOf<T>() == 1)
return UnsafeAsT(ConvertToInt8(variant));
if (Unsafe.SizeOf<T>() == 2)
return UnsafeAsT(ConvertToInt16(variant));
if (Unsafe.SizeOf<T>() == 4)
return UnsafeAsT(ConvertToInt32(variant));
if (Unsafe.SizeOf<T>() == 8)
return UnsafeAsT(ConvertToInt64(variant));
throw UnsupportedType<T>();
}
return GenericConversion<T>.FromVariant(variant);
}
}

View File

@@ -0,0 +1,328 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
/// <summary>
/// A pre-parsed relative or absolute path in a scene tree,
/// for use with <see cref="Node.GetNode(NodePath)"/> and similar functions.
/// It can reference a node, a resource within a node, or a property
/// of a node or resource.
/// For instance, <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>
/// would refer to the <c>size</c> property of the <c>texture</c>
/// resource on the node named <c>"Sprite2D"</c> which is a child of
/// the other named nodes in the path.
/// You will usually just pass a string to <see cref="Node.GetNode(NodePath)"/>
/// and it will be automatically converted, but you may occasionally
/// want to parse a path ahead of time with NodePath.
/// Exporting a NodePath variable will give you a node selection widget
/// in the properties panel of the editor, which can often be useful.
/// A NodePath is composed of a list of slash-separated node names
/// (like a filesystem path) and an optional colon-separated list of
/// "subnames" which can be resources or properties.
///
/// Note: In the editor, NodePath properties are automatically updated when moving,
/// renaming or deleting a node in the scene tree, but they are never updated at runtime.
/// </summary>
/// <example>
/// Some examples of NodePaths include the following:
/// <code>
/// // No leading slash means it is relative to the current node.
/// new NodePath("A"); // Immediate child A.
/// new NodePath("A/B"); // A's child B.
/// new NodePath("."); // The current node.
/// new NodePath(".."); // The parent node.
/// new NodePath("../C"); // A sibling node C.
/// // A leading slash means it is absolute from the SceneTree.
/// new NodePath("/root"); // Equivalent to GetTree().Root
/// new NodePath("/root/Main"); // If your main scene's root node were named "Main".
/// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene.
/// </code>
/// </example>
public sealed class NodePath : IDisposable, IEquatable<NodePath?>
{
internal godot_node_path.movable NativeValue;
private WeakReference<IDisposable>? _weakReferenceToSelf;
~NodePath()
{
Dispose(false);
}
/// <summary>
/// Disposes of this <see cref="NodePath"/>.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
}
private NodePath(godot_node_path nativeValueToOwn)
{
NativeValue = (godot_node_path.movable)nativeValueToOwn;
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
internal static NodePath CreateTakingOwnershipOfDisposableValue(godot_node_path nativeValueToOwn)
=> new NodePath(nativeValueToOwn);
/// <summary>
/// Constructs an empty <see cref="NodePath"/>.
/// </summary>
public NodePath()
{
}
/// <summary>
/// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>,
/// e.g.: <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>.
/// A path is absolute if it starts with a slash. Absolute paths
/// are only valid in the global scene tree, not within individual
/// scenes. In a relative path, <c>"."</c> and <c>".."</c> indicate
/// the current node and its parent.
/// The "subnames" optionally included after the path to the target
/// node can point to resources or properties, and can also be nested.
/// </summary>
/// <example>
/// Examples of valid NodePaths (assuming that those nodes exist and
/// have the referenced resources or properties):
/// <code>
/// // Points to the Sprite2D node.
/// "Path2D/PathFollow2D/Sprite2D"
/// // Points to the Sprite2D node and its "texture" resource.
/// // GetNode() would retrieve "Sprite2D", while GetNodeAndResource()
/// // would retrieve both the Sprite2D node and the "texture" resource.
/// "Path2D/PathFollow2D/Sprite2D:texture"
/// // Points to the Sprite2D node and its "position" property.
/// "Path2D/PathFollow2D/Sprite2D:position"
/// // Points to the Sprite2D node and the "x" component of its "position" property.
/// "Path2D/PathFollow2D/Sprite2D:position:x"
/// // Absolute path (from "root")
/// "/root/Level/Path2D"
/// </code>
/// </example>
/// <param name="path">A string that represents a path in a scene tree.</param>
public NodePath(string path)
{
if (!string.IsNullOrEmpty(path))
{
NativeValue = (godot_node_path.movable)NativeFuncs.godotsharp_node_path_new_from_string(path);
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
}
/// <summary>
/// Converts a string to a <see cref="NodePath"/>.
/// </summary>
/// <param name="from">The string to convert.</param>
public static implicit operator NodePath(string from) => new NodePath(from);
/// <summary>
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <param name="from">The <see cref="NodePath"/> to convert.</param>
[return: NotNullIfNotNull("from")]
public static implicit operator string?(NodePath? from) => from?.ToString();
/// <summary>
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <returns>A string representation of this <see cref="NodePath"/>.</returns>
public override string ToString()
{
if (IsEmpty)
return string.Empty;
var src = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_as_string(out godot_string dest, src);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
/// <summary>
/// Returns a node path with a colon character (<c>:</c>) prepended,
/// transforming it to a pure property path with no node name (defaults
/// to resolving from the current node).
/// </summary>
/// <example>
/// <code>
/// // This will be parsed as a node path to the "x" property in the "position" node.
/// var nodePath = new NodePath("position:x");
/// // This will be parsed as a node path to the "x" component of the "position" property in the current node.
/// NodePath propertyPath = nodePath.GetAsPropertyPath();
/// GD.Print(propertyPath); // :position:x
/// </code>
/// </example>
/// <returns>The <see cref="NodePath"/> as a pure property path.</returns>
public NodePath GetAsPropertyPath()
{
godot_node_path propertyPath = default;
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_as_property_path(self, ref propertyPath);
return CreateTakingOwnershipOfDisposableValue(propertyPath);
}
/// <summary>
/// Returns all names concatenated with a slash character (<c>/</c>).
/// </summary>
/// <example>
/// <code>
/// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
/// GD.Print(nodepath.GetConcatenatedNames()); // Path2D/PathFollow2D/Sprite2D
/// </code>
/// </example>
/// <returns>The names concatenated with <c>/</c>.</returns>
public string GetConcatenatedNames()
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_concatenated_names(self, out godot_string names);
using (names)
return Marshaling.ConvertStringToManaged(names);
}
/// <summary>
/// Returns all subnames concatenated with a colon character (<c>:</c>)
/// as separator, i.e. the right side of the first colon in a node path.
/// </summary>
/// <example>
/// <code>
/// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
/// GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path
/// </code>
/// </example>
/// <returns>The subnames concatenated with <c>:</c>.</returns>
public string GetConcatenatedSubNames()
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_concatenated_subnames(self, out godot_string subNames);
using (subNames)
return Marshaling.ConvertStringToManaged(subNames);
}
/// <summary>
/// Gets the node name indicated by <paramref name="idx"/> (0 to <see cref="GetNameCount"/>).
/// </summary>
/// <example>
/// <code>
/// var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D");
/// GD.Print(nodePath.GetName(0)); // Path2D
/// GD.Print(nodePath.GetName(1)); // PathFollow2D
/// GD.Print(nodePath.GetName(2)); // Sprite
/// </code>
/// </example>
/// <param name="idx">The name index.</param>
/// <returns>The name at the given index <paramref name="idx"/>.</returns>
public string GetName(int idx)
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_name(self, idx, out godot_string name);
using (name)
return Marshaling.ConvertStringToManaged(name);
}
/// <summary>
/// Gets the number of node names which make up the path.
/// Subnames (see <see cref="GetSubNameCount"/>) are not included.
/// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names.
/// </summary>
/// <returns>The number of node names which make up the path.</returns>
public int GetNameCount()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_get_name_count(self);
}
/// <summary>
/// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubNameCount"/>).
/// </summary>
/// <param name="idx">The subname index.</param>
/// <returns>The subname at the given index <paramref name="idx"/>.</returns>
public string GetSubName(int idx)
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_subname(self, idx, out godot_string subName);
using (subName)
return Marshaling.ConvertStringToManaged(subName);
}
/// <summary>
/// Gets the number of resource or property names ("subnames") in the path.
/// Each subname is listed after a colon character (<c>:</c>) in the node path.
/// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames.
/// </summary>
/// <returns>The number of subnames in the path.</returns>
public int GetSubNameCount()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_get_subname_count(self);
}
/// <summary>
/// Returns <see langword="true"/> if the node path is absolute (as opposed to relative),
/// which means that it starts with a slash character (<c>/</c>). Absolute node paths can
/// be used to access the root node (<c>"/root"</c>) or autoloads (e.g. <c>"/global"</c>
/// if a "global" autoload was registered).
/// </summary>
/// <returns>If the <see cref="NodePath"/> is an absolute path.</returns>
public bool IsAbsolute()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_is_absolute(self).ToBool();
}
/// <summary>
/// Returns <see langword="true"/> if the node path is empty.
/// </summary>
/// <returns>If the <see cref="NodePath"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
public static bool operator ==(NodePath? left, NodePath? right)
{
if (left is null)
return right is null;
return left.Equals(right);
}
public static bool operator !=(NodePath? left, NodePath? right)
{
return !(left == right);
}
public bool Equals([NotNullWhen(true)] NodePath? other)
{
if (other is null)
return false;
var self = (godot_node_path)NativeValue;
var otherNative = (godot_node_path)other.NativeValue;
return NativeFuncs.godotsharp_node_path_equals(self, otherNative).ToBool();
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return ReferenceEquals(this, obj) || (obj is NodePath other && Equals(other));
}
public override int GetHashCode()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_hash(self);
}
}
}

View File

@@ -0,0 +1,439 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// Plane represents a normalized plane equation.
/// "Over" or "Above" the plane is considered the side of
/// the plane towards where the normal is pointing.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Plane : IEquatable<Plane>
{
private Vector3 _normal;
private real_t _d;
/// <summary>
/// The normal of the plane, which must be a unit vector.
/// In the scalar equation of the plane <c>ax + by + cz = d</c>, this is
/// the vector <c>(a, b, c)</c>, where <c>d</c> is the <see cref="D"/> property.
/// </summary>
/// <value>Equivalent to <see cref="X"/>, <see cref="Y"/>, and <see cref="Z"/>.</value>
public Vector3 Normal
{
readonly get { return _normal; }
set { _normal = value; }
}
/// <summary>
/// The distance from the origin to the plane (in the direction of
/// <see cref="Normal"/>). This value is typically non-negative.
/// In the scalar equation of the plane <c>ax + by + cz = d</c>,
/// this is <c>d</c>, while the <c>(a, b, c)</c> coordinates are represented
/// by the <see cref="Normal"/> property.
/// </summary>
/// <value>The plane's distance from the origin.</value>
public real_t D
{
readonly get { return _d; }
set { _d = value; }
}
/// <summary>
/// The X component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s X value.</value>
public real_t X
{
readonly get
{
return _normal.X;
}
set
{
_normal.X = value;
}
}
/// <summary>
/// The Y component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s Y value.</value>
public real_t Y
{
readonly get
{
return _normal.Y;
}
set
{
_normal.Y = value;
}
}
/// <summary>
/// The Z component of the plane's normal vector.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/>'s Z value.</value>
public real_t Z
{
readonly get
{
return _normal.Z;
}
set
{
_normal.Z = value;
}
}
/// <summary>
/// Returns the shortest distance from this plane to the position <paramref name="point"/>.
/// </summary>
/// <param name="point">The position to use for the calculation.</param>
/// <returns>The shortest distance.</returns>
public readonly real_t DistanceTo(Vector3 point)
{
return _normal.Dot(point) - _d;
}
/// <summary>
/// Returns the center of the plane, the point on the plane closest to the origin.
/// The point where the normal line going through the origin intersects the plane.
/// </summary>
/// <value>Equivalent to <see cref="Normal"/> multiplied by <see cref="D"/>.</value>
public readonly Vector3 GetCenter()
{
return _normal * _d;
}
/// <summary>
/// Returns <see langword="true"/> if point is inside the plane.
/// Comparison uses a custom minimum tolerance threshold.
/// </summary>
/// <param name="point">The point to check.</param>
/// <param name="tolerance">The tolerance threshold.</param>
/// <returns>A <see langword="bool"/> for whether or not the plane has the point.</returns>
public readonly bool HasPoint(Vector3 point, real_t tolerance = Mathf.Epsilon)
{
real_t dist = _normal.Dot(point) - _d;
return Mathf.Abs(dist) <= tolerance;
}
/// <summary>
/// Returns the intersection point of the three planes: <paramref name="b"/>, <paramref name="c"/>,
/// and this plane. If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="b">One of the three planes to use in the calculation.</param>
/// <param name="c">One of the three planes to use in the calculation.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? Intersect3(Plane b, Plane c)
{
real_t denom = _normal.Cross(b._normal).Dot(c._normal);
if (Mathf.IsZeroApprox(denom))
{
return null;
}
Vector3 result = (b._normal.Cross(c._normal) * _d) +
(c._normal.Cross(_normal) * b._d) +
(_normal.Cross(b._normal) * c._d);
return result / denom;
}
/// <summary>
/// Returns the intersection point of a ray consisting of the position <paramref name="from"/>
/// and the direction normal <paramref name="dir"/> with this plane.
/// If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="from">The start of the ray.</param>
/// <param name="dir">The direction of the ray, normalized.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? IntersectsRay(Vector3 from, Vector3 dir)
{
real_t den = _normal.Dot(dir);
if (Mathf.IsZeroApprox(den))
{
return null;
}
real_t dist = (_normal.Dot(from) - _d) / den;
// This is a ray, before the emitting pos (from) does not exist
if (dist > Mathf.Epsilon)
{
return null;
}
return from - (dir * dist);
}
/// <summary>
/// Returns the intersection point of a line segment from
/// position <paramref name="begin"/> to position <paramref name="end"/> with this plane.
/// If no intersection is found, <see langword="null"/> is returned.
/// </summary>
/// <param name="begin">The start of the line segment.</param>
/// <param name="end">The end of the line segment.</param>
/// <returns>The intersection, or <see langword="null"/> if none is found.</returns>
public readonly Vector3? IntersectsSegment(Vector3 begin, Vector3 end)
{
Vector3 segment = begin - end;
real_t den = _normal.Dot(segment);
if (Mathf.IsZeroApprox(den))
{
return null;
}
real_t dist = (_normal.Dot(begin) - _d) / den;
// Only allow dist to be in the range of 0 to 1, with tolerance.
if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon)
{
return null;
}
return begin - (segment * dist);
}
/// <summary>
/// Returns <see langword="true"/> if this plane is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return _normal.IsFinite() && Mathf.IsFinite(D);
}
/// <summary>
/// Returns <see langword="true"/> if <paramref name="point"/> is located above the plane.
/// </summary>
/// <param name="point">The point to check.</param>
/// <returns>A <see langword="bool"/> for whether or not the point is above the plane.</returns>
public readonly bool IsPointOver(Vector3 point)
{
return _normal.Dot(point) > _d;
}
/// <summary>
/// Returns the plane scaled to unit length.
/// </summary>
/// <returns>A normalized version of the plane.</returns>
public readonly Plane Normalized()
{
real_t len = _normal.Length();
if (len == 0)
{
return new Plane(0, 0, 0, 0);
}
return new Plane(_normal / len, _d / len);
}
/// <summary>
/// Returns the orthogonal projection of <paramref name="point"/> into the plane.
/// </summary>
/// <param name="point">The point to project.</param>
/// <returns>The projected point.</returns>
public readonly Vector3 Project(Vector3 point)
{
return point - (_normal * DistanceTo(point));
}
// Constants
private static readonly Plane _planeYZ = new Plane(1, 0, 0, 0);
private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0);
private static readonly Plane _planeXY = new Plane(0, 0, 1, 0);
/// <summary>
/// A <see cref="Plane"/> that extends in the Y and Z axes (normal vector points +X).
/// </summary>
/// <value>Equivalent to <c>new Plane(1, 0, 0, 0)</c>.</value>
public static Plane PlaneYZ { get { return _planeYZ; } }
/// <summary>
/// A <see cref="Plane"/> that extends in the X and Z axes (normal vector points +Y).
/// </summary>
/// <value>Equivalent to <c>new Plane(0, 1, 0, 0)</c>.</value>
public static Plane PlaneXZ { get { return _planeXZ; } }
/// <summary>
/// A <see cref="Plane"/> that extends in the X and Y axes (normal vector points +Z).
/// </summary>
/// <value>Equivalent to <c>new Plane(0, 0, 1, 0)</c>.</value>
public static Plane PlaneXY { get { return _planeXY; } }
/// <summary>
/// Constructs a <see cref="Plane"/> from four values.
/// <paramref name="a"/>, <paramref name="b"/> and <paramref name="c"/> become the
/// components of the resulting plane's <see cref="Normal"/> vector.
/// <paramref name="d"/> becomes the plane's distance from the origin.
/// </summary>
/// <param name="a">The X component of the plane's normal vector.</param>
/// <param name="b">The Y component of the plane's normal vector.</param>
/// <param name="c">The Z component of the plane's normal vector.</param>
/// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(real_t a, real_t b, real_t c, real_t d)
{
_normal = new Vector3(a, b, c);
_d = d;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector.
/// The plane will intersect the origin.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
public Plane(Vector3 normal)
{
_normal = normal;
_d = 0;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and
/// the plane's distance to the origin <paramref name="d"/>.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
/// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(Vector3 normal, real_t d)
{
_normal = normal;
_d = d;
}
/// <summary>
/// Constructs a <see cref="Plane"/> from a <paramref name="normal"/> vector and
/// a <paramref name="point"/> on the plane.
/// </summary>
/// <param name="normal">The normal of the plane, must be a unit vector.</param>
/// <param name="point">The point on the plane.</param>
public Plane(Vector3 normal, Vector3 point)
{
_normal = normal;
_d = _normal.Dot(point);
}
/// <summary>
/// Constructs a <see cref="Plane"/> from the three points, given in clockwise order.
/// </summary>
/// <param name="v1">The first point.</param>
/// <param name="v2">The second point.</param>
/// <param name="v3">The third point.</param>
public Plane(Vector3 v1, Vector3 v2, Vector3 v3)
{
_normal = (v1 - v3).Cross(v1 - v2);
_normal.Normalize();
_d = _normal.Dot(v1);
}
/// <summary>
/// Returns the negative value of the <see cref="Plane"/>.
/// This is the same as writing <c>new Plane(-p.Normal, -p.D)</c>.
/// This operation flips the direction of the normal vector and
/// also flips the distance value, resulting in a Plane that is
/// in the same place, but facing the opposite direction.
/// </summary>
/// <param name="plane">The plane to negate/flip.</param>
/// <returns>The negated/flipped plane.</returns>
public static Plane operator -(Plane plane)
{
return new Plane(-plane._normal, -plane._d);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Plane"/>s are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the planes are exactly equal.</returns>
public static bool operator ==(Plane left, Plane right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Plane"/>s are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the planes are not equal.</returns>
public static bool operator !=(Plane left, Plane right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the plane and the other object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Plane other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="other"/> are equal.
/// </summary>
/// <param name="other">The other plane to compare.</param>
/// <returns>Whether or not the planes are exactly equal.</returns>
public readonly bool Equals(Plane other)
{
return _normal == other._normal && _d == other._d;
}
/// <summary>
/// Returns <see langword="true"/> if this plane and <paramref name="other"/> are
/// approximately equal, by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
/// </summary>
/// <param name="other">The other plane to compare.</param>
/// <returns>Whether or not the planes are approximately equal.</returns>
public readonly bool IsEqualApprox(Plane other)
{
return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(_d, other._d);
}
/// <summary>
/// Serves as the hash function for <see cref="Plane"/>.
/// </summary>
/// <returns>A hash code for this plane.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_normal, _d);
}
/// <summary>
/// Converts this <see cref="Plane"/> to a string.
/// </summary>
/// <returns>A string representation of this plane.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this plane.</returns>
public readonly string ToString(string? format)
{
return $"{_normal.ToString(format)}, {_d.ToString(format, CultureInfo.InvariantCulture)}";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,848 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// A unit quaternion used for representing 3D rotations.
/// Quaternions need to be normalized to be used for rotation.
///
/// It is similar to <see cref="Basis"/>, which implements matrix
/// representation of rotations, and can be parametrized using both
/// an axis-angle pair or Euler angles. Basis stores rotation, scale,
/// and shearing, while Quaternion only stores rotation.
///
/// Due to its compactness and the way it is stored in memory, certain
/// operations (obtaining axis-angle and performing SLERP, in particular)
/// are more efficient and robust against floating-point errors.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Quaternion : IEquatable<Quaternion>
{
/// <summary>
/// X component of the quaternion (imaginary <c>i</c> axis part).
/// Quaternion components should usually not be manipulated directly.
/// </summary>
public real_t X;
/// <summary>
/// Y component of the quaternion (imaginary <c>j</c> axis part).
/// Quaternion components should usually not be manipulated directly.
/// </summary>
public real_t Y;
/// <summary>
/// Z component of the quaternion (imaginary <c>k</c> axis part).
/// Quaternion components should usually not be manipulated directly.
/// </summary>
public real_t Z;
/// <summary>
/// W component of the quaternion (real part).
/// Quaternion components should usually not be manipulated directly.
/// </summary>
public real_t W;
/// <summary>
/// Access quaternion components using their index.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is not 0, 1, 2 or 3.
/// </exception>
/// <value>
/// <c>[0]</c> is equivalent to <see cref="X"/>,
/// <c>[1]</c> is equivalent to <see cref="Y"/>,
/// <c>[2]</c> is equivalent to <see cref="Z"/>,
/// <c>[3]</c> is equivalent to <see cref="W"/>.
/// </value>
public real_t this[int index]
{
readonly get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
case 2:
return Z;
case 3:
return W;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0:
X = value;
break;
case 1:
Y = value;
break;
case 2:
Z = value;
break;
case 3:
W = value;
break;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
/// <summary>
/// Returns the angle between this quaternion and <paramref name="to"/>.
/// This is the magnitude of the angle you would need to rotate
/// by to get from one to the other.
///
/// Note: This method has an abnormally high amount
/// of floating-point error, so methods such as
/// <see cref="Mathf.IsZeroApprox(real_t)"/> will not work reliably.
/// </summary>
/// <param name="to">The other quaternion.</param>
/// <returns>The angle between the quaternions.</returns>
public readonly real_t AngleTo(Quaternion to)
{
real_t dot = Dot(to);
return Mathf.Acos(Mathf.Clamp(dot * dot * 2 - 1, -1, 1));
}
/// <summary>
/// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion,
/// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>.
/// </summary>
/// <param name="b">The destination quaternion.</param>
/// <param name="preA">A quaternion before this quaternion.</param>
/// <param name="postB">A quaternion after <paramref name="b"/>.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <returns>The interpolated quaternion.</returns>
public readonly Quaternion SphericalCubicInterpolate(Quaternion b, Quaternion preA, Quaternion postB, real_t weight)
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized");
}
if (!b.IsNormalized())
{
throw new ArgumentException("Argument is not normalized", nameof(b));
}
#endif
// Align flip phases.
Quaternion fromQ = new Basis(this).GetRotationQuaternion();
Quaternion preQ = new Basis(preA).GetRotationQuaternion();
Quaternion toQ = new Basis(b).GetRotationQuaternion();
Quaternion postQ = new Basis(postB).GetRotationQuaternion();
// Flip quaternions to shortest path if necessary.
bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0;
preQ = flip1 ? -preQ : preQ;
bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0;
toQ = flip2 ? -toQ : toQ;
bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0;
postQ = flip3 ? -postQ : postQ;
// Calc by Expmap in fromQ space.
Quaternion lnFrom = new Quaternion(0, 0, 0, 0);
Quaternion lnTo = (fromQ.Inverse() * toQ).Log();
Quaternion lnPre = (fromQ.Inverse() * preQ).Log();
Quaternion lnPost = (fromQ.Inverse() * postQ).Log();
Quaternion ln = new Quaternion(
Mathf.CubicInterpolate(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight),
Mathf.CubicInterpolate(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight),
Mathf.CubicInterpolate(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight),
0);
Quaternion q1 = fromQ * ln.Exp();
// Calc by Expmap in toQ space.
lnFrom = (toQ.Inverse() * fromQ).Log();
lnTo = new Quaternion(0, 0, 0, 0);
lnPre = (toQ.Inverse() * preQ).Log();
lnPost = (toQ.Inverse() * postQ).Log();
ln = new Quaternion(
Mathf.CubicInterpolate(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight),
Mathf.CubicInterpolate(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight),
Mathf.CubicInterpolate(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight),
0);
Quaternion q2 = toQ * ln.Exp();
// To cancel error made by Expmap ambiguity, do blending.
return q1.Slerp(q2, weight);
}
/// <summary>
/// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion,
/// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>.
/// It can perform smoother interpolation than <see cref="SphericalCubicInterpolate"/>
/// by the time values.
/// </summary>
/// <param name="b">The destination quaternion.</param>
/// <param name="preA">A quaternion before this quaternion.</param>
/// <param name="postB">A quaternion after <paramref name="b"/>.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <param name="bT"></param>
/// <param name="preAT"></param>
/// <param name="postBT"></param>
/// <returns>The interpolated quaternion.</returns>
public readonly Quaternion SphericalCubicInterpolateInTime(Quaternion b, Quaternion preA, Quaternion postB, real_t weight, real_t bT, real_t preAT, real_t postBT)
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized");
}
if (!b.IsNormalized())
{
throw new ArgumentException("Argument is not normalized", nameof(b));
}
#endif
// Align flip phases.
Quaternion fromQ = new Basis(this).GetRotationQuaternion();
Quaternion preQ = new Basis(preA).GetRotationQuaternion();
Quaternion toQ = new Basis(b).GetRotationQuaternion();
Quaternion postQ = new Basis(postB).GetRotationQuaternion();
// Flip quaternions to shortest path if necessary.
bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0;
preQ = flip1 ? -preQ : preQ;
bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0;
toQ = flip2 ? -toQ : toQ;
bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0;
postQ = flip3 ? -postQ : postQ;
// Calc by Expmap in fromQ space.
Quaternion lnFrom = new Quaternion(0, 0, 0, 0);
Quaternion lnTo = (fromQ.Inverse() * toQ).Log();
Quaternion lnPre = (fromQ.Inverse() * preQ).Log();
Quaternion lnPost = (fromQ.Inverse() * postQ).Log();
Quaternion ln = new Quaternion(
Mathf.CubicInterpolateInTime(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight, bT, preAT, postBT),
Mathf.CubicInterpolateInTime(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight, bT, preAT, postBT),
Mathf.CubicInterpolateInTime(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight, bT, preAT, postBT),
0);
Quaternion q1 = fromQ * ln.Exp();
// Calc by Expmap in toQ space.
lnFrom = (toQ.Inverse() * fromQ).Log();
lnTo = new Quaternion(0, 0, 0, 0);
lnPre = (toQ.Inverse() * preQ).Log();
lnPost = (toQ.Inverse() * postQ).Log();
ln = new Quaternion(
Mathf.CubicInterpolateInTime(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight, bT, preAT, postBT),
Mathf.CubicInterpolateInTime(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight, bT, preAT, postBT),
Mathf.CubicInterpolateInTime(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight, bT, preAT, postBT),
0);
Quaternion q2 = toQ * ln.Exp();
// To cancel error made by Expmap ambiguity, do blending.
return q1.Slerp(q2, weight);
}
/// <summary>
/// Returns the dot product of two quaternions.
/// </summary>
/// <param name="b">The other quaternion.</param>
/// <returns>The dot product.</returns>
public readonly real_t Dot(Quaternion b)
{
return (X * b.X) + (Y * b.Y) + (Z * b.Z) + (W * b.W);
}
public readonly Quaternion Exp()
{
Vector3 v = new Vector3(X, Y, Z);
real_t theta = v.Length();
v = v.Normalized();
if (theta < Mathf.Epsilon || !v.IsNormalized())
{
return new Quaternion(0, 0, 0, 1);
}
return new Quaternion(v, theta);
}
public readonly real_t GetAngle()
{
return 2 * Mathf.Acos(W);
}
public readonly Vector3 GetAxis()
{
if (Mathf.Abs(W) > 1 - Mathf.Epsilon)
{
return new Vector3(X, Y, Z);
}
real_t r = 1 / Mathf.Sqrt(1 - W * W);
return new Vector3(X * r, Y * r, Z * r);
}
/// <summary>
/// Returns Euler angles (in the YXZ convention: when decomposing,
/// first Z, then X, and Y last) corresponding to the rotation
/// represented by the unit quaternion. Returned vector contains
/// the rotation angles in the format (X angle, Y angle, Z angle).
/// </summary>
/// <returns>The Euler angle representation of this quaternion.</returns>
public readonly Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz)
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized.");
}
#endif
var basis = new Basis(this);
return basis.GetEuler(order);
}
/// <summary>
/// Returns the inverse of the quaternion.
/// </summary>
/// <returns>The inverse quaternion.</returns>
public readonly Quaternion Inverse()
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized.");
}
#endif
return new Quaternion(-X, -Y, -Z, W);
}
/// <summary>
/// Returns <see langword="true"/> if this quaternion is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return Mathf.IsFinite(X) && Mathf.IsFinite(Y) && Mathf.IsFinite(Z) && Mathf.IsFinite(W);
}
/// <summary>
/// Returns whether the quaternion is normalized or not.
/// </summary>
/// <returns>A <see langword="bool"/> for whether the quaternion is normalized or not.</returns>
public readonly bool IsNormalized()
{
return Mathf.Abs(LengthSquared() - 1) <= Mathf.Epsilon;
}
public readonly Quaternion Log()
{
Vector3 v = GetAxis() * GetAngle();
return new Quaternion(v.X, v.Y, v.Z, 0);
}
/// <summary>
/// Returns the length (magnitude) of the quaternion.
/// </summary>
/// <seealso cref="LengthSquared"/>
/// <value>Equivalent to <c>Mathf.Sqrt(LengthSquared)</c>.</value>
public readonly real_t Length()
{
return Mathf.Sqrt(LengthSquared());
}
/// <summary>
/// Returns the squared length (squared magnitude) of the quaternion.
/// This method runs faster than <see cref="Length"/>, so prefer it if
/// you need to compare quaternions or need the squared length for some formula.
/// </summary>
/// <value>Equivalent to <c>Dot(this)</c>.</value>
public readonly real_t LengthSquared()
{
return Dot(this);
}
/// <summary>
/// Returns a copy of the quaternion, normalized to unit length.
/// </summary>
/// <returns>The normalized quaternion.</returns>
public readonly Quaternion Normalized()
{
return this / Length();
}
/// <summary>
/// Returns the result of the spherical linear interpolation between
/// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>.
///
/// Note: Both quaternions must be normalized.
/// </summary>
/// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <returns>The resulting quaternion of the interpolation.</returns>
public readonly Quaternion Slerp(Quaternion to, real_t weight)
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized.");
}
if (!to.IsNormalized())
{
throw new ArgumentException("Argument is not normalized.", nameof(to));
}
#endif
// Calculate cosine.
real_t cosom = Dot(to);
var to1 = new Quaternion();
// Adjust signs if necessary.
if (cosom < 0.0)
{
cosom = -cosom;
to1 = -to;
}
else
{
to1 = to;
}
real_t sinom, scale0, scale1;
// Calculate coefficients.
if (1.0 - cosom > Mathf.Epsilon)
{
// Standard case (Slerp).
real_t omega = Mathf.Acos(cosom);
sinom = Mathf.Sin(omega);
scale0 = Mathf.Sin((1.0f - weight) * omega) / sinom;
scale1 = Mathf.Sin(weight * omega) / sinom;
}
else
{
// Quaternions are very close so we can do a linear interpolation.
scale0 = 1.0f - weight;
scale1 = weight;
}
// Calculate final values.
return new Quaternion
(
(scale0 * X) + (scale1 * to1.X),
(scale0 * Y) + (scale1 * to1.Y),
(scale0 * Z) + (scale1 * to1.Z),
(scale0 * W) + (scale1 * to1.W)
);
}
/// <summary>
/// Returns the result of the spherical linear interpolation between
/// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>, but without
/// checking if the rotation path is not bigger than 90 degrees.
/// </summary>
/// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <returns>The resulting quaternion of the interpolation.</returns>
public readonly Quaternion Slerpni(Quaternion to, real_t weight)
{
#if DEBUG
if (!IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized");
}
if (!to.IsNormalized())
{
throw new ArgumentException("Argument is not normalized", nameof(to));
}
#endif
real_t dot = Dot(to);
if (Mathf.Abs(dot) > 0.9999f)
{
return this;
}
real_t theta = Mathf.Acos(dot);
real_t sinT = 1.0f / Mathf.Sin(theta);
real_t newFactor = Mathf.Sin(weight * theta) * sinT;
real_t invFactor = Mathf.Sin((1.0f - weight) * theta) * sinT;
return new Quaternion
(
(invFactor * X) + (newFactor * to.X),
(invFactor * Y) + (newFactor * to.Y),
(invFactor * Z) + (newFactor * to.Z),
(invFactor * W) + (newFactor * to.W)
);
}
// Constants
private static readonly Quaternion _identity = new Quaternion(0, 0, 0, 1);
/// <summary>
/// The identity quaternion, representing no rotation.
/// Equivalent to an identity <see cref="Basis"/> matrix. If a vector is transformed by
/// an identity quaternion, it will not change.
/// </summary>
/// <value>Equivalent to <c>new Quaternion(0, 0, 0, 1)</c>.</value>
public static Quaternion Identity { get { return _identity; } }
/// <summary>
/// Constructs a <see cref="Quaternion"/> defined by the given values.
/// </summary>
/// <param name="x">X component of the quaternion (imaginary <c>i</c> axis part).</param>
/// <param name="y">Y component of the quaternion (imaginary <c>j</c> axis part).</param>
/// <param name="z">Z component of the quaternion (imaginary <c>k</c> axis part).</param>
/// <param name="w">W component of the quaternion (real part).</param>
public Quaternion(real_t x, real_t y, real_t z, real_t w)
{
X = x;
Y = y;
Z = z;
W = w;
}
/// <summary>
/// Constructs a <see cref="Quaternion"/> from the given <see cref="Basis"/>.
/// </summary>
/// <param name="basis">The <see cref="Basis"/> to construct from.</param>
public Quaternion(Basis basis)
{
this = basis.GetQuaternion();
}
/// <summary>
/// Constructs a <see cref="Quaternion"/> that will rotate around the given axis
/// by the specified angle. The axis must be a normalized vector.
/// </summary>
/// <param name="axis">The axis to rotate around. Must be normalized.</param>
/// <param name="angle">The angle to rotate, in radians.</param>
public Quaternion(Vector3 axis, real_t angle)
{
#if DEBUG
if (!axis.IsNormalized())
{
throw new ArgumentException("Argument is not normalized.", nameof(axis));
}
#endif
real_t d = axis.Length();
if (d == 0f)
{
X = 0f;
Y = 0f;
Z = 0f;
W = 0f;
}
else
{
(real_t sin, real_t cos) = Mathf.SinCos(angle * 0.5f);
real_t s = sin / d;
X = axis.X * s;
Y = axis.Y * s;
Z = axis.Z * s;
W = cos;
}
}
public Quaternion(Vector3 arcFrom, Vector3 arcTo)
{
#if DEBUG
if (arcFrom.IsZeroApprox() || arcTo.IsZeroApprox())
{
throw new ArgumentException("The vectors must not be zero.");
}
#endif
#if REAL_T_IS_DOUBLE
const real_t AlmostOne = 0.999999999999999;
#else
const real_t AlmostOne = 0.99999975f;
#endif
Vector3 n0 = arcFrom.Normalized();
Vector3 n1 = arcTo.Normalized();
real_t d = n0.Dot(n1);
if (Mathf.Abs(d) > AlmostOne)
{
if (d >= 0.0f)
{
X = 0.0f;
Y = 0.0f;
Z = 0.0f;
W = 1.0f;
return; // Vectors are same.
}
Vector3 axis = n0.GetAnyPerpendicular();
X = axis.X;
Y = axis.Y;
Z = axis.Z;
W = 0.0f;
}
else
{
Vector3 c = n0.Cross(n1);
real_t s = Mathf.Sqrt((1.0f + d) * 2.0f);
real_t rs = 1.0f / s;
X = c.X * rs;
Y = c.Y * rs;
Z = c.Z * rs;
W = s * 0.5f;
}
this = Normalized();
}
/// <summary>
/// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
/// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
/// given in the vector format as (X angle, Y angle, Z angle).
/// </summary>
/// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
public static Quaternion FromEuler(Vector3 eulerYXZ)
{
real_t halfA1 = eulerYXZ.Y * 0.5f;
real_t halfA2 = eulerYXZ.X * 0.5f;
real_t halfA3 = eulerYXZ.Z * 0.5f;
// R = Y(a1).X(a2).Z(a3) convention for Euler angles.
// Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
// a3 is the angle of the first rotation, following the notation in this reference.
(real_t sinA1, real_t cosA1) = Mathf.SinCos(halfA1);
(real_t sinA2, real_t cosA2) = Mathf.SinCos(halfA2);
(real_t sinA3, real_t cosA3) = Mathf.SinCos(halfA3);
return new Quaternion(
(sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3),
(sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3),
(cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3),
(sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3)
);
}
/// <summary>
/// Composes these two quaternions by multiplying them together.
/// This has the effect of rotating the second quaternion
/// (the child) by the first quaternion (the parent).
/// </summary>
/// <param name="left">The parent quaternion.</param>
/// <param name="right">The child quaternion.</param>
/// <returns>The composed quaternion.</returns>
public static Quaternion operator *(Quaternion left, Quaternion right)
{
return new Quaternion
(
(left.W * right.X) + (left.X * right.W) + (left.Y * right.Z) - (left.Z * right.Y),
(left.W * right.Y) + (left.Y * right.W) + (left.Z * right.X) - (left.X * right.Z),
(left.W * right.Z) + (left.Z * right.W) + (left.X * right.Y) - (left.Y * right.X),
(left.W * right.W) - (left.X * right.X) - (left.Y * right.Y) - (left.Z * right.Z)
);
}
/// <summary>
/// Returns a Vector3 rotated (multiplied) by the quaternion.
/// </summary>
/// <param name="quaternion">The quaternion to rotate by.</param>
/// <param name="vector">A Vector3 to transform.</param>
/// <returns>The rotated Vector3.</returns>
public static Vector3 operator *(Quaternion quaternion, Vector3 vector)
{
#if DEBUG
if (!quaternion.IsNormalized())
{
throw new InvalidOperationException("Quaternion is not normalized.");
}
#endif
var u = new Vector3(quaternion.X, quaternion.Y, quaternion.Z);
Vector3 uv = u.Cross(vector);
return vector + (((uv * quaternion.W) + u.Cross(uv)) * 2);
}
/// <summary>
/// Returns a Vector3 rotated (multiplied) by the inverse quaternion.
/// <c>vector * quaternion</c> is equivalent to <c>quaternion.Inverse() * vector</c>. See <see cref="Inverse"/>.
/// </summary>
/// <param name="vector">A Vector3 to inversely rotate.</param>
/// <param name="quaternion">The quaternion to rotate by.</param>
/// <returns>The inversely rotated Vector3.</returns>
public static Vector3 operator *(Vector3 vector, Quaternion quaternion)
{
return quaternion.Inverse() * vector;
}
/// <summary>
/// Adds each component of the left <see cref="Quaternion"/>
/// to the right <see cref="Quaternion"/>. This operation is not
/// meaningful on its own, but it can be used as a part of a
/// larger expression, such as approximating an intermediate
/// rotation between two nearby rotations.
/// </summary>
/// <param name="left">The left quaternion to add.</param>
/// <param name="right">The right quaternion to add.</param>
/// <returns>The added quaternion.</returns>
public static Quaternion operator +(Quaternion left, Quaternion right)
{
return new Quaternion(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W);
}
/// <summary>
/// Subtracts each component of the left <see cref="Quaternion"/>
/// by the right <see cref="Quaternion"/>. This operation is not
/// meaningful on its own, but it can be used as a part of a
/// larger expression.
/// </summary>
/// <param name="left">The left quaternion to subtract.</param>
/// <param name="right">The right quaternion to subtract.</param>
/// <returns>The subtracted quaternion.</returns>
public static Quaternion operator -(Quaternion left, Quaternion right)
{
return new Quaternion(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W);
}
/// <summary>
/// Returns the negative value of the <see cref="Quaternion"/>.
/// This is the same as writing
/// <c>new Quaternion(-q.X, -q.Y, -q.Z, -q.W)</c>. This operation
/// results in a quaternion that represents the same rotation.
/// </summary>
/// <param name="quat">The quaternion to negate.</param>
/// <returns>The negated quaternion.</returns>
public static Quaternion operator -(Quaternion quat)
{
return new Quaternion(-quat.X, -quat.Y, -quat.Z, -quat.W);
}
/// <summary>
/// Multiplies each component of the <see cref="Quaternion"/>
/// by the given <see cref="real_t"/>. This operation is not
/// meaningful on its own, but it can be used as a part of a
/// larger expression.
/// </summary>
/// <param name="left">The quaternion to multiply.</param>
/// <param name="right">The value to multiply by.</param>
/// <returns>The multiplied quaternion.</returns>
public static Quaternion operator *(Quaternion left, real_t right)
{
return new Quaternion(left.X * right, left.Y * right, left.Z * right, left.W * right);
}
/// <summary>
/// Multiplies each component of the <see cref="Quaternion"/>
/// by the given <see cref="real_t"/>. This operation is not
/// meaningful on its own, but it can be used as a part of a
/// larger expression.
/// </summary>
/// <param name="left">The value to multiply by.</param>
/// <param name="right">The quaternion to multiply.</param>
/// <returns>The multiplied quaternion.</returns>
public static Quaternion operator *(real_t left, Quaternion right)
{
return new Quaternion(right.X * left, right.Y * left, right.Z * left, right.W * left);
}
/// <summary>
/// Divides each component of the <see cref="Quaternion"/>
/// by the given <see cref="real_t"/>. This operation is not
/// meaningful on its own, but it can be used as a part of a
/// larger expression.
/// </summary>
/// <param name="left">The quaternion to divide.</param>
/// <param name="right">The value to divide by.</param>
/// <returns>The divided quaternion.</returns>
public static Quaternion operator /(Quaternion left, real_t right)
{
return left * (1.0f / right);
}
/// <summary>
/// Returns <see langword="true"/> if the quaternions are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left quaternion.</param>
/// <param name="right">The right quaternion.</param>
/// <returns>Whether or not the quaternions are exactly equal.</returns>
public static bool operator ==(Quaternion left, Quaternion right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the quaternions are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left quaternion.</param>
/// <param name="right">The right quaternion.</param>
/// <returns>Whether or not the quaternions are not equal.</returns>
public static bool operator !=(Quaternion left, Quaternion right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if this quaternion and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the quaternion and the other object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Quaternion other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are equal.
/// </summary>
/// <param name="other">The other quaternion to compare.</param>
/// <returns>Whether or not the quaternions are exactly equal.</returns>
public readonly bool Equals(Quaternion other)
{
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
/// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are approximately equal,
/// by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
/// </summary>
/// <param name="other">The other quaternion to compare.</param>
/// <returns>Whether or not the quaternions are approximately equal.</returns>
public readonly bool IsEqualApprox(Quaternion other)
{
return Mathf.IsEqualApprox(X, other.X) && Mathf.IsEqualApprox(Y, other.Y) && Mathf.IsEqualApprox(Z, other.Z) && Mathf.IsEqualApprox(W, other.W);
}
/// <summary>
/// Serves as the hash function for <see cref="Quaternion"/>.
/// </summary>
/// <returns>A hash code for this quaternion.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
/// Converts this <see cref="Quaternion"/> to a string.
/// </summary>
/// <returns>A string representation of this quaternion.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this quaternion.</returns>
public readonly string ToString(string? format)
{
return $"({X.ToString(format, CultureInfo.InvariantCulture)}, {Y.ToString(format, CultureInfo.InvariantCulture)}, {Z.ToString(format, CultureInfo.InvariantCulture)}, {W.ToString(format, CultureInfo.InvariantCulture)})";
}
}
}

View File

@@ -0,0 +1,499 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 2D axis-aligned bounding box. Rect2 consists of a position, a size, and
/// several utility functions. It is typically used for fast overlap tests.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Rect2 : IEquatable<Rect2>
{
private Vector2 _position;
private Vector2 _size;
/// <summary>
/// Beginning corner. Typically has values lower than <see cref="End"/>.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector2 Position
{
readonly get { return _position; }
set { _position = value; }
}
/// <summary>
/// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive.
/// If the size is negative, you can use <see cref="Abs"/> to fix it.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector2 Size
{
readonly get { return _size; }
set { _size = value; }
}
/// <summary>
/// Ending corner. This is calculated as <see cref="Position"/> plus <see cref="Size"/>.
/// Setting this value will change the size.
/// </summary>
/// <value>
/// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>,
/// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/>
/// </value>
public Vector2 End
{
readonly get { return _position + _size; }
set { _size = value - _position; }
}
/// <summary>
/// The area of this <see cref="Rect2"/>.
/// See also <see cref="HasArea"/>.
/// </summary>
public readonly real_t Area
{
get { return _size.X * _size.Y; }
}
/// <summary>
/// Returns a <see cref="Rect2"/> with equivalent position and size, modified so that
/// the top-left corner is the origin and width and height are positive.
/// </summary>
/// <returns>The modified <see cref="Rect2"/>.</returns>
public readonly Rect2 Abs()
{
Vector2 end = End;
Vector2 topLeft = end.Min(_position);
return new Rect2(topLeft, _size.Abs());
}
/// <summary>
/// Returns the intersection of this <see cref="Rect2"/> and <paramref name="b"/>.
/// If the rectangles do not intersect, an empty <see cref="Rect2"/> is returned.
/// </summary>
/// <param name="b">The other <see cref="Rect2"/>.</param>
/// <returns>
/// The intersection of this <see cref="Rect2"/> and <paramref name="b"/>,
/// or an empty <see cref="Rect2"/> if they do not intersect.
/// </returns>
public readonly Rect2 Intersection(Rect2 b)
{
Rect2 newRect = b;
if (!Intersects(newRect))
{
return new Rect2();
}
newRect._position = b._position.Max(_position);
Vector2 bEnd = b._position + b._size;
Vector2 end = _position + _size;
newRect._size = bEnd.Min(end) - newRect._position;
return newRect;
}
/// <summary>
/// Returns <see langword="true"/> if this <see cref="Rect2"/> is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public bool IsFinite()
{
return _position.IsFinite() && _size.IsFinite();
}
/// <summary>
/// Returns <see langword="true"/> if this <see cref="Rect2"/> completely encloses another one.
/// </summary>
/// <param name="b">The other <see cref="Rect2"/> that may be enclosed.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not this <see cref="Rect2"/> encloses <paramref name="b"/>.
/// </returns>
public readonly bool Encloses(Rect2 b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
b._position.X + b._size.X <= _position.X + _size.X &&
b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
/// Returns this <see cref="Rect2"/> expanded to include a given point.
/// </summary>
/// <param name="to">The point to include.</param>
/// <returns>The expanded <see cref="Rect2"/>.</returns>
public readonly Rect2 Expand(Vector2 to)
{
Rect2 expanded = this;
Vector2 begin = expanded._position;
Vector2 end = expanded._position + expanded._size;
if (to.X < begin.X)
{
begin.X = to.X;
}
if (to.Y < begin.Y)
{
begin.Y = to.Y;
}
if (to.X > end.X)
{
end.X = to.X;
}
if (to.Y > end.Y)
{
end.Y = to.Y;
}
expanded._position = begin;
expanded._size = end - begin;
return expanded;
}
/// <summary>
/// Returns the center of the <see cref="Rect2"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// </summary>
/// <returns>The center.</returns>
public readonly Vector2 GetCenter()
{
return _position + (_size * 0.5f);
}
/// <summary>
/// Returns the support point in a given direction.
/// This is useful for collision detection algorithms.
/// </summary>
/// <param name="direction">The direction to find support for.</param>
/// <returns>A vector representing the support.</returns>
public readonly Vector2 GetSupport(Vector2 direction)
{
Vector2 support = _position;
if (direction.X > 0.0f)
{
support.X += _size.X;
}
if (direction.Y > 0.0f)
{
support.Y += _size.Y;
}
return support;
}
/// <summary>
/// Returns a copy of the <see cref="Rect2"/> grown by the specified amount
/// on all sides.
/// </summary>
/// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/>
/// <seealso cref="GrowSide(Side, real_t)"/>
/// <param name="by">The amount to grow by.</param>
/// <returns>The grown <see cref="Rect2"/>.</returns>
public readonly Rect2 Grow(real_t by)
{
Rect2 g = this;
g._position.X -= by;
g._position.Y -= by;
g._size.X += by * 2;
g._size.Y += by * 2;
return g;
}
/// <summary>
/// Returns a copy of the <see cref="Rect2"/> grown by the specified amount
/// on each side individually.
/// </summary>
/// <seealso cref="Grow(real_t)"/>
/// <seealso cref="GrowSide(Side, real_t)"/>
/// <param name="left">The amount to grow by on the left side.</param>
/// <param name="top">The amount to grow by on the top side.</param>
/// <param name="right">The amount to grow by on the right side.</param>
/// <param name="bottom">The amount to grow by on the bottom side.</param>
/// <returns>The grown <see cref="Rect2"/>.</returns>
public readonly Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom)
{
Rect2 g = this;
g._position.X -= left;
g._position.Y -= top;
g._size.X += left + right;
g._size.Y += top + bottom;
return g;
}
/// <summary>
/// Returns a copy of the <see cref="Rect2"/> grown by the specified amount
/// on the specified <see cref="Side"/>.
/// </summary>
/// <seealso cref="Grow(real_t)"/>
/// <seealso cref="GrowIndividual(real_t, real_t, real_t, real_t)"/>
/// <param name="side">The side to grow.</param>
/// <param name="by">The amount to grow by.</param>
/// <returns>The grown <see cref="Rect2"/>.</returns>
public readonly Rect2 GrowSide(Side side, real_t by)
{
Rect2 g = this;
g = g.GrowIndividual(Side.Left == side ? by : 0,
Side.Top == side ? by : 0,
Side.Right == side ? by : 0,
Side.Bottom == side ? by : 0);
return g;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
/// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area.
/// </returns>
public readonly bool HasArea()
{
return _size.X > 0.0f && _size.Y > 0.0f;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2"/> contains a point,
/// or <see langword="false"/> otherwise.
/// </summary>
/// <param name="point">The point to check.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> contains <paramref name="point"/>.
/// </returns>
public readonly bool HasPoint(Vector2 point)
{
if (point.X < _position.X)
return false;
if (point.Y < _position.Y)
return false;
if (point.X >= _position.X + _size.X)
return false;
if (point.Y >= _position.Y + _size.Y)
return false;
return true;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2"/> overlaps with <paramref name="b"/>
/// (i.e. they have at least one point in common).
///
/// If <paramref name="includeBorders"/> is <see langword="true"/>,
/// they will also be considered overlapping if their borders touch,
/// even without intersection.
/// </summary>
/// <param name="b">The other <see cref="Rect2"/> to check for intersections with.</param>
/// <param name="includeBorders">Whether or not to consider borders.</param>
/// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns>
public readonly bool Intersects(Rect2 b, bool includeBorders = false)
{
if (includeBorders)
{
if (_position.X > b._position.X + b._size.X)
{
return false;
}
if (_position.X + _size.X < b._position.X)
{
return false;
}
if (_position.Y > b._position.Y + b._size.Y)
{
return false;
}
if (_position.Y + _size.Y < b._position.Y)
{
return false;
}
}
else
{
if (_position.X >= b._position.X + b._size.X)
{
return false;
}
if (_position.X + _size.X <= b._position.X)
{
return false;
}
if (_position.Y >= b._position.Y + b._size.Y)
{
return false;
}
if (_position.Y + _size.Y <= b._position.Y)
{
return false;
}
}
return true;
}
/// <summary>
/// Returns a larger <see cref="Rect2"/> that contains this <see cref="Rect2"/> and <paramref name="b"/>.
/// </summary>
/// <param name="b">The other <see cref="Rect2"/>.</param>
/// <returns>The merged <see cref="Rect2"/>.</returns>
public readonly Rect2 Merge(Rect2 b)
{
Rect2 newRect;
newRect._position = b._position.Min(_position);
newRect._size = (b._position + b._size).Max(_position + _size);
newRect._size -= newRect._position; // Make relative again
return newRect;
}
/// <summary>
/// Constructs a <see cref="Rect2"/> from a position and size.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="size">The size.</param>
public Rect2(Vector2 position, Vector2 size)
{
_position = position;
_size = size;
}
/// <summary>
/// Constructs a <see cref="Rect2"/> from a position, width, and height.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Rect2(Vector2 position, real_t width, real_t height)
{
_position = position;
_size = new Vector2(width, height);
}
/// <summary>
/// Constructs a <see cref="Rect2"/> from x, y, and size.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="size">The size.</param>
public Rect2(real_t x, real_t y, Vector2 size)
{
_position = new Vector2(x, y);
_size = size;
}
/// <summary>
/// Constructs a <see cref="Rect2"/> from x, y, width, and height.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Rect2(real_t x, real_t y, real_t width, real_t height)
{
_position = new Vector2(x, y);
_size = new Vector2(width, height);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Rect2"/>s are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the rects are exactly equal.</returns>
public static bool operator ==(Rect2 left, Rect2 right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Rect2"/>s are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the rects are not equal.</returns>
public static bool operator !=(Rect2 left, Rect2 right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the rect and the other object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rect2 other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal.
/// </summary>
/// <param name="other">The other rect to compare.</param>
/// <returns>Whether or not the rects are exactly equal.</returns>
public readonly bool Equals(Rect2 other)
{
return _position.Equals(other._position) && _size.Equals(other._size);
}
/// <summary>
/// Returns <see langword="true"/> if this rect and <paramref name="other"/> are approximately equal,
/// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
/// </summary>
/// <param name="other">The other rect to compare.</param>
/// <returns>Whether or not the rects are approximately equal.</returns>
public readonly bool IsEqualApprox(Rect2 other)
{
return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size);
}
/// <summary>
/// Serves as the hash function for <see cref="Rect2"/>.
/// </summary>
/// <returns>A hash code for this rect.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_position, _size);
}
/// <summary>
/// Converts this <see cref="Rect2"/> to a string.
/// </summary>
/// <returns>A string representation of this rect.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Rect2"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this rect.</returns>
public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
}
}

View File

@@ -0,0 +1,439 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 2D axis-aligned bounding box using integers. Rect2I consists of a position, a size, and
/// several utility functions. It is typically used for fast overlap tests.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Rect2I : IEquatable<Rect2I>
{
private Vector2I _position;
private Vector2I _size;
/// <summary>
/// Beginning corner. Typically has values lower than <see cref="End"/>.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector2I Position
{
readonly get { return _position; }
set { _position = value; }
}
/// <summary>
/// Size from <see cref="Position"/> to <see cref="End"/>. Typically all components are positive.
/// If the size is negative, you can use <see cref="Abs"/> to fix it.
/// </summary>
/// <value>Directly uses a private field.</value>
public Vector2I Size
{
readonly get { return _size; }
set { _size = value; }
}
/// <summary>
/// Ending corner. This is calculated as <see cref="Position"/> plus <see cref="Size"/>.
/// Setting this value will change the size.
/// </summary>
/// <value>
/// Getting is equivalent to <paramref name="value"/> = <see cref="Position"/> + <see cref="Size"/>,
/// setting is equivalent to <see cref="Size"/> = <paramref name="value"/> - <see cref="Position"/>
/// </value>
public Vector2I End
{
readonly get { return _position + _size; }
set { _size = value - _position; }
}
/// <summary>
/// The area of this <see cref="Rect2I"/>.
/// See also <see cref="HasArea"/>.
/// </summary>
public readonly int Area
{
get { return _size.X * _size.Y; }
}
/// <summary>
/// Returns a <see cref="Rect2I"/> with equivalent position and size, modified so that
/// the top-left corner is the origin and width and height are positive.
/// </summary>
/// <returns>The modified <see cref="Rect2I"/>.</returns>
public readonly Rect2I Abs()
{
Vector2I end = End;
Vector2I topLeft = end.Min(_position);
return new Rect2I(topLeft, _size.Abs());
}
/// <summary>
/// Returns the intersection of this <see cref="Rect2I"/> and <paramref name="b"/>.
/// If the rectangles do not intersect, an empty <see cref="Rect2I"/> is returned.
/// </summary>
/// <param name="b">The other <see cref="Rect2I"/>.</param>
/// <returns>
/// The intersection of this <see cref="Rect2I"/> and <paramref name="b"/>,
/// or an empty <see cref="Rect2I"/> if they do not intersect.
/// </returns>
public readonly Rect2I Intersection(Rect2I b)
{
Rect2I newRect = b;
if (!Intersects(newRect))
{
return new Rect2I();
}
newRect._position = b._position.Max(_position);
Vector2I bEnd = b._position + b._size;
Vector2I end = _position + _size;
newRect._size = bEnd.Min(end) - newRect._position;
return newRect;
}
/// <summary>
/// Returns <see langword="true"/> if this <see cref="Rect2I"/> completely encloses another one.
/// </summary>
/// <param name="b">The other <see cref="Rect2I"/> that may be enclosed.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not this <see cref="Rect2I"/> encloses <paramref name="b"/>.
/// </returns>
public readonly bool Encloses(Rect2I b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
b._position.X + b._size.X <= _position.X + _size.X &&
b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
/// Returns this <see cref="Rect2I"/> expanded to include a given point.
/// </summary>
/// <param name="to">The point to include.</param>
/// <returns>The expanded <see cref="Rect2I"/>.</returns>
public readonly Rect2I Expand(Vector2I to)
{
Rect2I expanded = this;
Vector2I begin = expanded._position;
Vector2I end = expanded._position + expanded._size;
if (to.X < begin.X)
{
begin.X = to.X;
}
if (to.Y < begin.Y)
{
begin.Y = to.Y;
}
if (to.X > end.X)
{
end.X = to.X;
}
if (to.Y > end.Y)
{
end.Y = to.Y;
}
expanded._position = begin;
expanded._size = end - begin;
return expanded;
}
/// <summary>
/// Returns the center of the <see cref="Rect2I"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// If <see cref="Size"/> is an odd number, the returned center
/// value will be rounded towards <see cref="Position"/>.
/// </summary>
/// <returns>The center.</returns>
public readonly Vector2I GetCenter()
{
return _position + (_size / 2);
}
/// <summary>
/// Returns a copy of the <see cref="Rect2I"/> grown by the specified amount
/// on all sides.
/// </summary>
/// <seealso cref="GrowIndividual(int, int, int, int)"/>
/// <seealso cref="GrowSide(Side, int)"/>
/// <param name="by">The amount to grow by.</param>
/// <returns>The grown <see cref="Rect2I"/>.</returns>
public readonly Rect2I Grow(int by)
{
Rect2I g = this;
g._position.X -= by;
g._position.Y -= by;
g._size.X += by * 2;
g._size.Y += by * 2;
return g;
}
/// <summary>
/// Returns a copy of the <see cref="Rect2I"/> grown by the specified amount
/// on each side individually.
/// </summary>
/// <seealso cref="Grow(int)"/>
/// <seealso cref="GrowSide(Side, int)"/>
/// <param name="left">The amount to grow by on the left side.</param>
/// <param name="top">The amount to grow by on the top side.</param>
/// <param name="right">The amount to grow by on the right side.</param>
/// <param name="bottom">The amount to grow by on the bottom side.</param>
/// <returns>The grown <see cref="Rect2I"/>.</returns>
public readonly Rect2I GrowIndividual(int left, int top, int right, int bottom)
{
Rect2I g = this;
g._position.X -= left;
g._position.Y -= top;
g._size.X += left + right;
g._size.Y += top + bottom;
return g;
}
/// <summary>
/// Returns a copy of the <see cref="Rect2I"/> grown by the specified amount
/// on the specified <see cref="Side"/>.
/// </summary>
/// <seealso cref="Grow(int)"/>
/// <seealso cref="GrowIndividual(int, int, int, int)"/>
/// <param name="side">The side to grow.</param>
/// <param name="by">The amount to grow by.</param>
/// <returns>The grown <see cref="Rect2I"/>.</returns>
public readonly Rect2I GrowSide(Side side, int by)
{
Rect2I g = this;
g = g.GrowIndividual(Side.Left == side ? by : 0,
Side.Top == side ? by : 0,
Side.Right == side ? by : 0,
Side.Bottom == side ? by : 0);
return g;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2I"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2I"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
/// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2I"/> has area.
/// </returns>
public readonly bool HasArea()
{
return _size.X > 0 && _size.Y > 0;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2I"/> contains a point,
/// or <see langword="false"/> otherwise.
/// </summary>
/// <param name="point">The point to check.</param>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2I"/> contains <paramref name="point"/>.
/// </returns>
public readonly bool HasPoint(Vector2I point)
{
if (point.X < _position.X)
return false;
if (point.Y < _position.Y)
return false;
if (point.X >= _position.X + _size.X)
return false;
if (point.Y >= _position.Y + _size.Y)
return false;
return true;
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rect2I"/> overlaps with <paramref name="b"/>
/// (i.e. they have at least one point in common).
/// </summary>
/// <param name="b">The other <see cref="Rect2I"/> to check for intersections with.</param>
/// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns>
public readonly bool Intersects(Rect2I b)
{
if (_position.X >= b._position.X + b._size.X)
return false;
if (_position.X + _size.X <= b._position.X)
return false;
if (_position.Y >= b._position.Y + b._size.Y)
return false;
if (_position.Y + _size.Y <= b._position.Y)
return false;
return true;
}
/// <summary>
/// Returns a larger <see cref="Rect2I"/> that contains this <see cref="Rect2I"/> and <paramref name="b"/>.
/// </summary>
/// <param name="b">The other <see cref="Rect2I"/>.</param>
/// <returns>The merged <see cref="Rect2I"/>.</returns>
public readonly Rect2I Merge(Rect2I b)
{
Rect2I newRect;
newRect._position = b._position.Min(_position);
newRect._size = (b._position + b._size).Max(_position + _size);
newRect._size -= newRect._position; // Make relative again
return newRect;
}
/// <summary>
/// Constructs a <see cref="Rect2I"/> from a position and size.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="size">The size.</param>
public Rect2I(Vector2I position, Vector2I size)
{
_position = position;
_size = size;
}
/// <summary>
/// Constructs a <see cref="Rect2I"/> from a position, width, and height.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Rect2I(Vector2I position, int width, int height)
{
_position = position;
_size = new Vector2I(width, height);
}
/// <summary>
/// Constructs a <see cref="Rect2I"/> from x, y, and size.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="size">The size.</param>
public Rect2I(int x, int y, Vector2I size)
{
_position = new Vector2I(x, y);
_size = size;
}
/// <summary>
/// Constructs a <see cref="Rect2I"/> from x, y, width, and height.
/// </summary>
/// <param name="x">The position's X coordinate.</param>
/// <param name="y">The position's Y coordinate.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Rect2I(int x, int y, int width, int height)
{
_position = new Vector2I(x, y);
_size = new Vector2I(width, height);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Rect2I"/>s are exactly equal.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the rects are equal.</returns>
public static bool operator ==(Rect2I left, Rect2I right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the
/// <see cref="Rect2I"/>s are not equal.
/// </summary>
/// <param name="left">The left rect.</param>
/// <param name="right">The right rect.</param>
/// <returns>Whether or not the rects are not equal.</returns>
public static bool operator !=(Rect2I left, Rect2I right)
{
return !left.Equals(right);
}
/// <summary>
/// Converts this <see cref="Rect2I"/> to a <see cref="Rect2"/>.
/// </summary>
/// <param name="value">The rect to convert.</param>
public static implicit operator Rect2(Rect2I value)
{
return new Rect2(value._position, value._size);
}
/// <summary>
/// Converts a <see cref="Rect2"/> to a <see cref="Rect2I"/>.
/// </summary>
/// <param name="value">The rect to convert.</param>
public static explicit operator Rect2I(Rect2 value)
{
return new Rect2I((Vector2I)value.Position, (Vector2I)value.Size);
}
/// <summary>
/// Returns <see langword="true"/> if this rect and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the rect and the other object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rect2I other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if this rect and <paramref name="other"/> are equal.
/// </summary>
/// <param name="other">The other rect to compare.</param>
/// <returns>Whether or not the rects are equal.</returns>
public readonly bool Equals(Rect2I other)
{
return _position.Equals(other._position) && _size.Equals(other._size);
}
/// <summary>
/// Serves as the hash function for <see cref="Rect2I"/>.
/// </summary>
/// <returns>A hash code for this rect.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_position, _size);
}
/// <summary>
/// Converts this <see cref="Rect2I"/> to a string.
/// </summary>
/// <returns>A string representation of this rect.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Rect2I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this rect.</returns>
public readonly string ToString(string? format)
{
return $"{_position.ToString(format)}, {_size.ToString(format)}";
}
}
}

View File

@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
#nullable enable
namespace Godot;
internal class ReflectionUtils
{
private static readonly HashSet<Type>? _tupleTypeSet;
private static readonly Dictionary<Type, string>? _builtinTypeNameDictionary;
internal static readonly bool IsEditorHintCached;
static ReflectionUtils()
{
IsEditorHintCached = Engine.IsEditorHint();
if (!IsEditorHintCached)
{
return;
}
_tupleTypeSet = new HashSet<Type>
{
// ValueTuple with only one element should be treated as normal generic type.
//typeof(ValueTuple<>),
typeof(ValueTuple<,>),
typeof(ValueTuple<,,>),
typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>),
typeof(ValueTuple<,,,,,>),
typeof(ValueTuple<,,,,,,>),
typeof(ValueTuple<,,,,,,,>),
};
_builtinTypeNameDictionary ??= new Dictionary<Type, string>
{
{ typeof(sbyte), "sbyte" },
{ typeof(byte), "byte" },
{ typeof(short), "short" },
{ typeof(ushort), "ushort" },
{ typeof(int), "int" },
{ typeof(uint), "uint" },
{ typeof(long), "long" },
{ typeof(ulong), "ulong" },
{ typeof(nint), "nint" },
{ typeof(nuint), "nuint" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" },
{ typeof(bool), "bool" },
{ typeof(char), "char" },
{ typeof(string), "string" },
{ typeof(object), "object" },
};
}
public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
.GetType(typeFullName);
}
public static string ConstructTypeName(Type type)
{
if (!IsEditorHintCached)
{
return type.Name;
}
if (type is { IsArray: false, IsGenericType: false })
{
return GetSimpleTypeName(type);
}
var typeNameBuilder = new StringBuilder();
AppendType(typeNameBuilder, type);
return typeNameBuilder.ToString();
static void AppendType(StringBuilder sb, Type type)
{
if (type.IsArray)
{
AppendArray(sb, type);
}
else if (type.IsGenericType)
{
AppendGeneric(sb, type);
}
else
{
sb.Append(GetSimpleTypeName(type));
}
}
static void AppendArray(StringBuilder sb, Type type)
{
// Append inner most non-array element.
var elementType = type.GetElementType()!;
while (elementType.IsArray)
{
elementType = elementType.GetElementType()!;
}
AppendType(sb, elementType);
// Append brackets.
AppendArrayBrackets(sb, type);
static void AppendArrayBrackets(StringBuilder sb, Type? type)
{
while (type != null && type.IsArray)
{
int rank = type.GetArrayRank();
sb.Append('[');
sb.Append(',', rank - 1);
sb.Append(']');
type = type.GetElementType();
}
}
}
static void AppendGeneric(StringBuilder sb, Type type)
{
var genericArgs = type.GenericTypeArguments;
var genericDefinition = type.GetGenericTypeDefinition();
// Nullable<T>
if (genericDefinition == typeof(Nullable<>))
{
AppendType(sb, genericArgs[0]);
sb.Append('?');
return;
}
// ValueTuple
Debug.Assert(_tupleTypeSet != null);
if (_tupleTypeSet.Contains(genericDefinition))
{
sb.Append('(');
while (true)
{
// We assume that ValueTuple has 1~8 elements.
// And the 8th element (TRest) is always another ValueTuple.
// This is a hard coded tuple element length check.
if (genericArgs.Length != 8)
{
AppendParamTypes(sb, genericArgs);
break;
}
else
{
AppendParamTypes(sb, genericArgs.AsSpan(0, 7));
sb.Append(", ");
// TRest should be a ValueTuple!
var nextTuple = genericArgs[7];
genericArgs = nextTuple.GenericTypeArguments;
}
}
sb.Append(')');
return;
}
// Normal generic
var typeName = type.Name.AsSpan();
sb.Append(typeName[..typeName.LastIndexOf('`')]);
sb.Append('<');
AppendParamTypes(sb, genericArgs);
sb.Append('>');
static void AppendParamTypes(StringBuilder sb, ReadOnlySpan<Type> genericArgs)
{
int n = genericArgs.Length - 1;
for (int i = 0; i < n; i += 1)
{
AppendType(sb, genericArgs[i]);
sb.Append(", ");
}
AppendType(sb, genericArgs[n]);
}
}
static string GetSimpleTypeName(Type type)
{
Debug.Assert(_builtinTypeNameDictionary != null);
return _builtinTypeNameDictionary.TryGetValue(type, out string? name) ? name : type.Name;
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// The RID type is used to access a low-level resource by its unique ID.
/// RIDs are opaque, which means they do not grant access to the resource
/// by themselves. They are used by the low-level server classes, such as
/// <see cref="DisplayServer"/>, <see cref="RenderingServer"/>,
/// <see cref="TextServer"/>, etc.
///
/// A low-level resource may correspond to a high-level <see cref="Resource"/>,
/// such as <see cref="Texture"/> or <see cref="Mesh"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct Rid : IEquatable<Rid>
{
private readonly ulong _id; // Default is 0
internal Rid(ulong id)
{
_id = id;
}
/// <summary>
/// Constructs a new <see cref="Rid"/> for the given <see cref="GodotObject"/> <paramref name="from"/>.
/// </summary>
public Rid(GodotObject from)
=> _id = from is Resource res ? res.GetRid()._id : default;
/// <summary>
/// Returns the ID of the referenced low-level resource.
/// </summary>
/// <returns>The ID of the referenced resource.</returns>
public ulong Id => _id;
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rid"/> is not <c>0</c>.
/// </summary>
/// <returns>Whether or not the ID is valid.</returns>
public bool IsValid => _id != 0;
/// <summary>
/// Returns <see langword="true"/> if both <see cref="Rid"/>s are equal,
/// which means they both refer to the same low-level resource.
/// </summary>
/// <param name="left">The left RID.</param>
/// <param name="right">The right RID.</param>
/// <returns>Whether or not the RIDs are equal.</returns>
public static bool operator ==(Rid left, Rid right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the <see cref="Rid"/>s are not equal.
/// </summary>
/// <param name="left">The left RID.</param>
/// <param name="right">The right RID.</param>
/// <returns>Whether or not the RIDs are equal.</returns>
public static bool operator !=(Rid left, Rid right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if this RID and <paramref name="obj"/> are equal.
/// </summary>
/// <param name="obj">The other object to compare.</param>
/// <returns>Whether or not the color and the other object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Rid other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the RIDs are equal.
/// </summary>
/// <param name="other">The other RID.</param>
/// <returns>Whether or not the RIDs are equal.</returns>
public readonly bool Equals(Rid other)
{
return _id == other.Id;
}
/// <summary>
/// Serves as the hash function for <see cref="Rid"/>.
/// </summary>
/// <returns>A hash code for this RID.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(_id);
}
/// <summary>
/// Converts this <see cref="Rid"/> to a string.
/// </summary>
/// <returns>A string representation of this Rid.</returns>
public override readonly string ToString() => $"RID({Id.ToString(null, CultureInfo.InvariantCulture)})";
}
}

View File

@@ -0,0 +1,38 @@
namespace Godot
{
/// <summary>
/// Represents a signal defined in an object.
/// </summary>
public readonly struct Signal : IAwaitable<Variant[]>
{
private readonly GodotObject _owner;
private readonly StringName _signalName;
/// <summary>
/// Object that contains the signal.
/// </summary>
public GodotObject Owner => _owner;
/// <summary>
/// Name of the signal.
/// </summary>
public StringName Name => _signalName;
/// <summary>
/// Creates a new <see cref="Signal"/> with the name <paramref name="name"/>
/// in the specified <paramref name="owner"/>.
/// </summary>
/// <param name="owner">Object that contains the signal.</param>
/// <param name="name">Name of the signal.</param>
public Signal(GodotObject owner, StringName name)
{
_owner = owner;
_signalName = name;
}
public IAwaiter<Variant[]> GetAwaiter()
{
return new SignalAwaiter(_owner, _signalName, _owner);
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
{
public class SignalAwaiter : IAwaiter<Variant[]>, IAwaitable<Variant[]>
{
private bool _completed;
private Variant[] _result;
private Action _continuation;
public SignalAwaiter(GodotObject source, StringName signal, GodotObject target)
{
var awaiterGcHandle = CustomGCHandle.AllocStrong(this);
using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy(
(godot_string_name)(signal?.NativeValue ?? default));
NativeFuncs.godotsharp_internal_signal_awaiter_connect(GodotObject.GetPtr(source), in signalSrc,
GodotObject.GetPtr(target), GCHandle.ToIntPtr(awaiterGcHandle));
}
public bool IsCompleted => _completed;
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public Variant[] GetResult() => _result;
public IAwaiter<Variant[]> GetAwaiter() => this;
[UnmanagedCallersOnly]
internal static unsafe void SignalCallback(IntPtr awaiterGCHandlePtr, godot_variant** args, int argCount,
godot_bool* outAwaiterIsNull)
{
try
{
var awaiter = (SignalAwaiter)GCHandle.FromIntPtr(awaiterGCHandlePtr).Target;
if (awaiter == null)
{
*outAwaiterIsNull = godot_bool.True;
return;
}
*outAwaiterIsNull = godot_bool.False;
awaiter._completed = true;
if (argCount > 0)
{
Variant[] signalArgs = new Variant[argCount];
for (int i = 0; i < argCount; i++)
signalArgs[i] = Variant.CreateCopyingBorrowed(*args[i]);
awaiter._result = signalArgs;
}
else
{
awaiter._result = [];
}
awaiter._continuation?.Invoke();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outAwaiterIsNull = godot_bool.False;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
/// <summary>
/// StringNames are immutable strings designed for general-purpose representation of unique names.
/// StringName ensures that only one instance of a given name exists (so two StringNames with the
/// same value are the same object).
/// Comparing them is much faster than with regular strings, because only the pointers are compared,
/// not the whole strings.
/// </summary>
public sealed class StringName : IDisposable, IEquatable<StringName?>
{
internal godot_string_name.movable NativeValue;
private WeakReference<IDisposable>? _weakReferenceToSelf;
~StringName()
{
Dispose(false);
}
/// <summary>
/// Disposes of this <see cref="StringName"/>.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
}
private StringName(godot_string_name nativeValueToOwn)
{
NativeValue = (godot_string_name.movable)nativeValueToOwn;
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
internal static StringName CreateTakingOwnershipOfDisposableValue(godot_string_name nativeValueToOwn)
=> new StringName(nativeValueToOwn);
/// <summary>
/// Constructs an empty <see cref="StringName"/>.
/// </summary>
public StringName()
{
}
/// <summary>
/// Constructs a <see cref="StringName"/> from the given <paramref name="name"/> string.
/// </summary>
/// <param name="name">String to construct the <see cref="StringName"/> from.</param>
public StringName(string name)
{
if (!string.IsNullOrEmpty(name))
{
NativeValue = (godot_string_name.movable)NativeFuncs.godotsharp_string_name_new_from_string(name);
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
}
/// <summary>
/// Converts a string to a <see cref="StringName"/>.
/// </summary>
/// <param name="from">The string to convert.</param>
public static implicit operator StringName(string from) => new StringName(from);
/// <summary>
/// Converts a <see cref="StringName"/> to a string.
/// </summary>
/// <param name="from">The <see cref="StringName"/> to convert.</param>
[return: NotNullIfNotNull("from")]
public static implicit operator string?(StringName? from) => from?.ToString();
/// <summary>
/// Converts this <see cref="StringName"/> to a string.
/// </summary>
/// <returns>A string representation of this <see cref="StringName"/>.</returns>
public override string ToString()
{
if (IsEmpty)
return string.Empty;
var src = (godot_string_name)NativeValue;
NativeFuncs.godotsharp_string_name_as_string(out godot_string dest, src);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
/// <summary>
/// Check whether this <see cref="StringName"/> is empty.
/// </summary>
/// <returns>If the <see cref="StringName"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
public static bool operator ==(StringName? left, StringName? right)
{
if (left is null)
return right is null;
return left.Equals(right);
}
public static bool operator !=(StringName? left, StringName? right)
{
return !(left == right);
}
public bool Equals([NotNullWhen(true)] StringName? other)
{
if (other is null)
return false;
return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef;
}
public static bool operator ==(StringName? left, in godot_string_name right)
{
if (left is null)
return right.IsEmpty;
return left.Equals(right);
}
public static bool operator !=(StringName? left, in godot_string_name right)
{
return !(left == right);
}
public static bool operator ==(in godot_string_name left, StringName? right)
{
return right == left;
}
public static bool operator !=(in godot_string_name left, StringName? right)
{
return !(right == left);
}
public bool Equals(in godot_string_name other)
{
return NativeValue.DangerousSelfRef == other;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return ReferenceEquals(this, obj) || (obj is StringName other && Equals(other));
}
public override int GetHashCode()
{
return NativeValue.DangerousSelfRef.GetHashCode();
}
}
}

View File

@@ -0,0 +1,665 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations.
/// It can represent transformations such as translation, rotation, or scaling.
/// It consists of a three <see cref="Vector2"/> values: x, y, and the origin.
///
/// For more information, read this documentation article:
/// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Transform2D : IEquatable<Transform2D>
{
/// <summary>
/// The basis matrix's X vector (column 0). Equivalent to array index <c>[0]</c>.
/// </summary>
public Vector2 X;
/// <summary>
/// The basis matrix's Y vector (column 1). Equivalent to array index <c>[1]</c>.
/// </summary>
public Vector2 Y;
/// <summary>
/// The origin vector (column 2, the third column). Equivalent to array index <c>[2]</c>.
/// The origin vector represents translation.
/// </summary>
public Vector2 Origin;
/// <summary>
/// Returns the transform's rotation (in radians).
/// </summary>
public readonly real_t Rotation => Mathf.Atan2(X.Y, X.X);
/// <summary>
/// Returns the scale.
/// </summary>
public readonly Vector2 Scale
{
get
{
real_t detSign = Mathf.Sign(Determinant());
return new Vector2(X.Length(), detSign * Y.Length());
}
}
/// <summary>
/// Returns the transform's skew (in radians).
/// </summary>
public readonly real_t Skew
{
get
{
real_t detSign = Mathf.Sign(Determinant());
return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f;
}
}
/// <summary>
/// Access whole columns in the form of <see cref="Vector2"/>.
/// The third column is the <see cref="Origin"/> vector.
/// </summary>
/// <param name="column">Which column vector.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="column"/> is not 0, 1 or 2.
/// </exception>
public Vector2 this[int column]
{
readonly get
{
switch (column)
{
case 0:
return X;
case 1:
return Y;
case 2:
return Origin;
default:
throw new ArgumentOutOfRangeException(nameof(column));
}
}
set
{
switch (column)
{
case 0:
X = value;
return;
case 1:
Y = value;
return;
case 2:
Origin = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(column));
}
}
}
/// <summary>
/// Access matrix elements in column-major order.
/// The third column is the <see cref="Origin"/> vector.
/// </summary>
/// <param name="column">Which column, the matrix horizontal position.</param>
/// <param name="row">Which row, the matrix vertical position.</param>
public real_t this[int column, int row]
{
readonly get
{
return this[column][row];
}
set
{
Vector2 columnVector = this[column];
columnVector[row] = value;
this[column] = columnVector;
}
}
/// <summary>
/// Returns the inverse of the transform, under the assumption that
/// the basis is invertible (must have non-zero determinant).
/// </summary>
/// <seealso cref="Inverse"/>
/// <returns>The inverse transformation matrix.</returns>
public readonly Transform2D AffineInverse()
{
real_t det = Determinant();
if (det == 0)
throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
Transform2D inv = this;
inv[0, 0] = this[1, 1];
inv[1, 1] = this[0, 0];
real_t detInv = 1.0f / det;
inv[0] *= new Vector2(detInv, -detInv);
inv[1] *= new Vector2(-detInv, detInv);
inv[2] = inv.BasisXform(-inv[2]);
return inv;
}
/// <summary>
/// Returns the determinant of the basis matrix. If the basis is
/// uniformly scaled, then its determinant equals the square of the
/// scale factor.
///
/// A negative determinant means the basis was flipped, so one part of
/// the scale is negative. A zero determinant means the basis isn't
/// invertible, and is usually considered invalid.
/// </summary>
/// <returns>The determinant of the basis matrix.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly real_t Determinant()
{
return (X.X * Y.Y) - (X.Y * Y.X);
}
/// <summary>
/// Returns a vector transformed (multiplied) by the basis matrix.
/// This method does not account for translation (the <see cref="Origin"/> vector).
/// </summary>
/// <seealso cref="BasisXformInv(Vector2)"/>
/// <param name="v">A vector to transform.</param>
/// <returns>The transformed vector.</returns>
public readonly Vector2 BasisXform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v));
}
/// <summary>
/// Returns a vector transformed (multiplied) by the inverse basis matrix,
/// under the assumption that the basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// This method does not account for translation (the <see cref="Origin"/> vector).
/// <c>transform.BasisXformInv(vector)</c> is equivalent to <c>transform.Inverse().BasisXform(vector)</c>. See <see cref="Inverse"/>.
/// For non-orthonormal transforms (e.g. with scaling) <c>transform.AffineInverse().BasisXform(vector)</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <seealso cref="BasisXform(Vector2)"/>
/// <param name="v">A vector to inversely transform.</param>
/// <returns>The inversely transformed vector.</returns>
public readonly Vector2 BasisXformInv(Vector2 v)
{
return new Vector2(X.Dot(v), Y.Dot(v));
}
/// <summary>
/// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="weight"/>.
/// </summary>
/// <param name="transform">The other transform.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <returns>The interpolated transform.</returns>
public readonly Transform2D InterpolateWith(Transform2D transform, real_t weight)
{
return new Transform2D
(
Mathf.LerpAngle(Rotation, transform.Rotation, weight),
Scale.Lerp(transform.Scale, weight),
Mathf.LerpAngle(Skew, transform.Skew, weight),
Origin.Lerp(transform.Origin, weight)
);
}
/// <summary>
/// Returns the inverse of the transform, under the assumption that
/// the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for
/// non-orthonormal transforms (e.g. with scaling).
/// </summary>
/// <returns>The inverse matrix.</returns>
public readonly Transform2D Inverse()
{
Transform2D inv = this;
// Swap
inv.X.Y = Y.X;
inv.Y.X = X.Y;
inv.Origin = inv.BasisXform(-inv.Origin);
return inv;
}
/// <summary>
/// Returns <see langword="true"/> if this transform is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return X.IsFinite() && Y.IsFinite() && Origin.IsFinite();
}
/// <summary>
/// Returns the transform with the basis orthogonal (90 degrees),
/// and normalized axis vectors (scale of 1 or -1).
/// </summary>
/// <returns>The orthonormalized transform.</returns>
public readonly Transform2D Orthonormalized()
{
Transform2D ortho = this;
Vector2 orthoX = ortho.X;
Vector2 orthoY = ortho.Y;
orthoX.Normalize();
orthoY = orthoY - orthoX * orthoX.Dot(orthoY);
orthoY.Normalize();
ortho.X = orthoX;
ortho.Y = orthoY;
return ortho;
}
/// <summary>
/// Rotates the transform by <paramref name="angle"/> (in radians).
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="angle">The angle to rotate, in radians.</param>
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform2D Rotated(real_t angle)
{
return new Transform2D(angle, new Vector2()) * this;
}
/// <summary>
/// Rotates the transform by <paramref name="angle"/> (in radians).
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="angle">The angle to rotate, in radians.</param>
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform2D RotatedLocal(real_t angle)
{
return this * new Transform2D(angle, new Vector2());
}
/// <summary>
/// Scales the transform by the given scaling factor.
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="scale">The scale to introduce.</param>
/// <returns>The scaled transformation matrix.</returns>
public readonly Transform2D Scaled(Vector2 scale)
{
Transform2D copy = this;
copy.X *= scale;
copy.Y *= scale;
copy.Origin *= scale;
return copy;
}
/// <summary>
/// Scales the transform by the given scaling factor.
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="scale">The scale to introduce.</param>
/// <returns>The scaled transformation matrix.</returns>
public readonly Transform2D ScaledLocal(Vector2 scale)
{
Transform2D copy = this;
copy.X *= scale;
copy.Y *= scale;
return copy;
}
private readonly real_t Tdotx(Vector2 with)
{
return (this[0, 0] * with[0]) + (this[1, 0] * with[1]);
}
private readonly real_t Tdoty(Vector2 with)
{
return (this[0, 1] * with[0]) + (this[1, 1] * with[1]);
}
/// <summary>
/// Translates the transform by the given <paramref name="offset"/>.
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="offset">The offset to translate by.</param>
/// <returns>The translated matrix.</returns>
public readonly Transform2D Translated(Vector2 offset)
{
Transform2D copy = this;
copy.Origin += offset;
return copy;
}
/// <summary>
/// Translates the transform by the given <paramref name="offset"/>.
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="offset">The offset to translate by.</param>
/// <returns>The translated matrix.</returns>
public readonly Transform2D TranslatedLocal(Vector2 offset)
{
Transform2D copy = this;
copy.Origin += copy.BasisXform(offset);
return copy;
}
// Constants
private static readonly Transform2D _identity = new Transform2D(1, 0, 0, 1, 0, 0);
private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0);
private static readonly Transform2D _flipY = new Transform2D(1, 0, 0, -1, 0, 0);
/// <summary>
/// The identity transform, with no translation, rotation, or scaling applied.
/// This is used as a replacement for <c>Transform2D()</c> in GDScript.
/// Do not use <c>new Transform2D()</c> with no arguments in C#, because it sets all values to zero.
/// </summary>
/// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)</c>.</value>
public static Transform2D Identity { get { return _identity; } }
/// <summary>
/// The transform that will flip something along the X axis.
/// </summary>
/// <value>Equivalent to <c>new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)</c>.</value>
public static Transform2D FlipX { get { return _flipX; } }
/// <summary>
/// The transform that will flip something along the Y axis.
/// </summary>
/// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)</c>.</value>
public static Transform2D FlipY { get { return _flipY; } }
/// <summary>
/// Constructs a transformation matrix from 3 vectors (matrix columns).
/// </summary>
/// <param name="xAxis">The X vector, or column index 0.</param>
/// <param name="yAxis">The Y vector, or column index 1.</param>
/// <param name="originPos">The origin vector, or column index 2.</param>
public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos)
{
X = xAxis;
Y = yAxis;
Origin = originPos;
}
/// <summary>
/// Constructs a transformation matrix from the given components.
/// Arguments are named such that xy is equal to calling <c>X.Y</c>.
/// </summary>
/// <param name="xx">The X component of the X column vector, accessed via <c>t.X.X</c> or <c>[0][0]</c>.</param>
/// <param name="xy">The Y component of the X column vector, accessed via <c>t.X.Y</c> or <c>[0][1]</c>.</param>
/// <param name="yx">The X component of the Y column vector, accessed via <c>t.Y.X</c> or <c>[1][0]</c>.</param>
/// <param name="yy">The Y component of the Y column vector, accessed via <c>t.Y.Y</c> or <c>[1][1]</c>.</param>
/// <param name="ox">The X component of the origin vector, accessed via <c>t.Origin.X</c> or <c>[2][0]</c>.</param>
/// <param name="oy">The Y component of the origin vector, accessed via <c>t.Origin.Y</c> or <c>[2][1]</c>.</param>
public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy)
{
X = new Vector2(xx, xy);
Y = new Vector2(yx, yy);
Origin = new Vector2(ox, oy);
}
/// <summary>
/// Constructs a transformation matrix from a <paramref name="rotation"/> value and
/// <paramref name="origin"/> vector.
/// </summary>
/// <param name="rotation">The rotation of the new transform, in radians.</param>
/// <param name="origin">The origin vector, or column index 2.</param>
public Transform2D(real_t rotation, Vector2 origin)
{
(real_t sin, real_t cos) = Mathf.SinCos(rotation);
X.X = Y.Y = cos;
X.Y = Y.X = sin;
Y.X *= -1;
Origin = origin;
}
/// <summary>
/// Constructs a transformation matrix from a <paramref name="rotation"/> value,
/// <paramref name="scale"/> vector, <paramref name="skew"/> value, and
/// <paramref name="origin"/> vector.
/// </summary>
/// <param name="rotation">The rotation of the new transform, in radians.</param>
/// <param name="scale">The scale of the new transform.</param>
/// <param name="skew">The skew of the new transform, in radians.</param>
/// <param name="origin">The origin vector, or column index 2.</param>
public Transform2D(real_t rotation, Vector2 scale, real_t skew, Vector2 origin)
{
(real_t rotationSin, real_t rotationCos) = Mathf.SinCos(rotation);
(real_t rotationSkewSin, real_t rotationSkewCos) = Mathf.SinCos(rotation + skew);
X.X = rotationCos * scale.X;
Y.Y = rotationSkewCos * scale.Y;
Y.X = -rotationSkewSin * scale.Y;
X.Y = rotationSin * scale.X;
Origin = origin;
}
/// <summary>
/// Composes these two transformation matrices by multiplying them
/// together. This has the effect of transforming the second transform
/// (the child) by the first transform (the parent).
/// </summary>
/// <param name="left">The parent transform.</param>
/// <param name="right">The child transform.</param>
/// <returns>The composed transform.</returns>
public static Transform2D operator *(Transform2D left, Transform2D right)
{
left.Origin = left * right.Origin;
real_t x0 = left.Tdotx(right.X);
real_t x1 = left.Tdoty(right.X);
real_t y0 = left.Tdotx(right.Y);
real_t y1 = left.Tdoty(right.Y);
left.X.X = x0;
left.X.Y = x1;
left.Y.X = y0;
left.Y.Y = y1;
return left;
}
/// <summary>
/// Returns a Vector2 transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="vector">A Vector2 to transform.</param>
/// <returns>The transformed Vector2.</returns>
public static Vector2 operator *(Transform2D transform, Vector2 vector)
{
return new Vector2(transform.Tdotx(vector), transform.Tdoty(vector)) + transform.Origin;
}
/// <summary>
/// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="vector">A Vector2 to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed Vector2.</returns>
public static Vector2 operator *(Vector2 vector, Transform2D transform)
{
Vector2 vInv = vector - transform.Origin;
return new Vector2(transform.X.Dot(vInv), transform.Y.Dot(vInv));
}
/// <summary>
/// Returns a Rect2 transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="rect">A Rect2 to transform.</param>
/// <returns>The transformed Rect2.</returns>
public static Rect2 operator *(Transform2D transform, Rect2 rect)
{
Vector2 pos = transform * rect.Position;
Vector2 toX = transform.X * rect.Size.X;
Vector2 toY = transform.Y * rect.Size.Y;
return new Rect2(pos, new Vector2()).Expand(pos + toX).Expand(pos + toY).Expand(pos + toX + toY);
}
/// <summary>
/// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>rect * transform</c> is equivalent to <c>transform.Inverse() * rect</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * rect</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="rect">A Rect2 to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed Rect2.</returns>
public static Rect2 operator *(Rect2 rect, Transform2D transform)
{
Vector2 pos = rect.Position * transform;
Vector2 to1 = new Vector2(rect.Position.X, rect.Position.Y + rect.Size.Y) * transform;
Vector2 to2 = new Vector2(rect.Position.X + rect.Size.X, rect.Position.Y + rect.Size.Y) * transform;
Vector2 to3 = new Vector2(rect.Position.X + rect.Size.X, rect.Position.Y) * transform;
return new Rect2(pos, new Vector2()).Expand(to1).Expand(to2).Expand(to3);
}
/// <summary>
/// Returns a copy of the given Vector2[] transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="array">A Vector2[] to transform.</param>
/// <returns>The transformed copy of the Vector2[].</returns>
public static Vector2[] operator *(Transform2D transform, Vector2[] array)
{
Vector2[] newArray = new Vector2[array.Length];
for (int i = 0; i < array.Length; i++)
{
newArray[i] = transform * array[i];
}
return newArray;
}
/// <summary>
/// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="array">A Vector2[] to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed copy of the Vector2[].</returns>
public static Vector2[] operator *(Vector2[] array, Transform2D transform)
{
Vector2[] newArray = new Vector2[array.Length];
for (int i = 0; i < array.Length; i++)
{
newArray[i] = array[i] * transform;
}
return newArray;
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left transform.</param>
/// <param name="right">The right transform.</param>
/// <returns>Whether or not the transforms are exactly equal.</returns>
public static bool operator ==(Transform2D left, Transform2D right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left transform.</param>
/// <param name="right">The right transform.</param>
/// <returns>Whether or not the transforms are not equal.</returns>
public static bool operator !=(Transform2D left, Transform2D right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the transform is exactly equal
/// to the given object (<paramref name="obj"/>).
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the transform and the object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Transform2D other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="other">The other transform to compare.</param>
/// <returns>Whether or not the matrices are exactly equal.</returns>
public readonly bool Equals(Transform2D other)
{
return X.Equals(other.X) && Y.Equals(other.Y) && Origin.Equals(other.Origin);
}
/// <summary>
/// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal,
/// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
/// </summary>
/// <param name="other">The other transform to compare.</param>
/// <returns>Whether or not the matrices are approximately equal.</returns>
public readonly bool IsEqualApprox(Transform2D other)
{
return X.IsEqualApprox(other.X) && Y.IsEqualApprox(other.Y) && Origin.IsEqualApprox(other.Origin);
}
/// <summary>
/// Serves as the hash function for <see cref="Transform2D"/>.
/// </summary>
/// <returns>A hash code for this transform.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y, Origin);
}
/// <summary>
/// Converts this <see cref="Transform2D"/> to a string.
/// </summary>
/// <returns>A string representation of this transform.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this transform.</returns>
public readonly string ToString(string? format)
{
return $"[X: {X.ToString(format)}, Y: {Y.ToString(format)}, O: {Origin.ToString(format)}]";
}
}
}

View File

@@ -0,0 +1,688 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.ComponentModel;
#nullable enable
namespace Godot
{
/// <summary>
/// 3×4 matrix (3 rows, 4 columns) used for 3D linear transformations.
/// It can represent transformations such as translation, rotation, or scaling.
/// It consists of a <see cref="Godot.Basis"/> (first 3 columns) and a
/// <see cref="Vector3"/> for the origin (last column).
///
/// For more information, read this documentation article:
/// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Transform3D : IEquatable<Transform3D>
{
/// <summary>
/// The <see cref="Godot.Basis"/> of this transform. Contains the X, Y, and Z basis
/// vectors (columns 0 to 2) and is responsible for rotation and scale.
/// </summary>
public Basis Basis;
/// <summary>
/// The origin vector (column 3, the fourth column). Equivalent to array index <c>[3]</c>.
/// </summary>
public Vector3 Origin;
/// <summary>
/// Access whole columns in the form of <see cref="Vector3"/>.
/// The fourth column is the <see cref="Origin"/> vector.
/// </summary>
/// <param name="column">Which column vector.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="column"/> is not 0, 1, 2 or 3.
/// </exception>
public Vector3 this[int column]
{
readonly get
{
switch (column)
{
case 0:
return Basis.Column0;
case 1:
return Basis.Column1;
case 2:
return Basis.Column2;
case 3:
return Origin;
default:
throw new ArgumentOutOfRangeException(nameof(column));
}
}
set
{
switch (column)
{
case 0:
Basis.Column0 = value;
return;
case 1:
Basis.Column1 = value;
return;
case 2:
Basis.Column2 = value;
return;
case 3:
Origin = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(column));
}
}
}
/// <summary>
/// Access matrix elements in column-major order.
/// The fourth column is the <see cref="Origin"/> vector.
/// </summary>
/// <param name="column">Which column, the matrix horizontal position.</param>
/// <param name="row">Which row, the matrix vertical position.</param>
public real_t this[int column, int row]
{
readonly get
{
if (column == 3)
{
return Origin[row];
}
return Basis[column, row];
}
set
{
if (column == 3)
{
Origin[row] = value;
return;
}
Basis[column, row] = value;
}
}
/// <summary>
/// Returns the inverse of the transform, under the assumption that
/// the basis is invertible (must have non-zero determinant).
/// </summary>
/// <seealso cref="Inverse"/>
/// <returns>The inverse transformation matrix.</returns>
public readonly Transform3D AffineInverse()
{
Basis basisInv = Basis.Inverse();
return new Transform3D(basisInv, basisInv * -Origin);
}
/// <summary>
/// Returns a transform interpolated between this transform and another
/// <paramref name="transform"/> by a given <paramref name="weight"/>
/// (on the range of 0.0 to 1.0).
/// </summary>
/// <param name="transform">The other transform.</param>
/// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
/// <returns>The interpolated transform.</returns>
public readonly Transform3D InterpolateWith(Transform3D transform, real_t weight)
{
Vector3 sourceScale = Basis.Scale;
Quaternion sourceRotation = Basis.GetRotationQuaternion();
Vector3 sourceLocation = Origin;
Vector3 destinationScale = transform.Basis.Scale;
Quaternion destinationRotation = transform.Basis.GetRotationQuaternion();
Vector3 destinationLocation = transform.Origin;
var interpolated = new Transform3D();
Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized();
Vector3 scale = sourceScale.Lerp(destinationScale, weight);
interpolated.Basis.SetQuaternionScale(quaternion, scale);
interpolated.Origin = sourceLocation.Lerp(destinationLocation, weight);
return interpolated;
}
/// <summary>
/// Returns the inverse of the transform, under the assumption that
/// the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for
/// non-orthonormal transforms (e.g. with scaling).
/// </summary>
/// <returns>The inverse matrix.</returns>
public readonly Transform3D Inverse()
{
Basis basisTr = Basis.Transposed();
return new Transform3D(basisTr, basisTr * -Origin);
}
/// <summary>
/// Returns <see langword="true"/> if this transform is finite, by calling
/// <see cref="Mathf.IsFinite(real_t)"/> on each component.
/// </summary>
/// <returns>Whether this vector is finite or not.</returns>
public readonly bool IsFinite()
{
return Basis.IsFinite() && Origin.IsFinite();
}
/// <summary>
/// Returns a copy of the transform rotated such that the forward axis (-Z)
/// points towards the <paramref name="target"/> position.
/// The up axis (+Y) points as close to the <paramref name="up"/> vector
/// as possible while staying perpendicular to the forward axis.
/// The resulting transform is orthonormalized.
/// The existing rotation, scale, and skew information from the original transform is discarded.
/// The <paramref name="target"/> and <paramref name="up"/> vectors cannot be zero,
/// cannot be parallel to each other, and are defined in global/parent space.
/// </summary>
/// <param name="target">The object to look at.</param>
/// <param name="up">The relative up direction.</param>
/// <param name="useModelFront">
/// If true, then the model is oriented in reverse,
/// towards the model front axis (+Z, Vector3.ModelFront),
/// which is more useful for orienting 3D models.
/// </param>
/// <returns>The resulting transform.</returns>
public readonly Transform3D LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false)
{
Transform3D t = this;
t.SetLookAt(Origin, target, up ?? Vector3.Up, useModelFront);
return t;
}
/// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly Transform3D LookingAt(Vector3 target, Vector3 up)
{
return LookingAt(target, up, false);
}
/// <summary>
/// Returns the transform with the basis orthogonal (90 degrees),
/// and normalized axis vectors (scale of 1 or -1).
/// </summary>
/// <returns>The orthonormalized transform.</returns>
public readonly Transform3D Orthonormalized()
{
return new Transform3D(Basis.Orthonormalized(), Origin);
}
/// <summary>
/// Rotates the transform around the given <paramref name="axis"/> by <paramref name="angle"/> (in radians).
/// The axis must be a normalized vector.
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="axis">The axis to rotate around. Must be normalized.</param>
/// <param name="angle">The angle to rotate, in radians.</param>
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform3D Rotated(Vector3 axis, real_t angle)
{
return new Transform3D(new Basis(axis, angle), new Vector3()) * this;
}
/// <summary>
/// Rotates the transform around the given <paramref name="axis"/> by <paramref name="angle"/> (in radians).
/// The axis must be a normalized vector.
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="axis">The axis to rotate around. Must be normalized.</param>
/// <param name="angle">The angle to rotate, in radians.</param>
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform3D RotatedLocal(Vector3 axis, real_t angle)
{
Basis tmpBasis = new Basis(axis, angle);
return new Transform3D(Basis * tmpBasis, Origin);
}
/// <summary>
/// Scales the transform by the given 3D <paramref name="scale"/> factor.
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="scale">The scale to introduce.</param>
/// <returns>The scaled transformation matrix.</returns>
public readonly Transform3D Scaled(Vector3 scale)
{
return new Transform3D(Basis.Scaled(scale), Origin * scale);
}
/// <summary>
/// Scales the transform by the given 3D <paramref name="scale"/> factor.
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="scale">The scale to introduce.</param>
/// <returns>The scaled transformation matrix.</returns>
public readonly Transform3D ScaledLocal(Vector3 scale)
{
Basis tmpBasis = Basis.FromScale(scale);
return new Transform3D(Basis * tmpBasis, Origin);
}
private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up, bool useModelFront = false)
{
Basis = Basis.LookingAt(target - eye, up, useModelFront);
Origin = eye;
}
/// <summary>
/// Translates the transform by the given <paramref name="offset"/>.
/// The operation is done in the parent/global frame, equivalent to
/// multiplying the matrix from the left.
/// </summary>
/// <param name="offset">The offset to translate by.</param>
/// <returns>The translated matrix.</returns>
public readonly Transform3D Translated(Vector3 offset)
{
return new Transform3D(Basis, Origin + offset);
}
/// <summary>
/// Translates the transform by the given <paramref name="offset"/>.
/// The operation is done in the local frame, equivalent to
/// multiplying the matrix from the right.
/// </summary>
/// <param name="offset">The offset to translate by.</param>
/// <returns>The translated matrix.</returns>
public readonly Transform3D TranslatedLocal(Vector3 offset)
{
return new Transform3D(Basis, new Vector3
(
Origin[0] + Basis.Row0.Dot(offset),
Origin[1] + Basis.Row1.Dot(offset),
Origin[2] + Basis.Row2.Dot(offset)
));
}
// Constants
private static readonly Transform3D _identity = new Transform3D(Basis.Identity, Vector3.Zero);
private static readonly Transform3D _flipX = new Transform3D(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero);
private static readonly Transform3D _flipY = new Transform3D(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero);
private static readonly Transform3D _flipZ = new Transform3D(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero);
/// <summary>
/// The identity transform, with no translation, rotation, or scaling applied.
/// This is used as a replacement for <c>Transform()</c> in GDScript.
/// Do not use <c>new Transform()</c> with no arguments in C#, because it sets all values to zero.
/// </summary>
/// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value>
public static Transform3D Identity { get { return _identity; } }
/// <summary>
/// The transform that will flip something along the X axis.
/// </summary>
/// <value>Equivalent to <c>new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)</c>.</value>
public static Transform3D FlipX { get { return _flipX; } }
/// <summary>
/// The transform that will flip something along the Y axis.
/// </summary>
/// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)</c>.</value>
public static Transform3D FlipY { get { return _flipY; } }
/// <summary>
/// The transform that will flip something along the Z axis.
/// </summary>
/// <value>Equivalent to <c>new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)</c>.</value>
public static Transform3D FlipZ { get { return _flipZ; } }
/// <summary>
/// Constructs a transformation matrix from 4 vectors (matrix columns).
/// </summary>
/// <param name="column0">The X vector, or column index 0.</param>
/// <param name="column1">The Y vector, or column index 1.</param>
/// <param name="column2">The Z vector, or column index 2.</param>
/// <param name="origin">The origin vector, or column index 3.</param>
public Transform3D(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin)
{
Basis = new Basis(column0, column1, column2);
Origin = origin;
}
/// <summary>
/// Constructs a transformation matrix from the given components.
/// Arguments are named such that xy is equal to calling <c>Basis.X.Y</c>.
/// </summary>
/// <param name="xx">The X component of the X column vector, accessed via <c>t.Basis.X.X</c> or <c>[0][0]</c>.</param>
/// <param name="yx">The X component of the Y column vector, accessed via <c>t.Basis.Y.X</c> or <c>[1][0]</c>.</param>
/// <param name="zx">The X component of the Z column vector, accessed via <c>t.Basis.Z.X</c> or <c>[2][0]</c>.</param>
/// <param name="xy">The Y component of the X column vector, accessed via <c>t.Basis.X.Y</c> or <c>[0][1]</c>.</param>
/// <param name="yy">The Y component of the Y column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[1][1]</c>.</param>
/// <param name="zy">The Y component of the Z column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[2][1]</c>.</param>
/// <param name="xz">The Z component of the X column vector, accessed via <c>t.Basis.X.Y</c> or <c>[0][2]</c>.</param>
/// <param name="yz">The Z component of the Y column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[1][2]</c>.</param>
/// <param name="zz">The Z component of the Z column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[2][2]</c>.</param>
/// <param name="ox">The X component of the origin vector, accessed via <c>t.Origin.X</c> or <c>[2][0]</c>.</param>
/// <param name="oy">The Y component of the origin vector, accessed via <c>t.Origin.Y</c> or <c>[2][1]</c>.</param>
/// <param name="oz">The Z component of the origin vector, accessed via <c>t.Origin.Z</c> or <c>[2][2]</c>.</param>
public Transform3D(real_t xx, real_t yx, real_t zx, real_t xy, real_t yy, real_t zy, real_t xz, real_t yz, real_t zz, real_t ox, real_t oy, real_t oz)
{
Basis = new Basis(xx, yx, zx, xy, yy, zy, xz, yz, zz);
Origin = new Vector3(ox, oy, oz);
}
/// <summary>
/// Constructs a transformation matrix from the given <paramref name="basis"/> and
/// <paramref name="origin"/> vector.
/// </summary>
/// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param>
/// <param name="origin">The origin vector, or column index 3.</param>
public Transform3D(Basis basis, Vector3 origin)
{
Basis = basis;
Origin = origin;
}
/// <summary>
/// Constructs a transformation matrix from the given <paramref name="projection"/>
/// by trimming the last row of the projection matrix (<c>projection.X.W</c>,
/// <c>projection.Y.W</c>, <c>projection.Z.W</c>, and <c>projection.W.W</c>
/// are not copied over).
/// </summary>
/// <param name="projection">The <see cref="Projection"/> to create the transform from.</param>
public Transform3D(Projection projection)
{
Basis = new Basis
(
projection.X.X, projection.Y.X, projection.Z.X,
projection.X.Y, projection.Y.Y, projection.Z.Y,
projection.X.Z, projection.Y.Z, projection.Z.Z
);
Origin = new Vector3
(
projection.W.X,
projection.W.Y,
projection.W.Z
);
}
/// <summary>
/// Composes these two transformation matrices by multiplying them
/// together. This has the effect of transforming the second transform
/// (the child) by the first transform (the parent).
/// </summary>
/// <param name="left">The parent transform.</param>
/// <param name="right">The child transform.</param>
/// <returns>The composed transform.</returns>
public static Transform3D operator *(Transform3D left, Transform3D right)
{
left.Origin = left * right.Origin;
left.Basis *= right.Basis;
return left;
}
/// <summary>
/// Returns a Vector3 transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="vector">A Vector3 to transform.</param>
/// <returns>The transformed Vector3.</returns>
public static Vector3 operator *(Transform3D transform, Vector3 vector)
{
return new Vector3
(
transform.Basis.Row0.Dot(vector) + transform.Origin.X,
transform.Basis.Row1.Dot(vector) + transform.Origin.Y,
transform.Basis.Row2.Dot(vector) + transform.Origin.Z
);
}
/// <summary>
/// Returns a Vector3 transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="vector">A Vector3 to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed Vector3.</returns>
public static Vector3 operator *(Vector3 vector, Transform3D transform)
{
Vector3 vInv = vector - transform.Origin;
return new Vector3
(
(transform.Basis.Row0[0] * vInv.X) + (transform.Basis.Row1[0] * vInv.Y) + (transform.Basis.Row2[0] * vInv.Z),
(transform.Basis.Row0[1] * vInv.X) + (transform.Basis.Row1[1] * vInv.Y) + (transform.Basis.Row2[1] * vInv.Z),
(transform.Basis.Row0[2] * vInv.X) + (transform.Basis.Row1[2] * vInv.Y) + (transform.Basis.Row2[2] * vInv.Z)
);
}
/// <summary>
/// Returns an AABB transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="aabb">An AABB to transform.</param>
/// <returns>The transformed AABB.</returns>
public static Aabb operator *(Transform3D transform, Aabb aabb)
{
Vector3 min = aabb.Position;
Vector3 max = aabb.Position + aabb.Size;
Vector3 tmin = transform.Origin;
Vector3 tmax = transform.Origin;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
real_t e = transform.Basis[j][i] * min[j];
real_t f = transform.Basis[j][i] * max[j];
if (e < f)
{
tmin[i] += e;
tmax[i] += f;
}
else
{
tmin[i] += f;
tmax[i] += e;
}
}
}
return new Aabb(tmin, tmax - tmin);
}
/// <summary>
/// Returns an AABB transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>aabb * transform</c> is equivalent to <c>transform.Inverse() * aabb</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * aabb</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="aabb">An AABB to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed AABB.</returns>
public static Aabb operator *(Aabb aabb, Transform3D transform)
{
Vector3 pos = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z + aabb.Size.Z) * transform;
Vector3 to1 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z) * transform;
Vector3 to2 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y, aabb.Position.Z + aabb.Size.Z) * transform;
Vector3 to3 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y, aabb.Position.Z) * transform;
Vector3 to4 = new Vector3(aabb.Position.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z + aabb.Size.Z) * transform;
Vector3 to5 = new Vector3(aabb.Position.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z) * transform;
Vector3 to6 = new Vector3(aabb.Position.X, aabb.Position.Y, aabb.Position.Z + aabb.Size.Z) * transform;
Vector3 to7 = new Vector3(aabb.Position.X, aabb.Position.Y, aabb.Position.Z) * transform;
return new Aabb(pos, new Vector3()).Expand(to1).Expand(to2).Expand(to3).Expand(to4).Expand(to5).Expand(to6).Expand(to7);
}
/// <summary>
/// Returns a Plane transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="plane">A Plane to transform.</param>
/// <returns>The transformed Plane.</returns>
public static Plane operator *(Transform3D transform, Plane plane)
{
Basis bInvTrans = transform.Basis.Inverse().Transposed();
// Transform a single point on the plane.
Vector3 point = transform * (plane.Normal * plane.D);
// Use inverse transpose for correct normals with non-uniform scaling.
Vector3 normal = (bInvTrans * plane.Normal).Normalized();
real_t d = normal.Dot(point);
return new Plane(normal, d);
}
/// <summary>
/// Returns a Plane transformed (multiplied) by the inverse transformation matrix.
/// <c>plane * transform</c> is equivalent to <c>transform.AffineInverse() * plane</c>. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="plane">A Plane to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed Plane.</returns>
public static Plane operator *(Plane plane, Transform3D transform)
{
Transform3D tInv = transform.AffineInverse();
Basis bTrans = transform.Basis.Transposed();
// Transform a single point on the plane.
Vector3 point = tInv * (plane.Normal * plane.D);
// Note that instead of precalculating the transpose, an alternative
// would be to use the transpose for the basis transform.
// However that would be less SIMD friendly (requiring a swizzle).
// So the cost is one extra precalced value in the calling code.
// This is probably worth it, as this could be used in bottleneck areas. And
// where it is not a bottleneck, the non-fast method is fine.
// Use transpose for correct normals with non-uniform scaling.
Vector3 normal = (bTrans * plane.Normal).Normalized();
real_t d = normal.Dot(point);
return new Plane(normal, d);
}
/// <summary>
/// Returns a copy of the given Vector3[] transformed (multiplied) by the transformation matrix.
/// </summary>
/// <param name="transform">The transformation to apply.</param>
/// <param name="array">A Vector3[] to transform.</param>
/// <returns>The transformed copy of the Vector3[].</returns>
public static Vector3[] operator *(Transform3D transform, Vector3[] array)
{
Vector3[] newArray = new Vector3[array.Length];
for (int i = 0; i < array.Length; i++)
{
newArray[i] = transform * array[i];
}
return newArray;
}
/// <summary>
/// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
/// is fine, scaling/skew is not).
/// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>.
/// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>.
/// </summary>
/// <param name="array">A Vector3[] to inversely transform.</param>
/// <param name="transform">The transformation to apply.</param>
/// <returns>The inversely transformed copy of the Vector3[].</returns>
public static Vector3[] operator *(Vector3[] array, Transform3D transform)
{
Vector3[] newArray = new Vector3[array.Length];
for (int i = 0; i < array.Length; i++)
{
newArray[i] = array[i] * transform;
}
return newArray;
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left transform.</param>
/// <param name="right">The right transform.</param>
/// <returns>Whether or not the transforms are exactly equal.</returns>
public static bool operator ==(Transform3D left, Transform3D right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are not equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="left">The left transform.</param>
/// <param name="right">The right transform.</param>
/// <returns>Whether or not the transforms are not equal.</returns>
public static bool operator !=(Transform3D left, Transform3D right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the transform is exactly equal
/// to the given object (<paramref name="obj"/>).
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the transform and the object are exactly equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Transform3D other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the transforms are exactly equal.
/// Note: Due to floating-point precision errors, consider using
/// <see cref="IsEqualApprox"/> instead, which is more reliable.
/// </summary>
/// <param name="other">The other transform to compare.</param>
/// <returns>Whether or not the matrices are exactly equal.</returns>
public readonly bool Equals(Transform3D other)
{
return Basis.Equals(other.Basis) && Origin.Equals(other.Origin);
}
/// <summary>
/// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal,
/// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component.
/// </summary>
/// <param name="other">The other transform to compare.</param>
/// <returns>Whether or not the matrices are approximately equal.</returns>
public readonly bool IsEqualApprox(Transform3D other)
{
return Basis.IsEqualApprox(other.Basis) && Origin.IsEqualApprox(other.Origin);
}
/// <summary>
/// Serves as the hash function for <see cref="Transform3D"/>.
/// </summary>
/// <returns>A hash code for this transform.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(Basis, Origin);
}
/// <summary>
/// Converts this <see cref="Transform3D"/> to a string.
/// </summary>
/// <returns>A string representation of this transform.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Transform3D"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this transform.</returns>
public readonly string ToString(string? format)
{
return $"[X: {Basis.X.ToString(format)}, Y: {Basis.Y.ToString(format)}, Z: {Basis.Z.ToString(format)}, O: {Origin.ToString(format)}]";
}
}
}

View File

@@ -0,0 +1,953 @@
using System;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
namespace Godot;
#nullable enable
// TODO: Disabled because it is a false positive, see https://github.com/dotnet/roslyn-analyzers/issues/6151
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
public partial struct Variant : IDisposable
#pragma warning restore CA1001
{
internal godot_variant.movable NativeVar;
private object? _obj;
private Disposer? _disposer;
private sealed class Disposer : IDisposable
{
private godot_variant.movable _native;
private WeakReference<IDisposable>? _weakReferenceToSelf;
public Disposer(in godot_variant.movable nativeVar)
{
_native = nativeVar;
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
~Disposer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
_native.DangerousSelfRef.Dispose();
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
}
}
private Variant(in godot_variant nativeVar)
{
NativeVar = (godot_variant.movable)nativeVar;
_obj = null;
switch (nativeVar.Type)
{
case Type.Nil:
case Type.Bool:
case Type.Int:
case Type.Float:
case Type.Vector2:
case Type.Vector2I:
case Type.Rect2:
case Type.Rect2I:
case Type.Vector3:
case Type.Vector3I:
case Type.Vector4:
case Type.Vector4I:
case Type.Plane:
case Type.Quaternion:
case Type.Color:
case Type.Rid:
_disposer = null;
break;
default:
{
_disposer = new Disposer(NativeVar);
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// Explicit name to make it very clear
public static Variant CreateTakingOwnershipOfDisposableValue(in godot_variant nativeValueToOwn) =>
new(nativeValueToOwn);
// Explicit name to make it very clear
public static Variant CreateCopyingBorrowed(in godot_variant nativeValueToOwn) =>
new(NativeFuncs.godotsharp_variant_new_copy(nativeValueToOwn));
/// <summary>
/// Constructs a new <see cref="Godot.NativeInterop.godot_variant"/> from this instance.
/// The caller is responsible of disposing the new instance to avoid memory leaks.
/// </summary>
public godot_variant CopyNativeVariant() =>
NativeFuncs.godotsharp_variant_new_copy((godot_variant)NativeVar);
public void Dispose()
{
_disposer?.Dispose();
NativeVar = default;
_obj = null;
}
// TODO: Consider renaming Variant.Type to VariantType and this property to Type. VariantType would also avoid ambiguity with System.Type.
public Type VariantType => NativeVar.DangerousSelfRef.Type;
public override string ToString() => AsString();
public object? Obj =>
_obj ??= NativeVar.DangerousSelfRef.Type switch
{
Type.Bool => AsBool(),
Type.Int => AsInt64(),
Type.Float => AsDouble(),
Type.String => AsString(),
Type.Vector2 => AsVector2(),
Type.Vector2I => AsVector2I(),
Type.Rect2 => AsRect2(),
Type.Rect2I => AsRect2I(),
Type.Vector3 => AsVector3(),
Type.Vector3I => AsVector3I(),
Type.Transform2D => AsTransform2D(),
Type.Vector4 => AsVector4(),
Type.Vector4I => AsVector4I(),
Type.Plane => AsPlane(),
Type.Quaternion => AsQuaternion(),
Type.Aabb => AsAabb(),
Type.Basis => AsBasis(),
Type.Transform3D => AsTransform3D(),
Type.Projection => AsProjection(),
Type.Color => AsColor(),
Type.StringName => AsStringName(),
Type.NodePath => AsNodePath(),
Type.Rid => AsRid(),
Type.Object => AsGodotObject(),
Type.Callable => AsCallable(),
Type.Signal => AsSignal(),
Type.Dictionary => AsGodotDictionary(),
Type.Array => AsGodotArray(),
Type.PackedByteArray => AsByteArray(),
Type.PackedInt32Array => AsInt32Array(),
Type.PackedInt64Array => AsInt64Array(),
Type.PackedFloat32Array => AsFloat32Array(),
Type.PackedFloat64Array => AsFloat64Array(),
Type.PackedStringArray => AsStringArray(),
Type.PackedVector2Array => AsVector2Array(),
Type.PackedVector3Array => AsVector3Array(),
Type.PackedVector4Array => AsVector4Array(),
Type.PackedColorArray => AsColorArray(),
Type.Nil => null,
Type.Max or _ =>
throw new InvalidOperationException($"Invalid Variant type: {NativeVar.DangerousSelfRef.Type}"),
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant From<[MustBeVariant] T>(in T from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFrom(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T As<[MustBeVariant] T>() =>
VariantUtils.ConvertTo<T>(NativeVar.DangerousSelfRef);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AsBool() =>
VariantUtils.ConvertToBool((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char AsChar() =>
(char)VariantUtils.ConvertToUInt16((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public sbyte AsSByte() =>
VariantUtils.ConvertToInt8((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short AsInt16() =>
VariantUtils.ConvertToInt16((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int AsInt32() =>
VariantUtils.ConvertToInt32((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long AsInt64() =>
VariantUtils.ConvertToInt64((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte AsByte() =>
VariantUtils.ConvertToUInt8((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort AsUInt16() =>
VariantUtils.ConvertToUInt16((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint AsUInt32() =>
VariantUtils.ConvertToUInt32((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong AsUInt64() =>
VariantUtils.ConvertToUInt64((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float AsSingle() =>
VariantUtils.ConvertToFloat32((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double AsDouble() =>
VariantUtils.ConvertToFloat64((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string AsString() =>
VariantUtils.ConvertToString((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 AsVector2() =>
VariantUtils.ConvertToVector2((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2I AsVector2I() =>
VariantUtils.ConvertToVector2I((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rect2 AsRect2() =>
VariantUtils.ConvertToRect2((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rect2I AsRect2I() =>
VariantUtils.ConvertToRect2I((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Transform2D AsTransform2D() =>
VariantUtils.ConvertToTransform2D((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 AsVector3() =>
VariantUtils.ConvertToVector3((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3I AsVector3I() =>
VariantUtils.ConvertToVector3I((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Basis AsBasis() =>
VariantUtils.ConvertToBasis((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion AsQuaternion() =>
VariantUtils.ConvertToQuaternion((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Transform3D AsTransform3D() =>
VariantUtils.ConvertToTransform3D((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 AsVector4() =>
VariantUtils.ConvertToVector4((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4I AsVector4I() =>
VariantUtils.ConvertToVector4I((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Projection AsProjection() =>
VariantUtils.ConvertToProjection((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Aabb AsAabb() =>
VariantUtils.ConvertToAabb((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color AsColor() =>
VariantUtils.ConvertToColor((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Plane AsPlane() =>
VariantUtils.ConvertToPlane((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Callable AsCallable() =>
VariantUtils.ConvertToCallable((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Signal AsSignal() =>
VariantUtils.ConvertToSignal((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte[] AsByteArray() =>
VariantUtils.ConvertAsPackedByteArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int[] AsInt32Array() =>
VariantUtils.ConvertAsPackedInt32ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long[] AsInt64Array() =>
VariantUtils.ConvertAsPackedInt64ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float[] AsFloat32Array() =>
VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double[] AsFloat64Array() =>
VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string[] AsStringArray() =>
VariantUtils.ConvertAsPackedStringArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2[] AsVector2Array() =>
VariantUtils.ConvertAsPackedVector2ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3[] AsVector3Array() =>
VariantUtils.ConvertAsPackedVector3ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4[] AsVector4Array() =>
VariantUtils.ConvertAsPackedVector4ArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color[] AsColorArray() =>
VariantUtils.ConvertAsPackedColorArrayToSystemArray((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] AsGodotObjectArray<T>()
where T : GodotObject =>
VariantUtils.ConvertToSystemArrayOfGodotObject<T>((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Collections.Dictionary<TKey, TValue> AsGodotDictionary<[MustBeVariant] TKey, [MustBeVariant] TValue>() =>
VariantUtils.ConvertToDictionary<TKey, TValue>((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Collections.Array<T> AsGodotArray<[MustBeVariant] T>() =>
VariantUtils.ConvertToArray<T>((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StringName[] AsSystemArrayOfStringName() =>
VariantUtils.ConvertToSystemArrayOfStringName((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NodePath[] AsSystemArrayOfNodePath() =>
VariantUtils.ConvertToSystemArrayOfNodePath((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rid[] AsSystemArrayOfRid() =>
VariantUtils.ConvertToSystemArrayOfRid((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GodotObject AsGodotObject() =>
VariantUtils.ConvertToGodotObject((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StringName AsStringName() =>
VariantUtils.ConvertToStringName((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public NodePath AsNodePath() =>
VariantUtils.ConvertToNodePath((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Rid AsRid() =>
VariantUtils.ConvertToRid((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Collections.Dictionary AsGodotDictionary() =>
VariantUtils.ConvertToDictionary((godot_variant)NativeVar);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Collections.Array AsGodotArray() =>
VariantUtils.ConvertToArray((godot_variant)NativeVar);
// Explicit conversion operators to supported types
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator bool(Variant from) => from.AsBool();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator char(Variant from) => from.AsChar();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator sbyte(Variant from) => from.AsSByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator short(Variant from) => from.AsInt16();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator int(Variant from) => from.AsInt32();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator long(Variant from) => from.AsInt64();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator byte(Variant from) => from.AsByte();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator ushort(Variant from) => from.AsUInt16();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator uint(Variant from) => from.AsUInt32();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator ulong(Variant from) => from.AsUInt64();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator float(Variant from) => from.AsSingle();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator double(Variant from) => from.AsDouble();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator string(Variant from) => from.AsString();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector2(Variant from) => from.AsVector2();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector2I(Variant from) => from.AsVector2I();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rect2(Variant from) => from.AsRect2();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rect2I(Variant from) => from.AsRect2I();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Transform2D(Variant from) => from.AsTransform2D();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector3(Variant from) => from.AsVector3();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector3I(Variant from) => from.AsVector3I();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Basis(Variant from) => from.AsBasis();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Quaternion(Variant from) => from.AsQuaternion();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Transform3D(Variant from) => from.AsTransform3D();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector4(Variant from) => from.AsVector4();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector4I(Variant from) => from.AsVector4I();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Projection(Variant from) => from.AsProjection();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Aabb(Variant from) => from.AsAabb();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Color(Variant from) => from.AsColor();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Plane(Variant from) => from.AsPlane();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Callable(Variant from) => from.AsCallable();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Signal(Variant from) => from.AsSignal();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator byte[](Variant from) => from.AsByteArray();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator int[](Variant from) => from.AsInt32Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator long[](Variant from) => from.AsInt64Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator float[](Variant from) => from.AsFloat32Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator double[](Variant from) => from.AsFloat64Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator string[](Variant from) => from.AsStringArray();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector2[](Variant from) => from.AsVector2Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector3[](Variant from) => from.AsVector3Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Vector4[](Variant from) => from.AsVector4Array();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Color[](Variant from) => from.AsColorArray();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator StringName[](Variant from) => from.AsSystemArrayOfStringName();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator NodePath[](Variant from) => from.AsSystemArrayOfNodePath();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rid[](Variant from) => from.AsSystemArrayOfRid();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator GodotObject(Variant from) => from.AsGodotObject();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator StringName(Variant from) => from.AsStringName();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator NodePath(Variant from) => from.AsNodePath();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rid(Variant from) => from.AsRid();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Collections.Dictionary(Variant from) => from.AsGodotDictionary();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Collections.Array(Variant from) => from.AsGodotArray();
// While we provide implicit conversion operators, normal methods are still needed for
// casts that are not done implicitly (e.g.: raw array to Span, enum to integer, etc).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(bool from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(char from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(sbyte from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(short from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(int from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(long from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(byte from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(ushort from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(uint from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(ulong from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(float from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(double from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(string from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector2 from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector2I from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Rect2 from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Rect2I from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Transform2D from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector3 from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector3I from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Basis from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Quaternion from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Transform3D from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector4 from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Vector4I from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Projection from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Aabb from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Color from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Plane from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Callable from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Signal from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<byte> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<int> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<long> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<float> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<double> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<string> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<Vector2> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<Vector3> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<Vector4> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<Color> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(GodotObject[] from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom<[MustBeVariant] TKey, [MustBeVariant] TValue>(Collections.Dictionary<TKey, TValue> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom<[MustBeVariant] T>(Collections.Array<T> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<StringName> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<NodePath> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Span<Rid> from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(GodotObject from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(StringName from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(NodePath from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Rid from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Collections.Dictionary from) => from;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Variant CreateFrom(Collections.Array from) => from;
// Implicit conversion operators
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(bool from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBool(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(char from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(sbyte from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(short from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(int from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(long from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(byte from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(ushort from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(uint from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(ulong from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(float from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(double from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(string from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromString(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector2 from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector2I from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2I(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Rect2 from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Rect2I from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2I(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Transform2D from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform2D(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector3 from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector3I from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3I(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Basis from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBasis(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Quaternion from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromQuaternion(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Transform3D from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform3D(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector4 from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector4I from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4I(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Projection from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromProjection(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Aabb from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromAabb(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Color from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromColor(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Plane from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPlane(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Callable from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromCallable(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Signal from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSignal(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(byte[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(int[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(long[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(float[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(double[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(string[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector2[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector3[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Vector4[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Color[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(GodotObject[] from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfGodotObject(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(StringName[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(NodePath[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Rid[] from) =>
(Variant)from.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<byte> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedByteArray(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<int> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt32Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<long> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt64Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<float> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat32Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<double> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat64Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<string> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedStringArray(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<Vector2> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector2Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<Vector3> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector3Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<Vector4> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector4Array(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<Color> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedColorArray(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<StringName> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfStringName(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<NodePath> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfNodePath(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<Rid> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfRid(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(GodotObject from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromGodotObject(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(StringName from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromStringName(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(NodePath from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromNodePath(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Rid from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRid(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Collections.Dictionary from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Collections.Array from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from));
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,713 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 2-element structure that can be used to represent 2D grid coordinates or pairs of integers.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector2I : IEquatable<Vector2I>
{
/// <summary>
/// Enumerated index values for the axes.
/// Returned by <see cref="MaxAxisIndex"/> and <see cref="MinAxisIndex"/>.
/// </summary>
public enum Axis
{
/// <summary>
/// The vector's X axis.
/// </summary>
X = 0,
/// <summary>
/// The vector's Y axis.
/// </summary>
Y
}
/// <summary>
/// The vector's X component. Also accessible by using the index position <c>[0]</c>.
/// </summary>
public int X;
/// <summary>
/// The vector's Y component. Also accessible by using the index position <c>[1]</c>.
/// </summary>
public int Y;
/// <summary>
/// Access vector components using their index.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is not 0 or 1.
/// </exception>
/// <value>
/// <c>[0]</c> is equivalent to <see cref="X"/>,
/// <c>[1]</c> is equivalent to <see cref="Y"/>.
/// </value>
public int this[int index]
{
readonly get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0:
X = value;
return;
case 1:
Y = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
/// <summary>
/// Helper method for deconstruction into a tuple.
/// </summary>
public readonly void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
/// <summary>
/// Returns a new vector with all components in absolute values (i.e. positive).
/// </summary>
/// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns>
public readonly Vector2I Abs()
{
return new Vector2I(Mathf.Abs(X), Mathf.Abs(Y));
}
/// <summary>
/// Returns the aspect ratio of this vector, the ratio of <see cref="X"/> to <see cref="Y"/>.
/// </summary>
/// <returns>The <see cref="X"/> component divided by the <see cref="Y"/> component.</returns>
public readonly real_t Aspect()
{
return X / (real_t)Y;
}
/// <summary>
/// Returns a new vector with all components clamped between the
/// components of <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The vector with minimum allowed values.</param>
/// <param name="max">The vector with maximum allowed values.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector2I Clamp(Vector2I min, Vector2I max)
{
return new Vector2I
(
Mathf.Clamp(X, min.X, max.X),
Mathf.Clamp(Y, min.Y, max.Y)
);
}
/// <summary>
/// Returns a new vector with all components clamped between the
/// <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The minimum allowed value.</param>
/// <param name="max">The maximum allowed value.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector2I Clamp(int min, int max)
{
return new Vector2I
(
Mathf.Clamp(X, min, max),
Mathf.Clamp(Y, min, max)
);
}
/// <summary>
/// Returns the squared distance between this vector and <paramref name="to"/>.
/// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
/// you need to compare vectors or need the squared distance for some formula.
/// </summary>
/// <param name="to">The other vector to use.</param>
/// <returns>The squared distance between the two vectors.</returns>
public readonly int DistanceSquaredTo(Vector2I to)
{
return (to - this).LengthSquared();
}
/// <summary>
/// Returns the distance between this vector and <paramref name="to"/>.
/// </summary>
/// <seealso cref="DistanceSquaredTo(Vector2I)"/>
/// <param name="to">The other vector to use.</param>
/// <returns>The distance between the two vectors.</returns>
public readonly real_t DistanceTo(Vector2I to)
{
return (to - this).Length();
}
/// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
/// <returns>The length of this vector.</returns>
public readonly real_t Length()
{
int x2 = X * X;
int y2 = Y * Y;
return Mathf.Sqrt(x2 + y2);
}
/// <summary>
/// Returns the squared length (squared magnitude) of this vector.
/// This method runs faster than <see cref="Length"/>, so prefer it if
/// you need to compare vectors or need the squared length for some formula.
/// </summary>
/// <returns>The squared length of this vector.</returns>
public readonly int LengthSquared()
{
int x2 = X * X;
int y2 = Y * Y;
return x2 + y2;
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector2I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector2I Max(Vector2I with)
{
return new Vector2I
(
Mathf.Max(X, with.X),
Mathf.Max(Y, with.Y)
);
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector2I(Mathf.Max(X, with), Mathf.Max(Y, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector2I Max(int with)
{
return new Vector2I
(
Mathf.Max(X, with),
Mathf.Max(Y, with)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector2I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector2I Min(Vector2I with)
{
return new Vector2I
(
Mathf.Min(X, with.X),
Mathf.Min(Y, with.Y)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector2I(Mathf.Min(X, with), Mathf.Min(Y, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector2I Min(int with)
{
return new Vector2I
(
Mathf.Min(X, with),
Mathf.Min(Y, with)
);
}
/// <summary>
/// Returns the axis of the vector's highest value. See <see cref="Axis"/>.
/// If both components are equal, this method returns <see cref="Axis.X"/>.
/// </summary>
/// <returns>The index of the highest axis.</returns>
public readonly Axis MaxAxisIndex()
{
return X < Y ? Axis.Y : Axis.X;
}
/// <summary>
/// Returns the axis of the vector's lowest value. See <see cref="Axis"/>.
/// If both components are equal, this method returns <see cref="Axis.Y"/>.
/// </summary>
/// <returns>The index of the lowest axis.</returns>
public readonly Axis MinAxisIndex()
{
return X < Y ? Axis.X : Axis.Y;
}
/// <summary>
/// Returns a vector with each component set to one or negative one, depending
/// on the signs of this vector's components, or zero if the component is zero,
/// by calling <see cref="Mathf.Sign(int)"/> on each component.
/// </summary>
/// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns>
public readonly Vector2I Sign()
{
Vector2I v = this;
v.X = Mathf.Sign(v.X);
v.Y = Mathf.Sign(v.Y);
return v;
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>.
/// </summary>
/// <param name="step">A vector value representing the step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector2I Snapped(Vector2I step)
{
return new Vector2I
(
(int)Mathf.Snapped((double)X, (double)step.X),
(int)Mathf.Snapped((double)Y, (double)step.Y)
);
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>.
/// </summary>
/// <param name="step">The step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector2I Snapped(int step)
{
return new Vector2I
(
(int)Mathf.Snapped((double)X, (double)step),
(int)Mathf.Snapped((double)Y, (double)step)
);
}
// Constants
private static readonly Vector2I _minValue = new Vector2I(int.MinValue, int.MinValue);
private static readonly Vector2I _maxValue = new Vector2I(int.MaxValue, int.MaxValue);
private static readonly Vector2I _zero = new Vector2I(0, 0);
private static readonly Vector2I _one = new Vector2I(1, 1);
private static readonly Vector2I _up = new Vector2I(0, -1);
private static readonly Vector2I _down = new Vector2I(0, 1);
private static readonly Vector2I _right = new Vector2I(1, 0);
private static readonly Vector2I _left = new Vector2I(-1, 0);
/// <summary>
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector2.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(int.MinValue, int.MinValue)</c>.</value>
public static Vector2I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector2.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(int.MaxValue, int.MaxValue)</c>.</value>
public static Vector2I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(0, 0)</c>.</value>
public static Vector2I Zero { get { return _zero; } }
/// <summary>
/// One vector, a vector with all components set to <c>1</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(1, 1)</c>.</value>
public static Vector2I One { get { return _one; } }
/// <summary>
/// Up unit vector. Y is down in 2D, so this vector points -Y.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(0, -1)</c>.</value>
public static Vector2I Up { get { return _up; } }
/// <summary>
/// Down unit vector. Y is down in 2D, so this vector points +Y.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(0, 1)</c>.</value>
public static Vector2I Down { get { return _down; } }
/// <summary>
/// Right unit vector. Represents the direction of right.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(1, 0)</c>.</value>
public static Vector2I Right { get { return _right; } }
/// <summary>
/// Left unit vector. Represents the direction of left.
/// </summary>
/// <value>Equivalent to <c>new Vector2I(-1, 0)</c>.</value>
public static Vector2I Left { get { return _left; } }
/// <summary>
/// Constructs a new <see cref="Vector2I"/> with the given components.
/// </summary>
/// <param name="x">The vector's X component.</param>
/// <param name="y">The vector's Y component.</param>
public Vector2I(int x, int y)
{
X = x;
Y = y;
}
/// <summary>
/// Adds each component of the <see cref="Vector2I"/>
/// with the components of the given <see cref="Vector2I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The added vector.</returns>
public static Vector2I operator +(Vector2I left, Vector2I right)
{
left.X += right.X;
left.Y += right.Y;
return left;
}
/// <summary>
/// Subtracts each component of the <see cref="Vector2I"/>
/// by the components of the given <see cref="Vector2I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The subtracted vector.</returns>
public static Vector2I operator -(Vector2I left, Vector2I right)
{
left.X -= right.X;
left.Y -= right.Y;
return left;
}
/// <summary>
/// Returns the negative value of the <see cref="Vector2I"/>.
/// This is the same as writing <c>new Vector2I(-v.X, -v.Y)</c>.
/// This operation flips the direction of the vector while
/// keeping the same magnitude.
/// </summary>
/// <param name="vec">The vector to negate/flip.</param>
/// <returns>The negated/flipped vector.</returns>
public static Vector2I operator -(Vector2I vec)
{
vec.X = -vec.X;
vec.Y = -vec.Y;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector2I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The vector to multiply.</param>
/// <param name="scale">The scale to multiply by.</param>
/// <returns>The multiplied vector.</returns>
public static Vector2I operator *(Vector2I vec, int scale)
{
vec.X *= scale;
vec.Y *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector2I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="scale">The scale to multiply by.</param>
/// <param name="vec">The vector to multiply.</param>
/// <returns>The multiplied vector.</returns>
public static Vector2I operator *(int scale, Vector2I vec)
{
vec.X *= scale;
vec.Y *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector2I"/>
/// by the components of the given <see cref="Vector2I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The multiplied vector.</returns>
public static Vector2I operator *(Vector2I left, Vector2I right)
{
left.X *= right.X;
left.Y *= right.Y;
return left;
}
/// <summary>
/// Divides each component of the <see cref="Vector2I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The divided vector.</returns>
public static Vector2I operator /(Vector2I vec, int divisor)
{
vec.X /= divisor;
vec.Y /= divisor;
return vec;
}
/// <summary>
/// Divides each component of the <see cref="Vector2I"/>
/// by the components of the given <see cref="Vector2I"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The divided vector.</returns>
public static Vector2I operator /(Vector2I vec, Vector2I divisorv)
{
vec.X /= divisorv.X;
vec.Y /= divisorv.Y;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector2I"/>
/// with the components of the given <see langword="int"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector2I(10, -20) % 7); // Prints "(3, -6)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The remainder vector.</returns>
public static Vector2I operator %(Vector2I vec, int divisor)
{
vec.X %= divisor;
vec.Y %= divisor;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector2I"/>
/// with the components of the given <see cref="Vector2I"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector2I(10, -20) % new Vector2I(7, 8)); // Prints "(3, -4)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The remainder vector.</returns>
public static Vector2I operator %(Vector2I vec, Vector2I divisorv)
{
vec.X %= divisorv.X;
vec.Y %= divisorv.Y;
return vec;
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public static bool operator ==(Vector2I left, Vector2I right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are not equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are not equal.</returns>
public static bool operator !=(Vector2I left, Vector2I right)
{
return !left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Vector2I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than the right.</returns>
public static bool operator <(Vector2I left, Vector2I right)
{
if (left.X == right.X)
{
return left.Y < right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector2I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than the right.</returns>
public static bool operator >(Vector2I left, Vector2I right)
{
if (left.X == right.X)
{
return left.Y > right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Compares two <see cref="Vector2I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than or equal to the right.</returns>
public static bool operator <=(Vector2I left, Vector2I right)
{
if (left.X == right.X)
{
return left.Y <= right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector2I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than or equal to the right.</returns>
public static bool operator >=(Vector2I left, Vector2I right)
{
if (left.X == right.X)
{
return left.Y >= right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Converts this <see cref="Vector2I"/> to a <see cref="Vector2"/>.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static implicit operator Vector2(Vector2I value)
{
return new Vector2(value.X, value.Y);
}
/// <summary>
/// Converts a <see cref="Vector2"/> to a <see cref="Vector2I"/> by truncating
/// components' fractional parts (rounding towards zero). For a different
/// behavior consider passing the result of <see cref="Vector2.Ceil"/>,
/// <see cref="Vector2.Floor"/> or <see cref="Vector2.Round"/> to this conversion operator instead.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static explicit operator Vector2I(Vector2 value)
{
return new Vector2I((int)value.X, (int)value.Y);
}
/// <summary>
/// Returns <see langword="true"/> if the vector is equal
/// to the given object (<paramref name="obj"/>).
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector2I other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="other">The other vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public readonly bool Equals(Vector2I other)
{
return X == other.X && Y == other.Y;
}
/// <summary>
/// Serves as the hash function for <see cref="Vector2I"/>.
/// </summary>
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y);
}
/// <summary>
/// Converts this <see cref="Vector2I"/> to a string.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Vector2I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public readonly string ToString(string? format)
{
return $"({X.ToString(format, CultureInfo.InvariantCulture)}, {Y.ToString(format, CultureInfo.InvariantCulture)})";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,775 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 3-element structure that can be used to represent 3D grid coordinates or sets of integers.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector3I : IEquatable<Vector3I>
{
/// <summary>
/// Enumerated index values for the axes.
/// Returned by <see cref="MaxAxisIndex"/> and <see cref="MinAxisIndex"/>.
/// </summary>
public enum Axis
{
/// <summary>
/// The vector's X axis.
/// </summary>
X = 0,
/// <summary>
/// The vector's Y axis.
/// </summary>
Y,
/// <summary>
/// The vector's Z axis.
/// </summary>
Z
}
/// <summary>
/// The vector's X component. Also accessible by using the index position <c>[0]</c>.
/// </summary>
public int X;
/// <summary>
/// The vector's Y component. Also accessible by using the index position <c>[1]</c>.
/// </summary>
public int Y;
/// <summary>
/// The vector's Z component. Also accessible by using the index position <c>[2]</c>.
/// </summary>
public int Z;
/// <summary>
/// Access vector components using their <paramref name="index"/>.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is not 0, 1 or 2.
/// </exception>
/// <value>
/// <c>[0]</c> is equivalent to <see cref="X"/>,
/// <c>[1]</c> is equivalent to <see cref="Y"/>,
/// <c>[2]</c> is equivalent to <see cref="Z"/>.
/// </value>
public int this[int index]
{
readonly get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
case 2:
return Z;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0:
X = value;
return;
case 1:
Y = value;
return;
case 2:
Z = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
/// <summary>
/// Helper method for deconstruction into a tuple.
/// </summary>
public readonly void Deconstruct(out int x, out int y, out int z)
{
x = X;
y = Y;
z = Z;
}
/// <summary>
/// Returns a new vector with all components in absolute values (i.e. positive).
/// </summary>
/// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns>
public readonly Vector3I Abs()
{
return new Vector3I(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z));
}
/// <summary>
/// Returns a new vector with all components clamped between the
/// components of <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The vector with minimum allowed values.</param>
/// <param name="max">The vector with maximum allowed values.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector3I Clamp(Vector3I min, Vector3I max)
{
return new Vector3I
(
Mathf.Clamp(X, min.X, max.X),
Mathf.Clamp(Y, min.Y, max.Y),
Mathf.Clamp(Z, min.Z, max.Z)
);
}
/// <summary>
/// Returns a new vector with all components clamped between the
/// <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The minimum allowed value.</param>
/// <param name="max">The maximum allowed value.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector3I Clamp(int min, int max)
{
return new Vector3I
(
Mathf.Clamp(X, min, max),
Mathf.Clamp(Y, min, max),
Mathf.Clamp(Z, min, max)
);
}
/// <summary>
/// Returns the squared distance between this vector and <paramref name="to"/>.
/// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
/// you need to compare vectors or need the squared distance for some formula.
/// </summary>
/// <param name="to">The other vector to use.</param>
/// <returns>The squared distance between the two vectors.</returns>
public readonly int DistanceSquaredTo(Vector3I to)
{
return (to - this).LengthSquared();
}
/// <summary>
/// Returns the distance between this vector and <paramref name="to"/>.
/// </summary>
/// <seealso cref="DistanceSquaredTo(Vector3I)"/>
/// <param name="to">The other vector to use.</param>
/// <returns>The distance between the two vectors.</returns>
public readonly real_t DistanceTo(Vector3I to)
{
return (to - this).Length();
}
/// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
/// <returns>The length of this vector.</returns>
public readonly real_t Length()
{
int x2 = X * X;
int y2 = Y * Y;
int z2 = Z * Z;
return Mathf.Sqrt(x2 + y2 + z2);
}
/// <summary>
/// Returns the squared length (squared magnitude) of this vector.
/// This method runs faster than <see cref="Length"/>, so prefer it if
/// you need to compare vectors or need the squared length for some formula.
/// </summary>
/// <returns>The squared length of this vector.</returns>
public readonly int LengthSquared()
{
int x2 = X * X;
int y2 = Y * Y;
int z2 = Z * Z;
return x2 + y2 + z2;
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector3I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector3I Max(Vector3I with)
{
return new Vector3I
(
Mathf.Max(X, with.X),
Mathf.Max(Y, with.Y),
Mathf.Max(Z, with.Z)
);
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector3I(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector3I Max(int with)
{
return new Vector3I
(
Mathf.Max(X, with),
Mathf.Max(Y, with),
Mathf.Max(Z, with)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector3I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector3I Min(Vector3I with)
{
return new Vector3I
(
Mathf.Min(X, with.X),
Mathf.Min(Y, with.Y),
Mathf.Min(Z, with.Z)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector3I(Mathf.Min(X, with), Mathf.Min(Y, with), Mathf.Min(Z, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector3I Min(int with)
{
return new Vector3I
(
Mathf.Min(X, with),
Mathf.Min(Y, with),
Mathf.Min(Z, with)
);
}
/// <summary>
/// Returns the axis of the vector's highest value. See <see cref="Axis"/>.
/// If all components are equal, this method returns <see cref="Axis.X"/>.
/// </summary>
/// <returns>The index of the highest axis.</returns>
public readonly Axis MaxAxisIndex()
{
return X < Y ? (Y < Z ? Axis.Z : Axis.Y) : (X < Z ? Axis.Z : Axis.X);
}
/// <summary>
/// Returns the axis of the vector's lowest value. See <see cref="Axis"/>.
/// If all components are equal, this method returns <see cref="Axis.Z"/>.
/// </summary>
/// <returns>The index of the lowest axis.</returns>
public readonly Axis MinAxisIndex()
{
return X < Y ? (X < Z ? Axis.X : Axis.Z) : (Y < Z ? Axis.Y : Axis.Z);
}
/// <summary>
/// Returns a vector with each component set to one or negative one, depending
/// on the signs of this vector's components, or zero if the component is zero,
/// by calling <see cref="Mathf.Sign(int)"/> on each component.
/// </summary>
/// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns>
public readonly Vector3I Sign()
{
Vector3I v = this;
v.X = Mathf.Sign(v.X);
v.Y = Mathf.Sign(v.Y);
v.Z = Mathf.Sign(v.Z);
return v;
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>.
/// </summary>
/// <param name="step">A vector value representing the step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector3I Snapped(Vector3I step)
{
return new Vector3I
(
(int)Mathf.Snapped((double)X, (double)step.X),
(int)Mathf.Snapped((double)Y, (double)step.Y),
(int)Mathf.Snapped((double)Z, (double)step.Z)
);
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>.
/// </summary>
/// <param name="step">The step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector3I Snapped(int step)
{
return new Vector3I
(
(int)Mathf.Snapped((double)X, (double)step),
(int)Mathf.Snapped((double)Y, (double)step),
(int)Mathf.Snapped((double)Z, (double)step)
);
}
// Constants
private static readonly Vector3I _minValue = new Vector3I(int.MinValue, int.MinValue, int.MinValue);
private static readonly Vector3I _maxValue = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue);
private static readonly Vector3I _zero = new Vector3I(0, 0, 0);
private static readonly Vector3I _one = new Vector3I(1, 1, 1);
private static readonly Vector3I _up = new Vector3I(0, 1, 0);
private static readonly Vector3I _down = new Vector3I(0, -1, 0);
private static readonly Vector3I _right = new Vector3I(1, 0, 0);
private static readonly Vector3I _left = new Vector3I(-1, 0, 0);
private static readonly Vector3I _forward = new Vector3I(0, 0, -1);
private static readonly Vector3I _back = new Vector3I(0, 0, 1);
/// <summary>
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector3.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(int.MinValue, int.MinValue, int.MinValue)</c>.</value>
public static Vector3I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector3.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value>
public static Vector3I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, 0, 0)</c>.</value>
public static Vector3I Zero { get { return _zero; } }
/// <summary>
/// One vector, a vector with all components set to <c>1</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(1, 1, 1)</c>.</value>
public static Vector3I One { get { return _one; } }
/// <summary>
/// Up unit vector.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, 1, 0)</c>.</value>
public static Vector3I Up { get { return _up; } }
/// <summary>
/// Down unit vector.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, -1, 0)</c>.</value>
public static Vector3I Down { get { return _down; } }
/// <summary>
/// Right unit vector. Represents the local direction of right,
/// and the global direction of east.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(1, 0, 0)</c>.</value>
public static Vector3I Right { get { return _right; } }
/// <summary>
/// Left unit vector. Represents the local direction of left,
/// and the global direction of west.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(-1, 0, 0)</c>.</value>
public static Vector3I Left { get { return _left; } }
/// <summary>
/// Forward unit vector. Represents the local direction of forward,
/// and the global direction of north.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, 0, -1)</c>.</value>
public static Vector3I Forward { get { return _forward; } }
/// <summary>
/// Back unit vector. Represents the local direction of back,
/// and the global direction of south.
/// </summary>
/// <value>Equivalent to <c>new Vector3I(0, 0, 1)</c>.</value>
public static Vector3I Back { get { return _back; } }
/// <summary>
/// Constructs a new <see cref="Vector3I"/> with the given components.
/// </summary>
/// <param name="x">The vector's X component.</param>
/// <param name="y">The vector's Y component.</param>
/// <param name="z">The vector's Z component.</param>
public Vector3I(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
/// <summary>
/// Adds each component of the <see cref="Vector3I"/>
/// with the components of the given <see cref="Vector3I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The added vector.</returns>
public static Vector3I operator +(Vector3I left, Vector3I right)
{
left.X += right.X;
left.Y += right.Y;
left.Z += right.Z;
return left;
}
/// <summary>
/// Subtracts each component of the <see cref="Vector3I"/>
/// by the components of the given <see cref="Vector3I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The subtracted vector.</returns>
public static Vector3I operator -(Vector3I left, Vector3I right)
{
left.X -= right.X;
left.Y -= right.Y;
left.Z -= right.Z;
return left;
}
/// <summary>
/// Returns the negative value of the <see cref="Vector3I"/>.
/// This is the same as writing <c>new Vector3I(-v.X, -v.Y, -v.Z)</c>.
/// This operation flips the direction of the vector while
/// keeping the same magnitude.
/// </summary>
/// <param name="vec">The vector to negate/flip.</param>
/// <returns>The negated/flipped vector.</returns>
public static Vector3I operator -(Vector3I vec)
{
vec.X = -vec.X;
vec.Y = -vec.Y;
vec.Z = -vec.Z;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector3I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The vector to multiply.</param>
/// <param name="scale">The scale to multiply by.</param>
/// <returns>The multiplied vector.</returns>
public static Vector3I operator *(Vector3I vec, int scale)
{
vec.X *= scale;
vec.Y *= scale;
vec.Z *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector3I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="scale">The scale to multiply by.</param>
/// <param name="vec">The vector to multiply.</param>
/// <returns>The multiplied vector.</returns>
public static Vector3I operator *(int scale, Vector3I vec)
{
vec.X *= scale;
vec.Y *= scale;
vec.Z *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector3I"/>
/// by the components of the given <see cref="Vector3I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The multiplied vector.</returns>
public static Vector3I operator *(Vector3I left, Vector3I right)
{
left.X *= right.X;
left.Y *= right.Y;
left.Z *= right.Z;
return left;
}
/// <summary>
/// Divides each component of the <see cref="Vector3I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The divided vector.</returns>
public static Vector3I operator /(Vector3I vec, int divisor)
{
vec.X /= divisor;
vec.Y /= divisor;
vec.Z /= divisor;
return vec;
}
/// <summary>
/// Divides each component of the <see cref="Vector3I"/>
/// by the components of the given <see cref="Vector3I"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The divided vector.</returns>
public static Vector3I operator /(Vector3I vec, Vector3I divisorv)
{
vec.X /= divisorv.X;
vec.Y /= divisorv.Y;
vec.Z /= divisorv.Z;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector3I"/>
/// with the components of the given <see langword="int"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector3I(10, -20, 30) % 7); // Prints "(3, -6, 2)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The remainder vector.</returns>
public static Vector3I operator %(Vector3I vec, int divisor)
{
vec.X %= divisor;
vec.Y %= divisor;
vec.Z %= divisor;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector3I"/>
/// with the components of the given <see cref="Vector3I"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector3I(10, -20, 30) % new Vector3I(7, 8, 9)); // Prints "(3, -4, 3)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The remainder vector.</returns>
public static Vector3I operator %(Vector3I vec, Vector3I divisorv)
{
vec.X %= divisorv.X;
vec.Y %= divisorv.Y;
vec.Z %= divisorv.Z;
return vec;
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public static bool operator ==(Vector3I left, Vector3I right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are not equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are not equal.</returns>
public static bool operator !=(Vector3I left, Vector3I right)
{
return !left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Vector3I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors, and then with the Z values.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than the right.</returns>
public static bool operator <(Vector3I left, Vector3I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
return left.Z < right.Z;
}
return left.Y < right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector3I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors, and then with the Z values.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than the right.</returns>
public static bool operator >(Vector3I left, Vector3I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
return left.Z > right.Z;
}
return left.Y > right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Compares two <see cref="Vector3I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors, and then with the Z values.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than or equal to the right.</returns>
public static bool operator <=(Vector3I left, Vector3I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
return left.Z <= right.Z;
}
return left.Y < right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector3I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y values of the two vectors, and then with the Z values.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than or equal to the right.</returns>
public static bool operator >=(Vector3I left, Vector3I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
return left.Z >= right.Z;
}
return left.Y > right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Converts this <see cref="Vector3I"/> to a <see cref="Vector3"/>.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static implicit operator Vector3(Vector3I value)
{
return new Vector3(value.X, value.Y, value.Z);
}
/// <summary>
/// Converts a <see cref="Vector3"/> to a <see cref="Vector3I"/> by truncating
/// components' fractional parts (rounding towards zero). For a different
/// behavior consider passing the result of <see cref="Vector3.Ceil"/>,
/// <see cref="Vector3.Floor"/> or <see cref="Vector3.Round"/> to this conversion operator instead.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static explicit operator Vector3I(Vector3 value)
{
return new Vector3I((int)value.X, (int)value.Y, (int)value.Z);
}
/// <summary>
/// Returns <see langword="true"/> if the vector is equal
/// to the given object (<paramref name="obj"/>).
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector3I other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="other">The other vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public readonly bool Equals(Vector3I other)
{
return X == other.X && Y == other.Y && Z == other.Z;
}
/// <summary>
/// Serves as the hash function for <see cref="Vector3I"/>.
/// </summary>
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y, Z);
}
/// <summary>
/// Converts this <see cref="Vector3I"/> to a string.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Vector3I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public readonly string ToString(string? format)
{
return $"({X.ToString(format, CultureInfo.InvariantCulture)}, {Y.ToString(format, CultureInfo.InvariantCulture)}, {Z.ToString(format, CultureInfo.InvariantCulture)})";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,801 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
#nullable enable
namespace Godot
{
/// <summary>
/// 4-element structure that can be used to represent 4D grid coordinates or sets of integers.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Vector4I : IEquatable<Vector4I>
{
/// <summary>
/// Enumerated index values for the axes.
/// Returned by <see cref="MaxAxisIndex"/> and <see cref="MinAxisIndex"/>.
/// </summary>
public enum Axis
{
/// <summary>
/// The vector's X axis.
/// </summary>
X = 0,
/// <summary>
/// The vector's Y axis.
/// </summary>
Y,
/// <summary>
/// The vector's Z axis.
/// </summary>
Z,
/// <summary>
/// The vector's W axis.
/// </summary>
W
}
/// <summary>
/// The vector's X component. Also accessible by using the index position <c>[0]</c>.
/// </summary>
public int X;
/// <summary>
/// The vector's Y component. Also accessible by using the index position <c>[1]</c>.
/// </summary>
public int Y;
/// <summary>
/// The vector's Z component. Also accessible by using the index position <c>[2]</c>.
/// </summary>
public int Z;
/// <summary>
/// The vector's W component. Also accessible by using the index position <c>[3]</c>.
/// </summary>
public int W;
/// <summary>
/// Access vector components using their <paramref name="index"/>.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is not 0, 1, 2 or 3.
/// </exception>
/// <value>
/// <c>[0]</c> is equivalent to <see cref="X"/>,
/// <c>[1]</c> is equivalent to <see cref="Y"/>,
/// <c>[2]</c> is equivalent to <see cref="Z"/>.
/// <c>[3]</c> is equivalent to <see cref="W"/>.
/// </value>
public int this[int index]
{
readonly get
{
switch (index)
{
case 0:
return X;
case 1:
return Y;
case 2:
return Z;
case 3:
return W;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0:
X = value;
return;
case 1:
Y = value;
return;
case 2:
Z = value;
return;
case 3:
W = value;
return;
default:
throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
/// <summary>
/// Helper method for deconstruction into a tuple.
/// </summary>
public readonly void Deconstruct(out int x, out int y, out int z, out int w)
{
x = X;
y = Y;
z = Z;
w = W;
}
/// <summary>
/// Returns a new vector with all components in absolute values (i.e. positive).
/// </summary>
/// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns>
public readonly Vector4I Abs()
{
return new Vector4I(Mathf.Abs(X), Mathf.Abs(Y), Mathf.Abs(Z), Mathf.Abs(W));
}
/// <summary>
/// Returns a new vector with all components clamped between the
/// components of <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The vector with minimum allowed values.</param>
/// <param name="max">The vector with maximum allowed values.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector4I Clamp(Vector4I min, Vector4I max)
{
return new Vector4I
(
Mathf.Clamp(X, min.X, max.X),
Mathf.Clamp(Y, min.Y, max.Y),
Mathf.Clamp(Z, min.Z, max.Z),
Mathf.Clamp(W, min.W, max.W)
);
}
/// <summary>
/// Returns a new vector with all components clamped between
/// <paramref name="min"/> and <paramref name="max"/> using
/// <see cref="Mathf.Clamp(int, int, int)"/>.
/// </summary>
/// <param name="min">The minimum allowed value.</param>
/// <param name="max">The maximum allowed value.</param>
/// <returns>The vector with all components clamped.</returns>
public readonly Vector4I Clamp(int min, int max)
{
return new Vector4I
(
Mathf.Clamp(X, min, max),
Mathf.Clamp(Y, min, max),
Mathf.Clamp(Z, min, max),
Mathf.Clamp(W, min, max)
);
}
/// <summary>
/// Returns the squared distance between this vector and <paramref name="to"/>.
/// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
/// you need to compare vectors or need the squared distance for some formula.
/// </summary>
/// <param name="to">The other vector to use.</param>
/// <returns>The squared distance between the two vectors.</returns>
public readonly int DistanceSquaredTo(Vector4I to)
{
return (to - this).LengthSquared();
}
/// <summary>
/// Returns the distance between this vector and <paramref name="to"/>.
/// </summary>
/// <seealso cref="DistanceSquaredTo(Vector4I)"/>
/// <param name="to">The other vector to use.</param>
/// <returns>The distance between the two vectors.</returns>
public readonly real_t DistanceTo(Vector4I to)
{
return (to - this).Length();
}
/// <summary>
/// Returns the length (magnitude) of this vector.
/// </summary>
/// <seealso cref="LengthSquared"/>
/// <returns>The length of this vector.</returns>
public readonly real_t Length()
{
int x2 = X * X;
int y2 = Y * Y;
int z2 = Z * Z;
int w2 = W * W;
return Mathf.Sqrt(x2 + y2 + z2 + w2);
}
/// <summary>
/// Returns the squared length (squared magnitude) of this vector.
/// This method runs faster than <see cref="Length"/>, so prefer it if
/// you need to compare vectors or need the squared length for some formula.
/// </summary>
/// <returns>The squared length of this vector.</returns>
public readonly int LengthSquared()
{
int x2 = X * X;
int y2 = Y * Y;
int z2 = Z * Z;
int w2 = W * W;
return x2 + y2 + z2 + w2;
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector4I(Mathf.Max(X, with.X), Mathf.Max(Y, with.Y), Mathf.Max(Z, with.Z), Mathf.Max(W, with.W))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector4I Max(Vector4I with)
{
return new Vector4I
(
Mathf.Max(X, with.X),
Mathf.Max(Y, with.Y),
Mathf.Max(Z, with.Z),
Mathf.Max(W, with.W)
);
}
/// <summary>
/// Returns the result of the component-wise maximum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector4I(Mathf.Max(X, with), Mathf.Max(Y, with), Mathf.Max(Z, with), Mathf.Max(W, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting maximum vector.</returns>
public readonly Vector4I Max(int with)
{
return new Vector4I
(
Mathf.Max(X, with),
Mathf.Max(Y, with),
Mathf.Max(Z, with),
Mathf.Max(W, with)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector4I(Mathf.Min(X, with.X), Mathf.Min(Y, with.Y), Mathf.Min(Z, with.Z), Mathf.Min(W, with.W))</c>.
/// </summary>
/// <param name="with">The other vector to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector4I Min(Vector4I with)
{
return new Vector4I
(
Mathf.Min(X, with.X),
Mathf.Min(Y, with.Y),
Mathf.Min(Z, with.Z),
Mathf.Min(W, with.W)
);
}
/// <summary>
/// Returns the result of the component-wise minimum between
/// this vector and <paramref name="with"/>.
/// Equivalent to <c>new Vector4I(Mathf.Min(X, with), Mathf.Min(Y, with), Mathf.Min(Z, with), Mathf.Min(W, with))</c>.
/// </summary>
/// <param name="with">The other value to use.</param>
/// <returns>The resulting minimum vector.</returns>
public readonly Vector4I Min(int with)
{
return new Vector4I
(
Mathf.Min(X, with),
Mathf.Min(Y, with),
Mathf.Min(Z, with),
Mathf.Min(W, with)
);
}
/// <summary>
/// Returns the axis of the vector's highest value. See <see cref="Axis"/>.
/// If all components are equal, this method returns <see cref="Axis.X"/>.
/// </summary>
/// <returns>The index of the highest axis.</returns>
public readonly Axis MaxAxisIndex()
{
int max_index = 0;
int max_value = X;
for (int i = 1; i < 4; i++)
{
if (this[i] > max_value)
{
max_index = i;
max_value = this[i];
}
}
return (Axis)max_index;
}
/// <summary>
/// Returns the axis of the vector's lowest value. See <see cref="Axis"/>.
/// If all components are equal, this method returns <see cref="Axis.W"/>.
/// </summary>
/// <returns>The index of the lowest axis.</returns>
public readonly Axis MinAxisIndex()
{
int min_index = 0;
int min_value = X;
for (int i = 1; i < 4; i++)
{
if (this[i] <= min_value)
{
min_index = i;
min_value = this[i];
}
}
return (Axis)min_index;
}
/// <summary>
/// Returns a vector with each component set to one or negative one, depending
/// on the signs of this vector's components, or zero if the component is zero,
/// by calling <see cref="Mathf.Sign(int)"/> on each component.
/// </summary>
/// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns>
public readonly Vector4I Sign()
{
return new Vector4I(Mathf.Sign(X), Mathf.Sign(Y), Mathf.Sign(Z), Mathf.Sign(W));
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of the corresponding component in <paramref name="step"/>.
/// </summary>
/// <param name="step">A vector value representing the step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector4I Snapped(Vector4I step)
{
return new Vector4I(
(int)Mathf.Snapped((double)X, (double)step.X),
(int)Mathf.Snapped((double)Y, (double)step.Y),
(int)Mathf.Snapped((double)Z, (double)step.Z),
(int)Mathf.Snapped((double)W, (double)step.W)
);
}
/// <summary>
/// Returns a new vector with each component snapped to the closest multiple of <paramref name="step"/>.
/// </summary>
/// <param name="step">The step size to snap to.</param>
/// <returns>The snapped vector.</returns>
public readonly Vector4I Snapped(int step)
{
return new Vector4I(
(int)Mathf.Snapped((double)X, (double)step),
(int)Mathf.Snapped((double)Y, (double)step),
(int)Mathf.Snapped((double)Z, (double)step),
(int)Mathf.Snapped((double)W, (double)step)
);
}
// Constants
private static readonly Vector4I _minValue = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue);
private static readonly Vector4I _maxValue = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue);
private static readonly Vector4I _zero = new Vector4I(0, 0, 0, 0);
private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1);
/// <summary>
/// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector4.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue)</c>.</value>
public static Vector4I MinValue { get { return _minValue; } }
/// <summary>
/// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector4.Inf"/>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value>
public static Vector4I MaxValue { get { return _maxValue; } }
/// <summary>
/// Zero vector, a vector with all components set to <c>0</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(0, 0, 0, 0)</c>.</value>
public static Vector4I Zero { get { return _zero; } }
/// <summary>
/// One vector, a vector with all components set to <c>1</c>.
/// </summary>
/// <value>Equivalent to <c>new Vector4I(1, 1, 1, 1)</c>.</value>
public static Vector4I One { get { return _one; } }
/// <summary>
/// Constructs a new <see cref="Vector4I"/> with the given components.
/// </summary>
/// <param name="x">The vector's X component.</param>
/// <param name="y">The vector's Y component.</param>
/// <param name="z">The vector's Z component.</param>
/// <param name="w">The vector's W component.</param>
public Vector4I(int x, int y, int z, int w)
{
X = x;
Y = y;
Z = z;
W = w;
}
/// <summary>
/// Adds each component of the <see cref="Vector4I"/>
/// with the components of the given <see cref="Vector4I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The added vector.</returns>
public static Vector4I operator +(Vector4I left, Vector4I right)
{
left.X += right.X;
left.Y += right.Y;
left.Z += right.Z;
left.W += right.W;
return left;
}
/// <summary>
/// Subtracts each component of the <see cref="Vector4I"/>
/// by the components of the given <see cref="Vector4I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The subtracted vector.</returns>
public static Vector4I operator -(Vector4I left, Vector4I right)
{
left.X -= right.X;
left.Y -= right.Y;
left.Z -= right.Z;
left.W -= right.W;
return left;
}
/// <summary>
/// Returns the negative value of the <see cref="Vector4I"/>.
/// This is the same as writing <c>new Vector4I(-v.X, -v.Y, -v.Z, -v.W)</c>.
/// This operation flips the direction of the vector while
/// keeping the same magnitude.
/// </summary>
/// <param name="vec">The vector to negate/flip.</param>
/// <returns>The negated/flipped vector.</returns>
public static Vector4I operator -(Vector4I vec)
{
vec.X = -vec.X;
vec.Y = -vec.Y;
vec.Z = -vec.Z;
vec.W = -vec.W;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector4I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The vector to multiply.</param>
/// <param name="scale">The scale to multiply by.</param>
/// <returns>The multiplied vector.</returns>
public static Vector4I operator *(Vector4I vec, int scale)
{
vec.X *= scale;
vec.Y *= scale;
vec.Z *= scale;
vec.W *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector4I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="scale">The scale to multiply by.</param>
/// <param name="vec">The vector to multiply.</param>
/// <returns>The multiplied vector.</returns>
public static Vector4I operator *(int scale, Vector4I vec)
{
vec.X *= scale;
vec.Y *= scale;
vec.Z *= scale;
vec.W *= scale;
return vec;
}
/// <summary>
/// Multiplies each component of the <see cref="Vector4I"/>
/// by the components of the given <see cref="Vector4I"/>.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>The multiplied vector.</returns>
public static Vector4I operator *(Vector4I left, Vector4I right)
{
left.X *= right.X;
left.Y *= right.Y;
left.Z *= right.Z;
left.W *= right.W;
return left;
}
/// <summary>
/// Divides each component of the <see cref="Vector4I"/>
/// by the given <see langword="int"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The divided vector.</returns>
public static Vector4I operator /(Vector4I vec, int divisor)
{
vec.X /= divisor;
vec.Y /= divisor;
vec.Z /= divisor;
vec.W /= divisor;
return vec;
}
/// <summary>
/// Divides each component of the <see cref="Vector4I"/>
/// by the components of the given <see cref="Vector4I"/>.
/// </summary>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The divided vector.</returns>
public static Vector4I operator /(Vector4I vec, Vector4I divisorv)
{
vec.X /= divisorv.X;
vec.Y /= divisorv.Y;
vec.Z /= divisorv.Z;
vec.W /= divisorv.W;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector4I"/>
/// with the components of the given <see langword="int"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector4I(10, -20, 30, -40) % 7); // Prints "(3, -6, 2, -5)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisor">The divisor value.</param>
/// <returns>The remainder vector.</returns>
public static Vector4I operator %(Vector4I vec, int divisor)
{
vec.X %= divisor;
vec.Y %= divisor;
vec.Z %= divisor;
vec.W %= divisor;
return vec;
}
/// <summary>
/// Gets the remainder of each component of the <see cref="Vector4I"/>
/// with the components of the given <see cref="Vector4I"/>.
/// This operation uses truncated division, which is often not desired
/// as it does not work well with negative numbers.
/// Consider using <see cref="Mathf.PosMod(int, int)"/> instead
/// if you want to handle negative numbers.
/// </summary>
/// <example>
/// <code>
/// GD.Print(new Vector4I(10, -20, 30, -40) % new Vector4I(6, 7, 8, 9)); // Prints "(4, -6, 6, -4)"
/// </code>
/// </example>
/// <param name="vec">The dividend vector.</param>
/// <param name="divisorv">The divisor vector.</param>
/// <returns>The remainder vector.</returns>
public static Vector4I operator %(Vector4I vec, Vector4I divisorv)
{
vec.X %= divisorv.X;
vec.Y %= divisorv.Y;
vec.Z %= divisorv.Z;
vec.W %= divisorv.W;
return vec;
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public static bool operator ==(Vector4I left, Vector4I right)
{
return left.Equals(right);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are not equal.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the vectors are not equal.</returns>
public static bool operator !=(Vector4I left, Vector4I right)
{
return !left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Vector4I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y, Z and finally W values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than the right.</returns>
public static bool operator <(Vector4I left, Vector4I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
if (left.Z == right.Z)
{
return left.W < right.W;
}
return left.Z < right.Z;
}
return left.Y < right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector4I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y, Z and finally W values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than the right.</returns>
public static bool operator >(Vector4I left, Vector4I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
if (left.Z == right.Z)
{
return left.W > right.W;
}
return left.Z > right.Z;
}
return left.Y > right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Compares two <see cref="Vector4I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is less than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y, Z and finally W values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is less than or equal to the right.</returns>
public static bool operator <=(Vector4I left, Vector4I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
if (left.Z == right.Z)
{
return left.W <= right.W;
}
return left.Z < right.Z;
}
return left.Y < right.Y;
}
return left.X < right.X;
}
/// <summary>
/// Compares two <see cref="Vector4I"/> vectors by first checking if
/// the X value of the <paramref name="left"/> vector is greater than
/// or equal to the X value of the <paramref name="right"/> vector.
/// If the X values are exactly equal, then it repeats this check
/// with the Y, Z and finally W values of the two vectors.
/// This operator is useful for sorting vectors.
/// </summary>
/// <param name="left">The left vector.</param>
/// <param name="right">The right vector.</param>
/// <returns>Whether or not the left is greater than or equal to the right.</returns>
public static bool operator >=(Vector4I left, Vector4I right)
{
if (left.X == right.X)
{
if (left.Y == right.Y)
{
if (left.Z == right.Z)
{
return left.W >= right.W;
}
return left.Z > right.Z;
}
return left.Y > right.Y;
}
return left.X > right.X;
}
/// <summary>
/// Converts this <see cref="Vector4I"/> to a <see cref="Vector4"/>.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static implicit operator Vector4(Vector4I value)
{
return new Vector4(value.X, value.Y, value.Z, value.W);
}
/// <summary>
/// Converts a <see cref="Vector4"/> to a <see cref="Vector4I"/> by truncating
/// components' fractional parts (rounding towards zero). For a different
/// behavior consider passing the result of <see cref="Vector4.Ceil"/>,
/// <see cref="Vector4.Floor"/> or <see cref="Vector4.Round"/> to this conversion operator instead.
/// </summary>
/// <param name="value">The vector to convert.</param>
public static explicit operator Vector4I(Vector4 value)
{
return new Vector4I((int)value.X, (int)value.Y, (int)value.Z, (int)value.W);
}
/// <summary>
/// Returns <see langword="true"/> if the vector is equal
/// to the given object (<paramref name="obj"/>).
/// </summary>
/// <param name="obj">The object to compare with.</param>
/// <returns>Whether or not the vector and the object are equal.</returns>
public override readonly bool Equals([NotNullWhen(true)] object? obj)
{
return obj is Vector4I other && Equals(other);
}
/// <summary>
/// Returns <see langword="true"/> if the vectors are equal.
/// </summary>
/// <param name="other">The other vector.</param>
/// <returns>Whether or not the vectors are equal.</returns>
public readonly bool Equals(Vector4I other)
{
return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
}
/// <summary>
/// Serves as the hash function for <see cref="Vector4I"/>.
/// </summary>
/// <returns>A hash code for this vector.</returns>
public override readonly int GetHashCode()
{
return HashCode.Combine(X, Y, Z, W);
}
/// <summary>
/// Converts this <see cref="Vector4I"/> to a string.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public override readonly string ToString() => ToString(null);
/// <summary>
/// Converts this <see cref="Vector4I"/> to a string with the given <paramref name="format"/>.
/// </summary>
/// <returns>A string representation of this vector.</returns>
public readonly string ToString(string? format)
{
return $"({X.ToString(format, CultureInfo.InvariantCulture)}, {Y.ToString(format, CultureInfo.InvariantCulture)}, {Z.ToString(format, CultureInfo.InvariantCulture)}, {W.ToString(format, CultureInfo.InvariantCulture)})";
}
}
}

Some files were not shown because too many files have changed in this diff Show More