agentmux_cef\reducer/
top_level.rs1use std::time::Instant;
8
9use crate::state::*;
10
11use super::{DispatchOutput, HostEvent, HostState, emit_error, TOP_LEVEL_CREATION_HISTORY_CAP};
12
13pub(super) fn handle_enqueue_top_level_window(
16 state: &mut HostState,
17 request: TopLevelCreationRequest,
18) -> DispatchOutput {
19 if state.quit_state != QuitState::Running {
20 return emit_error(state, format!("enqueue_top_level_window: not Running (label={})", request.label));
21 }
22
23 if state.top_level_creation.in_flight.is_some()
26 && request.source == TopLevelSource::User
27 {
28 return emit_error(state, format!("enqueue_top_level_window: busy in-flight (label={})", request.label));
29 }
30
31 state.top_level_creation.queue.push_back(request);
32 let queue_len = state.top_level_creation.queue.len();
33 let v = state.bump_version();
34 let mut out = DispatchOutput {
35 events: vec![HostEvent::TopLevelQueueLengthChanged { len: queue_len, version: v }],
36 ..Default::default()
37 };
38 if state.top_level_creation.in_flight.is_none() {
40 let started = start_next_top_level_if_idle(state);
41 out.events.extend(started.events);
42 }
43 out
44}
45
46pub(super) fn start_next_top_level_if_idle(state: &mut HostState) -> DispatchOutput {
57 if state.top_level_creation.in_flight.is_some() {
58 return DispatchOutput::default();
59 }
60 if state.quit_state != QuitState::Running {
61 return DispatchOutput::default();
62 }
63 let request = match state.top_level_creation.queue.pop_front() {
64 Some(r) => r,
65 None => return DispatchOutput::default(),
66 };
67 state.top_level_creation.next_creation_id =
68 state.top_level_creation.next_creation_id.wrapping_add(1);
69 let creation_id = state.top_level_creation.next_creation_id;
70 let now = Instant::now();
71 state.top_level_creation.in_flight = Some(InFlightCreation {
72 creation_id,
73 label: request.label.clone(),
74 started_at: now,
75 phase: CreationPhase::Started,
76 });
77 let label = request.label.clone();
78 let source = request.source.clone();
79 let queue_len = state.top_level_creation.queue.len();
80 let v_req = state.bump_version();
81 let v_started = state.bump_version();
82 let v_eff = state.bump_version();
83 let v_qlen = state.bump_version();
84 DispatchOutput {
85 events: vec![
86 HostEvent::TopLevelCreationRequested {
87 creation_id,
88 source,
89 label: label.clone(),
90 version: v_req,
91 },
92 HostEvent::TopLevelCreationStarted {
93 creation_id,
94 label: label.clone(),
95 version: v_started,
96 },
97 HostEvent::Effect {
98 effect: EffectKind::PostCreateWindow { request, creation_id },
99 version: v_eff,
100 },
101 HostEvent::TopLevelQueueLengthChanged { len: queue_len, version: v_qlen },
102 ],
103 ..Default::default()
104 }
105}
106
107pub(super) fn handle_top_level_callback_fired(state: &mut HostState, label: String) -> DispatchOutput {
108 let matches_in_flight = state
109 .top_level_creation
110 .in_flight
111 .as_ref()
112 .map(|c| c.label == label)
113 .unwrap_or(false);
114 if !matches_in_flight {
115 let orphan_browser = state.browsers.get(&label).map(|h| h.browser.clone());
120 if let Some(browser) = orphan_browser {
121 let v = state.bump_version();
122 return DispatchOutput {
123 events: vec![HostEvent::Effect {
124 effect: EffectKind::CloseOrphanBrowser { browser },
125 version: v,
126 }],
127 ..Default::default()
128 };
129 }
130 return DispatchOutput::default();
131 }
132 let inflight = state.top_level_creation.in_flight.take().unwrap();
133 let now = Instant::now();
134 let latency_ms = now.duration_since(inflight.started_at).as_millis() as u64;
135 push_top_level_history(
136 state,
137 CompletedCreation {
138 creation_id: inflight.creation_id,
139 label: inflight.label.clone(),
140 outcome: TopLevelCreationOutcome::Completed,
141 started_at: inflight.started_at,
142 finished_at: now,
143 last_phase: CreationPhase::BrowserCallbackFired,
144 },
145 );
146 let v_done = state.bump_version();
147 let mut out = DispatchOutput {
148 events: vec![HostEvent::TopLevelCreationCompleted {
149 creation_id: inflight.creation_id,
150 label: inflight.label,
151 latency_ms,
152 version: v_done,
153 }],
154 ..Default::default()
155 };
156 let next = start_next_top_level_if_idle(state);
157 out.events.extend(next.events);
158 out
159}
160
161pub(super) fn handle_top_level_renderer_terminated(
162 state: &mut HostState,
163 label: String,
164 status: String,
165) -> DispatchOutput {
166 let matches = state
167 .top_level_creation
168 .in_flight
169 .as_ref()
170 .map(|c| c.label == label)
171 .unwrap_or(false);
172 if !matches {
173 return DispatchOutput::default();
174 }
175 let inflight = state.top_level_creation.in_flight.take().unwrap();
176 let now = Instant::now();
177 let outcome = TopLevelCreationOutcome::RendererTerminated { status };
178 push_top_level_history(
179 state,
180 CompletedCreation {
181 creation_id: inflight.creation_id,
182 label: inflight.label.clone(),
183 outcome: outcome.clone(),
184 started_at: inflight.started_at,
185 finished_at: now,
186 last_phase: inflight.phase,
187 },
188 );
189 let v = state.bump_version();
190 let mut out = DispatchOutput {
191 events: vec![HostEvent::TopLevelCreationFailed {
192 creation_id: inflight.creation_id,
193 label: inflight.label,
194 outcome,
195 version: v,
196 }],
197 ..Default::default()
198 };
199 let next = start_next_top_level_if_idle(state);
200 out.events.extend(next.events);
201 out
202}
203
204pub(super) fn handle_top_level_externally_closed(state: &mut HostState, label: String) -> DispatchOutput {
205 let matches = state
206 .top_level_creation
207 .in_flight
208 .as_ref()
209 .map(|c| c.label == label)
210 .unwrap_or(false);
211 if !matches {
212 return DispatchOutput::default();
213 }
214 let inflight = state.top_level_creation.in_flight.take().unwrap();
215 let now = Instant::now();
216 let outcome = TopLevelCreationOutcome::ExternallyClosed;
217 push_top_level_history(
218 state,
219 CompletedCreation {
220 creation_id: inflight.creation_id,
221 label: inflight.label.clone(),
222 outcome: outcome.clone(),
223 started_at: inflight.started_at,
224 finished_at: now,
225 last_phase: inflight.phase,
226 },
227 );
228 let v = state.bump_version();
229 let mut out = DispatchOutput {
230 events: vec![HostEvent::TopLevelCreationFailed {
231 creation_id: inflight.creation_id,
232 label: inflight.label,
233 outcome,
234 version: v,
235 }],
236 ..Default::default()
237 };
238 let next = start_next_top_level_if_idle(state);
239 out.events.extend(next.events);
240 out
241}
242
243pub(super) fn push_top_level_history(state: &mut HostState, entry: CompletedCreation) {
244 if state.top_level_creation.history.len() >= TOP_LEVEL_CREATION_HISTORY_CAP {
245 state.top_level_creation.history.pop_front();
246 }
247 state.top_level_creation.history.push_back(entry);
248}
249