1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
//! For testing your extension.
use std::time::Duration;
use crate::{CallbackMessage, Context, State, Value};
use crate::{ArmaCallContext, Caller, Mission, Server, Source};
/// Wrapper around [`crate::Extension`] used for testing.
pub struct Extension(crate::Extension);
const BUFFER_SIZE: libc::size_t = 10240; // The sized used by Arma 3 as of 2021-12-30
#[derive(Debug, PartialEq, Eq)]
/// Result of an event handler
pub enum Result<T, E> {
/// an event has been handled and the handler is done, the value of T is the return value of the event handler
Ok(T),
/// the handler has encountered an error, the value of T is the return value of the event handler
Err(E),
/// an event is handled but the handler is not done and should receive another event
Continue,
/// the handler reached the specified timeout
Timeout,
}
impl<T, E> Result<T, E> {
/// Returns true if the result is an ok result
pub fn is_ok(&self) -> bool {
matches!(self, Self::Ok(_))
}
/// Returns true if the result is an error
pub fn is_err(&self) -> bool {
matches!(self, Self::Err(_))
}
/// Returns true if the result is a continue result
pub fn is_continue(&self) -> bool {
matches!(self, Self::Continue)
}
/// Returns true if the result is a timeout result
pub fn is_timeout(&self) -> bool {
matches!(self, Self::Timeout)
}
}
impl Extension {
/// Create a new testing Extension
pub fn new(ext: crate::Extension) -> Self {
Self(ext)
}
#[must_use]
/// Returns a context for simulating interactions with Arma
pub fn context(&self) -> Context {
self.0.context().with_buffer_size(BUFFER_SIZE)
}
#[must_use]
/// Get a reference to the extensions state container
pub fn state(&self) -> &State {
&self.0.group.state
}
#[must_use]
#[allow(clippy::too_many_arguments)]
/// Call a function with Arma call context.
///
/// # Safety
/// This function is unsafe because it interacts with the C API.
pub fn call_with_context(
&self,
function: &str,
args: Option<Vec<String>>,
caller: Caller,
source: Source,
mission: Mission,
server: Server,
remote_exec_owner: i16,
) -> (String, libc::c_int) {
self.0.context_manager.replace(Some(ArmaCallContext::new(
caller,
source,
mission,
server,
remote_exec_owner,
)));
unsafe { self.handle_call(function, args) }
}
#[must_use]
/// Call a function without Arma call context.
///
/// # Safety
/// This function is unsafe because it interacts with the C API.
///
/// # Note
/// If the `call-context` feature is enabled, this function passes default values for each field.
pub fn call(&self, function: &str, args: Option<Vec<String>>) -> (String, libc::c_int) {
self.0.context_manager.replace(None);
unsafe { self.handle_call(function, args) }
}
unsafe fn handle_call(
&self,
function: &str,
args: Option<Vec<String>>,
) -> (String, libc::c_int) {
let mut output = [0; BUFFER_SIZE];
let len = args.as_ref().map(|a| a.len().try_into().unwrap());
let mut args_pointer = args.map(|v| {
v.into_iter()
.map(|s| std::ffi::CString::new(s).unwrap().into_raw())
.collect::<Vec<*mut i8>>()
});
let res = self.0.group.handle(
self.context(),
&self.0.context_manager,
function,
output.as_mut_ptr(),
BUFFER_SIZE,
args_pointer.as_mut().map(Vec::as_mut_ptr),
len,
);
if let Some(args) = args_pointer {
for arg in args {
let _ = std::ffi::CString::from_raw(arg);
}
}
(
std::ffi::CStr::from_ptr(output.as_ptr())
.to_str()
.unwrap()
.to_string(),
res,
)
}
/// Create a callback handler
///
/// Returns a Result from the handler if the callback was handled,
/// or `Result::Timeout` if either no event was received, or the handler
/// returned `Result::Continue` until the timeout was reached.
///
/// The handler must return a Result indicating the callback was handled to exit
/// `Result::Continue` will continue to provide events to the handler until another variant is returned
pub fn callback_handler<F, T, E>(&self, handler: F, timeout: Duration) -> Result<T, E>
where
F: Fn(&str, &str, Option<Value>) -> Result<T, E>,
{
let (_, rx) = &self.0.callback_channel;
let deadline = std::time::Instant::now() + timeout;
loop {
match rx.recv_deadline(deadline) {
Ok(CallbackMessage::Call(name, func, data)) => match handler(&name, &func, data) {
Result::Ok(value) => return Result::Ok(value),
Result::Err(error) => return Result::Err(error),
Result::Timeout => return Result::Timeout,
Result::Continue => {}
},
_ => return Result::Timeout,
}
}
}
}