Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 committed Aug 10, 2023
1 parent 08e2328 commit 514d4df
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 7 deletions.
26 changes: 26 additions & 0 deletions crates/connector/src/game/greceiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ impl GameProcessor {
ToGame::Leave => {
self.process_leave(message.meta).await;
}
ToGame::Start => {
self.process_start().await;
}
ToGame::Initialized => {
self.process_initialized(message.meta).await;
}
}

if self.state.is_empty().await {
Expand Down Expand Up @@ -199,6 +205,15 @@ impl GameProcessor {
self.send(&FromGame::JoinError(JoinError::GameFull), meta.source)
.await;
}
JoinErrorInner::GameNotOpened => {
warn!(
"Player {:?} could not join game on port {} because the game is no longer opened.",
meta.source, self.port
);

self.send(&FromGame::JoinError(JoinError::GameNotOpened), meta.source)
.await;
}
}
}
}
Expand Down Expand Up @@ -233,6 +248,17 @@ impl GameProcessor {
self.send_all(&FromGame::PeerLeft(id), None).await;
}

async fn process_start(&mut self) {
self.state.start().await;
self.send_all(&FromGame::Starting, None).await;
}

async fn process_initialized(&mut self, meta: MessageMeta) {
if self.state.mark_initialized(meta.source).await {
self.send_all(&FromGame::Started, None).await;
}
}

/// Send a reliable message to all players of the game.
///
/// # Arguments
Expand Down
81 changes: 79 additions & 2 deletions crates/connector/src/game/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ impl GameState {
self.inner.write().await.remove(addr)
}

/// If the game is in state `Open`, change it state to `Starting`.
pub(super) async fn start(&mut self) {
self.inner.write().await.start()
}

/// Marks a player as initialized. Returns true if the game was just
/// started.
pub(super) async fn mark_initialized(&mut self, addr: SocketAddr) -> bool {
self.inner.write().await.mark_initialized(addr)
}

/// Constructs and returns package targets which includes all or all but
/// one players connected to the game. It returns None if there is no
/// matching target.
Expand All @@ -52,13 +63,15 @@ impl GameState {

struct GameStateInner {
available_ids: AvailableIds,
state: GameStateX,
players: AHashMap<SocketAddr, Player>,
}

impl GameStateInner {
fn new(max_players: u8) -> Self {
Self {
available_ids: AvailableIds::new(max_players),
state: GameStateX::Open,
players: AHashMap::new(),
}
}
Expand All @@ -72,11 +85,15 @@ impl GameStateInner {
}

fn add(&mut self, addr: SocketAddr) -> Result<u8, JoinError> {
if self.state != GameStateX::Open {
return Err(JoinError::GameNotOpened);
}

match self.players.entry(addr) {
Entry::Occupied(_) => Err(JoinError::AlreadyJoined),
Entry::Vacant(vacant) => match self.available_ids.lease() {
Some(id) => {
vacant.insert(Player { id });
vacant.insert(Player::new(id));
Ok(id)
}
None => Err(JoinError::GameFull),
Expand All @@ -94,6 +111,27 @@ impl GameStateInner {
}
}

fn start(&mut self) {
if self.state == GameStateX::Open {
self.state = GameStateX::Starting;
}
}

fn mark_initialized(&mut self, addr: SocketAddr) -> bool {
let prev = self.state;

if matches!(self.state, GameStateX::Starting) {
if let Some(player) = self.players.get_mut(&addr) {
player.initialized = true;
}
if self.players.values().all(|p| p.initialized) {
self.state = GameStateX::Started;
}
}

self.state == GameStateX::Started && self.state != prev
}

fn targets(&self, exclude: Option<SocketAddr>) -> Option<Targets<'static>> {
let len = if exclude.map_or(false, |e| self.players.contains_key(&e)) {
self.players.len() - 1
Expand Down Expand Up @@ -153,16 +191,36 @@ impl AvailableIds {
}
}

#[derive(Debug, Error)]
#[derive(Debug, Error, PartialEq)]
pub(super) enum JoinError {
#[error("The player has already joined the game.")]
AlreadyJoined,
#[error("The game is full.")]
GameFull,
#[error("The game is no longer opened.")]
GameNotOpened,
}

struct Player {
id: u8,
initialized: bool,
}

impl Player {
fn new(id: u8) -> Self {
Self {
id,
initialized: false,
}
}
}

// TODO better name
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum GameStateX {
Open,
Starting,
Started,
}

#[cfg(test)]
Expand Down Expand Up @@ -221,6 +279,25 @@ mod tests {
}));
}

#[test]
fn test_transitions() {
let client_a: SocketAddr = "127.0.0.1:8081".parse().unwrap();
let client_b: SocketAddr = "127.0.0.1:8082".parse().unwrap();
let client_c: SocketAddr = "127.0.0.1:8083".parse().unwrap();

let mut state = GameStateInner::new(3);

state.add(client_a).unwrap();
state.add(client_b).unwrap();

state.start();

assert_eq!(state.add(client_c), Err(JoinError::GameNotOpened));

assert!(!state.mark_initialized(client_b));
assert!(state.mark_initialized(client_a));
}

#[test]
fn test_targets() {
let mut state = GameStateInner::new(8);
Expand Down
2 changes: 1 addition & 1 deletion crates/map/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl MapHash {
}

/// Constructs the map hash from a hexadecimal string.
pub(crate) fn from_hex(hex: &str) -> Result<Self, HexError> {
pub fn from_hex(hex: &str) -> Result<Self, HexError> {
if hex.len() != 64 {
return Err(HexError::InvalidLenError);
}
Expand Down
39 changes: 36 additions & 3 deletions crates/menu/src/multiplayer/joined.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
use bevy::prelude::*;
use de_core::state::AppState;
use de_multiplayer::ShutdownMultiplayerEvent;
use de_core::{
assets::asset_path,
gconfig::{GameConfig, LocalPlayers},
player::Player,
state::AppState,
};
use de_lobby_model::GameMap;
use de_map::hash::MapHash;
use de_multiplayer::{GameStartingEvent, ShutdownMultiplayerEvent};

use super::MultiplayerState;

pub(crate) struct JoinedGamePlugin;

impl Plugin for JoinedGamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnExit(MultiplayerState::GameJoined), cleanup);
app.add_systems(OnExit(MultiplayerState::GameJoined), cleanup)
.add_systems(
Update,
handle_starting
.run_if(in_state(MultiplayerState::GameJoined))
.run_if(on_event::<GameStartingEvent>()),
);
}
}

Expand All @@ -17,3 +30,23 @@ fn cleanup(state: Res<State<AppState>>, mut shutdown: EventWriter<ShutdownMultip
shutdown.send(ShutdownMultiplayerEvent);
}
}

