First test kinda working

This commit is contained in:
Valère Plantevin
2026-05-12 11:21:40 -04:00
parent cac6c9ac02
commit d3f09ee062
36 changed files with 3903 additions and 102 deletions

View File

@@ -1,68 +1,111 @@
use std::sync::Mutex;
use bevy::prelude::*;
use bevy::state::app::StatesPlugin;
use tokio::runtime::Handle;
use tokio::sync::mpsc;
use crate::transport::QuicMessage;
use crate::transport::server::run_substrate_server;
use crate::config::AppConfig;
use crate::transport::{QuicMessage, T1Sender, T2Sender, T3Inbound, T3Sender};
use crate::transport::server::{accept_loop, bind_endpoint};
use crate::transport::state::ServerState;
const T1_CAPACITY: usize = 1024;
const T2_CAPACITY: usize = 512;
const T3_CAPACITY: usize = 256;
pub struct EcsQuicTransportPlugin{}
pub struct EcsQuicTransportPlugin;
/// Receive halves of the three tier channels, wrapped so they can sit in a
/// Bevy `Resource`. The `world` module's ingest system is the sole reader.
#[derive(Resource)]
struct BridgeReceivers {
t1: Mutex<mpsc::Receiver<QuicMessage>>,
t2: Mutex<mpsc::Receiver<QuicMessage>>,
t3: Mutex<mpsc::Receiver<QuicMessage>>,
pub(crate) struct BridgeReceivers {
pub(crate) t1: Mutex<mpsc::Receiver<QuicMessage>>,
pub(crate) t2: Mutex<mpsc::Receiver<QuicMessage>>,
pub(crate) t3: Mutex<mpsc::Receiver<T3Inbound>>,
}
fn ingest_system(bridge: Res<BridgeReceivers>){
let mut t1 = bridge.t1.lock().unwrap();
// Tier 1: drain up to N messages, drop the rest
for _ in 0..T1_CAPACITY {
match t1.try_recv() {
Ok(msg) => { /* write RawSensorData */ }
Err(_) => break,
}
}
// T2/T3: drain completely, these are low volume
let mut t2 = bridge.t2.lock().unwrap();
while let Ok(msg) = t2.try_recv() { /* ... */ }
let mut t3 = bridge.t3.lock().unwrap();
while let Ok(msg) = t3.try_recv() { /* ... */ }
#[derive(Resource, Clone)]
pub(crate) struct BridgeSenders {
pub(crate) t1: T1Sender,
pub(crate) t2: T2Sender,
pub(crate) t3: T3Sender,
}
impl Plugin for EcsQuicTransportPlugin{
#[derive(Resource, Clone)]
pub(crate) struct TokioHandle(pub(crate) Handle);
/// Bring up the QUIC listener using the loaded `AppConfig` and transition to
/// `ServerState::Started`. Runs once via `OnEnter(ServerState::Starting)`.
fn start_quic_server(
config: Res<AppConfig>,
senders: Res<BridgeSenders>,
runtime: Res<TokioHandle>,
mut next: ResMut<NextState<ServerState>>,
) {
tracing::info!("entering ServerState::Starting — bringing up QUIC listener");
// `Endpoint::server` is sync but needs a tokio runtime context for
// `Handle::current()`; entering the runtime is enough — no async block
// required.
let _guard = runtime.0.enter();
let endpoint = bind_endpoint(&config.network).expect("failed to bind QUIC endpoint");
drop(_guard);
tracing::info!(local = ?endpoint.local_addr().ok(), "QUIC listener bound");
let s = senders.clone();
runtime.0.spawn(accept_loop(endpoint, s.t1, s.t2, s.t3));
next.set(ServerState::Started);
tracing::info!("ServerState::Started");
}
impl Plugin for EcsQuicTransportPlugin {
fn build(&self, app: &mut App) {
// Create the channels for multi-thread communication
let (t1_tx, t1_rx) =
mpsc::channel::<QuicMessage>(T1_CAPACITY);
let (t2_tx, t2_rx) =
mpsc::channel::<QuicMessage>(T2_CAPACITY);
let (t3_tx, t3_rx) =
mpsc::channel::<QuicMessage>(T3_CAPACITY);
// Three-tier bridge between the tokio-side QUIC accept loop and the
// ECS PreUpdate ingest system (in the `world` module).
let (t1_tx, t1_rx) = mpsc::channel::<QuicMessage>(T1_CAPACITY);
let (t2_tx, t2_rx) = mpsc::channel::<QuicMessage>(T2_CAPACITY);
let (t3_tx, t3_rx) = mpsc::channel::<T3Inbound>(T3_CAPACITY);
let quic_handle = std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
// Spawn a tokio runtime on a dedicated OS thread, ship its Handle back
// to the ECS, and keep the runtime alive for the lifetime of the app
// by parking on `pending()`.
let (handle_tx, handle_rx) = std::sync::mpsc::sync_channel::<Handle>(1);
std::thread::Builder::new()
.name("quic-runtime".to_string())
.spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.thread_name("quic-worker")
.build()
.expect("build tokio runtime");
handle_tx
.send(rt.handle().clone())
.expect("send tokio Handle to ECS");
rt.block_on(std::future::pending::<()>());
})
.expect("spawn quic-runtime thread");
rt.block_on(async move {
run_substrate_server(t1_tx, t2_tx, t3_tx).await;
});
});
let handle = handle_rx.recv().expect("receive tokio Handle");
app.insert_resource(BridgeReceivers {
t1: Mutex::new(t1_rx),
t2: Mutex::new(t2_rx),
t3: Mutex::new(t3_rx),
});
app.add_systems(PreUpdate, ingest_system);
// Bevy 0.18 split state machinery into its own plugin; under
// MinimalPlugins it isn't installed by default.
app.add_plugins(StatesPlugin)
.init_state::<ServerState>()
.insert_resource(TokioHandle(handle))
.insert_resource(BridgeSenders {
t1: T1Sender::new(t1_tx),
t2: T2Sender::new(t2_tx),
t3: T3Sender::new(t3_tx),
})
.insert_resource(BridgeReceivers {
t1: Mutex::new(t1_rx),
t2: Mutex::new(t2_rx),
t3: Mutex::new(t3_rx),
})
.add_systems(OnEnter(ServerState::Starting), start_quic_server);
}
}
}