mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-06 01:16:24 -06:00
177 lines
6.8 KiB
Rust
177 lines
6.8 KiB
Rust
//! Tauri-Bevy integration plugin
|
|
//!
|
|
//! This module provides the main integration between Tauri and Bevy, handling
|
|
//! the main application loop and event bridging.
|
|
|
|
use borders_core::app::{App, Plugin, Update};
|
|
use borders_core::time::Time;
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
use std::sync::{Arc, Mutex};
|
|
use std::time::Duration;
|
|
use tauri::{Manager, RunEvent};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use std::time::Instant;
|
|
#[cfg(target_arch = "wasm32")]
|
|
use web_time::Instant;
|
|
|
|
use crate::render_bridge::{TauriRenderBridgeTransport, cache_leaderboard_snapshot_system};
|
|
|
|
const TARGET_FPS: f64 = 60.0;
|
|
|
|
pub fn generate_tauri_context() -> tauri::Context {
|
|
tauri::generate_context!()
|
|
}
|
|
|
|
fn setup_tauri_integration(app: &mut App, tauri_app: &tauri::AppHandle, shared_render_state: Arc<Mutex<Option<borders_core::ui::protocol::RenderInit>>>, shared_leaderboard_state: Arc<Mutex<Option<borders_core::ui::protocol::LeaderboardSnapshot>>>) {
|
|
tracing::debug!("Setup tauri integration");
|
|
|
|
// Register state for render bridge commands
|
|
tauri_app.manage(Arc::new(Mutex::new(None::<borders_core::ui::protocol::CameraStateUpdate>)));
|
|
tauri_app.manage(Arc::new(Mutex::new(None::<borders_core::networking::GameView>)));
|
|
|
|
// InputState - shared between Tauri commands and ECS systems
|
|
let input_state_shared = Arc::new(Mutex::new(borders_core::ui::input::InputState::new()));
|
|
tauri_app.manage(input_state_shared.clone());
|
|
app.insert_non_send_resource(input_state_shared);
|
|
|
|
// Register shared state with Tauri (for get_game_state command)
|
|
tauri_app.manage(shared_render_state.clone());
|
|
tauri_app.manage(shared_leaderboard_state.clone());
|
|
|
|
// Get the message queue from the transport (already added as plugin)
|
|
let transport = app.world().get_resource::<borders_core::ui::RenderBridge<TauriRenderBridgeTransport>>().expect("RenderBridge should be added by plugin");
|
|
let message_queue = transport.transport.inbound_messages();
|
|
tauri_app.manage(message_queue);
|
|
|
|
// Store shared states in world
|
|
app.insert_non_send_resource(shared_leaderboard_state);
|
|
}
|
|
|
|
pub struct TauriPlugin {
|
|
setup: Box<dyn Fn() -> tauri::App + Send + Sync>,
|
|
}
|
|
|
|
impl TauriPlugin {
|
|
pub fn new<F>(setup: F) -> Self
|
|
where
|
|
F: Fn() -> tauri::App + Send + Sync + 'static,
|
|
{
|
|
Self { setup: Box::new(setup) }
|
|
}
|
|
}
|
|
|
|
impl TauriPlugin {
|
|
pub fn build_and_run(self, mut app: App) -> ! {
|
|
let tauri_app = (self.setup)();
|
|
|
|
// Create shared state for game state recovery
|
|
let shared_render_state = Arc::new(Mutex::new(None::<borders_core::ui::protocol::RenderInit>));
|
|
let shared_leaderboard_state = Arc::new(Mutex::new(None::<borders_core::ui::protocol::LeaderboardSnapshot>));
|
|
|
|
// Create transport for Tauri frontend (handles both render and UI communication)
|
|
let transport = TauriRenderBridgeTransport::new(tauri_app.handle().clone(), shared_render_state.clone());
|
|
|
|
// Add the render bridge plugin to handle all frontend communication
|
|
borders_core::ui::FrontendPlugin::new(transport).build(&mut app);
|
|
|
|
// Set up Tauri integration directly (no startup system needed)
|
|
setup_tauri_integration(&mut app, tauri_app.handle(), shared_render_state, shared_leaderboard_state);
|
|
|
|
// Add the leaderboard caching system
|
|
app.add_systems(Update, cache_leaderboard_snapshot_system);
|
|
|
|
// Run the app
|
|
run_tauri_app(app, tauri_app);
|
|
std::process::exit(0)
|
|
}
|
|
}
|
|
|
|
pub fn run_tauri_app(app: App, tauri_app: tauri::App) {
|
|
let app_rc = Rc::new(RefCell::new(app));
|
|
let mut tauri_app = tauri_app;
|
|
let mut is_initialized = false;
|
|
let mut last_frame_time = Instant::now();
|
|
|
|
let target_frame_duration = Duration::from_secs_f64(1.0 / TARGET_FPS);
|
|
|
|
loop {
|
|
let frame_start = Instant::now();
|
|
|
|
#[allow(deprecated)]
|
|
tauri_app.run_iteration(move |_app_handle, event: RunEvent| {
|
|
match event {
|
|
tauri::RunEvent::Ready => {
|
|
// Event acknowledged, actual setup happens below
|
|
}
|
|
tauri::RunEvent::ExitRequested { .. } => {
|
|
// Track session end and flush analytics before exit
|
|
if borders_core::telemetry::client().is_some() {
|
|
tracing::debug!("ExitRequested: tracking session end and flushing analytics");
|
|
|
|
// Create a minimal runtime for blocking operations
|
|
let runtime = tokio::runtime::Builder::new_current_thread().enable_time().enable_io().build().expect("Failed to create tokio runtime for flush");
|
|
|
|
runtime.block_on(async {
|
|
// Track session end event
|
|
borders_core::telemetry::track_session_end().await;
|
|
|
|
// Flush all pending events (the batch-triggered send is now synchronous)
|
|
if let Some(client) = borders_core::telemetry::client() {
|
|
let timeout = std::time::Duration::from_millis(500);
|
|
match tokio::time::timeout(timeout, client.flush()).await {
|
|
Ok(_) => {
|
|
tracing::debug!("Analytics flushed successfully before exit")
|
|
}
|
|
Err(_) => {
|
|
tracing::warn!("Analytics flush timed out after 500ms")
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
});
|
|
|
|
if tauri_app.webview_windows().is_empty() {
|
|
tauri_app.cleanup_before_exit();
|
|
break;
|
|
}
|
|
|
|
// Initialize game plugin on first iteration after Tauri is ready
|
|
if !is_initialized {
|
|
let mut app = app_rc.borrow_mut();
|
|
|
|
// Add core game plugin
|
|
borders_core::GamePlugin::new(borders_core::plugin::NetworkMode::Local).build(&mut app);
|
|
|
|
app.run_startup();
|
|
app.finish();
|
|
app.cleanup();
|
|
|
|
is_initialized = true;
|
|
last_frame_time = Instant::now(); // Reset timer after initialization
|
|
tracing::info!("Game initialized");
|
|
}
|
|
|
|
// Update time resource with delta from PREVIOUS frame
|
|
let mut app = app_rc.borrow_mut();
|
|
let delta = frame_start.duration_since(last_frame_time);
|
|
if let Some(mut time) = app.world_mut().get_resource_mut::<Time>() {
|
|
time.update(delta);
|
|
}
|
|
|
|
app.update();
|
|
|
|
let frame_duration = frame_start.elapsed();
|
|
if frame_duration < target_frame_duration {
|
|
std::thread::sleep(target_frame_duration - frame_duration);
|
|
}
|
|
|
|
last_frame_time = frame_start;
|
|
}
|
|
}
|