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