Skip to main content

scarab_plugin_api/object_model/
handle.rs

1//! Object handles for type-safe references to terminal objects
2//!
3//! Handles provide a safe way to reference objects across the daemon-client boundary
4//! and within Fusabi scripts. Each handle includes a generation counter to detect
5//! stale references after object deletion/recreation.
6
7use std::fmt;
8
9/// Type of object this handle references
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u8)]
12pub enum ObjectType {
13    /// A top-level window (GUI window)
14    Window = 0,
15    /// A tab within a window
16    Tab = 1,
17    /// A pane within a tab
18    Pane = 2,
19    /// A multiplexer window (logical container)
20    MuxWindow = 3,
21    /// A multiplexer tab
22    MuxTab = 4,
23    /// A multiplexer pane
24    MuxPane = 5,
25}
26
27impl fmt::Display for ObjectType {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            ObjectType::Window => write!(f, "Window"),
31            ObjectType::Tab => write!(f, "Tab"),
32            ObjectType::Pane => write!(f, "Pane"),
33            ObjectType::MuxWindow => write!(f, "MuxWindow"),
34            ObjectType::MuxTab => write!(f, "MuxTab"),
35            ObjectType::MuxPane => write!(f, "MuxPane"),
36        }
37    }
38}
39
40/// A handle to an object managed by the object registry
41///
42/// Handles are lightweight, copyable references that include:
43/// - `object_type`: The type of object being referenced
44/// - `id`: Unique identifier for this object
45/// - `generation`: Counter incremented each time an ID is reused
46///
47/// The generation counter prevents the ABA problem where an object is deleted
48/// and a new object reuses the same ID.
49///
50/// # Examples
51///
52/// ```
53/// use scarab_plugin_api::object_model::{ObjectHandle, ObjectType};
54///
55/// let handle = ObjectHandle::new(ObjectType::Window, 42, 1);
56/// assert_eq!(handle.id(), 42);
57/// assert_eq!(handle.object_type(), ObjectType::Window);
58/// assert_eq!(handle.generation(), 1);
59/// ```
60#[derive(Clone, Copy, PartialEq, Eq, Hash)]
61pub struct ObjectHandle {
62    object_type: ObjectType,
63    id: u64,
64    generation: u32,
65}
66
67impl ObjectHandle {
68    /// Create a new object handle
69    ///
70    /// # Arguments
71    ///
72    /// * `object_type` - The type of object this handle references
73    /// * `id` - Unique identifier for this object
74    /// * `generation` - Generation counter for detecting stale handles
75    pub const fn new(object_type: ObjectType, id: u64, generation: u32) -> Self {
76        Self {
77            object_type,
78            id,
79            generation,
80        }
81    }
82
83    /// Get the object type
84    #[inline]
85    pub const fn object_type(&self) -> ObjectType {
86        self.object_type
87    }
88
89    /// Get the object ID
90    #[inline]
91    pub const fn id(&self) -> u64 {
92        self.id
93    }
94
95    /// Get the generation counter
96    #[inline]
97    pub const fn generation(&self) -> u32 {
98        self.generation
99    }
100
101    /// Check if this handle is valid for the given generation
102    ///
103    /// Returns true if the handle's generation matches the provided generation,
104    /// indicating the handle is still valid and hasn't been invalidated by
105    /// object deletion/recreation.
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use scarab_plugin_api::object_model::{ObjectHandle, ObjectType};
111    ///
112    /// let handle = ObjectHandle::new(ObjectType::Window, 1, 5);
113    /// assert!(handle.is_valid(5));
114    /// assert!(!handle.is_valid(6));
115    /// ```
116    #[inline]
117    pub const fn is_valid(&self, current_generation: u32) -> bool {
118        self.generation == current_generation
119    }
120
121    /// Create a new handle with an incremented generation
122    ///
123    /// This is useful when an object is deleted and its ID is reused for a new object.
124    #[inline]
125    pub const fn next_generation(&self) -> Self {
126        Self {
127            object_type: self.object_type,
128            id: self.id,
129            generation: self.generation.wrapping_add(1),
130        }
131    }
132}
133
134impl fmt::Debug for ObjectHandle {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(
137            f,
138            "ObjectHandle({}, id={}, gen={})",
139            self.object_type, self.id, self.generation
140        )
141    }
142}
143
144impl fmt::Display for ObjectHandle {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        write!(f, "{}#{}", self.object_type, self.id)
147    }
148}
149
150// Safety: ObjectHandle contains only primitive types that are Send + Sync
151unsafe impl Send for ObjectHandle {}
152unsafe impl Sync for ObjectHandle {}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_handle_creation() {
160        let handle = ObjectHandle::new(ObjectType::Window, 42, 1);
161        assert_eq!(handle.object_type(), ObjectType::Window);
162        assert_eq!(handle.id(), 42);
163        assert_eq!(handle.generation(), 1);
164    }
165
166    #[test]
167    fn test_handle_equality() {
168        let h1 = ObjectHandle::new(ObjectType::Tab, 10, 1);
169        let h2 = ObjectHandle::new(ObjectType::Tab, 10, 1);
170        let h3 = ObjectHandle::new(ObjectType::Tab, 10, 2);
171        let h4 = ObjectHandle::new(ObjectType::Pane, 10, 1);
172
173        assert_eq!(h1, h2);
174        assert_ne!(h1, h3); // Different generation
175        assert_ne!(h1, h4); // Different type
176    }
177
178    #[test]
179    fn test_handle_validation() {
180        let handle = ObjectHandle::new(ObjectType::Window, 1, 5);
181        assert!(handle.is_valid(5));
182        assert!(!handle.is_valid(4));
183        assert!(!handle.is_valid(6));
184    }
185
186    #[test]
187    fn test_next_generation() {
188        let h1 = ObjectHandle::new(ObjectType::Pane, 100, 1);
189        let h2 = h1.next_generation();
190
191        assert_eq!(h2.id(), 100);
192        assert_eq!(h2.object_type(), ObjectType::Pane);
193        assert_eq!(h2.generation(), 2);
194        assert!(!h1.is_valid(2));
195        assert!(h2.is_valid(2));
196    }
197
198    #[test]
199    fn test_generation_wrapping() {
200        let handle = ObjectHandle::new(ObjectType::Window, 1, u32::MAX);
201        let next = handle.next_generation();
202        assert_eq!(next.generation(), 0); // Wrapped around
203    }
204
205    #[test]
206    fn test_handle_copy() {
207        let h1 = ObjectHandle::new(ObjectType::Tab, 5, 3);
208        let h2 = h1; // Should copy, not move
209        assert_eq!(h1, h2);
210        assert_eq!(h1.id(), h2.id()); // h1 still valid
211    }
212
213    #[test]
214    fn test_object_type_display() {
215        assert_eq!(ObjectType::Window.to_string(), "Window");
216        assert_eq!(ObjectType::Tab.to_string(), "Tab");
217        assert_eq!(ObjectType::Pane.to_string(), "Pane");
218        assert_eq!(ObjectType::MuxWindow.to_string(), "MuxWindow");
219        assert_eq!(ObjectType::MuxTab.to_string(), "MuxTab");
220        assert_eq!(ObjectType::MuxPane.to_string(), "MuxPane");
221    }
222
223    #[test]
224    fn test_handle_display() {
225        let handle = ObjectHandle::new(ObjectType::Window, 42, 1);
226        assert_eq!(handle.to_string(), "Window#42");
227    }
228
229    #[test]
230    fn test_handle_debug() {
231        let handle = ObjectHandle::new(ObjectType::Pane, 7, 3);
232        let debug_str = format!("{:?}", handle);
233        assert!(debug_str.contains("Pane"));
234        assert!(debug_str.contains("id=7"));
235        assert!(debug_str.contains("gen=3"));
236    }
237}