use serde_json::Value;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
pub struct Event {
name: String, // 事件名
data: Option<Value>, // 数据使用Value格式
}
impl Event {
pub fn new(name: &str, data: Option<Value>) -> Self {
Self {
name: name.to_string(),
data,
}
}
}
// type EventListener = fn(Option<Value>);
type EventListener = Box<dyn FnMut(Option<Value>)>;
type ListenersMap = HashMap<String, Vec<EventListener>>;
struct EventDispatcher {
listeners: ListenersMap,
}
impl EventDispatcher {
pub fn new() -> Self {
Self {
listeners: HashMap::new(),
}
}
pub fn dispatch_event(&mut self, event: Event) {
if let Some(listeners) = self.listeners.get_mut(&event.name) {
for listener in listeners {
listener(event.data.clone());
}
}
}
pub fn add_event_listener(&mut self, name: &str, listener: EventListener) {
let arr = self
.listeners
.entry(name.to_string())
.or_insert_with(Vec::new);
arr.push(listener);
// let func = &(listener);
// if !arr.contains(func) {
// arr.push(listener);
// }
}
pub fn on(&mut self, name: &str, listener: EventListener) {
self.add_event_listener(name, listener);
}
pub fn emit(&mut self, name: &str, params: Option<Value>) {
self.dispatch_event(Event::new(name, params))
}
}
trait PluginOptions {
fn name(&self) -> &str;
fn deps(&self) -> Vec<&str>;
fn install(&mut self, engine: &mut Engine);
fn dispose(&mut self, engine: &mut Engine);
}
trait StrategyOptions {
fn name(&self) -> &str;
fn condition(&self) -> Vec<&str>;
fn exec(&self, engine: &mut Engine);
fn rollback(&mut self, engine: &mut Engine);
}
type PluginsMap = HashMap<String, Box<dyn PluginOptions>>;
type StrategysMap = HashMap<String, Box<dyn StrategyOptions>>;
struct Engine {
event: EventDispatcher,
plugins: PluginsMap,
strategys: StrategysMap,
}
impl Engine {
pub fn new() -> Self {
Self {
event: EventDispatcher::new(),
plugins: HashMap::new(),
strategys: HashMap::new(),
}
}
pub fn install<P: PluginOptions + 'static>(&mut self, plugin: P) -> String {
let name = plugin.name();
if !self.plugins.contains_key(name) {
// 不存在对应的插件
// 检查插件依赖
let mut deps: Vec<&str> = Vec::new();
for dep in plugin.deps() {
// 依赖插件不存在
if !self.plugins.contains_key(dep) {
deps.push(dep);
}
}
if !deps.is_empty() {
// 提示错误
return format!(
"安装插件 '{}' 出错:依赖插件 '{}' 不存在",
name,
deps.join(",")
);
}
// 执行插件安装方法
let mut plugin_box = Box::new(plugin);
plugin_box.install(self);
// 记录插件
self.plugins
.insert(plugin_box.name().to_string(), plugin_box);
}
"".to_string()
}
pub fn uninstall(&mut self, plugin_name: &str) {
if let Some(mut plugin) = self.plugins.remove(plugin_name) {
// 策略B依赖插件A,插件A被卸载,关联的策略B也要被回滚
let mut strategies = Vec::new();
for (_, strategy) in self.strategys.iter() {
if strategy.condition().contains(&plugin_name) {
strategies.push(strategy.name().to_string());
}
}
// 执行回滚操作
for strategy_name in strategies {
self.rollback(&strategy_name);
}
// 插件B依赖插件A,插件A被卸载,关联的插件B也要被卸载
let mut plugins = Vec::new();
for (_, plugin) in self.plugins.iter() {
if plugin.deps().contains(&plugin_name) {
plugins.push(plugin.name().to_string());
}
}
// 执行关键插件的卸载
for plugin_name in plugins {
self.uninstall(&plugin_name);
}
// 执行销毁方法
plugin.dispose(self);
} else {
println!("插件 {} 不存在", plugin_name)
}
}
pub fn exec<S: StrategyOptions + 'static>(&mut self, strategy: S) -> String {
let name = strategy.name();
if !self.strategys.contains_key(name) {
// 不存在对应的策略
// 检查策略依赖
let mut plugins: Vec<&str> = Vec::new();
for plugin in strategy.condition() {
// 依赖插件不存在,记录起来
if !self.plugins.contains_key(plugin) {
plugins.push(plugin);
}
}
if plugins.len() > 0 {
// 提示错误
return format!(
"执行策略 '{}' 出错:依赖插件 '{}' 不存在",
name,
plugins.join(",")
);
}
// 执行策略
strategy.exec(self);
// 记录策略
self.strategys
.insert(strategy.name().to_string(), Box::new(strategy));
}
"".to_string()
}
pub fn rollback(&mut self, strategy_name: &str) {
// 找到与name对应的策略并删除
if let Some(mut strategy) = self.strategys.remove(strategy_name) {
// 执行回滚方法
strategy.rollback(self);
} else {
println!("策略 {} 不存在", strategy_name)
}
}
}
struct APlugin;
impl PluginOptions for APlugin {
fn name(&self) -> &str {
"APlugin"
}
fn deps(&self) -> Vec<&str> {
vec![]
}
fn install(&mut self, engine: &mut Engine) {
println!("--APlugin--");
let engine_rc = Rc::new(RefCell::new(engine));
let engine_move = Rc::clone(&engine_rc);
let listener = Box::new(move |x| {
println!("--1--{:?}", x);
engine_move.borrow_mut().event.emit("list", None);
});
engine_rc.borrow_mut().event.on("get-list", listener);
}
fn dispose(&mut self, engine: &mut Engine) {}
}
struct BPlugin;
impl PluginOptions for BPlugin {
fn name(&self) -> &str {
"BPlugin"
}
fn deps(&self) -> Vec<&str> {
vec!["APlugin"]
}
fn install(&mut self, engine: &mut Engine) {
println!("--BPlugin--");
engine.event.emit("get-list", Some(1.into()));
engine.event.on(
"list",
Box::new(|x| {
println!("--2--{:?}", x);
}),
);
}
fn dispose(&mut self, engine: &mut Engine) {}
}
struct AStrategy;
impl StrategyOptions for AStrategy {
fn name(&self) -> &str {
"AStrategy"
}
fn condition(&self) -> Vec<&str> {
vec!["APlugin", "BPlugin"]
}
fn exec(&self, engine: &mut Engine) {
println!("--AStrategy--");
}
fn rollback(&mut self, engine: &mut Engine) {}
}
fn main() {
let mut engine = Engine::new();
engine.install(APlugin);
engine.install(BPlugin);
engine.exec(AStrategy);
}
In event listeners you generally can't use any temporary references.
If something is borrowed temporarily with & or &mut, then it's always temporary and can't live any longer. Rc won't help. There is no way to make temporary loan non-temporary.
You have to use self-contained types, like Rc<RefCell<Engine>>
, without limiting it by &mut
.
You could add lifetime annotations (there's a hidden 'static
in Box<…>
that can be replaced with explicit scope name), but in most cases that will make the entire code impossible to use, because the whole event listener will become temporary itself. Additionally, lifetimes of &mut
loans are exclusive and extra restrictive, so your event listener probably wouldn't even handle more than one listener.
Temporary loans with static scopes (Rust references) are not a good fit for the observer pattern that mixes many scopes and lifetimes dynamically.
Demand:
A. The application is developed in an event-driven mode
B. The application functions are expanded in the form of plugins
C. Business logic is organized in the form of strategies
D. Communicate with each module of the application based on events
For a demand like mine, how should it be designed to be the best solution
Rust uses Rc
and Arc
to keep non-temporary references to objects.
&mut
is a special case, and it's not a general purpose tool. It is limited and impossible to use in many situations on purpose, by design.
Correct application design in Rust involves deciding when &
and Arc
are appropriate. For even-driven applications, Arc
is a good design, and &
is in most cases a mistake caused by confusing it with by-reference objects in garbage collected languages. Rust doesn't have garbage collection, so application design patterns for GC languages don't look the same in Rust.
I have just come into contact with rust for a short time. The development mindset mentioned above for requirements is frequently used in typescript projects and I am quite familiar with it. Therefore, I want to apply this development mindset in rust as well
You should be quite familiar with rust. I hope you can provide a specific solution. Thank you
I suggest first learning about how concurrency is handled safely in Rust, before trying to apply that knowledge to an event drive architecture.
I've received it. Maybe I was deeply influenced by GC-like languages and couldn't change my mind for a moment
Some more direct advice, in case this is what you're looking for: You shouldn't store &mut
in an Rc, and you should use Rc instead of Box when you are sharing a value.
One way to start is to change your PluginOptions as follows, and then change all other code accordingly.
trait PluginOptions {
fn name(&self) -> &str;
fn deps(&self) -> Vec<&str>;
fn install(&mut self, engine: Rc<RefCell<Engine>>); // <<< use Rc
fn dispose(&mut self, engine: Rc<RefCell<Engine>>); // <<< use Rc
}
If you end up using multiple threads or an async runtime, you'll also need to change Rc<RefCell<>>
to Arc<Mutex<>>
.
Please feel free to ask questions as you make code changes.