Skip to main content

root/
lib.rs

1mod arrow;
2mod directory;
3#[cfg(feature = "iroh")]
4pub mod iroh;
5mod mount;
6mod node;
7pub use arrow::query::{Query, SearchDescriptionResult};
8//以后要把内存中的 handler task Sender Receiver 和 数据分开处理
9pub use mount::{Mount, Root};
10
11use std::cell::RefCell;
12use std::sync::LazyLock;
13
14use anyhow::{Result, anyhow};
15use dynamic::{Dynamic, MsgPack, MsgUnpack, Type};
16use rand::RngExt;
17
18use tokio::sync::mpsc;
19
20pub type Msg<T> = (T, Option<mpsc::Sender<T>>);
21pub type MsgSender<T> = mpsc::Sender<Msg<T>>;
22pub type MsgReceiver<T> = mpsc::Receiver<Msg<T>>;
23
24pub fn tx_rx<T: Send>() -> (MsgSender<T>, MsgReceiver<T>) {
25    let (tx, rx) = mpsc::channel(1024);
26    (tx, rx)
27}
28
29pub fn block_on_async<F, T>(f: F) -> T
30where
31    F: FnOnce() -> std::pin::Pin<Box<dyn Future<Output = T> + Send>> + 'static + Send,
32    T: Send + 'static,
33{
34    if tokio::runtime::Handle::try_current().is_ok() {
35        let (tx, rx) = tokio::sync::oneshot::channel();
36        tokio::task::spawn(async move {
37            let result = f().await;
38            let _ = tx.send(result);
39        });
40        tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(async { rx.await.unwrap() }))
41    } else {
42        let rt = tokio::runtime::Runtime::new().unwrap();
43        rt.block_on(f())
44    }
45}
46
47use smol_str::SmolStr;
48pub fn start_task<F>(info: Dynamic, f: F) -> Dynamic
49where
50    F: FnOnce() -> std::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>> + 'static + Send,
51{
52    let id = uuid::Uuid::new_v4().to_string();
53    let task = SmolStr::new(format!("local/tasks/{}", id));
54    let r = if tokio::runtime::Handle::try_current().is_ok() {
55        Object::Task(tokio::task::spawn(async move { f().await }), info)
56    } else {
57        Object::ThreadTask(
58            std::thread::spawn(move || {
59                let rt = tokio::runtime::Runtime::new().unwrap();
60                rt.block_on(f())
61            }),
62            info,
63        )
64    };
65    let _ = add(&task, r);
66    log::info!("start task {:?}", task);
67    id.into()
68}
69
70#[macro_export]
71macro_rules! sync_await {
72    ($future:expr) => {
73        $crate::block_on_async(move || Box::pin($future))
74    };
75}
76
77pub fn send<T: Send + 'static>(tx: &MsgSender<T>, msg: T) -> Result<()> {
78    tx.try_send((msg, None)).map_err(|e| anyhow!("发送失败: {}", e))
79}
80
81pub fn call<T: Send + 'static>(tx: &MsgSender<T>, msg: T) -> Result<T> {
82    let (reply_tx, mut reply_rx) = mpsc::channel::<T>(1024);
83    tx.try_send((msg, Some(reply_tx))).map_err(|e| anyhow!("发送失败: {}", e))?;
84    reply_rx.try_recv().map_err(|e| anyhow!("接收回复失败: {}", e))
85}
86
87use tokio::task::JoinHandle;
88
89use crate::node::Node;
90#[derive(Debug)]
91pub enum Object {
92    Value(Dynamic),                                           //基本的值
93    Native(fn(Dynamic) -> Dynamic),                           //函数处理对象
94    Func(i64, Type),                                          //裸指针
95    Tx(MsgSender<Dynamic>, Dynamic),                          //包括 Tx 信息
96    Task(JoinHandle<Result<()>>, Dynamic),                    //异步任务
97    ThreadTask(std::thread::JoinHandle<Result<()>>, Dynamic), //同步任务
98}
99
100impl Into<Object> for Dynamic {
101    fn into(self) -> Object {
102        Object::Value(self)
103    }
104}
105
106impl Object {
107    pub fn value(&self) -> Dynamic {
108        match self {
109            Self::Value(v) => v.deep_clone(),
110            Self::Task(_, info) => info.deep_clone(),
111            Self::ThreadTask(_, info) => info.deep_clone(),
112            Self::Tx(_, info) => info.deep_clone(),
113            _ => Dynamic::Null,
114        }
115    }
116}
117
118impl Default for Object {
119    fn default() -> Self {
120        Self::Value(Dynamic::Null)
121    }
122}
123
124impl MsgPack for Object {
125    fn encode(&self, buf: &mut Vec<u8>) {
126        match self {
127            Self::Value(v) => v.encode(buf),
128            _ => {}
129        }
130    }
131}
132
133impl MsgUnpack for Object {
134    fn decode(buf: &[u8]) -> Result<(Self, usize)> {
135        let (v, len) = Dynamic::decode(buf)?;
136        Ok((Self::Value(v), len))
137    }
138}
139
140static ROOT: LazyLock<Root<Object>> = LazyLock::new(|| {
141    let root = Root::<Object>::new();
142    if let Ok((mount, name)) = root.get_mount("local/fight") {
143        mount.add(name, Object::Native(fight));
144    }
145    root
146});
147
148thread_local! {
149    static SEND_STACK: RefCell<Vec<String>> = RefCell::new(Vec::new());
150}
151
152fn builtin_native(name: &str) -> Option<fn(Dynamic) -> Dynamic> {
153    match name {
154        "fight" | "native.fight" | "native::fight" => Some(fight),
155        _ => None,
156    }
157}
158
159fn get_value(input: &Dynamic, keys: &[&str]) -> Option<Dynamic> {
160    keys.iter().find_map(|key| input.get_dynamic(key))
161}
162
163fn get_string(input: &Dynamic, keys: &[&str]) -> Option<String> {
164    get_value(input, keys).map(|value| value.as_str().to_string())
165}
166
167fn get_i64(input: &Dynamic, keys: &[&str]) -> Option<i64> {
168    get_value(input, keys).and_then(|value| value.as_int())
169}
170
171fn get_f64(input: &Dynamic, keys: &[&str]) -> Option<f64> {
172    get_value(input, keys).and_then(|value| value.as_float())
173}
174
175fn get_range(input: &Dynamic, range_keys: &[&str], min_keys: &[&str], max_keys: &[&str], default_min: i64, default_max: i64) -> (i64, i64) {
176    if let Some(range) = get_value(input, range_keys) {
177        let min = range.get_idx(0).and_then(|v| v.as_int()).unwrap_or(default_min);
178        let max = range.get_idx(1).and_then(|v| v.as_int()).unwrap_or(default_max);
179        return normalize_range(min, max, default_min, default_max);
180    }
181
182    let min = get_i64(input, min_keys).unwrap_or(default_min);
183    let max = get_i64(input, max_keys).unwrap_or(default_max);
184    normalize_range(min, max, default_min, default_max)
185}
186
187fn normalize_range(min: i64, max: i64, default_min: i64, default_max: i64) -> (i64, i64) {
188    let min = min.max(0);
189    let max = max.max(0);
190    if min == 0 && max == 0 {
191        (default_min, default_max)
192    } else if min <= max {
193        (min, max)
194    } else {
195        (max, min)
196    }
197}
198
199fn role_name(role: &Dynamic) -> String {
200    get_string(role, &["name", "名称"]).unwrap_or_else(|| "未命名".to_string())
201}
202
203fn role_hp(role: &Dynamic) -> i64 {
204    get_i64(role, &["hp", "health", "生命"]).unwrap_or(0).max(0)
205}
206
207fn role_max_hp(role: &Dynamic) -> i64 {
208    get_i64(role, &["max_hp", "max_health", "最大生命"]).unwrap_or_else(|| role_hp(role)).max(1)
209}
210
211fn role_speed(role: &Dynamic) -> i64 {
212    get_i64(role, &["speed", "initiative", "速度"]).unwrap_or(0)
213}
214
215fn role_attack_range(role: &Dynamic) -> (i64, i64) {
216    get_range(role, &["attack", "攻击"], &["attack_min", "min_attack", "攻击下限"], &["attack_max", "max_attack", "攻击上限"], 8, 15)
217}
218
219fn role_defense_range(role: &Dynamic) -> (i64, i64) {
220    get_range(role, &["defense", "防御"], &["defense_min", "min_defense", "防御下限"], &["defense_max", "max_defense", "防御上限"], 1, 4)
221}
222
223fn role_block_chance(role: &Dynamic) -> f64 {
224    get_f64(role, &["block_chance", "block", "格挡几率"]).unwrap_or(0.15).clamp(0.0, 0.95)
225}
226
227fn role_block_ratio(role: &Dynamic) -> f64 {
228    get_f64(role, &["block_ratio", "格挡减伤"]).unwrap_or(0.5).clamp(0.0, 1.0)
229}
230
231fn role_crit_chance(role: &Dynamic) -> f64 {
232    get_f64(role, &["crit_chance", "crit", "暴击几率"]).unwrap_or(0.1).clamp(0.0, 1.0)
233}
234
235fn role_crit_ratio(role: &Dynamic) -> f64 {
236    get_f64(role, &["crit_ratio", "暴击倍率"]).unwrap_or(1.5).max(1.0)
237}
238
239fn set_i64(role: &Dynamic, key: &str, value: i64) {
240    role.set_dynamic(key.into(), value);
241}
242
243fn set_bool(role: &Dynamic, key: &str, value: bool) {
244    role.set_dynamic(key.into(), value);
245}
246
247fn normalize_role(input: &Dynamic, side: &'static str, default_name: &str) -> Dynamic {
248    let role = if input.is_map() { input.deep_clone() } else { Dynamic::map(Default::default()) };
249    let name = get_string(&role, &["name", "名称"]).unwrap_or_else(|| default_name.to_string());
250    let hp = get_i64(&role, &["hp", "health", "生命"]).unwrap_or(100).max(1);
251    let (attack_min, attack_max) = role_attack_range(&role);
252    let (defense_min, defense_max) = role_defense_range(&role);
253
254    role.set_dynamic("name".into(), name);
255    role.set_dynamic("side".into(), side);
256    set_i64(&role, "hp", hp);
257    set_i64(&role, "max_hp", role_max_hp(&role).max(hp));
258    set_i64(&role, "attack_min", attack_min);
259    set_i64(&role, "attack_max", attack_max);
260    set_i64(&role, "defense_min", defense_min);
261    set_i64(&role, "defense_max", defense_max);
262    role.set_dynamic("block_chance".into(), role_block_chance(&role));
263    role.set_dynamic("block_ratio".into(), role_block_ratio(&role));
264    role.set_dynamic("crit_chance".into(), role_crit_chance(&role));
265    role.set_dynamic("crit_ratio".into(), role_crit_ratio(&role));
266    set_i64(&role, "speed", role_speed(&role));
267    set_bool(&role, "alive", hp > 0);
268    role
269}
270
271fn make_fight_record(entries: [(&str, Dynamic); 10]) -> Dynamic {
272    let mut map = std::collections::BTreeMap::<SmolStr, Dynamic>::new();
273    for (key, value) in entries {
274        map.insert(key.into(), value);
275    }
276    Dynamic::map(map)
277}
278
279fn fight(msg: Dynamic) -> Dynamic {
280    let left_input = msg.get_dynamic("left").or_else(|| msg.get_dynamic("a")).unwrap_or(Dynamic::Null);
281    let right_input = msg.get_dynamic("right").or_else(|| msg.get_dynamic("b")).unwrap_or(Dynamic::Null);
282    if !left_input.is_map() || !right_input.is_map() {
283        let mut error = std::collections::BTreeMap::new();
284        error.insert("error".into(), "fight expects {left: {...}, right: {...}}".into());
285        return Dynamic::map(error);
286    }
287
288    let left = normalize_role(&left_input, "left", "左侧");
289    let right = normalize_role(&right_input, "right", "右侧");
290    let max_rounds = get_i64(&msg, &["max_rounds", "最大回合"]).unwrap_or(50).clamp(1, 500) as usize;
291
292    let mut rng = rand::rng();
293    let mut process = Vec::new();
294    let mut records = Vec::new();
295    let mut left_turn = if role_speed(&left) == role_speed(&right) { rng.random_bool(0.5) } else { role_speed(&left) > role_speed(&right) };
296
297    let mut round_count = 0usize;
298    while role_hp(&left) > 0 && role_hp(&right) > 0 && round_count < max_rounds {
299        round_count += 1;
300        let (attacker, defender) = if left_turn { (&left, &right) } else { (&right, &left) };
301
302        let (attack_min, attack_max) = role_attack_range(attacker);
303        let (defense_min, defense_max) = role_defense_range(defender);
304        let attack_roll = rng.random_range(attack_min..=attack_max);
305        let defense_roll = rng.random_range(defense_min..=defense_max);
306        let blocked = rng.random_bool(role_block_chance(defender));
307        let crit = rng.random_bool(role_crit_chance(attacker));
308
309        let mut damage = (attack_roll - defense_roll).max(1);
310        if crit {
311            damage = ((damage as f64) * role_crit_ratio(attacker)).round() as i64;
312        }
313        if blocked {
314            damage = ((damage as f64) * (1.0 - role_block_ratio(defender))).round() as i64;
315        }
316        damage = damage.max(if blocked { 0 } else { 1 });
317
318        let defender_hp = (role_hp(defender) - damage).max(0);
319        let defender_max_hp = role_max_hp(defender);
320        set_i64(defender, "hp", defender_hp);
321        set_bool(defender, "alive", defender_hp > 0);
322
323        let line = format!(
324            "第{}回合 {} 攻击 {},攻击 {},防御 {},{}{}造成 {} 点伤害,{} 剩余 {} / {}",
325            round_count,
326            role_name(attacker),
327            role_name(defender),
328            attack_roll,
329            defense_roll,
330            if crit { "触发暴击," } else { "" },
331            if blocked { "被格挡后," } else { "" },
332            damage,
333            role_name(defender),
334            defender_hp,
335            defender_max_hp,
336        );
337        process.push(line.clone().into());
338        records.push(make_fight_record([
339            ("round", (round_count as i64).into()),
340            ("attacker", role_name(attacker).into()),
341            ("attacker_side", get_string(attacker, &["side"]).unwrap_or_default().into()),
342            ("defender", role_name(defender).into()),
343            ("defender_side", get_string(defender, &["side"]).unwrap_or_default().into()),
344            ("attack_roll", attack_roll.into()),
345            ("defense_roll", defense_roll.into()),
346            ("blocked", blocked.into()),
347            ("critical", crit.into()),
348            ("damage", damage.into()),
349        ]));
350        if let Some(last) = records.last() {
351            last.insert("defender_hp", defender_hp);
352            last.insert("text", line);
353        }
354
355        left_turn = !left_turn;
356    }
357
358    let left_hp = role_hp(&left);
359    let right_hp = role_hp(&right);
360    let left_name = role_name(&left);
361    let right_name = role_name(&right);
362    let winner = if left_hp == right_hp {
363        "draw".to_string()
364    } else if left_hp > right_hp {
365        left_name.clone()
366    } else {
367        right_name.clone()
368    };
369    let loser = if winner == "draw" {
370        String::new()
371    } else if winner == left_name {
372        right_name
373    } else {
374        left_name
375    };
376
377    let mut result = std::collections::BTreeMap::new();
378    result.insert("winner".into(), winner.into());
379    result.insert("loser".into(), loser.into());
380    result.insert("draw".into(), (left_hp == right_hp).into());
381    result.insert("round_count".into(), (round_count as i64).into());
382    result.insert("process".into(), Dynamic::list(process));
383    result.insert("records".into(), Dynamic::list(records));
384    result.insert("left".into(), left);
385    result.insert("right".into(), right);
386    Dynamic::map(result)
387}
388
389pub fn mount_memory(name: &str) -> bool {
390    ROOT.mount_memory(name)
391}
392
393pub fn mount_redis(name: &str, url: &str) -> Result<bool> {
394    ROOT.mount_redis(name, url)
395}
396
397pub fn mount_fjall(data_dir: &str) -> Result<bool> {
398    ROOT.mount_fjall("fjall", data_dir)
399}
400
401#[cfg(feature = "iroh")]
402pub fn mount_iroh(name: &str, node_id: &str, local_dir: &str) -> Result<bool> {
403    ROOT.mount_iroh(name, node_id, local_dir)
404}
405
406pub fn get_mount<'a>(name: &'a str) -> Result<(Mount<Object>, &'a str)> {
407    ROOT.get_mount(name)
408}
409
410pub fn add(name: &str, obj: Object) -> Result<bool> {
411    let (m, name) = get_mount(name)?;
412    let mut obj = obj;
413    let expire = take_object_expire(&mut obj);
414    let added = m.add(name, obj);
415    if added {
416        apply_redis_expire(&m, name, expire);
417    }
418    Ok(added)
419}
420
421pub fn add_native(name: &str, native_name: &str) -> Result<bool> {
422    let Some(handler) = builtin_native(native_name) else {
423        return Ok(false);
424    };
425    let (m, name) = get_mount(name)?;
426    Ok(m.add(name, Object::Native(handler)))
427}
428
429pub fn add_value<T: Into<Dynamic>>(name: &str, val: T) -> Result<bool> {
430    let (m, name) = get_mount(name)?;
431    let mut value = val.into();
432    let expire = take_dynamic_expire(&mut value);
433    let added = m.add(name, Object::Value(value));
434    if added {
435        apply_redis_expire(&m, name, expire);
436    }
437    Ok(added)
438}
439
440pub fn get(name: &str) -> Result<Dynamic> {
441    let (m, name) = get_mount(name)?;
442    m.get(name, |obj| obj.value())
443}
444
445pub fn dir(name: &str) -> Result<Dynamic> {
446    let mount_name = name.split_once('/').map(|(n, _)| n).unwrap_or(name);
447    let (m, name) = get_mount(name)?;
448    m.dir(name).map(|names| if matches!(m, Mount::Redis { .. }) { names.into_iter().map(|n| format!("{}/{}", mount_name, n).into()).collect::<Vec<SmolStr>>().into() } else { names.into() })
449}
450
451#[cfg(feature = "iroh")]
452pub fn sync<F>(name: &str, mut progress: F) -> Result<Dynamic>
453where
454    F: FnMut(Dynamic) + Send + 'static,
455{
456    let mount_name = name.split_once('/').map(|(n, _)| n.to_string()).unwrap_or_else(|| name.to_string());
457    let (m, name) = get_mount(name)?;
458    let progress_mount_name = mount_name.clone();
459    let values = m.sync(name, move |value| progress(iroh_progress_to_dynamic(&progress_mount_name, value)))?;
460    Ok(Dynamic::list(values.into_iter().map(iroh_summary_to_dynamic).collect()))
461}
462
463#[cfg(feature = "iroh")]
464fn iroh_summary_to_dynamic(value: iroh::IrohSummary) -> Dynamic {
465    dynamic::map!(
466        "id"=> value.id,
467        "name"=> value.name,
468        "hash"=> value.hash,
469        "size"=> value.size as i64,
470        "modified_ms"=> value.modified_ms as i64
471    )
472}
473
474#[cfg(feature = "iroh")]
475fn iroh_progress_to_dynamic(mount_name: &str, progress: iroh::IrohSyncProgress) -> Dynamic {
476    let path = format!("{}/{}", mount_name, progress.value.id);
477    dynamic::map!(
478        "kind"=> "file",
479        "total"=> progress.total as i64,
480        "current"=> progress.current as i64,
481        "path"=> path,
482        "id"=> progress.value.id,
483        "name"=> progress.value.name,
484        "hash"=> progress.value.hash,
485        "size"=> progress.value.size as i64,
486        "modified_ms"=> progress.value.modified_ms as i64
487    )
488}
489
490pub fn contains(name: &str) -> bool {
491    if let Ok((m, name)) = get_mount(name) { m.contains(name) } else { false }
492}
493
494pub fn remove(name: &str) -> Result<Dynamic> {
495    let (m, name) = get_mount(name)?;
496    match m.remove(name) {
497        Ok(Object::Value(v)) => Ok(v),
498        _ => Err(anyhow!("没有删除对象")),
499    }
500}
501
502pub fn add_list(name: &str) -> Result<()> {
503    let (m, name) = get_mount(name)?;
504    m.add_list(name);
505    Ok(())
506}
507
508pub fn push(name: &str, value: Dynamic) -> Result<usize> {
509    let (m, name) = get_mount(name)?;
510    let mut value = value;
511    let expire = take_dynamic_expire(&mut value);
512    let len = m.push(name, Object::Value(value))?;
513    apply_redis_expire(&m, name, expire);
514    Ok(len)
515}
516
517pub fn add_map(name: &str) -> Result<()> {
518    let (m, name) = get_mount(name)?;
519    m.add_map(name);
520    Ok(())
521}
522
523pub fn insert(name: &str, key: &str, value: Dynamic) -> Result<()> {
524    let (m, name) = get_mount(name)?;
525    let mut value = value;
526    let expire = take_dynamic_expire(&mut value);
527    let _ = m.insert(name, key, Object::Value(value));
528    apply_redis_expire(&m, name, expire);
529    Ok(())
530}
531
532fn take_object_expire(obj: &mut Object) -> Option<i64> {
533    if let Object::Value(value) = obj { take_dynamic_expire(value) } else { None }
534}
535
536fn take_dynamic_expire(value: &mut Dynamic) -> Option<i64> {
537    value.remove_dynamic("@expire").and_then(|expire| expire.as_int()).filter(|expire| *expire > 0)
538}
539
540fn apply_redis_expire(mount: &Mount<Object>, name: &str, expire: Option<i64>) {
541    let Some(expire) = expire else {
542        return;
543    };
544    if let Mount::Redis { client, rl: _ } = mount
545        && let Ok(mut conn) = client.get_connection()
546    {
547        let _ = conn.expire::<&str, ()>(name, expire);
548    }
549}
550
551pub fn get_key(name: &str, key: &str) -> Result<Dynamic> {
552    let (m, name) = get_mount(name)?;
553    m.get_key(name, key, |obj| obj.value())
554}
555
556/// 原子并发更新一个节点的值。返回更新后的值。
557/// 节点必须已存在,否则返回 Err。
558pub fn update<F>(name: &str, mut f: F) -> Result<Dynamic>
559where
560    F: FnMut(Dynamic) -> Dynamic + Send + 'static,
561{
562    let (m, name) = get_mount(name)?;
563    m.get_mut(name, move |obj| match obj {
564        Object::Value(v) => {
565            let current = std::mem::take(v);
566            let next = f(current);
567            let result = next.deep_clone();
568            *v = next;
569            result
570        }
571        _ => Dynamic::Null,
572    })
573}
574
575/// 原子并发更新一个 map 节点内某个 key 的值。返回更新后的值。
576pub fn update_key<F>(name: &str, key: &str, mut f: F) -> Result<Dynamic>
577where
578    F: FnMut(Dynamic) -> Dynamic + Send + 'static,
579{
580    let (m, name) = get_mount(name)?;
581    m.get_key_mut(name, key, move |obj| match obj {
582        Object::Value(v) => {
583            let current = std::mem::take(v);
584            let next = f(current);
585            let result = next.deep_clone();
586            *v = next;
587            result
588        }
589        _ => Dynamic::Null,
590    })
591}
592
593use redis::Commands;
594pub fn get_list(name: &str) -> Result<Vec<Dynamic>> {
595    let (m, name) = get_mount(name)?;
596    match m {
597        Mount::Memory(m) => m
598            .read_sync(name, |_, v| match v {
599                Node::List(l) => l.iter().map(|(_, item)| item.value()).collect(),
600                _ => Vec::new(),
601            })
602            .ok_or(anyhow!("未发现 {}", name)),
603        Mount::Redis { client, rl: _ } => {
604            let mut conn = client.get_connection()?;
605            let items: Vec<Vec<u8>> = conn.lrange(name, 0, -1)?;
606            let items: Vec<Dynamic> = items.into_iter().map(|buf| Dynamic::decode(buf.as_slice()).map(|(v, _)| v).unwrap_or(Dynamic::Null)).collect();
607            Ok(items)
608        }
609        Mount::Fjall { .. } => {
610            let len = m.len(name)?;
611            let mut items = Vec::new();
612            for idx in 0..len {
613                if let Ok(value) = m.get_idx(name, idx, |obj| obj.value()) {
614                    items.push(value);
615                }
616            }
617            Ok(items)
618        }
619        #[cfg(feature = "iroh")]
620        Mount::Iroh { .. } => {
621            let len = m.len(name)?;
622            let mut items = Vec::new();
623            for idx in 0..len {
624                if let Ok(value) = m.get_idx(name, idx, |obj| obj.value()) {
625                    items.push(value);
626                }
627            }
628            Ok(items)
629        }
630    }
631}
632
633#[derive(Debug)]
634enum MyFn {
635    Null,
636    Sender(MsgSender<Dynamic>),
637    Native(fn(Dynamic) -> Dynamic),
638    Script(i64, Type),
639}
640
641impl MyFn {
642    fn call(&self, msg: Dynamic) -> Result<Dynamic> {
643        match self {
644            MyFn::Sender(tx) => call(tx, msg),
645            MyFn::Native(f) => Ok(f(msg)),
646            MyFn::Script(ptr, ty) => dynamic::call_fn(*ptr, ty.clone(), Box::new(msg)).map(|r| r.as_ref().clone()),
647            MyFn::Null => Ok(Dynamic::Null),
648        }
649    }
650}
651
652impl From<&Object> for MyFn {
653    fn from(obj: &Object) -> Self {
654        match obj {
655            Object::Tx(tx, _) => MyFn::Sender(tx.clone()),
656            Object::Task(t, _) => {
657                t.abort();
658                MyFn::Null
659            }
660            Object::Func(ptr, ty) => MyFn::Script(*ptr, ty.clone()),
661            Object::Native(f) => MyFn::Native(*f),
662            _ => MyFn::Null,
663        }
664    }
665}
666
667pub fn send_msg(name: &str, msg: Dynamic) -> Result<Dynamic> {
668    let entered = SEND_STACK.with(|stack| {
669        let mut stack = stack.borrow_mut();
670        if stack.len() >= 64 {
671            return Err(anyhow!("root::send call depth exceeded at {}", name));
672        }
673        if stack.iter().any(|item| item == name) {
674            return Err(anyhow!("root::send recursive path detected: {}", name));
675        }
676        stack.push(name.to_string());
677        Ok(())
678    });
679    entered?;
680    let result = (|| {
681        let (m, name) = get_mount(name)?;
682        let f: MyFn = m.get(name, |obj| obj.into())?;
683        f.call(msg)
684    })();
685    SEND_STACK.with(|stack| {
686        let _ = stack.borrow_mut().pop();
687    });
688    result
689}
690
691pub fn send_idx_msg(name: &str, idx: usize, msg: Dynamic) -> Result<Dynamic> {
692    let stack_name = format!("{}[{}]", name, idx);
693    let entered = SEND_STACK.with(|stack| {
694        let mut stack = stack.borrow_mut();
695        if stack.len() >= 64 {
696            return Err(anyhow!("root::send_idx call depth exceeded at {}", stack_name));
697        }
698        if stack.iter().any(|item| item == &stack_name) {
699            return Err(anyhow!("root::send_idx recursive path detected: {}", stack_name));
700        }
701        stack.push(stack_name);
702        Ok(())
703    });
704    entered?;
705    let result = (|| {
706        let (m, name) = get_mount(name)?;
707        let f: MyFn = m.get_idx(name, idx, |obj| obj.into())?;
708        f.call(msg)
709    })();
710    SEND_STACK.with(|stack| {
711        let _ = stack.borrow_mut().pop();
712    });
713    result
714}
715
716#[cfg(test)]
717mod tests {
718    use super::*;
719
720    fn fight_request() -> Dynamic {
721        let left = Dynamic::map(Default::default());
722        left.insert("name", "战士");
723        left.insert("hp", 30);
724        left.insert("attack_min", 8);
725        left.insert("attack_max", 12);
726        left.insert("defense_min", 1);
727        left.insert("defense_max", 3);
728        left.insert("block_chance", 0.2);
729
730        let right = Dynamic::map(Default::default());
731        right.insert("name", "盗贼");
732        right.insert("hp", 24);
733        right.insert("attack_min", 6);
734        right.insert("attack_max", 10);
735        right.insert("defense_min", 0);
736        right.insert("defense_max", 2);
737        right.insert("block_chance", 0.1);
738
739        let msg = Dynamic::map(Default::default());
740        msg.insert("left", left);
741        msg.insert("right", right);
742        msg
743    }
744
745    #[test]
746    fn add_native_registers_builtin_fight_handler() {
747        assert!(add_native("local/test/fight_handler", "native.fight").unwrap());
748        let result = send_msg("local/test/fight_handler", fight_request()).unwrap();
749        assert!(result.is_map());
750        assert!(result.get_dynamic("winner").is_some());
751        assert!(result.get_dynamic("process").is_some_and(|v| v.is_list()));
752    }
753
754    #[test]
755    fn local_fight_handler_is_available_by_default() {
756        assert!(contains("local/fight"));
757        let result = send_msg("local/fight", fight_request()).unwrap();
758        let rounds = result.get_dynamic("round_count").and_then(|v| v.as_int()).unwrap_or_default();
759        assert!(rounds > 0);
760    }
761
762    #[test]
763    fn take_dynamic_expire_strips_positive_expire_metadata() {
764        let mut value = dynamic::map!("@expire"=> 30, "name"=> "zust");
765
766        assert_eq!(take_dynamic_expire(&mut value), Some(30));
767        assert!(!value.contains("@expire"));
768        assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
769    }
770
771    #[test]
772    fn take_dynamic_expire_ignores_non_positive_expire_metadata() {
773        let mut value = dynamic::map!("@expire"=> 0, "name"=> "zust");
774
775        assert_eq!(take_dynamic_expire(&mut value), None);
776        assert!(!value.contains("@expire"));
777    }
778
779    #[test]
780    fn update_increments_value_atomically() {
781        let path = format!("local/test/update_inc/{}", uuid::Uuid::new_v4());
782        add_value(&path, 0_i64).unwrap();
783
784        let next = update(&path, |v| v + Dynamic::I64(1)).unwrap();
785        assert_eq!(next.as_int(), Some(1));
786        assert_eq!(get(&path).unwrap().as_int(), Some(1));
787
788        let next = update(&path, |v| v + Dynamic::I64(41)).unwrap();
789        assert_eq!(next.as_int(), Some(42));
790        assert_eq!(get(&path).unwrap().as_int(), Some(42));
791    }
792
793    #[test]
794    fn update_concurrent_threads_no_lost_update() {
795        let path = format!("local/test/update_concurrent/{}", uuid::Uuid::new_v4());
796        add_value(&path, 0_i64).unwrap();
797
798        const THREADS: i64 = 8;
799        const ITERS: i64 = 1000;
800
801        let mut handles = Vec::new();
802        for _ in 0..THREADS {
803            let p = path.clone();
804            handles.push(std::thread::spawn(move || {
805                for _ in 0..ITERS {
806                    update(&p, |v| v + Dynamic::I64(1)).unwrap();
807                }
808            }));
809        }
810        for h in handles {
811            h.join().unwrap();
812        }
813
814        assert_eq!(get(&path).unwrap().as_int(), Some(THREADS * ITERS));
815    }
816
817    #[test]
818    fn update_key_increments_map_value() {
819        let path = format!("local/test/update_key/{}", uuid::Uuid::new_v4());
820        add_map(&path).unwrap();
821        insert(&path, "n", 0_i64.into()).unwrap();
822
823        const THREADS: i64 = 8;
824        const ITERS: i64 = 500;
825
826        let mut handles = Vec::new();
827        for _ in 0..THREADS {
828            let p = path.clone();
829            handles.push(std::thread::spawn(move || {
830                for _ in 0..ITERS {
831                    update_key(&p, "n", |v| v + Dynamic::I64(1)).unwrap();
832                }
833            }));
834        }
835        for h in handles {
836            h.join().unwrap();
837        }
838
839        assert_eq!(get_key(&path, "n").unwrap().as_int(), Some(THREADS * ITERS));
840    }
841
842    #[test]
843    fn update_returns_err_when_missing() {
844        let path = format!("local/test/update_missing/{}", uuid::Uuid::new_v4());
845        let result = update(&path, |v| v + Dynamic::I64(1));
846        assert!(result.is_err());
847    }
848
849    #[test]
850    fn root_get_returns_independent_dynamic_values() {
851        let path = format!("local/test/root_get_clone/{}", uuid::Uuid::new_v4());
852        let nested = dynamic::map!("score"=> 1);
853        let value = dynamic::map!("nested"=> nested.clone(), "items"=> Dynamic::list(vec![nested]));
854
855        add_value(&path, value).unwrap();
856
857        let first = get(&path).unwrap();
858        first.get_dynamic("nested").unwrap().insert("score", 2);
859        first.get_dynamic("items").unwrap().get_idx(0).unwrap().insert("score", 3);
860
861        let second = get(&path).unwrap();
862        assert_eq!(second.get_dynamic("nested").unwrap().get_dynamic("score").and_then(|v| v.as_int()), Some(1));
863        assert_eq!(second.get_dynamic("items").unwrap().get_idx(0).unwrap().get_dynamic("score").and_then(|v| v.as_int()), Some(1));
864    }
865}