28个稳定版本
1.10.5 | 2023年9月12日 |
---|---|
1.9.3 | 2023年1月23日 |
1.8.2 | 2022年12月9日 |
1.8.1 | 2022年11月13日 |
1.3.2 | 2021年12月31日 |
#99 在 算法 分类中
每月 39次下载
150KB
3.5K SLoC
arma-rs
制作Arma 3扩展的最佳方式。
使用方法
[dependencies]
arma-rs = "1.10.4"
[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]
命令分组
命令可以分组,使大型项目管理更加容易。
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)
}
}
使用格式 group:command
调用命令分组。您可以根据需要嵌套分组。
"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]
回调函数
扩展回调可以在扩展中的任何地方通过在处理器开头添加类型为 Context
的变量来调用。
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)
}
调用上下文
从Arma v2.11开始,每次调用扩展时都提供了额外的上下文。可以通过可选的 Context
参数访问此上下文。
use arma_rs::Context;
pub fn call_context(ctx: Context) -> String {
format!(
"{:?},{:?},{:?},{:?}",
ctx.caller(),
ctx.source(),
ctx.mission(),
ctx.server()
)
}
pub fn group() -> arma_rs::Group {
arma_rs::Group::new().command("call_context", call_context)
}
可以通过使用 call-context
功能标志来切换对此上下文的支持,该标志默认启用。
持久状态
扩展和命令分组都允许使用基于类型的持久状态值,每个类型最多一个实例。然后可以通过可选的 Context
参数访问这些状态值。
全局状态
扩展状态可以从任何命令处理器访问。
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(())
}
分组状态
命令分组状态只能从同一组内的命令处理器访问。
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))
}
自定义返回类型
如果您正在携带带有自己类型的现有Rust库,可以轻松定义它们如何转换为Arma。
use arma_rs::{IntoArma, Value};
#[derive(Default)]
pub struct MemoryReport {
total: u64,
free: u64,
avail: u64,
}
impl IntoArma for MemoryReport {
fn to_arma(&self) -> Value {
Value::Array(
vec![self.total, self.free, self.avail]
.into_iter()
.map(|v| v.to_string().to_arma())
.collect(),
)
}
}
错误代码
默认情况下,arma-rs将仅允许通过 RvExtensionArgs
发送命令。使用仅带有函数名称的 callExtension
将返回空字符串。
"my_extension" callExtension "hello:english" // returns ""
"my_extension" callExtension ["hello:english", []] // returns ["Hello", 0, 0]
可以通过在构建扩展时调用 .allow_no_args()
来更改此行为。建议不要使用此功能,而是实现错误处理。
代码 | 描述 |
---|---|
0 | 成功 |
1 | 找不到命令 |
2x | 无效的参数数量,x 是接收到的计数 |
3x | 无效的参数类型,x 是参数位置 |
4 | 尝试写入比缓冲区更大的值 |
9 | 应用程序错误,来自使用 Result |
错误示例
use arma_rs::Context;
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn overflow(ctx: Context) -> String {
"X".repeat(ctx.buffer_len() + 1)
}
pub fn should_error(error: bool) -> Result<String, String> {
if error {
Err(String::from("told to error"))
} else {
Ok(String::from("told to succeed"))
}
}
"my_extension" callExtension ["add", [1, 2]]; // Returns ["3", 0, 0]
"my_extension" callExtension ["sub", [1, 2]]; // Returns ["", 1, 0]
"my_extension" callExtension ["add", [1, 2, 3]]; // Returns ["", 23, 0], didn't expect 3 elements
"my_extension" callExtension ["add", [1, "two"]]; // Returns ["", 31, 0], unable to parse the second argument
"my_extension" callExtension ["overflow", []]; // Returns ["", 4, 0], the return size was larger than the buffer
"my_extension" callExtension ["should_error", [true]]; // Returns ["told to error", 9, 0]
"my_extension" callExtension ["should_error", [false]]; // Returns ["told to succeed", 0, 0]
测试
可以通过使用 extension.call()
方法创建测试。
mod tests {
#[test]
fn hello() {
let extension = init().testing();
let (output, _) = extension.call("hello:english", None);
assert_eq!(output, "hello");
}
#[test]
fn welcome() {
let extension = init().testing();
let (output, _) =
extension.call("welcome:english", Some(vec!["John".to_string()]));
assert_eq!(output, "Welcome John");
}
#[test]
fn sleep_1sec() {
let extension = Extension::build()
.group("timer", super::group())
.finish()
.testing();
let (_, code) = extension.call(
"timer:sleep",
Some(vec!["1".to_string(), "test".to_string()]),
);
assert_eq!(code, 0);
let result = extension.callback_handler(
|name, func, data| {
assert_eq!(name, "timer:sleep");
assert_eq!(func, "done");
if let Some(Value::String(s)) = data {
Result::Ok(s)
} else {
Result::Err("Data was not a string".to_string())
}
},
Duration::from_secs(2),
);
assert_eq!(Result::Ok("test".to_string()), result);
}
}
单元配置数组
arma-rs 包含一个 配置模块,以帮助处理 Arma 的单元配置数组。
use arma_rs::{FromArma, loadout::{Loadout, InventoryItem, Weapon, Magazine}};
let l = r#"[[],[],[],["U_Marshal",[]],[],[],"H_Cap_headphones","G_Aviator",[],["ItemMap","ItemGPS","","ItemCompass","ItemWatch",""]]"#;
let mut loadout = Loadout::from_arma(l.to_string()).unwrap();
loadout.set_secondary({
let mut weapon = Weapon::new("launch_B_Titan_short_F".to_string());
weapon.set_primary_magazine(Magazine::new("Titan_AT".to_string(), 1));
weapon
});
loadout.set_primary({
let mut weapon = Weapon::new("arifle_MXC_F".to_string());
weapon.set_optic("optic_Holosight".to_string());
weapon
});
let uniform = loadout.uniform_mut();
uniform.set_class("U_B_CombatUniform_mcam".to_string());
let uniform_items = uniform.items_mut().unwrap();
uniform_items.push(InventoryItem::new_item("FirstAidKit".to_string(), 3));
uniform_items.push(InventoryItem::new_magazine("30Rnd_65x39_caseless_mag".to_string(), 5, 30));
常见的 Rust 库
arma-rs 支持一些常见的 Rust 库。您可以通过将它们的名称添加到 arma-rs 的功能中启用它们的支持。
arma-rs = { version = "1.8.0", features = ["chrono"] }
如果您想添加对新库的支持,请首先创建一个问题。
chrono
chrono - 转换为 Arma
NaiveDateTime
和 DateTime<TimeZone>
将转换为 Arma 的日期数组。时区将始终转换为 UTC。
chrono - 从 Arma 转换
Arma 的日期数组可以转换为 NaiveDateTime
。
uuid
uuid - 转换为 Arma
Uuid
将转换为字符串。
serde_json
serde_json - 转换为 Arma
任何 serde_json::Value
变体会转换为适当的 Arma 类型。
为 x86 (32 位) 构建
rustup toolchain install stable-i686-pc-windows-msvc
cargo +stable-i686-pc-windows-msvc build
贡献
欢迎提交拉取请求。对于重大更改,请先打开一个问题来讨论您想进行哪些更改。
依赖项
~0.4–40MB
~570K SLoC