1pub mod combinator;
6mod combine;
7pub mod connection;
8pub mod err;
9pub mod session;
10
11use crate::connection::Connection;
12use crate::err::HostLogicError;
13use crate::session::GameSession;
14use app_version::Version;
15use flood_rs::{Deserialize, Serialize};
16use freelist_rs::FreeList;
17use log::trace;
18use monotonic_time_rs::Millis;
19use nimble_protocol::host_to_client::PongInfo;
20use nimble_protocol::prelude::{ClientToHostCommands, HostToClientCommands};
21use nimble_protocol::NIMBLE_PROTOCOL_VERSION;
22use nimble_step::Step;
23use std::collections::HashMap;
24use std::fmt::{Debug, Display};
25use tick_id::TickId;
26
27pub trait GameStateProvider {
28 fn state(&self, tick_id: TickId) -> (TickId, Vec<u8>);
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub enum Phase {
33 WaitingForValidConnectRequest,
34 Connected,
35}
36
37pub const NIMBLE_VERSION: Version = Version::new(
38 NIMBLE_PROTOCOL_VERSION.major,
39 NIMBLE_PROTOCOL_VERSION.minor,
40 NIMBLE_PROTOCOL_VERSION.patch,
41);
42
43#[derive(Debug, Copy, Clone)]
47pub struct HostConnectionId(pub u8);
48
49pub struct HostLogic<StepT: Clone + Eq + Debug + Deserialize + Serialize + Display> {
53 #[allow(unused)]
54 connections: HashMap<u8, Connection<StepT>>,
55 session: GameSession<StepT>,
56 free_list: FreeList<u8>,
57 deterministic_simulation_version: Version,
58}
59
60impl<StepT: Clone + Eq + Debug + Deserialize + Serialize + Display> HostLogic<StepT> {
61 #[must_use]
72 pub fn new(tick_id: TickId, deterministic_simulation_version: Version) -> Self {
73 Self {
74 connections: HashMap::new(),
75 session: GameSession::new(tick_id),
76 free_list: FreeList::<u8>::new(0xff),
77 deterministic_simulation_version,
78 }
79 }
80
81 pub fn create_connection(&mut self) -> Option<HostConnectionId> {
89 let new_connection_id = self.free_list.allocate();
90 if let Some(id) = new_connection_id {
91 self.connections.insert(id, Connection::new());
92 Some(HostConnectionId(id))
93 } else {
94 None
95 }
96 }
97
98 #[must_use]
108 pub fn get(&self, connection_id: HostConnectionId) -> Option<&Connection<StepT>> {
109 self.connections.get(&connection_id.0)
110 }
111
112 pub fn destroy_connection(
126 &mut self,
127 connection_id: HostConnectionId,
128 ) -> Result<(), HostLogicError> {
129 self.free_list
130 .free(connection_id.0)
131 .map_err(|err| HostLogicError::FreeListError {
132 connection_id,
133 message: err,
134 })?;
135
136 if self.connections.remove(&connection_id.0).is_some() {
137 Ok(())
138 } else {
139 Err(HostLogicError::UnknownConnectionId(connection_id))
140 }
141 }
142
143 #[must_use]
149 pub const fn session(&self) -> &GameSession<StepT> {
150 &self.session
151 }
152
153 pub fn post_update(&mut self) {
157 self.session.combinator.produce_authoritative_steps();
158 }
159
160 pub fn update(
180 &mut self,
181 connection_id: HostConnectionId,
182 now: Millis,
183 request: &ClientToHostCommands<StepT>,
184 state_provider: &impl GameStateProvider,
185 ) -> Result<Vec<HostToClientCommands<Step<StepT>>>, HostLogicError> {
186 trace!("host_logic: receive: \n{request}");
187 if let Some(ref mut connection) = self.connections.get_mut(&connection_id.0) {
188 match &connection.phase {
189 Phase::Connected => {
190 match request {
191 ClientToHostCommands::JoinGameType(join_game_request) => Ok(vec![
192 connection.on_join(&mut self.session, join_game_request)?,
193 ]),
194 ClientToHostCommands::Steps(add_steps_request) => {
195 Ok(vec![connection.on_steps(
196 &mut self.session.combinator,
197 add_steps_request,
198 )?])
199 }
200 ClientToHostCommands::DownloadGameState(download_game_state_request) => {
201 Ok(connection.on_download(
202 self.session.combinator.tick_id_to_produce(),
203 now,
204 download_game_state_request,
205 state_provider,
206 )?)
207 }
208 ClientToHostCommands::BlobStreamChannel(blob_stream_command) => {
209 connection.on_blob_stream(now, blob_stream_command)
210 }
211 ClientToHostCommands::ConnectType(connect_request) => {
212 trace!("notice: got connection request, even though we are connected, but will send response anyway");
213 connection
214 .on_connect(connect_request, &self.deterministic_simulation_version)
215 }
216 ClientToHostCommands::Ping(ping_info) => Ok(Self::on_ping(*ping_info)),
217 }
218 }
219 Phase::WaitingForValidConnectRequest => match request {
220 ClientToHostCommands::ConnectType(connect_request) => connection
221 .on_connect(connect_request, &self.deterministic_simulation_version),
222 _ => Err(HostLogicError::NeedConnectRequestFirst),
223 },
224 }
225 } else {
226 Err(HostLogicError::UnknownConnectionId(connection_id))
227 }
228 }
229
230 fn on_ping(lower_millis: u16) -> Vec<HostToClientCommands<Step<StepT>>> {
231 vec![HostToClientCommands::Pong(PongInfo { lower_millis })]
232 }
233}