/ src / apatheia / tasks.nim
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