@lovelace_lol/polymer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,461 @@
1
+ (ns polymer.gesture.goap
2
+ (:require [clojure.set :as set]
3
+ [clojure.string :as str]
4
+ [polymer.gesture.state :as state]))
5
+
6
+ ;; Gesture planning is intentionally local. It resolves a command into gesture
7
+ ;; objectives, chooses authored gesture data, and proposes scheduler work. It
8
+ ;; does not build snippets, mutate state, or call Animation/Embody directly.
9
+ ;;
10
+ ;; The planned goal is: reach a state where one suitable authored arm/hand
11
+ ;; gesture is scheduled through Animation without violating enabled/cooldown,
12
+ ;; active-effector conflict, or max-active constraints. If that state cannot be
13
+ ;; reached, the plan records the refusal reason instead of letting the host or
14
+ ;; Animation guess.
15
+
16
+ (def supported-command-types
17
+ #{"configure"
18
+ "loadGestures"
19
+ "playGesture"
20
+ "playEmoji"
21
+ "gesture.goal"
22
+ "performGestureGoal"
23
+ "playGoal"
24
+ "stopGesture"
25
+ "stopAll"
26
+ "reset"})
27
+
28
+ (def playable-command-types
29
+ #{"playGesture" "playEmoji" "gesture.goal" "performGestureGoal" "playGoal"})
30
+
31
+ (def goal-command-types
32
+ #{"gesture.goal" "performGestureGoal" "playGoal"})
33
+
34
+ (defn lower-string [value]
35
+ (some-> value state/clean-string str/lower-case))
36
+
37
+ (defn lower-strings [values]
38
+ (->> (or values [])
39
+ (keep lower-string)
40
+ distinct
41
+ vec))
42
+
43
+ (defn goal-input [command]
44
+ (merge (dissoc command :type :goal)
45
+ (dissoc (or (state/js-data (:goal command)) {}) :type)))
46
+
47
+ (defn command-gesture-id [command world]
48
+ (let [input (goal-input command)]
49
+ (case (:type command)
50
+ "playGesture" (state/clean-string (or (:gestureId input) (:id input)))
51
+ "stopGesture" (state/clean-string (or (:gestureId command) (:id command)))
52
+ "playEmoji" (get (:emojiMappings world) (state/clean-string (:emoji input)))
53
+ (state/clean-string (or (:gestureId input) (:id input))))))
54
+
55
+ (defn preferred-gesture-id [command]
56
+ (let [input (goal-input command)]
57
+ (state/clean-string (or (:preferredGestureId input)
58
+ (:preferredId input)
59
+ (:preferredGesture input)))))
60
+
61
+ (defn hard-gesture-id? [command]
62
+ (or (= "playGesture" (:type command))
63
+ (boolean (some? (state/clean-string (or (:gestureId (goal-input command))
64
+ (:id (goal-input command))))))))
65
+
66
+ (defn goal-present? [goal]
67
+ (or (:gestureId goal)
68
+ (:preferredGestureId goal)
69
+ (:emoji goal)
70
+ (:intent goal)
71
+ (seq (:tags goal))
72
+ (seq (:requiredTags goal))
73
+ (:scope goal)
74
+ (seq (:affectedBones goal))
75
+ (seq (:requiredBones goal))
76
+ (seq (:avoidBones goal))
77
+ (:sourceText goal)
78
+ (:textRepresentation goal)))
79
+
80
+ (defn command-goal [command world now]
81
+ (let [type (:type command)
82
+ input (goal-input command)
83
+ gesture-id (command-gesture-id command world)
84
+ preferred-id (preferred-gesture-id command)
85
+ emoji (state/clean-string (or (:emoji input) (:emoji command)))
86
+ hard-id? (or (hard-gesture-id? command)
87
+ (and (= "playEmoji" type) gesture-id))]
88
+ {:type type
89
+ :commandType type
90
+ :now now
91
+ :enabled (get-in world [:config :enabled])
92
+ :gestureId gesture-id
93
+ :hardGestureId hard-id?
94
+ :preferredGestureId preferred-id
95
+ :emoji emoji
96
+ :intent (lower-string (or (:intent input) (:kind input) (:purpose input)))
97
+ :tags (lower-strings (:tags input))
98
+ :requiredTags (lower-strings (or (:requiredTags input) (:mustHaveTags input)))
99
+ :scope (lower-string (:scope input))
100
+ :affectedBones (state/clean-strings (:affectedBones input))
101
+ :requiredBones (state/clean-strings (:requiredBones input))
102
+ :avoidBones (state/clean-strings (:avoidBones input))
103
+ :sourceText (lower-string (:sourceText input))
104
+ :textRepresentation (lower-string (:textRepresentation input))
105
+ :name (state/clean-string (:name input))
106
+ :intensity (:intensity input)
107
+ :trigger (or (:trigger input)
108
+ (case type
109
+ "playEmoji" "emoji"
110
+ "playGesture" "explicit"
111
+ "gesture.goal" "goal"
112
+ "performGestureGoal" "goal"
113
+ "playGoal" "goal"
114
+ "command"))
115
+ :activeNames (state/active-names world)
116
+ :activeSnippets (:activeSnippets world)
117
+ :activeGestureNames (state/active-names-for-gesture world gesture-id)
118
+ :replaceActive (get-in world [:config :replaceActive])
119
+ :maxActive (get-in world [:config :maxActive])
120
+ :gestureCount (count (:gestures world))
121
+ :config (:config world)}))
122
+
123
+ (defn gesture-has-motion? [gesture]
124
+ (or (seq (:keyframes gesture))
125
+ (seq (:bones gesture))))
126
+
127
+ (defn gesture-terms [gesture]
128
+ (lower-strings (concat [(:id gesture)
129
+ (:name gesture)
130
+ (:description gesture)
131
+ (:sourceText gesture)
132
+ (:textRepresentation gesture)
133
+ (:emoji gesture)
134
+ (:scope gesture)]
135
+ (:tags gesture)
136
+ (:affectedBones gesture))))
137
+
138
+ (defn includes-term? [terms needle]
139
+ (and needle
140
+ (some #(or (= needle %)
141
+ (str/includes? % needle))
142
+ terms)))
143
+
144
+ (defn goal-text-terms [goal]
145
+ (->> [(:intent goal) (:sourceText goal) (:textRepresentation goal)]
146
+ (keep identity)
147
+ distinct
148
+ vec))
149
+
150
+ (defn matching-goal-text [goal terms]
151
+ (->> (goal-text-terms goal)
152
+ (filter #(includes-term? terms %))
153
+ vec))
154
+
155
+ (defn intersects? [left right]
156
+ (boolean (seq (set/intersection (set (or left []))
157
+ (set (or right []))))))
158
+
159
+ (defn tags-match? [goal-tags gesture-tags]
160
+ (let [gesture-tag-set (set gesture-tags)]
161
+ (every? gesture-tag-set goal-tags)))
162
+
163
+ (defn reject-reason [goal gesture-id gesture world]
164
+ (let [terms (gesture-terms gesture)
165
+ gesture-tags (lower-strings (:tags gesture))
166
+ affected-bones (state/clean-strings (or (:affectedBones gesture) (keys (:bones gesture))))]
167
+ (cond
168
+ (and (:hardGestureId goal) (not= (:gestureId goal) gesture-id))
169
+ "not-requested-gesture"
170
+
171
+ (not gesture)
172
+ "unknown-gesture"
173
+
174
+ (not (gesture-has-motion? gesture))
175
+ "gesture-has-no-motion"
176
+
177
+ (and (seq (:requiredTags goal))
178
+ (not (tags-match? (:requiredTags goal) gesture-tags)))
179
+ "required-tags"
180
+
181
+ (and (seq (:requiredBones goal))
182
+ (not (every? (set affected-bones) (:requiredBones goal))))
183
+ "required-bones"
184
+
185
+ (and (seq (:avoidBones goal))
186
+ (intersects? (:avoidBones goal) affected-bones))
187
+ "avoided-bones"
188
+
189
+ (and (:emoji goal)
190
+ (not (:hardGestureId goal))
191
+ (not= (:emoji goal) (state/clean-string (:emoji gesture))))
192
+ "emoji-mismatch"
193
+
194
+ (and (not (:hardGestureId goal))
195
+ (seq (goal-text-terms goal))
196
+ (empty? (matching-goal-text goal terms))
197
+ (empty? (set/intersection (set (:tags goal)) (set gesture-tags))))
198
+ "goal-text-mismatch"
199
+
200
+ (not (state/gesture-ready? world gesture-id (:now goal)))
201
+ "cooldown"
202
+
203
+ :else nil)))
204
+
205
+ (defn gesture-priority [goal gesture]
206
+ (state/number-or (:priority gesture) (:priority (:config goal))))
207
+
208
+ (defn recency-penalty [world gesture-id now]
209
+ (let [last-played (get-in world [:lastPlayedByGesture gesture-id])]
210
+ (if-not last-played
211
+ 0
212
+ (* 15 (max 0 (- 1 (/ (- now last-played) 5000)))))))
213
+
214
+ (defn candidate-score [goal gesture-id gesture world]
215
+ (let [terms (gesture-terms gesture)
216
+ gesture-tags (lower-strings (:tags gesture))
217
+ affected-bones (state/clean-strings (or (:affectedBones gesture) (keys (:bones gesture))))
218
+ text-matches (matching-goal-text goal terms)
219
+ priority (gesture-priority goal gesture)
220
+ tag-matches (count (set/intersection (set (:tags goal)) (set gesture-tags)))
221
+ required-tag-matches (count (set/intersection (set (:requiredTags goal)) (set gesture-tags)))
222
+ bone-matches (count (set/intersection (set (:affectedBones goal)) (set affected-bones)))
223
+ scope-match? (and (:scope goal) (= (:scope goal) (lower-string (:scope gesture))))
224
+ score (+ (if (= (:gestureId goal) gesture-id) 1000 0)
225
+ (if (= (:preferredGestureId goal) gesture-id) 300 0)
226
+ (if (and (:emoji goal) (= (:emoji goal) (state/clean-string (:emoji gesture)))) 220 0)
227
+ (if (includes-term? terms (:intent goal)) 160 0)
228
+ (* 80 (count (remove #{(:intent goal)} text-matches)))
229
+ (* 45 tag-matches)
230
+ (* 55 required-tag-matches)
231
+ (* 35 bone-matches)
232
+ (if scope-match? 30 0)
233
+ (if (and (:scope goal) (= "both" (lower-string (:scope gesture)))) 10 0)
234
+ (min 50 (max -50 priority))
235
+ (- (recency-penalty world gesture-id (:now goal))))]
236
+ {:gestureId gesture-id
237
+ :score score
238
+ :priority priority
239
+ :matches {:intent (when (includes-term? terms (:intent goal)) (:intent goal))
240
+ :text text-matches
241
+ :tags (vec (set/intersection (set (:tags goal)) (set gesture-tags)))
242
+ :requiredTags (vec (set/intersection (set (:requiredTags goal)) (set gesture-tags)))
243
+ :scope (when scope-match? (:scope goal))
244
+ :bones (vec (set/intersection (set (:affectedBones goal)) (set affected-bones)))
245
+ :emoji (when (and (:emoji goal) (= (:emoji goal) (state/clean-string (:emoji gesture))))
246
+ (:emoji goal))}}))
247
+
248
+ (defn candidates [goal world]
249
+ (->> (:gestures world)
250
+ (keep (fn [[gesture-id gesture]]
251
+ (when-not (reject-reason goal gesture-id gesture world)
252
+ (assoc (candidate-score goal gesture-id gesture world)
253
+ :gesture gesture))))
254
+ (sort-by (juxt (comp - :score) (comp - :priority) :gestureId))
255
+ vec))
256
+
257
+ (defn selected-candidate [goal world]
258
+ (first (candidates goal world)))
259
+
260
+ (defn failure-step [goal]
261
+ (cond
262
+ (not (contains? supported-command-types (:type goal)))
263
+ {:op "fail" :reason "unsupported-command" :commandType (:type goal)}
264
+
265
+ (and (contains? playable-command-types (:type goal))
266
+ (not (:enabled goal)))
267
+ {:op "ignore" :reason "disabled"}
268
+
269
+ (and (= "playEmoji" (:type goal))
270
+ (not (:emoji goal)))
271
+ {:op "fail" :reason "missing-emoji"}
272
+
273
+ (and (contains? goal-command-types (:type goal))
274
+ (not (goal-present? goal)))
275
+ {:op "fail" :reason "missing-goal"}
276
+
277
+ (and (#{"playGesture" "stopGesture"} (:type goal))
278
+ (not (:gestureId goal)))
279
+ {:op "fail" :reason "missing-gesture"}
280
+
281
+ :else nil))
282
+
283
+ (defn candidate-failure-step [goal world]
284
+ (let [hard-id (:gestureId goal)
285
+ hard-gesture (get (:gestures world) hard-id)]
286
+ (cond
287
+ (and hard-id (not hard-gesture))
288
+ {:op "fail" :reason "unknown-gesture" :gestureId hard-id}
289
+
290
+ (and hard-gesture (not (gesture-has-motion? hard-gesture)))
291
+ {:op "fail" :reason "gesture-has-no-motion" :gestureId hard-id}
292
+
293
+ (and hard-id (not (state/gesture-ready? world hard-id (:now goal))))
294
+ {:op "ignore" :reason "cooldown" :gestureId hard-id}
295
+
296
+ (zero? (:gestureCount goal))
297
+ {:op "fail" :reason "empty-library"}
298
+
299
+ :else
300
+ {:op "ignore" :reason "no-gesture-candidate"})))
301
+
302
+ (defn active-conflicts [goal gesture]
303
+ (let [affected-bones (state/clean-strings (or (:affectedBones gesture) (keys (:bones gesture))))]
304
+ (->> (:activeSnippets goal)
305
+ (keep (fn [[name active]]
306
+ (when (intersects? affected-bones (:affectedBones active))
307
+ (assoc active :name name))))
308
+ vec)))
309
+
310
+ (defn capacity-removals [goal conflict-names]
311
+ (let [remaining (->> (:activeSnippets goal)
312
+ (remove (fn [[name _]] (contains? (set conflict-names) name)))
313
+ (map second)
314
+ (sort-by :scheduledAt)
315
+ vec)
316
+ overflow (max 0 (- (inc (count remaining)) (:maxActive goal)))]
317
+ (->> remaining
318
+ (take overflow)
319
+ (map :name)
320
+ vec)))
321
+
322
+ (defn active-resolution-steps [goal gesture]
323
+ (let [conflicts (active-conflicts goal gesture)
324
+ conflict-names (mapv :name conflicts)]
325
+ (cond
326
+ (and (seq conflict-names) (not (:replaceActive goal)))
327
+ [{:op "ignore"
328
+ :reason "active-conflict"
329
+ :names conflict-names}]
330
+
331
+ (and (not (:replaceActive goal))
332
+ (>= (count (:activeNames goal)) (:maxActive goal)))
333
+ [{:op "ignore" :reason "max-active"}]
334
+
335
+ :else
336
+ (let [capacity-names (capacity-removals goal conflict-names)]
337
+ (cond-> []
338
+ (seq conflict-names)
339
+ (conj {:op "remove-active-gestures"
340
+ :names conflict-names
341
+ :reason "conflict"})
342
+
343
+ (seq capacity-names)
344
+ (conj {:op "remove-active-gestures"
345
+ :names capacity-names
346
+ :reason "capacity"}))))))
347
+
348
+ (defn gesture-context [goal candidate]
349
+ (cond-> {:name (:name goal)
350
+ :intensity (:intensity goal)
351
+ :trigger (:trigger goal)
352
+ :intent (:intent goal)
353
+ :goal {:intent (:intent goal)
354
+ :emoji (:emoji goal)
355
+ :tags (:tags goal)
356
+ :requiredTags (:requiredTags goal)
357
+ :scope (:scope goal)
358
+ :affectedBones (:affectedBones goal)
359
+ :requiredBones (:requiredBones goal)
360
+ :avoidBones (:avoidBones goal)}
361
+ :selection {:gestureId (:gestureId candidate)
362
+ :score (:score candidate)
363
+ :matches (:matches candidate)}}
364
+ (nil? (:name goal)) (dissoc :name)
365
+ (nil? (:intensity goal)) (dissoc :intensity)
366
+ (nil? (:intent goal)) (update :goal dissoc :intent)
367
+ (nil? (:emoji goal)) (update :goal dissoc :emoji)
368
+ (empty? (:tags goal)) (update :goal dissoc :tags)
369
+ (empty? (:requiredTags goal)) (update :goal dissoc :requiredTags)
370
+ (nil? (:scope goal)) (update :goal dissoc :scope)
371
+ (empty? (:affectedBones goal)) (update :goal dissoc :affectedBones)
372
+ (empty? (:requiredBones goal)) (update :goal dissoc :requiredBones)
373
+ (empty? (:avoidBones goal)) (update :goal dissoc :avoidBones)))
374
+
375
+ (defn playable-steps [goal world]
376
+ (if-let [failure (failure-step goal)]
377
+ [failure]
378
+ (let [candidate (selected-candidate goal world)
379
+ gesture (:gesture candidate)]
380
+ (if-not candidate
381
+ [(candidate-failure-step goal world)]
382
+ (let [resolution (active-resolution-steps goal gesture)]
383
+ (if (= "ignore" (:op (first resolution)))
384
+ [{:op "resolve-gesture-goal"
385
+ :goal (dissoc goal :activeSnippets :config)}
386
+ {:op "select-gesture"
387
+ :gestureId (:gestureId candidate)
388
+ :score (:score candidate)
389
+ :matches (:matches candidate)}
390
+ (first resolution)]
391
+ (vec (concat [{:op "resolve-gesture-goal"
392
+ :goal (dissoc goal :activeSnippets :config)}
393
+ {:op "select-gesture"
394
+ :gestureId (:gestureId candidate)
395
+ :score (:score candidate)
396
+ :matches (:matches candidate)}]
397
+ resolution
398
+ [{:op "build-gesture-snippet"
399
+ :gestureId (:gestureId candidate)
400
+ :context (gesture-context goal candidate)}
401
+ {:op "schedule-animation"
402
+ :targetAgency "animation"}
403
+ {:op "record-schedule"}]))))))))
404
+
405
+ (defn stop-steps [goal]
406
+ [{:op "remove-active-gestures"
407
+ :names (:activeGestureNames goal)
408
+ :reason "requested"}])
409
+
410
+ (defn command-steps [goal]
411
+ (if-let [failure (failure-step goal)]
412
+ [failure]
413
+ (case (:type goal)
414
+ "configure" [{:op "apply-config"}]
415
+ "loadGestures" [{:op "load-gestures"}]
416
+ "reset" [{:op "remove-active-gestures"
417
+ :names (:activeNames goal)
418
+ :reason "reset"}
419
+ {:op "reset-state"}]
420
+ "stopAll" [{:op "remove-active-gestures"
421
+ :names (:activeNames goal)
422
+ :reason "requested"}]
423
+ "stopGesture" (stop-steps goal)
424
+ ("playGesture" "playEmoji" "gesture.goal" "performGestureGoal" "playGoal")
425
+ ::playable
426
+ [{:op "fail" :reason "unsupported-command" :commandType (:type goal)}])))
427
+
428
+ (defn plan-ok? [steps]
429
+ (not-any? #(= "fail" (:op %)) steps))
430
+
431
+ (defn noop? [steps]
432
+ (boolean (some #(= "ignore" (:op %)) steps)))
433
+
434
+ (defn remove-names [steps]
435
+ (->> steps
436
+ (keep (fn [step]
437
+ (when (= "remove-active-gestures" (:op step))
438
+ (:names step))))
439
+ (apply concat)
440
+ vec))
441
+
442
+ (defn remove-reason [steps]
443
+ (or (:reason (some #(when (= "remove-active-gestures" (:op %)) %) steps))
444
+ "requested"))
445
+
446
+ (defn plan-command [command world now]
447
+ (let [goal (command-goal command world now)
448
+ steps (if (contains? playable-command-types (:type goal))
449
+ (playable-steps goal world)
450
+ (command-steps goal))
451
+ candidate-step (some #(when (= "select-gesture" (:op %)) %) steps)
452
+ build-step (some #(when (= "build-gesture-snippet" (:op %)) %) steps)]
453
+ {:goal goal
454
+ :steps steps
455
+ :ok (plan-ok? steps)
456
+ :noop (noop? steps)
457
+ :gestureId (:gestureId candidate-step)
458
+ :gesture (get (:gestures world) (:gestureId candidate-step))
459
+ :context (:context build-step)
460
+ :removeNames (remove-names steps)
461
+ :removeReason (remove-reason steps)}))
@@ -0,0 +1,122 @@
1
+ (ns polymer.gesture.scheduler)
2
+
3
+ ;; Gesture has no direct runtime effector. Its scheduler owns ordering,
4
+ ;; replacement, cancellation, and diagnostics, then emits animation requests for
5
+ ;; the Animation agency to accept or reject through its own scheduler/runtime path.
6
+
7
+ (defn now-ms []
8
+ (.now js/Date))
9
+
10
+ (defn channel-effector [channel]
11
+ (get-in channel [:target :id]))
12
+
13
+ (defn snippet-effectors [snippet]
14
+ (->> (:channels snippet)
15
+ (keep channel-effector)
16
+ distinct
17
+ vec))
18
+
19
+ (defn queue-entry [effect queue-index]
20
+ (assoc effect
21
+ :queueIndex queue-index
22
+ :queuedAt (now-ms)))
23
+
24
+ (defn finite-positive-seconds? [value]
25
+ (and (number? value)
26
+ (js/isFinite value)
27
+ (not (js/isNaN value))
28
+ (pos? value)))
29
+
30
+ (defn create-scheduler [{:keys [emit-event on-complete]}]
31
+ (let [queue (atom [])
32
+ completion-timers (atom {})
33
+ disposed? (atom false)]
34
+ (letfn [(clear-completion! [name]
35
+ (when-let [timer (get @completion-timers name)]
36
+ (js/clearTimeout timer)
37
+ (swap! completion-timers dissoc name)))
38
+
39
+ (clear-all-completions! []
40
+ (doseq [[_ timer] @completion-timers]
41
+ (js/clearTimeout timer))
42
+ (reset! completion-timers {}))
43
+
44
+ (schedule-completion! [snippet-data]
45
+ (let [name (:name snippet-data)
46
+ max-time (:maxTime snippet-data)]
47
+ (clear-completion! name)
48
+ (when (and name
49
+ (not (:loop snippet-data))
50
+ (finite-positive-seconds? max-time))
51
+ (let [timer
52
+ (js/setTimeout
53
+ (fn []
54
+ (swap! completion-timers dissoc name)
55
+ (when (and (not @disposed?) on-complete)
56
+ (on-complete name)))
57
+ (+ 50 (* 1000 max-time)))]
58
+ (swap! completion-timers assoc name timer)))))
59
+
60
+ (enqueue! [effect]
61
+ (let [entry (queue-entry effect (count @queue))]
62
+ (swap! queue conj entry)
63
+ entry))
64
+
65
+ (schedule-animation! [snippet-data options]
66
+ (let [effectors (snippet-effectors snippet-data)
67
+ entry (enqueue! {:type "scheduleAnimation"
68
+ :agency "gesture"
69
+ :requestId (:name snippet-data)
70
+ :snippetName (:name snippet-data)
71
+ :effectors effectors})]
72
+ (emit-event {:type "animation.requestScheduleSnippet"
73
+ :agency "gesture"
74
+ :requestId (:requestId entry)
75
+ :snippet snippet-data
76
+ :options options
77
+ :effectors effectors
78
+ :queueIndex (:queueIndex entry)
79
+ :queuedAt (:queuedAt entry)})
80
+ (schedule-completion! snippet-data)
81
+ entry))
82
+
83
+ (remove-animation! [name reason]
84
+ (clear-completion! name)
85
+ (let [entry (enqueue! {:type "removeAnimation"
86
+ :agency "gesture"
87
+ :requestId name
88
+ :name name
89
+ :reason reason})]
90
+ (emit-event {:type "animation.requestRemoveSnippet"
91
+ :agency "gesture"
92
+ :requestId name
93
+ :name name
94
+ :reason reason
95
+ :queueIndex (:queueIndex entry)
96
+ :queuedAt (:queuedAt entry)})
97
+ entry))]
98
+ {:schedule
99
+ (fn [snippet-data options]
100
+ (when-not @disposed?
101
+ (schedule-animation! snippet-data options)))
102
+
103
+ :remove
104
+ (fn [name reason]
105
+ (when-not @disposed?
106
+ (remove-animation! name reason)))
107
+
108
+ :remove-many
109
+ (fn [names reason]
110
+ (when-not @disposed?
111
+ (doseq [name names]
112
+ (remove-animation! name reason))))
113
+
114
+ :queue
115
+ (fn []
116
+ @queue)
117
+
118
+ :dispose
119
+ (fn []
120
+ (reset! disposed? true)
121
+ (clear-all-completions!)
122
+ (reset! queue []))})))