179 lines
6.2 KiB
Rust
179 lines
6.2 KiB
Rust
use anyhow::Result;
|
||
use showen_v2::core::config::AppConfig;
|
||
use showen_v2::core::plugin_loader::PluginLoader;
|
||
use showen_v2::core::service_manager::ServiceManager;
|
||
#[cfg(not(test))]
|
||
use showen_v2::core::version_manager::VersionManager;
|
||
use showen_v2::plugins::{
|
||
ai::AiPlugin, ble::BlePlugin, device::DevicePlugin, http::HttpPlugin, live2d::Live2DPlugin,
|
||
screen::ScreenPlugin, video::VideoPlugin, wifi::WifiPlugin,
|
||
};
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use std::sync::Arc;
|
||
|
||
fn main() -> Result<()> {
|
||
// 解析命令行参数
|
||
let args: Vec<String> = std::env::args().collect();
|
||
|
||
// 处理 --validate 参数
|
||
if args.contains(&"--validate".to_string()) {
|
||
let config_path = args
|
||
.iter()
|
||
.skip_while(|arg| *arg != "--config")
|
||
.nth(1)
|
||
.cloned()
|
||
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
|
||
|
||
println!("验证配置文件: {}", config_path);
|
||
let config = AppConfig::from_file(&config_path)?;
|
||
config.validate_paths()?;
|
||
println!("✓ 配置文件有效");
|
||
return Ok(());
|
||
}
|
||
|
||
// 获取配置文件路径
|
||
let config_path = args
|
||
.iter()
|
||
.skip_while(|arg| *arg != "--config")
|
||
.nth(1)
|
||
.cloned()
|
||
.or_else(|| args.get(1).cloned())
|
||
.unwrap_or_else(|| "configs/dog_state_machine.json".to_string());
|
||
|
||
println!("ShowenV2 — 数字生命窗口平台");
|
||
println!("加载配置: {}", config_path);
|
||
|
||
let config = AppConfig::from_file(&config_path)?;
|
||
config.validate_paths()?;
|
||
|
||
// 在 config move 进 ServiceManager 之前,提取 AI 相关配置
|
||
let ai_model_store = config.ai.model_store.clone();
|
||
let ai_tools_dir = config.ai.tools_dir.clone();
|
||
let ai_whisper_lib_dir = config.ai.whisper_lib_dir.clone();
|
||
let ai_piper_lib_dir = config.ai.piper_lib_dir.clone();
|
||
let ai_piper_config = config.ai.piper_config.clone();
|
||
let ai_espeak_data_dir = config.ai.espeak_data_dir.clone();
|
||
let ai_tmp_dir = config.ai.tmp_dir.clone();
|
||
let ai_context_window = config.ai.context_window;
|
||
|
||
let mut manager = ServiceManager::new(config);
|
||
|
||
// 按依赖顺序注册插件
|
||
// 独立插件:device, screen, wifi, video, ble, ai
|
||
// 依赖插件:http (依赖 video)
|
||
|
||
println!("注册插件...");
|
||
manager.register(Box::new(DevicePlugin::new_default()));
|
||
println!(" ✓ DevicePlugin");
|
||
|
||
manager.register(Box::new(ScreenPlugin::new()));
|
||
println!(" ✓ ScreenPlugin");
|
||
|
||
manager.register(Box::new(WifiPlugin::new()));
|
||
println!(" ✓ WifiPlugin");
|
||
|
||
manager.register(Box::new(VideoPlugin::new()));
|
||
println!(" ✓ VideoPlugin");
|
||
|
||
manager.register(Box::new(Live2DPlugin::new()));
|
||
println!(" ✓ Live2DPlugin");
|
||
|
||
manager.register(Box::new(BlePlugin::new()));
|
||
println!(" ✓ BlePlugin");
|
||
|
||
// AiPlugin: 需要在注册前创建实例以获取 pipeline 句柄,再注册进 manager
|
||
// pipeline 会传给 HttpPlugin,建立 HTTP → AI 的直接通道
|
||
let ai_plugin = AiPlugin::new_default(
|
||
ai_model_store,
|
||
ai_tools_dir,
|
||
ai_whisper_lib_dir,
|
||
ai_piper_lib_dir,
|
||
ai_piper_config,
|
||
ai_espeak_data_dir,
|
||
ai_tmp_dir,
|
||
ai_context_window,
|
||
);
|
||
let ai_pipeline = ai_plugin.pipeline();
|
||
let ai_models = ai_plugin.models();
|
||
manager.register(Box::new(ai_plugin));
|
||
println!(" ✓ AiPlugin");
|
||
|
||
let mut http_plugin = HttpPlugin::new();
|
||
// 注册 AI 句柄到 HttpState(在 http init 之前建立连接)
|
||
http_plugin.set_ai_pipeline(ai_pipeline);
|
||
http_plugin.set_ai_models(ai_models);
|
||
manager.register(Box::new(http_plugin));
|
||
println!(" ✓ HttpPlugin");
|
||
|
||
// 加载动态插件
|
||
let plugin_store = std::path::Path::new("plugin_store");
|
||
if plugin_store.exists() {
|
||
println!("扫描动态插件...");
|
||
#[cfg(not(test))]
|
||
manager.set_version_manager(VersionManager::new(PluginLoader::new(plugin_store)));
|
||
let loader = PluginLoader::new(plugin_store);
|
||
match loader.load_registry() {
|
||
Ok(registry) => {
|
||
for (plugin_id, entry) in ®istry.plugins {
|
||
if !entry.enabled {
|
||
println!(" - {plugin_id} (禁用)");
|
||
continue;
|
||
}
|
||
|
||
match loader.load_plugin(plugin_id, Some(&entry.active_version)) {
|
||
Ok((plugin, manifest)) => {
|
||
manager.register_dynamic_with_manifest(
|
||
Box::new(plugin),
|
||
manifest.error_policy,
|
||
entry.max_errors,
|
||
manifest.required_capabilities,
|
||
manifest.capabilities,
|
||
manifest.auto_test,
|
||
);
|
||
println!(" ✓ {} v{} (动态)", plugin_id, entry.active_version);
|
||
}
|
||
Err(e) => {
|
||
eprintln!(" ✗ {} v{} 加载失败: {e}", plugin_id, entry.active_version);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
eprintln!("读取插件注册表失败: {e}");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置 Ctrl+C 信号处理
|
||
let running = Arc::new(AtomicBool::new(true));
|
||
let r = running.clone();
|
||
let shutdown_tx = manager.sender();
|
||
|
||
ctrlc::set_handler(move || {
|
||
println!("\n收到退出信号,正在关闭...");
|
||
r.store(false, Ordering::SeqCst);
|
||
// 注入 Shutdown 消息,唤醒主消息循环并触发优雅停机
|
||
let _ = shutdown_tx.send(showen_v2::core::message::Envelope {
|
||
from: "signal".to_string(),
|
||
to: showen_v2::core::message::Destination::Manager,
|
||
message: showen_v2::core::message::Message::Shutdown,
|
||
});
|
||
})?;
|
||
|
||
println!("启动所有插件...");
|
||
manager.start_all()?;
|
||
|
||
println!("ShowenV2 运行中... (按 Ctrl+C 退出)");
|
||
|
||
// 运行主循环
|
||
while running.load(Ordering::SeqCst) {
|
||
manager.run()?;
|
||
}
|
||
|
||
println!("正在停止所有插件...");
|
||
manager.stop_all()?;
|
||
println!("ShowenV2 已退出");
|
||
|
||
Ok(())
|
||
}
|