You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1061 lines
54 KiB
1061 lines
54 KiB
;; TaskSystem.cake: Interface into task-based multithreading
|
|
(import "ComptimeHelpers.cake" "CHelpers.cake"
|
|
&defs-only "Licenses.cake")
|
|
(c-import "stdlib.h" ;; malloc, free
|
|
"string.h") ;; memset
|
|
|
|
(export-and-evaluate
|
|
(add-c-search-directory-module "Dependencies/enkiTS/src")
|
|
(c-import "TaskScheduler_c.h"))
|
|
|
|
(register-module-license "EnkiTS" s-enkits-license-string)
|
|
|
|
(forward-declare
|
|
(struct enkiCompletionAction)
|
|
(struct enkiTaskSet)
|
|
(struct enkiPinnedTask)
|
|
(struct enkiCompletable)
|
|
(struct enkiDependency)
|
|
(struct enkiTaskScheduler))
|
|
|
|
(def-function-signature-global task-system-task-signature (task-range-start (unsigned int)
|
|
task-range-end (unsigned int)
|
|
task-thread-id (unsigned int)
|
|
args-void (addr void)))
|
|
|
|
;; Helps to keep def-task from having to define a different interface for pinned tasks
|
|
(defstruct task-system-pinned-task-arguments
|
|
task-thread-id (unsigned int)
|
|
task-to-execute task-system-task-signature
|
|
task-to-execute-args (addr void))
|
|
|
|
;; Data is allocated in order to execute a task system. This struct keeps track of all of it so that
|
|
;; it can be freed once the system is completely finished
|
|
(defstruct task-system-execute-data
|
|
;; Created by enkiTS
|
|
cleanup-action (addr enkiCompletionAction) ;; The action that will free these things
|
|
task-sets (addr (addr enkiTaskSet))
|
|
num-task-sets (unsigned int)
|
|
pinned-tasks (addr (addr enkiPinnedTask))
|
|
num-pinned-tasks (unsigned int)
|
|
pinned-task-arguments (addr (addr task-system-pinned-task-arguments))
|
|
num-pinned-task-arguments (unsigned int)
|
|
completables (addr (addr enkiCompletable))
|
|
num-completables (unsigned int)
|
|
dependencies (addr (addr enkiDependency))
|
|
num-dependencies (unsigned int)
|
|
;; Created by us
|
|
task-arguments (addr (addr void))
|
|
num-task-arguments (unsigned int))
|
|
|
|
(defstruct task-system
|
|
task-scheduler (addr enkiTaskScheduler))
|
|
|
|
(var-global g-task-system task-system (array 0))
|
|
|
|
(var-global g-task-system-main-thread-index (const int) 0)
|
|
|
|
;;
|
|
;; Interface
|
|
;;
|
|
|
|
(defun task-system-initialize ()
|
|
(set (field g-task-system task-scheduler) (enkiNewTaskScheduler))
|
|
(enkiInitTaskScheduler (field g-task-system task-scheduler)))
|
|
|
|
(defun task-system-shutdown ()
|
|
(enkiWaitforAllAndShutdown (field g-task-system task-scheduler))
|
|
(enkiDeleteTaskScheduler (field g-task-system task-scheduler))
|
|
(set (field g-task-system task-scheduler) null))
|
|
|
|
;; Must call to make pinned tasks run on the current thread, unless you call task-system-wait-for-all
|
|
;; This only runs pinned tasks, if any
|
|
(defun task-system-run-thread-pinned-tasks ()
|
|
(enkiRunPinnedTasks (field g-task-system task-scheduler)))
|
|
|
|
(defun task-system-wait-for-all ()
|
|
(enkiWaitForAll (field g-task-system task-scheduler)))
|
|
|
|
(defun task-system-get-num-task-threads (&return (unsigned int))
|
|
(return
|
|
(type-cast (enkiGetNumTaskThreads (field g-task-system task-scheduler))
|
|
(unsigned int))))
|
|
|
|
;; Creates a task function with destructured arguments and the appropriate signature for TaskSystem
|
|
;; The following variables are implicit to task context:
|
|
;; - task-range-start uint32_t
|
|
;; - task-range-end uint32_t
|
|
;; - task-thread-id uint32_t
|
|
;; The arguments structure is called <task-name>-arguments
|
|
(defmacro def-task (task-name symbol parameters (index array) &rest body any)
|
|
;; Won't be specified for invocations with empty parameters
|
|
(var destructured-arguments (template (in std vector) Token))
|
|
(var args-definition (template (in std vector) Token))
|
|
(scope ;; Destructure userdata struct for convenience
|
|
(var end-token-index int (FindCloseParenTokenIndex tokens parameters))
|
|
(var num-parameters int (getNumArguments tokens parameters end-token-index))
|
|
(unless (or (not num-parameters)
|
|
(= 0 (mod num-parameters 2)))
|
|
(ErrorAtToken (at parameters tokens) "expected even number of parameters. Parameters must be in " \
|
|
"name type pairs")
|
|
(return false))
|
|
|
|
(when num-parameters
|
|
(var args-struct-name Token (deref task-name))
|
|
(call-on append (field args-struct-name contents) "-arguments")
|
|
(tokenize-push destructured-arguments
|
|
(var args (addr (token-splice-addr args-struct-name))
|
|
(type-cast args-void (addr (token-splice-addr args-struct-name)))))
|
|
(var args-definition-fields (template (in std vector) Token))
|
|
(var arguments-to-structure (template (in std vector) Token))
|
|
|
|
(each-token-argument-in tokens (+ 1 parameters) end-token-index current-token-index
|
|
(var name-token (addr (const Token)) (addr (at current-token-index tokens)))
|
|
(incr current-token-index)
|
|
(var type-token (addr (const Token)) (addr (at current-token-index tokens)))
|
|
|
|
(tokenize-push destructured-arguments
|
|
(var (token-splice name-token) (token-splice type-token)
|
|
(path args > (token-splice name-token))))
|
|
(tokenize-push args-definition-fields
|
|
(token-splice name-token) (token-splice type-token))
|
|
(tokenize-push arguments-to-structure
|
|
(set (path arguments-out > (token-splice name-token))
|
|
(token-splice name-token))))
|
|
|
|
(unless (call-on empty args-definition-fields)
|
|
(var args-structure-func-name Token args-struct-name)
|
|
(call-on append (field args-structure-func-name contents) "-fill")
|
|
(tokenize-push args-definition
|
|
(defstruct-local (token-splice-addr args-struct-name)
|
|
(token-splice-array args-definition-fields))
|
|
;; Why do we need a create function? Because we don't want task-system-run to need to wait
|
|
;; for the def-task to resolve in order to know which arguments go where. Instead, we let
|
|
;; task-system-run call a function which will eventually resolve to setting the arguments
|
|
(defun-local (token-splice-addr args-structure-func-name)
|
|
(arguments-out (addr (token-splice-addr args-struct-name))
|
|
(token-splice-array args-definition-fields))
|
|
(token-splice-array arguments-to-structure))))))
|
|
|
|
(tokenize-push output
|
|
(token-splice-array args-definition)
|
|
;; Signature must match task system signature
|
|
(defun-local (token-splice task-name) (task-range-start uint32_t task-range-end uint32_t
|
|
task-thread-id uint32_t args-void (addr void))
|
|
(token-splice-array destructured-arguments)
|
|
(token-splice-rest body tokens)))
|
|
(return true))
|
|
|
|
(defun task-system-pinned-task-wrapper (args-void (addr void))
|
|
(var pinned-task-args (addr task-system-pinned-task-arguments)
|
|
(type-cast args-void (addr task-system-pinned-task-arguments)))
|
|
(call (path pinned-task-args > task-to-execute)
|
|
1 1 ;; Pinned tasks cannot have size/range
|
|
(path pinned-task-args > task-thread-id)
|
|
(path pinned-task-args > task-to-execute-args)))
|
|
|
|
;; Internal. No need to call explicitly
|
|
(defun task-system-cleanup-execution-action (args-void (addr void) task-thread-id (unsigned int))
|
|
(var-cast-to args (addr task-system-execute-data) args-void)
|
|
|
|
(each-in-range (path args > num-task-sets) n
|
|
(enkiDeleteTaskSet (field g-task-system task-scheduler)
|
|
(at n (path args > task-sets))))
|
|
(free (path args > task-sets))
|
|
|
|
(each-in-range (path args > num-pinned-tasks) n
|
|
(enkiDeletePinnedTask (field g-task-system task-scheduler)
|
|
(at n (path args > pinned-tasks))))
|
|
(free (path args > pinned-tasks))
|
|
|
|
(each-in-range (path args > num-pinned-task-arguments) n
|
|
(free (at n (path args > pinned-task-arguments))))
|
|
(free (path args > pinned-task-arguments))
|
|
|
|
(each-in-range (path args > num-completables) n
|
|
(enkiDeleteCompletable (field g-task-system task-scheduler)
|
|
(at n (path args > completables))))
|
|
(free (path args > completables))
|
|
|
|
(each-in-range (path args > num-dependencies) n
|
|
(enkiDeleteDependency (field g-task-system task-scheduler)
|
|
(at n (path args > dependencies))))
|
|
(free (path args > dependencies))
|
|
|
|
(each-in-range (path args > num-task-arguments) n
|
|
(free (at n (path args > task-arguments))))
|
|
(free (path args > task-arguments))
|
|
|
|
;; Delete ourself
|
|
(enkiDeleteCompletionAction (field g-task-system task-scheduler)
|
|
(path args > cleanup-action))
|
|
(free args))
|
|
|
|
;; Create tasks and dependencies via specifying the tasks as if they were direct function invocations.
|
|
;; The caller needs to make sure any pointers to variables passed in to the task system are not on
|
|
;; the stack, otherwise the task system will point to garbage once the function exits.
|
|
;; The task system is implicitly sequential. Specify (parallel) context to run in parallel. You can
|
|
;; then specify (sequential) inside a (parallel) to compose complex behaviors.
|
|
(defmacro task-system-execute (&rest arguments (index array))
|
|
(var is-debug bool false)
|
|
(var end-token-index int (FindCloseParenTokenIndex tokens startTokenIndex))
|
|
;; Declared outside loop to reduce allocations
|
|
(var parsed-arguments (template (in std vector) Token))
|
|
(defenum task-run-context-state
|
|
task-run-context-parallel
|
|
task-run-context-sequential
|
|
task-run-context-on-complete)
|
|
(defstruct task-run-context
|
|
state task-run-context-state
|
|
end-state-index int)
|
|
(var context-stack (template (in std vector) task-run-context))
|
|
(var next-free-sync-id int 0)
|
|
(var next-free-dependency-id int 0)
|
|
(var next-free-task-name-index int 0)
|
|
(var next-free-task-args-name int 0)
|
|
(var next-free-pinned-task-args-name int 0)
|
|
;; Setting dependencies:
|
|
;; - If in sequential state, add dependency to previous task
|
|
;; - If in parallel state, do not add any dependencies between tasks
|
|
;; - If entering parallel state from sequential, add dependency to final task in sequence
|
|
;; - If entering sequential state from parallel, all parallel set dependency on first task in sequence
|
|
;; - If entering sequential parallel state (i.e., (sequence (parallel) (parallel))), generate a
|
|
;; sync point to avoid N x M dependencies
|
|
(var parallel-sync-point-stack (template (in std vector) Token))
|
|
;; Start implicitly sequential so someone reading the straightline code likely guesses correctly
|
|
(call-on push_back context-stack (array task-run-context-sequential
|
|
end-token-index))
|
|
(defenum task-type
|
|
task-type-none
|
|
task-type-completable
|
|
task-type-task-set
|
|
task-type-pinned-task)
|
|
(defstruct depend-task
|
|
token Token
|
|
type task-type)
|
|
(var task-to-depend-on-stack (template (in std vector) depend-task))
|
|
(var empty-token Token)
|
|
(call-on push_back task-to-depend-on-stack (array empty-token task-type-none))
|
|
;; There must be more than one task to kick things off only in the case of starting with a
|
|
;; parallel block
|
|
(var starting-tasks (template (in std vector) depend-task))
|
|
(var start-tasks-are-parallel bool false)
|
|
|
|
;; Memory management
|
|
(var created-task-sets (template (in std vector) Token))
|
|
(var created-pinned-tasks (template (in std vector) Token))
|
|
(var created-pinned-task-arguments (template (in std vector) Token))
|
|
(var created-completables (template (in std vector) Token))
|
|
(var created-dependencies (template (in std vector) Token))
|
|
(var created-task-arguments (template (in std vector) Token))
|
|
|
|
(c-for (var current-token-index int arguments)
|
|
(< current-token-index (field (call-on back context-stack) end-state-index))
|
|
(incr current-token-index)
|
|
(var current-token (addr (const Token)) (addr (at current-token-index tokens)))
|
|
(cond
|
|
((= TokenType_OpenParen (path current-token > type))
|
|
(var invocation-index int (+ 1 current-token-index))
|
|
(var invocation-token (addr (const Token)) (addr (at invocation-index tokens)))
|
|
(unless (ExpectTokenType "task-system-execute" (deref invocation-token) TokenType_Symbol)
|
|
(return false))
|
|
(cond
|
|
((std-str-equals (path invocation-token > contents) "parallel")
|
|
(when (call-on empty starting-tasks)
|
|
(set start-tasks-are-parallel true))
|
|
(when is-debug (NoteAtToken (deref invocation-token) "Push parallel & create sync point"))
|
|
(when (= task-run-context-parallel (field (call-on back context-stack) state))
|
|
(ErrorAtToken (deref invocation-token)
|
|
"unnecessary parallel while already in parallel context. This can cause "\
|
|
"unexpected consequences. Remove this (parallel) and inline tasks instead")
|
|
(return false))
|
|
(call-on push_back context-stack
|
|
(array task-run-context-parallel
|
|
(FindCloseParenTokenIndex tokens current-token-index)))
|
|
(scope ;; Create completable which doesn't need an associated function. Only used for syncing
|
|
(var parallel-sync-name Token (deref invocation-token))
|
|
(token-contents-snprintf parallel-sync-name "parallel-sync-%d" next-free-sync-id)
|
|
(call-on push_back created-completables parallel-sync-name)
|
|
(tokenize-push output
|
|
(var (token-splice-addr parallel-sync-name) (addr enkiCompletable)
|
|
(enkiCreateCompletable (field g-task-system task-scheduler))))
|
|
(call-on push_back parallel-sync-point-stack parallel-sync-name)
|
|
(incr next-free-sync-id))
|
|
;; Skip over invocation
|
|
(incr current-token-index))
|
|
|
|
((std-str-equals (path invocation-token > contents) "sequential")
|
|
(when is-debug (NoteAtToken (deref invocation-token) "Push sequential"))
|
|
(when (= task-run-context-sequential (field (call-on back context-stack) state))
|
|
(ErrorAtToken (deref invocation-token)
|
|
"unnecessary sequential while already in sequential context. This can cause "\
|
|
"unexpected consequences. Remove this (sequential) and inline tasks instead")
|
|
(return false))
|
|
(call-on push_back context-stack
|
|
(array task-run-context-sequential
|
|
(FindCloseParenTokenIndex tokens current-token-index)))
|
|
;; Clone the previous depend so the first nested task can use it, but it will be overwritten
|
|
(call-on push_back task-to-depend-on-stack (call-on back task-to-depend-on-stack))
|
|
;; Skip over invocation
|
|
(incr current-token-index))
|
|
|
|
(true ;; Handle task invocation
|
|
(var task-name Token (deref invocation-token))
|
|
(token-contents-snprintf task-name "%s-task-set-%d"
|
|
(call-on c_str (field task-name contents))
|
|
next-free-task-name-index)
|
|
(incr next-free-task-name-index)
|
|
|
|
;; Parse task size and min-range out of arguments, which can contain arbitrary user args
|
|
(var task-size (addr (const Token)) null)
|
|
(var task-min-range (addr (const Token)) null)
|
|
(var task-before-start (addr (const Token)) null)
|
|
(var is-pinned-to-main-thread bool false)
|
|
(var end-invocation-index int (FindCloseParenTokenIndex tokens current-token-index))
|
|
(call-on clear parsed-arguments)
|
|
(each-token-argument-in tokens (+ 1 invocation-index) end-invocation-index argument-index
|
|
(var current-argument (addr (const Token)) (addr (at argument-index tokens)))
|
|
(cond
|
|
((and (= TokenType_Symbol (path current-argument > type))
|
|
(std-str-equals (path current-argument > contents) ":task-size"))
|
|
(set task-size (+ 1 current-argument))
|
|
(incr argument-index))
|
|
((and (= TokenType_Symbol (path current-argument > type))
|
|
(std-str-equals (path current-argument > contents) ":task-min-range"))
|
|
(set task-min-range (+ 1 current-argument))
|
|
(incr argument-index))
|
|
((and (= TokenType_Symbol (path current-argument > type))
|
|
(std-str-equals (path current-argument > contents) ":pin-to-main-thread"))
|
|
(set is-pinned-to-main-thread true))
|
|
((and (= TokenType_Symbol (path current-argument > type))
|
|
(std-str-equals (path current-argument > contents) ":before-start"))
|
|
(set task-before-start (+ 1 current-argument))
|
|
(incr argument-index))
|
|
(true
|
|
(tokenize-push parsed-arguments (token-splice current-argument)))))
|
|
|
|
(when (or (call-on empty starting-tasks)
|
|
(and start-tasks-are-parallel ;; Need to add all parallel
|
|
(or (= 2 (call-on size context-stack))
|
|
;; Sequential within first parallel; only add first of sequence (incl all parallels)
|
|
(call-on empty task-to-depend-on-stack)
|
|
(call-on empty (field (call-on back task-to-depend-on-stack) token contents)))))
|
|
(when is-debug (Logf "Adding %s to start\n" (call-on c_str (field task-name contents))))
|
|
(call-on push_back starting-tasks (array task-name (? is-pinned-to-main-thread task-type-pinned-task
|
|
task-type-task-set))))
|
|
|
|
(when is-pinned-to-main-thread
|
|
(when task-size
|
|
(ErrorAtToken (deref task-size) "cannot pin tasks with variable sizes")
|
|
(return false))
|
|
(when task-min-range
|
|
(ErrorAtToken (deref task-min-range) "cannot pin tasks with variable sizes")
|
|
(return false)))
|
|
|
|
(var params-setters (template (in std vector) Token))
|
|
;; Pinned tasks have a special interface that's different enough to require this
|
|
(if is-pinned-to-main-thread
|
|
(scope ;; Pinned
|
|
(call-on push_back created-pinned-tasks task-name)
|
|
(var pinned-task-arguments-name Token (deref invocation-token))
|
|
(token-contents-snprintf pinned-task-arguments-name "pinned-task-args-%d"
|
|
next-free-pinned-task-args-name)
|
|
(incr next-free-pinned-task-args-name)
|
|
(call-on push_back created-pinned-task-arguments pinned-task-arguments-name)
|
|
|
|
(if (call-on empty parsed-arguments)
|
|
(scope ;; No user arguments
|
|
(tokenize-push output
|
|
(var (token-splice-addr task-name) (addr enkiPinnedTask)
|
|
(enkiCreatePinnedTask (field g-task-system task-scheduler)
|
|
task-system-pinned-task-wrapper
|
|
g-task-system-main-thread-index)) ;; Hard-coded to main thread for now
|
|
(var (token-splice-addr pinned-task-arguments-name)
|
|
(addr task-system-pinned-task-arguments)
|
|
(type-cast (malloc (sizeof (type task-system-pinned-task-arguments)))
|
|
(addr task-system-pinned-task-arguments)))
|
|
(scope
|
|
(var task-params (struct enkiParamsPinnedTask) (enkiGetParamsPinnedTask (token-splice-addr task-name)))
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-thread-id)
|
|
g-task-system-main-thread-index) ;; Hard coded to main thread
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-to-execute)
|
|
(token-splice invocation-token))
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-to-execute-args)
|
|
null)
|
|
(set (field task-params pArgs) (token-splice-addr pinned-task-arguments-name))
|
|
(enkiSetParamsPinnedTask (token-splice-addr task-name) task-params))))
|
|
(scope ;; Arguments; do auto-structuring
|
|
(var task-argument-type Token (deref invocation-token))
|
|
(call-on append (field task-argument-type contents) "-arguments")
|
|
(var task-argument-fill Token (deref invocation-token))
|
|
(call-on append (field task-argument-fill contents) "-arguments-fill")
|
|
(var task-arguments-name Token (deref invocation-token))
|
|
(token-contents-snprintf task-arguments-name "task-args-%d" next-free-task-args-name)
|
|
(incr next-free-task-args-name)
|
|
(call-on push_back created-task-arguments task-arguments-name)
|
|
|
|
(tokenize-push output
|
|
(var (token-splice-addr task-name) (addr enkiPinnedTask)
|
|
(enkiCreatePinnedTask (field g-task-system task-scheduler)
|
|
task-system-pinned-task-wrapper
|
|
g-task-system-main-thread-index)) ;; Hard-coded to main thread for now
|
|
(var-cast-to (token-splice-addr task-arguments-name)
|
|
(addr (token-splice-addr task-argument-type))
|
|
(malloc (sizeof (type (token-splice-addr task-argument-type)))))
|
|
(var (token-splice-addr pinned-task-arguments-name)
|
|
(addr task-system-pinned-task-arguments)
|
|
(type-cast (malloc (sizeof (type task-system-pinned-task-arguments)))
|
|
(addr task-system-pinned-task-arguments)))
|
|
(scope
|
|
(var task-params (struct enkiParamsPinnedTask)
|
|
(enkiGetParamsPinnedTask (token-splice-addr task-name)))
|
|
((token-splice-addr task-argument-fill) (token-splice-addr task-arguments-name)
|
|
(token-splice-array parsed-arguments))
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-thread-id)
|
|
g-task-system-main-thread-index) ;; Hard coded to main thread
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-to-execute)
|
|
(token-splice invocation-token))
|
|
(set (path (token-splice-addr pinned-task-arguments-name) > task-to-execute-args)
|
|
(token-splice-addr task-arguments-name))
|
|
(set (field task-params pArgs) (token-splice-addr pinned-task-arguments-name))
|
|
(enkiSetParamsPinnedTask (token-splice-addr task-name) task-params))))))
|
|
(scope ;; Regular task (runs on any thread)
|
|
(call-on push_back created-task-sets task-name)
|
|
(if task-size
|
|
(tokenize-push params-setters
|
|
(set (field task-params setSize) (token-splice task-size)))
|
|
(tokenize-push params-setters
|
|
(set (field task-params setSize) 1)))
|
|
(if task-min-range
|
|
(tokenize-push params-setters
|
|
(set (field task-params minRange) (token-splice task-min-range)))
|
|
(tokenize-push params-setters
|
|
(set (field task-params minRange) 1)))
|
|
|
|
(if (call-on empty parsed-arguments)
|
|
(scope ;; No user arguments
|
|
(tokenize-push output
|
|
(var (token-splice-addr task-name) (addr enkiTaskSet)
|
|
(enkiCreateTaskSet (field g-task-system task-scheduler)
|
|
(token-splice invocation-token)))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet)
|
|
(enkiGetParamsTaskSet (token-splice-addr task-name)))
|
|
(token-splice-array params-setters)
|
|
(enkiSetParamsTaskSet (token-splice-addr task-name) task-params))))
|
|
(scope ;; Arguments; do auto-structuring
|
|
(var task-argument-type Token (deref invocation-token))
|
|
(call-on append (field task-argument-type contents) "-arguments")
|
|
(var task-argument-fill Token (deref invocation-token))
|
|
(call-on append (field task-argument-fill contents) "-arguments-fill")
|
|
(var task-arguments-name Token (deref invocation-token))
|
|
(token-contents-snprintf task-arguments-name "task-args-%d" next-free-task-args-name)
|
|
(incr next-free-task-args-name)
|
|
(call-on push_back created-task-arguments task-arguments-name)
|
|
|
|
(tokenize-push output
|
|
(var (token-splice-addr task-name) (addr enkiTaskSet)
|
|
(enkiCreateTaskSet (field g-task-system task-scheduler)
|
|
(token-splice invocation-token)))
|
|
(var-cast-to (token-splice-addr task-arguments-name)
|
|
(addr (token-splice-addr task-argument-type))
|
|
(malloc (sizeof (type (token-splice-addr task-argument-type)))))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet)
|
|
(enkiGetParamsTaskSet (token-splice-addr task-name)))
|
|
(token-splice-array params-setters)
|
|
((token-splice-addr task-argument-fill) (token-splice-addr task-arguments-name)
|
|
(token-splice-array parsed-arguments))
|
|
(set (field task-params pArgs) (token-splice-addr task-arguments-name))
|
|
(enkiSetParamsTaskSet (token-splice-addr task-name) task-params)))))))
|
|
|
|
(var task-depending-arg (template (in std vector) Token))
|
|
(if is-pinned-to-main-thread
|
|
(tokenize-push task-depending-arg
|
|
(enkiGetCompletableFromPinnedTask (token-splice-addr task-name)))
|
|
(tokenize-push task-depending-arg
|
|
(enkiGetCompletableFromTaskSet (token-splice-addr task-name))))
|
|
|
|
;; Sequential dependency
|
|
(unless (or (call-on empty task-to-depend-on-stack)
|
|
(call-on empty (field (call-on back task-to-depend-on-stack) token contents)))
|
|
(var task-to-depend-on (addr Token)
|
|
(addr (field (call-on back task-to-depend-on-stack) token)))
|
|
(when is-debug
|
|
(NoteAtTokenf (deref invocation-token) "After %s,"
|
|
(call-on c_str (path task-to-depend-on > contents))))
|
|
(var dependency-name Token (deref invocation-token))
|
|
(token-contents-snprintf dependency-name
|
|
"sequential-dependency-%d" next-free-dependency-id)
|
|
(call-on push_back created-dependencies dependency-name)
|
|
(var task-to-depend-on-arg (template (in std vector) Token))
|
|
(cond
|
|
((= task-type-completable (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(token-splice task-to-depend-on)))
|
|
((= task-type-task-set (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromTaskSet (token-splice task-to-depend-on))))
|
|
((= task-type-pinned-task (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromPinnedTask (token-splice task-to-depend-on)))))
|
|
|
|
(tokenize-push output
|
|
(var (token-splice-addr dependency-name) (addr enkiDependency)
|
|
(enkiCreateDependency (field g-task-system task-scheduler)))
|
|
(enkiSetDependency (token-splice-addr dependency-name)
|
|
(token-splice-array task-to-depend-on-arg)
|
|
(token-splice-array task-depending-arg)))
|
|
(incr next-free-dependency-id))
|
|
|
|
(when is-debug
|
|
(NoteAtTokenf (deref invocation-token) "invoke %s %s."
|
|
(call-on c_str (field task-name contents))
|
|
(? (= task-run-context-sequential (field (call-on back context-stack) state))
|
|
"in sequence" "in parallel")))
|
|
|
|
;; Sync point dependency
|
|
(when (and (= task-run-context-parallel (field (call-on back context-stack) state))
|
|
(not (call-on empty parallel-sync-point-stack)))
|
|
(when is-debug
|
|
(NoteAtTokenf (deref invocation-token) "Sync point %s depends on %s"
|
|
(call-on c_str (field (call-on back parallel-sync-point-stack) contents))
|
|
(call-on c_str (path invocation-token > contents))))
|
|
(var dependency-name Token (deref invocation-token))
|
|
(token-contents-snprintf dependency-name
|
|
"sync-dependency-%d" next-free-dependency-id)
|
|
(call-on push_back created-dependencies dependency-name)
|
|
(tokenize-push output
|
|
(var (token-splice-addr dependency-name) (addr enkiDependency)
|
|
(enkiCreateDependency (field g-task-system task-scheduler)))
|
|
(enkiSetDependency (token-splice-addr dependency-name)
|
|
(token-splice-array task-depending-arg)
|
|
(token-splice-addr (call-on back parallel-sync-point-stack))))
|
|
(incr next-free-dependency-id))
|
|
|
|
(when (= task-run-context-sequential (field (call-on back context-stack) state))
|
|
(when is-debug (Logf "(Sequential) Setting task-to-depend-on to %s\n"
|
|
(call-on c_str (field task-name contents))))
|
|
(set (field (call-on back task-to-depend-on-stack) token) task-name)
|
|
(set (field (call-on back task-to-depend-on-stack) type)
|
|
(? is-pinned-to-main-thread task-type-pinned-task task-type-task-set)))
|
|
|
|
(set current-token-index (getNextArgument tokens current-token-index
|
|
(field (call-on back context-stack) end-state-index)))
|
|
;; Absorb next increment
|
|
(decr current-token-index))))
|
|
(true
|
|
(ErrorAtToken (at current-token-index tokens)
|
|
"unexpected symbol or string. Expected only task invocations or task run " \
|
|
"context specifiers, e.g. (parallel ...)")
|
|
(return false)))
|
|
|
|
;; Pop contexts
|
|
(while (and (>= (+ 1 current-token-index)
|
|
(field (call-on back context-stack) end-state-index))
|
|
(< 1 (call-on size context-stack)))
|
|
(when is-debug (NoteAtToken (at current-token-index tokens) "Pop"))
|
|
(var state-before-pop task-run-context-state (field (call-on back context-stack) state))
|
|
(call-on pop_back context-stack)
|
|
(var state-after-pop task-run-context-state (field (call-on back context-stack) state))
|
|
|
|
;; When popping a sequential inside a parallel, make the parent parallel's sync point depend
|
|
;; on final sync point or task in sequential
|
|
(when (and (= state-before-pop task-run-context-sequential)
|
|
(= state-after-pop task-run-context-parallel)
|
|
(not (call-on empty parallel-sync-point-stack)))
|
|
(when is-debug
|
|
(Logf "Sequential %s required by %s\n"
|
|
(call-on c_str (field (call-on back task-to-depend-on-stack) token contents))
|
|
(call-on c_str (field (call-on back parallel-sync-point-stack) contents))))
|
|
;; TODO: Big copy-paste
|
|
(var dependency-name Token (call-on back parallel-sync-point-stack))
|
|
(token-contents-snprintf dependency-name
|
|
"parallel-final-dependency-%d" next-free-dependency-id)
|
|
(call-on push_back created-dependencies dependency-name)
|
|
(var task-to-depend-on-arg (template (in std vector) Token))
|
|
(cond
|
|
((= task-type-completable (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token))))
|
|
((= task-type-task-set (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromTaskSet
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token)))))
|
|
((= task-type-pinned-task (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromPinnedTask
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token))))))
|
|
(tokenize-push output
|
|
(var (token-splice-addr dependency-name) (addr enkiDependency)
|
|
(enkiCreateDependency (field g-task-system task-scheduler)))
|
|
(enkiSetDependency (token-splice-addr dependency-name)
|
|
(token-splice-array task-to-depend-on-arg)
|
|
(token-splice-addr (call-on back parallel-sync-point-stack))))
|
|
(incr next-free-dependency-id))
|
|
|
|
(when (and (= 1 (call-on size context-stack))
|
|
(= state-before-pop task-run-context-parallel))
|
|
(when is-debug (Log "Clear start-tasks-are-parallel\n"))
|
|
(set start-tasks-are-parallel false))
|
|
|
|
;; We never allow nested sequentials, which means the next task never has to depend on the
|
|
;; final sequential
|
|
(when (= state-before-pop task-run-context-sequential)
|
|
(when is-debug (Log "Pop task-to-depend-on\n"))
|
|
(call-on pop_back task-to-depend-on-stack))
|
|
|
|
(when (and (= state-before-pop task-run-context-parallel)
|
|
(not (call-on empty parallel-sync-point-stack)))
|
|
(when is-debug (Logf "(Parallel sync) Setting task-to-depend-on to %s\n"
|
|
(call-on c_str (field (call-on back parallel-sync-point-stack)
|
|
contents))))
|
|
(set (field (call-on back task-to-depend-on-stack) token)
|
|
(call-on back parallel-sync-point-stack))
|
|
(set (field (call-on back task-to-depend-on-stack) type) task-type-completable)
|
|
(call-on pop_back parallel-sync-point-stack))
|
|
|
|
;; Skip closing paren
|
|
(incr current-token-index)))
|
|
|
|
(scope ;; Create completion action which will clean up all allocations
|
|
(when is-debug
|
|
(Logf "Final dependency: %s\n"
|
|
(call-on c_str (field (call-on back task-to-depend-on-stack) token contents)))
|
|
(Log "\ncreated-task-sets:\n")
|
|
(prettyPrintTokens created-task-sets)
|
|
(Log "\ncreated-pinned-tasks:\n")
|
|
(prettyPrintTokens created-pinned-tasks)
|
|
(Log "\ncreated-pinned-task-arguments:\n")
|
|
(prettyPrintTokens created-pinned-task-arguments)
|
|
(Log "\ncreated-completables:\n")
|
|
(prettyPrintTokens created-completables)
|
|
(Log "\ncreated-dependencies:\n")
|
|
(prettyPrintTokens created-dependencies)
|
|
(Log "\ncreated-task-arguments:\n")
|
|
(prettyPrintTokens created-task-arguments))
|
|
|
|
;; Setup the completion action
|
|
(var task-to-depend-on-arg (template (in std vector) Token))
|
|
(cond ;; TODO Copy-paste!
|
|
((= task-type-completable (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token))))
|
|
((= task-type-task-set (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromTaskSet
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token)))))
|
|
((= task-type-pinned-task (field (call-on back task-to-depend-on-stack) type))
|
|
(tokenize-push task-to-depend-on-arg
|
|
(enkiGetCompletableFromPinnedTask
|
|
(token-splice-addr (field (call-on back task-to-depend-on-stack) token))))))
|
|
(tokenize-push output
|
|
(var cleanup-action-arguments (addr task-system-execute-data)
|
|
(type-cast (malloc (sizeof (type task-system-execute-data)))
|
|
(addr task-system-execute-data)))
|
|
(memset cleanup-action-arguments 0 (sizeof (type task-system-execute-data)))
|
|
(var cleanup-action (addr enkiCompletionAction)
|
|
(enkiCreateCompletionAction (field g-task-system task-scheduler) null ;; No pre-complete
|
|
task-system-cleanup-execution-action))
|
|
(var action-params (struct enkiParamsCompletionAction)
|
|
(enkiGetParamsCompletionAction cleanup-action))
|
|
(set (field action-params pArgsPostComplete) cleanup-action-arguments)
|
|
(set (field action-params pDependency) (token-splice-array task-to-depend-on-arg))
|
|
(enkiSetParamsCompletionAction cleanup-action action-params)
|
|
|
|
;; So we can delete ourself
|
|
(set (path cleanup-action-arguments > cleanup-action) cleanup-action))
|
|
|
|
;; Fill task execute data so we know what to delete
|
|
(var base-token Token (at startTokenIndex tokens))
|
|
(set (field base-token type) TokenType_Symbol)
|
|
|
|
(var type-task-sets-field Token base-token)
|
|
(set (field type-task-sets-field contents) "enkiTaskSet")
|
|
(var type-pinned-tasks-field Token base-token)
|
|
(set (field type-pinned-tasks-field contents) "enkiPinnedTask")
|
|
(var type-pinned-task-arguments-field Token base-token)
|
|
(set (field type-pinned-task-arguments-field contents) "task-system-pinned-task-arguments")
|
|
(var type-completables-field Token base-token)
|
|
(set (field type-completables-field contents) "enkiCompletable")
|
|
(var type-dependencies-field Token base-token)
|
|
(set (field type-dependencies-field contents) "enkiDependency")
|
|
(var type-task-arguments-field Token base-token)
|
|
(set (field type-task-arguments-field contents) "void")
|
|
|
|
(defstruct cleanup-type-field
|
|
field-name (addr (const char))
|
|
type Token
|
|
names (addr (template (in std vector) Token)))
|
|
(var things-to-cleanup-by-type (array cleanup-type-field)
|
|
(array
|
|
(array "task-sets" type-task-sets-field
|
|
(addr created-task-sets))
|
|
(array "pinned-tasks" type-pinned-tasks-field
|
|
(addr created-pinned-tasks))
|
|
(array "pinned-task-arguments" type-pinned-task-arguments-field
|
|
(addr created-pinned-task-arguments))
|
|
(array "completables" type-completables-field
|
|
(addr created-completables))
|
|
(array "dependencies" type-dependencies-field
|
|
(addr created-dependencies))
|
|
(array "task-arguments" type-task-arguments-field
|
|
(addr created-task-arguments))))
|
|
(each-in-array things-to-cleanup-by-type i
|
|
(var current-type (addr cleanup-type-field) (addr (at i things-to-cleanup-by-type)))
|
|
(when (call-on-ptr empty (path current-type > names))
|
|
(continue))
|
|
(var field-token Token base-token)
|
|
(set (field field-token contents) (path current-type > field-name))
|
|
(var size-token Token (at startTokenIndex tokens))
|
|
(token-contents-snprintf size-token "%d"
|
|
(type-cast (call-on-ptr size (path current-type > names)) int))
|
|
(set (field size-token type) TokenType_Symbol)
|
|
(var number-field-name Token field-token)
|
|
(token-contents-snprintf number-field-name "num-%s" (path current-type > field-name))
|
|
(tokenize-push output
|
|
(var (token-splice-addr field-token)
|
|
(addr (addr (token-splice-addr (path current-type > type))))
|
|
(type-cast (calloc (token-splice-addr size-token)
|
|
(sizeof (type (addr (token-splice-addr (path current-type > type))))))
|
|
(addr (addr (token-splice-addr (path current-type > type))))))
|
|
(set (path cleanup-action-arguments > (token-splice-addr field-token))
|
|
(token-splice-addr field-token))
|
|
(set (path cleanup-action-arguments > (token-splice-addr number-field-name))
|
|
(token-splice-addr size-token)))
|
|
|
|
(var name-index int 0)
|
|
(for-in name (ref Token) (deref (path current-type > names))
|
|
(var index Token field-token)
|
|
(token-contents-snprintf index "%d" name-index)
|
|
(tokenize-push output
|
|
(set (at (token-splice-addr index) (token-splice-addr field-token))
|
|
(token-splice-addr name)))
|
|
(incr name-index))))
|
|
|
|
;; Kicking off the system:
|
|
;; - Push first task of sequence, if any
|
|
;; - Push all parallels if first in sequence
|
|
(for-in start-task (ref depend-task) starting-tasks
|
|
(cond
|
|
((= task-type-task-set (field start-task type))
|
|
(tokenize-push output (enkiAddTaskSet (field g-task-system task-scheduler)
|
|
(token-splice-addr (field start-task token)))))
|
|
((= task-type-pinned-task (field start-task type))
|
|
(tokenize-push output (enkiAddPinnedTask (field g-task-system task-scheduler)
|
|
(token-splice-addr (field start-task token)))))
|
|
(true
|
|
(ErrorAtTokenf (field start-task token) "unexpected starting task type %d"
|
|
(field start-task type)))))
|
|
|
|
(return true))
|
|
|
|
;;
|
|
;; Building
|
|
;;
|
|
(add-dependency-git-submodule clone-enkiTS
|
|
"https://github.com/dougbinks/enkiTS"
|
|
"Dependencies/enkiTS")
|
|
(add-cpp-build-dependency "TaskScheduler.cpp" "TaskScheduler_c.cpp")
|
|
(comptime-cond
|
|
('Unix
|
|
(add-library-dependency "pthread"))) ;; Enki's interface to threads on Linux
|
|
|
|
;;
|
|
;; Tests
|
|
;;
|
|
(comptime-cond
|
|
('auto-test
|
|
(import "CHelpers.cake") ;; array-size
|
|
(c-import "stdio.h" "stdlib.h")
|
|
|
|
;; Only used on hand-written enki calls
|
|
(var task-scheduler (addr enkiTaskScheduler) null)
|
|
|
|
(def-task test-task (num int str (addr (const char)))
|
|
(fprintf stderr "[thread %d] args: %d %s\n" task-thread-id num str))
|
|
|
|
(def-task my-long-task-with-message (message (addr (const char)))
|
|
(each-in-range (- task-range-end task-range-start) i
|
|
(fprintf stderr "[thread %d] %s %d\n" task-thread-id message (+ i task-range-start))))
|
|
|
|
(def-task some-other-task ()
|
|
(fprintf stderr "[thread %d] Hello\n" task-thread-id))
|
|
|
|
(def-task on-complete-task ()
|
|
(fprintf stderr "[thread %d] Tasks complete\n" task-thread-id))
|
|
|
|
;; Print 0-100 in no particular order (distributed on many threads)
|
|
(defun-local my-long-task (start uint32_t end uint32_t threadnum uint32_t args (addr void))
|
|
;; (fprintf stderr "[thread %d] %d - %d\n" threadnum start end)
|
|
(each-in-range (- end start) i
|
|
(fprintf stderr "[thread %d] %d\n" threadnum (+ i start))))
|
|
|
|
(defun-local after-my-long-task (start uint32_t end uint32_t threadnum uint32_t args (addr void))
|
|
(fprintf stderr "[thread %d] All my dependencies completed\n" threadnum))
|
|
|
|
;; How to clean up the task which invoked this cleanup? Answer: enki Completables
|
|
(def-task cleanup-complete-tasks (my-task (addr enkiTaskSet) my-dependent-task (addr enkiTaskSet)
|
|
my-other-dependent-task (addr enkiTaskSet)
|
|
dependencies (addr (addr enkiDependency))
|
|
num-dependencies size_t)
|
|
(enkiDeleteTaskSet task-scheduler my-task)
|
|
(enkiDeleteTaskSet task-scheduler my-dependent-task)
|
|
(enkiDeleteTaskSet task-scheduler my-other-dependent-task)
|
|
(each-in-range num-dependencies i
|
|
(enkiDeleteDependency task-scheduler (at i dependencies)))
|
|
(fprintf stderr "[thread %d] Cleaned up tasks\n" task-thread-id))
|
|
|
|
(defun-local on-my-long-task-complete (args (addr void) threadnum uint32_t)
|
|
(fprintf stderr "[thread %d] All tasks completed\n" threadnum)
|
|
|
|
(fprintf stderr "\nCreating tasks within the complete call!\n")
|
|
(var my-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler my-long-task))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-task))
|
|
(set (field task-params setSize) 100)
|
|
(set (field task-params minRange) 10)
|
|
(enkiSetParamsTaskSet my-task task-params))
|
|
|
|
;; This time, use dependencies rather than completion action
|
|
(var my-dependent-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler after-my-long-task))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-dependent-task))
|
|
(set (field task-params setSize) 1)
|
|
(set (field task-params minRange) 1)
|
|
(enkiSetParamsTaskSet my-dependent-task task-params))
|
|
|
|
(var my-other-dependent-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler test-task))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-other-dependent-task))
|
|
(var-static my-args test-task-arguments
|
|
(array 42 "Hello, TaskSystem!"))
|
|
(set (field task-params pArgs) (addr my-args))
|
|
(set (field task-params setSize) 1)
|
|
(set (field task-params minRange) 1)
|
|
(enkiSetParamsTaskSet my-other-dependent-task task-params))
|
|
|
|
(var num-dependencies int 4)
|
|
(var-cast-to dependencies (addr (addr enkiDependency)) (calloc num-dependencies
|
|
(sizeof (type (addr enkiDependency)))))
|
|
(var free-dependency (addr (addr enkiDependency)) dependencies)
|
|
|
|
(scope
|
|
(var dependency (addr enkiDependency) (enkiCreateDependency task-scheduler))
|
|
(enkiSetDependency dependency
|
|
(enkiGetCompletableFromTaskSet my-task)
|
|
(enkiGetCompletableFromTaskSet my-dependent-task))
|
|
(set (deref free-dependency) dependency)
|
|
(incr free-dependency))
|
|
(scope
|
|
(var dependency (addr enkiDependency) (enkiCreateDependency task-scheduler))
|
|
(enkiSetDependency dependency
|
|
(enkiGetCompletableFromTaskSet my-task)
|
|
(enkiGetCompletableFromTaskSet my-other-dependent-task))
|
|
(set (deref free-dependency) dependency)
|
|
(incr free-dependency))
|
|
|
|
(var cleanup-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler cleanup-complete-tasks))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet cleanup-task))
|
|
(var-cast-to cleanup-args (addr cleanup-complete-tasks-arguments)
|
|
(malloc (sizeof (type cleanup-complete-tasks-arguments))))
|
|
(cleanup-complete-tasks-arguments-fill cleanup-args my-task my-dependent-task my-other-dependent-task
|
|
dependencies num-dependencies)
|
|
(set (field task-params pArgs) cleanup-args)
|
|
(set (field task-params setSize) 1)
|
|
(set (field task-params minRange) 1)
|
|
(enkiSetParamsTaskSet cleanup-task task-params))
|
|
(scope
|
|
(var dependency (addr enkiDependency) (enkiCreateDependency task-scheduler))
|
|
(enkiSetDependency dependency
|
|
(enkiGetCompletableFromTaskSet my-other-dependent-task)
|
|
(enkiGetCompletableFromTaskSet cleanup-task))
|
|
(set (deref free-dependency) dependency)
|
|
(incr free-dependency))
|
|
(scope
|
|
(var dependency (addr enkiDependency) (enkiCreateDependency task-scheduler))
|
|
(enkiSetDependency dependency
|
|
(enkiGetCompletableFromTaskSet my-dependent-task)
|
|
(enkiGetCompletableFromTaskSet cleanup-task))
|
|
(set (deref free-dependency) dependency)
|
|
(incr free-dependency))
|
|
|
|
;; No need to add my-dependent-task etc.
|
|
(enkiAddTaskSet task-scheduler my-task))
|
|
|
|
(defun-nodecl disabled-test-enki-task-system (&return int)
|
|
(set task-scheduler (enkiNewTaskScheduler))
|
|
(enkiInitTaskScheduler task-scheduler)
|
|
|
|
(fprintf stderr "Created %d threads\n" (enkiGetNumTaskThreads task-scheduler))
|
|
|
|
(var my-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler my-long-task))
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-task))
|
|
(set (field task-params setSize) 100)
|
|
(set (field task-params minRange) 10)
|
|
(enkiSetParamsTaskSet my-task task-params)
|
|
|
|
(var completion-action (addr enkiCompletionAction)
|
|
(enkiCreateCompletionAction task-scheduler null on-my-long-task-complete))
|
|
(var completion-args (struct enkiParamsCompletionAction)
|
|
(enkiGetParamsCompletionAction completion-action))
|
|
(set (field completion-args pDependency) (enkiGetCompletableFromTaskSet my-task))
|
|
(enkiSetParamsCompletionAction completion-action completion-args)
|
|
|
|
(enkiAddTaskSet task-scheduler my-task)
|
|
|
|
(enkiWaitforAllAndShutdown task-scheduler)
|
|
(enkiDeleteTaskSet task-scheduler my-task)
|
|
(enkiDeleteCompletionAction task-scheduler completion-action)
|
|
(enkiDeleteTaskScheduler task-scheduler)
|
|
(return 0))
|
|
|
|
;;
|
|
;; Test completion action changing the next task's args
|
|
;;
|
|
|
|
(defun-local discover-task (start uint32_t end uint32_t threadnum uint32_t args (addr void))
|
|
;; (fprintf stderr "[thread %d] %d - %d\n" threadnum start end)
|
|
(fprintf stderr "[thread %d] Discovering work to do\n" threadnum))
|
|
|
|
(defun-local utilize-task (start uint32_t end uint32_t threadnum uint32_t args (addr void))
|
|
(each-in-range (- end start) i
|
|
(fprintf stderr "[thread %d] utilize %d\n" threadnum (+ i start))))
|
|
|
|
(defun-local on-discover-task-complete (args (addr void) threadnum uint32_t)
|
|
(fprintf stderr "[thread %d] Set task params\n" threadnum)
|
|
(var-cast-to my-utilize-task (addr enkiTaskSet) args)
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-utilize-task))
|
|
;; Discovered amount of work would go here
|
|
(set (field task-params setSize) 30)
|
|
(set (field task-params minRange) 5)
|
|
(enkiSetParamsTaskSet my-utilize-task task-params))
|
|
|
|
(defun-nodecl test--enki-task-system-completion (&return int)
|
|
(set task-scheduler (enkiNewTaskScheduler))
|
|
(enkiInitTaskScheduler task-scheduler)
|
|
|
|
(fprintf stderr "Created %d threads\n" (enkiGetNumTaskThreads task-scheduler))
|
|
|
|
(var my-discover-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler discover-task))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-discover-task))
|
|
(set (field task-params setSize) 1)
|
|
(set (field task-params minRange) 1)
|
|
(enkiSetParamsTaskSet my-discover-task task-params))
|
|
|
|
(var my-utilize-task (addr enkiTaskSet) (enkiCreateTaskSet task-scheduler utilize-task))
|
|
(scope
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet my-utilize-task))
|
|
(set (field task-params setSize) 1)
|
|
(set (field task-params minRange) 1)
|
|
(enkiSetParamsTaskSet my-utilize-task task-params))
|
|
|
|
(var completion-action (addr enkiCompletionAction)
|
|
(enkiCreateCompletionAction task-scheduler on-discover-task-complete null))
|
|
(var completion-args (struct enkiParamsCompletionAction)
|
|
(enkiGetParamsCompletionAction completion-action))
|
|
(set (field completion-args pDependency) (enkiGetCompletableFromTaskSet my-discover-task))
|
|
(set (field completion-args pArgsPreComplete) my-utilize-task)
|
|
(enkiSetParamsCompletionAction completion-action completion-args)
|
|
|
|
(scope ;; Leak
|
|
(var dependency (addr enkiDependency) (enkiCreateDependency task-scheduler))
|
|
(enkiSetDependency dependency
|
|
(enkiGetCompletableFromCompletionAction completion-action)
|
|
(enkiGetCompletableFromTaskSet my-utilize-task)))
|
|
|
|
(enkiAddTaskSet task-scheduler my-discover-task)
|
|
|
|
(enkiWaitforAllAndShutdown task-scheduler)
|
|
(enkiDeleteTaskSet task-scheduler my-discover-task)
|
|
(enkiDeleteTaskSet task-scheduler my-utilize-task)
|
|
(enkiDeleteCompletionAction task-scheduler completion-action)
|
|
(enkiDeleteTaskScheduler task-scheduler)
|
|
(return 0))
|
|
|
|
;;
|
|
;; Test task-system macros
|
|
;;
|
|
(def-task discover-task-sys ()
|
|
(fprintf stderr "[thread %d] Discovering work to do\n" task-thread-id))
|
|
(def-task on-discover-task-complete-sys (utilize-task (addr enkiTaskSet))
|
|
(fprintf stderr "[thread %d] Set task params\n" task-thread-id)
|
|
(var-cast-to my-utilize-task (addr enkiTaskSet) args)
|
|
(var task-params (struct enkiParamsTaskSet) (enkiGetParamsTaskSet utilize-task))
|
|
;; Discovered amount of work would go here
|
|
(set (field task-params setSize) 30)
|
|
(set (field task-params minRange) 5)
|
|
(enkiSetParamsTaskSet utilize-task task-params))
|
|
(def-task utilize-task-sys ()
|
|
(each-in-range (- task-range-end task-range-start) i
|
|
(fprintf stderr "[thread %d] utilize %d\n" task-thread-id (+ i task-range-start))))
|
|
|
|
(defun-nodecl test--task-systems (&return int)
|
|
(task-system-initialize)
|
|
|
|
(scope
|
|
(fprintf stderr "\nTest completion setting next task work\n\n")
|
|
(task-system-execute
|
|
(discover-task-sys)
|
|
(utilize-task-sys
|
|
:before-start (on-discover-task-complete-sys (task utilize-task-sys))))
|
|
(task-system-wait-for-all))
|
|
|
|
(scope
|
|
(fprintf stderr "\nvariant 1\n\n")
|
|
(task-system-execute
|
|
(test-task 0 "Very start: Phase 0")
|
|
(parallel
|
|
(some-other-task)
|
|
(sequential
|
|
(parallel
|
|
(my-long-task-with-message :task-size 20 :task-min-range 5 "Here's a number:")
|
|
(test-task 1 "Some time in Phase 1"))
|
|
(test-task 2 "After phase 1 (Phase 2)"))
|
|
(test-task 1 "Also some time in Phase 1"))
|
|
(test-task 3 "After Phase 2")
|
|
(on-complete-task))
|
|
(task-system-wait-for-all))
|
|
|
|
(scope
|
|
(fprintf stderr "\nvariant 2\n\n")
|
|
(task-system-execute
|
|
(parallel
|
|
(some-other-task)
|
|
(sequential
|
|
(parallel
|
|
(my-long-task-with-message :task-size 20 :task-min-range 5 "Here's a number:")
|
|
(test-task 1 "Some time in Phase 1"))
|
|
(test-task 2 "After phase 1 (Phase 2)"))
|
|
(test-task 1 "Also some time in Phase 1"))
|
|
(test-task 3 "After Phase 2")
|
|
(on-complete-task))
|
|
(task-system-wait-for-all))
|
|
|
|
(scope
|
|
(fprintf stderr "\nvariant 3: Pinned task test\n\n")
|
|
(task-system-execute
|
|
(parallel
|
|
(sequential
|
|
(parallel
|
|
(my-long-task-with-message :task-size 20 :task-min-range 5 "Here's a number:")
|
|
(test-task :pin-to-main-thread 1 "Some time in Phase 1, only on main thread"))
|
|
(test-task 2 :pin-to-main-thread "After phase 1 (Phase 2), only on main thread"))
|
|
(test-task 1 "Also some time in Phase 1"))
|
|
(test-task 3 "After Phase 2")
|
|
(on-complete-task :pin-to-main-thread))
|
|
(task-system-wait-for-all))
|
|
|
|
(scope
|
|
(fprintf stderr "\nvariant 4: Completion action after parallel\n\n")
|
|
(task-system-execute
|
|
(parallel
|
|
(my-long-task-with-message :task-size 20 :task-min-range 5 "Here's a number:")
|
|
(test-task 1 "Parallel buddy 1")
|
|
(test-task 1 "Parallel buddy 2")))
|
|
(task-system-wait-for-all))
|
|
|
|
(task-system-shutdown)
|
|
(return 0))))
|
|
|