]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
lib: implement Promises
authorTimePath <andrew.hardaker1995@gmail.com>
Thu, 26 Apr 2018 11:49:50 +0000 (21:49 +1000)
committerTimePath <andrew.hardaker1995@gmail.com>
Thu, 26 Apr 2018 11:52:35 +0000 (21:52 +1000)
qcsrc/lib/_all.inc
qcsrc/lib/_mod.inc
qcsrc/lib/_mod.qh
qcsrc/lib/promise.qc [new file with mode: 0644]
qcsrc/lib/promise.qh [new file with mode: 0644]

index addced2b3f0a5d6aa7932f9cc224ed5843047bfd..0bed40bbf081fcd3c784719cc463c0db1be14dcb 100644 (file)
 #include "oo.qh"
 #include "p2mathlib.qc"
 #include "progname.qh"
+#include "promise.qc"
 #include "random.qc"
 #include "registry.qh"
 #include "registry_net.qh"
index 115a6a070fe7b076bc651d80ef5933b0f6eaed27..8df43c95f4fbec1957848010211ff54561923750 100644 (file)
@@ -2,6 +2,7 @@
 #include <lib/angle.qc>
 #include <lib/json.qc>
 #include <lib/p2mathlib.qc>
+#include <lib/promise.qc>
 #include <lib/random.qc>
 #include <lib/sortlist.qc>
 #include <lib/test.qc>
index fd97f51b65dc09130abfa19daeb6b2266a20c9f6..8645347d6914cf07c915ff7c088737e2dcf6f8c3 100644 (file)
@@ -2,6 +2,7 @@
 #include <lib/angle.qh>
 #include <lib/json.qh>
 #include <lib/p2mathlib.qh>
+#include <lib/promise.qh>
 #include <lib/random.qh>
 #include <lib/sortlist.qh>
 #include <lib/test.qh>
