templar_curator_primitives/policy/supply_queue/
mod.rs

1//! Supply queue for managing pending allocation requests.
2
3use alloc::vec::Vec;
4use templar_vault_kernel::TargetId;
5use typed_builder::TypedBuilder;
6
7/// An entry in the supply queue representing a pending allocation.
8#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
9#[derive(Clone, PartialEq, Eq, TypedBuilder)]
10#[builder(field_defaults(setter(into)))]
11pub struct SupplyQueueEntry {
12    pub target_id: TargetId,
13    pub amount: u128,
14    #[builder(default)]
15    pub priority: u8,
16    #[builder(default)]
17    pub queued_at_ns: u64,
18}
19
20impl SupplyQueueEntry {
21    #[must_use]
22    pub fn new(target_id: TargetId, amount: u128) -> Self {
23        Self {
24            target_id,
25            amount,
26            priority: 0,
27            queued_at_ns: 0,
28        }
29    }
30}
31
32impl From<(TargetId, u128)> for SupplyQueueEntry {
33    fn from(value: (TargetId, u128)) -> Self {
34        Self::new(value.0, value.1)
35    }
36}
37
38/// A queue of pending supply requests.
39#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
40#[derive(Clone, Default)]
41pub struct SupplyQueue {
42    pub entries: Vec<SupplyQueueEntry>,
43    pub max_length: usize,
44}
45
46impl SupplyQueue {
47    #[must_use]
48    pub fn is_empty(&self) -> bool {
49        self.entries.is_empty()
50    }
51
52    #[must_use]
53    pub fn len(&self) -> usize {
54        self.entries.len()
55    }
56
57    #[must_use]
58    pub fn is_full(&self) -> bool {
59        self.max_length > 0 && self.entries.len() >= self.max_length
60    }
61
62    /// Add an entry to the supply queue.
63    ///
64    /// Entries are inserted in priority order (higher priority first).
65    /// Within the same priority, FIFO order is maintained.
66    pub fn enqueue(&self, entry: SupplyQueueEntry) -> Result<Self, SupplyQueueError> {
67        if entry.amount == 0 {
68            return Err(SupplyQueueError::ZeroAmount);
69        }
70
71        if self.is_full() {
72            return Err(SupplyQueueError::QueueFull {
73                max_length: self.max_length,
74            });
75        }
76
77        let mut new_queue = self.clone();
78
79        // Insert maintaining priority order (higher priority first)
80        let insert_pos = new_queue
81            .entries
82            .iter()
83            .position(|e| e.priority < entry.priority)
84            .unwrap_or(new_queue.entries.len());
85
86        new_queue.entries.insert(insert_pos, entry);
87
88        Ok(new_queue)
89    }
90
91    pub fn dequeue(&self) -> Result<(Self, SupplyQueueEntry), SupplyQueueError> {
92        if self.is_empty() {
93            return Err(SupplyQueueError::QueueEmpty);
94        }
95
96        let mut new_queue = self.clone();
97        let entry = new_queue.entries.remove(0);
98
99        Ok((new_queue, entry))
100    }
101
102    #[must_use]
103    pub fn peek(&self) -> Option<&SupplyQueueEntry> {
104        self.entries.first()
105    }
106
107    #[must_use]
108    pub fn total(&self) -> u128 {
109        self.entries
110            .iter()
111            .fold(0u128, |acc, e| acc.saturating_add(e.amount))
112    }
113
114    /// Returns totals grouped by target ID.
115    #[must_use]
116    pub fn totals_by_target(&self) -> Vec<(TargetId, u128)> {
117        let mut totals: Vec<(TargetId, u128)> = Vec::new();
118        for entry in &self.entries {
119            if let Some((_, sum)) = totals
120                .iter_mut()
121                .find(|(target_id, _)| *target_id == entry.target_id)
122            {
123                *sum = sum.saturating_add(entry.amount);
124            } else {
125                totals.push((entry.target_id, entry.amount));
126            }
127        }
128        totals.sort_unstable_by_key(|(target_id, _)| *target_id);
129        totals
130    }
131
132    /// Remove all entries for a specific target from the queue.
133    #[must_use]
134    pub fn remove_target(&self, target_id: TargetId) -> Self {
135        let mut new_queue = self.clone();
136        new_queue.entries.retain(|e| e.target_id != target_id);
137        new_queue
138    }
139
140    /// Drain the queue into a list of entries.
141    #[must_use]
142    pub fn drain(&self) -> (Self, Vec<SupplyQueueEntry>) {
143        let entries: Vec<SupplyQueueEntry> = self.entries.to_vec();
144        let empty_queue = Self {
145            entries: Vec::new(),
146            max_length: self.max_length,
147        };
148        (empty_queue, entries)
149    }
150
151    /// Convert the queue to an allocation plan.
152    ///
153    /// Aggregates entries by target and returns a plan suitable for the
154    /// allocation state machine.
155    #[must_use]
156    pub fn to_allocation_plan(&self) -> Vec<(TargetId, u128)> {
157        self.totals_by_target()
158    }
159
160    /// Get total amount for a specific target.
161    #[must_use]
162    pub fn total_for_target(&self, target_id: TargetId) -> u128 {
163        self.entries
164            .iter()
165            .filter(|e| e.target_id == target_id)
166            .fold(0u128, |acc, e| acc.saturating_add(e.amount))
167    }
168
169    /// Check if a target has any pending entries.
170    #[must_use]
171    pub fn has_target(&self, target_id: TargetId) -> bool {
172        self.entries.iter().any(|e| e.target_id == target_id)
173    }
174}
175
176impl From<Vec<SupplyQueueEntry>> for SupplyQueue {
177    fn from(entries: Vec<SupplyQueueEntry>) -> Self {
178        Self {
179            entries,
180            max_length: 0,
181        }
182    }
183}
184
185/// Errors that can occur during supply queue operations.
186#[templar_vault_macros::vault_derive]
187#[derive(Clone, PartialEq, Eq)]
188pub enum SupplyQueueError {
189    /// Queue is at maximum capacity.
190    QueueFull { max_length: usize },
191    /// Amount must be greater than zero.
192    ZeroAmount,
193    /// Queue is empty.
194    QueueEmpty,
195}