godot/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
Ignacio Roldán Etcheverry 483071716e C#: Move marshaling logic and generated glue to C#
We will be progressively moving most code to C#.
The plan is to only use Mono's embedding APIs to set things at launch.
This will make it much easier to later support CoreCLR too which
doesn't have rich embedding APIs.

Additionally the code in C# is more maintainable and makes it easier
to implement new features, e.g.: runtime codegen which we could use to
avoid using reflection for marshaling everytime a field, property or
method is accessed.

SOME NOTES ON INTEROP

We make the same assumptions as GDNative about the size of the Godot
structures we use. We take it a bit further by also assuming the layout
of fields in some cases, which is riskier but let's us squeeze out some
performance by avoiding unnecessary managed to native calls.

Code that deals with native structs is less safe than before as there's
no RAII and copy constructors in C#. It's like using the GDNative C API
directly. One has to take special care to free values they own.
Perhaps we could use roslyn analyzers to check this, but I don't know
any that uses attributes to determine what's owned or borrowed.

As to why we maily use pointers for native structs instead of ref/out:
- AFAIK (and confirmed with a benchmark) ref/out are pinned
  during P/Invoke calls and that has a cost.
- Native struct fields can't be ref/out in the first place.
- A `using` local can't be passed as ref/out, only `in`. Calling a
  method or property on an `in` value makes a silent copy, so we want
  to avoid `in`.

REGARDING THE BUILD SYSTEM

There's no longer a `mono_glue=yes/no` SCons options. We no longer
need to build with `mono_glue=no`, generate the glue and then build
again with `mono_glue=yes`. We build only once and generate the glue
(which is in C# now).
However, SCons no longer builds the C# projects for us. Instead one
must run `build_assemblies.py`, e.g.:
```sh
%godot_src_root%/modules/mono/build_scripts/build_assemblies.py \
        --godot-output-dir=%godot_src_root%/bin \
        --godot-target=release_debug`
```
We could turn this into a custom build target, but I don't know how
to do that with SCons (it's possible with Meson).

OTHER NOTES

Most of the moved code doesn't follow the C# naming convention and
still has the word Mono in the names despite no longer dealing with
Mono's embedding APIs. This is just temporary while transitioning,
to make it easier to understand what was moved where.
2021-08-20 10:24:56 +02:00

281 lines
8.5 KiB
C#

#if REAL_T_IS_DOUBLE
using real_t = System.Double;
#else
using real_t = System.Single;
#endif
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Godot.NativeInterop;
// TODO: Add comments describing what this class does. It is not obvious.
namespace Godot
{
public static partial class GD
{
public static unsafe object Bytes2Var(byte[] bytes, bool allowObjects = false)
{
using var varBytes = Marshaling.mono_array_to_PackedByteArray(bytes);
return godot_icall_GD_bytes2var(&varBytes, allowObjects);
}
public static object Convert(object what, Variant.Type type)
{
return godot_icall_GD_convert(what, type);
}
public static real_t Db2Linear(real_t db)
{
return (real_t)Math.Exp(db * 0.11512925464970228420089957273422);
}
private static object[] GetPrintParams(object[] parameters)
{
if (parameters == null)
{
return new[] { "null" };
}
return Array.ConvertAll(parameters, x => x?.ToString() ?? "null");
}
public static int Hash(object var)
{
return godot_icall_GD_hash(var);
}
public static Object InstanceFromId(ulong instanceId)
{
return godot_icall_GD_instance_from_id(instanceId);
}
public static real_t Linear2Db(real_t linear)
{
return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321);
}
public static Resource Load(string path)
{
return ResourceLoader.Load(path);
}
public static T Load<T>(string path) where T : class
{
return ResourceLoader.Load<T>(path);
}
public static void PushError(string message)
{
godot_icall_GD_pusherror(message);
}
public static void PushWarning(string message)
{
godot_icall_GD_pushwarning(message);
}
public static void Print(params object[] what)
{
godot_icall_GD_print(GetPrintParams(what));
}
public static void PrintStack()
{
Print(System.Environment.StackTrace);
}
public static void PrintErr(params object[] what)
{
godot_icall_GD_printerr(GetPrintParams(what));
}
public static void PrintRaw(params object[] what)
{
godot_icall_GD_printraw(GetPrintParams(what));
}
public static void PrintS(params object[] what)
{
godot_icall_GD_prints(GetPrintParams(what));
}
public static void PrintT(params object[] what)
{
godot_icall_GD_printt(GetPrintParams(what));
}
public static float Randf()
{
return godot_icall_GD_randf();
}
public static uint Randi()
{
return godot_icall_GD_randi();
}
public static void Randomize()
{
godot_icall_GD_randomize();
}
public static double RandRange(double from, double to)
{
return godot_icall_GD_randf_range(from, to);
}
public static int RandRange(int from, int to)
{
return godot_icall_GD_randi_range(from, to);
}
public static uint RandFromSeed(ref ulong seed)
{
return godot_icall_GD_rand_seed(seed, out seed);
}
public static IEnumerable<int> Range(int end)
{
return Range(0, end, 1);
}
public static IEnumerable<int> Range(int start, int end)
{
return Range(start, end, 1);
}
public static IEnumerable<int> Range(int start, int end, int 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;
}
}
public static void Seed(ulong seed)
{
godot_icall_GD_seed(seed);
}
public static string Str(params object[] what)
{
return godot_icall_GD_str(what);
}
public static object Str2Var(string str)
{
return godot_icall_GD_str2var(str);
}
public static bool TypeExists(StringName type)
{
return godot_icall_GD_type_exists(ref type.NativeValue);
}
public static unsafe byte[] Var2Bytes(object var, bool fullObjects = false)
{
godot_packed_byte_array varBytes;
godot_icall_GD_var2bytes(var, fullObjects, &varBytes);
using (varBytes)
{
return Marshaling.PackedByteArray_to_mono_array(&varBytes);
}
}
public static string Var2Str(object var)
{
return godot_icall_GD_var2str(var);
}
public static Variant.Type TypeToVariantType(Type type)
{
return godot_icall_TypeToVariantType(type);
}
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern unsafe object godot_icall_GD_bytes2var(godot_packed_byte_array* bytes, bool allowObjects);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object godot_icall_GD_convert(object what, Variant.Type type);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int godot_icall_GD_hash(object var);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern Object godot_icall_GD_instance_from_id(ulong instanceId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_print(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_printerr(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_printraw(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_prints(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_printt(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern float godot_icall_GD_randf();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern uint godot_icall_GD_randi();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_randomize();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern double godot_icall_GD_randf_range(double from, double to);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int godot_icall_GD_randi_range(int from, int to);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_seed(ulong seed);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string godot_icall_GD_str(object[] what);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object godot_icall_GD_str2var(string str);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool godot_icall_GD_type_exists(ref godot_string_name type);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern unsafe void godot_icall_GD_var2bytes(object what, bool fullObjects, godot_packed_byte_array* bytes);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern string godot_icall_GD_var2str(object var);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_pusherror(string type);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void godot_icall_GD_pushwarning(string type);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Variant.Type godot_icall_TypeToVariantType(Type type);
}
}