Index: binaries/data/mods/_test.scriptinterface/promises/simple.js
===================================================================
--- /dev/null
+++ binaries/data/mods/_test.scriptinterface/promises/simple.js
@@ -0,0 +1,33 @@
+var test = 0;
+
+function incrementTest()
+{
+ test += 1;
+}
+
+async function waitAndIncrement(promise)
+{
+ await promise;
+ incrementTest();
+}
+
+function runTest()
+{
+ var rsv;
+ let prom = new Promise((resolve, reject) => {
+ incrementTest();
+ rsv = resolve;
+ });
+ waitAndIncrement(prom);
+ TS_ASSERT_EQUALS(test, 1);
+ rsv();
+ // At this point, waitAndIncrement is still not run, but is now free to run.
+ TS_ASSERT_EQUALS(test, 1);
+}
+
+runTest();
+
+function endTest()
+{
+ TS_ASSERT_EQUALS(test, 2);
+}
Index: binaries/data/mods/_test.scriptinterface/promises/threaded.js
===================================================================
--- /dev/null
+++ binaries/data/mods/_test.scriptinterface/promises/threaded.js
@@ -0,0 +1,13 @@
+var res = 0;
+
+async function runSquare() {
+ res = await Engine.AsyncSquare(5);
+}
+
+runSquare();
+// At this point the function is not run.
+TS_ASSERT_EQUALS(res, 0);
+
+function endTest() {
+ TS_ASSERT_EQUALS(res, 5 * 5);
+}
Index: source/scriptinterface/Promises.h
===================================================================
--- /dev/null
+++ source/scriptinterface/Promises.h
@@ -0,0 +1,96 @@
+/* Copyright (C) 2022 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_SCRIPTINTERFACE_JOBQUEUE
+#define INCLUDED_SCRIPTINTERFACE_JOBQUEUE
+
+#include "js/Promise.h"
+
+#include "scriptinterface/ScriptInterface.h"
+#include "scriptinterface/ScriptRequest.h"
+
+#include "ps/TaskManager.h"
+
+class ScriptInterface;
+
+namespace Script
+{
+/**
+ * Spidermonkey has to handle debugger interruptions to the job queue, which is a rather complex topic (see header).
+ * We aren't going to care about that, only queuing jobs & running them.
+ */
+class JobQueue : public JS::JobQueue {
+public:
+ JobQueue() {};
+
+ virtual ~JobQueue() = default;
+
+ JSObject* getIncumbentGlobal(JSContext* cx)
+ {
+ return JS::CurrentGlobalOrNull(cx);
+ }
+
+ bool enqueuePromiseJob(JSContext* cx, JS::HandleObject UNUSED(promise), JS::HandleObject job, JS::HandleObject UNUSED(allocationSite), JS::HandleObject UNUSED(incumbentGlobal))
+ {
+ ScriptRequest rq(cx);
+ const_cast(rq.GetScriptInterface()).AddJob(job);
+ return true;
+ }
+
+ virtual void runJobs(JSContext*) {};
+
+ virtual bool empty() const { return true; };
+
+protected:
+ // This is used by the debugger-interruptible queue.
+ virtual js::UniquePtr saveJobQueue(JSContext*) { return nullptr; };
+
+private:
+};
+
+/**
+ * Run a job in the thread pool. This returns a promise right away, and once the future completes will resolve the promise.
+ */
+template
+JS::HandleValue RunAsPromise(const ScriptInterface& scriptInterface, Args... args)
+{
+ ScriptRequest rq(scriptInterface);
+ JS::RootedObject prom(rq.cx, JS::NewPromiseObject(rq.cx, nullptr));
+
+ // Return a function to type-erase the return type of the callable.
+ Future test = Threading::TaskManager::Instance().PushTask([args...]() -> std::function{
+ auto ret = callable(args...);
+ return [ret] (const ScriptRequest& rq2) -> JS::HandleValue {
+ JS::RootedValue val(rq2.cx);
+ Script::ToJSVal(rq2, &val, ret);
+ return val;
+ };
+ });
+ JS::Heap promObject(prom.get());
+ const_cast(scriptInterface).AddPromise(promObject, std::move(test));
+
+ JS::RootedValue promVal(rq.cx, JS::ObjectValue(*prom));
+ // For some reason, this is needed of the returned object isn't interpreted as a promise
+ // despite there apparently being no pending exception anyways?
+ ScriptException::CatchPending(rq);
+
+ return promVal;
+}
+
+}
+
+#endif // INCLUDED_SCRIPTINTERFACE_JOBQUEUE
Index: source/scriptinterface/ScriptContext.h
===================================================================
--- source/scriptinterface/ScriptContext.h
+++ source/scriptinterface/ScriptContext.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -27,6 +27,10 @@
constexpr int DEFAULT_CONTEXT_SIZE = 16 * 1024 * 1024;
constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024;
+namespace Script {
+class JobQueue;
+}
+
/**
* Abstraction around a SpiderMonkey JSContext.
*
@@ -86,6 +90,7 @@
private:
JSContext* m_cx;
+ std::unique_ptr m_jobqueue;
void PrepareZonesForIncrementalGC() const;
std::list m_Realms;
Index: source/scriptinterface/ScriptContext.cpp
===================================================================
--- source/scriptinterface/ScriptContext.cpp
+++ source/scriptinterface/ScriptContext.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptEngine.h"
#include "scriptinterface/ScriptInterface.h"
+#include "scriptinterface/Promises.h"
void GCSliceCallbackHook(JSContext* UNUSED(cx), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc))
{
@@ -122,6 +123,9 @@
JS::ContextOptionsRef(m_cx).setStrictMode(true);
ScriptEngine::GetSingleton().RegisterContext(m_cx);
+
+ m_jobqueue = std::make_unique();
+ JS::SetJobQueue(m_cx, m_jobqueue.get());
}
ScriptContext::~ScriptContext()
Index: source/scriptinterface/ScriptInterface.h
===================================================================
--- source/scriptinterface/ScriptInterface.h
+++ source/scriptinterface/ScriptInterface.h
@@ -24,6 +24,8 @@
#include "scriptinterface/ScriptRequest.h"
#include "scriptinterface/ScriptTypes.h"
+#include "ps/FutureForward.h"
+
#include