Skip to content

feat(webgl): native controller support & interop #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ crashlytics-build.properties
.DS_Store

# builds
dojo-webgl
dojo-ios
dojo-macos
dojo-windows
webgl
/dojo-webgl
/dojo-ios
/dojo-macos
/dojo-windows
/webgl
48 changes: 48 additions & 0 deletions Assets/Dojo/Plugins/WebGL/controller.jslib
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
mergeInto(LibraryManager.library, {
NewController: function (rpcUrl, chainId, policies) {
const rpcUrl = UTF8ToString(rpcUrl);
const chainId = UTF8ToString(chainId);
const policies = JSON.parse(UTF8ToString(policies));

const opts = {
chains: [
{ rpcUrl }
],
defaultChainId: chainId,
policies: policies.reduce((acc, policy) => {
if (!acc[policy.target]) {
// If target doesn't exist, initialize it
acc[policy.target] = {
methods: [],
description: policy.description // Use description from the first policy encountered
};
}
// Append methods from the current policy to the target's methods array
acc[policy.target].methods.push(policy.method);
return acc;
}, {})
}

window.controllerProvider = new ControllerProvider(opts)
},
Probe: function (cb) {
window.controllerProvider.probe().then((account) => {
dynCall_vi(cb, account ? true : false)
})
},
Connect: function (cb) {
window.controllerProvider.connect().then((account) => {
dynCall_vi(cb, account ? true : false)
})
},
Disconnect: function (cb) {
window.controllerProvider.disconnect().then(() => {
dynCall_vi(cb)
})
},
Execute: function (cb, calls) {
window.controllerProvider.account.execute(JSON.parse(UTF8ToString(calls))).then((result) => {
dynCall_vi(cb, result.transaction_hash)
})
}
});
35 changes: 31 additions & 4 deletions Assets/Dojo/Runtime/Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace Dojo
{

[Serializable]
public struct Policy
{
public FieldElement target;
Expand All @@ -27,6 +29,29 @@ public dojo.Policy ToNative()
}
}

[Serializable]
public struct ControllerCall {
public FieldElement contractAddress;
public string entrypoint;
public FieldElement[] calldata;

public ControllerCall(FieldElement contractAddress, string entrypoint, FieldElement[] calldata)
{
this.contractAddress = contractAddress;
this.entrypoint = entrypoint;
this.calldata = calldata;
}

public dojo.Call ToNative()
{
return new dojo.Call {
to = contractAddress.Inner,
selector = entrypoint,
calldata = calldata.Select(c => c.Inner).ToArray()
};
}
}

public unsafe class Controller
{
private dojo.ControllerAccount* controller;
Expand Down Expand Up @@ -111,10 +136,11 @@ public static bool Clear(Policy[] policies, FieldElement chainId)
return result.ok;
}

public FieldElement ExecuteRaw(dojo.Call[] calls)
public FieldElement ExecuteRaw(ControllerCall[] calls)
{
var nativeCalls = calls.Select(c => c.ToNative()).ToArray();
dojo.Call* callsPtr;
fixed (dojo.Call* ptr = &calls[0])
fixed (dojo.Call* ptr = &nativeCalls[0])
{
callsPtr = ptr;
}
Expand All @@ -128,10 +154,11 @@ public FieldElement ExecuteRaw(dojo.Call[] calls)
return new FieldElement(result.ok);
}

public FieldElement ExecuteFromOutside(dojo.Call[] calls)
public FieldElement ExecuteFromOutside(ControllerCall[] calls)
{
var nativeCalls = calls.Select(c => c.ToNative()).ToArray();
dojo.Call* callsPtr;
fixed (dojo.Call* ptr = &calls[0])
fixed (dojo.Call* ptr = &nativeCalls[0])
{
callsPtr = ptr;
}
Expand Down
120 changes: 120 additions & 0 deletions Assets/Dojo/Runtime/ControllerInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#if UNITY_WEBGL && !UNITY_EDITOR

using System;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using AOT;
using bottlenoselabs.C2CS.Runtime;
using Newtonsoft.Json;
using UnityEngine;
using System.Linq;

namespace Dojo
{
public static class ControllerInterop
{
[DllImport("__Internal")]
private static extern void NewController(CString rpcUrl, CString chainId, CString policies);

[DllImport("__Internal")]
private static extern void Probe(Action<bool> cb);

private static class ProbeHelper
{
public static TaskCompletionSource<bool> Tcs;

[MonoPInvokeCallback(typeof(Action<bool>))]
public static void Callback(bool result)
{
Tcs.SetResult(result);
}
}

public static Task<bool> Probe()
{
ProbeHelper.Tcs = new TaskCompletionSource<bool>();
Probe(ProbeHelper.Callback);
return ProbeHelper.Tcs.Task;
}


[DllImport("__Internal")]
private static extern void Connect(Action<bool> cb);

private static class ConnectHelper
{
public static TaskCompletionSource<bool> Tcs;

[MonoPInvokeCallback(typeof(Action<bool>))]
public static void Callback(bool result)
{
Tcs.SetResult(result);
}
}

public static Task<bool> Connect()
{
ConnectHelper.Tcs = new TaskCompletionSource<bool>();
Connect(ConnectHelper.Callback);
return ConnectHelper.Tcs.Task;
}


[DllImport("__Internal")]
private static extern void Disconnect(Action cb);

private static class DisconnectHelper
{
public static TaskCompletionSource<bool> Tcs; // Using bool to signal completion

[MonoPInvokeCallback(typeof(Action))]
public static void Callback()
{
Tcs.SetResult(true); // Signal completion
}
}

public static Task Disconnect()
{
DisconnectHelper.Tcs = new TaskCompletionSource<bool>();
Disconnect(DisconnectHelper.Callback);
return DisconnectHelper.Tcs.Task;
}


[DllImport("__Internal")]
private static extern void Execute(Action<string> cb, CString calls);

private static class ExecuteHelper
{
public static TaskCompletionSource<string> Tcs;

[MonoPInvokeCallback(typeof(Action<string>))]
public static void Callback(string result)
{
// The result is the transaction hash from the JS side
Tcs.SetResult(result);
}
}


// Takes ControllerCall structs, serializes them into the expected JSON format
public static Task<string> Execute(ControllerCall[] calls)
{
ExecuteHelper.Tcs = new TaskCompletionSource<string>();
var serializedCalls = calls.Select(call => new SerializedCall(
new FieldElement(call.to),
call.selector,
// Ensure dojo.FieldElement can be converted to Dojo.Starknet.FieldElement if they differ
// Assuming direct conversion or access to underlying data works:
call.calldata.ToArray().Select(f => new FieldElement(f)).ToArray()
)).ToArray();

var jsonCalls = JsonConvert.SerializeObject(serializedCalls);
Execute(ExecuteHelper.Callback, new CString(jsonCalls));
return ExecuteHelper.Tcs.Task;
}
}
}

#endif // UNITY_WEBGL && !UNITY_EDITOR
Loading
Loading