Skip to content

Commit

Permalink
add task promise conversion for quickjs
Browse files Browse the repository at this point in the history
  • Loading branch information
KurtGokhan committed Feb 17, 2024
1 parent 7f2e867 commit 4a2ba6d
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 4 deletions.
89 changes: 89 additions & 0 deletions Runtime/Scripting/QuickJS/QuickJSApiBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using QuickJS;
using QuickJS.Experimental;
using QuickJS.Native;
Expand All @@ -20,6 +21,7 @@ public class QuickJSApiBridge : IJSApiBridge, IDisposable

private ScriptFunction createDictionaryProxy;
private ScriptFunction createListProxy;
private ScriptFunction createTaskProxy;

public JSPayloadHeader GetPayloadHeader(QScriptContext context, JSValue val)
{
Expand Down Expand Up @@ -68,6 +70,10 @@ public JSValue NewBridgeObject(QScriptContext context, object o, JSValue proto)
{
proxy = CreateListProxy(context, val);
}
else if (typeof(Task).IsAssignableFrom(o.GetType()) || typeof(ValueTask).IsAssignableFrom(o.GetType()))
{
proxy = CreateTaskProxy(context, val);
}

if (!proxy.IsUndefined())
{
Expand Down Expand Up @@ -112,6 +118,13 @@ private JSValue CreateListProxy(QScriptContext context, JSValue target)
return UseProxyCreator(context, target, creator);
}

private JSValue CreateTaskProxy(QScriptContext context, JSValue target)
{
var creator = createTaskProxy ??
(createTaskProxy = CreateTaskProxyCreator(context));
return UseProxyCreator(context, target, creator);
}

private static unsafe ScriptFunction CreateDictionaryProxyCreator(QScriptContext _context)
{
var ctx = (JSContext) _context;
Expand Down Expand Up @@ -425,12 +438,88 @@ object convertToListItemType(IList list, object value)
return proxy;
}

private static unsafe ScriptFunction CreateTaskProxyCreator(QScriptContext _context)
{
var ctx = (JSContext) _context;

var proxyCreator = _context.EvalSource<ScriptFunction>(@"
function createTaskProxyCreator (thenFn) {
return function createTaskProxy(targetProxy) {
let promise = null;
function asPromise() {
if(!promise) promise = new Promise((rs, rj) => thenFn(targetProxy, rs, rj));
return promise;
}
const res = new Proxy(targetProxy, {
get(target, key, receiver) {
if(key === '" + KeyForCSharpIdentity + @"') return target;
if(key === 'then') return function thenProxy (resolve, reject) {
return asPromise().then(resolve, reject);
};
if(key === 'catch') return function catchProxy (reject) {
return asPromise().catch(reject);
};
if(key === 'finally') return function finallyProxy (settle) {
return asPromise().finally(settle);
};
return target[key];
},
});
const originalPrototype = Object.getPrototypeOf(targetProxy);
const prototypeProxy = new Proxy(originalPrototype, {
get(target, key, receiver) {
if(key in target) return target[key];
return Promise.prototype[key];
}
});
Object.setPrototypeOf(res, prototypeProxy);
return res;
};
}
createTaskProxyCreator;
", "ReactUnity/quickjs/createTaskProxy");

var then = new Action<object, Action<object>, Action<object>>(
(obj, resolve, reject) => {
var task = obj is ValueTask vt ? vt.AsTask() : obj as Task;
var awaiter = task.GetAwaiter();
awaiter.OnCompleted(() => {
if (task.IsFaulted) reject(task.Exception.InnerException);
else
{
var result = task.GetType().GetProperty("Result")?.GetValue(task);
resolve(result);
}
});
});

var prs = new object[] { then };


var proxy = proxyCreator.Invoke<ScriptFunction>(prs);

proxyCreator.Dispose();

return proxy;
}

public void Dispose()
{
createDictionaryProxy?.Dispose();
createDictionaryProxy = null;
createListProxy?.Dispose();
createListProxy = null;
createTaskProxy?.Dispose();
createTaskProxy = null;
}
}
}
Expand Down
46 changes: 42 additions & 4 deletions Tests/Editor/Core/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public IEnumerator ArrayItemsCanBeAccessedNaturally()
[EditorInjectableTest(Script = @"
function App() { };
(async function() {
await Globals.task0;
Globals.task0Done = true;
})();
(async function() {
await Globals.task1;
Globals.task1Done = true;
Expand All @@ -193,43 +198,76 @@ public IEnumerator ArrayItemsCanBeAccessedNaturally()
Globals.task3Done = true;
Assert.True(failed);
})();
(async function() {
const res = await Globals.task4;
Assert.AreEqual(7, res);
Globals.task4Done = true;
})();
", AutoRender = false)]
[Ignore("Not implemented")]
public IEnumerator TasksCanBeUsedNaturally()
{
IgnoreForEngine(JavascriptEngineType.Jint);

var t0 = Task.CompletedTask;
var t1 = new TaskCompletionSource<int>();
var t2 = new TaskCompletionSource<int>();
var t3 = new TaskCompletionSource<int>();
var t4 = new TaskCompletionSource<int>();
var valueTask = new ValueTask(t4.Task);

Globals["task0"] = t0;
Globals["task1"] = t1.Task;
Globals["task2"] = t2.Task;
Globals["task3"] = t3.Task;
Globals["task4"] = valueTask;

Globals["task0Done"] = false;
Globals["task1Done"] = false;
Globals["task2Done"] = false;
Globals["task3Done"] = false;
Globals["task4Done"] = false;

Render();

yield return null;
yield return null;

Assert.True((bool) Globals["task0Done"]);
Assert.False((bool) Globals["task1Done"]);
Assert.False((bool) Globals["task2Done"]);
Assert.False((bool) Globals["task3Done"]);
Assert.False((bool) Globals["task4Done"]);

t1.SetResult(0);
t1.Task.Wait();
yield return null;
Assert.True((bool) Globals["task1Done"]);


t2.SetResult(3);
t2.Task.Wait();
yield return null;
Assert.True((bool) Globals["task2Done"]);


t3.SetException(new Exception("fall"));
yield return null;
Assert.True((bool) Globals["task3Done"]);
if (EngineType != JavascriptEngineType.ClearScript)
{
// Doesn't work for ClearScript for some reason

t3.SetException(new Exception("fall"));
try { t3.Task.Wait(); } catch { }
yield return null;
Assert.True((bool) Globals["task3Done"]);


// Value tasks aren't supported in ClearScript

t4.SetResult(7);
t4.Task.Wait();
yield return null;
Assert.True((bool) Globals["task4Done"]);
}
}
}
}

0 comments on commit 4a2ba6d

Please sign in to comment.