use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use crate::context::GlobalContext;
use crate::error::Result;
use crate::events::SubscriptionId;
use crate::module::ModuleInstance;
use crate::router::HypenRouter;
use crate::state::State;
pub const DEFAULT_PERSIST_CAP: usize = 10;
pub trait ManagedModule: Send + Sync {
fn name(&self) -> &str;
fn mount(&self);
fn activate(&self);
fn deactivate(&self);
fn destroy(&self);
fn is_persistent(&self) -> bool {
false
}
}
impl<S: State> ManagedModule for ModuleInstance<S> {
fn name(&self) -> &str {
ModuleInstance::name(self)
}
fn mount(&self) {
ModuleInstance::mount(self)
}
fn activate(&self) {
ModuleInstance::activate(self)
}
fn deactivate(&self) {
ModuleInstance::deactivate(self)
}
fn destroy(&self) {
ModuleInstance::unmount(self)
}
fn is_persistent(&self) -> bool {
false
}
}
pub type ModuleFactory =
Arc<dyn Fn() -> Result<Arc<dyn ManagedModule>> + Send + Sync>;
pub struct RouteDefinition {
pub path: String,
pub component: String,
pub factory: ModuleFactory,
pub persist: Option<bool>,
}
impl RouteDefinition {
pub fn factory<F>(path: impl Into<String>, component: impl Into<String>, f: F) -> Self
where
F: Fn() -> Result<Arc<dyn ManagedModule>> + Send + Sync + 'static,
{
Self {
path: path.into(),
component: component.into(),
factory: Arc::new(f),
persist: None,
}
}
pub fn persist(mut self, persist: bool) -> Self {
self.persist = Some(persist);
self
}
fn cache_key(&self) -> String {
self.component.to_lowercase()
}
}
#[derive(Debug, Clone)]
pub struct ManagedRouterOptions {
pub max_persisted_modules: usize,
pub default_persist: bool,
}
impl Default for ManagedRouterOptions {
fn default() -> Self {
Self {
max_persisted_modules: DEFAULT_PERSIST_CAP,
default_persist: false,
}
}
}
struct State_ {
routes: Vec<RouteDefinition>,
active: Option<(usize, Arc<dyn ManagedModule>)>,
persisted: HashMap<String, Arc<dyn ManagedModule>>,
lru: VecDeque<String>,
unsub: Option<SubscriptionId>,
}
pub struct ManagedRouter {
router: Arc<HypenRouter>,
global_context: Arc<GlobalContext>,
options: ManagedRouterOptions,
inner: Arc<Mutex<State_>>,
}
impl ManagedRouter {
pub fn new(
router: Arc<HypenRouter>,
global_context: Arc<GlobalContext>,
options: ManagedRouterOptions,
) -> Self {
Self {
router,
global_context,
options,
inner: Arc::new(Mutex::new(State_ {
routes: Vec::new(),
active: None,
persisted: HashMap::new(),
lru: VecDeque::new(),
unsub: None,
})),
}
}
pub fn add_route(&self, route: RouteDefinition) -> &Self {
self.inner.lock().unwrap().routes.push(route);
self
}
pub fn start(&self) {
{
let mut g = self.inner.lock().unwrap();
if g.unsub.is_some() {
return;
}
let inner = Arc::clone(&self.inner);
let router = Arc::clone(&self.router);
let context = Arc::clone(&self.global_context);
let options = self.options.clone();
let sub = self.router.on_navigate(move |_payload| {
let path = router.current_path();
handle_route_change(&inner, &context, &options, &path);
});
g.unsub = Some(sub);
}
let path = self.router.current_path();
handle_route_change(&self.inner, &self.global_context, &self.options, &path);
}
pub fn stop(&self) {
let (active, persisted, sub) = {
let mut g = self.inner.lock().unwrap();
let active = g.active.take().map(|(_, m)| m);
let persisted: Vec<_> = g.persisted.drain().collect();
g.lru.clear();
(active, persisted, g.unsub.take())
};
if let Some(sub) = sub {
self.router.off(sub);
}
if let Some(m) = active {
m.deactivate();
m.destroy();
self.global_context.unregister_module(&m.name().to_lowercase());
}
for (key, m) in persisted {
m.destroy();
self.global_context.unregister_module(&key);
}
}
pub fn get_active_module(&self) -> Option<Arc<dyn ManagedModule>> {
self.inner
.lock()
.unwrap()
.active
.as_ref()
.map(|(_, m)| Arc::clone(m))
}
pub fn get_active_route_path(&self) -> Option<String> {
let g = self.inner.lock().unwrap();
g.active.as_ref().map(|(idx, _)| g.routes[*idx].path.clone())
}
pub fn persisted_keys(&self) -> Vec<String> {
self.inner.lock().unwrap().lru.iter().cloned().collect()
}
}
impl Drop for ManagedRouter {
fn drop(&mut self) {
if self.inner.lock().unwrap().unsub.is_some() {
self.stop();
}
}
}
fn handle_route_change(
inner: &Arc<Mutex<State_>>,
global_context: &Arc<GlobalContext>,
options: &ManagedRouterOptions,
path: &str,
) {
let matched_idx = {
let g = inner.lock().unwrap();
g.routes
.iter()
.position(|r| hypen_engine::match_path(&r.path, path).is_some())
};
let Some(idx) = matched_idx else {
unmount_active(inner, global_context, options);
return;
};
{
let g = inner.lock().unwrap();
if let Some((active_idx, _)) = &g.active {
if *active_idx == idx {
return;
}
}
}
let preloaded = {
let mut g = inner.lock().unwrap();
let key = g.routes[idx].cache_key();
g.persisted.remove(&key).map(|m| {
g.lru.retain(|k| k != &key);
m
})
};
unmount_active(inner, global_context, options);
mount_route(inner, global_context, idx, preloaded);
}
fn mount_route(
inner: &Arc<Mutex<State_>>,
global_context: &Arc<GlobalContext>,
idx: usize,
preloaded: Option<Arc<dyn ManagedModule>>,
) {
enum Plan {
Hit(Arc<dyn ManagedModule>),
Miss { key: String, factory: ModuleFactory },
}
let plan = if let Some(m) = preloaded {
Plan::Hit(m)
} else {
let g = inner.lock().unwrap();
let key = g.routes[idx].cache_key();
let factory = Arc::clone(&g.routes[idx].factory);
Plan::Miss { key, factory }
};
let module = match plan {
Plan::Hit(m) => {
let mut g = inner.lock().unwrap();
g.active = Some((idx, Arc::clone(&m)));
drop(g);
m.activate();
return;
}
Plan::Miss { key, factory } => match (factory)() {
Ok(m) => {
global_context.register_module_state(&key, serde_json::Value::Null);
let mut g = inner.lock().unwrap();
g.active = Some((idx, Arc::clone(&m)));
drop(g);
m
}
Err(e) => {
eprintln!(
"[hypen-server] managed_router: factory for component '{}' failed: {e}",
key_for_idx(inner, idx)
);
let mut g = inner.lock().unwrap();
g.active = None;
return;
}
},
};
module.mount();
module.activate();
}
fn unmount_active(
inner: &Arc<Mutex<State_>>,
global_context: &Arc<GlobalContext>,
options: &ManagedRouterOptions,
) {
let prev = {
let mut g = inner.lock().unwrap();
g.active.take()
};
let Some((idx, module)) = prev else { return };
let (key, persist) = {
let g = inner.lock().unwrap();
let route = &g.routes[idx];
(
route.cache_key(),
route.persist.unwrap_or(options.default_persist),
)
};
module.deactivate();
if persist {
let mut evictees: Vec<(String, Arc<dyn ManagedModule>)> = Vec::new();
{
let mut g = inner.lock().unwrap();
g.persisted.insert(key.clone(), module);
g.lru.retain(|k| k != &key);
g.lru.push_back(key.clone());
while g.lru.len() > options.max_persisted_modules {
if let Some(oldest) = g.lru.pop_front() {
if let Some(m) = g.persisted.remove(&oldest) {
evictees.push((oldest, m));
}
}
}
}
for (k, m) in evictees {
m.destroy();
global_context.unregister_module(&k);
}
} else {
module.destroy();
global_context.unregister_module(&key);
}
}
fn key_for_idx(inner: &Arc<Mutex<State_>>, idx: usize) -> String {
inner
.lock()
.unwrap()
.routes
.get(idx)
.map(|r| r.cache_key())
.unwrap_or_default()
}