GameLib is a collection of libraries for creating applications in Cakelisp.
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.

1036 lines
52 KiB

;; TaskSystem.cake: Interface into task-based multithreading
(import "ComptimeHelpers.cake" "CHelpers.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"))
(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 (* 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 (* 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 (* enkiCompletionAction) ;; The action that will free these things
task-sets (* (* enkiTaskSet))
num-task-sets (unsigned int)
pinned-tasks (* (* enkiPinnedTask))
num-pinned-tasks (unsigned int)
pinned-task-arguments (* (* task-system-pinned-task-arguments))
num-pinned-task-arguments (unsigned int)
completables (* (* enkiCompletable))
num-completables (unsigned int)
dependencies (* (* enkiDependency))
num-dependencies (unsigned int)
;; Created by us
task-arguments (* (* void))
num-task-arguments (unsigned int))
(defstruct task-system
task-scheduler (* enkiTaskScheduler))
(var-global g-task-system task-system (array 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)))
;; 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 (<> (in std vector) Token))
(var args-definition (<> (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 (% 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 (* (token-splice-addr args-struct-name))
(type-cast args-void (* (token-splice-addr args-struct-name)))))
(var args-definition-fields (<> (in std vector) Token))
(var arguments-to-structure (<> (in std vector) Token))
(each-token-argument-in tokens (+ 1 parameters) end-token-index current-token-index
(var name-token (* (const Token)) (addr (at current-token-index tokens)))
(incr current-token-index)
(var type-token (* (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 (* (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 (* void))
(token-splice-array destructured-arguments)
(token-splice-rest body tokens)))
(return true))
(defun task-system-pinned-task-wrapper (args-void (* void))
(var pinned-task-args (* task-system-pinned-task-arguments)
(type-cast args-void (* 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 (* void) task-thread-id (unsigned int))
(var-cast-to args (* 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 (<> (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 (<> (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 (<> (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 (<> (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 (<> (in std vector) depend-task))
(var start-tasks-are-parallel bool false)
;; Memory management
(var created-task-sets (<> (in std vector) Token))
(var created-pinned-tasks (<> (in std vector) Token))
(var created-pinned-task-arguments (<> (in std vector) Token))
(var created-completables (<> (in std vector) Token))
(var created-dependencies (<> (in std vector) Token))
(var created-task-arguments (<> (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 (* (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 (* (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) (* 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 (* (const Token)) null)
(var task-min-range (* (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 (* (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))
(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 (<> (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) (* enkiPinnedTask)
(enkiCreatePinnedTask (field g-task-system task-scheduler)
task-system-pinned-task-wrapper
0)) ;; Hard-coded to main thread for now
(var (token-splice-addr pinned-task-arguments-name)
(* task-system-pinned-task-arguments)
(type-cast (malloc (sizeof (type task-system-pinned-task-arguments)))
(* task-system-pinned-task-arguments)))
(scope
(var task-params enkiParamsPinnedTask (enkiGetParamsPinnedTask (token-splice-addr task-name)))
(set (path (token-splice-addr pinned-task-arguments-name) > task-thread-id)
0) ;; 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) (* enkiPinnedTask)
(enkiCreatePinnedTask (field g-task-system task-scheduler)
task-system-pinned-task-wrapper
0)) ;; Hard-coded to main thread for now
(var-cast-to (token-splice-addr task-arguments-name)
(* (token-splice-addr task-argument-type))
(malloc (sizeof (type (token-splice-addr task-argument-type)))))
(var (token-splice-addr pinned-task-arguments-name)
(* task-system-pinned-task-arguments)
(type-cast (malloc (sizeof (type task-system-pinned-task-arguments)))
(* task-system-pinned-task-arguments)))
(scope
(var task-params 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)
0) ;; 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) (* enkiTaskSet)
(enkiCreateTaskSet (field g-task-system task-scheduler)
(token-splice invocation-token)))
(scope
(var task-params 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) (* enkiTaskSet)
(enkiCreateTaskSet (field g-task-system task-scheduler)
(token-splice invocation-token)))
(var-cast-to (token-splice-addr task-arguments-name)
(* (token-splice-addr task-argument-type))
(malloc (sizeof (type (token-splice-addr task-argument-type)))))
(scope
(var task-params 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 (<> (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 (* 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 (<> (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) (* 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) (* 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 (<> (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) (* 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 (<> (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 (* task-system-execute-data)
(type-cast (malloc (sizeof (type task-system-execute-data)))
(* task-system-execute-data)))
(memset cleanup-action-arguments 0 (sizeof (type task-system-execute-data)))
(var cleanup-action (* enkiCompletionAction)
(enkiCreateCompletionAction (field g-task-system task-scheduler) null
task-system-cleanup-execution-action))
(var action-params 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 created-task-sets-field Token base-token)
(set (field created-task-sets-field contents) "task-sets")
(var created-pinned-tasks-field Token base-token)
(set (field created-pinned-tasks-field contents) "pinned-tasks")
(var created-pinned-task-arguments-field Token base-token)
(set (field created-pinned-task-arguments-field contents) "pinned-task-arguments")
(var created-completables-field Token base-token)
(set (field created-completables-field contents) "completables")
(var created-dependencies-field Token base-token)
(set (field created-dependencies-field contents) "dependencies")
(var created-task-arguments-field Token base-token)
(set (field created-task-arguments-field contents) "task-arguments")
(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 Token
type Token
names (* (<> (in std vector) Token)))
(var things-to-cleanup-by-type ([] cleanup-type-field)
(array
(array created-task-sets-field type-task-sets-field
(addr created-task-sets))
(array created-pinned-tasks-field type-pinned-tasks-field
(addr created-pinned-tasks))
(array created-pinned-task-arguments-field type-pinned-task-arguments-field
(addr created-pinned-task-arguments))
(array created-completables-field type-completables-field
(addr created-completables))
(array created-dependencies-field type-dependencies-field
(addr created-dependencies))
(array created-task-arguments-field type-task-arguments-field
(addr created-task-arguments))))
(each-in-array things-to-cleanup-by-type i
(var current-type (* cleanup-type-field) (addr (at i things-to-cleanup-by-type)))
(when (call-on-ptr empty (path current-type > names))
(continue))
(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 (path current-type > field))
(token-contents-snprintf number-field-name "num-%s"
(call-on c_str (path current-type > field . contents)))
(tokenize-push output
(var (token-splice-addr (path current-type > field))
(* (* (token-splice-addr (path current-type > type))))
(type-cast (calloc (token-splice-addr size-token)
(sizeof (type (* (token-splice-addr (path current-type > type))))))
(* (* (token-splice-addr (path current-type > type))))))
(set (path cleanup-action-arguments > (token-splice-addr (path current-type > field)))
(token-splice-addr (path current-type > field)))
(set (path cleanup-action-arguments > (token-splice-addr number-field-name))
(token-splice-addr size-token)))
(var name-index int 0)
(for-in name (& Token) (deref (path current-type > names))
(var index Token (path current-type > field))
(token-contents-snprintf index "%d" name-index)
(tokenize-push output
(set (at (token-splice-addr index) (token-splice-addr (path current-type > field)))
(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 (& 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)))))