nimble_host/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/nimble-rust/nimble
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5
6/*!
7# Nimble Host Crate
8
9The `nimble-host` crate provides the core functionality for managing game sessions and connections in the Nimble multiplayer framework. It handles host logic, connection management, and communication between clients and the host.
10
11## Features
12
13- **Connection Management**: Create, manage, and destroy client connections.
14- **Host Logic**: Integrates with `nimble_host_logic` to manage game state and handle client commands.
15- **Datagram Handling**: Efficiently processes incoming and outgoing datagrams with chunking support.
16- **Serialization**: Supports serialization and deserialization of commands using `flood_rs`.
17
18*/
19
20pub mod err;
21pub mod prelude;
22
23use crate::err::HostError;
24use datagram_chunker::DatagramChunker;
25use flood_rs::prelude::OutOctetStream;
26use flood_rs::{Deserialize, Serialize};
27use hexify::format_hex;
28use log::{debug, trace};
29use monotonic_time_rs::Millis;
30use nimble_host_logic::{
31    connection::Connection, session::GameSession, GameStateProvider, HostLogic,
32};
33use nimble_layer::NimbleLayer;
34use nimble_protocol::prelude::ClientToHostCommands;
35use std::collections::HashMap;
36use std::fmt::{Debug, Display};
37use tick_id::TickId;
38
39/// Represents a connection managed by the host.
40#[derive(Default, Debug)]
41pub struct HostConnection {
42    layer: NimbleLayer,
43}
44
45impl HostConnection {
46    #[must_use]
47    pub fn new() -> Self {
48        Self {
49            layer: NimbleLayer::default(),
50        }
51    }
52}
53
54/// Unique identifier for a host connection.
55pub struct ConnectionId(u8);
56
57impl ConnectionId {
58    /// Retrieves the inner u8 value of the `ConnectionId`.
59    #[must_use]
60    pub const fn inner(&self) -> u8 {
61        self.0
62    }
63}
64
65/// The main host structure managing game logic and client connections.
66///
67/// Host handles the game session, processes client commands, and manages the state of each connection.
68///
69/// # Type Parameters
70///
71/// - `StepT`: The type representing a step in the game logic. Must implement Clone, Debug, Eq, Deserialize, Serialize, and Display.
72pub struct Host<StepT: Clone + Debug + Eq + Deserialize + Serialize + Display> {
73    logic: HostLogic<StepT>,
74    connections: HashMap<u8, HostConnection>,
75}
76
77impl<StepT: Clone + Deserialize + Serialize + Eq + Debug + Display> Host<StepT> {
78    /// Creates a new Host instance with the specified application version and initial tick ID.
79    ///
80    /// # Arguments
81    ///
82    /// * `app_version` - The version of the application.
83    /// * `tick_id` - The initial tick identifier.
84    #[must_use]
85    pub fn new(app_version: app_version::Version, tick_id: TickId) -> Self {
86        Self {
87            logic: HostLogic::<StepT>::new(tick_id, app_version),
88            connections: HashMap::new(),
89        }
90    }
91
92    /// Returns a reference to the internal `HostLogic` for debugging purposes.
93    #[must_use]
94    pub const fn debug_logic(&self) -> &HostLogic<StepT> {
95        &self.logic
96    }
97
98    /// Retrieves a specific connection's logic by its connection ID for debugging purposes.
99    #[must_use]
100    pub fn debug_get_logic(
101        &self,
102        connection_id: nimble_host_logic::HostConnectionId,
103    ) -> Option<&Connection<StepT>> {
104        self.logic.get(connection_id)
105    }
106
107    /// Returns a reference to the current game session.
108    #[must_use]
109    pub const fn session(&self) -> &GameSession<StepT> {
110        self.logic.session()
111    }
112
113    /// Updates the host state based on incoming datagrams from a client.
114    ///
115    /// Processes the datagram, updates game logic, and prepares outgoing datagrams to be sent back to the client.
116    ///
117    /// # Arguments
118    ///
119    /// * `connection_id` - The ID of the connection sending the datagram.
120    /// * `now` - The current time in milliseconds.
121    /// * `datagram` - The incoming datagram data.
122    /// * `state_provider` - A reference to an implementation providing game state if needed.
123    ///
124    /// # Returns
125    ///
126    /// A `Result` containing a vector of outgoing datagrams or a `HostError` on failure.
127    ///
128    /// # Errors
129    ///
130    /// `HostError` TODO:
131    pub fn update(
132        &mut self,
133        connection_id: nimble_host_logic::HostConnectionId,
134        now: Millis,
135        datagram: &[u8],
136        state_provider: &impl GameStateProvider,
137    ) -> Result<Vec<Vec<u8>>, HostError> {
138        trace!(
139            "time:{now}: host received for connection:{} payload:\n{}",
140            connection_id.0,
141            format_hex(datagram)
142        );
143
144        let found_connection = self
145            .connections
146            .get_mut(&connection_id.0)
147            .ok_or(HostError::ConnectionNotFound(connection_id.0))?;
148
149        let datagram_without_layer = found_connection.layer.receive(datagram)?;
150
151        let deserialized_commands = datagram_chunker::deserialize_datagram::<
152            ClientToHostCommands<StepT>,
153        >(datagram_without_layer)?;
154
155        let mut all_commands_to_send = Vec::new();
156        for deserialized_command in deserialized_commands {
157            let commands_to_send =
158                self.logic
159                    .update(connection_id, now, &deserialized_command, state_provider)?;
160
161            all_commands_to_send.extend(commands_to_send);
162        }
163
164        self.logic.post_update();
165
166        let mut datagram_chunker = DatagramChunker::new(1024);
167        for cmd in all_commands_to_send {
168            let mut out_stream = OutOctetStream::new();
169            cmd.serialize(&mut out_stream)?;
170            datagram_chunker.push(out_stream.octets_ref())?;
171        }
172
173        let outgoing_datagrams = datagram_chunker.finalize();
174
175        let out_datagrams = found_connection.layer.send(&outgoing_datagrams)?;
176
177        for (index, datagram) in out_datagrams.iter().enumerate() {
178            trace!(
179                "host sending index {} payload:\n{}",
180                index,
181                format_hex(datagram)
182            );
183        }
184
185        Ok(out_datagrams)
186    }
187
188    /// Retrieves a reference to a `HostConnection` by its connection ID.
189    ///
190    /// # Arguments
191    ///
192    /// * `connection_id` - The ID of the connection to retrieve.
193    #[must_use]
194    pub fn get(
195        &self,
196        connection_id: nimble_host_logic::HostConnectionId,
197    ) -> Option<&HostConnection> {
198        self.connections.get(&connection_id.0)
199    }
200
201    /// Creates a new connection and adds it to the host.
202    ///
203    /// # Returns
204    ///
205    /// An `Option` containing the new `HostConnectionId` if successful, or `None` if the connection could not be created.
206    pub fn create_connection(&mut self) -> Option<nimble_host_logic::HostConnectionId> {
207        if let Some(connection_id) = self.logic.create_connection() {
208            self.connections
209                .insert(connection_id.0, HostConnection::new());
210            debug!("Created connection {:?}", connection_id);
211            Some(connection_id)
212        } else {
213            None
214        }
215    }
216
217    /// Destroys an existing connection and removes it from the host.
218    ///
219    /// # Arguments
220    ///
221    /// * `connection_id` - The ID of the connection to destroy.
222    ///
223    /// # Errors
224    ///
225    /// Returns a `HostError` if the connection could not be found or destroyed.
226    pub fn destroy_connection(
227        &mut self,
228        connection_id: nimble_host_logic::HostConnectionId,
229    ) -> Result<(), HostError> {
230        debug!("destroying connection {:?}", connection_id);
231        self.connections.remove(&connection_id.0);
232        self.logic.destroy_connection(connection_id)?;
233        Ok(())
234    }
235}