tawasuyu

tawasuyu/brahman

Rust 326TOML 123JSON 17Markdown 144606 Definitions

⎇ 72758e75ce515f355f27615671c95ecaf8c2a8bc crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/natal_cache.rs Rust · 116 lines · 3.9 KB History Who touched what raw

perf(tahuantinsuyu): LRU cache de NatalChart por (birth, config, offset) 904f33406 · sergio ·

1//! LRU cache para `NatalChart` por contenido.
2//!
3//! `NatalChart::compute` cuesta varios ms (VSOP2013 + casas + aspectos
4//! base). En el shell, mover el slider de orbe o tocar un toggle
5//! dispara un `compose()` completo donde la **misma** carta natal del
6//! sujeto principal se recomputa idéntica. Lo mismo pasa con el partner
7//! de Synastry / Composite — cada drag de slider rearma `partner_natal`.
8//!
9//! Este cache de 8 entradas es suficiente: el usuario rara vez tiene
10//! más de 2 cartas activas a la vez (natal + partner) y el LRU bota la
11//! más vieja cuando se llena. La clave es el **contenido** de
12//! `StoredBirthData + StoredChartConfig + offset_minutes`, así que
13//! editar una carta invalida automáticamente su entrada.
14
15use std::collections::hash_map::DefaultHasher;
16use std::hash::{Hash, Hasher};
17use std::sync::{Arc, Mutex, OnceLock};
18
19use eternal_astrology::NatalChart;
20use tahuantinsuyu_model::{StoredBirthData, StoredChartConfig};
21
22const CAPACITY: usize = 8;
23
24type Key = u64;
25
26struct Cache {
27    /// Front = más reciente, back = más viejo. `VecDeque` simple — con
28    /// cap 8 el search lineal cuesta menos que un HashMap.
29    entries: Vec<(Key, Arc<NatalChart>)>,
30}
31
32impl Cache {
33    fn new() -> Self {
34        Self {
35            entries: Vec::with_capacity(CAPACITY),
36        }
37    }
38
39    fn get(&mut self, k: Key) -> Option<Arc<NatalChart>> {
40        let idx = self.entries.iter().position(|(kk, _)| *kk == k)?;
41        // Move-to-front para mantener LRU.
42        let hit = self.entries.remove(idx);
43        let chart = hit.1.clone();
44        self.entries.insert(0, hit);
45        Some(chart)
46    }
47
48    fn put(&mut self, k: Key, v: Arc<NatalChart>) {
49        // Si ya existe la entrada (race: dos threads computaron lo mismo
50        // antes de poblar), reemplaza in-place.
51        if let Some(idx) = self.entries.iter().position(|(kk, _)| *kk == k) {
52            self.entries.remove(idx);
53        }
54        self.entries.insert(0, (k, v));
55        if self.entries.len() > CAPACITY {
56            self.entries.pop();
57        }
58    }
59}
60
61static CACHE: OnceLock<Mutex<Cache>> = OnceLock::new();
62
63fn cache() -> &'static Mutex<Cache> {
64    CACHE.get_or_init(|| Mutex::new(Cache::new()))
65}
66
67/// Hash de contenido: incluye todos los campos relevantes para el
68/// cómputo de la carta natal. `f64` se hashea via `to_bits` para evitar
69/// el `Hash` ausente de los flotantes.
70pub fn key_for(
71    birth: &StoredBirthData,
72    config: &StoredChartConfig,
73    offset_minutes: i64,
74) -> u64 {
75    let mut h = DefaultHasher::new();
76    // Birth data — fecha/hora/lugar.
77    birth.year.hash(&mut h);
78    birth.month.hash(&mut h);
79    birth.day.hash(&mut h);
80    birth.hour.hash(&mut h);
81    birth.minute.hash(&mut h);
82    birth.second.to_bits().hash(&mut h);
83    birth.tz_offset_minutes.hash(&mut h);
84    birth.latitude_deg.to_bits().hash(&mut h);
85    birth.longitude_deg.to_bits().hash(&mut h);
86    birth.altitude_m.to_bits().hash(&mut h);
87    // Config — todos los toggles que afectan el cómputo de placements y
88    // casas. Los enums derivan Debug; reusamos eso para hashear sin
89    // forzarles `Hash` manualmente.
90    format!("{:?}", config.house_system).hash(&mut h);
91    format!("{:?}", config.zodiac).hash(&mut h);
92    config.ayanamsha.hash(&mut h);
93    config.bodies.hash(&mut h);
94    config.include_south_node.hash(&mut h);
95    config.include_lilith.hash(&mut h);
96    config.include_main_belt_asteroids.hash(&mut h);
97    config.include_fixed_stars.hash(&mut h);
98    // Offset temporal (rectificación rápida).
99    offset_minutes.hash(&mut h);
100    h.finish()
101}
102
103/// Consulta. Devuelve `None` en miss; el caller debe computar y llamar
104/// a `insert`.
105pub fn get(k: Key) -> Option<Arc<NatalChart>> {
106    cache().lock().ok()?.get(k)
107}
108
109/// Inserta una entrada. Idempotente: re-insertar la misma key la mueve
110/// al frente.
111pub fn insert(k: Key, v: Arc<NatalChart>) {
112    if let Ok(mut guard) = cache().lock() {
113        guard.put(k, v);
114    }
115}
116