From a3858847e816923d8bb867e11f64f12f22ec6eaa Mon Sep 17 00:00:00 2001 From: TimePath Date: Thu, 26 Apr 2018 21:49:50 +1000 Subject: [PATCH] lib: implement Promises --- qcsrc/lib/_all.inc | 1 + qcsrc/lib/_mod.inc | 1 + qcsrc/lib/_mod.qh | 1 + qcsrc/lib/promise.qc | 214 +++++++++++++++++++++++++++++++++++++++++++ qcsrc/lib/promise.qh | 31 +++++++ 5 files changed, 248 insertions(+) create mode 100644 qcsrc/lib/promise.qc create mode 100644 qcsrc/lib/promise.qh diff --git a/qcsrc/lib/_all.inc b/qcsrc/lib/_all.inc index addced2b3..0bed40bbf 100644 --- a/qcsrc/lib/_all.inc +++ b/qcsrc/lib/_all.inc @@ -133,6 +133,7 @@ #include "oo.qh" #include "p2mathlib.qc" #include "progname.qh" +#include "promise.qc" #include "random.qc" #include "registry.qh" #include "registry_net.qh" diff --git a/qcsrc/lib/_mod.inc b/qcsrc/lib/_mod.inc index 115a6a070..8df43c95f 100644 --- a/qcsrc/lib/_mod.inc +++ b/qcsrc/lib/_mod.inc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/lib/_mod.qh b/qcsrc/lib/_mod.qh index fd97f51b6..8645347d6 100644 --- a/qcsrc/lib/_mod.qh +++ b/qcsrc/lib/_mod.qh @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/lib/promise.qc b/qcsrc/lib/promise.qc new file mode 100644 index 000000000..475208e34 --- /dev/null +++ b/qcsrc/lib/promise.qc @@ -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 index 000000000..a793f5858 --- /dev/null +++ b/qcsrc/lib/promise.qh @@ -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 -- 2.39.2