summary refs log tree commit diff stats
path: root/lib/pure/coro.nim
diff options
context:
space:
mode:
authorrku <rokups@zoho.com>2015-07-31 17:18:16 +0300
committerrku <rokups@zoho.com>2015-07-31 17:59:50 +0300
commit43bfda057b56d6c67222d4c86e3be90dd3f01e59 (patch)
tree45fd5d1b1934b698618d221334b172bfd23b1567 /lib/pure/coro.nim
parentdf0e1a515b40d09527f0ad7f5aafa226d3ff37a5 (diff)
downloadNim-43bfda057b56d6c67222d4c86e3be90dd3f01e59.tar.gz
Coroutine support for i386/amd64 platforms unix/windows OSes markAndSweep/refCounting GCs.
Diffstat (limited to 'lib/pure/coro.nim')
-rw-r--r--lib/pure/coro.nim145
1 files changed, 145 insertions, 0 deletions
diff --git a/lib/pure/coro.nim b/lib/pure/coro.nim
new file mode 100644
index 000000000..6ef5f6f54
--- /dev/null
+++ b/lib/pure/coro.nim
@@ -0,0 +1,145 @@
+#
+#
+#            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 coroDefaultStackSize = 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 coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) =
+  ## 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 coroYield*(sleepTime: float=0) =
+  ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds.
+  ## Until then other coroutines are executed.
+  var oldFrame = getFrame()
+  var sp {.volatile.}: pointer
+  GC_setCurrentStack(current.stack, cast[pointer](addr sp))
+  current.sleepTime = sleep_time
+  current.lastRun = epochTime()
+  if setjmp(current.ctx) == 0:
+    longjmp(mainCtx, 1)
+  setFrame(oldFrame)
+{.pop.}
+
+proc coroRun*() =
+  ## 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 coroAlive*(c: proc()): bool =
+  ## Returns ``true`` if coroutine has not returned, ``false`` otherwise.
+  for coro in items(coroutines):
+    if coro.fn == c:
+      return true
+
+proc coroWait*(c: proc(), interval=0.01) =
+  ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often.
+  while coroAlive(c):
+    coroYield interval
+
+
+when isMainModule:
+  var stackCheckValue = 1100220033
+  proc c2()
+
+  proc c1() =
+    for i in 0 .. 3:
+      echo "c1"
+      coroYield 0.05
+    echo "c1 exits"
+
+
+  proc c2() =
+    for i in 0 .. 3:
+      echo "c2"
+      coroYield 0.025
+    coroWait(c1)
+    echo "c2 exits"
+
+  coroStart(c1)
+  coroStart(c2)
+  coroRun()
+  echo "done ", stackCheckValue