1use crate::SimulationInitOptions;
2use crate::simulation::{Input, InputAge, State};
3use crate::snapshot_serdes::NewClientHeader;
4use crate::thread_comms::*;
5use crate::tick::TickInfo;
6use borger_plugin_sdk::diff_ser::DiffSerializer;
7use borger_plugin_sdk::multiplayer_tradeoff::{AnyTradeOff, Impl};
8use borger_plugin_sdk::primitive::usize32;
9use borger_plugin_sdk::traits::{ConstructCustomStruct, UntrackedState};
10use borger_plugin_sdk::{ClientKind, TickID};
11use log::debug;
12use std::collections::VecDeque;
13use std::rc::Rc;
14use std::sync::mpsc::channel as sync_unbounded_channel;
15use std::time::Duration;
16use web_time::Instant;
17
18#[cfg(feature = "server")]
19use {
20 crate::tick::UnrollbackableNetEvent,
21 std::collections::HashMap,
22 std::sync::mpsc::{Receiver as SyncReceiver, Sender as SyncSender},
23 tokio::sync::mpsc::UnboundedSender as AsyncSender,
24};
25
26#[cfg(feature = "client")]
27use {
28 crate::presentation_collect::PresentationCollectContext, crate::snapshot_serdes, atomicbox::AtomicOptionBox,
29 std::sync::Arc,
30};
31
32#[cfg(not(feature = "singlethreaded"))]
33use wasm_thread as thread;
34
35#[cfg(feature = "client")]
36mod client;
37mod seek;
38#[cfg(feature = "server")]
39mod server;
40
41#[cfg(feature = "server")]
43const TRACE_TICK_ADVANCEMENT: bool = false;
44#[cfg(feature = "client")]
45const TRACE_TICK_ADVANCEMENT: bool = false;
46
47pub struct SimControllerExternals {
50 pub internals: SimThreading,
51
52 #[cfg(feature = "server")]
53 pub new_connection_sender: SyncSender<AsyncSender<SimToClientCommand>>,
54 #[cfg(feature = "client")]
55 pub comms: PresentationToSimChannel,
56}
57
58#[cfg(not(feature = "singlethreaded"))]
62struct SimMoveAcrossThreads {
63 o: SimulationInitOptions,
64
65 #[cfg(feature = "client")]
66 new_client_snapshot: Vec<u8>,
67
68 #[cfg(feature = "server")]
69 new_connection_receiver: SyncReceiver<AsyncSender<SimToClientCommand>>,
70 #[cfg(feature = "client")]
71 comms: SimToPresentationChannel,
72}
73
74pub enum SimThreading {
75 #[cfg(not(feature = "singlethreaded"))]
76 Multithreaded(thread::JoinHandle<()>),
77
78 #[cfg(any(feature = "singlethreaded", feature = "session_replay"))]
79 #[allow(private_interfaces)]
80 Singlethreaded(SimControllerInternals),
81}
82
83struct SimControllerInternals {
85 ctx: GameContext<Impl>,
86 o: SimulationInitOptions,
87
88 #[cfg(feature = "server")]
93 input_history: HashMap<usize32, InternalInputHistory>,
94 #[cfg(feature = "client")]
95 input_history: InternalInputHistory,
96
97 #[cfg(feature = "server")]
98 new_connection_receiver: SyncReceiver<AsyncSender<SimToClientCommand>>,
99 #[cfg(feature = "server")]
100 comms: HashMap<usize32, SimToClientChannel>,
101 #[cfg(feature = "client")]
102 comms: SimToPresentationChannel,
103
104 #[cfg(feature = "server")]
105 server_events: VecDeque<UnrollbackableNetEvent>,
106
107 #[cfg(feature = "client")]
108 local_client_id: usize32,
109 #[cfg(feature = "client")]
110 calibration_samples: VecDeque<i16>,
111 #[cfg(feature = "client")]
112 initial_calibration: bool,
113}
114
115pub struct GameContext<TradeOff: AnyTradeOff> {
116 pub state: State,
117 pub tick: TickInfo,
118 pub diff: DiffSerializer<TradeOff>,
119}
120
121#[derive(Default, Debug)]
122struct InternalInputHistory {
123 entries: VecDeque<InternalInputEntry>,
133
134 #[cfg(feature = "server")]
139 timed_out_ticks: TickID,
140
141 #[cfg(feature = "server")]
144 latest_received: Input,
145
146 #[cfg(feature = "server")]
149 is_timed_out: bool,
150}
151
152#[derive(Default, Debug, Clone)]
153struct InternalInputEntry {
154 input: Input,
155
156 #[cfg(feature = "server")]
157 age: InputAge,
158
159 #[cfg(feature = "server")]
179 ping: Option<i16>, #[cfg(feature = "client")]
181 ping: bool, }
183
184#[cfg(feature = "client")]
185struct ClientComms {
186 sim_comms: SimToPresentationChannel,
187 presentation_comms: PresentationToSimChannel,
188}
189
190#[cfg(feature = "client")]
191fn make_client_comms() -> ClientComms {
192 let sim_out = Arc::new(AtomicOptionBox::none());
193 let (to_presentation, from_sim) = sync_unbounded_channel();
194 let (to_sim, from_presentation) = sync_unbounded_channel();
195
196 let sim_comms = SimToPresentationChannel {
197 to_presentation,
198 from_presentation,
199 sim_out: sim_out.clone(),
200 };
201 let presentation_comms = PresentationToSimChannel {
202 to_sim,
203 from_sim,
204 sim_out,
205 };
206
207 ClientComms {
208 sim_comms,
209 presentation_comms,
210 }
211}
212
213struct MakeContext {
214 ctx: GameContext<Impl>,
215
216 #[cfg(feature = "client")]
217 new_client_header: NewClientHeader,
218}
219
220fn make_context(
221 o: &SimulationInitOptions,
222 #[cfg(feature = "client")] new_client_snapshot: Vec<u8>,
223) -> MakeContext {
224 let mut state = State::construct(&Rc::default(), ClientKind::NA);
225 o.init_static_level_geom.map(|init_level| {
226 init_level(&mut state);
227 state.reset_untracked(); });
229
230 #[cfg(feature = "client")]
231 let new_client_header = snapshot_serdes::des_new_client(&mut state, new_client_snapshot).unwrap();
232
233 #[cfg(feature = "server")]
234 let tick_info = TickInfo::new(o.sim_dt, 0, 0);
235 #[cfg(feature = "client")]
236 let tick_info = TickInfo::new(
237 o.sim_dt,
238 new_client_header.tick_id_snapshot,
239 new_client_header.fast_forward_ticks,
240 );
241
242 MakeContext {
243 ctx: GameContext {
244 state,
245 tick: tick_info,
246 diff: DiffSerializer::default(),
247 },
248
249 #[cfg(feature = "client")]
250 new_client_header,
251 }
252}
253
254#[cfg(not(feature = "singlethreaded"))]
255pub fn init_multithreaded(
256 o: SimulationInitOptions,
257 #[cfg(feature = "client")] new_client_snapshot: Vec<u8>,
258) -> SimControllerExternals {
259 #[cfg(feature = "server")]
260 let (new_connection_sender, new_connection_receiver) = sync_unbounded_channel();
261
262 #[cfg(feature = "client")]
263 let ClientComms {
264 sim_comms,
265 presentation_comms,
266 } = make_client_comms();
267
268 let move_me = SimMoveAcrossThreads {
269 o,
270
271 #[cfg(feature = "client")]
272 new_client_snapshot,
273
274 #[cfg(feature = "server")]
275 new_connection_receiver,
276 #[cfg(feature = "client")]
277 comms: sim_comms,
278 };
279
280 let thread = thread::spawn(move || loop_multithreaded(move_me));
281
282 SimControllerExternals {
283 internals: SimThreading::Multithreaded(thread),
284
285 #[cfg(feature = "server")]
286 new_connection_sender,
287 #[cfg(feature = "client")]
288 comms: presentation_comms,
289 }
290}
291
292#[cfg(not(feature = "singlethreaded"))]
293fn loop_multithreaded(moved_data: SimMoveAcrossThreads) {
294 let MakeContext {
295 ctx,
296 #[cfg(feature = "client")]
297 new_client_header,
298 } = make_context(
299 &moved_data.o,
300 #[cfg(feature = "client")]
301 moved_data.new_client_snapshot,
302 );
303
304 let mut sim = SimControllerInternals {
305 ctx,
306 o: moved_data.o,
307
308 #[cfg(feature = "server")]
309 input_history: HashMap::new(),
310 #[cfg(feature = "client")]
311 input_history: InternalInputHistory::default(),
312
313 #[cfg(feature = "server")]
314 new_connection_receiver: moved_data.new_connection_receiver,
315 #[cfg(feature = "server")]
316 comms: HashMap::new(),
317 #[cfg(feature = "client")]
318 comms: moved_data.comms,
319
320 #[cfg(feature = "server")]
321 server_events: VecDeque::from([UnrollbackableNetEvent::ServerStart]),
322
323 #[cfg(feature = "client")]
324 local_client_id: new_client_header.client_id,
325 #[cfg(feature = "client")]
326 calibration_samples: VecDeque::new(),
327 #[cfg(feature = "client")]
328 initial_calibration: true,
329 };
330
331 #[cfg(feature = "client")]
332 sim.initial_fast_forward(new_client_header.fast_forward_ticks);
333
334 loop {
335 if sim.scheduled_tick(
336 #[cfg(feature = "session_replay")]
337 false,
338 ) == None
339 {
340 break;
341 }
342
343 let next_tick_time = sim.ctx.tick.get_now();
344 let now = Instant::now();
345
346 if next_tick_time > now {
347 thread::sleep(next_tick_time - now);
352 } else if TRACE_TICK_ADVANCEMENT && now > next_tick_time {
353 debug!("simulation tick hiccuped");
357 }
358 }
359}
360
361#[cfg(any(feature = "singlethreaded", feature = "session_replay"))]
362pub fn init_singlethreaded(o: SimulationInitOptions, new_client_snapshot: Vec<u8>) -> SimControllerExternals {
363 let ClientComms {
364 sim_comms,
365 presentation_comms,
366 } = make_client_comms();
367
368 let MakeContext {
369 ctx,
370 new_client_header,
371 } = make_context(&o, new_client_snapshot);
372
373 let mut internals = SimControllerInternals {
374 ctx,
375 o,
376 input_history: InternalInputHistory::default(),
377 comms: sim_comms,
378 local_client_id: new_client_header.client_id,
379 calibration_samples: VecDeque::new(),
380 initial_calibration: true,
381 };
382
383 internals.initial_fast_forward(new_client_header.fast_forward_ticks);
384
385 SimControllerExternals {
386 internals: SimThreading::Singlethreaded(internals),
387 comms: presentation_comms,
388 }
389}
390
391#[cfg(feature = "singlethreaded")]
392impl SimControllerExternals {
393 pub fn loop_singlethreaded(&mut self) {
394 let SimThreading::Singlethreaded(sim) = &mut self.internals;
395
396 let tick_id_accumulator = sim.ctx.tick.get_tick_at(Instant::now());
397 while sim.ctx.tick.id_cur < tick_id_accumulator {
398 if sim.scheduled_tick(
399 #[cfg(feature = "session_replay")]
400 false,
401 ) == None
402 {
403 break;
404 }
405 }
406 }
407}
408
409#[cfg(feature = "session_replay")]
410pub fn replay_session(o: SimulationInitOptions, actions: Vec<SessionReplayAction>) -> Result<(), ()> {
411 let mut actions = actions.into_iter();
412 let Some(SessionReplayAction::Init(new_client_snapshot)) = actions.next() else {
413 return Err(());
414 };
415
416 let mut externals = init_singlethreaded(o, new_client_snapshot);
417 #[cfg_attr(feature = "singlethreaded", allow(irrefutable_let_patterns))]
418 let SimThreading::Singlethreaded(sim) = &mut externals.internals else {
419 unreachable!()
420 };
421
422 for action in actions {
424 match action {
425 SessionReplayAction::ScheduledTick => sim.scheduled_tick(true).unwrap(),
426 SessionReplayAction::ReceiveComm(msg) => externals.comms.to_sim.send(msg).map_err(|_| ())?,
427 SessionReplayAction::Init(_) => return Err(()),
428 };
429
430 while let Ok(_) = externals.comms.from_sim.try_recv() {}
432 }
433
434 sim.scheduled_tick(true).unwrap();
437
438 Ok(())
439}
440
441impl SimControllerInternals {
442 #[must_use]
443 fn scheduled_tick(&mut self, #[cfg(feature = "session_replay")] is_replay: bool) -> Option<()> {
445 self.ctx.tick.id_target += 1;
446 if TRACE_TICK_ADVANCEMENT {
447 debug!("begin scheduled tick @{:?}", self.ctx.tick);
448 }
449
450 self.scheduled_tick_impl()?;
476
477 if TRACE_TICK_ADVANCEMENT {
478 debug!("end scheduled tick");
479 }
480
481 #[cfg(feature = "session_replay")]
482 if !is_replay {
483 self.comms
484 .to_presentation
485 .send(SimToPresentationCommand::SessionReplayAction(
486 SessionReplayAction::ScheduledTick,
487 ))
488 .unwrap();
489 }
490
491 Some(())
492 }
493}
494
495impl InternalInputHistory {
496 fn generate_bogus_inputs(&mut self, amount: TickID) {
506 self.entries
507 .extend((0..amount + 1).map(|_| InternalInputEntry::default()));
508 }
509}