1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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 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
|