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) const 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 #[must_use]
54 pub const fn group(&self) -> &GroupContext {
56 &self.group
57 }
58
59 #[must_use]
60 pub const fn buffer_len(&self) -> usize {
63 if self.buffer_size == 0 {
64 0
65 } else {
66 self.buffer_size - 1
67 }
68 }
69
70 fn callback(&self, name: &str, func: &str, data: Option<Value>) -> Result<(), CallbackError> {
71 self.callback_tx
72 .send(CallbackMessage::Call(
73 name.to_string(),
74 func.to_string(),
75 data,
76 ))
77 .map_err(|_| CallbackError::ChannelClosed)
78 }
79
80 pub fn callback_data<V>(&self, name: &str, func: &str, data: V) -> Result<(), CallbackError>
83 where
84 V: IntoArma,
85 {
86 self.callback(name, func, Some(data.to_arma()))
87 }
88
89 pub fn callback_null(&self, name: &str, func: &str) -> Result<(), CallbackError> {
92 self.callback(name, func, None)
93 }
94}
95
96#[derive(Debug)]
98pub enum CallbackError {
99 ChannelClosed,
101}
102
103impl std::fmt::Display for CallbackError {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 Self::ChannelClosed => write!(f, "Callback channel closed"),
107 }
108 }
109}
110
111impl IntoArma for CallbackError {
112 fn to_arma(&self) -> Value {
113 Value::String(self.to_string())
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::State;
121 use crossbeam_channel::{Sender, bounded};
122 use std::sync::Arc;
123
124 fn context(tx: Sender<CallbackMessage>) -> Context {
125 Context::new(
126 tx,
127 GlobalContext::new(String::new(), Arc::new(State::default())),
128 GroupContext::new(Arc::new(State::default())),
129 )
130 }
131
132 #[test]
133 fn context_buffer_len_zero() {
134 let (tx, _) = bounded(0);
135 assert_eq!(context(tx).buffer_len(), 0);
136 }
137
138 #[test]
139 fn context_buffer_len() {
140 let (tx, _) = bounded(0);
141 assert_eq!(context(tx).with_buffer_size(100).buffer_len(), 99);
142 }
143
144 #[test]
145 fn context_callback_block() {
146 let (tx, rx) = bounded(0);
147 let callback_tx = tx.clone();
148 std::thread::spawn(|| {
149 context(callback_tx).callback_null("", "").unwrap();
150 });
151 let callback_tx = tx;
152 std::thread::spawn(|| {
153 context(callback_tx).callback_data("", "", "").unwrap();
154 });
155
156 std::thread::sleep(std::time::Duration::from_millis(50));
157 assert_eq!(rx.iter().count(), 2);
158 }
159
160 #[test]
161 fn context_callback_closed() {
162 let (tx, _) = bounded(0);
163 assert!(matches!(
164 context(tx.clone()).callback_null("", ""),
165 Err(CallbackError::ChannelClosed)
166 ));
167 assert!(matches!(
168 context(tx).callback_data("", "", ""),
169 Err(CallbackError::ChannelClosed)
170 ));
171 }
172}