Skip to main content

onlyne_client/session/
slice.rs

1//! Role-slice hot reload after `Event::SpecReloaded`.
2//!
3//! `reconfigure` currently only runs on `welcome`. A live connection that
4//! stays up across `onlyne reload` never sees that frame, so this module
5//! compares the `query_roles` row against the dispatcher's current slice
6//! and calls `reconfigure` only when `runtime`, `max_sessions`, or the role's
7//! `allowed_targets` changed.
8//!
9//! The drive travels in the slice because it is the runtime's property, read
10//! from the spec's `[client.runtime]`; the placement it pairs with is the
11//! machine's and never appears here (`docs/v2-PLAN.md` §"驱动与放置").
12
13use onlyne_config::Drive;
14use onlyne_proto::{RoleInfo, Welcome};
15
16/// The fields `reconfigure` consumes.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct RoleSlice {
19    /// How the client talks to the runtime (`[client.runtime] drive`).
20    pub drive: Drive,
21    pub command: Vec<String>,
22    pub max_sessions: u32,
23    /// The roles a session of this role owes a delivery to (`allowed_targets`).
24    /// It is the whole policy, read twice: the server gates the ACL on it, and
25    /// the client's completion guard owes it.
26    pub required_targets: Vec<String>,
27}
28
29impl RoleSlice {
30    pub fn from_welcome(welcome: &Welcome) -> Self {
31        let runtime = welcome.runtime.clone().unwrap_or_default();
32        Self {
33            drive: drive_of(runtime.drive),
34            command: runtime.command,
35            max_sessions: welcome.max_sessions,
36            required_targets: welcome.allowed_targets.clone(),
37        }
38    }
39
40    pub fn from_role_info(info: &RoleInfo, _current: &RoleSlice) -> Self {
41        Self {
42            drive: drive_of(info.runtime.drive),
43            command: info.runtime.command.clone(),
44            max_sessions: info.max_sessions,
45            required_targets: info.edges.clone(),
46        }
47    }
48}
49
50/// The wire's drive as the file's drive.
51///
52/// The two vocabularies are separate on purpose — `onlyne-config` owns the
53/// spelling a file uses and `onlyne-proto` the one a frame uses — so this is
54/// the one place the client crosses between them, and the pair rule
55/// (`onlyne_config::validate_drive_placement`) is written in the file's terms.
56pub fn drive_of(wire: onlyne_proto::Drive) -> Drive {
57    match wire {
58        onlyne_proto::Drive::Plugin => Drive::Plugin,
59        onlyne_proto::Drive::Acp => Drive::Acp,
60        onlyne_proto::Drive::Exec => Drive::Exec,
61    }
62}
63
64/// Fields that would change if `next` were applied.
65pub fn slice_diff(current: &RoleSlice, next: &RoleSlice) -> Vec<&'static str> {
66    let mut fields = Vec::new();
67    if current.drive != next.drive || current.command != next.command {
68        fields.push("runtime");
69    }
70    if current.max_sessions != next.max_sessions {
71        fields.push("max_sessions");
72    }
73    if current.required_targets != next.required_targets {
74        fields.push("allowed_targets");
75    }
76    fields
77}
78
79/// Apply `next` when it differs; return the fields that changed.
80pub fn apply_if_changed(
81    current: &RoleSlice,
82    next: RoleSlice,
83) -> Option<(RoleSlice, Vec<&'static str>)> {
84    let fields = slice_diff(current, &next);
85    if fields.is_empty() {
86        None
87    } else {
88        Some((next, fields))
89    }
90}