fn handle_starting(mut commands: Commands, mut next_state: ResMut<NextState<AppState>>) {
// TODO use real conf
let map = GameMap::new("a".to_owned(), "b".to_owned());
let Ok(hash) = MapHash::from_hex(map.hash()) else {
// TODO move to a different state
// TODO show a toast
return;
};
let map_path = hash.construct_path(asset_path("maps"));

commands.insert_resource(GameConfig::new(
map_path,
// TODO use real game conf
Player::Player4,
// TODO use real player
LocalPlayers::new(Player::Player1),
));
next_state.set(AppState::InGame);
}
20 changes: 20 additions & 0 deletions crates/multiplayer/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_event::<GameOpenedEvent>()
.add_event::<GameJoinedEvent>()
.add_event::<GameStartingEvent>()
.add_systems(OnEnter(NetState::Connected), (setup, open_or_join))
.add_systems(OnEnter(NetState::None), cleanup)
.add_systems(
Expand Down Expand Up @@ -57,6 +58,10 @@ impl GameJoinedEvent {
}
}

/// Joined game starting has just initiated.
#[derive(Event)]
pub struct GameStartingEvent;

#[derive(Resource)]
pub(crate) struct Players {
local: Option<Player>,
Expand Down Expand Up @@ -133,6 +138,7 @@ fn process_from_game(
mut fatals: EventWriter<FatalErrorEvent>,
state: Res<State<NetState>>,
mut joined_events: EventWriter<GameJoinedEvent>,
mut starting_events: EventWriter<GameStartingEvent>,
mut next_state: ResMut<NextState<NetState>>,
) {
for event in inputs.iter() {
Expand Down Expand Up @@ -162,6 +168,11 @@ fn process_from_game(
JoinError::GameFull => {
fatals.send(FatalErrorEvent::new("Game is full, cannot join."));
}
JoinError::GameNotOpened => {
fatals.send(FatalErrorEvent::new(
"Game is no longer opened, cannot join.",
));
}
JoinError::AlreadyJoined => {
fatals.send(FatalErrorEvent::new(
"Already joined the game, cannot re-join.",
Expand All @@ -184,6 +195,15 @@ fn process_from_game(
FromGame::PeerLeft(id) => {
info!("Peer {id} left.");
}
FromGame::Starting => {
info!("Multiplayer game is starting.");
starting_events.send(GameStartingEvent);
}
FromGame::Started => {
info!("Multiplayer game is fully started.");

// TODO
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/multiplayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use stats::StatsPlugin;

pub use crate::{
config::{ConnectionType, NetGameConf},
game::{GameJoinedEvent, GameOpenedEvent},
game::{GameJoinedEvent, GameOpenedEvent, GameStartingEvent},
lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent},
netstate::NetState,
};
Expand Down
12 changes: 12 additions & 0 deletions crates/net/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ pub enum ToGame {
///
/// The game is automatically closed once all players disconnect.
Leave,
/// This initiates game starting.
Start,
/// The game switches from starting state to started state once this
/// message is received from all players.
Initialized,
}

/// Message to be sent from a game server to a player/client (inside of a
Expand Down Expand Up @@ -73,11 +78,18 @@ pub enum FromGame {
/// Informs the player that another player with the given ID just
/// disconnected from the same game.
PeerLeft(u8),
/// Informs the client that the game is starting. The game is no longer
/// available for joining. The client should start game initialization.
Starting,
/// Informs the client that the game just started, id est it they can play.
Started,
}

#[derive(Encode, Decode)]
pub enum JoinError {
GameFull,
/// The game is no longer opened.
GameNotOpened,
/// The player has already joined the game.
AlreadyJoined,
/// The player already participates on a different game.
Expand Down

0 comments on commit 514d4df

Please sign in to comment.