khora_control/registry.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Agent registry for automatic registration and ordered iteration.
16
17use khora_core::agent::dependency::AgentDependency;
18use khora_core::agent::timing::AgentImportance;
19use khora_core::agent::{Agent, EngineMode, ExecutionPhase};
20use khora_core::control::gorna::AgentId;
21use std::sync::{Arc, Mutex};
22
23/// Tuple returned by [`AgentRegistry::collect_for_phase`] for each agent
24/// matching the requested phase and mode: `(agent, importance, priority,
25/// dependencies)`.
26pub type PhaseAgentEntry = (
27 Arc<Mutex<dyn Agent>>,
28 AgentImportance,
29 f32,
30 Vec<AgentDependency>,
31);
32
33/// Entry in the agent registry containing the agent and its priority.
34struct AgentEntry {
35 agent: Arc<Mutex<dyn Agent>>,
36 priority: f32,
37 /// Modes in which this agent is active.
38 /// Empty = active in all modes.
39 active_modes: Vec<EngineMode>,
40}
41
42/// Registry that manages all registered agents with automatic priority ordering.
43///
44/// Agents are automatically sorted by priority (highest first) and can be
45/// iterated in execution order.
46pub struct AgentRegistry {
47 entries: Vec<AgentEntry>,
48}
49
50impl AgentRegistry {
51 /// Creates a new empty registry.
52 pub fn new() -> Self {
53 Self {
54 entries: Vec::new(),
55 }
56 }
57
58 /// Registers an agent with the given priority.
59 ///
60 /// Higher priority values mean the agent is updated first.
61 /// The agent is active in all engine modes.
62 pub fn register(&mut self, agent: Arc<Mutex<dyn Agent>>, priority: f32) {
63 self.register_for_mode(agent, priority, vec![]);
64 }
65
66 /// Registers an agent with the given priority, active only in the specified modes.
67 ///
68 /// Higher priority values mean the agent is updated first.
69 /// If `modes` is empty, the agent is active in all modes.
70 pub fn register_for_mode(
71 &mut self,
72 agent: Arc<Mutex<dyn Agent>>,
73 priority: f32,
74 modes: Vec<EngineMode>,
75 ) {
76 let id = agent.lock().map(|a| a.id()).unwrap_or(AgentId::Asset);
77 log::info!(
78 "AgentRegistry: Registered {:?} (priority={:.2}, modes={:?})",
79 id,
80 priority,
81 modes
82 );
83
84 self.entries.push(AgentEntry {
85 agent,
86 priority,
87 active_modes: modes,
88 });
89 self.entries.sort_by(|a, b| {
90 b.priority
91 .partial_cmp(&a.priority)
92 .unwrap_or(std::cmp::Ordering::Equal)
93 });
94 }
95
96 /// Returns the number of registered agents.
97 pub fn len(&self) -> usize {
98 self.entries.len()
99 }
100
101 /// Returns true if no agents are registered.
102 pub fn is_empty(&self) -> bool {
103 self.entries.is_empty()
104 }
105
106 /// Returns an iterator over all agents in priority order (highest first).
107 pub fn iter(&self) -> impl Iterator<Item = &Arc<Mutex<dyn Agent>>> {
108 self.entries.iter().map(|e| &e.agent)
109 }
110
111 /// Returns the [`AgentId`]s of every registered agent in priority order.
112 ///
113 /// Used by the scheduler to seed a fresh `AgentCompletionMap` per frame.
114 pub fn all_ids(&self) -> Vec<AgentId> {
115 self.entries
116 .iter()
117 .filter_map(|entry| entry.agent.lock().ok().map(|a| a.id()))
118 .collect()
119 }
120
121 /// Initializes all agents once after registration.
122 ///
123 /// Calls `on_initialize()` on each agent in priority order, giving them
124 /// access to the engine services for caching and lane setup.
125 pub fn initialize_all(&self, context: &mut khora_core::EngineContext<'_>) {
126 for entry in &self.entries {
127 if let Ok(mut agent) = entry.agent.lock() {
128 let id = agent.id();
129 agent.on_initialize(context);
130 log::info!("AgentRegistry: Initialized {:?}", id);
131 }
132 }
133 }
134
135 /// Executes all agents in priority order.
136 ///
137 /// Called each frame. Each agent selects the appropriate lanes and
138 /// dispatches their execution.
139 pub fn execute_all(&self, context: &mut khora_core::EngineContext<'_>) {
140 for entry in &self.entries {
141 if let Ok(mut agent) = entry.agent.lock() {
142 agent.execute(context);
143 }
144 }
145 }
146
147 /// Returns the agent with the given ID, if registered.
148 pub fn get_by_id(&self, id: AgentId) -> Option<Arc<Mutex<dyn Agent>>> {
149 for entry in &self.entries {
150 if let Ok(agent) = entry.agent.lock() {
151 if agent.id() == id {
152 return Some(entry.agent.clone());
153 }
154 }
155 }
156 None
157 }
158
159 /// Collects agents that are allowed to run in the given phase and mode.
160 /// Returns a list of (agent, importance, priority, dependencies).
161 pub fn collect_for_phase(
162 &self,
163 phase: ExecutionPhase,
164 mode: &EngineMode,
165 ) -> Vec<PhaseAgentEntry> {
166 self.entries
167 .iter()
168 .filter_map(|entry| {
169 let agent = entry.agent.lock().ok()?;
170 let timing = agent.execution_timing();
171
172 // Filter by phase
173 if !timing.allowed_phases.contains(&phase) {
174 return None;
175 }
176
177 // Filter by mode (empty = all modes)
178 if !entry.active_modes.is_empty() && !entry.active_modes.contains(mode) {
179 return None;
180 }
181
182 Some((
183 entry.agent.clone(),
184 timing.importance,
185 timing.priority,
186 timing.dependencies,
187 ))
188 })
189 .collect()
190 }
191
192 /// Executes a specific agent by ID.
193 pub fn execute_agent(&self, id: AgentId, context: &mut khora_core::EngineContext<'_>) -> bool {
194 if let Some(agent) = self.get_by_id(id) {
195 if let Ok(mut a) = agent.lock() {
196 a.execute(context);
197 return true;
198 }
199 }
200 false
201 }
202}
203
204impl Default for AgentRegistry {
205 fn default() -> Self {
206 Self::new()
207 }
208}