@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,211 @@
|
|
|
1
|
+
(ns polymer.gaze.domain)
|
|
2
|
+
|
|
3
|
+
;; Gaze domain code is pure data transformation. It turns outside facts and
|
|
4
|
+
;; goals into normalized targets and scores, but it never schedules work or
|
|
5
|
+
;; talks to an animation/runtime API.
|
|
6
|
+
|
|
7
|
+
(def zero-target {:x 0 :y 0 :z 0})
|
|
8
|
+
|
|
9
|
+
(defn finite-number?
|
|
10
|
+
[value]
|
|
11
|
+
(and (number? value) (js/isFinite value)))
|
|
12
|
+
|
|
13
|
+
(defn number-or
|
|
14
|
+
[value fallback]
|
|
15
|
+
(if (finite-number? value) value fallback))
|
|
16
|
+
|
|
17
|
+
(defn clamp
|
|
18
|
+
[lo hi value]
|
|
19
|
+
(min hi (max lo value)))
|
|
20
|
+
|
|
21
|
+
(defn data-map
|
|
22
|
+
[value]
|
|
23
|
+
(cond
|
|
24
|
+
(map? value) value
|
|
25
|
+
value (js->clj value :keywordize-keys true)
|
|
26
|
+
:else {}))
|
|
27
|
+
|
|
28
|
+
(defn normalize-target
|
|
29
|
+
([value] (normalize-target value zero-target))
|
|
30
|
+
([value fallback]
|
|
31
|
+
(let [input (data-map value)
|
|
32
|
+
fallback (merge zero-target fallback)]
|
|
33
|
+
{:x (clamp -1 1 (number-or (:x input) (:x fallback)))
|
|
34
|
+
:y (clamp -1 1 (number-or (:y input) (:y fallback)))
|
|
35
|
+
:z (number-or (:z input) (:z fallback))})))
|
|
36
|
+
|
|
37
|
+
(defn add-targets
|
|
38
|
+
[base offset]
|
|
39
|
+
{:x (clamp -1 1 (+ (:x base) (:x offset)))
|
|
40
|
+
:y (clamp -1 1 (+ (:y base) (:y offset)))
|
|
41
|
+
:z (number-or (:z base) 0)})
|
|
42
|
+
|
|
43
|
+
(defn target-distance
|
|
44
|
+
[a b]
|
|
45
|
+
(js/Math.hypot (- (:x a) (:x b))
|
|
46
|
+
(- (:y a) (:y b))))
|
|
47
|
+
|
|
48
|
+
(defn mirror-target
|
|
49
|
+
[target mirrored?]
|
|
50
|
+
(if mirrored?
|
|
51
|
+
(update target :x -)
|
|
52
|
+
target))
|
|
53
|
+
|
|
54
|
+
(defn smooth-alpha
|
|
55
|
+
[distance smooth-factor]
|
|
56
|
+
;; This follows the Latticework experimental behavior: smoothFactor 1 means
|
|
57
|
+
;; immediate target adoption, while lower values ease toward distant targets
|
|
58
|
+
;; without letting a single update jump too far.
|
|
59
|
+
(let [base (max 0 smooth-factor)]
|
|
60
|
+
(if (>= base 1)
|
|
61
|
+
1
|
|
62
|
+
(min 0.7 (+ base (* distance 0.25))))))
|
|
63
|
+
|
|
64
|
+
(defn smooth-target
|
|
65
|
+
[previous raw-target smooth-factor]
|
|
66
|
+
(let [distance (target-distance raw-target previous)
|
|
67
|
+
alpha (smooth-alpha distance smooth-factor)]
|
|
68
|
+
{:x (+ (:x previous) (* (- (:x raw-target) (:x previous)) alpha))
|
|
69
|
+
:y (+ (:y previous) (* (- (:y raw-target) (:y previous)) alpha))
|
|
70
|
+
:z (:z raw-target)}))
|
|
71
|
+
|
|
72
|
+
(defn movement-duration-ms
|
|
73
|
+
[base-ms delta scale minimum-ms]
|
|
74
|
+
(int (js/Math.round (max minimum-ms (* base-ms (+ scale delta))))))
|
|
75
|
+
|
|
76
|
+
(defn plan-target
|
|
77
|
+
[target previous-target config options now-ms]
|
|
78
|
+
(let [raw-target (mirror-target (normalize-target target) (:mirrored config))
|
|
79
|
+
smoothed-target (smooth-target previous-target
|
|
80
|
+
raw-target
|
|
81
|
+
(:smoothFactor config))
|
|
82
|
+
delta (target-distance smoothed-target previous-target)
|
|
83
|
+
force? (boolean (:force options))
|
|
84
|
+
base-duration (max 50 (:transitionDurationMs config))
|
|
85
|
+
eye-duration (movement-duration-ms base-duration delta 0.4 50)
|
|
86
|
+
head-duration (movement-duration-ms base-duration delta 0.6 80)]
|
|
87
|
+
{:agency "gaze"
|
|
88
|
+
:rawTarget raw-target
|
|
89
|
+
:target smoothed-target
|
|
90
|
+
:previousTarget previous-target
|
|
91
|
+
:delta delta
|
|
92
|
+
:accepted (or force? (>= delta (:minDelta config)))
|
|
93
|
+
:force force?
|
|
94
|
+
:eyeDurationMs eye-duration
|
|
95
|
+
:headDurationMs head-duration
|
|
96
|
+
:createdAt now-ms}))
|
|
97
|
+
|
|
98
|
+
(defn candidate-target
|
|
99
|
+
[candidate]
|
|
100
|
+
(let [candidate (data-map candidate)]
|
|
101
|
+
(or (:target candidate)
|
|
102
|
+
(:gazeTarget candidate)
|
|
103
|
+
(:lookTarget candidate)
|
|
104
|
+
(when (or (contains? candidate :x)
|
|
105
|
+
(contains? candidate :y))
|
|
106
|
+
candidate))))
|
|
107
|
+
|
|
108
|
+
(defn candidate-score
|
|
109
|
+
[candidate]
|
|
110
|
+
(let [candidate (data-map candidate)]
|
|
111
|
+
(+ (number-or (:priority candidate) 0)
|
|
112
|
+
(number-or (:weight candidate) 0)
|
|
113
|
+
(number-or (:confidence candidate) 0))))
|
|
114
|
+
|
|
115
|
+
(defn normalize-attention-candidate
|
|
116
|
+
[index candidate]
|
|
117
|
+
(when-let [target (candidate-target candidate)]
|
|
118
|
+
(let [candidate (data-map candidate)]
|
|
119
|
+
{:target (normalize-target target)
|
|
120
|
+
:source (or (:source candidate) (:agency candidate) "attention")
|
|
121
|
+
:label (or (:label candidate) (:id candidate) (str "candidate-" index))
|
|
122
|
+
:score (candidate-score candidate)
|
|
123
|
+
:priority (number-or (:priority candidate) 0)
|
|
124
|
+
:weight (number-or (:weight candidate) 0)
|
|
125
|
+
:confidence (number-or (:confidence candidate) 0)})))
|
|
126
|
+
|
|
127
|
+
(defn attention-candidates
|
|
128
|
+
[payload]
|
|
129
|
+
(let [payload (data-map payload)
|
|
130
|
+
targets (:targets payload)]
|
|
131
|
+
(cond
|
|
132
|
+
(sequential? targets) targets
|
|
133
|
+
(:target payload) [payload]
|
|
134
|
+
:else [])))
|
|
135
|
+
|
|
136
|
+
(defn choose-attention-target
|
|
137
|
+
[payload]
|
|
138
|
+
;; A transducer is useful here because candidate extraction is a pure
|
|
139
|
+
;; map/filter/reduce pipeline. The planner receives only the winning target
|
|
140
|
+
;; fact instead of open-coding selection logic around scheduler state.
|
|
141
|
+
(let [candidates (attention-candidates payload)
|
|
142
|
+
normalized (into []
|
|
143
|
+
(comp (map-indexed normalize-attention-candidate)
|
|
144
|
+
(filter some?))
|
|
145
|
+
candidates)]
|
|
146
|
+
(when (seq normalized)
|
|
147
|
+
(apply max-key :score normalized))))
|
|
148
|
+
|
|
149
|
+
(def conversation-attention-policy
|
|
150
|
+
;; Gaze owns conversation look-intent policy. The character network only
|
|
151
|
+
;; forwards conversation.* facts; it must not invent geometry or options.
|
|
152
|
+
{"conversation.userUtterance" {:target {:x 0 :y 0.08 :z 0}
|
|
153
|
+
:priority 0.25
|
|
154
|
+
:confidence 0.5
|
|
155
|
+
:label "conversation-user"
|
|
156
|
+
:options {:eyeEnabled true
|
|
157
|
+
:headEnabled false}}
|
|
158
|
+
"conversation.agentUtterance" {:target {:x 0 :y 0.04 :z 0}
|
|
159
|
+
:priority 0.2
|
|
160
|
+
:confidence 0.4
|
|
161
|
+
:label "conversation-agent"
|
|
162
|
+
:options {:eyeEnabled true
|
|
163
|
+
:headEnabled false}}})
|
|
164
|
+
|
|
165
|
+
(defn conversation-attention-command
|
|
166
|
+
[command]
|
|
167
|
+
(let [payload (data-map command)
|
|
168
|
+
type (:type payload)
|
|
169
|
+
policy (get conversation-attention-policy type)]
|
|
170
|
+
(when policy
|
|
171
|
+
{:type "attention.fact"
|
|
172
|
+
:source "conversation"
|
|
173
|
+
:turnId (:turnId payload)
|
|
174
|
+
:text (:text payload)
|
|
175
|
+
:options (:options policy)
|
|
176
|
+
:targets [(assoc (select-keys policy [:target :priority :confidence :label])
|
|
177
|
+
:source "conversation")]})))
|
|
178
|
+
|
|
179
|
+
(defn command-target
|
|
180
|
+
[command]
|
|
181
|
+
(let [payload (data-map command)
|
|
182
|
+
type (:type payload)]
|
|
183
|
+
(case type
|
|
184
|
+
"attention.fact"
|
|
185
|
+
(choose-attention-target payload)
|
|
186
|
+
|
|
187
|
+
("conversation.userUtterance" "conversation.agentUtterance")
|
|
188
|
+
(when-let [attention (conversation-attention-command payload)]
|
|
189
|
+
(choose-attention-target attention))
|
|
190
|
+
|
|
191
|
+
"camera.fact"
|
|
192
|
+
(when-let [offset (:relativeOffset payload)]
|
|
193
|
+
{:target (normalize-target offset)
|
|
194
|
+
:source (or (:source payload) "cameraContext")
|
|
195
|
+
:label "camera-relative-offset"
|
|
196
|
+
:score 0.4})
|
|
197
|
+
|
|
198
|
+
("camera.stale" "clearCameraOffset")
|
|
199
|
+
{:target zero-target
|
|
200
|
+
:source (or (:source payload) "cameraContext")
|
|
201
|
+
:label "camera-relative-clear"
|
|
202
|
+
:score 0.4}
|
|
203
|
+
|
|
204
|
+
(when-let [target (or (:target payload)
|
|
205
|
+
(:gazeTarget payload)
|
|
206
|
+
(:lookTarget payload)
|
|
207
|
+
(:value payload))]
|
|
208
|
+
{:target (normalize-target target)
|
|
209
|
+
:source (or (:source payload) "command")
|
|
210
|
+
:label (or (:label payload) "direct")
|
|
211
|
+
:score (number-or (:priority payload) 0)}))))
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
(ns polymer.gaze.goap
|
|
2
|
+
(:require [polymer.gaze.domain :as domain]))
|
|
3
|
+
|
|
4
|
+
;; The Gaze planner is local GOAP-style decision logic. It decides whether a
|
|
5
|
+
;; target fact should become a look request, but it does not publish streams or
|
|
6
|
+
;; mutate scheduler queues.
|
|
7
|
+
|
|
8
|
+
(def supported-command-types
|
|
9
|
+
#{"configure"
|
|
10
|
+
"setMode"
|
|
11
|
+
"set-mode"
|
|
12
|
+
"setActive"
|
|
13
|
+
"set-active"
|
|
14
|
+
"enable"
|
|
15
|
+
"disable"
|
|
16
|
+
"setTarget"
|
|
17
|
+
"set-target"
|
|
18
|
+
"focusTarget"
|
|
19
|
+
"attention.fact"
|
|
20
|
+
"conversation.userUtterance"
|
|
21
|
+
"conversation.agentUtterance"
|
|
22
|
+
"conversation.cancelRequested"
|
|
23
|
+
"camera.fact"
|
|
24
|
+
"camera.stale"
|
|
25
|
+
"clearCameraOffset"
|
|
26
|
+
"reset"
|
|
27
|
+
"cancel"})
|
|
28
|
+
|
|
29
|
+
(defn command-type
|
|
30
|
+
[command]
|
|
31
|
+
(:type command))
|
|
32
|
+
|
|
33
|
+
(defn request-id
|
|
34
|
+
[prefix now-ms]
|
|
35
|
+
(str prefix ":" now-ms ":" (rand-int 1000000)))
|
|
36
|
+
|
|
37
|
+
(defn enabled-for-request?
|
|
38
|
+
[config options]
|
|
39
|
+
(and (:enabled config)
|
|
40
|
+
(or (if (contains? options :eyeEnabled)
|
|
41
|
+
(boolean (:eyeEnabled options))
|
|
42
|
+
(:eyesEnabled config))
|
|
43
|
+
(if (contains? options :headEnabled)
|
|
44
|
+
(boolean (:headEnabled options))
|
|
45
|
+
(:headEnabled config)))))
|
|
46
|
+
|
|
47
|
+
(defn target-request
|
|
48
|
+
[plan state options source label]
|
|
49
|
+
{:type "eyeHeadTracking.requestGaze"
|
|
50
|
+
:agency "gaze"
|
|
51
|
+
:targetAgency "eyeHeadTracking"
|
|
52
|
+
:requestId (:requestId plan)
|
|
53
|
+
:source source
|
|
54
|
+
:label label
|
|
55
|
+
:mode (:mode state)
|
|
56
|
+
:target (:target plan)
|
|
57
|
+
:rawTarget (:rawTarget plan)
|
|
58
|
+
:previousTarget (:previousTarget plan)
|
|
59
|
+
:eyeEnabled (if (contains? options :eyeEnabled)
|
|
60
|
+
(boolean (:eyeEnabled options))
|
|
61
|
+
(get-in state [:config :eyesEnabled]))
|
|
62
|
+
:headEnabled (if (contains? options :headEnabled)
|
|
63
|
+
(boolean (:headEnabled options))
|
|
64
|
+
(get-in state [:config :headEnabled]))
|
|
65
|
+
:headFollowEyes (if (contains? options :headFollowEyes)
|
|
66
|
+
(boolean (:headFollowEyes options))
|
|
67
|
+
(get-in state [:config :headFollowEyes]))
|
|
68
|
+
:eyeIntensity (get-in state [:config :eyeIntensity])
|
|
69
|
+
:headIntensity (get-in state [:config :headIntensity])
|
|
70
|
+
:eyeDurationMs (:eyeDurationMs plan)
|
|
71
|
+
:headDurationMs (:headDurationMs plan)
|
|
72
|
+
:createdAt (:createdAt plan)})
|
|
73
|
+
|
|
74
|
+
(defn target-steps
|
|
75
|
+
[command state now-ms]
|
|
76
|
+
(let [payload (domain/data-map command)
|
|
77
|
+
type (:type payload)
|
|
78
|
+
conversation-attention (when (#{"conversation.userUtterance"
|
|
79
|
+
"conversation.agentUtterance"}
|
|
80
|
+
type)
|
|
81
|
+
(domain/conversation-attention-command payload))
|
|
82
|
+
effective-payload (or conversation-attention payload)
|
|
83
|
+
options (domain/data-map (:options effective-payload))
|
|
84
|
+
target-fact (domain/command-target payload)
|
|
85
|
+
camera-command? (#{"camera.fact" "camera.stale" "clearCameraOffset"} type)]
|
|
86
|
+
(if-not target-fact
|
|
87
|
+
[{:op "fail"
|
|
88
|
+
:reason "missing-target"
|
|
89
|
+
:commandType type}]
|
|
90
|
+
(let [camera-offset (if camera-command?
|
|
91
|
+
(:target target-fact)
|
|
92
|
+
(:cameraRelativeOffset state))
|
|
93
|
+
base-target (if camera-command?
|
|
94
|
+
(:baseTarget state)
|
|
95
|
+
(:target target-fact))
|
|
96
|
+
adjusted-target (domain/add-targets base-target camera-offset)
|
|
97
|
+
plan (assoc (domain/plan-target adjusted-target
|
|
98
|
+
(:lastRequestedTarget state)
|
|
99
|
+
(:config state)
|
|
100
|
+
options
|
|
101
|
+
now-ms)
|
|
102
|
+
:requestId (request-id "gaze" now-ms)
|
|
103
|
+
:source (:source target-fact)
|
|
104
|
+
:label (:label target-fact)
|
|
105
|
+
:score (:score target-fact)
|
|
106
|
+
:baseTarget base-target
|
|
107
|
+
:cameraRelativeOffset camera-offset
|
|
108
|
+
:cameraFact (when camera-command? payload))
|
|
109
|
+
request (target-request plan state options (:source target-fact) (:label target-fact))]
|
|
110
|
+
(cond
|
|
111
|
+
(not (enabled-for-request? (:config state) options))
|
|
112
|
+
[{:op "record-target" :plan plan}
|
|
113
|
+
{:op "publish-target-received" :plan plan}
|
|
114
|
+
{:op "ignore-target" :plan plan :reason "disabled"}]
|
|
115
|
+
|
|
116
|
+
(not (:accepted plan))
|
|
117
|
+
[{:op "record-target" :plan plan}
|
|
118
|
+
{:op "publish-target-received" :plan plan}
|
|
119
|
+
{:op "publish-target-planned" :plan plan}
|
|
120
|
+
{:op "ignore-target" :plan plan :reason "min-delta"}]
|
|
121
|
+
|
|
122
|
+
:else
|
|
123
|
+
[{:op "record-target" :plan plan}
|
|
124
|
+
{:op "publish-target-received" :plan plan}
|
|
125
|
+
{:op "publish-target-planned" :plan plan}
|
|
126
|
+
{:op "queue-request" :plan plan :request request}])))))
|
|
127
|
+
|
|
128
|
+
(defn reset-steps
|
|
129
|
+
[command now-ms]
|
|
130
|
+
(let [payload (domain/data-map command)
|
|
131
|
+
request {:type "eyeHeadTracking.requestReset"
|
|
132
|
+
:agency "gaze"
|
|
133
|
+
:targetAgency "eyeHeadTracking"
|
|
134
|
+
:requestId (request-id "gaze:reset" now-ms)
|
|
135
|
+
:durationMs (domain/number-or (:durationMs payload)
|
|
136
|
+
(domain/number-or (:duration-ms payload) 300))
|
|
137
|
+
:eyes (if (contains? payload :eyes) (boolean (:eyes payload)) true)
|
|
138
|
+
:head (if (contains? payload :head) (boolean (:head payload)) true)
|
|
139
|
+
:requestedAt now-ms}]
|
|
140
|
+
[{:op "request-reset" :request request}]))
|
|
141
|
+
|
|
142
|
+
(defn cancel-steps
|
|
143
|
+
[command now-ms]
|
|
144
|
+
(let [payload (domain/data-map command)
|
|
145
|
+
request {:type "eyeHeadTracking.requestCancel"
|
|
146
|
+
:agency "gaze"
|
|
147
|
+
:targetAgency "eyeHeadTracking"
|
|
148
|
+
:requestId (request-id "gaze:cancel" now-ms)
|
|
149
|
+
:reason (or (:reason payload) "cancelled")
|
|
150
|
+
:requestedAt now-ms}]
|
|
151
|
+
[{:op "request-cancel" :request request}]))
|
|
152
|
+
|
|
153
|
+
(defn command-steps
|
|
154
|
+
[command state now-ms]
|
|
155
|
+
(let [type (command-type command)]
|
|
156
|
+
(cond
|
|
157
|
+
(not (contains? supported-command-types type))
|
|
158
|
+
[{:op "fail"
|
|
159
|
+
:reason "unsupported-command"
|
|
160
|
+
:commandType type}]
|
|
161
|
+
|
|
162
|
+
(= "configure" type)
|
|
163
|
+
[{:op "apply-config" :config (:config command)}
|
|
164
|
+
{:op "publish-status" :status (:status state) :reason "configured"}]
|
|
165
|
+
|
|
166
|
+
(#{"setMode" "set-mode"} type)
|
|
167
|
+
[{:op "set-mode" :mode (:mode command)}
|
|
168
|
+
{:op "publish-status" :status (:status state) :reason "mode"}]
|
|
169
|
+
|
|
170
|
+
(#{"setActive" "set-active"} type)
|
|
171
|
+
[{:op "set-active" :active (:active command)}
|
|
172
|
+
{:op "publish-status" :status (:status state) :reason "active"}]
|
|
173
|
+
|
|
174
|
+
(= "enable" type)
|
|
175
|
+
[{:op "set-enabled" :enabled true}
|
|
176
|
+
{:op "publish-status" :status "enabled" :reason "enabled"}]
|
|
177
|
+
|
|
178
|
+
(= "disable" type)
|
|
179
|
+
[{:op "set-enabled" :enabled false}
|
|
180
|
+
{:op "request-cancel"
|
|
181
|
+
:request {:type "eyeHeadTracking.requestCancel"
|
|
182
|
+
:agency "gaze"
|
|
183
|
+
:targetAgency "eyeHeadTracking"
|
|
184
|
+
:requestId (request-id "gaze:disable" now-ms)
|
|
185
|
+
:reason "disabled"
|
|
186
|
+
:requestedAt now-ms}}
|
|
187
|
+
{:op "publish-status" :status "disabled" :reason "disabled"}]
|
|
188
|
+
|
|
189
|
+
(= "reset" type)
|
|
190
|
+
(reset-steps command now-ms)
|
|
191
|
+
|
|
192
|
+
(#{"cancel" "conversation.cancelRequested"} type)
|
|
193
|
+
(cancel-steps (cond-> command
|
|
194
|
+
(= "conversation.cancelRequested" type)
|
|
195
|
+
(assoc :reason (or (:reason command) "conversation-cancel")))
|
|
196
|
+
now-ms)
|
|
197
|
+
|
|
198
|
+
:else
|
|
199
|
+
(target-steps command state now-ms))))
|
|
200
|
+
|
|
201
|
+
(defn plan-command
|
|
202
|
+
[command state now-ms]
|
|
203
|
+
(let [steps (command-steps command state now-ms)]
|
|
204
|
+
{:agency "gaze"
|
|
205
|
+
:commandType (command-type command)
|
|
206
|
+
:createdAt now-ms
|
|
207
|
+
:ok (not= "fail" (:op (first steps)))
|
|
208
|
+
:steps steps}))
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
(ns polymer.gaze.scheduler
|
|
2
|
+
(:require [polymer.gaze.state :as state]))
|
|
3
|
+
|
|
4
|
+
;; The Gaze scheduler owns ordering and coalescing for look requests. A burst of
|
|
5
|
+
;; attention/camera updates should become one downstream request, not many
|
|
6
|
+
;; overlapping eye/head movements.
|
|
7
|
+
|
|
8
|
+
(defn now-ms
|
|
9
|
+
[]
|
|
10
|
+
(.now js/Date))
|
|
11
|
+
|
|
12
|
+
(defn clear-timeout!
|
|
13
|
+
[timeout-atom]
|
|
14
|
+
(when-let [timeout @timeout-atom]
|
|
15
|
+
(js/clearTimeout timeout)
|
|
16
|
+
(reset! timeout-atom nil)))
|
|
17
|
+
|
|
18
|
+
(defn queue-entry
|
|
19
|
+
[step index]
|
|
20
|
+
(assoc step
|
|
21
|
+
:agency "gaze"
|
|
22
|
+
:queueIndex index
|
|
23
|
+
:queuedAt (now-ms)))
|
|
24
|
+
|
|
25
|
+
(defn create-scheduler
|
|
26
|
+
[{:keys [state-atom emit-event]}]
|
|
27
|
+
(let [coalesce-timeout (atom nil)
|
|
28
|
+
pending-request (atom nil)
|
|
29
|
+
queue (atom [])
|
|
30
|
+
disposed? (atom false)]
|
|
31
|
+
(letfn [(enqueue! [step]
|
|
32
|
+
(let [entry (queue-entry step (count @queue))]
|
|
33
|
+
(swap! queue conj entry)
|
|
34
|
+
entry))
|
|
35
|
+
|
|
36
|
+
(publish-status! [status reason]
|
|
37
|
+
(emit-event (cond-> {:type "gaze.status"
|
|
38
|
+
:agency "gaze"
|
|
39
|
+
:status status
|
|
40
|
+
:mode (:mode @state-atom)
|
|
41
|
+
:active (:active @state-atom)
|
|
42
|
+
:enabled (get-in @state-atom [:config :enabled])
|
|
43
|
+
:at (now-ms)}
|
|
44
|
+
reason (assoc :reason reason))))
|
|
45
|
+
|
|
46
|
+
(publish-request-now! []
|
|
47
|
+
(when-not @disposed?
|
|
48
|
+
(clear-timeout! coalesce-timeout)
|
|
49
|
+
(when-let [request @pending-request]
|
|
50
|
+
(reset! pending-request nil)
|
|
51
|
+
(enqueue! {:op "publish-request"
|
|
52
|
+
:requestId (:requestId request)})
|
|
53
|
+
(let [published-at (now-ms)]
|
|
54
|
+
(swap! state-atom state/record-requested request published-at)
|
|
55
|
+
(emit-event (assoc request :publishedAt published-at))))))
|
|
56
|
+
|
|
57
|
+
(queue-gaze-request! [request]
|
|
58
|
+
(when-not @disposed?
|
|
59
|
+
(let [queued-at (now-ms)
|
|
60
|
+
request (assoc request :queuedAt queued-at)
|
|
61
|
+
delay-ms (get-in @state-atom [:config :coalesceMs])]
|
|
62
|
+
(reset! pending-request request)
|
|
63
|
+
(swap! state-atom state/record-pending request)
|
|
64
|
+
(clear-timeout! coalesce-timeout)
|
|
65
|
+
(enqueue! {:op "coalesce-request"
|
|
66
|
+
:requestId (:requestId request)
|
|
67
|
+
:coalesceMs delay-ms})
|
|
68
|
+
(if (pos? delay-ms)
|
|
69
|
+
(reset! coalesce-timeout
|
|
70
|
+
(js/setTimeout publish-request-now! delay-ms))
|
|
71
|
+
(publish-request-now!)))))
|
|
72
|
+
|
|
73
|
+
(ignore-target! [plan reason]
|
|
74
|
+
(let [ignored {:type "gaze.targetIgnored"
|
|
75
|
+
:agency "gaze"
|
|
76
|
+
:requestId (:requestId plan)
|
|
77
|
+
:rawTarget (:rawTarget plan)
|
|
78
|
+
:target (:target plan)
|
|
79
|
+
:reason reason
|
|
80
|
+
:ignoredAt (now-ms)}]
|
|
81
|
+
(swap! state-atom state/record-ignored ignored)
|
|
82
|
+
(emit-event ignored)))
|
|
83
|
+
|
|
84
|
+
(cancel-pending! []
|
|
85
|
+
(clear-timeout! coalesce-timeout)
|
|
86
|
+
(reset! pending-request nil)
|
|
87
|
+
(swap! state-atom assoc :pendingRequest nil))
|
|
88
|
+
|
|
89
|
+
(execute-step! [step]
|
|
90
|
+
(enqueue! step)
|
|
91
|
+
(case (:op step)
|
|
92
|
+
"apply-config"
|
|
93
|
+
(do
|
|
94
|
+
(swap! state-atom state/configure (:config step))
|
|
95
|
+
(publish-status! (:status @state-atom) "configured"))
|
|
96
|
+
|
|
97
|
+
"set-mode"
|
|
98
|
+
(do
|
|
99
|
+
(swap! state-atom state/set-mode (:mode step) (now-ms))
|
|
100
|
+
(publish-status! (:status @state-atom) "mode"))
|
|
101
|
+
|
|
102
|
+
"set-active"
|
|
103
|
+
(do
|
|
104
|
+
(swap! state-atom state/set-active (:active step) (now-ms))
|
|
105
|
+
(publish-status! (:status @state-atom) "active"))
|
|
106
|
+
|
|
107
|
+
"set-enabled"
|
|
108
|
+
(do
|
|
109
|
+
(swap! state-atom state/set-enabled (:enabled step) (now-ms))
|
|
110
|
+
(publish-status! (if (:enabled step) "enabled" "disabled")
|
|
111
|
+
(if (:enabled step) "enabled" "disabled")))
|
|
112
|
+
|
|
113
|
+
"record-target"
|
|
114
|
+
(swap! state-atom state/record-plan (:plan step))
|
|
115
|
+
|
|
116
|
+
"publish-target-received"
|
|
117
|
+
(emit-event {:type "gaze.targetReceived"
|
|
118
|
+
:agency "gaze"
|
|
119
|
+
:requestId (get-in step [:plan :requestId])
|
|
120
|
+
:rawTarget (get-in step [:plan :rawTarget])
|
|
121
|
+
:source (get-in step [:plan :source])
|
|
122
|
+
:label (get-in step [:plan :label])
|
|
123
|
+
:at (now-ms)})
|
|
124
|
+
|
|
125
|
+
"publish-target-planned"
|
|
126
|
+
(emit-event {:type "gaze.targetPlanned"
|
|
127
|
+
:agency "gaze"
|
|
128
|
+
:requestId (get-in step [:plan :requestId])
|
|
129
|
+
:rawTarget (get-in step [:plan :rawTarget])
|
|
130
|
+
:target (get-in step [:plan :target])
|
|
131
|
+
:delta (get-in step [:plan :delta])
|
|
132
|
+
:eyeDurationMs (get-in step [:plan :eyeDurationMs])
|
|
133
|
+
:headDurationMs (get-in step [:plan :headDurationMs])
|
|
134
|
+
:at (now-ms)})
|
|
135
|
+
|
|
136
|
+
"ignore-target"
|
|
137
|
+
(ignore-target! (:plan step) (:reason step))
|
|
138
|
+
|
|
139
|
+
"queue-request"
|
|
140
|
+
(queue-gaze-request! (:request step))
|
|
141
|
+
|
|
142
|
+
"request-reset"
|
|
143
|
+
(do
|
|
144
|
+
(cancel-pending!)
|
|
145
|
+
(swap! state-atom state/record-reset (:request step))
|
|
146
|
+
(emit-event (:request step)))
|
|
147
|
+
|
|
148
|
+
"request-cancel"
|
|
149
|
+
(do
|
|
150
|
+
(cancel-pending!)
|
|
151
|
+
(swap! state-atom state/record-cancel (:request step))
|
|
152
|
+
(emit-event (:request step)))
|
|
153
|
+
|
|
154
|
+
"fail"
|
|
155
|
+
(emit-event {:type "error"
|
|
156
|
+
:agency "gaze"
|
|
157
|
+
:message (:reason step)
|
|
158
|
+
:commandType (:commandType step)})
|
|
159
|
+
|
|
160
|
+
nil))]
|
|
161
|
+
{:schedule
|
|
162
|
+
(fn [plan]
|
|
163
|
+
(when-not @disposed?
|
|
164
|
+
(doseq [step (:steps plan)]
|
|
165
|
+
(execute-step! step))))
|
|
166
|
+
|
|
167
|
+
:flush
|
|
168
|
+
(fn []
|
|
169
|
+
(publish-request-now!))
|
|
170
|
+
|
|
171
|
+
:reset
|
|
172
|
+
(fn []
|
|
173
|
+
(cancel-pending!)
|
|
174
|
+
(reset! queue []))
|
|
175
|
+
|
|
176
|
+
:queue
|
|
177
|
+
(fn []
|
|
178
|
+
@queue)
|
|
179
|
+
|
|
180
|
+
:dispose
|
|
181
|
+
(fn []
|
|
182
|
+
(reset! disposed? true)
|
|
183
|
+
(cancel-pending!)
|
|
184
|
+
(reset! queue []))})))
|