diff --git a/qcsrc/lib/promise.qc b/qcsrc/lib/promise.qc
new file mode 100644 (file)
index 0000000..475208e
--- /dev/null
@@ -0,0 +1,214 @@
+#include "promise.qh"
+
+.int _ref_count;
+.void(entity this) _ref_finalize;
+
+void ref_init(entity this, int init, void(entity this) finalize)
+{
+    this._ref_count = init;
+    this._ref_finalize = finalize;
+}
+
+// todo: rename to `ref`
+entity REF(entity this)
+{
+    this._ref_count += 1;
+    return this;
+}
+
+entity unref(Promise this)
+{
+    this._ref_count -= 1;
+    if (!this._ref_count) {
+        LOG_DEBUGF("Finalize entity %i (from %s)", this, this.sourceLoc);
+        this._ref_finalize(this);
+        return NULL;
+    }
+    return this;
+}
+
+enum {
+    PROMISE_PENDING,
+    PROMISE_RESOLVED,
+    PROMISE_REJECTED,
+};
+
+classfield(Promise) .int _promise_state;
+classfield(Promise) .entity _promise_result;
+classfield(Promise) .IntrusiveList _promise_handlers;
+
+entityclass(PromiseHandler);
+classfield(PromiseHandler) .Promise _promise_handler_ret;
+classfield(PromiseHandler) .entity _promise_handler_data;
+classfield(PromiseHandler) .Promise(Promise ret, entity result, entity userdata) _promise_handler_resolve;
+classfield(PromiseHandler) .Promise(Promise ret, entity err, entity userdata) _promise_handler_reject;
+
+void _Promise_finalize(Promise this)
+{
+    delete(this);
+}
+
+Promise Promise_new_(Promise this)
+{
+    ref_init(this, 2, _Promise_finalize);
+    this._promise_result = this; // promises default to being their own result to save on entities
+    return this;
+}
+
+void _Promise_handle(Promise this, PromiseHandler h);
+
+void Promise_resolve(Promise this)
+{
+    if (!this) {
+        LOG_SEVERE("Attempted to resolve a null promise");
+        return;
+    }
+    if (this._promise_state != PROMISE_PENDING) {
+        LOG_SEVEREF("Resolved non-pending promise %i", this);
+        return;
+    }
+    this._promise_state = PROMISE_RESOLVED;
+    if (this._promise_handlers) {
+        IL_EACH(this._promise_handlers, true, _Promise_handle(this, it));
+        IL_DELETE(this._promise_handlers);
+    }
+    unref(this);
+    return;
+}
+
+void Promise_reject(Promise this)
+{
+    if (!this) {
+        LOG_SEVERE("Attempted to reject a null promise");
+        return;
+    }
+    if (this._promise_state != PROMISE_PENDING) {
+        LOG_SEVEREF("Rejected non-pending promise %i", this);
+        return;
+    }
+    this._promise_state = PROMISE_REJECTED;
+    if (this._promise_handlers) {
+        IL_EACH(this._promise_handlers, true, _Promise_handle(this, it));
+        IL_DELETE(this._promise_handlers);
+    }
+    unref(this);
+    return;
+}
+
+Promise _Promise_then(
+        Promise this,
+        Promise ret,
+        Promise(Promise ret, entity result, entity userdata) onResolve,
+        Promise(Promise ret, entity result, entity userdata) onReject,
+        entity userdata
+);
+
+void _Promise_handle(Promise this, PromiseHandler h)
+{
+    switch (this._promise_state) {
+        case PROMISE_PENDING:
+            if (!this._promise_handlers) {
+                this._promise_handlers = IL_NEW();
+            }
+            IL_PUSH(this._promise_handlers, h);
+            break;
+        case PROMISE_RESOLVED: {
+            Promise ret = h._promise_handler_ret;
+            Promise p = h._promise_handler_resolve(ret, this._promise_result, h._promise_handler_data);
+            if (p != ret) _Promise_then(p, ret, func_null, func_null, NULL); // bind p -> ret
+            delete(h);
+            break;
+        }
+        case PROMISE_REJECTED: {
+            Promise ret = h._promise_handler_ret;
+            Promise p = h._promise_handler_reject(ret, this._promise_result, h._promise_handler_data);
+            if (p != ret) _Promise_then(p, ret, func_null, func_null, NULL); // bind p -> ret
+            delete(h);
+            break;
+        }
+    }
+}
+
+void _Promise_done(
+        Promise this,
+        Promise(Promise ret, entity result, entity userdata) onResolve,
+        Promise(Promise ret, entity err, entity userdata) onReject,
+        Promise ret,
+        entity userdata
+)
+{
+    PromiseHandler h = new_pure(PromiseHandler);
+    h._promise_handler_ret = ret;
+    h._promise_handler_data = userdata;
+    h._promise_handler_resolve = onResolve;
+    h._promise_handler_reject = onReject;
+    _Promise_handle(this, h);
+}
+
+Promise _Promise_onResolve_default(Promise ret, entity result, entity userdata)
+{
+    ret._promise_result = result;
+    Promise_resolve(ret);
+    return ret;
+}
+
+Promise _Promise_onReject_default(Promise ret, entity err, entity userdata)
+{
+    ret._promise_result = err;
+    Promise_reject(ret);
+    return ret;
+}
+
+Promise _Promise_then(
+        Promise this,
+        Promise ret,
+        Promise(Promise ret, entity result, entity userdata) onResolve,
+        Promise(Promise ret, entity result, entity userdata) onReject,
+        entity userdata
+)
+{
+    _Promise_done(
+            this,
+            (onResolve ? onResolve : _Promise_onResolve_default),
+            (onReject ? onReject : _Promise_onReject_default),
+            ret,
+            userdata
+    );
+    return ret;
+}
+
+Promise Promise_then_(
+        Promise this,
+        Promise ret,
+        Promise(Promise ret, entity result, entity userdata) onResolve,
+        entity userdata
+)
+{
+    unref(ret); // ret is a temporary
+    return _Promise_then(this, ret, onResolve, func_null, userdata);
+}
+
+Promise Promise_catch_(
+        Promise this,
+        Promise ret,
+        Promise(Promise ret, entity result, entity userdata) onReject,
+        entity userdata
+)
+{
+    unref(ret); // ret is a temporary
+    return _Promise_then(this, ret, func_null, onReject, userdata);
+}
+
+// utils
+
+#ifndef MENUQC
+
+Promise Promise_sleep(float n)
+{
+    Promise p = unref(Promise_new());
+    setthink(p, Promise_resolve);
+    p.nextthink = time + n;
+    return p;
+}
+
+#endif
diff --git a/qcsrc/lib/promise.qh b/qcsrc/lib/promise.qh
new file mode 100644 (file)
index 0000000..a793f58
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+entityclass(Promise);
+
+#define Promise_new() Promise_new_(new_pure(Promise))
+Promise Promise_new_(Promise this);
+
+/**
+ * notify all Promise_then subscribers that this promise has resolved
+ */
+void Promise_resolve(Promise this);
+
+#define Promise_then(this, handler, userdata) Promise_then_(this, Promise_new(), handler, userdata)
+Promise Promise_then_(Promise this, Promise ret, Promise(Promise ret, entity result, entity userdata) handler, entity userdata);
+
+/**
+ * notify all Promise_catch subscribers that this promise has rejected
+ */
+void Promise_reject(Promise this);
+
+#define Promise_catch(this, handler, userdata) Promise_catch_(this, Promise_new(), handler, userdata)
+Promise Promise_catch_(Promise this, Promise ret, Promise(Promise ret, entity err, entity userdata) handler, entity userdata);
+
+// utils
+
+#ifndef MENUQC
+
+// TODO: support menu
+Promise Promise_sleep(float n);
+
+#endif