Use Attributes for JS<->Beef binding

This commit is contained in:
Simon Lübeß
2024-11-17 19:09:52 +01:00
parent 28c2922b89
commit 080861ed4a
4 changed files with 169 additions and 73 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
FileVersion = 1 FileVersion = 1
Projects = {GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}, ScriptCore = {Path = "ScriptCore"}, Ultralight = {Path = "GlitchyEngine/vendor/Ultralight-beef"}, EditorUI = {Path = "GlitchyEditor/EditorUI"}} Projects = {GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}, box2d-beef = {Path = "GlitchyEngine/vendor/box2D"}, "Beef.Linq" = {Path = "GlitchyEngine/vendor/Beef.Linq/src"}, ScriptCore = {Path = "ScriptCore"}, Ultralight = {Path = "GlitchyEngine/vendor/Ultralight-beef"}, EditorUI = {Path = "GlitchyEditor/EditorUI"}}
WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"]} WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper", "ScriptCore"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon", "box2d-beef", "Beef.Linq"], EditorDependencies = ["Ultralight"]}
[Workspace] [Workspace]
StartupProject = "GlitchyEditor" StartupProject = "GlitchyEditor"
@@ -0,0 +1,53 @@
using System;
using System.Reflection;
using System.Collections;
namespace GlitchyEditor.Ultralight;
[AttributeUsage(.Method |.Field)]
struct BindToJsFunctionAttribute : Attribute
{
public String FunctionName;
public this(String functionName)
{
FunctionName = functionName;
}
}
[AttributeUsage(.Method)]
struct BeefMethodBinderAttribute : Attribute, IOnMethodInit
{
[Comptime]
public void OnMethodInit(MethodInfo targetMethod, Self* prev)
{
HashSet<String> boundJsFunctions = new HashSet<String>();
for (MethodInfo methodInfo in targetMethod.DeclaringType.GetMethods(.Instance | .Public | .NonPublic))
{
if (methodInfo.GetCustomAttribute<BindToJsFunctionAttribute>() case .Ok(let attribute))
{
Runtime.Assert(!boundJsFunctions.Contains(attribute.FunctionName), new $"The JS-Method {attribute.FunctionName} is already bound to another method.");
boundJsFunctions.Add(attribute.FunctionName);
String str = new $"BindMethodToJsFunction(context, scriptGlue, \"{attribute.FunctionName}\", new:stdAlloc => {methodInfo.Name});\n";
Compiler.EmitMethodExit(targetMethod, str);
}
}
for (FieldInfo fieldInfo in targetMethod.DeclaringType.GetFields(.Instance | .Public | .NonPublic))
{
if (fieldInfo.GetCustomAttribute<BindToJsFunctionAttribute>() case .Ok(let attribute))
{
// Runtime.Assert(t.IsDelegate, new $"The field must be of delegate type.");
Runtime.Assert(!boundJsFunctions.Contains(attribute.FunctionName), new $"The JS-Method {attribute.FunctionName} is already bound to another method.");
boundJsFunctions.Add(attribute.FunctionName);
String str = new $"{fieldInfo.Name} = GetJsCallbackDelegate(context, scriptGlue, \"{attribute.FunctionName}\");\n";
Compiler.EmitMethodExit(targetMethod, str);
}
}
}
}
@@ -59,9 +59,9 @@ class UltralightMainWindow : UltralightWindow
private void UpdateEntityHierarchy() private void UpdateEntityHierarchy()
{ {
if (DoStuffFunction == null) if (uiCallUpdateEntities == null)
{ {
Log.EngineLogger.Error($"{nameof(DoStuffFunction)} is null, skipping entity hierarchy update."); Log.EngineLogger.Error($"{nameof(uiCallUpdateEntities)} is null, skipping entity hierarchy update.");
return; return;
} }
@@ -248,7 +248,7 @@ class UltralightMainWindow : UltralightWindow
} }
JSValueRef exception = null; JSValueRef exception = null;
DoStuffFunction(context, Span<JSValueRef>(&jsArray, 1), &exception); uiCallUpdateEntities(context, Span<JSValueRef>(&jsArray, 1), &exception);
if (exception != null) if (exception != null)
{ {
@@ -263,8 +263,15 @@ class UltralightMainWindow : UltralightWindow
} }
} }
[BindToJsFunction("requestEntityHierarchyUpdate")]
void HandleRequestEntityHierarchyUpdate(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{
_forceEntityHierarchyRebuild = true;
}
private bool _hoveringNonClientArea; private bool _hoveringNonClientArea;
[BindToJsFunction("handleHoverNonClientArea")]
void HandleHoverNonClientArea(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null) void HandleHoverNonClientArea(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{ {
Log.EngineLogger.Info("HandleHoverNonClientArea"); Log.EngineLogger.Info("HandleHoverNonClientArea");
@@ -290,6 +297,24 @@ class UltralightMainWindow : UltralightWindow
Log.ClientLogger.Info($"_hoveringNonClientArea: {_hoveringNonClientArea}"); Log.ClientLogger.Info($"_hoveringNonClientArea: {_hoveringNonClientArea}");
} }
[BindToJsFunction("handleHoverMaximizeWindow")]
void HandleHoverMaximizeButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{
HandleHoverTitlebarButton(.Maximize, context, thisObject, arguments, exception);
}
[BindToJsFunction("handleHoverMinimizeWindow")]
void HandleHoverMinimizeButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{
HandleHoverTitlebarButton(.Minimize, context, thisObject, arguments, exception);
}
[BindToJsFunction("handleHoverCloseWindow")]
void HandleHoverCloseButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{
HandleHoverTitlebarButton(.Close, context, thisObject, arguments, exception);
}
void HandleHoverTitlebarButton(TitleBarButton button, JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null) void HandleHoverTitlebarButton(TitleBarButton button, JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{ {
if (arguments.Length != 1) if (arguments.Length != 1)
@@ -309,82 +334,30 @@ class UltralightMainWindow : UltralightWindow
EditorLayer.HoveredTitleBarButton = _window.[Friend]_hoveredTitleBarButton; EditorLayer.HoveredTitleBarButton = _window.[Friend]_hoveredTitleBarButton;
} }
private void RegisterBeefFunction(JSContextRef context, JSObjectRef object, StringView functionName, JSCallback callback) [BindToJsFunction("handleClickMaximizeWindow")]
void HandleClickMaximizeButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{ {
JSObjectRef func = UltralightHelper.CreateJsFunctionFromDelegate(context, callback); _window.ToggleMaximize();
JSStringRef name = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
JSObjectRef exception;
JSObjectSetProperty(context, object, name, func, 0, &exception);
JSStringRelease(name);
} }
private JsFunctionCall DoStuffFunction; [BindToJsFunction("handleClickMinimizeWindow")]
void HandleClickMinimizeButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
delegate JSValueRef JsFunctionCall(JSContextRef context, Span<JSValueRef> arguments, JSValueRef* exception);
private JsFunctionCall GetJsCallbackDelegate(JSContextRef context, JSObjectRef object, StringView functionName)
{ {
JSStringRef functionNameString = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!()); _window.Minimize();
defer JSStringRelease(functionNameString);
JSValueRef func = JSObjectGetProperty(context, object, functionNameString, null);
if (!JSValueIsObject(context, func))
{
return null;
}
JSObjectRef functionObject = JSValueToObject(context, func, null);
if (functionObject == null || !JSObjectIsFunction(context, functionObject))
{
return null;
}
return new (c, args, ex) => {
return JSObjectCallAsFunction(c, functionObject, object, (uint32)args.Length, args.Ptr, ex);
};
} }
private void RegisterEngineGlueFunctions() [BindToJsFunction("handleClickCloseWindow")]
void HandleClickCloseButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
{ {
JSContextRef context = ulViewLockJSContext(_view); _window.Close();
defer ulViewUnlockJSContext(_view);
JSObjectRef globalObject = JSContextGetGlobalObject(context);
JSValueRef exception = null;
JSStringRef scriptGlueName = JSStringCreateWithUTF8CString("EngineGlue");
JSValueRef scriptGlue = JSObjectGetProperty(context, globalObject, scriptGlueName, &exception);
JSStringRelease(scriptGlueName);
if (JSValueGetType(context, scriptGlue) == .kJSTypeUndefined)
{
Log.EngineLogger.Error("Failed to get EngineGlue object from JS context.");
return;
}
StdAllocator stdAlloc = StdAllocator();
RegisterBeefFunction(context, scriptGlue, "handleHoverNonClientArea", new:stdAlloc => HandleHoverNonClientArea);
RegisterBeefFunction(context, scriptGlue, "handleHoverMaximizeWindow", new:stdAlloc (c, t, a, e) => HandleHoverTitlebarButton(.Maximize, c, t, a, e));
RegisterBeefFunction(context, scriptGlue, "handleHoverMinimizeWindow", new:stdAlloc (c, t, a, e) => HandleHoverTitlebarButton(.Minimize, c, t, a, e));
RegisterBeefFunction(context, scriptGlue, "handleHoverCloseWindow", new:stdAlloc (c, t, a, e) => HandleHoverTitlebarButton(.Close, c, t, a, e));
RegisterBeefFunction(context, scriptGlue, "handleClickMaximizeWindow", new:stdAlloc (c, t, a, e) => { _window.ToggleMaximize(); });
RegisterBeefFunction(context, scriptGlue, "handleClickMinimizeWindow", new:stdAlloc (c, t, a, e) => { _window.Minimize(); });
RegisterBeefFunction(context, scriptGlue, "handleClickCloseWindow", new:stdAlloc (c, t, a, e) => { _window.Close(); });
RegisterBeefFunction(context, scriptGlue, "requestEntityHierarchyUpdate", new:stdAlloc (c, t, a, e) => { _forceEntityHierarchyRebuild = true; });
DoStuffFunction = GetJsCallbackDelegate(context, scriptGlue, "callFromEngine_updateEntities");
} }
protected override void OnDOMReady(C_View* caller, uint64 frame_id, bool is_main_frame, C_String* url) [BindToJsFunction("callFromEngine_updateEntities")]
{ private JsFunctionCall uiCallUpdateEntities ~ delete _;
RegisterEngineGlueFunctions();
[BeefMethodBinder]
protected override void BindBeefMethodsToJsFunctions(OpaqueJSContext* context, OpaqueJSValue* scriptGlue, StdAllocator stdAlloc)
{
base.BindBeefMethodsToJsFunctions(context, scriptGlue, stdAlloc);
} }
} }
@@ -150,6 +150,73 @@ abstract class UltralightWindow
_texture = newTexture; _texture = newTexture;
} }
protected delegate JSValueRef JsFunctionCall(JSContextRef context, Span<JSValueRef> arguments, JSValueRef* exception);
protected JsFunctionCall GetJsCallbackDelegate(JSContextRef context, JSObjectRef object, StringView functionName)
{
JSStringRef functionNameString = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
defer JSStringRelease(functionNameString);
JSValueRef func = JSObjectGetProperty(context, object, functionNameString, null);
if (!JSValueIsObject(context, func))
{
return null;
}
JSObjectRef functionObject = JSValueToObject(context, func, null);
if (functionObject == null || !JSObjectIsFunction(context, functionObject))
{
return null;
}
return new (c, args, ex) => {
return JSObjectCallAsFunction(c, functionObject, object, (uint32)args.Length, args.Ptr, ex);
};
}
protected void BindMethodToJsFunction(JSContextRef context, JSObjectRef object, StringView functionName, JSCallback callback)
{
JSObjectRef func = UltralightHelper.CreateJsFunctionFromDelegate(context, callback);
JSStringRef name = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
JSObjectRef exception;
JSObjectSetProperty(context, object, name, func, 0, &exception);
JSStringRelease(name);
}
//[BeefMethodBinder]
protected virtual void BindBeefMethodsToJsFunctions(JSContextRef context, JSValueRef scriptGlue, StdAllocator stdAlloc)
{
}
private void RegisterEngineGlueFunctions()
{
JSContextRef context = ulViewLockJSContext(_view);
defer ulViewUnlockJSContext(_view);
JSObjectRef globalObject = JSContextGetGlobalObject(context);
JSValueRef exception = null;
JSStringRef scriptGlueName = JSStringCreateWithUTF8CString("EngineGlue");
JSValueRef scriptGlue = JSObjectGetProperty(context, globalObject, scriptGlueName, &exception);
JSStringRelease(scriptGlueName);
if (JSValueGetType(context, scriptGlue) == .kJSTypeUndefined)
{
Log.EngineLogger.Error("Failed to get EngineGlue object from JS context.");
return;
}
StdAllocator stdAlloc = StdAllocator();
BindBeefMethodsToJsFunctions(context, scriptGlue, stdAlloc);
//uiCallUpdateEntities = GetJsCallbackDelegate(context, scriptGlue, "callFromEngine_updateEntities");
}
#region Events #region Events
private void EventHandler(Event e) private void EventHandler(Event e)
@@ -287,7 +354,10 @@ abstract class UltralightWindow
window.OnDOMReady(caller, frame_id, is_main_frame, url); window.OnDOMReady(caller, frame_id, is_main_frame, url);
} }
protected virtual void OnDOMReady(ULView caller, uint64 frame_id, bool is_main_frame, ULString url) { } protected virtual void OnDOMReady(ULView caller, uint64 frame_id, bool is_main_frame, ULString url)
{
RegisterEngineGlueFunctions();
}
private static void OnFailedLoading(void* user_data, C_View* caller, uint64 frame_id, bool is_main_frame, C_String* url, C_String* description, C_String* error_domain, int32 error_code) private static void OnFailedLoading(void* user_data, C_View* caller, uint64 frame_id, bool is_main_frame, C_String* url, C_String* description, C_String* error_domain, int32 error_code)
{ {