@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.
- package/README.md +226 -0
- package/dist/index.d.ts +2603 -0
- package/dist/index.js +1031 -0
- package/dist/test.cjs +1519 -0
- package/dist/worker/agency-worker.js +972 -0
- package/dist/worker/manifest.edn +1 -0
- package/package.json +57 -0
- package/shadow-cljs.edn +173 -0
- package/src/polymer/animation/agency.cljs +84 -0
- package/src/polymer/animation/domain.cljs +110 -0
- package/src/polymer/animation/planner.cljs +88 -0
- package/src/polymer/animation/runtime.cljs +137 -0
- package/src/polymer/animation/scheduler.cljs +178 -0
- package/src/polymer/animation/service.cljs +981 -0
- package/src/polymer/animation/state.cljs +112 -0
- package/src/polymer/blink/agency.cljs +131 -0
- package/src/polymer/blink/planner.cljs +115 -0
- package/src/polymer/blink/scheduler.cljs +90 -0
- package/src/polymer/blink/snippet.cljs +50 -0
- package/src/polymer/blink/state.cljs +102 -0
- package/src/polymer/camera_context/agency.cljs +132 -0
- package/src/polymer/camera_context/domain.cljs +186 -0
- package/src/polymer/camera_context/planner.cljs +50 -0
- package/src/polymer/camera_context/scheduler.cljs +113 -0
- package/src/polymer/camera_context/state.cljs +121 -0
- package/src/polymer/character.cljs +471 -0
- package/src/polymer/conversation/agency.cljs +83 -0
- package/src/polymer/conversation/domain.cljs +35 -0
- package/src/polymer/conversation/effector.cljs +130 -0
- package/src/polymer/conversation/planner.cljs +178 -0
- package/src/polymer/conversation/scheduler.cljs +202 -0
- package/src/polymer/conversation/service.cljs +329 -0
- package/src/polymer/conversation/state.cljs +186 -0
- package/src/polymer/core.cljs +172 -0
- package/src/polymer/eye_head/agency.cljs +75 -0
- package/src/polymer/eye_head/domain.cljs +133 -0
- package/src/polymer/eye_head/goap.cljs +106 -0
- package/src/polymer/eye_head/scheduler.cljs +176 -0
- package/src/polymer/eye_head/service.cljs +259 -0
- package/src/polymer/eye_head/snippet.cljs +92 -0
- package/src/polymer/eye_head/state.cljs +167 -0
- package/src/polymer/gaze/agency.cljs +85 -0
- package/src/polymer/gaze/domain.cljs +211 -0
- package/src/polymer/gaze/goap.cljs +208 -0
- package/src/polymer/gaze/scheduler.cljs +184 -0
- package/src/polymer/gaze/state.cljs +202 -0
- package/src/polymer/gesture/agency.cljs +185 -0
- package/src/polymer/gesture/goap.cljs +461 -0
- package/src/polymer/gesture/scheduler.cljs +122 -0
- package/src/polymer/gesture/snippet.cljs +198 -0
- package/src/polymer/gesture/state.cljs +273 -0
- package/src/polymer/lipsync/agency.cljs +593 -0
- package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
- package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
- package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
- package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
- package/src/polymer/lipsync/goap.cljs +132 -0
- package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
- package/src/polymer/lipsync/scheduler.cljs +302 -0
- package/src/polymer/lipsync/state.cljs +202 -0
- package/src/polymer/nlp/phonemes.cljs +164 -0
- package/src/polymer/prosodic/agency.cljs +196 -0
- package/src/polymer/prosodic/goap.cljs +122 -0
- package/src/polymer/prosodic/scheduler.cljs +99 -0
- package/src/polymer/prosodic/snippet.cljs +106 -0
- package/src/polymer/prosodic/state.cljs +139 -0
- package/src/polymer/runtime/embody.cljs +117 -0
- package/src/polymer/runtime/embody_wasm.cljs +177 -0
- package/src/polymer/runtime/rust_embody_host.cljs +645 -0
- package/src/polymer/society.cljs +239 -0
- package/src/polymer/stream.cljs +130 -0
- package/src/polymer/transcription/agency.cljs +69 -0
- package/src/polymer/transcription/domain.cljs +68 -0
- package/src/polymer/transcription/effector.cljs +611 -0
- package/src/polymer/transcription/planner.cljs +203 -0
- package/src/polymer/transcription/scheduler.cljs +225 -0
- package/src/polymer/transcription/service.cljs +209 -0
- package/src/polymer/transcription/state.cljs +239 -0
- package/src/polymer/tts/agency.cljs +503 -0
- package/src/polymer/tts/azure.cljs +290 -0
- package/src/polymer/tts/planner.cljs +178 -0
- package/src/polymer/tts/runtime.cljs +624 -0
- package/src/polymer/tts/scheduler.cljs +162 -0
- package/src/polymer/tts/state.cljs +191 -0
- 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 []))})))
|