tasks.nim
1 import std/[macros, strutils] 2 3 import macroutils 4 5 import jobs 6 export jobs 7 8 ## Tasks provide a convenience wrapper for using the jobs module. It also 9 ## provides some extra conveniences like handling a subset of `openArray[T]` 10 ## types in a safe manner using `OpenArrayHolder[T]` type. 11 ## 12 ## The `asyncTask` macro works by creating a wrapper proc around the 13 ## annotated user proc. The transformation looks similar to: 14 ## 15 ## .. code-block:: 16 ## proc doHashes*(data: openArray[byte], opts: HashOptions): float {.asyncTask.} = 17 ## result = 10.0 18 ## 19 ## 20 ## .. code-block:: 21 ## proc doHashesTasklet*(data: openArray[byte]; opts: HashOptions): float {.nimcall.} = 22 ## result = 10.0 23 ## 24 ## proc doHashes*(jobResult: JobResult[float]; data: OpenArrayHolder[byte]; 25 ## opts: HashOptions) {.nimcall.} = 26 ## let val {.inject.} = doHashesTasklet(convertParamType(data), 27 ## convertParamType(opts)) 28 ## discard jobResult.queue.send((jobResult.id, val)) 29 ## 30 ## Paramters with type of `openArray[T]` have special support and are converted 31 ## into the `OpenArrayHolder[T]` type from the jobs module. See the jobs module 32 ## for more information. 33 ## 34 template convertParamType*[T](obj: OpenArrayHolder[T]): auto = 35 static: 36 echo "CONVERTPARAMTYPE:: ", $typeof(obj) 37 obj.toOpenArray() 38 39 template convertParamType*(obj: typed): auto = 40 obj 41 42 macro asyncTask*(p: untyped): untyped = 43 ## Pragma to transfer a proc into a "tasklet" which runs 44 ## the proc body in a separate thread and returns the result 45 ## in an async compatible manner. 46 ## 47 48 let 49 procId = p[0] 50 # procLineInfo = p.lineInfoObj 51 # genericParams = p[2] 52 params = p[3] 53 # pragmas = p[4] 54 body = p[6] 55 name = repr(procId).strip(false, true, {'*'}) 56 57 if not hasReturnType(params): 58 error("tasklet definition must have return type", p) 59 60 # setup inner tasklet proc 61 let tp = mkProc(procId.procIdentAppend("Tasklet"), params, body) 62 63 # setup async wrapper code 64 var asyncBody = newStmtList() 65 let tcall = newCall(ident(name & "Tasklet")) 66 for paramId, paramType in paramsIter(params): 67 tcall.add newCall("convertParamType", paramId) 68 asyncBody = quote: 69 let val {.inject.} = `tcall` 70 discard jobResult.queue.send((jobResult.id, val)) 71 72 let retType = 73 if not hasReturnType(params): 74 ident"void" 75 else: 76 params.getReturnType() 77 78 let jobArg = nnkIdentDefs.newTree( 79 ident"jobResult", nnkBracketExpr.newTree(ident"JobResult", retType), newEmptyNode() 80 ) 81 var asyncParams = nnkFormalParams.newTree() 82 asyncParams.add newEmptyNode() 83 asyncParams.add jobArg 84 for i, p in params[1 ..^ 1]: 85 let pt = p[1] 86 if pt.kind == nnkBracketExpr and pt[0].repr == "openArray": 87 # special case openArray to support special OpenArrayHolder from jobs module 88 p[1] = nnkBracketExpr.newTree(ident"OpenArrayHolder", pt[1]) 89 asyncParams.add p 90 else: 91 asyncParams.add p 92 93 let fn = mkProc(procId, asyncParams, asyncBody) 94 95 result = newStmtList() 96 result.add tp 97 result.add fn 98 99 when isMainModule: 100 echo "asyncTask:body:\n", result.repr 101 102 when isMainModule: 103 type HashOptions* = object 104 striped*: bool 105 106 proc doHashes*(data: openArray[byte], opts: HashOptions): float {.asyncTask.} = 107 echo "hashing" 108 result = 10.0