Fixed calling engine functions from scripts

- Removed mono references in generator
- Implemented StringView type in C#
This commit is contained in:
Simon Lübeß
2025-08-08 19:03:09 +02:00
parent 042ae83a0f
commit d156618453
10 changed files with 330 additions and 277 deletions
@@ -1,5 +1,6 @@
using System; using System;
using GlitchyEngine.Core; using GlitchyEngine.Core;
using GlitchyEngine.Scripting.Classes;
using static GlitchyEngine.Scripting.ScriptEngine; using static GlitchyEngine.Scripting.ScriptEngine;
@@ -16,7 +17,8 @@ class NewScriptClass
public this(StringView fullName, Guid guid, ScriptMethods methods, bool runInEditMode = false) public this(StringView fullName, Guid guid, ScriptMethods methods, bool runInEditMode = false)
{ {
FullName = new String(fullName); // TODO: Remove the need for null termination
FullName = new String(fullName)..EnsureNullTerminator();
Guid = guid; Guid = guid;
int lastDotIndex = FullName.LastIndexOf('.'); int lastDotIndex = FullName.LastIndexOf('.');
+2 -14
View File
@@ -302,7 +302,7 @@ static class ScriptEngine
//ScriptGlue.RegisterManagedComponents(); //ScriptGlue.RegisterManagedComponents();
} }
//InitAssemblyWatcher(); InitAssemblyWatcher();
} }
/// Starts the script runtime and sets the context scene. /// Starts the script runtime and sets the context scene.
@@ -343,8 +343,6 @@ static class ScriptEngine
/// Disposes of and replaces the old instance, if one exists. /// Disposes of and replaces the old instance, if one exists.
public static bool InitializeInstance(Entity entity, ScriptComponent* script) public static bool InitializeInstance(Entity entity, ScriptComponent* script)
{ {
Log.EngineLogger.Error($"{Compiler.CallerMemberName} not updated yet.");
NewScriptClass scriptClass = GetScriptClass(script.ScriptClassName); NewScriptClass scriptClass = GetScriptClass(script.ScriptClassName);
if (scriptClass == null) if (scriptClass == null)
@@ -480,16 +478,6 @@ static class ScriptEngine
s_RootDomain = null;*/ s_RootDomain = null;*/
} }
// TODO: Do we still need this? It was only called by ScriptGlue
/// Returns the script instance or null.
public static void* GetManagedInstance(UUID entityId)
{
//if (_entityScriptInstances.TryGetValue(entityId, let scriptInstance))
// return scriptInstance.MonoInstance;
return null;
}
public static NewScriptClass GetScriptClass(StringView name) public static NewScriptClass GetScriptClass(StringView name)
{ {
EntityClasses.TryGetValue(name, let scriptClass); EntityClasses.TryGetValue(name, let scriptClass);
@@ -501,7 +489,7 @@ static class ScriptEngine
{ {
String entityInfo = scope .(); String entityInfo = scope .();
if (entityId != .Zero) if (entityId != .Zero && Context != null)
{ {
exception.EntityId = entityId; exception.EntityId = entityId;
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -407,9 +407,8 @@ namespace GlitchyEngine.World
collision.Rigidbody = rigidbodyEntityA.UUID; collision.Rigidbody = rigidbodyEntityA.UUID;
collision.OtherRigidbody = rigidbodyEntityB.UUID; collision.OtherRigidbody = rigidbodyEntityB.UUID;
// TODO: scriptOfColliderA?.Instance?.InvokeOnCollisionEnter2D(collision);
//scriptOfColliderA?.Instance?.InvokeOnCollisionEnter2D(collision); scriptOfRigidbodyA?.Instance?.InvokeOnCollisionEnter2D(collision);
//scriptOfRigidbodyA?.Instance?.InvokeOnCollisionEnter2D(collision);
} }
bool fireEventB = colliderEntityB.TryGetComponent<ScriptComponent>(let scriptOfColliderB); bool fireEventB = colliderEntityB.TryGetComponent<ScriptComponent>(let scriptOfColliderB);
@@ -423,8 +422,8 @@ namespace GlitchyEngine.World
collision.Rigidbody = rigidbodyEntityB.UUID; collision.Rigidbody = rigidbodyEntityB.UUID;
collision.OtherRigidbody = rigidbodyEntityA.UUID; collision.OtherRigidbody = rigidbodyEntityA.UUID;
//scriptOfColliderB?.Instance?.InvokeOnCollisionEnter2D(collision); scriptOfColliderB?.Instance?.InvokeOnCollisionEnter2D(collision);
//scriptOfRigidbodyB?.Instance?.InvokeOnCollisionEnter2D(collision); scriptOfRigidbodyB?.Instance?.InvokeOnCollisionEnter2D(collision);
} }
}; };
_contactListener.endContactCallback = (contact, userData) => { _contactListener.endContactCallback = (contact, userData) => {
+7 -2
View File
@@ -427,13 +427,18 @@ public class Entity : EngineObject
/// <returns>The new <see cref="Entity"/>.</returns> /// <returns>The new <see cref="Entity"/>.</returns>
public static Entity CreateInstance(Entity entity) public static Entity CreateInstance(Entity entity)
{ {
if (entity == null)
{
throw new ArgumentException("The provided instance must not be null!", nameof(entity));
}
ScriptGlue.Entity_CreateInstance(entity.UUID, out UUID newEntityId); ScriptGlue.Entity_CreateInstance(entity.UUID, out UUID newEntityId);
return new Entity(newEntityId); return new Entity(newEntityId);
} }
/// <summary> /// <summary>
/// Will be executed once after the entity has be created. /// Will be called once after the entity has be created.
/// </summary> /// </summary>
protected internal virtual void OnCreate() { } protected internal virtual void OnCreate() { }
@@ -443,7 +448,7 @@ public class Entity : EngineObject
protected internal virtual void OnUpdate(float deltaTime) { } protected internal virtual void OnUpdate(float deltaTime) { }
/// <summary> /// <summary>
/// Will be executed once when the entity is being destroyed. /// Will be called once when the entity is being destroyed.
/// </summary> /// </summary>
protected internal virtual void OnDestroy() { } protected internal virtual void OnDestroy() { }
} }
+73
View File
@@ -0,0 +1,73 @@
using GlitchyEngine.Core;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace GlitchyEngine.Native;
[DebuggerDisplay("{ToString(),raw}")]
[StructLayout(LayoutKind.Sequential, Pack = 0)]
[EngineClass("System.StringView")]
internal unsafe struct StringView
{
public byte* Utf8Ptr;
public long Length;
public StringView()
{
Utf8Ptr = null;
Length = 0;
}
public StringView(byte* utf8Ptr, long length)
{
Utf8Ptr = utf8Ptr;
Length = length;
}
public override string? ToString()
{
if (Utf8Ptr == null)
return null;
if (Length == 0)
return string.Empty;
if (Length is < 0 or > int.MaxValue)
{
throw new InvalidOperationException($"String length is invalid: {Length}.");
}
return Encoding.UTF8.GetString(Utf8Ptr, (int)Length);
}
/// <summary>
/// Creates a String<see cref="StringView"/>View that can be passed to native code. This method allocates native memory that must be freed using
/// <see cref="NativeMemory.Free"/>.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <returns>The <see cref="StringView"/> pointing to the native memory containing the UTF8-text; or null, if <see cref="s"/> was null.</returns>
/// <remarks>
/// The allocated string is guaranteed to have a null terminator.
/// The null terminator is not counted into the length of the resulting <see cref="StringView"/>.
/// </remarks>
public static StringView FromManagedString(string? s)
{
if (s == null)
return new StringView();
int maxByteCount = Encoding.UTF8.GetMaxByteCount(s.Length);
byte* pointer = (byte*)NativeMemory.Alloc((nuint) checked (maxByteCount + 1));
int bytes = Encoding.UTF8.GetBytes((ReadOnlySpan<char>) s, new Span<byte>(pointer, maxByteCount));
pointer[bytes] = (byte) 0;
return new StringView(pointer, bytes);
}
public static void FreeNativeMemory(StringView s)
{
NativeMemory.Free(s.Utf8Ptr);
}
}
+1 -1
View File
@@ -17,6 +17,6 @@ public static class Physics2D
ScriptGlue.Physics2D_GetGravity(out float2 gravity); ScriptGlue.Physics2D_GetGravity(out float2 gravity);
return gravity; return gravity;
} }
set => ScriptGlue.Physics2D_SetGravity(in value); set => ScriptGlue.Physics2D_SetGravity(value);
} }
} }
+34 -14
View File
@@ -8,18 +8,24 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Loader; using System.Runtime.Loader;
using GlitchyEngine.Native;
using GlitchyEngine.Physics;
namespace GlitchyEngine; namespace GlitchyEngine;
/// <summary>
/// Contains functions pointer to engine functions that can be called form scripts.
/// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct EngineFunctions internal unsafe partial struct EngineFunctions
{ {
} }
/// <summary> /// <summary>
/// All methods in here are glued to the ScriptGlue.bf in the engine. /// Provides the interface between engine and scripts.
/// </summary> /// </summary>
internal static unsafe partial class ScriptGlue internal static unsafe partial class ScriptGlue
{ {
@@ -187,7 +193,16 @@ internal static unsafe partial class ScriptGlue
static ScriptMethods HasMethod(Type type, string methodName, ScriptMethods methodFlag) static ScriptMethods HasMethod(Type type, string methodName, ScriptMethods methodFlag)
{ {
MethodInfo? methodInfo = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); MethodInfo? methodInfo = null;
Type? currentType = type;
while (methodInfo == null && currentType != typeof(Entity) && currentType != null)
{
methodInfo = currentType.GetMethod(methodName,
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
currentType = currentType.BaseType;
}
return methodInfo != null ? methodFlag : ScriptMethods.None; return methodInfo != null ? methodFlag : ScriptMethods.None;
} }
@@ -281,7 +296,7 @@ internal static unsafe partial class ScriptGlue
} }
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
public static void InvokeEntityOnUpdate(UUID entityId, float deltaTime) public static void InvokeEntityOnUpdate(UUID entityId, float deltaTime)
{ {
try try
@@ -340,7 +355,7 @@ internal static unsafe partial class ScriptGlue
Debug.Assert(scriptInstance != null, "Failed to create script instance."); Debug.Assert(scriptInstance != null, "Failed to create script instance.");
EntityScriptInstances.Add(entityId, (scriptInstance!, scriptType)); EntityScriptInstances.Add(entityId, (scriptInstance, scriptType));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -489,6 +504,8 @@ internal static unsafe partial class ScriptGlue
#endregion #endregion
#region Custom engine call implementations
public static void Entity_AddComponent(UUID entityId, Type componentType) public static void Entity_AddComponent(UUID entityId, Type componentType)
{ {
ComponentTypeFunctions[componentType].AddComponent(entityId); ComponentTypeFunctions[componentType].AddComponent(entityId);
@@ -520,8 +537,8 @@ internal static unsafe partial class ScriptGlue
public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string fullTypeName) public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string fullTypeName)
{ {
byte* fieldNameConverted = (byte*)Marshal.StringToCoTaskMemUTF8(fieldName); StringView fieldNameConverted = StringView.FromManagedString(fieldName);
byte* fullTypeNameConverted = (byte*)Marshal.StringToCoTaskMemUTF8(fullTypeName); StringView fullTypeNameConverted = StringView.FromManagedString(fullTypeName);
void* valueObjectConverted = null; void* valueObjectConverted = null;
bool deleteValueObject = false; bool deleteValueObject = false;
@@ -530,29 +547,32 @@ internal static unsafe partial class ScriptGlue
{ {
case SerializationType.String: case SerializationType.String:
case SerializationType.Enum: case SerializationType.Enum:
if (valueObject is String stringValue) if (valueObject is string stringValue)
{ {
valueObjectConverted = (void*)Marshal.StringToCoTaskMemUTF8(stringValue); StringView nativeString = StringView.FromManagedString(stringValue);
valueObjectConverted = nativeString.Utf8Ptr;
deleteValueObject = true; deleteValueObject = true;
} }
break; break;
default: default:
if (valueObject is not null) if (valueObject is not null)
{ {
void* p = &valueObject; #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
object?* objectRef = &valueObject;
// Skip Object Header (IntPtr) + Method Table (IntPtr) // Skip Object Header (IntPtr) + Method Table (IntPtr)
valueObjectConverted = (byte*)*(IntPtr*)p + sizeof(IntPtr); valueObjectConverted = (byte*)*(IntPtr*)objectRef + sizeof(IntPtr);
float i = *(float*)valueObjectConverted;
} }
break; break;
} }
_engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, valueObjectConverted, fullTypeNameConverted); _engineFunctions.Serialization_SerializeField((void*)serializationContext, type, fieldNameConverted, valueObjectConverted, fullTypeNameConverted);
Marshal.FreeCoTaskMem((IntPtr)fieldNameConverted); NativeMemory.Free(fieldNameConverted.Utf8Ptr);
Marshal.FreeCoTaskMem((IntPtr)fullTypeNameConverted); NativeMemory.Free(fullTypeNameConverted.Utf8Ptr);
if (deleteValueObject) if (deleteValueObject)
Marshal.FreeCoTaskMem((IntPtr)valueObjectConverted); NativeMemory.Free(valueObjectConverted);
} }
#endregion Custom engine call implementations
} }
@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using GlitchyEngine.Editor; using GlitchyEngine.Editor;
using GlitchyEngine.Native;
namespace GlitchyEngine.Serialization; namespace GlitchyEngine.Serialization;
@@ -105,7 +106,7 @@ public class DeserializationObject
return type; return type;
} }
public DeserializationObject? GetDeserializedObject(UUID id) public unsafe DeserializationObject? GetDeserializedObject(UUID id)
{ {
DeserializationObject context; DeserializationObject context;
@@ -148,15 +149,8 @@ public class DeserializationObject
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct DataHelper internal struct DataHelper
{ {
[StructLayout(LayoutKind.Sequential)]
public unsafe struct StringView
{
public byte* Utf8Ptr;
public long Length;
}
[FieldOffset(0)] [FieldOffset(0)]
public EngineObjectReferenceHelper EngineObjectReference; public EngineObjectReferenceHelper EngineObjectReference;
@@ -187,27 +181,11 @@ public class DeserializationObject
ScriptGlue.Serialization_DeserializeField(_internalContext, expectedType, completeFieldName, rawData, out SerializationType actualType); ScriptGlue.Serialization_DeserializeField(_internalContext, expectedType, completeFieldName, rawData, out SerializationType actualType);
string? GetString()
{
if (dataHelper.String.Utf8Ptr == null)
return null;
if (dataHelper.String.Length == 0)
return string.Empty;
if (dataHelper.String.Length is < 0 or > int.MaxValue)
{
throw new InvalidOperationException($"String length is invalid: {dataHelper.String.Length}");
}
return Encoding.UTF8.GetString(dataHelper.String.Utf8Ptr, (int)dataHelper.String.Utf8Ptr);
}
object? value = actualType switch object? value = actualType switch
{ {
SerializationType.Bool => *(bool*)rawData, SerializationType.Bool => *(bool*)rawData,
SerializationType.Char => *(char*)rawData, SerializationType.Char => *(char*)rawData,
SerializationType.String => GetString(), SerializationType.String => dataHelper.String.ToString(),
SerializationType.Int8 => *(sbyte*)rawData, SerializationType.Int8 => *(sbyte*)rawData,
SerializationType.Int16 => *(short*)rawData, SerializationType.Int16 => *(short*)rawData,
SerializationType.Int32 => *(int*)rawData, SerializationType.Int32 => *(int*)rawData,
@@ -219,7 +197,7 @@ public class DeserializationObject
SerializationType.Float => *(float*)rawData, SerializationType.Float => *(float*)rawData,
SerializationType.Double => *(double*)rawData, SerializationType.Double => *(double*)rawData,
SerializationType.Decimal => *(decimal*)rawData, SerializationType.Decimal => *(decimal*)rawData,
SerializationType.Enum => GetString(), SerializationType.Enum => dataHelper.String.ToString(),
SerializationType.EngineObjectReference => dataHelper.EngineObjectReference, SerializationType.EngineObjectReference => dataHelper.EngineObjectReference,
SerializationType.ObjectReference => dataHelper.UUID, SerializationType.ObjectReference => dataHelper.UUID,
_ => NoValueDeserialized _ => NoValueDeserialized
@@ -522,7 +500,7 @@ public class DeserializationObject
} }
catch catch
{ {
Log.Error($"Failed to parse \"{valueName}\" as enum-type \"{enumType}\""); Log.Error($"Failed to deserialize field \"{fieldName}\": Could not parse \"{valueName}\" as enum-type \"{enumType}\"");
} }
return NoValueDeserialized; return NoValueDeserialized;
+11 -30
View File
@@ -194,36 +194,6 @@ public class ScriptGlueGenerator : IIncrementalGenerator
ReturnValueConversion = null ReturnValueConversion = null
}); });
beefTypeToMappedType.Add("Mono.MonoString*", new MappedType
{
BeefTypeName = "object /*TODO: Mono.MonoString**/",
ReturnValueConversion = null
});
beefTypeToMappedType.Add("Mono.MonoException*", new MappedType
{
BeefTypeName = "object /*TODO: Mono.MonoException**/",
ReturnValueConversion = null
});
beefTypeToMappedType.Add("Mono.MonoArray*", new MappedType
{
BeefTypeName = "object /*TODO: Mono.MonoArray**/",
ReturnValueConversion = null
});
beefTypeToMappedType.Add("Mono.MonoObject*", new MappedType
{
BeefTypeName = "object /*TODO: Mono.MonoObject**/",
ReturnValueConversion = null
});
beefTypeToMappedType.Add("Mono.MonoReflectionType*", new MappedType
{
BeefTypeName = "object /*TODO: Mono.MonoReflectionType**/",
ReturnValueConversion = null
});
beefTypeToMappedType.Add("uint8*", new MappedType beefTypeToMappedType.Add("uint8*", new MappedType
{ {
BeefTypeName = "uint8*", BeefTypeName = "uint8*",
@@ -287,6 +257,17 @@ public class ScriptGlueGenerator : IIncrementalGenerator
CSharpWrapperType = "bool", CSharpWrapperType = "bool",
ReturnValueConversion = "EngineErrors.ThrowIfError(returnValue);\nreturn (returnValue == EngineResult.Ok);" ReturnValueConversion = "EngineErrors.ThrowIfError(returnValue);\nreturn (returnValue == EngineResult.Ok);"
}); });
beefTypeToMappedType.Add("System.StringView", new MappedType()
{
BeefTypeName = "System.StringView",
CSharpTypeName = "GlitchyEngine.Native.StringView",
CSharpWrapperType = "string",
ReturnValueConversion = "return returnValue.ToString();",
WrapperConvertInput = "var {0} = GlitchyEngine.Native.StringView.FromManagedString({1});",
WrapperCleanupInput = "GlitchyEngine.Native.StringView.FreeNativeMemory({0});",
WrapperOutConversion = "{1} = {0}.ToString();"
});
} }
private static void GenerateFunctionPointer(GlueMethod method, Dictionary<string, MappedType> beefTypeToMappedType, StringBuilder output) private static void GenerateFunctionPointer(GlueMethod method, Dictionary<string, MappedType> beefTypeToMappedType, StringBuilder output)