Add config and basic architecture for QUIC

This commit is contained in:
Valère Plantevin
2026-05-04 16:13:40 -04:00
parent 42ab81899c
commit 4ec5b98df4
15 changed files with 246 additions and 2 deletions

49
substrate/src/config.rs Normal file
View File

@@ -0,0 +1,49 @@
use bevy::prelude::Resource;
use figment::Figment;
use figment::providers::{Env, Format, Serialized, Toml};
use serde::{Deserialize, Serialize};
#[derive(Debug, Resource, Serialize, Deserialize)]
pub struct AppConfig {
pub network: QuicConfig,
pub simulation: SimulationConfig,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SimulationConfig {
pub tick_rate_hz: u32,
pub max_entities: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QuicConfig {
pub server_port: u16,
pub server_interface: String,
pub server_cert: String,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
network : QuicConfig {
server_port: 9000,
server_interface: "0.0.0.0".to_string(),
server_cert: "cert.pem".to_string(),
},
simulation: SimulationConfig {
tick_rate_hz: 60,
max_entities: 10000,
},
}
}
}
impl AppConfig {
pub fn load(config_file: &str) -> Result<Self, figment::Error> {
Figment::new()
.merge(Serialized::defaults(Self::default())) // compiled-in defaults
.merge(Toml::file(config_file)) // config file
.merge(Env::prefixed("APP_")) // env overrides, e.g. APP_NETWORK__PORT=9000
.extract()
}
}

View File

@@ -1,3 +1,17 @@
mod transport;
mod config;
use std::sync::Arc;
use bevy::prelude::*;
use crate::config::AppConfig;
fn main() {
println!("Hello, world!");
}
let config = AppConfig::load("config.toml").expect("Failed to load config");
println!("{:?}", config);
App::new()
.insert_resource(config)
.add_plugins(MinimalPlugins)
.add_plugins(transport::ecs::EcsQuicTransportPlugin{})
.run();
}

View File

@@ -0,0 +1,69 @@
use std::sync::{Arc, Mutex};
use bevy::prelude::*;
use tokio::sync::mpsc;
use crate::config::QuicConfig;
use crate::transport::QuicMessage;
use crate::transport::server::run_substrate_server;
const T1_CAPACITY: usize = 1024;
const T2_CAPACITY: usize = 512;
const T3_CAPACITY: usize = 256;
pub struct EcsQuicTransportPlugin{}
#[derive(Resource)]
struct BridgeReceivers {
t1: Mutex<mpsc::Receiver<QuicMessage>>,
t2: Mutex<mpsc::Receiver<QuicMessage>>,
t3: Mutex<mpsc::Receiver<QuicMessage>>,
}
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() { /* ... */ }
}
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);
let quic_handle = std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
rt.block_on(async move {
run_substrate_server(t1_tx, t2_tx, t3_tx).await;
});
});
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);
}
}

View File

@@ -0,0 +1,11 @@
pub mod ecs;
mod server;
#[derive(Debug, Clone, Default, Copy, PartialEq)]
pub struct QuicMessage{
pub device_id: uuid::Uuid,
pub data_stream_id: u16,
pub raw_value: f64,
pub timestamp_us: u64,
pub sequence_number: u32,
}

View File

@@ -0,0 +1,8 @@
use tokio::sync::mpsc::Sender;
use crate::transport::QuicMessage;
pub async fn run_substrate_server(t1_tx: Sender<QuicMessage>,
t2_tx: Sender<QuicMessage>,
t3_tx: Sender<QuicMessage>) {
}