Skip to main content

sim_lib_expr_tree_server/
model.rs

1//! Public bounded server model.
2
3use std::collections::VecDeque;
4
5use sim_kernel::{Expr, Symbol};
6use sim_value::build;
7
8/// Opaque identity of one authoritative expression-tree session.
9#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10pub struct SessionId(pub(crate) String);
11
12impl SessionId {
13    /// Parses an opaque session resource symbol.
14    pub fn from_resource(resource: &Symbol) -> Option<Self> {
15        (resource.namespace.as_deref() == Some("expr-tree/session"))
16            .then(|| Self(resource.name.to_string()))
17    }
18
19    /// Returns the standard resource symbol carried by surface requests.
20    pub fn resource(&self) -> Symbol {
21        Symbol::qualified("expr-tree/session", self.0.clone())
22    }
23}
24
25/// Opaque identity of one bounded session watch.
26#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
27pub struct WatchId(pub(crate) String);
28
29/// Hard lifecycle and backpressure limits for one server.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct ExpressionTreeServerLimits {
32    /// Maximum concurrent authoritative sessions.
33    pub max_sessions: usize,
34    /// Logical request ticks a session may remain idle.
35    pub max_idle_ticks: u64,
36    /// Maximum watches registered on one session.
37    pub max_watches_per_session: usize,
38    /// Maximum queued changes retained per watch and implicit change feed.
39    pub watch_capacity: usize,
40    /// Maximum entries rendered in one directory page.
41    pub max_page_entries: usize,
42    /// Maximum expanded directory depth rendered in one snapshot.
43    pub max_snapshot_depth: usize,
44}
45
46impl Default for ExpressionTreeServerLimits {
47    fn default() -> Self {
48        Self {
49            max_sessions: 128,
50            max_idle_ticks: 10_000,
51            max_watches_per_session: 16,
52            watch_capacity: 128,
53            max_page_entries: 128,
54            max_snapshot_depth: 16,
55        }
56    }
57}
58
59impl ExpressionTreeServerLimits {
60    pub(crate) fn validate(self) -> bool {
61        self.max_sessions > 0
62            && self.max_idle_ticks > 0
63            && self.max_watches_per_session > 0
64            && self.watch_capacity > 0
65            && self.max_page_entries > 0
66            && self.max_snapshot_depth > 0
67    }
68}
69
70/// One revisioned server-side change observation.
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub struct ChangeEvent {
73    /// Session resource whose snapshot changed.
74    pub resource: Symbol,
75    /// Session revision after the change.
76    pub revision: u64,
77    /// Mandatory monotone server logical tick.
78    pub logical_tick: u64,
79    /// Optional human wall-clock observation.
80    pub wall_ms: Option<u64>,
81    /// Stable change kind.
82    pub kind: String,
83    /// Canonical affected path, when known.
84    pub path: Option<String>,
85}
86
87impl ChangeEvent {
88    pub(crate) fn to_expr(&self) -> Expr {
89        build::map(vec![
90            ("resource", Expr::Symbol(self.resource.clone())),
91            ("revision", build::uint(self.revision)),
92            ("logical-tick", build::uint(self.logical_tick)),
93            (
94                "wall-ms",
95                self.wall_ms.map(build::uint).unwrap_or(Expr::Nil),
96            ),
97            ("kind", build::sym(&self.kind)),
98            (
99                "path",
100                self.path.as_ref().map(build::text).unwrap_or(Expr::Nil),
101            ),
102        ])
103    }
104}
105
106/// One bounded watch poll with explicit overflow evidence.
107#[derive(Clone, Debug, Eq, PartialEq)]
108pub struct WatchBatch {
109    /// Drained events in logical order.
110    pub events: Vec<ChangeEvent>,
111    /// Lifetime events dropped because this consumer was slow.
112    pub dropped: u64,
113    /// Whether the watch has been cancelled.
114    pub cancelled: bool,
115}
116
117pub(crate) struct WatchState {
118    pub(crate) events: VecDeque<ChangeEvent>,
119    pub(crate) dropped: u64,
120    pub(crate) cancelled: bool,
121}
122
123impl WatchState {
124    pub(crate) fn new() -> Self {
125        Self {
126            events: VecDeque::new(),
127            dropped: 0,
128            cancelled: false,
129        }
130    }
131
132    pub(crate) fn push(&mut self, event: ChangeEvent, capacity: usize) {
133        if self.cancelled {
134            return;
135        }
136        if self.events.len() == capacity {
137            self.events.pop_front();
138            self.dropped = self.dropped.saturating_add(1);
139        }
140        self.events.push_back(event);
141    }
142}