From 91343ac1d2bd24d72d44ad493d6d1e7a98ae81a7 Mon Sep 17 00:00:00 2001 From: TimePath Date: Wed, 25 Apr 2018 21:40:05 +1000 Subject: [PATCH] Promises: initial commit, leaks entities --- qcsrc/lib/_all.inc | 1 + qcsrc/lib/_mod.inc | 1 + qcsrc/lib/_mod.qh | 1 + qcsrc/lib/promise.qc | 185 +++++++++++++++++++++++++++++++++ qcsrc/lib/promise.qh | 19 ++++ qcsrc/server/command/sv_cmd.qc | 4 + 6 files changed, 211 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 d45dc208e..07f360520 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..6e8d9ef7e --- /dev/null +++ b/qcsrc/lib/promise.qc @@ -0,0 +1,185 @@ +#include "promise.qh" + +enum { + PROMISE_PENDING, + PROMISE_RESOLVED, + PROMISE_REJECTED, +}; + +classfield(Promise) .int _promise_state; +classfield(Promise) .entity _promise_result; +classfield(Promise) .IntrusiveList _promise_handlers; +/** non-pending promises with no references may removed. TODO: implement (sv_cmd find Promise) */ +classfield(Promise) .int _promise_refcount; + +entityclass(PromiseHandler); +classfield(PromiseHandler) .Promise _promise_handler_ret; +classfield(PromiseHandler) .entity _promise_handler_data; +classfield(PromiseHandler) .int(Promise ret, entity result, entity userdata) _promise_handler_resolve; +classfield(PromiseHandler) .int(Promise ret, entity err, entity userdata) _promise_handler_reject; + +Promise promise_new() +{ + Promise p = new_pure(Promise); + p._promise_result = p; // promises default to being their own result to save on entities + return p; +} + +void promise_ref(Promise this) +{ + this._promise_refcount += 1; +} + +void promise_unref(Promise this) +{ + this._promise_refcount -= 1; +} + +void promise_handle(Promise this, PromiseHandler h); + +int promise_resolve(Promise this) +{ + if (!this) { + LOG_SEVERE("Attempted to resolve a null promise"); + return this._promise_state; + } + if (this._promise_state != PROMISE_PENDING) { + LOG_SEVEREF("Resolved non-pending promise %i", this); + return this._promise_state; + } + this._promise_state = PROMISE_RESOLVED; + if (this._promise_handlers) { + IL_EACH(this._promise_handlers, true, promise_handle(this, it)); + IL_DELETE(this._promise_handlers); + } + return this._promise_state; +} + +int promise_reject(Promise this) +{ + if (!this) { + LOG_SEVERE("Attempted to reject a null promise"); + return this._promise_state; + } + if (this._promise_state != PROMISE_PENDING) { + LOG_SEVEREF("Rejected non-pending promise %i", this); + return this._promise_state; + } + this._promise_state = PROMISE_REJECTED; + if (this._promise_handlers) { + IL_EACH(this._promise_handlers, true, promise_handle(this, it)); + IL_DELETE(this._promise_handlers); + } + return this._promise_state; +} + +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: + h._promise_handler_resolve(h._promise_handler_ret, this._promise_result, h._promise_handler_data); + delete(h); + break; + case PROMISE_REJECTED: + h._promise_handler_reject(h._promise_handler_ret, this._promise_result, h._promise_handler_data); + delete(h); + break; + } +} + +void promise_done( + Promise this, + int(Promise ret, entity result, entity userdata) onResolve, + int(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); +} + +int _promise_onResolve_default(Promise ret, entity result, entity userdata) +{ + ret._promise_result = result; + return promise_resolve(ret); +} + +int _promise_onReject_default(Promise ret, entity err, entity userdata) +{ + ret._promise_result = err; + return promise_reject(ret); +} + +Promise _promise_then( + Promise this, + int(Promise ret, entity result, entity userdata) onResolve, + int(Promise ret, entity result, entity userdata) onReject, + entity userdata +) +{ + Promise ret = promise_new(); + promise_done( + this, + (onResolve ? onResolve : _promise_onResolve_default), + (onReject ? onReject : _promise_onReject_default), + ret, + userdata + ); + return ret; +} + +Promise promise_then( + Promise this, + int(Promise ret, entity result, entity userdata) onResolve, + entity userdata +) +{ + return _promise_then(this, onResolve, func_null, userdata); +} + +Promise promise_catch( + Promise this, + int(Promise ret, entity result, entity userdata) onReject, + entity userdata +) +{ + return _promise_then(this, func_null, onReject, userdata); +} + +int promise_test_then(Promise ret, entity result, entity userdata); +int promise_test_catch(Promise ret, entity err, entity userdata); +.int pval; + +void promise_test() +{ + Promise p0 = promise_new(); p0.pval = 5; + Promise p = p0; + p = promise_then(p, promise_test_then, NULL); + p = promise_then(p, promise_test_then, NULL); + p = promise_catch(p, promise_test_catch, NULL); + promise_resolve(p0); +} + +int promise_test_then(Promise ret, entity result, entity userdata) +{ + LOG_INFOF("in .then(): %d", result.pval); + ret.pval = 10; + return promise_reject(ret); +} + +int promise_test_catch(Promise ret, entity err, entity userdata) +{ + LOG_INFOF("in .catch(): %d", err.pval); + return promise_resolve(ret); +} diff --git a/qcsrc/lib/promise.qh b/qcsrc/lib/promise.qh new file mode 100644 index 000000000..a8a95d3d4 --- /dev/null +++ b/qcsrc/lib/promise.qh @@ -0,0 +1,19 @@ +#pragma once + +entityclass(Promise); + +Promise promise_new(); + +/** + * notify all Promise_then subscribers that this promise has resolved + */ +int promise_resolve(Promise this); + +Promise promise_then(Promise this, int(Promise ret, entity result, entity userdata) handler, entity userdata); + +/** + * notify all Promise_catch subscribers that this promise has rejected + */ +int promise_reject(Promise this); + +Promise promise_catch(Promise this, int(Promise ret, entity err, entity userdata) handler, entity userdata); diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 6de4507b1..ddbdb6061 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -1852,3 +1852,7 @@ void GameCommand(string command) // nothing above caught the command, must be invalid LOG_INFO(((command != "") ? strcat("Unknown server command \"", command, "\"") : "No command provided"), ". For a list of supported commands, try sv_cmd help."); } + +SERVER_COMMAND(test, "Test Promises") { + promise_test(); +} -- 2.39.2