From 96630ef1c21c9f0fdb7d89fc475f95a1ca2ac6a7 Mon Sep 17 00:00:00 2001 From: mluogh Date: Wed, 13 May 2020 19:40:08 -0700 Subject: [PATCH 1/5] Add serialiable and flatbufferable representations of World and Tank --- schema/world.fbs | 1 - server/src/main.rs | 32 ++++--- server/src/math.rs | 5 ++ server/src/serialization.rs | 166 ++++++++++++++++++++++++++++++++++++ server/src/world.rs | 40 +++++++++ 5 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 server/src/math.rs create mode 100644 server/src/serialization.rs create mode 100644 server/src/world.rs diff --git a/schema/world.fbs b/schema/world.fbs index 5a64a8c..8d7c256 100644 --- a/schema/world.fbs +++ b/schema/world.fbs @@ -17,4 +17,3 @@ table WorldState { player: Tank; others: [Tank]; } - diff --git a/server/src/main.rs b/server/src/main.rs index 5bd5c34..b5293cf 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,7 @@ +mod math; +mod serialization; +mod world; + use std::collections::{HashSet, VecDeque}; use std::net::SocketAddr; use std::time; @@ -10,7 +14,10 @@ use log::{debug, info, warn}; use tungstenite::Message; use schema::actions_generated::get_root_as_action_root; -use schema::{messages_generated, world_generated}; + +use math::Position; +use serialization::{Config, Serializable}; +use world::{Tank, World}; type Peers = Arc>>; type Buffer = Vec; @@ -130,22 +137,13 @@ async fn run() -> anyhow::Result<()> { }); let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(1024); - let world = world_generated::WorldState::create( - &mut builder, - &world_generated::WorldStateArgs { - player: None, - others: None, - }, - ); - let message = messages_generated::MessageRoot::create( - &mut builder, - &messages_generated::MessageRootArgs { - message_type: messages_generated::Message::WorldState, - message: Some(world.as_union_value()), - }, - ); - builder.finish(message, None); - let world = Arc::new(builder.finished_data().to_vec()); + let mut world = World::new(); + + world.add_tank(Tank::new(0, Position { x: 69.0, y: 420.0 })); + world.add_tank(Tank::new(1, Position { x: 23.0, y: 54.0 })); + world.add_tank(Tank::new(2, Position { x: 84.0, y: 34.0 })); + + let world = Arc::new(world.serialize(&mut builder, &Config::new(0))); // Listen for new WebSocket connections. while let Ok((stream, address)) = tcp_listener.accept().await { diff --git a/server/src/math.rs b/server/src/math.rs new file mode 100644 index 0000000..e0db49f --- /dev/null +++ b/server/src/math.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub struct Position { + pub x: f32, + pub y: f32, +} diff --git a/server/src/serialization.rs b/server/src/serialization.rs new file mode 100644 index 0000000..2fb225b --- /dev/null +++ b/server/src/serialization.rs @@ -0,0 +1,166 @@ +use flatbuffers::{FlatBufferBuilder, ForwardsUOffset, Vector, WIPOffset}; + +use schema::math_generated; +use schema::messages_generated; +use schema::world_generated; + +use log::error; + +use crate::world::{Tank, World}; + +pub trait Serializable { + fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Vec; +} + +trait Flatbufferable<'a> { + type Buffer; + fn add_to_fb( + &self, + builder: &mut FlatBufferBuilder<'a>, + config: &Config, + ) -> WIPOffset; +} + +pub struct Config { + pub player: u16, +} + +impl Config { + pub fn new(player: u16) -> Config { + Config { player } + } +} + +impl<'a> Flatbufferable<'a> for Tank { + type Buffer = world_generated::Tank<'a>; + #[allow(unused_variables)] + fn add_to_fb( + &self, + builder: &mut FlatBufferBuilder<'a>, + config: &Config, + ) -> WIPOffset> { + world_generated::Tank::create( + builder, + &world_generated::TankArgs { + pos: Some(&math_generated::Vec2::new( + self.pos_ref().x, + self.pos_ref().y, + )), + }, + ) + } +} + +// TODO: 1) see if it's possible to add a test for this. Currently it's hard because you can't +// get_root for an array. Perhaps make a testing schema that just has the [Tank] field. +// 2) See if we can simplify for all Vec<&T> where T is Flatbufferable. Currently difficult because +// each T has its own associated Buffer type. +impl<'a> Flatbufferable<'a> for Vec<&Tank> { + type Buffer = Vector<'a, ForwardsUOffset>>; + fn add_to_fb( + &self, + builder: &mut FlatBufferBuilder<'a>, + config: &Config, + ) -> WIPOffset>>> { + let mut vec = Vec::new(); + + for tank in self.iter() { + vec.push(tank.add_to_fb(builder, config)); + } + + builder.create_vector(vec.as_slice()) + } +} + +impl Serializable for World { + fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Vec { + builder.reset(); + + let (player, other_tanks): (Vec<&Tank>, Vec<&Tank>) = self + .tanks_ref() + .iter() + .partition(|tank| tank.player() == config.player); + + if player.len() != 1 { + error!("Could not find player in World."); + return Vec::new(); + } + + let player = player.get(0).unwrap().add_to_fb(builder, config); + let other_tanks = other_tanks.add_to_fb(builder, config); + + let world = world_generated::WorldState::create( + builder, + &world_generated::WorldStateArgs { + player: Some(player), + others: Some(other_tanks), + }, + ); + + let message = messages_generated::MessageRoot::create( + builder, + &messages_generated::MessageRootArgs { + message_type: messages_generated::Message::WorldState, + message: Some(world.as_union_value()), + }, + ); + + builder.finish(message, None); + builder.finished_data().to_vec() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::math::Position; + use flatbuffers::get_root; + + #[test] + fn tank_can_be_flatbuffered() { + let config = Config::new(0); + let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(1024); + let tank = Tank::new(0, Position { x: 69.0, y: 420.0 }); + let tank_buf = tank.add_to_fb(&mut builder, &config); + builder.finish(tank_buf, None); + + let recovered_tank = get_root::(builder.finished_data()); + + assert_eq!(recovered_tank.pos().unwrap().x(), tank.pos_ref().x); + assert_eq!(recovered_tank.pos().unwrap().y(), tank.pos_ref().y); + } + + #[test] + fn world_can_be_serialized() { + let config = Config::new(0); + let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(1024); + + let mut world = World::new(); + + world.add_tank(Tank::new(0, Position { x: 69.0, y: 420.0 })); + world.add_tank(Tank::new(1, Position { x: 23.0, y: 54.0 })); + world.add_tank(Tank::new(2, Position { x: 84.0, y: 34.0 })); + + let world_as_bytes = world.serialize(&mut builder, &config); + + let message = get_root::(world_as_bytes.as_ref()); + assert_eq!( + message.message_type(), + messages_generated::Message::WorldState + ); + let recovered_world = message.message_as_world_state().unwrap(); + + let player = recovered_world.player().unwrap(); + let others = recovered_world.others().unwrap(); + let tanks = world.tanks_ref(); + + assert_eq!(player.pos().unwrap().x(), tanks[0].pos_ref().x); + assert_eq!(player.pos().unwrap().y(), tanks[0].pos_ref().y); + + assert_eq!(others.len(), 2); + assert_eq!(others.get(0).pos().unwrap().x(), tanks[1].pos_ref().x); + assert_eq!(others.get(0).pos().unwrap().y(), tanks[1].pos_ref().y); + assert_eq!(others.get(1).pos().unwrap().x(), tanks[2].pos_ref().x); + assert_eq!(others.get(1).pos().unwrap().y(), tanks[2].pos_ref().y); + } +} diff --git a/server/src/world.rs b/server/src/world.rs new file mode 100644 index 0000000..716b266 --- /dev/null +++ b/server/src/world.rs @@ -0,0 +1,40 @@ +use crate::math::Position; + +pub struct World { + tanks: Vec, +} + +pub struct Tank { + // TODO: discuss what player would look like and replace. + player: u16, + pos: Position, +} + +impl Tank { + pub fn player(&self) -> u16 { + self.player + } + + pub fn pos_ref(&self) -> &Position { + &self.pos + } + + pub fn new(player: u16, pos: Position) -> Tank { + Tank { player, pos } + } +} + +impl World { + pub fn new() -> World { + World { tanks: Vec::new() } + } + + // TODO: replace with add_player after discussion on representing player. + pub fn add_tank(&mut self, tank: Tank) { + self.tanks.push(tank); + } + + pub fn tanks_ref(&self) -> &Vec { + &self.tanks + } +} From 37610905b256b0ccea6ad0bb01ab3b0f1a3ae694 Mon Sep 17 00:00:00 2001 From: mluogh Date: Thu, 14 May 2020 00:43:04 -0700 Subject: [PATCH 2/5] ming nitter --- server/src/main.rs | 11 ++++--- server/src/math.rs | 2 +- server/src/serialization.rs | 59 +++++++++++++++++++------------------ server/src/world.rs | 6 ++-- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index b5293cf..38b1a36 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,7 +1,3 @@ -mod math; -mod serialization; -mod world; - use std::collections::{HashSet, VecDeque}; use std::net::SocketAddr; use std::time; @@ -15,8 +11,11 @@ use tungstenite::Message; use schema::actions_generated::get_root_as_action_root; +mod math; +mod serialization; +mod world; use math::Position; -use serialization::{Config, Serializable}; +use serialization::{Config, SerializableAsMessage}; use world::{Tank, World}; type Peers = Arc>>; @@ -143,7 +142,7 @@ async fn run() -> anyhow::Result<()> { world.add_tank(Tank::new(1, Position { x: 23.0, y: 54.0 })); world.add_tank(Tank::new(2, Position { x: 84.0, y: 34.0 })); - let world = Arc::new(world.serialize(&mut builder, &Config::new(0))); + let world = Arc::new(world.serialize(&mut builder, &Config::new(0)).unwrap()); // Listen for new WebSocket connections. while let Ok((stream, address)) = tcp_listener.accept().await { diff --git a/server/src/math.rs b/server/src/math.rs index e0db49f..3c88914 100644 --- a/server/src/math.rs +++ b/server/src/math.rs @@ -1,4 +1,4 @@ -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct Position { pub x: f32, pub y: f32, diff --git a/server/src/serialization.rs b/server/src/serialization.rs index 2fb225b..0105641 100644 --- a/server/src/serialization.rs +++ b/server/src/serialization.rs @@ -1,15 +1,15 @@ +use anyhow::{anyhow, Result}; use flatbuffers::{FlatBufferBuilder, ForwardsUOffset, Vector, WIPOffset}; +use log::error; use schema::math_generated; use schema::messages_generated; use schema::world_generated; -use log::error; - use crate::world::{Tank, World}; -pub trait Serializable { - fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Vec; +pub trait SerializableAsMessage { + fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Result>; } trait Flatbufferable<'a> { @@ -22,12 +22,12 @@ trait Flatbufferable<'a> { } pub struct Config { - pub player: u16, + pub player_id: u16, } impl Config { - pub fn new(player: u16) -> Config { - Config { player } + pub fn new(player_id: u16) -> Config { + Config { player_id } } } @@ -42,10 +42,7 @@ impl<'a> Flatbufferable<'a> for Tank { world_generated::Tank::create( builder, &world_generated::TankArgs { - pos: Some(&math_generated::Vec2::new( - self.pos_ref().x, - self.pos_ref().y, - )), + pos: Some(&math_generated::Vec2::new(self.pos().x, self.pos().y)), }, ) } @@ -72,18 +69,24 @@ impl<'a> Flatbufferable<'a> for Vec<&Tank> { } } -impl Serializable for World { - fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Vec { +impl SerializableAsMessage for World { + fn serialize(&self, builder: &mut FlatBufferBuilder, config: &Config) -> Result> { builder.reset(); let (player, other_tanks): (Vec<&Tank>, Vec<&Tank>) = self - .tanks_ref() + .tanks() .iter() - .partition(|tank| tank.player() == config.player); + .partition(|tank| tank.player() == config.player_id); if player.len() != 1 { - error!("Could not find player in World."); - return Vec::new(); + error!( + "There are {} players with id {}. Can't serialize world.", + player.len(), + config.player_id + ); + return Err(anyhow!( + "Config does not specify unique player; can't serialize for schema." + )); } let player = player.get(0).unwrap().add_to_fb(builder, config); @@ -106,7 +109,7 @@ impl Serializable for World { ); builder.finish(message, None); - builder.finished_data().to_vec() + Ok(builder.finished_data().to_vec()) } } @@ -126,8 +129,8 @@ mod tests { let recovered_tank = get_root::(builder.finished_data()); - assert_eq!(recovered_tank.pos().unwrap().x(), tank.pos_ref().x); - assert_eq!(recovered_tank.pos().unwrap().y(), tank.pos_ref().y); + assert_eq!(recovered_tank.pos().unwrap().x(), tank.pos().x); + assert_eq!(recovered_tank.pos().unwrap().y(), tank.pos().y); } #[test] @@ -141,7 +144,7 @@ mod tests { world.add_tank(Tank::new(1, Position { x: 23.0, y: 54.0 })); world.add_tank(Tank::new(2, Position { x: 84.0, y: 34.0 })); - let world_as_bytes = world.serialize(&mut builder, &config); + let world_as_bytes = world.serialize(&mut builder, &config).unwrap(); let message = get_root::(world_as_bytes.as_ref()); assert_eq!( @@ -152,15 +155,15 @@ mod tests { let player = recovered_world.player().unwrap(); let others = recovered_world.others().unwrap(); - let tanks = world.tanks_ref(); + let tanks = world.tanks(); - assert_eq!(player.pos().unwrap().x(), tanks[0].pos_ref().x); - assert_eq!(player.pos().unwrap().y(), tanks[0].pos_ref().y); + assert_eq!(player.pos().unwrap().x(), tanks[0].pos().x); + assert_eq!(player.pos().unwrap().y(), tanks[0].pos().y); assert_eq!(others.len(), 2); - assert_eq!(others.get(0).pos().unwrap().x(), tanks[1].pos_ref().x); - assert_eq!(others.get(0).pos().unwrap().y(), tanks[1].pos_ref().y); - assert_eq!(others.get(1).pos().unwrap().x(), tanks[2].pos_ref().x); - assert_eq!(others.get(1).pos().unwrap().y(), tanks[2].pos_ref().y); + assert_eq!(others.get(0).pos().unwrap().x(), tanks[1].pos().x); + assert_eq!(others.get(0).pos().unwrap().y(), tanks[1].pos().y); + assert_eq!(others.get(1).pos().unwrap().x(), tanks[2].pos().x); + assert_eq!(others.get(1).pos().unwrap().y(), tanks[2].pos().y); } } diff --git a/server/src/world.rs b/server/src/world.rs index 716b266..b7d85d7 100644 --- a/server/src/world.rs +++ b/server/src/world.rs @@ -15,8 +15,8 @@ impl Tank { self.player } - pub fn pos_ref(&self) -> &Position { - &self.pos + pub fn pos(&self) -> Position { + self.pos } pub fn new(player: u16, pos: Position) -> Tank { @@ -34,7 +34,7 @@ impl World { self.tanks.push(tank); } - pub fn tanks_ref(&self) -> &Vec { + pub fn tanks(&self) -> &Vec { &self.tanks } } From d7ae36a9636f0a972d9114e041bcdd1ea3c1cf65 Mon Sep 17 00:00:00 2001 From: mluogh Date: Thu, 14 May 2020 17:10:19 -0700 Subject: [PATCH 3/5] ming big nitter --- server/src/serialization.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server/src/serialization.rs b/server/src/serialization.rs index 0105641..89dfcfb 100644 --- a/server/src/serialization.rs +++ b/server/src/serialization.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, Result}; use flatbuffers::{FlatBufferBuilder, ForwardsUOffset, Vector, WIPOffset}; -use log::error; use schema::math_generated; use schema::messages_generated; @@ -79,11 +78,6 @@ impl SerializableAsMessage for World { .partition(|tank| tank.player() == config.player_id); if player.len() != 1 { - error!( - "There are {} players with id {}. Can't serialize world.", - player.len(), - config.player_id - ); return Err(anyhow!( "Config does not specify unique player; can't serialize for schema." )); From 68b0f7a9b2a09097debc54059e0e534b0ee9d0a2 Mon Sep 17 00:00:00 2001 From: mluogh Date: Thu, 14 May 2020 17:22:51 -0700 Subject: [PATCH 4/5] big nitter --- server/src/serialization.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/serialization.rs b/server/src/serialization.rs index 89dfcfb..e37579b 100644 --- a/server/src/serialization.rs +++ b/server/src/serialization.rs @@ -83,7 +83,10 @@ impl SerializableAsMessage for World { )); } - let player = player.get(0).unwrap().add_to_fb(builder, config); + let player = player + .first() + .ok_or(anyhow!("Player doesn't exist."))? + .add_to_fb(builder, config); let other_tanks = other_tanks.add_to_fb(builder, config); let world = world_generated::WorldState::create( From 9428a18389c575083ac7705640c4b48fd197ebe6 Mon Sep 17 00:00:00 2001 From: mluogh Date: Thu, 14 May 2020 17:36:14 -0700 Subject: [PATCH 5/5] could have just unwrapped --- server/src/serialization.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/serialization.rs b/server/src/serialization.rs index e37579b..29fdc94 100644 --- a/server/src/serialization.rs +++ b/server/src/serialization.rs @@ -85,7 +85,7 @@ impl SerializableAsMessage for World { let player = player .first() - .ok_or(anyhow!("Player doesn't exist."))? + .ok_or_else(|| anyhow!("Player doesn't exist."))? .add_to_fb(builder, config); let other_tanks = other_tanks.add_to_fb(builder, config);