ek-core 0.5.6

Business-agnostic plugin host and actor-over-bus runtime on top of ek-event: capability index with runtime service discovery, per-plugin actor workers, dynamic add/remove with lifecycle broadcasts, one-shot ask/ask_service with contract gates, panic quarantine, five-step close.
Documentation
//! actor_routing:每插件独立邮箱 FIFO / routes 隔离 / 分发线程零业务(探针)

mod common;

use common::{records, wait_until};
use ek_core::MainEngine;
use ek_event::Event;
use std::sync::Arc;
use std::time::Duration;

// AR-1/3 唯一入口 on_mail 在插件 worker 线程上 FIFO 串行;分发线程不执行业务
#[test]
fn ar1_fifo_and_worker_thread() {
    let eng = MainEngine::new(None);
    let got = records::<(String, u64)>(); // (线程名, 载荷值)
    let g = Arc::clone(&got);
    eng.add_plugin(
        common::test_plugin("oms")
            .routes_str(&["eTick."])
            .mail(move |e: &Event| {
                let v = *e.data.downcast_ref::<u64>().expect("payload type");
                common::push_record(&g, (std::thread::current().name().unwrap().to_string(), v));
            })
            .build(),
    )
    .unwrap();
    eng.start().unwrap();

    for i in 0..200u64 {
        eng.event_engine()
            .put(Event::with_data("eTick.", Arc::new(i)))
            .unwrap();
    }
    assert!(
        wait_until(Duration::from_secs(3), || common::snapshot(&got).len()
            == 200),
        "all mails delivered"
    );

    let all = common::snapshot(&got);
    for (idx, (thread_name, v)) in all.iter().enumerate() {
        assert_eq!(
            thread_name, "ek-worker.oms",
            "on_mail must run on plugin worker"
        );
        assert_eq!(*v, idx as u64, "FIFO order preserved");
    }
    eng.close_all(Duration::from_secs(1));
}

// AR-2 订阅面隔离:routes 声明决定收什么;别的 type 不串门
#[test]
fn ar2_routes_isolation() {
    let eng = MainEngine::new(None);
    let ta = common::counter();
    let tb = common::counter();
    let (a1, b1) = (Arc::clone(&ta), Arc::clone(&tb));
    eng.add_plugin(
        common::test_plugin("pa")
            .routes_str(&["eTick."])
            .mail(move |_e| {
                common::bump(&a1);
            })
            .build(),
    )
    .unwrap();
    eng.add_plugin(
        common::test_plugin("pb")
            .routes_str(&["eOrder."])
            .mail(move |_e| {
                common::bump(&b1);
            })
            .build(),
    )
    .unwrap();
    eng.start().unwrap();

    for i in 0..3u64 {
        eng.event_engine()
            .put(Event::with_data("eTick.", Arc::new(i)))
            .unwrap();
        eng.event_engine()
            .put(Event::with_data("eOrder.", Arc::new(i)))
            .unwrap();
    }
    assert!(wait_until(Duration::from_secs(2), || {
        common::load(&ta) == 3 && common::load(&tb) == 3
    }));
    std::thread::sleep(Duration::from_millis(200));
    assert_eq!(common::load(&ta), 3, "pa must not see eOrder.");
    assert_eq!(common::load(&tb), 3, "pb must not see eTick.");
    eng.close_all(Duration::from_secs(1));
}