using Mono; using System; using GlitchLog; using System.Reflection; using GlitchyEngine.Events; using GlitchyEngine.Math; using GlitchyEngine.World; using GlitchyEngine.Core; using System.Collections; using Box2D; using GlitchyEngine.Scripting; namespace GlitchyEngine.Scripting; using internal GlitchyEngine.Scripting; static class ScriptGlue { private static Dictionary s_AddComponentMethods = new .() ~ delete _; private static Dictionary s_HasComponentMethods = new .() ~ delete _; private static Dictionary s_RemoveComponentMethods = new .() ~ delete _; struct RegisterCallAttribute : Attribute { public String MethodName; public this(String methodName) { MethodName = methodName; } } /* Adding this attribute to a method will log method entry and returned Result errors */ [AttributeUsage(.Method)] struct RegisterMethodAttribute : Attribute, IOnMethodInit { [Comptime] public void OnMethodInit(MethodInfo method, Self* prev) { for (var methodInfo in typeof(ScriptGlue).GetMethods(.Static)) { if (methodInfo.GetCustomAttribute() case .Ok(let attribute)) { String functionType = scope $"{methodInfo.ReturnType}({methodInfo.GetParamsDecl(.. scope .())})"; String line = scope $"RegisterCall(\"{attribute.MethodName}\", => {methodInfo.Name});\n"; Compiler.EmitMethodEntry(method, line); } } } } public static void Init() { RegisterCalls(); RegisterMathFunctions(); } public static void RegisterManagedComponents() { s_AddComponentMethods.Clear(); s_HasComponentMethods.Clear(); s_RemoveComponentMethods.Clear(); RegisterComponent("GlitchyEngine.Core.Transform"); RegisterComponent("GlitchyEngine.Physics.Rigidbody2D"); } [RegisterMethod] private static void RegisterCalls() { // Generated at CompTime } private static void RegisterComponent(StringView cSharpClassName = "") where T : struct, new { String className; if (cSharpClassName.IsWhiteSpace) { className = scope:: $"GlitchyEngine."; typeof(T).GetName(className); } else { className = scope:: String(cSharpClassName); } className.EnsureNullTerminator(); MonoType* managedType = Mono.mono_reflection_type_from_name(className.CStr(), ScriptEngine.[Friend]s_CoreAssemblyImage); if (managedType != null) { s_AddComponentMethods[managedType] = (entity) => entity.AddComponent(); s_HasComponentMethods[managedType] = (entity) => entity.HasComponent(); s_RemoveComponentMethods[managedType] = (entity) => entity.RemoveComponent(); } else { Log.EngineLogger.AssertDebug(managedType != null, scope $"No C# component with name \"{className}\" found for Beef type \"{typeof(T)}\""); } } [RegisterCall("Log::LogMessage_Impl")] static void Log(int32 logLevel, MonoString* message) { char8* utfMessage = Mono.mono_string_to_utf8(message); Log.ClientLogger.Log((LogLevel)logLevel, StringView(utfMessage)); Mono.mono_free(utfMessage); } #region Input [RegisterCall("Input::IsKeyPressed")] static bool Input_IsKeyPressed(Key key) => Input.IsKeyPressed(key); [RegisterCall("Input::IsKeyReleased")] static bool Input_IsKeyReleased(Key key) => Input.IsKeyReleased(key); [RegisterCall("Input::IsKeyToggled")] static bool Input_IsKeyToggled(Key key) => Input.IsKeyToggled(key); [RegisterCall("Input::IsKeyPressing")] static bool Input_IsKeyPressing(Key key) => Input.IsKeyPressing(key); [RegisterCall("Input::IsKeyReleasing")] static bool Input_IsKeyReleasing(Key key) => Input.IsKeyReleasing(key); [RegisterCall("Input::IsMouseButtonPressed")] static bool Input_IsMouseButtonPressed(MouseButton mouseButton) => Input.IsMouseButtonPressed(mouseButton); [RegisterCall("Input::IsMouseButtonReleased")] static bool Input_IsMouseButtonReleased(MouseButton mouseButton) => Input.IsMouseButtonReleased(mouseButton); [RegisterCall("Input::IsMouseButtonPressing")] static bool Input_IsMouseButtonPressing(MouseButton mouseButton) => Input.IsMouseButtonPressing(mouseButton); [RegisterCall("Input::IsMouseButtonReleasing")] static bool Input_IsMouseButtonReleasing(MouseButton mouseButton) => Input.IsMouseButtonReleasing(mouseButton); #endregion Input #region Scene/Entity stuff [RegisterCall("ScriptGlue::Entity_Create")] static void Entity_Create(MonoObject* scriptInstance, MonoString* monoEntityName, MonoArray* componentTypes, out UUID entityId) { char8* entityName = Mono.mono_string_to_utf8(monoEntityName); Entity entity = ScriptEngine.Context.CreateEntity(StringView(entityName)); Mono.mono_free(entityName); entityId = entity.UUID; // TODO: Script instance if (componentTypes != null) { Entity_AddComponents(entityId, componentTypes); } } [RegisterCall("ScriptGlue::Entity_Destroy")] static void Entity_Destroy(UUID entityId) { Entity entity = ScriptEngine.Context.GetEntityByID(entityId); ScriptEngine.Context.DestroyEntityDeferred(entity); } [RegisterCall("ScriptGlue::Entity_CreateInstance")] static void Entity_CreateInstance(UUID entityId, out UUID newEntityId) { Entity entity = ScriptEngine.Context.GetEntityByID(entityId); Entity newEntity = ScriptEngine.Context.CreateInstance(entity); newEntityId = newEntity.UUID; } [RegisterCall("ScriptGlue::Entity_AddComponents")] static void Entity_AddComponents(UUID entityId, MonoArray* componentTypes) { if (componentTypes == null) return; uint length = Mono.mono_array_length(componentTypes); for (uint i < length) { MonoReflectionType* reflectionType = Mono.mono_array_get(componentTypes, i); Entity_AddComponent(entityId, reflectionType); } } [RegisterCall("ScriptGlue::Entity_AddComponent")] static void Entity_AddComponent(UUID entityId, MonoReflectionType* componentType) { MonoType* type = Mono.mono_reflection_type_get_type(componentType); Entity entity = ScriptEngine.Context.GetEntityByID(entityId); if (s_AddComponentMethods.TryGetValue(type, let addMethod)) addMethod(entity); else Log.EngineLogger.AssertDebug(false, "No managed component with the given type registered."); } [RegisterCall("ScriptGlue::Entity_HasComponent")] static bool Entity_HasComponent(UUID entityId, MonoReflectionType* componentType) { MonoType* type = Mono.mono_reflection_type_get_type(componentType); Entity entity = ScriptEngine.Context.GetEntityByID(entityId); if (s_HasComponentMethods.TryGetValue(type, let hasMethod)) return hasMethod(entity); Log.EngineLogger.AssertDebug(false, "No managed component with the given type registered."); return false; } [RegisterCall("ScriptGlue::Entity_RemoveComponent")] static void Entity_RemoveComponent(UUID entityId, MonoReflectionType* componentType) { MonoType* type = Mono.mono_reflection_type_get_type(componentType); Entity entity = ScriptEngine.Context.GetEntityByID(entityId); if (s_RemoveComponentMethods.TryGetValue(type, let removeMethod)) removeMethod(entity); else Log.EngineLogger.AssertDebug(false, "No managed component with the given type registered."); } [RegisterCall("ScriptGlue::Entity_FindEntityWithName")] static void Entity_FindEntityWithName(MonoString* monoName, UUID* outUuid) { *outUuid = UUID(0); char8* entityName = Mono.mono_string_to_utf8(monoName); StringView nameString = StringView(entityName); Result entityResult = ScriptEngine.Context.GetEntityByName(nameString); Mono.mono_free(entityName); if (entityResult case .Ok(let entity)) *outUuid = entity.UUID; } [RegisterCall("ScriptGlue::Entity_GetScriptInstance")] static void Entity_GetScriptInstance(UUID entityId, out MonoObject* instance) { instance = ScriptEngine.GetManagedInstance(entityId); } [RegisterCall("ScriptGlue::Entity_SetScript")] static MonoObject* Entity_SetScript(UUID entityId, MonoReflectionType* scriptType) { Entity entity = ScriptEngine.Context.GetEntityByID(entityId); if (!entity.HasComponent()) { entity.AddComponent(); } if (entity.TryGetComponent(let scriptComponent)) { scriptComponent.Instance = null; MonoType* type = Mono.mono_reflection_type_get_type(scriptType); scriptComponent.ScriptClassName = StringView(Mono.mono_type_full_name(type)); // Initializes the created instance // TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think. ScriptEngine.InitializeInstance(entity, scriptComponent); return scriptComponent.Instance.MonoInstance; } return null; } [RegisterCall("ScriptGlue::Entity_RemoveScript")] static void Entity_RemoveScript(UUID entityId) { Entity entity = ScriptEngine.Context.GetEntityByID(entityId); if (entity.TryGetComponent(let scriptComponent)) { ScriptEngine.DestroyInstance(entity, scriptComponent); entity.RemoveComponent(); } } #endregion #region TransformComponent [RegisterCall("ScriptGlue::Transform_GetTranslation")] static void Transform_GetTranslation(UUID entityId, out float3 translation) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); translation = entity.Transform.Position; } [RegisterCall("ScriptGlue::Transform_SetTranslation")] static void Transform_SetTranslation(UUID entityId, in float3 translation) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); entity.Transform.Position = translation; // if necessary reposition Rigidbody2D if (entity.TryGetComponent(let rigidbody2D)) { rigidbody2D.SetPosition(translation.XY); } } #endregion TransformComponent #region Rigidbody2D [RegisterCall("ScriptGlue::Rigidbody2D_ApplyForce")] static void Rigidbody2D_ApplyForce(UUID entityId, in float2 force, in float2 point, bool wakeUp) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); Box2D.Body.ApplyForce(rigidBody.[Friend]RuntimeBody, force, point, wakeUp); } [RegisterCall("ScriptGlue::Rigidbody2D_ApplyForceToCenter")] static void Rigidbody2D_ApplyForceToCenter(UUID entityId, in float2 force, bool wakeUp) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); Box2D.Body.ApplyForceToCenter(rigidBody.[Friend]RuntimeBody, force, wakeUp); } [RegisterCall("ScriptGlue::Rigidbody2D_SetPosition")] static void Rigidbody2D_SetPosition(UUID entityId, in float2 position) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); rigidBody.SetPosition(position); } [RegisterCall("ScriptGlue::Rigidbody2D_GetPosition")] static void Rigidbody2D_GetPosition(UUID entityId, out float2 position) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); position = rigidBody.GetPosition(); } [RegisterCall("ScriptGlue::Rigidbody2D_SetRotation")] static void Rigidbody2D_SetRotation(UUID entityId, in float rotation) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); rigidBody.SetAngle(rotation); } [RegisterCall("ScriptGlue::Rigidbody2D_GetRotation")] static void Rigidbody2D_GetRotation(UUID entityId, out float rotation) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); rotation = rigidBody.GetAngle(); } [RegisterCall("ScriptGlue::Rigidbody2D_GetLinearVelocity")] static void Rigidbody2D_GetLinearVelocity(UUID entityId, out float2 velocity) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); velocity = rigidBody.GetLinearVelocity(); } [RegisterCall("ScriptGlue::Rigidbody2D_SetLinearVelocity")] static void Rigidbody2D_SetLinearVelocity(UUID entityId, in float2 velocity) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); rigidBody.SetLinearVelocity(velocity); } [RegisterCall("ScriptGlue::Rigidbody2D_GetAngularVelocity")] static void Rigidbody2D_GetAngularVelocity(UUID entityId, out float velocity) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); velocity = rigidBody.GetAngularVelocity(); } [RegisterCall("ScriptGlue::Rigidbody2D_SetAngularVelocity")] static void Rigidbody2D_SetAngularVelocity(UUID entityId, in float velocity) { Scene scene = ScriptEngine.Context; Entity entity = scene.GetEntityByID(entityId); var rigidBody = entity.GetComponent(); rigidBody.SetAngularVelocity(velocity); } #endregion Rigidbody2D #region Physics2D // TODO: We need a wrapper class! [RegisterCall("ScriptGlue::Physics2D_GetGravity")] static void Physics2D_GetGravity(out float2 gravity) { Scene scene = ScriptEngine.Context; var box2DGravity = Box2D.World.GetGravity(scene.[Friend]_physicsWorld2D); gravity = *(float2*)&box2DGravity; } [RegisterCall("ScriptGlue::Physics2D_SetGravity")] static void Physics2D_SetGravity(in float2 gravity) { Scene scene = ScriptEngine.Context; #unwarn Box2D.World.SetGravity(scene.[Friend]_physicsWorld2D, gravity); } #endregion Physics2D #region Math private static void RegisterMathFunctions() { RegisterCall("ScriptGlue::modf_float", => GlitchyEngine.Math.modf); RegisterCall("ScriptGlue::modf_float2", => GlitchyEngine.Math.modf); RegisterCall("ScriptGlue::modf_float3", => GlitchyEngine.Math.modf); RegisterCall("ScriptGlue::modf_float4", => GlitchyEngine.Math.modf); RegisterHalfFunctions(); } private static void RegisterHalfFunctions() { RegisterCall("Math.MyHalf::FromFloat32", (value, halfValue) => halfValue = GlitchyEngine.Math.half.FromFloat32(value)); RegisterCall("Math.MyHalf::ToFloat32", (value, floatValue) => floatValue = GlitchyEngine.Math.half.ToFloat32(value)); RegisterCall("Math.MyHalf::LessThan_Impl", (left, right, result) => result = left < right); RegisterCall("Math.MyHalf::LessThanOrEqual_Impl", (left, right, result) => result = left <= right); RegisterCall("Math.MyHalf::GreaterThan_Impl", (left, right, result) => result = left > right); RegisterCall("Math.MyHalf::GreaterThanOrEqual_Impl", (left, right, result) => result = left >= right); RegisterCall("Math.MyHalf::Add_Impl", (left, right, result) => result = left + right); RegisterCall("Math.MyHalf::Subtract_Impl", (left, right, result) => result = left - right); RegisterCall("Math.MyHalf::Multiply_Impl", (left, right, result) => result = left * right); RegisterCall("Math.MyHalf::Divide_Impl", (left, right, result) => result = left / right); RegisterCall("Math.MyHalf::Modulo_Impl", (left, right, result) => result = left % right); RegisterCall("Math.MyHalf::Negate_Impl", (value, result) => result = -value); RegisterCall("Math.MyHalf::Increment_Impl", (value, result) => result = ++value); RegisterCall("Math.MyHalf::Decrement_Impl", (value, result) => result = --value); RegisterCall("Math.MyHalf::IsNegative_Impl", (value) => value.IsNegative); RegisterCall("Math.MyHalf::IsFinite_Impl", (value) => value.IsFinite); RegisterCall("Math.MyHalf::IsInfinity_Impl", (value) => value.IsInfinity); RegisterCall("Math.MyHalf::IsNan_Impl", (value) => value.IsNaN); RegisterCall("Math.MyHalf::IsSubnormal_Impl", (value) => value.IsSubnormal); } #endregion [RegisterCall("ScriptGlue::UUID_CreateNew")] static void UUID_Create(out UUID id) { id = UUID.Create(); } #region Application [RegisterCall("ScriptGlue::Application_IsEditor")] static bool Application_IsEditor() { return ScriptEngine.ApplicationInfo.IsEditor; } [RegisterCall("ScriptGlue::Application_IsPlayer")] static bool Application_IsPlayer() { return ScriptEngine.ApplicationInfo.IsPlayer; } [RegisterCall("ScriptGlue::Application_IsInEditMode")] static bool Application_IsInEditMode() { return ScriptEngine.ApplicationInfo.IsInEditMode; } [RegisterCall("ScriptGlue::Application_IsInPlayMode")] static bool Application_IsInPlayMode() { return ScriptEngine.ApplicationInfo.IsInPlayMode; } #endregion private static void RegisterCall(String name, T method) where T : var { Mono.mono_add_internal_call(scope $"GlitchyEngine.{name}", (void*)method); } }