alpine_protocol_sdk/
phase.rs

1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use crate::error::AlpineSdkError;
4use tracing::info;
5
6/// Represents the current progression through discovery/handshake phases.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(usize)]
9pub(crate) enum Phase {
10    Idle = 0,
11    Discovery = 1,
12    Handshake = 2,
13}
14
15impl Phase {
16    fn from_usize(value: usize) -> Self {
17        match value {
18            1 => Phase::Discovery,
19            2 => Phase::Handshake,
20            _ => Phase::Idle,
21        }
22    }
23
24    pub(crate) fn label(&self) -> &'static str {
25        match self {
26            Phase::Idle => "Idle",
27            Phase::Discovery => "Discovery",
28            Phase::Handshake => "Handshake",
29        }
30    }
31}
32
33static CURRENT_PHASE: AtomicUsize = AtomicUsize::new(Phase::Idle as usize);
34
35/// Guard that holds the SDK in a specific phase until dropped.
36pub struct PhaseGuard {
37    previous: Phase,
38    active: Phase,
39}
40
41impl Drop for PhaseGuard {
42    fn drop(&mut self) {
43        CURRENT_PHASE.store(self.previous as usize, Ordering::SeqCst);
44        info!(
45            "[ALPINE][PHASE] exiting phase: {} -> {}",
46            self.active.label(),
47            self.previous.label()
48        );
49    }
50}
51
52pub(crate) fn current_phase() -> Phase {
53    Phase::from_usize(CURRENT_PHASE.load(Ordering::SeqCst))
54}
55
56fn enter_phase(desired: Phase) -> Result<PhaseGuard, AlpineSdkError> {
57    info!(
58        "[ALPINE][PHASE] attempting to enter phase: {}",
59        desired.label()
60    );
61    match CURRENT_PHASE.compare_exchange(
62        Phase::Idle as usize,
63        desired as usize,
64        Ordering::SeqCst,
65        Ordering::SeqCst,
66    ) {
67        Ok(_) => {
68            info!("[ALPINE][PHASE] entered phase: {}", desired.label());
69            Ok(PhaseGuard {
70                previous: Phase::Idle,
71                active: desired,
72            })
73        }
74        Err(current) => match Phase::from_usize(current) {
75            Phase::Discovery => {
76                if desired == Phase::Handshake {
77                    Err(AlpineSdkError::InvalidPhaseTransition(
78                        "discovery already running".into(),
79                    ))
80                } else {
81                    Err(AlpineSdkError::InvalidPhaseTransition(
82                        "discovery already running".into(),
83                    ))
84                }
85            }
86            Phase::Handshake => {
87                if desired == Phase::Discovery {
88                    Err(AlpineSdkError::DiscoveryAfterHandshake)
89                } else {
90                    Err(AlpineSdkError::HandshakeAlreadyInProgress)
91                }
92            }
93            Phase::Idle => enter_phase(desired),
94        },
95    }
96}
97
98/// Claims the discovery phase, preventing handshake/streaming work while held.
99pub fn claim_discovery() -> Result<PhaseGuard, AlpineSdkError> {
100    enter_phase(Phase::Discovery)
101}
102
103/// Claims the handshake phase, preventing discovery while held.
104pub fn claim_handshake() -> Result<PhaseGuard, AlpineSdkError> {
105    enter_phase(Phase::Handshake)
106}