mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Use Attributes for JS<->Beef binding
This commit is contained in:
+1
-1
@@ -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);
|
[BindToJsFunction("handleClickCloseWindow")]
|
||||||
|
void HandleClickCloseButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
|
||||||
if (functionObject == null || !JSObjectIsFunction(context, functionObject))
|
|
||||||
{
|
{
|
||||||
return null;
|
_window.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new (c, args, ex) => {
|
[BindToJsFunction("callFromEngine_updateEntities")]
|
||||||
return JSObjectCallAsFunction(c, functionObject, object, (uint32)args.Length, args.Ptr, ex);
|
private JsFunctionCall uiCallUpdateEntities ~ delete _;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterEngineGlueFunctions()
|
[BeefMethodBinder]
|
||||||
|
protected override void BindBeefMethodsToJsFunctions(OpaqueJSContext* context, OpaqueJSValue* scriptGlue, StdAllocator stdAlloc)
|
||||||
{
|
{
|
||||||
JSContextRef context = ulViewLockJSContext(_view);
|
base.BindBeefMethodsToJsFunctions(context, scriptGlue, stdAlloc);
|
||||||
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)
|
|
||||||
{
|
|
||||||
RegisterEngineGlueFunctions();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user