1use std::sync::Arc;
10
11use bevy_ecs::{
12 lifecycle::{Insert, Remove},
13 prelude::*,
14};
15use rig_core::{
16 effect::{Delivery, DeliveryKind, EffectId, EffectKind, HandlerDescriptor, Outcome},
17 error::ErrorReport,
18 serve::{Recorder, cancelled},
19 streaming::{Item, Relayed, StreamEvent},
20};
21
22use super::{
23 effect::{EffectOutcome, InFlight, Issued},
24 handlers::Bound,
25};
26use bevy_ecs::component::Component;
27
28#[derive(Resource, Clone)]
35pub struct Recording(Arc<dyn Recorder + Send + Sync>);
36
37impl Recording {
38 pub fn new(recorder: impl Recorder + Send + Sync) -> Self {
40 Self(Arc::new(recorder))
41 }
42
43 pub fn install(world: &mut World, recorder: impl Recorder + Send + Sync) {
46 let recording = Self::new(recorder);
47 recording.begin_delivery_tracking();
48 let mut bound: Vec<HandlerDescriptor> = world
49 .query::<&Bound>()
50 .iter(world)
51 .map(|bound| bound.descriptor.clone())
52 .collect();
53 bound.sort_by(|a, b| a.key.cmp(&b.key));
54 recording.handlers(bound);
55 world.insert_resource(recording);
56 }
57}
58
59impl std::ops::Deref for Recording {
60 type Target = dyn Recorder + Send + Sync;
61
62 fn deref(&self) -> &Self::Target {
63 &*self.0
64 }
65}
66
67#[derive(Resource, Default)]
69pub struct DeliveryBatch(pub u64);
70
71pub fn begin_delivery_pass(mut batch: ResMut<DeliveryBatch>) {
73 batch.0 += 1;
74}
75
76pub fn record_outcome(
79 added: On<Add, EffectOutcome>,
80 issued: Query<(&Issued, Has<super::collect::CollectedOutcome>), With<InFlight>>,
81 recording: Option<Res<Recording>>,
82 batch: Res<DeliveryBatch>,
83) {
84 if let Some(recording) = recording
85 && let Ok((Issued(id), collected)) = issued.get(added.event().entity)
86 {
87 if !collected {
88 recording.unsupported_delivery("an in-flight EffectOutcome bypassed Collect; submit world answers with WorldOutcome or typed Answer instead");
89 }
90 recording.delivery(Delivery {
91 batch: batch.0,
92 id: *id,
93 kind: DeliveryKind::Outcome,
94 });
95 }
96}
97
98#[derive(Component, Clone, Default)]
102pub struct Observed(pub Arc<ObservedState>);
103
104#[derive(Component)]
108pub struct ReplacedBy(pub Arc<std::sync::Mutex<Option<String>>>);
109
110impl ReplacedBy {
111 pub fn layer(&self) -> Option<String> {
113 self.0
114 .lock()
115 .unwrap_or_else(std::sync::PoisonError::into_inner)
116 .clone()
117 }
118}
119
120#[derive(Default)]
122pub struct ObservedState {
123 state: std::sync::Mutex<Observation>,
124}
125
126#[derive(Default)]
127struct Observation {
128 outcome: Option<Result<Outcome, ErrorReport>>,
129 discarded: bool,
130 closed: bool,
131}
132
133impl ObservedState {
134 fn lock(&self) -> std::sync::MutexGuard<'_, Observation> {
135 self.state
136 .lock()
137 .unwrap_or_else(std::sync::PoisonError::into_inner)
138 }
139
140 pub fn take_outcome(&self) -> Option<Result<Outcome, ErrorReport>> {
143 let mut state = self.lock();
144 state.closed = true;
145 state.outcome.take()
146 }
147
148 pub fn has_outcome(&self) -> bool {
150 self.lock().outcome.is_some()
151 }
152
153 pub fn is_discarded(&self) -> bool {
155 self.lock().discarded
156 }
157}
158
159pub struct WorldObserver {
163 pub adapter: Option<rig_core::observe::AdapterContext>,
165 pub published: Option<Arc<rig_core::tool::PublishedContext>>,
167 pub id: EffectId,
169 pub recording: Option<Recording>,
171 pub observed: Arc<ObservedState>,
173 pub witness: Option<(super::witness::Witnessing, rig_core::observe::Subject)>,
175}
176
177impl WorldObserver {
178 fn record_answer(&self, state: &mut Observation, outcome: &Result<Outcome, ErrorReport>) {
179 if let (Some(recording), Some(output)) = (
180 &self.recording,
181 self.published
182 .as_ref()
183 .and_then(|published| published.result_context()),
184 ) {
185 recording.tool_output(self.id, output);
186 }
187 state.outcome = Some(outcome.clone());
188 }
189
190 fn record_item(&self, item: &Result<Relayed, ErrorReport>) {
191 if let Some(recording) = &self.recording
192 && recording.keep_events()
193 {
194 match item {
195 Ok(Relayed::Origin(origin)) => recording.origin(self.id, origin),
196 Ok(Relayed::Item(item)) => recording.event(self.id, item),
197 Ok(Relayed::Done(_)) => {}
198 Err(error) => recording.stream_error(self.id, error),
199 }
200 }
201 }
202}
203
204impl rig_core::serve::Observe for WorldObserver {
205 fn adapter_context(&self) -> Option<rig_core::observe::AdapterContext> {
206 if let Some(context) = &self.adapter {
207 return Some(context.clone());
208 }
209 let (witness, subject) = self.witness.as_ref()?;
210 let mut subject = subject.clone();
211 subject.effect = Some(self.id);
212 Some(rig_core::observe::AdapterContext::new(
213 witness.sink().clone(),
214 subject,
215 format!("effect/{}", self.id),
216 ))
217 }
218
219 fn outcome(&mut self, outcome: &Result<Outcome, ErrorReport>) {
220 let mut state = self.observed.lock();
221 if !state.closed {
222 self.record_answer(&mut state, outcome);
223 }
224 }
225
226 fn keep_events(&self) -> bool {
227 self.recording
228 .as_ref()
229 .is_some_and(|recording| recording.keep_events())
230 }
231
232 fn stream_item(
233 &mut self,
234 item: &Result<Relayed, ErrorReport>,
235 outcome: Option<&Result<Outcome, ErrorReport>>,
236 ) {
237 let mut state = self.observed.lock();
240 if state.closed {
241 return;
242 }
243 self.record_item(item);
244 if let Some(outcome) = outcome {
245 self.record_answer(&mut state, outcome);
246 }
247 }
248
249 fn origin(&mut self, origin: &rig_core::message::Origin) {
250 let state = self.observed.lock();
251 if !state.closed
252 && let Some(recording) = &self.recording
253 {
254 recording.origin(self.id, origin);
255 }
256 }
257
258 fn event(&mut self, item: &Item<StreamEvent>) {
259 let state = self.observed.lock();
260 if !state.closed
261 && let Some(recording) = &self.recording
262 {
263 recording.event(self.id, item);
264 }
265 }
266
267 fn stream_error(&mut self, error: &ErrorReport) {
268 let state = self.observed.lock();
269 if !state.closed
270 && let Some(recording) = &self.recording
271 {
272 recording.stream_error(self.id, error);
273 }
274 }
275
276 fn discard(&mut self, layer: &str) {
277 let mut state = self.observed.lock();
278 if !state.closed {
279 state.discarded = true;
280 if let Some(recording) = &self.recording {
281 recording.discard(self.id);
282 }
283 if let Some((witness, subject)) = &self.witness {
284 let mut subject = subject.clone();
285 subject.effect = Some(self.id);
286 witness.emit(
287 subject,
288 rig_core::observe::Stage::Handler,
289 rig_core::observe::Emitter::named(layer),
290 rig_core::observe::Action::Denied {
291 reason: rig_core::observe::Reason::code("layer_discarded"),
292 },
293 );
294 }
295 }
296 }
297
298 fn patch(&mut self, kind: &EffectKind) {
299 let state = self.observed.lock();
300 if !state.closed
301 && let Some(recording) = &self.recording
302 {
303 recording.patch(self.id, kind.clone());
304 }
305 }
306}
307
308pub fn record_cancelled(
313 removed: On<Remove, InFlight>,
314 effects: Query<(
315 &Issued,
316 Option<&EffectOutcome>,
317 Has<super::collect::CollectedOutcome>,
318 Option<&super::effect::Publishing>,
319 Option<&super::effect::ToolOutputs>,
320 Option<&Observed>,
321 )>,
322 recording: Option<Res<Recording>>,
323 batch: Res<DeliveryBatch>,
324) {
325 let Some(recording) = recording else {
326 return;
327 };
328 let Ok((Issued(id), outcome, collected, publishing, outputs, observed)) =
329 effects.get(removed.event().entity)
330 else {
331 return;
332 };
333 if observed.is_some_and(|observed| observed.0.is_discarded()) {
334 return;
335 }
336 match outcome {
337 Some(EffectOutcome(landed)) if collected => {
341 let original = observed.and_then(|observed| observed.0.take_outcome());
342 if observed.is_none()
343 && let Some(outputs) = outputs
344 {
345 recording.tool_output(*id, outputs.0.result_context());
346 }
347 recording.resolve(*id, original.unwrap_or_else(|| landed.clone()));
348 }
349 Some(_) => {}
350 None => {
351 let original = observed.and_then(|observed| observed.0.take_outcome());
352 let output = publishing
353 .and_then(|published| published.0.result_context())
354 .or_else(|| outputs.map(|outputs| outputs.0.result_context()));
355 if let Some(output) = output {
356 recording.tool_output(*id, output);
357 }
358 if original.as_ref().is_some_and(|answer| {
359 !answer
360 .as_ref()
361 .is_err_and(|error| error.kind == rig_core::error::ErrorKind::Cancelled)
362 }) {
363 recording.delivery(Delivery {
364 batch: batch.0,
365 id: *id,
366 kind: DeliveryKind::Cancelled,
367 });
368 }
369 recording.resolve(*id, original.unwrap_or_else(|| Err(cancelled())));
370 }
371 }
372}
373
374pub fn witness_cancelled(
377 removed: On<Remove, InFlight>,
378 effects: Query<(Has<EffectOutcome>, Option<&Observed>), With<Issued>>,
379 subjects: super::witness::Subjects,
380 witness: Option<Res<super::witness::Witnessing>>,
381) {
382 let entity = removed.event().entity;
383 let Some(witness) = witness else {
384 return;
385 };
386 let Ok((answered, observed)) = effects.get(entity) else {
387 return;
388 };
389 if answered || observed.is_some_and(|observed| observed.0.is_discarded()) {
390 return;
391 }
392 let observation = rig_core::observe::Observation::new(
393 subjects.of(entity),
394 rig_core::observe::Stage::Collect,
395 super::witness::bus_emitter(),
396 rig_core::observe::Action::Cancelled {
397 reason: rig_core::observe::Reason::from_report(&cancelled()),
398 },
399 );
400 witness.observe(observation);
401}
402
403pub fn record_bound(
405 inserted: On<Insert, Bound>,
406 bound: Query<&Bound>,
407 recording: Option<Res<Recording>>,
408) {
409 let Some(recording) = recording else {
410 return;
411 };
412 if let Ok(bound) = bound.get(inserted.event().entity) {
413 recording.handlers(vec![bound.descriptor.clone()]);
414 }
415}
416
417#[cfg(test)]
418mod tests;