use std::fmt::Debug;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use actix_web::{web, HttpRequest, HttpResponse};
use plaza::state_logic::StateLogic;
use plaza::{Agent, NoSnapshots, StateControllerBuilder, TickDriver};
use plaza_wire::frame::ProtocolVersion;
use plaza_wire::WireCodec;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::actix_ws::ActixWsPlazaSession;
use crate::conditioner::LinkSink;
use crate::manager::SessionOptions;
use super::{init_logging, Host};
pub const DEFAULT_WAKE_HZ: u32 = 120;
pub const DEFAULT_COMMAND_BUFFER: usize = 256;
pub struct SimWiring<Op, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
C: WireCodec,
{
pub session: Arc<ActixWsPlazaSession<Op, u64, C>>,
pub sim_clock: Arc<AtomicU64>,
}
impl<Op, C> SimWiring<Op, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
C: WireCodec,
{
pub fn link_sink(&self) -> LinkSink {
let session = self.session.clone();
Arc::new(move |profile| session.set_all_link_profiles(profile))
}
}
pub struct SimHost {
host: Host,
stepping: Stepping,
wake_hz: u32,
command_buffer: usize,
}
#[derive(Clone, Copy, Debug)]
enum Stepping {
Fixed(Duration),
Measured,
}
impl SimHost {
pub fn new(bind: impl Into<String>, step: Duration) -> Self {
Self {
host: Host::new(bind),
stepping: Stepping::Fixed(step),
wake_hz: DEFAULT_WAKE_HZ,
command_buffer: DEFAULT_COMMAND_BUFFER,
}
}
pub fn measured(bind: impl Into<String>, tick_hz: u32) -> Self {
Self {
host: Host::new(bind),
stepping: Stepping::Measured,
wake_hz: tick_hz,
command_buffer: DEFAULT_COMMAND_BUFFER,
}
}
pub fn serve_dir(mut self, dir: Option<String>) -> Self {
self.host = self.host.serve_dir(dir);
self
}
pub fn cache_bust(mut self, asset: impl Into<String>) -> Self {
self.host = self.host.cache_bust(asset);
self
}
pub fn announce(mut self, announce: bool) -> Self {
self.host = self.host.announce(announce);
self
}
pub fn wake_hz(mut self, hz: u32) -> Self {
self.wake_hz = hz;
self
}
pub fn command_buffer(mut self, size: usize) -> Self {
self.command_buffer = size;
self
}
pub async fn run<C, Op, S, L, F>(self, codec: C, protocol: u32, initial: S, logic_for: F) -> std::io::Result<()>
where
C: WireCodec,
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
S: Debug + Send + Sync + 'static,
L: StateLogic<Op, u64, S>,
F: FnOnce(&SimWiring<Op, C>) -> L,
{
init_logging();
let sim_clock = Arc::new(AtomicU64::new(0));
let session: Arc<ActixWsPlazaSession<Op, u64, C>> = ActixWsPlazaSession::with_options(
codec,
SessionOptions::with_protocol(ProtocolVersion(protocol)).clock({
let sim_clock = sim_clock.clone();
move || sim_clock.load(Ordering::Relaxed)
}),
);
let wiring = SimWiring {
session: session.clone(),
sim_clock,
};
let logic = logic_for(&wiring);
let (commands, controller) = StateControllerBuilder::new(Arc::new(logic), session.clone(), Arc::new(NoSnapshots), initial)
.snapshot_context_on_join(None)
.command_buffer(self.command_buffer)
.build();
tokio::spawn(controller.run());
match self.stepping {
Stepping::Fixed(step) => {
tracing::info!(wake_hz = self.wake_hz, step_ms = step.as_millis() as u64, "sim host starting");
tokio::spawn(TickDriver::from_hz(self.wake_hz).run_fixed(commands, step));
}
Stepping::Measured => {
tracing::info!(tick_hz = self.wake_hz, "sim host starting on measured time");
tokio::spawn(TickDriver::from_hz(self.wake_hz).run(commands));
}
}
let route_state = web::Data::new(RouteState {
session,
next_key: AtomicU64::new(1),
});
self
.host
.run(move |cfg| {
cfg.app_data(route_state.clone()).route("/ws", web::get().to(ws_route::<Op, C>));
})
.await
}
}
struct RouteState<Op, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
C: WireCodec,
{
session: Arc<ActixWsPlazaSession<Op, u64, C>>,
next_key: AtomicU64,
}
async fn ws_route<Op, C>(
req: HttpRequest,
stream: web::Payload,
state: web::Data<RouteState<Op, C>>,
) -> Result<HttpResponse, actix_web::Error>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
C: WireCodec,
{
let key = state.next_key.fetch_add(1, Ordering::Relaxed);
state.session.handle_connection(&req, stream, Agent::new_human(key))
}