arma_rs/context/
mod.rs

1//! Contextual execution information.
2
3use 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
15/// Contains information about the current execution context
16pub 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    /// Global context
49    pub const fn global(&self) -> &GlobalContext {
50        &self.global
51    }
52
53    /// Group context, is equal to `GlobalContext` if the call is from the global scope.
54    pub const fn group(&self) -> &GroupContext {
55        &self.group
56    }
57
58    #[must_use]
59    /// Returns the length in bytes of the output buffer.
60    /// This is the maximum size of the data that can be returned by the extension.
61    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    /// Sends a callback with data into Arma
80    /// <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#ExtensionCallback>
81    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    /// Sends a callback without data into Arma
89    /// <https://community.bistudio.com/wiki/Arma_3:_Mission_Event_Handlers#ExtensionCallback>
90    pub fn callback_null(&self, name: &str, func: &str) -> Result<(), CallbackError> {
91        self.callback(name, func, None)
92    }
93}
94
95/// Error that can occur when sending a callback
96#[derive(Debug)]
97pub enum CallbackError {
98    /// The callback channel has been closed
99    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}