1use crossbeam_channel::Sender;
4
5use crate::{CallbackMessage, IntoArma, Value};
6
7mod global;
8mod group;
9mod state;
10
11pub use self::state::ContextState;
12pub use global::GlobalContext;
13pub use group::GroupContext;
14
15pub struct Context {
17 callback_tx: Sender<CallbackMessage>,
18 global: GlobalContext,
19 group: GroupContext,
20 buffer_size: usize,
21}
22
23impl Context {
24 pub(crate) fn new(
25 callback_tx: Sender<CallbackMessage>,
26 global: GlobalContext,
27 group: GroupContext,
28 ) -> Self {
29 Self {
30 callback_tx,
31 global,
32 group,
33 buffer_size: 0,
34 }
35 }
36
37 pub(crate) fn with_group(mut self, ctx: GroupContext) -> Self {
38 self.group = ctx;
39 self
40 }
41
42 pub(crate) const fn with_buffer_size(mut self, buffer_size: usize) -> Self {
43 self.buffer_size = buffer_size;
44 self
45 }
46
47 #[must_use]
48 pub const fn global(&self) -> &GlobalContext {
50 &self.global
51 }
52
53 pub const fn group(&self) -> &GroupContext {
55 &self.group
56 }
57
58 #[must_use]
59 pub const fn buffer_len(&self) -> usize {
62 if self.buffer_size == 0 {
63 0
64 } else {
65 self.buffer_size - 1
66 }
67 }
68
69 fn callback(&self, name: &str, func: &str, data: Option<Value>) -> Result<(), CallbackError> {
70 self.callback_tx
71 .send(CallbackMessage::Call(
72 name.to_string(),
73 func.to_string(),
74 data,
75 ))
76 .map_err(|_| CallbackError::ChannelClosed)
77 }
78
79 pub fn callback_data<V>(&self, name: &str, func: &str, data: V) -> Result<(), CallbackError>
82 where
83 V: IntoArma,
84 {
85 self.callback(name, func, Some(data.to_arma()))
86 }
87
88 pub fn callback_null(&self, name: &str, func: &str) -> Result<(), CallbackError> {
91 self.callback(name, func, None)
92 }
93}
94
95#[derive(Debug)]
97pub enum CallbackError {
98 ChannelClosed,
100}
101
102impl std::fmt::Display for CallbackError {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match self {
105 Self::ChannelClosed => write!(f, "Callback channel closed"),
106 }
107 }
108}
109
110impl IntoArma for CallbackError {
111 fn to_arma(&self) -> Value {
112 Value::String(self.to_string())
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::State;
120 use crossbeam_channel::{bounded, Sender};
121 use std::sync::Arc;
122
123 fn context(tx: Sender<CallbackMessage>) -> Context {
124 Context::new(
125 tx,
126 GlobalContext::new(String::new(), Arc::new(State::default())),
127 GroupContext::new(Arc::new(State::default())),
128 )
129 }
130
131 #[test]
132 fn context_buffer_len_zero() {
133 let (tx, _) = bounded(0);
134 assert_eq!(context(tx).buffer_len(), 0);
135 }
136
137 #[test]
138 fn context_buffer_len() {
139 let (tx, _) = bounded(0);
140 assert_eq!(context(tx).with_buffer_size(100).buffer_len(), 99);
141 }
142
143 #[test]
144 fn context_callback_block() {
145 let (tx, rx) = bounded(0);
146 let callback_tx = tx.clone();
147 std::thread::spawn(|| {
148 context(callback_tx).callback_null("", "").unwrap();
149 });
150 let callback_tx = tx;
151 std::thread::spawn(|| {
152 context(callback_tx).callback_data("", "", "").unwrap();
153 });
154
155 std::thread::sleep(std::time::Duration::from_millis(50));
156 assert_eq!(rx.iter().count(), 2);
157 }
158
159 #[test]
160 fn context_callback_closed() {
161 let (tx, _) = bounded(0);
162 assert!(matches!(
163 context(tx.clone()).callback_null("", ""),
164 Err(CallbackError::ChannelClosed)
165 ));
166 assert!(matches!(
167 context(tx).callback_data("", "", ""),
168 Err(CallbackError::ChannelClosed)
169 ));
170 }
171}