1mod arrow;
2mod directory;
3mod mount;
4mod node;
5pub use arrow::query::{Query, SearchDescriptionResult};
6pub use mount::{Mount, Root};
8
9use std::cell::RefCell;
10use std::sync::{Arc, LazyLock};
11
12use anyhow::{Result, anyhow};
13use dynamic::{Dynamic, MsgPack, MsgUnpack, Type};
14use rand::RngExt;
15
16use tokio::sync::mpsc;
17
18pub type Msg<T> = (T, Option<mpsc::Sender<T>>);
19pub type MsgSender<T> = mpsc::Sender<Msg<T>>;
20pub type MsgReceiver<T> = mpsc::Receiver<Msg<T>>;
21
22pub fn tx_rx<T: Send>() -> (MsgSender<T>, MsgReceiver<T>) {
23 let (tx, rx) = mpsc::channel(1024);
24 (tx, rx)
25}
26
27pub fn block_on_async<F, T>(f: F) -> T
28where
29 F: FnOnce() -> std::pin::Pin<Box<dyn Future<Output = T> + Send>> + 'static + Send,
30 T: Send + 'static,
31{
32 if tokio::runtime::Handle::try_current().is_ok() {
33 let (tx, rx) = tokio::sync::oneshot::channel();
34 tokio::task::spawn(async move {
35 let result = f().await;
38 let _ = tx.send(result);
39 });
40 tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(async {
41 match rx.await {
42 Ok(value) => value,
43 Err(_) => panic!("block_on_async: spawn 任务在发送结果前异常退出"),
45 }
46 }))
47 } else {
48 let rt = tokio::runtime::Runtime::new().unwrap();
49 rt.block_on(f())
50 }
51}
52
53use smol_str::SmolStr;
54pub fn start_task<F>(info: Dynamic, f: F) -> Dynamic
55where
56 F: FnOnce() -> std::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>> + 'static + Send,
57{
58 let id = uuid::Uuid::new_v4().to_string();
59 let task = SmolStr::new(format!("local/tasks/{}", id));
60 let r = if tokio::runtime::Handle::try_current().is_ok() {
61 Object::Task(tokio::task::spawn(async move { f().await }), info)
62 } else {
63 Object::ThreadTask(
64 std::thread::spawn(move || {
65 let rt = tokio::runtime::Runtime::new().unwrap();
66 rt.block_on(f())
67 }),
68 info,
69 )
70 };
71 let _ = add(&task, r);
72 log::info!("start task {:?}", task);
73 id.into()
74}
75
76#[macro_export]
77macro_rules! sync_await {
78 ($future:expr) => {
79 $crate::block_on_async(move || Box::pin($future))
80 };
81}
82
83pub fn send<T: Send + 'static>(tx: &MsgSender<T>, msg: T) -> Result<()> {
84 tx.try_send((msg, None)).map_err(|e| anyhow!("发送失败: {}", e))
85}
86
87pub fn call<T: Send + 'static>(tx: &MsgSender<T>, msg: T) -> Result<T> {
88 let (reply_tx, reply_rx) = mpsc::channel::<T>(1024);
89 tx.try_send((msg, Some(reply_tx))).map_err(|e| anyhow!("发送失败: {}", e))?;
90 sync_await!(async move {
95 let mut rx = reply_rx;
96 rx.recv().await
97 })
98 .ok_or_else(|| anyhow!("接收回复失败: channel 关闭"))
99}
100
101use tokio::task::JoinHandle;
102
103use crate::node::Node;
104
105#[derive(Clone)]
111pub struct NativeHandler(Arc<dyn Fn(Dynamic) -> Dynamic + Send + Sync>);
112
113impl NativeHandler {
114 pub fn from_fn(f: fn(Dynamic) -> Dynamic) -> Self {
116 Self(Arc::new(f))
117 }
118
119 pub fn from_closure<F>(f: F) -> Self
121 where
122 F: Fn(Dynamic) -> Dynamic + Send + Sync + 'static,
123 {
124 Self(Arc::new(f))
125 }
126
127 pub fn call(&self, msg: Dynamic) -> Dynamic {
129 (self.0)(msg)
130 }
131}
132
133impl std::fmt::Debug for NativeHandler {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("NativeHandler").finish_non_exhaustive()
136 }
137}
138
139#[derive(Debug)]
140pub enum Object {
141 Value(Dynamic), Native(NativeHandler), Func(i64, Type), Tx(MsgSender<Dynamic>, Dynamic), Task(JoinHandle<Result<()>>, Dynamic), ThreadTask(std::thread::JoinHandle<Result<()>>, Dynamic), }
148
149impl Into<Object> for Dynamic {
150 fn into(self) -> Object {
151 Object::Value(self)
152 }
153}
154
155impl Object {
156 pub fn value(&self) -> Dynamic {
157 match self {
158 Self::Value(v) => v.deep_clone(),
159 Self::Task(_, info) => info.deep_clone(),
160 Self::ThreadTask(_, info) => info.deep_clone(),
161 Self::Tx(_, info) => info.deep_clone(),
162 _ => Dynamic::Null,
163 }
164 }
165}
166
167impl Default for Object {
168 fn default() -> Self {
169 Self::Value(Dynamic::Null)
170 }
171}
172
173impl MsgPack for Object {
174 fn encode(&self, buf: &mut Vec<u8>) {
175 match self {
176 Self::Value(v) => v.encode(buf),
177 _ => {}
178 }
179 }
180}
181
182impl MsgUnpack for Object {
183 fn decode(buf: &[u8]) -> Result<(Self, usize)> {
184 let (v, len) = Dynamic::decode(buf)?;
185 Ok((Self::Value(v), len))
186 }
187}
188
189static ROOT: LazyLock<Root<Object>> = LazyLock::new(|| {
190 let root = Root::<Object>::new();
191 if let Ok((mount, name)) = root.get_mount("local/fight") {
192 mount.add(name, Object::Native(NativeHandler::from_fn(fight)));
193 }
194 root
195});
196
197thread_local! {
198 static SEND_STACK: RefCell<Vec<String>> = RefCell::new(Vec::new());
199}
200
201fn builtin_native(name: &str) -> Option<NativeHandler> {
202 match name {
203 "fight" | "native.fight" | "native::fight" => Some(NativeHandler::from_fn(fight)),
204 _ => None,
205 }
206}
207
208fn get_value(input: &Dynamic, keys: &[&str]) -> Option<Dynamic> {
209 keys.iter().find_map(|key| input.get_dynamic(key))
210}
211
212fn get_string(input: &Dynamic, keys: &[&str]) -> Option<String> {
213 get_value(input, keys).map(|value| value.as_str().to_string())
214}
215
216fn get_i64(input: &Dynamic, keys: &[&str]) -> Option<i64> {
217 get_value(input, keys).and_then(|value| value.as_int())
218}
219
220fn get_f64(input: &Dynamic, keys: &[&str]) -> Option<f64> {
221 get_value(input, keys).and_then(|value| value.as_float())
222}
223
224fn get_range(input: &Dynamic, range_keys: &[&str], min_keys: &[&str], max_keys: &[&str], default_min: i64, default_max: i64) -> (i64, i64) {
225 if let Some(range) = get_value(input, range_keys) {
226 let min = range.get_idx(0).and_then(|v| v.as_int()).unwrap_or(default_min);
227 let max = range.get_idx(1).and_then(|v| v.as_int()).unwrap_or(default_max);
228 return normalize_range(min, max, default_min, default_max);
229 }
230
231 let min = get_i64(input, min_keys).unwrap_or(default_min);
232 let max = get_i64(input, max_keys).unwrap_or(default_max);
233 normalize_range(min, max, default_min, default_max)
234}
235
236fn normalize_range(min: i64, max: i64, default_min: i64, default_max: i64) -> (i64, i64) {
237 let min = min.max(0);
238 let max = max.max(0);
239 if min == 0 && max == 0 {
240 (default_min, default_max)
241 } else if min <= max {
242 (min, max)
243 } else {
244 (max, min)
245 }
246}
247
248fn role_name(role: &Dynamic) -> String {
249 get_string(role, &["name", "名称"]).unwrap_or_else(|| "未命名".to_string())
250}
251
252fn role_hp(role: &Dynamic) -> i64 {
253 get_i64(role, &["hp", "health", "生命"]).unwrap_or(0).max(0)
254}
255
256fn role_max_hp(role: &Dynamic) -> i64 {
257 get_i64(role, &["max_hp", "max_health", "最大生命"]).unwrap_or_else(|| role_hp(role)).max(1)
258}
259
260fn role_speed(role: &Dynamic) -> i64 {
261 get_i64(role, &["speed", "initiative", "速度"]).unwrap_or(0)
262}
263
264fn role_attack_range(role: &Dynamic) -> (i64, i64) {
265 get_range(role, &["attack", "攻击"], &["attack_min", "min_attack", "攻击下限"], &["attack_max", "max_attack", "攻击上限"], 8, 15)
266}
267
268fn role_defense_range(role: &Dynamic) -> (i64, i64) {
269 get_range(role, &["defense", "防御"], &["defense_min", "min_defense", "防御下限"], &["defense_max", "max_defense", "防御上限"], 1, 4)
270}
271
272fn role_block_chance(role: &Dynamic) -> f64 {
273 get_f64(role, &["block_chance", "block", "格挡几率"]).unwrap_or(0.15).clamp(0.0, 0.95)
274}
275
276fn role_block_ratio(role: &Dynamic) -> f64 {
277 get_f64(role, &["block_ratio", "格挡减伤"]).unwrap_or(0.5).clamp(0.0, 1.0)
278}
279
280fn role_crit_chance(role: &Dynamic) -> f64 {
281 get_f64(role, &["crit_chance", "crit", "暴击几率"]).unwrap_or(0.1).clamp(0.0, 1.0)
282}
283
284fn role_crit_ratio(role: &Dynamic) -> f64 {
285 get_f64(role, &["crit_ratio", "暴击倍率"]).unwrap_or(1.5).max(1.0)
286}
287
288fn set_i64(role: &Dynamic, key: &str, value: i64) {
289 role.set_dynamic(key.into(), value);
290}
291
292fn set_bool(role: &Dynamic, key: &str, value: bool) {
293 role.set_dynamic(key.into(), value);
294}
295
296fn normalize_role(input: &Dynamic, side: &'static str, default_name: &str) -> Dynamic {
297 let role = if input.is_map() { input.deep_clone() } else { Dynamic::map(Default::default()) };
298 let name = get_string(&role, &["name", "名称"]).unwrap_or_else(|| default_name.to_string());
299 let hp = get_i64(&role, &["hp", "health", "生命"]).unwrap_or(100).max(1);
300 let (attack_min, attack_max) = role_attack_range(&role);
301 let (defense_min, defense_max) = role_defense_range(&role);
302
303 role.set_dynamic("name".into(), name);
304 role.set_dynamic("side".into(), side);
305 set_i64(&role, "hp", hp);
306 set_i64(&role, "max_hp", role_max_hp(&role).max(hp));
307 set_i64(&role, "attack_min", attack_min);
308 set_i64(&role, "attack_max", attack_max);
309 set_i64(&role, "defense_min", defense_min);
310 set_i64(&role, "defense_max", defense_max);
311 role.set_dynamic("block_chance".into(), role_block_chance(&role));
312 role.set_dynamic("block_ratio".into(), role_block_ratio(&role));
313 role.set_dynamic("crit_chance".into(), role_crit_chance(&role));
314 role.set_dynamic("crit_ratio".into(), role_crit_ratio(&role));
315 set_i64(&role, "speed", role_speed(&role));
316 set_bool(&role, "alive", hp > 0);
317 role
318}
319
320fn make_fight_record(entries: [(&str, Dynamic); 10]) -> Dynamic {
321 let mut map = std::collections::BTreeMap::<SmolStr, Dynamic>::new();
322 for (key, value) in entries {
323 map.insert(key.into(), value);
324 }
325 Dynamic::map(map)
326}
327
328fn fight(msg: Dynamic) -> Dynamic {
329 let left_input = msg.get_dynamic("left").or_else(|| msg.get_dynamic("a")).unwrap_or(Dynamic::Null);
330 let right_input = msg.get_dynamic("right").or_else(|| msg.get_dynamic("b")).unwrap_or(Dynamic::Null);
331 if !left_input.is_map() || !right_input.is_map() {
332 let mut error = std::collections::BTreeMap::new();
333 error.insert("error".into(), "fight expects {left: {...}, right: {...}}".into());
334 return Dynamic::map(error);
335 }
336
337 let left = normalize_role(&left_input, "left", "左侧");
338 let right = normalize_role(&right_input, "right", "右侧");
339 let max_rounds = get_i64(&msg, &["max_rounds", "最大回合"]).unwrap_or(50).clamp(1, 500) as usize;
340
341 let mut rng = rand::rng();
342 let mut process = Vec::new();
343 let mut records = Vec::new();
344 let mut left_turn = if role_speed(&left) == role_speed(&right) { rng.random_bool(0.5) } else { role_speed(&left) > role_speed(&right) };
345
346 let mut round_count = 0usize;
347 while role_hp(&left) > 0 && role_hp(&right) > 0 && round_count < max_rounds {
348 round_count += 1;
349 let (attacker, defender) = if left_turn { (&left, &right) } else { (&right, &left) };
350
351 let (attack_min, attack_max) = role_attack_range(attacker);
352 let (defense_min, defense_max) = role_defense_range(defender);
353 let attack_roll = rng.random_range(attack_min..=attack_max);
354 let defense_roll = rng.random_range(defense_min..=defense_max);
355 let blocked = rng.random_bool(role_block_chance(defender));
356 let crit = rng.random_bool(role_crit_chance(attacker));
357
358 let mut damage = (attack_roll - defense_roll).max(1);
359 if crit {
360 damage = ((damage as f64) * role_crit_ratio(attacker)).round() as i64;
361 }
362 if blocked {
363 damage = ((damage as f64) * (1.0 - role_block_ratio(defender))).round() as i64;
364 }
365 damage = damage.max(if blocked { 0 } else { 1 });
366
367 let defender_hp = (role_hp(defender) - damage).max(0);
368 let defender_max_hp = role_max_hp(defender);
369 set_i64(defender, "hp", defender_hp);
370 set_bool(defender, "alive", defender_hp > 0);
371
372 let line = format!(
373 "第{}回合 {} 攻击 {},攻击 {},防御 {},{}{}造成 {} 点伤害,{} 剩余 {} / {}",
374 round_count,
375 role_name(attacker),
376 role_name(defender),
377 attack_roll,
378 defense_roll,
379 if crit { "触发暴击," } else { "" },
380 if blocked { "被格挡后," } else { "" },
381 damage,
382 role_name(defender),
383 defender_hp,
384 defender_max_hp,
385 );
386 process.push(line.clone().into());
387 records.push(make_fight_record([
388 ("round", (round_count as i64).into()),
389 ("attacker", role_name(attacker).into()),
390 ("attacker_side", get_string(attacker, &["side"]).unwrap_or_default().into()),
391 ("defender", role_name(defender).into()),
392 ("defender_side", get_string(defender, &["side"]).unwrap_or_default().into()),
393 ("attack_roll", attack_roll.into()),
394 ("defense_roll", defense_roll.into()),
395 ("blocked", blocked.into()),
396 ("critical", crit.into()),
397 ("damage", damage.into()),
398 ]));
399 if let Some(last) = records.last() {
400 last.insert("defender_hp", defender_hp);
401 last.insert("text", line);
402 }
403
404 left_turn = !left_turn;
405 }
406
407 let left_hp = role_hp(&left);
408 let right_hp = role_hp(&right);
409 let left_name = role_name(&left);
410 let right_name = role_name(&right);
411 let winner = if left_hp == right_hp {
412 "draw".to_string()
413 } else if left_hp > right_hp {
414 left_name.clone()
415 } else {
416 right_name.clone()
417 };
418 let loser = if winner == "draw" {
419 String::new()
420 } else if winner == left_name {
421 right_name
422 } else {
423 left_name
424 };
425
426 let mut result = std::collections::BTreeMap::new();
427 result.insert("winner".into(), winner.into());
428 result.insert("loser".into(), loser.into());
429 result.insert("draw".into(), (left_hp == right_hp).into());
430 result.insert("round_count".into(), (round_count as i64).into());
431 result.insert("process".into(), Dynamic::list(process));
432 result.insert("records".into(), Dynamic::list(records));
433 result.insert("left".into(), left);
434 result.insert("right".into(), right);
435 Dynamic::map(result)
436}
437
438pub fn mount_memory(name: &str) -> bool {
439 ROOT.mount_memory(name)
440}
441
442pub fn mount_redis(name: &str, url: &str) -> Result<bool> {
443 ROOT.mount_redis(name, url)
444}
445
446pub fn mount_fjall(name: &str, data_dir: &str) -> Result<bool> {
447 ROOT.mount_fjall(name, data_dir)
448}
449
450pub fn get_mount<'a>(name: &'a str) -> Result<(Mount<Object>, &'a str)> {
451 ROOT.get_mount(name)
452}
453
454pub fn add(name: &str, obj: Object) -> Result<bool> {
455 let (m, name) = get_mount(name)?;
456 let mut obj = obj;
457 let expire = take_object_expire(&mut obj);
458 let added = m.add(name, obj);
459 if added {
460 apply_redis_expire(&m, name, expire);
461 }
462 Ok(added)
463}
464
465pub trait IntoNativeHandler {
471 fn into_handler(self) -> Option<NativeHandler>;
472}
473
474impl IntoNativeHandler for &str {
476 fn into_handler(self) -> Option<NativeHandler> {
477 builtin_native(self)
478 }
479}
480
481impl<F> IntoNativeHandler for F
484where
485 F: Fn(Dynamic) -> Dynamic + Send + Sync + 'static,
486{
487 fn into_handler(self) -> Option<NativeHandler> {
488 Some(NativeHandler::from_closure(self))
489 }
490}
491
492pub fn add_native<H: IntoNativeHandler>(name: &str, handler: H) -> Result<bool> {
513 let Some(handler) = handler.into_handler() else {
514 return Ok(false);
515 };
516 let (m, name) = get_mount(name)?;
517 Ok(m.add(name, Object::Native(handler)))
518}
519
520pub fn add_value<T: Into<Dynamic>>(name: &str, val: T) -> Result<bool> {
521 let (m, name) = get_mount(name)?;
522 let mut value = val.into();
523 let expire = take_dynamic_expire(&mut value);
524 let added = m.add(name, Object::Value(value));
525 if added {
526 apply_redis_expire(&m, name, expire);
527 }
528 Ok(added)
529}
530
531pub fn get(name: &str) -> Result<Dynamic> {
532 let (m, name) = get_mount(name)?;
533 m.get(name, |obj| obj.value())
534}
535
536pub fn dir(name: &str) -> Result<Dynamic> {
537 let (m, name) = get_mount(name)?;
538 m.dir(name).map(Into::into)
539}
540
541pub fn keys(name: &str) -> Result<Dynamic> {
542 let (m, name) = get_mount(name)?;
543 m.keys(name).map(Into::into)
544}
545
546pub fn contains(name: &str) -> bool {
547 if let Ok((m, name)) = get_mount(name) { m.contains(name) } else { false }
548}
549
550pub fn remove(name: &str) -> Result<Dynamic> {
551 let (m, name) = get_mount(name)?;
552 match m.remove(name) {
553 Ok(Object::Value(v)) => Ok(v),
554 _ => Err(anyhow!("没有删除对象")),
555 }
556}
557
558pub fn add_list(name: &str) -> Result<()> {
559 let (m, name) = get_mount(name)?;
560 m.add_list(name);
561 Ok(())
562}
563
564pub fn push(name: &str, value: Dynamic) -> Result<usize> {
565 let (m, name) = get_mount(name)?;
566 let mut value = value;
567 let expire = take_dynamic_expire(&mut value);
568 let len = m.push(name, Object::Value(value))?;
569 apply_redis_expire(&m, name, expire);
570 Ok(len)
571}
572
573pub fn remove_idx(name: &str, idx: usize) -> Result<Dynamic> {
576 let (m, name) = get_mount(name)?;
577 match m.remove_idx(name, idx)? {
578 Object::Value(v) => Ok(v),
579 _ => Ok(Dynamic::Null),
580 }
581}
582
583pub fn add_map(name: &str) -> Result<()> {
584 let (m, name) = get_mount(name)?;
585 m.add_map(name);
586 Ok(())
587}
588
589pub fn insert(name: &str, key: &str, value: Dynamic) -> Result<()> {
590 let (m, name) = get_mount(name)?;
591 let mut value = value;
592 let expire = take_dynamic_expire(&mut value);
593 let _ = m.insert(name, key, Object::Value(value));
594 apply_redis_expire(&m, name, expire);
595 Ok(())
596}
597
598fn take_object_expire(obj: &mut Object) -> Option<i64> {
599 if let Object::Value(value) = obj { take_dynamic_expire(value) } else { None }
600}
601
602fn take_dynamic_expire(value: &mut Dynamic) -> Option<i64> {
603 value.remove_dynamic("@expire").and_then(|expire| expire.as_int()).filter(|expire| *expire > 0)
604}
605
606fn apply_redis_expire(mount: &Mount<Object>, name: &str, expire: Option<i64>) {
607 let Some(expire) = expire else {
608 return;
609 };
610 if let Mount::Redis { client, rl: _ } = mount
613 && let Ok(mut conn) = client.get_connection()
614 {
615 if let Err(e) = conn.expire::<&str, ()>(name, expire) {
616 log::warn!("redis expire {} {}s 失败: {e:#}", name, expire);
617 }
618 }
619}
620
621pub fn get_key(name: &str, key: &str) -> Result<Dynamic> {
622 let (m, name) = get_mount(name)?;
623 m.get_key(name, key, |obj| obj.value())
624}
625
626pub fn update<F>(name: &str, mut f: F) -> Result<Dynamic>
635where
636 F: FnMut(Dynamic) -> Dynamic + Send + 'static,
637{
638 let (m, name) = get_mount(name)?;
639 m.get_mut(name, move |obj| match obj {
640 Object::Value(v) => {
641 let current = std::mem::take(v);
642 let next = f(current);
643 let result = next.deep_clone();
644 *v = next;
645 result
646 }
647 _ => Dynamic::Null,
648 })
649}
650
651pub fn update_key<F>(name: &str, key: &str, mut f: F) -> Result<Dynamic>
656where
657 F: FnMut(Dynamic) -> Dynamic + Send + 'static,
658{
659 let (m, name) = get_mount(name)?;
660 m.get_key_mut(name, key, move |obj| match obj {
661 Object::Value(v) => {
662 let current = std::mem::take(v);
663 let next = f(current);
664 let result = next.deep_clone();
665 *v = next;
666 result
667 }
668 _ => Dynamic::Null,
669 })
670}
671
672use redis::Commands;
673pub fn get_list(name: &str) -> Result<Vec<Dynamic>> {
674 let (m, name) = get_mount(name)?;
675 match m {
676 Mount::Memory(m) => m
677 .read_sync(name, |_, v| match v {
678 Node::List(l) => l.iter().map(|(_, item)| item.value()).collect(),
679 _ => Vec::new(),
680 })
681 .ok_or(anyhow!("未发现 {}", name)),
682 Mount::Redis { client, rl: _ } => {
683 let mut conn = client.get_connection()?;
684 let items: Vec<Vec<u8>> = conn.lrange(name, 0, -1)?;
685 let items: Vec<Dynamic> = items.into_iter().map(|buf| Dynamic::decode(buf.as_slice()).map(|(v, _)| v).unwrap_or(Dynamic::Null)).collect();
686 Ok(items)
687 }
688 Mount::Fjall { .. } => {
689 let len = m.len(name)?;
690 let mut items = Vec::new();
691 for idx in 0..len {
692 if let Ok(value) = m.get_idx(name, idx, |obj| obj.value()) {
693 items.push(value);
694 }
695 }
696 Ok(items)
697 }
698 }
699}
700
701#[derive(Debug)]
702enum MyFn {
703 Null,
704 Sender(MsgSender<Dynamic>),
705 Native(NativeHandler),
706 Script(i64, Type),
707}
708
709impl MyFn {
710 fn call(&self, msg: Dynamic) -> Result<Dynamic> {
711 match self {
712 MyFn::Sender(tx) => call(tx, msg),
713 MyFn::Native(f) => Ok(f.call(msg)),
714 MyFn::Script(ptr, ty) => dynamic::call_fn(*ptr, ty.clone(), Box::new(msg)).map(|r| r.as_ref().clone()),
715 MyFn::Null => Ok(Dynamic::Null),
716 }
717 }
718}
719
720impl From<&Object> for MyFn {
721 fn from(obj: &Object) -> Self {
722 match obj {
723 Object::Tx(tx, _) => MyFn::Sender(tx.clone()),
724 Object::Task(_, _) => {
725 MyFn::Null
728 }
729 Object::Func(ptr, ty) => MyFn::Script(*ptr, ty.clone()),
730 Object::Native(f) => MyFn::Native(f.clone()),
731 _ => MyFn::Null,
732 }
733 }
734}
735
736struct SendStackGuard;
739impl Drop for SendStackGuard {
740 fn drop(&mut self) {
741 SEND_STACK.with(|stack| {
742 let _ = stack.borrow_mut().pop();
743 });
744 }
745}
746
747pub fn send_msg(name: &str, msg: Dynamic) -> Result<Dynamic> {
748 let entered = SEND_STACK.with(|stack| {
749 let mut stack = stack.borrow_mut();
750 if stack.len() >= 64 {
751 return Err(anyhow!("root::send call depth exceeded at {}", name));
752 }
753 if stack.iter().any(|item| item == name) {
754 return Err(anyhow!("root::send recursive path detected: {}", name));
755 }
756 stack.push(name.to_string());
757 Ok(())
758 });
759 entered?;
760 let _guard = SendStackGuard;
762 let (m, name) = get_mount(name)?;
763 let f: MyFn = m.get(name, |obj| obj.into())?;
764 f.call(msg)
765}
766
767pub fn send_idx_msg(name: &str, idx: usize, msg: Dynamic) -> Result<Dynamic> {
768 let stack_name = format!("{}[{}]", name, idx);
769 let entered = SEND_STACK.with(|stack| {
770 let mut stack = stack.borrow_mut();
771 if stack.len() >= 64 {
772 return Err(anyhow!("root::send_idx call depth exceeded at {}", stack_name));
773 }
774 if stack.iter().any(|item| item == &stack_name) {
775 return Err(anyhow!("root::send_idx recursive path detected: {}", stack_name));
776 }
777 stack.push(stack_name);
778 Ok(())
779 });
780 entered?;
781 let _guard = SendStackGuard;
782 let (m, name) = get_mount(name)?;
783 let f: MyFn = m.get_idx(name, idx, |obj| obj.into())?;
784 f.call(msg)
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 fn fight_request() -> Dynamic {
792 let left = Dynamic::map(Default::default());
793 left.insert("name", "战士");
794 left.insert("hp", 30);
795 left.insert("attack_min", 8);
796 left.insert("attack_max", 12);
797 left.insert("defense_min", 1);
798 left.insert("defense_max", 3);
799 left.insert("block_chance", 0.2);
800
801 let right = Dynamic::map(Default::default());
802 right.insert("name", "盗贼");
803 right.insert("hp", 24);
804 right.insert("attack_min", 6);
805 right.insert("attack_max", 10);
806 right.insert("defense_min", 0);
807 right.insert("defense_max", 2);
808 right.insert("block_chance", 0.1);
809
810 let msg = Dynamic::map(Default::default());
811 msg.insert("left", left);
812 msg.insert("right", right);
813 msg
814 }
815
816 #[test]
817 fn add_native_registers_builtin_fight_handler() {
818 assert!(add_native("local/test/fight_handler", "native.fight").unwrap());
819 let result = send_msg("local/test/fight_handler", fight_request()).unwrap();
820 assert!(result.is_map());
821 assert!(result.get_dynamic("winner").is_some());
822 assert!(result.get_dynamic("process").is_some_and(|v| v.is_list()));
823 }
824
825 #[test]
826 fn add_native_accepts_closure_with_captured_state() {
827 let multiplier: i64 = 10;
830 let closure = move |msg: Dynamic| -> Dynamic {
831 let n = msg.as_int().unwrap_or(0);
832 Dynamic::from(n * multiplier)
833 };
834 assert!(add_native("local/test/closure_handler", closure).unwrap());
835 let result = send_msg("local/test/closure_handler", Dynamic::from(5i64)).unwrap();
836 assert_eq!(result.as_int(), Some(50));
837 }
838
839 #[test]
840 fn add_native_accepts_bare_fn() {
841 fn echo(msg: Dynamic) -> Dynamic {
843 msg
844 }
845 assert!(add_native("local/test/echo", echo).unwrap());
846 let result = send_msg("local/test/echo", Dynamic::from(42i64)).unwrap();
847 assert_eq!(result.as_int(), Some(42));
848 }
849
850 #[test]
851 fn add_native_unknown_builtin_name_returns_false() {
852 assert!(!add_native("local/test/unknown", "no_such_builtin").unwrap());
854 }
855
856 #[test]
857 fn local_fight_handler_is_available_by_default() {
858 assert!(contains("local/fight"));
859 let result = send_msg("local/fight", fight_request()).unwrap();
860 let rounds = result.get_dynamic("round_count").and_then(|v| v.as_int()).unwrap_or_default();
861 assert!(rounds > 0);
862 }
863
864 #[test]
865 fn take_dynamic_expire_strips_positive_expire_metadata() {
866 let mut value = dynamic::map!("@expire"=> 30, "name"=> "zust");
867
868 assert_eq!(take_dynamic_expire(&mut value), Some(30));
869 assert!(!value.contains("@expire"));
870 assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
871 }
872
873 #[test]
874 fn take_dynamic_expire_ignores_non_positive_expire_metadata() {
875 let mut value = dynamic::map!("@expire"=> 0, "name"=> "zust");
876
877 assert_eq!(take_dynamic_expire(&mut value), None);
878 assert!(!value.contains("@expire"));
879 }
880
881 #[test]
882 fn update_increments_value_atomically() {
883 let path = format!("local/test/update_inc/{}", uuid::Uuid::new_v4());
884 add_value(&path, 0_i64).unwrap();
885
886 let next = update(&path, |v| v + Dynamic::I64(1)).unwrap();
887 assert_eq!(next.as_int(), Some(1));
888 assert_eq!(get(&path).unwrap().as_int(), Some(1));
889
890 let next = update(&path, |v| v + Dynamic::I64(41)).unwrap();
891 assert_eq!(next.as_int(), Some(42));
892 assert_eq!(get(&path).unwrap().as_int(), Some(42));
893 }
894
895 #[test]
896 fn update_concurrent_threads_no_lost_update() {
897 let path = format!("local/test/update_concurrent/{}", uuid::Uuid::new_v4());
898 add_value(&path, 0_i64).unwrap();
899
900 const THREADS: i64 = 8;
901 const ITERS: i64 = 1000;
902
903 let mut handles = Vec::new();
904 for _ in 0..THREADS {
905 let p = path.clone();
906 handles.push(std::thread::spawn(move || {
907 for _ in 0..ITERS {
908 update(&p, |v| v + Dynamic::I64(1)).unwrap();
909 }
910 }));
911 }
912 for h in handles {
913 h.join().unwrap();
914 }
915
916 assert_eq!(get(&path).unwrap().as_int(), Some(THREADS * ITERS));
917 }
918
919 #[test]
920 fn update_key_increments_map_value() {
921 let path = format!("local/test/update_key/{}", uuid::Uuid::new_v4());
922 add_map(&path).unwrap();
923 insert(&path, "n", 0_i64.into()).unwrap();
924
925 const THREADS: i64 = 8;
926 const ITERS: i64 = 500;
927
928 let mut handles = Vec::new();
929 for _ in 0..THREADS {
930 let p = path.clone();
931 handles.push(std::thread::spawn(move || {
932 for _ in 0..ITERS {
933 update_key(&p, "n", |v| v + Dynamic::I64(1)).unwrap();
934 }
935 }));
936 }
937 for h in handles {
938 h.join().unwrap();
939 }
940
941 assert_eq!(get_key(&path, "n").unwrap().as_int(), Some(THREADS * ITERS));
942 }
943
944 #[test]
945 fn keys_returns_memory_map_keys() {
946 let path = format!("local/test/keys/{}", uuid::Uuid::new_v4());
947 add_map(&path).unwrap();
948 insert(&path, "0", "zero".into()).unwrap();
949 insert(&path, "1", "one".into()).unwrap();
950
951 let key_list = keys(&path).unwrap();
952 let mut keys = (0..key_list.len()).map(|idx| key_list.get_idx(idx).unwrap().as_str().to_string()).collect::<Vec<_>>();
953 keys.sort();
954 assert_eq!(keys, vec!["0".to_string(), "1".to_string()]);
955 }
956
957 #[test]
958 fn dir_returns_immediate_child_names() {
959 let path = format!("local/test/public_dir/{}", uuid::Uuid::new_v4());
960 add_value(&format!("{path}/a"), 1_i64).unwrap();
961 add_value(&format!("{path}/sub/item"), 2_i64).unwrap();
962
963 let entries = dir(&path).unwrap();
964 let mut entries = (0..entries.len()).map(|idx| entries.get_idx(idx).unwrap().as_str().to_string()).collect::<Vec<_>>();
965 entries.sort();
966 assert_eq!(entries, vec!["a".to_string(), "sub".to_string()]);
967 }
968
969 #[test]
970 fn mount_fjall_uses_explicit_mount_name() {
971 let data_dir = std::env::temp_dir().join(format!("zust-root-fjall-named-{}", uuid::Uuid::new_v4()));
972 let data_dir_str = data_dir.to_str().unwrap();
973 let mount_name = format!("fjall_{}", uuid::Uuid::new_v4().simple());
974
975 assert!(mount_fjall(&mount_name, data_dir_str).unwrap());
976 add_value(&format!("{mount_name}/item"), 9_i64).unwrap();
977 assert_eq!(get(&format!("{mount_name}/item")).unwrap().as_int(), Some(9));
978 assert!(!contains("fjall/item"));
979
980 let _ = std::fs::remove_dir_all(data_dir);
981 }
982
983 #[test]
984 fn update_returns_err_when_missing() {
985 let path = format!("local/test/update_missing/{}", uuid::Uuid::new_v4());
986 let result = update(&path, |v| v + Dynamic::I64(1));
987 assert!(result.is_err());
988 }
989
990 #[test]
991 fn root_get_returns_independent_dynamic_values() {
992 let path = format!("local/test/root_get_clone/{}", uuid::Uuid::new_v4());
993 let nested = dynamic::map!("score"=> 1);
994 let value = dynamic::map!("nested"=> nested.clone(), "items"=> Dynamic::list(vec![nested]));
995
996 add_value(&path, value).unwrap();
997
998 let first = get(&path).unwrap();
999 first.get_dynamic("nested").unwrap().insert("score", 2);
1000 first.get_dynamic("items").unwrap().get_idx(0).unwrap().insert("score", 3);
1001
1002 let second = get(&path).unwrap();
1003 assert_eq!(second.get_dynamic("nested").unwrap().get_dynamic("score").and_then(|v| v.as_int()), Some(1));
1004 assert_eq!(second.get_dynamic("items").unwrap().get_idx(0).unwrap().get_dynamic("score").and_then(|v| v.as_int()), Some(1));
1005 }
1006}