feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

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,747 @@
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 halfExtents = _size * 0.5f;
Vector3 ofs = _position + halfExtents;
return ofs + new Vector3(
dir.X > 0f ? halfExtents.X : -halfExtents.X,
dir.Y > 0f ? halfExtents.Y : -halfExtents.Y,
dir.Z > 0f ? halfExtents.Z : -halfExtents.Z);
}
/// <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)
{
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,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,269 @@
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;
}
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;
}
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_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,117 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Godot.Bridge;
#nullable enable
public static partial class ScriptManagerBridge
{
private class ScriptTypeBiMap
{
public readonly object ReadWriteLock = new();
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 IReadOnlyCollection<string> 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} arguments 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} arguments, 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,310 @@
using System.Collections.Generic;
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 Dictionary<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 },
};
#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)
{
if (size < 0)
throw new ArgumentOutOfRangeException(nameof(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,892 @@
#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:
{
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,941 @@
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_keys(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 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>
[DebuggerTypeProxy(typeof(DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> :
IDictionary<TKey, TValue>,
IReadOnlyDictionary<TKey, TValue>,
IGenericGodotDictionary
{
private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) =>
VariantUtils.CreateFromDictionary(godotDictionary);
private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) =>
VariantUtils.ConvertToDictionary<TKey, TValue>(variant);
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();
}
/// <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)
{
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary));
_underlyingDict = new Dictionary();
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)
{
if (dictionary == null)
throw new ArgumentNullException(nameof(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,88 @@
using System;
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(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,686 @@
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 _);
}
}
}

View file

@ -0,0 +1,298 @@
using System;
using System.Diagnostics;
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);
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 unsafe void ConstructAndInitialize(
delegate* unmanaged<IntPtr> nativeCtor,
StringName nativeName,
Type cachedType,
bool refCounted
)
{
if (NativePtr == IntPtr.Zero)
{
Debug.Assert(nativeCtor != null);
NativePtr = nativeCtor();
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.
if (instance._disposed || instance.NativePtr == IntPtr.Zero)
throw new ObjectDisposedException(instance.GetType().FullName);
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 Type InternalGetClassNativeBase(Type t)
{
var name = t.Assembly.GetName().Name;
if (name == "GodotSharp" || name == "GodotSharpEditor")
return t;
Debug.Assert(t.BaseType is not null, "Script types must derive from a native Godot type.");
return InternalGetClassNativeBase(t.BaseType);
}
// 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<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.Any())
{
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} arguments.";
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
}
}

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,627 @@
#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;
}
// 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(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(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 unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<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 unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<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 unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<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 unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(
Span<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 unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(
Span<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(Span<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 unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(
Span<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 unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(
Span<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 unsafe godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(
Span<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 unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<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,565 @@
#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<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(in godot_string_name p_native_name,
in godot_array p_input, out godot_array r_output);
internal static partial void godotsharp_array_filter_godot_objects_by_non_native(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,
in godot_string p_name);
public static partial void godotsharp_node_path_new_from_string(out godot_node_path r_dest,
in godot_string p_name);
public static partial void
godotsharp_string_name_as_string(out godot_string r_dest, in godot_string_name p_name);
public static partial void godotsharp_node_path_as_string(out godot_string r_dest, 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(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(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);
// 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, in godot_string_name p_s);
public static partial void godotsharp_variant_new_copy(out godot_variant r_dest, in godot_variant p_src);
public static partial void godotsharp_variant_new_node_path(out godot_variant r_dest, 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, in Transform2D p_t2d);
public static partial void godotsharp_variant_new_basis(out godot_variant r_dest, in Basis p_basis);
public static partial void godotsharp_variant_new_transform3d(out godot_variant r_dest, in Transform3D p_trans);
public static partial void godotsharp_variant_new_projection(out godot_variant r_dest, in Projection p_proj);
public static partial void godotsharp_variant_new_aabb(out godot_variant r_dest, in Aabb p_aabb);
public static partial void godotsharp_variant_new_dictionary(out godot_variant r_dest,
in godot_dictionary p_dict);
public static partial void godotsharp_variant_new_array(out godot_variant r_dest, in godot_array p_arr);
public static partial void godotsharp_variant_new_packed_byte_array(out godot_variant r_dest,
in godot_packed_byte_array p_pba);
public static partial void godotsharp_variant_new_packed_int32_array(out godot_variant r_dest,
in godot_packed_int32_array p_pia);
public static partial void godotsharp_variant_new_packed_int64_array(out godot_variant r_dest,
in godot_packed_int64_array p_pia);
public static partial void godotsharp_variant_new_packed_float32_array(out godot_variant r_dest,
in godot_packed_float32_array p_pra);
public static partial void godotsharp_variant_new_packed_float64_array(out godot_variant r_dest,
in godot_packed_float64_array p_pra);
public static partial void godotsharp_variant_new_packed_string_array(out godot_variant r_dest,
in godot_packed_string_array p_psa);
public static partial void godotsharp_variant_new_packed_vector2_array(out godot_variant r_dest,
in godot_packed_vector2_array p_pv2a);
public static partial void godotsharp_variant_new_packed_vector3_array(out godot_variant r_dest,
in godot_packed_vector3_array p_pv3a);
public static partial void godotsharp_variant_new_packed_vector4_array(out godot_variant r_dest,
in godot_packed_vector4_array p_pv4a);
public static partial void godotsharp_variant_new_packed_color_array(out godot_variant r_dest,
in godot_packed_color_array p_pca);
public static partial godot_bool godotsharp_variant_as_bool(in godot_variant p_self);
public static partial Int64 godotsharp_variant_as_int(in godot_variant p_self);
public static partial double godotsharp_variant_as_float(in godot_variant p_self);
public static partial godot_string godotsharp_variant_as_string(in godot_variant p_self);
public static partial Vector2 godotsharp_variant_as_vector2(in godot_variant p_self);
public static partial Vector2I godotsharp_variant_as_vector2i(in godot_variant p_self);
public static partial Rect2 godotsharp_variant_as_rect2(in godot_variant p_self);
public static partial Rect2I godotsharp_variant_as_rect2i(in godot_variant p_self);
public static partial Vector3 godotsharp_variant_as_vector3(in godot_variant p_self);
public static partial Vector3I godotsharp_variant_as_vector3i(in godot_variant p_self);
public static partial Transform2D godotsharp_variant_as_transform2d(in godot_variant p_self);
public static partial Vector4 godotsharp_variant_as_vector4(in godot_variant p_self);
public static partial Vector4I godotsharp_variant_as_vector4i(in godot_variant p_self);
public static partial Plane godotsharp_variant_as_plane(in godot_variant p_self);
public static partial Quaternion godotsharp_variant_as_quaternion(in godot_variant p_self);
public static partial Aabb godotsharp_variant_as_aabb(in godot_variant p_self);
public static partial Basis godotsharp_variant_as_basis(in godot_variant p_self);
public static partial Transform3D godotsharp_variant_as_transform3d(in godot_variant p_self);
public static partial Projection godotsharp_variant_as_projection(in godot_variant p_self);
public static partial Color godotsharp_variant_as_color(in godot_variant p_self);
public static partial godot_string_name godotsharp_variant_as_string_name(in godot_variant p_self);
public static partial godot_node_path godotsharp_variant_as_node_path(in godot_variant p_self);
public static partial Rid godotsharp_variant_as_rid(in godot_variant p_self);
public static partial godot_callable godotsharp_variant_as_callable(in godot_variant p_self);
public static partial godot_signal godotsharp_variant_as_signal(in godot_variant p_self);
public static partial godot_dictionary godotsharp_variant_as_dictionary(in godot_variant p_self);
public static partial godot_array godotsharp_variant_as_array(in godot_variant p_self);
public static partial godot_packed_byte_array godotsharp_variant_as_packed_byte_array(in godot_variant p_self);
public static partial godot_packed_int32_array godotsharp_variant_as_packed_int32_array(in godot_variant p_self);
public static partial godot_packed_int64_array godotsharp_variant_as_packed_int64_array(in godot_variant p_self);
public static partial godot_packed_float32_array godotsharp_variant_as_packed_float32_array(
in godot_variant p_self);
public static partial godot_packed_float64_array godotsharp_variant_as_packed_float64_array(
in godot_variant p_self);
public static partial godot_packed_string_array godotsharp_variant_as_packed_string_array(
in godot_variant p_self);
public static partial godot_packed_vector2_array godotsharp_variant_as_packed_vector2_array(
in godot_variant p_self);
public static partial godot_packed_vector3_array godotsharp_variant_as_packed_vector3_array(
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(in godot_variant p_self);
public static partial godot_bool godotsharp_variant_equals(in godot_variant p_a, 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,
in godot_string_name p_src);
// node_path.h
public static partial void godotsharp_node_path_new_copy(out godot_node_path r_dest, 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, 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,
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(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_max(ref godot_array p_self, out godot_variant r_value);
public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value);
public static partial void godotsharp_array_pick_random(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(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);
// Dictionary
public static partial godot_bool godotsharp_dictionary_try_get_value(ref godot_dictionary p_self,
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(ref godot_dictionary p_self, out godot_array r_dest);
public static partial void godotsharp_dictionary_values(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(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(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_to_string(ref godot_dictionary p_self, out godot_string r_str);
// StringExtensions
public static partial void godotsharp_string_simplify_path(in godot_string p_self,
out godot_string r_simplified_path);
public static partial void godotsharp_string_to_camel_case(in godot_string p_self,
out godot_string r_camel_case);
public static partial void godotsharp_string_to_pascal_case(in godot_string p_self,
out godot_string r_pascal_case);
public static partial void godotsharp_string_to_snake_case(in godot_string p_self,
out godot_string r_snake_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(in godot_node_path p_self,
out godot_string r_names);
public static partial void godotsharp_node_path_get_concatenated_subnames(in godot_node_path p_self,
out godot_string r_subnames);
public static partial void godotsharp_node_path_get_name(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(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(in godot_packed_byte_array p_bytes,
godot_bool p_allow_objects,
out godot_variant r_ret);
internal static partial void godotsharp_convert(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(in godot_string p_str, out godot_variant r_ret);
internal static partial void godotsharp_var_to_bytes(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(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(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(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(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(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(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,658 @@
#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(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(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(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(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(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(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(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(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(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(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(Span<byte> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedByteArray(from);
return CreateFromPackedByteArray(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedInt32Array(Span<int> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedInt32Array(from);
return CreateFromPackedInt32Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedInt64Array(Span<long> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedInt64Array(from);
return CreateFromPackedInt64Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedFloat32Array(Span<float> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedFloat32Array(from);
return CreateFromPackedFloat32Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedFloat64Array(Span<double> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedFloat64Array(from);
return CreateFromPackedFloat64Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedStringArray(Span<string> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedStringArray(from);
return CreateFromPackedStringArray(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector2Array(Span<Vector2> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector2Array(from);
return CreateFromPackedVector2Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector3Array(Span<Vector3> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector3Array(from);
return CreateFromPackedVector3Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedVector4Array(Span<Vector4> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedVector4Array(from);
return CreateFromPackedVector4Array(nativePackedArray);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_variant CreateFromPackedColorArray(Span<Color> from)
{
using var nativePackedArray = Marshaling.ConvertSystemArrayToNativePackedColorArray(from);
return CreateFromPackedColorArray(nativePackedArray);
}
public static godot_variant CreateFromSystemArrayOfStringName(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(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(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(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(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(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(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)
=> p_var.Type == Variant.Type.Object ? p_var.Object : IntPtr.Zero;
[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(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(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(in godot_variant p_var)
=> NativeFuncs.godotsharp_variant_as_callable(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Callable ConvertToCallable(in godot_variant p_var)
=> Marshaling.ConvertCallableToManaged(ConvertToNativeCallable(p_var));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static godot_signal ConvertToNativeSignal(in godot_variant p_var)
=> NativeFuncs.godotsharp_variant_as_signal(p_var);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Signal ConvertToSignal(in godot_variant p_var)
=> Marshaling.ConvertSignalToManaged(ConvertToNativeSignal(p_var));
public static godot_array ConvertToNativeArray(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(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,416 @@
using System;
using System.Runtime.CompilerServices;
namespace Godot.NativeInterop;
#nullable enable
public partial class VariantUtils
{
private static Exception UnsupportedType<T>() => new InvalidOperationException(
$"The type is not supported for conversion to/from Variant: '{typeof(T).FullName}'");
internal static class GenericConversion<T>
{
public static unsafe godot_variant ToVariant(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>();
// ReSharper disable once StaticMemberInGenericType
internal static unsafe delegate*<in T, godot_variant> ToVariantCb;
// ReSharper disable once StaticMemberInGenericType
internal static unsafe delegate*<in godot_variant, T> FromVariantCb;
static GenericConversion()
{
RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static godot_variant CreateFrom<[MustBeVariant] T>(in T from)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static TTo UnsafeAs<TTo>(in T f) => Unsafe.As<T, TTo>(ref Unsafe.AsRef(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(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,826 @@
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)
{
Vector3 c = arcFrom.Cross(arcTo);
real_t d = arcFrom.Dot(arcTo);
if (d < -1.0f + Mathf.Epsilon)
{
X = 0f;
Y = 1f;
Z = 0f;
W = 0f;
}
else
{
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;
}
}
/// <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,479 @@
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 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,16 @@
using System;
using System.Linq;
#nullable enable
namespace Godot;
internal class ReflectionUtils
{
public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName)?
.GetType(typeFullName);
}
}

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,67 @@
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;
Variant[] signalArgs = new Variant[argCount];
for (int i = 0; i < argCount; i++)
signalArgs[i] = Variant.CreateCopyingBorrowed(*args[i]);
awaiter._result = signalArgs;
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.GetHashCode();
}
}
}

View file

@ -0,0 +1,664 @@
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(BasisDeterminant());
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(BasisDeterminant());
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 = BasisDeterminant();
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, its determinant is the square of the scale.
///
/// A negative determinant means the Y 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)]
private readonly real_t BasisDeterminant()
{
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[i][j] * min[j];
real_t f = transform.Basis[i][j] * 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)})";
}
}
}

View file

@ -0,0 +1,5 @@
#if REAL_T_IS_DOUBLE
global using real_t = System.Double;
#else
global using real_t = System.Single;
#endif

View file

@ -0,0 +1,148 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
<OutputPath>bin/$(Configuration)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
<EnableDefaultItems>false</EnableDefaultItems>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
<AnalysisMode>Recommended</AnalysisMode>
</PropertyGroup>
<PropertyGroup>
<Description>Godot C# Core API.</Description>
<Authors>Godot Engine contributors</Authors>
<PackageId>GodotSharp</PackageId>
<Version>4.3.0</Version>
<PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>Copyright (c) Godot Engine contributors</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<!-- SdkPackageVersions.props for easy access -->
<None Include="$(GodotSdkPackageVersionsFilePath)">
<Link>SdkPackageVersions.props</Link>
</None>
</ItemGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);GODOT</DefineConstants>
<DefineConstants Condition=" '$(GodotFloat64)' == 'true' ">REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReflectionAnalyzers" Version="0.1.22-dev" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" />
<!--PackageReference Include="IDisposableAnalyzers" Version="3.4.13" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /-->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- Sources -->
<ItemGroup>
<Compile Include="Core\Aabb.cs" />
<Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
<Compile Include="Core\Bridge\MethodInfo.cs" />
<Compile Include="Core\Callable.generics.cs" />
<Compile Include="Core\CustomGCHandle.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
<Compile Include="Core\Attributes\ExportCategoryAttribute.cs" />
<Compile Include="Core\Attributes\ExportGroupAttribute.cs" />
<Compile Include="Core\Attributes\ExportSubgroupAttribute.cs" />
<Compile Include="Core\Attributes\GlobalClassAttribute.cs" />
<Compile Include="Core\Attributes\GodotClassNameAttribute.cs" />
<Compile Include="Core\Attributes\IconAttribute.cs" />
<Compile Include="Core\Attributes\MustBeVariantAttribute.cs" />
<Compile Include="Core\Attributes\RpcAttribute.cs" />
<Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
<Compile Include="Core\Attributes\SignalAttribute.cs" />
<Compile Include="Core\Attributes\ToolAttribute.cs" />
<Compile Include="Core\Basis.cs" />
<Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
<Compile Include="Core\Bridge\GCHandleBridge.cs" />
<Compile Include="Core\Bridge\AlcReloadCfg.cs" />
<Compile Include="Core\Bridge\ManagedCallbacks.cs" />
<Compile Include="Core\Bridge\PropertyInfo.cs" />
<Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
<Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" />
<Compile Include="Core\Callable.cs" />
<Compile Include="Core\Color.cs" />
<Compile Include="Core\Colors.cs" />
<Compile Include="Core\DebuggingUtils.cs" />
<Compile Include="Core\DebugView.cs" />
<Compile Include="Core\DelegateUtils.cs" />
<Compile Include="Core\Dictionary.cs" />
<Compile Include="Core\Dispatcher.cs" />
<Compile Include="Core\Extensions\GodotObjectExtensions.cs" />
<Compile Include="Core\Extensions\NodeExtensions.cs" />
<Compile Include="Core\Extensions\PackedSceneExtensions.cs" />
<Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" />
<Compile Include="Core\GD.cs" />
<Compile Include="Core\GodotObject.base.cs" />
<Compile Include="Core\GodotObject.exceptions.cs" />
<Compile Include="Core\GodotSynchronizationContext.cs" />
<Compile Include="Core\GodotTaskScheduler.cs" />
<Compile Include="Core\GodotTraceListener.cs" />
<Compile Include="Core\GodotUnhandledExceptionEvent.cs" />
<Compile Include="Core\DisposablesTracker.cs" />
<Compile Include="Core\Interfaces\IAwaitable.cs" />
<Compile Include="Core\Interfaces\IAwaiter.cs" />
<Compile Include="Core\Interfaces\ISerializationListener.cs" />
<Compile Include="Core\Mathf.cs" />
<Compile Include="Core\MathfEx.cs" />
<Compile Include="Core\NativeInterop\CustomUnsafe.cs" />
<Compile Include="Core\NativeInterop\ExceptionUtils.cs" />
<Compile Include="Core\NativeInterop\GodotDllImportResolver.cs" />
<Compile Include="Core\NativeInterop\InteropUtils.cs" />
<Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" />
<Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" />
<Compile Include="Core\NativeInterop\VariantUtils.cs" />
<Compile Include="Core\NativeInterop\VariantUtils.generic.cs" />
<Compile Include="Core\NodePath.cs" />
<Compile Include="Core\Plane.cs" />
<Compile Include="Core\Projection.cs" />
<Compile Include="Core\Quaternion.cs" />
<Compile Include="Core\Rect2.cs" />
<Compile Include="Core\Rect2I.cs" />
<Compile Include="Core\ReflectionUtils.cs" />
<Compile Include="Core\Rid.cs" />
<Compile Include="Core\NativeInterop\NativeFuncs.cs" />
<Compile Include="Core\NativeInterop\InteropStructs.cs" />
<Compile Include="Core\NativeInterop\Marshaling.cs" />
<Compile Include="Core\Signal.cs" />
<Compile Include="Core\SignalAwaiter.cs" />
<Compile Include="Core\StringExtensions.cs" />
<Compile Include="Core\StringName.cs" />
<Compile Include="Core\Transform2D.cs" />
<Compile Include="Core\Transform3D.cs" />
<Compile Include="Core\Variant.cs" />
<Compile Include="Core\Vector2.cs" />
<Compile Include="Core\Vector2I.cs" />
<Compile Include="Core\Vector3.cs" />
<Compile Include="Core\Vector3I.cs" />
<Compile Include="Core\Vector4.cs" />
<Compile Include="Core\Vector4I.cs" />
<Compile Include="GlobalUsings.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<!-- Compat Sources -->
<ItemGroup>
<Compile Include="Compat.cs" />
</ItemGroup>
<!--
We import a props file with auto-generated includes. This works well with Rider.
However, Visual Studio and MonoDevelop won't list them in the solution explorer.
We can't use wildcards as there may be undesired old files still hanging around.
Fortunately code completion, go to definition and such still work.
-->
<Import Condition=" '$(GodotSkipGenerated)' == '' " Project="Generated\GeneratedIncludes.props" />
</Project>

View file

@ -0,0 +1,5 @@
<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:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated_005Cgodotobjects/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("GodotSharpEditor")]