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
|
||||
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]
|
||||
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()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ class UltralightMainWindow : UltralightWindow
|
||||
}
|
||||
|
||||
JSValueRef exception = null;
|
||||
DoStuffFunction(context, Span<JSValueRef>(&jsArray, 1), &exception);
|
||||
uiCallUpdateEntities(context, Span<JSValueRef>(&jsArray, 1), &exception);
|
||||
|
||||
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;
|
||||
|
||||
[BindToJsFunction("handleHoverNonClientArea")]
|
||||
void HandleHoverNonClientArea(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
|
||||
{
|
||||
Log.EngineLogger.Info("HandleHoverNonClientArea");
|
||||
@@ -290,6 +297,24 @@ class UltralightMainWindow : UltralightWindow
|
||||
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)
|
||||
{
|
||||
if (arguments.Length != 1)
|
||||
@@ -309,82 +334,30 @@ class UltralightMainWindow : UltralightWindow
|
||||
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);
|
||||
|
||||
JSStringRef name = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
|
||||
|
||||
JSObjectRef exception;
|
||||
JSObjectSetProperty(context, object, name, func, 0, &exception);
|
||||
|
||||
JSStringRelease(name);
|
||||
_window.ToggleMaximize();
|
||||
}
|
||||
|
||||
private JsFunctionCall DoStuffFunction;
|
||||
|
||||
delegate JSValueRef JsFunctionCall(JSContextRef context, Span<JSValueRef> arguments, JSValueRef* exception);
|
||||
|
||||
private JsFunctionCall GetJsCallbackDelegate(JSContextRef context, JSObjectRef object, StringView functionName)
|
||||
[BindToJsFunction("handleClickMinimizeWindow")]
|
||||
void HandleClickMinimizeButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
|
||||
{
|
||||
JSStringRef functionNameString = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
|
||||
defer JSStringRelease(functionNameString);
|
||||
_window.Minimize();
|
||||
}
|
||||
|
||||
JSValueRef func = JSObjectGetProperty(context, object, functionNameString, null);
|
||||
|
||||
if (!JSValueIsObject(context, func))
|
||||
[BindToJsFunction("handleClickCloseWindow")]
|
||||
void HandleClickCloseButton(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
|
||||
{
|
||||
return null;
|
||||
_window.Close();
|
||||
}
|
||||
|
||||
JSObjectRef functionObject = JSValueToObject(context, func, null);
|
||||
[BindToJsFunction("callFromEngine_updateEntities")]
|
||||
private JsFunctionCall uiCallUpdateEntities ~ delete _;
|
||||
|
||||
if (functionObject == null || !JSObjectIsFunction(context, functionObject))
|
||||
[BeefMethodBinder]
|
||||
protected override void BindBeefMethodsToJsFunctions(OpaqueJSContext* context, OpaqueJSValue* scriptGlue, StdAllocator stdAlloc)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new (c, args, ex) => {
|
||||
return JSObjectCallAsFunction(c, functionObject, object, (uint32)args.Length, args.Ptr, ex);
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
base.BindBeefMethodsToJsFunctions(context, scriptGlue, stdAlloc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,73 @@ abstract class UltralightWindow
|
||||
_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
|
||||
|
||||
private void EventHandler(Event e)
|
||||
@@ -287,7 +354,10 @@ abstract class UltralightWindow
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user