rig_ecs/systems/
backoff.rs1use std::time::Duration;
12
13use bevy_ecs::prelude::*;
14use bevy_reflect::Reflect;
15use bevy_time::DelayedCommandsExt;
16use rig_core::observe::Emitter;
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 agent::{Backoff, RunOf},
21 bus::{PendingEffect, acquire_hold, release_hold},
22};
23
24pub const BACKOFF_OWNER: &str = "rig-ecs/backoff";
26
27#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
30#[reflect(Component)]
31pub struct RetryAttempt(pub usize);
32
33pub fn delay(backoff: &Backoff, attempt: usize) -> Duration {
36 let factor = 2u32.saturating_pow(attempt.saturating_sub(1).min(31) as u32);
37 backoff.base.saturating_mul(factor).min(backoff.max)
38}
39
40pub fn hold_retries(
45 mut commands: Commands,
46 effects: Query<(Entity, &ChildOf), Added<PendingEffect>>,
47 turns: Query<(&ChildOf, &RetryAttempt)>,
48 runs: Query<&RunOf>,
49 backoffs: Query<&Backoff>,
50) {
51 for (effect, turn_of) in &effects {
52 let Ok((run_of, RetryAttempt(attempt))) = turns.get(turn_of.parent()) else {
53 continue;
54 };
55 let run = run_of.parent();
56 let Ok(RunOf(agent)) = runs.get(run) else {
57 continue;
58 };
59 let Some(backoff) = backoffs.get(run).ok().or_else(|| backoffs.get(*agent).ok()) else {
60 continue;
61 };
62 let wait = delay(backoff, *attempt);
63 commands.queue(move |world: &mut World| {
64 let _ = acquire_hold(world, effect, Emitter::named(BACKOFF_OWNER));
66 });
67 commands
68 .delayed()
69 .duration(wait)
70 .queue(move |world: &mut World| {
71 let _ = release_hold(world, effect, BACKOFF_OWNER);
72 });
73 }
74}