diff options
author | Michał Zieliński <michal@zielinscy.org.pl> | 2017-06-12 11:47:52 +0200 |
---|---|---|
committer | Michał Zieliński <michal@zielinscy.org.pl> | 2017-07-05 12:54:09 +0200 |
commit | 797690ba3ff415a457499ddf0edda24c31644b1d (patch) | |
tree | a52279f3dd7e3135f6f8764960a72dd30209fe99 | |
parent | 93827e6ab8effab38696f1d1c25bf65c2f538675 (diff) | |
download | Nim-797690ba3ff415a457499ddf0edda24c31644b1d.tar.gz |
Future: support for multiple callbacks
-rw-r--r-- | lib/pure/asyncfutures.nim | 83 | ||||
-rw-r--r-- | tests/async/tcallbacks.nim | 20 |
2 files changed, 82 insertions, 21 deletions
diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index f7d96d5e4..2d86d3d8d 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -4,8 +4,15 @@ import os, tables, strutils, times, heapqueue, options, deques # TODO: This shouldn't need to be included, but should ideally be exported. type + CallbackFunc = proc () {.closure, gcsafe.} + + CallbackList = object + function: CallbackFunc + next: ref CallbackList + FutureBase* = ref object of RootObj ## Untyped future. - cb: proc () {.closure,gcsafe.} + callbacks: CallbackList + finished: bool error*: ref Exception ## Stored exception errorStackTrace*: string @@ -86,6 +93,33 @@ proc checkFinished[T](future: Future[T]) = err.cause = future raise err +proc call(callbacks: var CallbackList) = + var current = callbacks + + while true: + if current.function != nil: + callSoon(current.function) + + if current.next == nil: + break + else: + current = current.next[] + + # callback will be called only once, let GC collect them now + callbacks.next = nil + callbacks.function = nil + +proc add(callbacks: var CallbackList, function: CallbackFunc) = + if callbacks.function == nil: + callbacks.function = function + assert callbacks.next == nil + else: + let newNext = new(ref CallbackList) + newNext.function = callbacks.function + newNext.next = callbacks.next + callbacks.next = newNext + callbacks.function = function + proc complete*[T](future: Future[T], val: T) = ## Completes ``future`` with value ``val``. #assert(not future.finished, "Future already finished, cannot finish twice.") @@ -93,8 +127,7 @@ proc complete*[T](future: Future[T], val: T) = assert(future.error == nil) future.value = val future.finished = true - if future.cb != nil: - future.cb() + future.callbacks.call() proc complete*(future: Future[void]) = ## Completes a void ``future``. @@ -102,8 +135,7 @@ proc complete*(future: Future[void]) = checkFinished(future) assert(future.error == nil) future.finished = true - if future.cb != nil: - future.cb() + future.callbacks.call() proc complete*[T](future: FutureVar[T]) = ## Completes a ``FutureVar``. @@ -111,8 +143,7 @@ proc complete*[T](future: FutureVar[T]) = checkFinished(fut) assert(fut.error == nil) fut.finished = true - if fut.cb != nil: - fut.cb() + fut.callbacks.call proc complete*[T](future: FutureVar[T], val: T) = ## Completes a ``FutureVar`` with value ``val``. @@ -134,26 +165,36 @@ proc fail*[T](future: Future[T], error: ref Exception) = future.error = error future.errorStackTrace = if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error) - if future.cb != nil: - future.cb() + future.callbacks.call + +proc clearCallbacks(future: FutureBase) = + future.callbacks.function = nil + future.callbacks.next = nil + +proc addCallback*(future: FutureBase, cb: proc() {.closure,gcsafe.}) = + ## Adds the callbacks proc to be called when the future completes. + ## + ## If future has already completed then ``cb`` will be called immediately. + assert cb != nil + if future.finished: + callSoon(cb) else: - # This is to prevent exceptions from being silently ignored when a future - # is discarded. - # TODO: This may turn out to be a bad idea. - # Turns out this is a bad idea. - #raise error - discard + future.callbacks.add cb + +proc addCallback*[T](future: Future[T], cb: proc(future: Future[T]) {.closure,gcsafe.}) = + ## Adds the callbacks proc to be called when the future completes. + ## + ## If future has already completed then ``cb`` will be called immediately. + future.addCallback proc() = cb(future) proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) = - ## Sets the callback proc to be called when the future completes. + ## Clears the list of callbacks and sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. ## - ## **Note**: You most likely want the other ``callback`` setter which - ## passes ``future`` as a param to the callback. - future.cb = cb - if future.finished: - callSoon(future.cb) + ## It's recommended to use ``addCallback`` or ``then`` instead. + future.clearCallbacks + future.addCallback cb proc `callback=`*[T](future: Future[T], cb: proc (future: Future[T]) {.closure,gcsafe.}) = diff --git a/tests/async/tcallbacks.nim b/tests/async/tcallbacks.nim new file mode 100644 index 000000000..8c08032cd --- /dev/null +++ b/tests/async/tcallbacks.nim @@ -0,0 +1,20 @@ +discard """ + exitcode: 0 + output: '''3 +2 +1 +5 +''' +""" +import asyncfutures + +let f1: Future[int] = newFuture[int]() +f1.addCallback(proc() = echo 1) +f1.addCallback(proc() = echo 2) +f1.addCallback(proc() = echo 3) +f1.complete(10) + +let f2: Future[int] = newFuture[int]() +f2.addCallback(proc() = echo 4) +f2.callback = proc() = echo 5 +f2.complete(10) |