Crate arma_rs

Crate arma_rs 

Source
Expand description

§arma-rs

Join the arma-rs Discord! codecov

The best way to make Arma 3 Extensions.

§Usage

[dependencies]
arma-rs = "1.12.1"

[lib]
name = "my_extension"
crate-type = ["cdylib"]

§Hello World

use arma_rs::{arma, Extension};

#[arma]
fn init() -> Extension {
    Extension::build()
        .command("hello", hello)
        .command("welcome", welcome)
        .finish()
}

pub fn hello() -> &'static str {
    "Hello"
}

pub fn welcome(name: String) -> String {
    format!("Welcome {}", name)
}
"my_extension" callExtension ["hello", []]; // Returns ["Hello", 0, 0]
"my_extension" callExtension ["welcome", ["John"]]; // Returns ["Welcome John", 0, 0]

§Command Groups

Commands can be grouped together, making your large projects much easier to manage.

use arma_rs::{arma, Extension, Group};

#[arma]
fn init() -> Extension {
    Extension::build()
        .group("hello",
            Group::new()
                .command("english", hello::english)
                .group("english",
                    Group::new()
                        .command("casual", hello::english_casual)
                )
                .command("french", hello::french),
        )
        .group("welcome",
            Group::new()
                .command("english", welcome::english)
                .command("french", welcome::french),
        )
        .finish()
}

mod hello {
    pub fn english() -> &'static str {
        "Hello"
    }
    pub fn english_casual() -> &'static str {
        "Hey"
    }
    pub fn french() -> &'static str {
        "Bonjour"
    }
}

mod welcome {
    pub fn english(name: String) -> String {
        format!("Welcome {}", name)
    }
    pub fn french(name: String) -> String {
        format!("Bienvenue {}", name)
    }
}

Commands groups are called by using the format group:command. You can nest groups as much as you want.

"my_extension" callExtension ["hello:english", []]; // Returns ["Hello", 0, 0]
"my_extension" callExtension ["hello:english:casual", []]; // Returns ["Hey", 0, 0]
"my_extension" callExtension ["hello:french", []]; // Returns ["Bonjour", 0, 0]

§Callbacks

Extension callbacks can be invoked anywhere in the extension by adding a variable of type Context to the start of a handler.

use arma_rs::Context;

pub fn sleep(ctx: Context, duration: u64, id: String) {
    std::thread::spawn(move || {
        std::thread::sleep(std::time::Duration::from_secs(duration));
        ctx.callback_data("example_timer", "done", Some(id));
    });
}

pub fn group() -> arma_rs::Group {
    arma_rs::Group::new().command("sleep", sleep)
}

§Call Context

Since Arma v2.11 additional context is provided each time the extension is called. This context can be accessed through the optional ArmaCallContext argument.

Since Arma v2.18 the context is only requested from Arma when the functionh has ArmaCallContext as an argument.

use arma_rs::{CallContext, CallContextStackTrace};

pub fn call_context(call_context: CallContext) -> String {
    format!(
        "{:?},{:?},{:?},{:?},{:?}",
        call_context.caller(),
        call_context.source(),
        call_context.mission(),
        call_context.server(),
        call_context.remote_exec_owner(),
    )
}

pub fn stack_trace(call_context: CallContextStackTrace) -> String {
    format!(
        "{:?}\n{:?}",
        call_context.source(),
        call_context.stack_trace()
    )
}

pub fn group() -> arma_rs::Group {
    arma_rs::Group::new()
        .command("call_context", call_context)
        .command("stack_trace", stack_trace)
}

§Persistent State

Both the extension and command groups allow for type based persistent state values with at most one instance per type. These state values can then be accessed through the optional Context argument.

§Global State

Extension state is accessible from any command handler.

use arma_rs::{arma, Context, ContextState, Extension};

use std::sync::atomic::{AtomicU32, Ordering};

#[arma]
fn init() -> Extension {
    Extension::build()
        .command("counter_increment", increment)
        .state(AtomicU32::new(0))
        .finish()
}

pub fn increment(ctx: Context) -> Result<(), ()> {
    let Some(counter) = ctx.global().get::<AtomicU32>() else {
        return Err(());
    };
    counter.fetch_add(1, Ordering::SeqCst);
    Ok(())
}

§Group State

Command group state is only accessible from command handlers within the same group.

use arma_rs::{Context, ContextState, Extension};

use std::sync::atomic::{AtomicU32, Ordering};

pub fn increment(ctx: Context) -> Result<(), ()> {
    let Some(counter) = ctx.group().get::<AtomicU32>() else {
        return Err(());
    };
    counter.fetch_add(1, Ordering::SeqCst);
    Ok(())
}

pub fn group() -> arma_rs::Group {
    arma_rs::Group::new()
        .command("increment", increment)
        .state(AtomicU32::new(0))
}

§Custom Types

If you’re bringing your existing Rust library with your own types, you can easily define how they are converted to and from Arma.

use arma_rs::{FromArma, IntoArma, Value, FromArmaError};

pub struct MemoryReport {
    total: u64,
    free: u64,
    avail: u64,
}

impl FromArma for MemoryReport {
    fn from_arma(s: String) -> Result<Self, FromArmaError> {
        let (total, free, avail) = <(u64, u64, u64)>::from_arma(s)?;
        Ok(Self { total, free, avail })
    }
}

impl IntoArma for MemoryReport {
    fn to_arma(&self) -> Value {
        Value::Array(
            vec![self.total, self.free, self.avail]
                .into_iter()
                .map(|v| v