-
-
Notifications
You must be signed in to change notification settings - Fork 116
/
useTask.ts
174 lines (159 loc) · 4.46 KB
/
useTask.ts
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import { getContext, onDestroy } from 'svelte'
import { readable, writable, type Readable } from 'svelte/store'
import { DAG, type Key, type Stage, type Task } from '../frame-scheduling'
import { browser } from '../lib/browser'
import type { ThrelteInternalContext } from '../lib/contexts'
import { useThrelte } from './useThrelte'
export type ThrelteUseTask = {
task: Task
stop: () => void
start: () => void
started: Readable<boolean>
}
export type ThrelteUseTaskOptions = {
/**
* If false, the task will not be started automatically and must be started
* by invoking the `start` function. Defaults to true.
*/
autoStart?: boolean
/**
* If false, the task handler will not automatically invalidate the task.
* This is useful if you want to manually invalidate the task. Defaults to
* true.
*/
autoInvalidate?: boolean
/**
* The task will be added to the stage after the specified task.
*/
after?: (Key | Task) | (Key | Task)[]
/**
* The task will be added to the stage before the specified task.
*/
before?: (Key | Task) | (Key | Task)[]
/**
* The stage to add the task to. Defaults to the main stage. If a task object
* is provided to `after` or `before`, the stage of that task will be used.
*/
stage?: Key | Stage
}
/**
* Adds a handler to threltes unified render loop.∏
*
* `start` and `stop` functions are returned and the options allow setting the
* handler to not start automatically.
*
* Use the options `after` and `before` to control the order of execution. Add
* the task to a specific stage with the option `stage`.
*
* @param {(ctx: ThrelteContext, delta: number) => void} fn callback function
* @param {ThrelteUseTaskOptions} options options
* @returns {ThrelteUseTask}
*/
export function useTask(
fn: (delta: number) => void,
options?: ThrelteUseTaskOptions
): ThrelteUseTask
export function useTask(
key: Key,
fn: (delta: number) => void,
options?: ThrelteUseTaskOptions
): ThrelteUseTask
export function useTask(
keyOrFn: Key | ((delta: number) => void),
fnOrOptions?: ((delta: number) => void) | ThrelteUseTaskOptions,
options?: ThrelteUseTaskOptions
): ThrelteUseTask {
if (!browser) {
return {
task: undefined as any,
start: () => undefined,
stop: () => undefined,
started: readable(false)
}
}
let key: Key
let fn: (delta: number) => void
let opts: ThrelteUseTaskOptions | undefined
if (DAG.isKey(keyOrFn)) {
key = keyOrFn
fn = fnOrOptions as (delta: number) => void
opts = options
} else {
key = Symbol('useTask')
fn = keyOrFn
opts = fnOrOptions as ThrelteUseTaskOptions | undefined
}
const ctx = useThrelte()
let stage: Stage = ctx.mainStage
if (opts) {
if (opts.stage) {
if (DAG.isValue(opts.stage)) {
stage = opts.stage
} else {
const maybeStage = ctx.scheduler.getStage(opts.stage)
if (!maybeStage) {
throw new Error(`No stage found with key ${opts.stage.toString()}`)
}
stage = maybeStage
}
} else if (opts.after) {
if (Array.isArray(opts.after)) {
for (let index = 0; index < opts.after.length; index++) {
const element = opts.after[index]
if (DAG.isValue(element)) {
stage = element.stage
break
}
}
} else if (DAG.isValue(opts.after)) {
stage = opts.after.stage
}
} else if (opts.before) {
if (Array.isArray(opts.before)) {
for (let index = 0; index < opts.before.length; index++) {
const element = opts.before[index]
if (DAG.isValue(element)) {
stage = element.stage
break
}
}
} else if (DAG.isValue(opts.before)) {
stage = opts.before.stage
}
}
}
const { autoInvalidations } = getContext<ThrelteInternalContext>('threlte-internal-context')
const started = writable(false)
const task = stage.createTask(key, fn, opts)
const start = () => {
started.set(true)
if (opts?.autoInvalidate ?? true) {
autoInvalidations.add(fn)
}
task.start()
}
const stop = () => {
started.set(true)
if (opts?.autoInvalidate ?? true) {
autoInvalidations.delete(fn)
}
task.stop()
}
if (opts?.autoStart ?? true) {
start()
} else {
stop()
}
onDestroy(() => {
if (!stage) return
stage.removeTask(key)
})
return {
task,
start,
stop,
started: {
subscribe: started.subscribe
}
}
}