rig_core/conversation/lineage.rs
1//! Which saved log a conversation continues.
2
3use serde::{Deserialize, Serialize};
4
5/// The saved log a [`Conversation`](super::Conversation) continues: a token
6/// a store chose when it started that log, carried by every value loaded
7/// from it or saved to it. A store appends a value's unsaved changes to a
8/// log only when the value holds what that log holds
9/// ([`Conversation::continues`](super::Conversation::continues)): its
10/// lineage, and as many of its changes. The count alone cannot tell two
11/// conversations apart.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct Lineage(u64);
15
16impl Lineage {
17 /// A conversation begun here from nothing and never saved: it continues
18 /// any empty log.
19 pub const EMPTY: Self = Self(0);
20
21 /// A conversation read back from its serde form, which does not carry
22 /// its lineage: it continues no log, so saving it writes it whole.
23 pub const DETACHED: Self = Self(u64::MAX);
24
25 /// The lineage of a log just started: neither [`EMPTY`](Self::EMPTY)
26 /// nor [`DETACHED`](Self::DETACHED), and unlike any other log's but by
27 /// a one in 2^64 chance.
28 pub fn fresh() -> Self {
29 let now = web_time::SystemTime::now()
30 .duration_since(web_time::UNIX_EPOCH)
31 .unwrap_or_default();
32 let clock = now.as_secs().rotate_left(32) ^ u64::from(now.subsec_nanos());
33 Self((fastrand::u64(..) ^ clock).clamp(1, u64::MAX - 1))
34 }
35
36 /// Whether this is a log's lineage: neither [`EMPTY`](Self::EMPTY) nor
37 /// [`DETACHED`](Self::DETACHED).
38 pub fn is_log(self) -> bool {
39 self != Self::EMPTY && self != Self::DETACHED
40 }
41}