1use std::{
10 collections::{HashMap, HashSet},
11 sync::Arc,
12};
13
14use bevy_ecs::prelude::*;
15
16use rig_core::serve::Reply;
17use rig_core::{
18 effect::{EffectId, HandlerKey},
19 error::{ErrorKind, ErrorReport},
20 serve::{Dispatch, Origin},
21};
22
23use super::{
24 effect::{
25 EffectOutcome, Held, IdCounter, InFlight, Issued, PendingEffect, Publishing, Reserved,
26 Scope, Seq, Streamed, Tasks, ToolInputs,
27 },
28 handlers::{HandlerIndex, Registry, Served, ServedBy, Serves},
29 plugin::{Policy, Wake},
30 record::Recording,
31 witness::{DispatchWitness, Refused, bus_emitter},
32};
33use rig_core::observe::{Action, Reason, Stage};
34
35pub type Candidate = (
38 Without<InFlight>,
39 Without<EffectOutcome>,
40 Without<Held>,
41 Without<Issued>,
42);
43
44pub type CandidateView = (
47 Entity,
48 &'static Seq,
49 &'static PendingEffect,
50 Option<&'static Reserved>,
51 Option<&'static ToolInputs>,
52 Option<&'static super::AdapterOperation>,
53 Option<&'static ServedBy>,
54);
55
56pub fn dispatch(
64 mut commands: Commands,
65 policy: Res<Policy>,
66 wake: Res<Wake>,
67 index: Res<HandlerIndex>,
68 registry: Registry,
69 mut tasks: Tasks,
70 serves: Query<&Serves>,
71 served_by: Query<&ServedBy>,
72 pending: Query<CandidateView, Candidate>,
73 in_flight: Query<&InFlight>,
74 parents: Query<&ChildOf>,
75 issued: Query<&Issued>,
76 scopes: Query<&Scope>,
77 recording: Option<Res<Recording>>,
78 witnessing: DispatchWitness,
79 mut ids: ResMut<IdCounter>,
80) {
81 let mut candidates: Vec<_> = pending.iter().collect();
82 candidates.sort_by_key(|(_, seq, ..)| **seq);
83 let mut intake = 0usize;
84 let DispatchWitness { witness, subjects } = witnessing;
85
86 let policy = policy.0;
87 let serial = policy.serial_per_handler;
88 let mut issued_now: HashMap<Entity, EffectId> = HashMap::new();
91 let mut busy_now: HashSet<Entity> = HashSet::new();
93
94 for (entity, _, effect, reserved, inputs, operation, resolved) in candidates {
95 if intake >= policy.command_capacity.max(1) {
98 return;
99 }
100 let key = &effect.key;
101 let handler = resolved
102 .map(|ServedBy(handler)| *handler)
103 .or_else(|| index.entity(key));
104 let served = handler.and_then(|handler| registry.served(handler));
105 if let Some(handler) = handler
106 && serial
107 && (busy_now.contains(&handler)
108 || serves
109 .get(handler)
110 .is_ok_and(|serves| serves.iter().any(|effect| in_flight.contains(effect))))
111 {
112 if ancestor_served_by(entity, handler, &parents, &in_flight, &served_by) {
113 if let Some(witness) = &witness {
114 let mut subject = subjects.of(entity);
115 subject.parent = nearest_issued(entity, &parents, &issued, &issued_now);
116 witness.emit(
117 subject,
118 Stage::Dispatch,
119 bus_emitter(),
120 Action::Refused {
121 reason: Reason::with_detail("reentrant", reentrant(key).message),
122 },
123 );
124 }
125 commands
126 .entity(entity)
127 .insert((Refused, EffectOutcome(Err(reentrant(key)))));
128 }
129 continue;
130 }
131 let Some(served) = served else {
132 if let Some(witness) = &witness {
133 let mut subject = subjects.of(entity);
134 subject.parent = nearest_issued(entity, &parents, &issued, &issued_now);
135 witness.emit(
136 subject,
137 Stage::Dispatch,
138 bus_emitter(),
139 Action::Refused {
140 reason: Reason::with_detail(
141 "handler_unavailable",
142 handler_unavailable(key).message,
143 ),
144 },
145 );
146 }
147 commands
148 .entity(entity)
149 .insert((Refused, EffectOutcome(Err(handler_unavailable(key)))));
150 continue;
151 };
152
153 let raw_id = reserved.map_or(ids.0, |Reserved(id)| id.as_u64());
154 let Some(next_id) = raw_id.checked_add(1) else {
155 let report = ErrorReport::new(ErrorKind::Request, "effect ID allocator exhausted");
156 if let Some(witness) = &witness {
157 let mut subject = subjects.of(entity);
158 subject.parent = nearest_issued(entity, &parents, &issued, &issued_now);
159 witness.emit(
160 subject,
161 Stage::Dispatch,
162 bus_emitter(),
163 Action::Refused {
164 reason: Reason::with_detail("ids_exhausted", report.message.clone()),
165 },
166 );
167 }
168 commands
169 .entity(entity)
170 .insert((Refused, EffectOutcome(Err(report))));
171 continue;
172 };
173 ids.0 = ids.0.max(next_id);
174 let id = EffectId::from_raw(raw_id);
175 let origin = Origin {
176 parent: nearest_issued(entity, &parents, &issued, &issued_now),
177 scope: nearest_scope(entity, &parents, &scopes)
178 .map(|scope| std::sync::Arc::from(scope.as_str())),
179 };
180 let mut subject = subjects.of(entity);
183 subject.effect = Some(id);
184 subject.parent = origin.parent;
185 let adapter = operation.map(|operation| {
186 operation
187 .context
188 .for_host_attempt(subject.clone(), operation.host_attempt)
189 });
190
191 if let Some(witness) = &witness {
192 witness.emit(
193 subject.clone(),
194 Stage::Dispatch,
195 bus_emitter(),
196 Action::Issued,
197 );
198 }
199 let mut entity_commands = commands.entity(entity);
200 match served {
201 Served::Task(handler) => {
202 if let Some(recording) = &recording {
203 recording.begin(id, key.clone(), effect.kind.clone(), origin);
204 }
205 let handler = handler.clone();
206 let kind = effect.kind.clone();
207 let observed = Arc::new(super::record::ObservedState::default());
208 entity_commands.insert(super::record::Observed(observed.clone()));
209 let mut dispatch = Dispatch::new(id, effect.is_stream());
210 if let rig_core::effect::EffectKind::ToolCall { .. } = &kind {
211 let inbound = inputs.map(|inputs| inputs.0.clone()).unwrap_or_default();
212 let published = rig_core::tool::PublishedContext::new();
213 dispatch = dispatch
214 .with_scope(Arc::new(inbound))
215 .with_scope(published.clone());
216 entity_commands.insert(Publishing(published));
217 }
218 let published = dispatch.scope::<rig_core::tool::PublishedContext>();
219 entity_commands.insert(super::record::ReplacedBy(dispatch.replaced_by()));
220 let dispatch = dispatch.with_observer(Box::new(super::record::WorldObserver {
221 adapter,
222 published,
223 id,
224 recording: recording.as_ref().map(|r| (**r).clone()),
225 observed,
226 witness: witness
227 .as_ref()
228 .map(|witness| ((**witness).clone(), subject.clone())),
229 }));
230 let streaming = effect.is_stream();
231 let wake = wake.clone();
232 let task = bevy_tasks::IoTaskPool::get().spawn(async move {
233 let reply = handler.handle(kind, dispatch).await;
234 let reply = if streaming {
235 reply
236 } else {
237 Reply::Outcome(reply.into_outcome().await)
238 };
239 wake.signal();
240 reply
241 });
242 entity_commands.insert(tasks.serving(entity, task));
243 if effect.is_stream() {
244 entity_commands.insert(Streamed::default());
245 }
246 }
247 Served::World(world) => {
248 if let Err(report) = (world.ask)(&mut entity_commands, &effect.kind) {
249 entity_commands.insert(EffectOutcome(Err(report)));
250 continue;
251 }
252 if let Some(recording) = &recording {
253 recording.begin(id, key.clone(), effect.kind.clone(), origin);
254 }
255 }
256 }
257 entity_commands
258 .insert((Issued(id), InFlight { key: key.clone() }))
259 .remove::<Reserved>();
260 if let Some(handler) = handler {
261 if resolved.is_none() {
262 entity_commands.insert(ServedBy(handler));
263 }
264 busy_now.insert(handler);
265 }
266 issued_now.insert(entity, id);
267 intake += 1;
268 }
269}
270
271fn ancestor_served_by(
273 entity: Entity,
274 handler: Entity,
275 parents: &Query<&ChildOf>,
276 in_flight: &Query<&InFlight>,
277 served_by: &Query<&ServedBy>,
278) -> bool {
279 let mut current = entity;
280 while let Ok(parent) = parents.get(current) {
281 current = parent.parent();
282 if in_flight.contains(current)
283 && served_by
284 .get(current)
285 .is_ok_and(|ServedBy(serving)| *serving == handler)
286 {
287 return true;
288 }
289 }
290 false
291}
292
293fn nearest_issued(
295 entity: Entity,
296 parents: &Query<&ChildOf>,
297 issued: &Query<&Issued>,
298 issued_now: &HashMap<Entity, EffectId>,
299) -> Option<EffectId> {
300 let mut current = entity;
301 while let Ok(parent) = parents.get(current) {
302 current = parent.parent();
303 if let Some(id) = issued_now.get(¤t) {
304 return Some(*id);
305 }
306 if let Ok(Issued(id)) = issued.get(current) {
307 return Some(*id);
308 }
309 }
310 None
311}
312
313fn nearest_scope(
315 entity: Entity,
316 parents: &Query<&ChildOf>,
317 scopes: &Query<&Scope>,
318) -> Option<String> {
319 let mut current = entity;
320 loop {
321 if let Ok(Scope(scope)) = scopes.get(current) {
322 return Some(scope.clone());
323 }
324 match parents.get(current) {
325 Ok(parent) => current = parent.parent(),
326 Err(_) => return None,
327 }
328 }
329}
330
331pub fn handler_unavailable(key: &HandlerKey) -> ErrorReport {
333 ErrorReport::new(
334 ErrorKind::HandlerUnavailable,
335 format!("no handler is bound to `{key}`"),
336 )
337 .with_retryable(false)
338}
339
340pub fn reentrant(key: &HandlerKey) -> ErrorReport {
343 ErrorReport::new(
344 ErrorKind::Request,
345 format!(
346 "`{key}` is served one at a time and an ancestor of this effect is in flight on it: served, it would wait for itself"
347 ),
348 )
349 .with_retryable(false)
350}