@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,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 []))})))