summary refs log tree commit diff stats
path: root/lib/pure/coro.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/coro.nim')
-rw-r--r--lib/pure/coro.nim143
1 files changed, 143 insertions, 0 deletions
diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim
new file mode 100644
index 000000000..c5724f26f
--- /dev/null
+++ b/lib/pure/coro.nim
@@ -0,0 +1,143 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Rokas Kupstys
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+when not defined(nimCoroutines):
+  {.error: "Coroutines require -d:nimCoroutines".}
+
+import os, times
+import macros
+import arch
+import lists
+
+const defaultStackSize = 512 * 1024
+
+type Coroutine = ref object
+  # prev: ptr Coroutine
+  # next: ptr Coroutine
+  ctx: JmpBuf
+  fn: proc()
+  started: bool
+  lastRun: float
+  sleepTime: float
+  stack: pointer
+  stacksize: int
+
+var coroutines = initDoublyLinkedList[Coroutine]()
+var current: Coroutine
+var mainCtx: JmpBuf
+
+
+proc GC_addStack(starts: pointer) {.cdecl, importc.}
+proc GC_removeStack(starts: pointer) {.cdecl, importc.}
+proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.}
+
+proc start*(c: proc(), stacksize: int=defaultStackSize) =
+  ## Adds coroutine to event loop. It does not run immediately.
+  var coro = Coroutine()
+  coro.fn = c
+  while coro.stack == nil:
+    coro.stack = alloc0(stacksize)
+  coro.stacksize = stacksize
+  coroutines.append(coro)
+
+{.push stackTrace: off.}
+proc suspend*(sleepTime: float=0) =
+  ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
+  ## Until then other coroutines are executed.
+  ##
+  ## This is similar to a `yield`:idx:, or a `yieldFrom`:idx in Python.
+  var oldFrame = getFrame()
+  var sp {.volatile.}: pointer
+  GC_setCurrentStack(current.stack, cast[pointer](addr sp))
+  current.sleepTime = sleepTime
+  current.lastRun = epochTime()
+  if setjmp(current.ctx) == 0:
+    longjmp(mainCtx, 1)
+  setFrame(oldFrame)
+{.pop.}
+
+proc run*() =
+  ## Starts main event loop which exits when all coroutines exit. Calling this proc
+  ## starts execution of first coroutine.
+  var node = coroutines.head
+  var minDelay: float = 0
+  var frame: PFrame
+  while node != nil:
+    var coro = node.value
+    current = coro
+    os.sleep(int(minDelay * 1000))
+
+    var remaining = coro.sleepTime - (epochTime() - coro.lastRun);
+    if remaining <= 0:
+      remaining = 0
+      let res = setjmp(mainCtx)
+      if res == 0:
+        frame = getFrame()
+        if coro.started:            # coroutine resumes
+          longjmp(coro.ctx, 1)
+        else:
+          coro.started = true       # coroutine starts
+          var stackEnd = cast[pointer](cast[ByteAddress](coro.stack) + coro.stacksize)
+          GC_addStack(coro.stack)
+          coroSwitchStack(stackEnd)
+          coro.fn()
+          coroRestoreStack()
+          GC_removeStack(coro.stack)
+          var next = node.prev
+          coroutines.remove(node)
+          dealloc(coro.stack)
+          node = next
+          setFrame(frame)
+      else:
+        setFrame(frame)
+
+    elif remaining > 0:
+      if minDelay > 0 and remaining > 0:
+        minDelay = min(remaining, minDelay)
+      else:
+        minDelay = remaining
+
+    if node == nil or node.next == nil:
+      node = coroutines.head
+    else:
+      node = node.next
+
+proc alive*(c: proc()): bool =
+  ## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
+  for coro in items(coroutines):
+    if coro.fn == c:
+      return true
+
+proc wait*(c: proc(), interval=0.01) =
+  ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
+  while alive(c):
+    suspend interval
+
+when defined(nimCoroutines) and isMainModule:
+  var stackCheckValue = 1100220033
+  proc c2()
+
+  proc c1() =
+    for i in 0 .. 3:
+      echo "c1"
+      suspend 0.05
+    echo "c1 exits"
+
+
+  proc c2() =
+    for i in 0 .. 3:
+      echo "c2"
+      suspend 0.025
+    wait(c1)
+    echo "c2 exits"
+
+  start(c1)
+  start(c2)
+  run()
+  echo "done ", stackCheckValue