1use crate::intrinsics::IntrinsicsPrior;
42use crate::session::{FrameImage, FrameSet};
43use cumulus3d_core::{CameraId, FeatureStore};
44use cumulus3d_features::{CameraMode, ExtractionOptions, FeatureExtractor, ImageReport};
45use std::collections::{BTreeMap, VecDeque};
46use std::path::PathBuf;
47use std::sync::atomic::{AtomicBool, Ordering};
48use std::sync::{Arc, Condvar, Mutex, MutexGuard};
49use std::time::{Duration, Instant};
50
51#[derive(Clone, Debug)]
53pub(crate) struct PositionExtract {
54 pub reports: Vec<ImageReport>,
56 pub notes: Vec<String>,
58 pub cams: BTreeMap<String, CameraId>,
60 pub elapsed: Duration,
62}
63
64struct Job {
65 images: Vec<FrameImage>,
66 epoch: u64,
67 cancelled: AtomicBool,
68 done: Mutex<Option<Result<PositionExtract, String>>>,
69 cv: Condvar,
70}
71
72impl Job {
73 fn fulfil(&self, r: Result<PositionExtract, String>) {
74 *lock(&self.done) = Some(r);
75 self.cv.notify_all();
76 }
77
78 fn wait(&self) -> Result<PositionExtract, String> {
79 let mut d = lock(&self.done);
80 loop {
81 if let Some(r) = d.as_ref() {
82 return r.clone();
83 }
84 d = self.cv.wait(d).unwrap_or_else(|p| p.into_inner());
85 }
86 }
87}
88
89struct State {
90 store: Arc<FeatureStore>,
91 jobs: VecDeque<Arc<Job>>,
93 queue: VecDeque<Arc<Job>>,
95 epoch: u64,
96 worker_started: bool,
97 closed: bool,
98}
99
100pub(crate) struct AheadCore {
101 extractor: Arc<FeatureExtractor>,
102 root: PathBuf,
103 opts: ExtractionOptions,
104 prior: Option<Arc<IntrinsicsPrior>>,
105 limit: usize,
106 cams: Mutex<BTreeMap<String, CameraId>>,
108 st: Mutex<State>,
109 cv: Condvar,
110}
111
112fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
113 m.lock().unwrap_or_else(|p| p.into_inner())
114}
115
116impl AheadCore {
117 pub(crate) fn new(
118 extractor: Arc<FeatureExtractor>,
119 root: PathBuf,
120 opts: ExtractionOptions,
121 prior: Option<Arc<IntrinsicsPrior>>,
122 limit: usize,
123 store: Arc<FeatureStore>,
124 ) -> Arc<Self> {
125 Arc::new(Self {
126 extractor,
127 root,
128 opts,
129 prior,
130 limit,
131 cams: Mutex::new(BTreeMap::new()),
132 st: Mutex::new(State { store, jobs: VecDeque::new(), queue: VecDeque::new(), epoch: 0, worker_started: false, closed: false }),
133 cv: Condvar::new(),
134 })
135 }
136
137 pub(crate) fn close(&self) {
139 let mut st = lock(&self.st);
140 st.closed = true;
141 for j in st.jobs.drain(..) {
142 j.cancelled.store(true, Ordering::Relaxed);
143 }
144 st.queue.clear();
145 self.cv.notify_all();
146 }
147
148 pub(crate) fn reset(&self, store: Arc<FeatureStore>, cams: BTreeMap<String, CameraId>) {
150 {
151 let mut st = lock(&self.st);
152 st.epoch += 1;
153 for j in st.jobs.drain(..) {
154 j.cancelled.store(true, Ordering::Relaxed);
155 }
156 st.queue.clear();
157 st.store = store;
158 }
159 *lock(&self.cams) = cams;
161 }
162
163 pub(crate) fn hint(self: &Arc<Self>, images: &[FrameImage]) -> bool {
166 if self.limit == 0 || images.is_empty() {
167 return false;
168 }
169 let mut st = lock(&self.st);
170 if st.closed {
171 return false;
172 }
173 if st.jobs.iter().any(|j| j.images == images) {
174 return true;
175 }
176 if st.jobs.len() >= self.limit {
177 return false;
178 }
179 let job = Arc::new(Job {
180 images: images.to_vec(),
181 epoch: st.epoch,
182 cancelled: AtomicBool::new(false),
183 done: Mutex::new(None),
184 cv: Condvar::new(),
185 });
186 st.jobs.push_back(Arc::clone(&job));
187 st.queue.push_back(job);
188 if !st.worker_started {
189 st.worker_started = true;
190 let core = Arc::clone(self);
191 let spawned = std::thread::Builder::new().name("c3d-ahead".into()).spawn(move || core.work());
192 if spawned.is_err() {
193 st.worker_started = false;
194 st.jobs.pop_back();
195 st.queue.pop_back();
196 return false;
197 }
198 }
199 self.cv.notify_all();
200 true
201 }
202
203 pub(crate) fn pending(&self) -> usize {
205 lock(&self.st).jobs.len()
206 }
207
208 fn work(self: Arc<Self>) {
209 loop {
210 let job = {
211 let mut st = lock(&self.st);
212 loop {
213 if st.closed {
214 return;
215 }
216 if let Some(j) = st.queue.pop_front() {
217 break j;
218 }
219 st = self.cv.wait(st).unwrap_or_else(|p| p.into_inner());
220 }
221 };
222 let mut cams = lock(&self.cams);
223 let (store, epoch) = {
224 let st = lock(&self.st);
225 (Arc::clone(&st.store), st.epoch)
226 };
227 let r = if job.epoch != epoch || job.cancelled.load(Ordering::Relaxed) {
228 Err("cancelled".to_string())
229 } else {
230 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
232 self.extract_many(&store, &[job.images.as_slice()], &mut cams).map(|mut v| v.remove(0))
233 }))
234 .unwrap_or_else(|_| Err("특징 추출 중 패닉".to_string()))
235 };
236 drop(cams);
237 job.fulfil(r);
238 }
239 }
240
241 pub(crate) fn take(&self, images: &[FrameImage]) -> Option<Result<PositionExtract, String>> {
244 let job = {
245 let mut st = lock(&self.st);
246 let i = st.jobs.iter().position(|j| j.images == images)?;
247 for stale in st.jobs.drain(..i) {
248 stale.cancelled.store(true, Ordering::Relaxed);
249 }
250 st.jobs.pop_front()?
251 };
252 Some(job.wait())
253 }
254
255 pub(crate) fn run_now(&self, store: &FeatureStore, positions: &[&[FrameImage]]) -> Result<Vec<PositionExtract>, String> {
257 let mut cams = lock(&self.cams);
258 self.extract_many(store, positions, &mut cams)
259 }
260
261 fn extract_many(
265 &self,
266 store: &FeatureStore,
267 positions: &[&[FrameImage]],
268 cams: &mut BTreeMap<String, CameraId>,
269 ) -> Result<Vec<PositionExtract>, String> {
270 let mut out: Vec<PositionExtract> = Vec::with_capacity(positions.len());
271 let mut rest = positions;
272 if cams.is_empty() {
273 let Some((first, tail)) = positions.split_first() else { return Ok(out) };
274 rest = tail;
275 let t0 = Instant::now();
276 let names: Vec<String> = first.iter().map(|i| i.name.clone()).collect();
277 let mut o = self.opts.clone();
278 o.reader.camera_mode = CameraMode::PerFolder;
279 let reports = self.extractor.extract_files(store, &self.root, &names, &o).map_err(|e| e.to_string())?;
280 for im in first.iter() {
281 let s = store.image_by_name(&im.name).ok_or_else(|| format!("{} 첫 영상 특징 추출 실패", im.camera))?;
282 cams.insert(im.camera.clone(), s.camera_id);
283 }
284 let notes = self.apply_prior(store, cams)?;
285 out.push(PositionExtract { reports, notes, cams: cams.clone(), elapsed: t0.elapsed() });
286 }
287 if rest.is_empty() {
288 return Ok(out);
289 }
290 let parallel = self.limit > 0 || rest.len() > 1;
293 let extract_group =
294 |group: &[&FrameImage], cams: &mut BTreeMap<String, CameraId>| -> Result<(Vec<ImageReport>, Vec<String>), String> {
295 let items: Vec<(String, CameraMode)> = group
296 .iter()
297 .map(|im| (im.name.clone(), cams.get(&im.camera).copied().map_or(CameraMode::PerFolder, CameraMode::Existing)))
298 .collect();
299 let reports = self.extractor.extract_ordered(store, &self.root, &items, &self.opts).map_err(|e| e.to_string())?;
300 let mut new_cam = false;
301 for im in group {
302 if !cams.contains_key(&im.camera) {
303 if let Some(s) = store.image_by_name(&im.name) {
304 cams.insert(im.camera.clone(), s.camera_id);
305 new_cam = true;
306 }
307 }
308 }
309 let notes = if new_cam { self.apply_prior(store, cams)? } else { Vec::new() };
310 Ok((reports, notes))
311 };
312 let t0 = Instant::now();
313 let mut done: Vec<(Vec<ImageReport>, Vec<String>)> = Vec::with_capacity(rest.len());
314 if parallel {
315 let all: Vec<&FrameImage> = rest.iter().flat_map(|p| p.iter()).collect();
316 let (reports, notes) = extract_group(&all, cams)?;
317 let mut reports = reports.into_iter();
318 for (i, pos) in rest.iter().enumerate() {
319 done.push((reports.by_ref().take(pos.len()).collect(), if i == 0 { notes.clone() } else { Vec::new() }));
320 }
321 } else {
322 for pos in rest {
323 let (mut reports, mut notes) = (Vec::new(), Vec::new());
324 for im in pos.iter() {
325 let (r, n) = extract_group(&[im], cams)?;
326 reports.extend(r);
327 notes.extend(n);
328 }
329 done.push((reports, notes));
330 }
331 }
332 let share = t0.elapsed() / rest.len() as u32;
333 for (reports, notes) in done {
334 out.push(PositionExtract { reports, notes, cams: cams.clone(), elapsed: share });
335 }
336 Ok(out)
337 }
338
339 fn apply_prior(&self, store: &FeatureStore, cams: &BTreeMap<String, CameraId>) -> Result<Vec<String>, String> {
342 let mut notes = Vec::new();
343 let Some(pr) = &self.prior else { return Ok(notes) };
344 let pr = pr.resolve(cams);
345 for mut cam in store.cameras() {
346 let before = cam.params.clone();
347 if pr.apply(&mut cam)? && cam.params != before {
348 notes.push(format!(
349 "[prior] 카메라 {}: 내부값 초기화 {} → {}",
350 cam.camera_id,
351 crate::session::fmt_params(&before),
352 crate::session::fmt_params(&cam.params)
353 ));
354 store.update_camera(cam).map_err(|e| e.to_string())?;
355 }
356 }
357 Ok(notes)
358 }
359}
360
361#[derive(Clone)]
364pub struct Lookahead(pub(crate) Arc<AheadCore>);
365
366impl Lookahead {
367 pub fn hint(&self, frames: &FrameSet) -> bool {
371 self.0.hint(&frames.images)
372 }
373
374 pub fn limit(&self) -> usize {
376 self.0.limit
377 }
378
379 pub fn pending(&self) -> usize {
381 self.0.pending()
382 }
383}
384
385impl std::fmt::Debug for Lookahead {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 f.debug_struct("Lookahead").field("limit", &self.0.limit).field("pending", &self.pending()).finish()
388 }
389}