From d544f6438a1a1ed5a4455855fad0b1ccee52def6 Mon Sep 17 00:00:00 2001 From: dank_meme01 Date: Wed, 22 Nov 2023 15:30:18 +0100 Subject: [PATCH] this is so cool --- server/game/Cargo.toml | 2 + server/game/src/bytebufferext.rs | 101 +++++++++++++++++++--- server/game/src/data/packets/mod.rs | 12 --- server/game/src/data/types/audio_frame.rs | 31 ++----- server/game/src/main.rs | 21 +++-- server/game/src/server.rs | 62 ++++++++----- server/game/src/server_thread.rs | 50 ++++++----- server/game/src/state.rs | 25 ++---- src/audio/audio_frame.cpp | 56 ++++++------ src/audio/audio_frame.hpp | 15 ++-- src/audio/audio_stream.cpp | 2 +- src/audio/opus_codec.hpp | 20 ++++- src/data/bytebuffer.hpp | 82 +++++++++++++++++- src/main.cpp | 2 +- src/util/data.hpp | 16 ++-- 15 files changed, 331 insertions(+), 166 deletions(-) diff --git a/server/game/Cargo.toml b/server/game/Cargo.toml index 2b4d0a46..a1d52ad0 100644 --- a/server/game/Cargo.toml +++ b/server/game/Cargo.toml @@ -20,3 +20,5 @@ serde_json = "1.0.108" time = { version = "0.3.30", features = ["formatting"] } tokio = { version = "1.34.0", features = ["full"] } parking_lot = "0.12.1" +array-init = "2.1.0" +num_enum = "0.7.1" diff --git a/server/game/src/bytebufferext.rs b/server/game/src/bytebufferext.rs index ffc73a27..1b776bf5 100644 --- a/server/game/src/bytebufferext.rs +++ b/server/game/src/bytebufferext.rs @@ -1,9 +1,7 @@ use crate::data::types::cocos; -use anyhow::Result; +use anyhow::{anyhow, Result}; use bytebuffer::{ByteBuffer, ByteReader}; -type ByteVec = Vec; - pub trait Encodable { fn encode(&self, buf: &mut ByteBuffer); } @@ -86,13 +84,16 @@ pub trait ByteBufferExt { pub trait ByteBufferExtWrite { fn write_bool(&mut self, val: bool); // write a byte vector, prefixed with 4 bytes indicating length - fn write_byte_array(&mut self, vec: &ByteVec); + fn write_byte_array(&mut self, vec: &[u8]); fn write_value(&mut self, val: &T); fn write_optional_value(&mut self, val: Option<&T>); + fn write_value_array(&mut self, val: &[T; N]); fn write_value_vec(&mut self, val: &[T]); + fn write_enum, B: Encodable>(&mut self, val: E); + fn write_color3(&mut self, val: cocos::Color3B); fn write_color4(&mut self, val: cocos::Color4B); fn write_point(&mut self, val: cocos::Point); @@ -101,13 +102,16 @@ pub trait ByteBufferExtWrite { pub trait ByteBufferExtRead { fn read_bool(&mut self) -> Result; // read a byte vector, prefixed with 4 bytes indicating length - fn read_byte_array(&mut self) -> Result; + fn read_byte_array(&mut self) -> Result>; fn read_value(&mut self) -> Result; fn read_optional_value(&mut self) -> Result>; + fn read_value_array(&mut self) -> Result<[T; N]>; fn read_value_vec(&mut self) -> Result>; + fn read_enum, B: Decodable>(&mut self) -> Result; + fn read_color3(&mut self) -> Result; fn read_color4(&mut self) -> Result; fn read_point(&mut self) -> Result; @@ -126,9 +130,9 @@ impl ByteBufferExtWrite for ByteBuffer { self.write_u8(if val { 1u8 } else { 0u8 }); } - fn write_byte_array(&mut self, vec: &ByteVec) { - self.write_u32(vec.len() as u32); - self.write_bytes(vec); + fn write_byte_array(&mut self, val: &[u8]) { + self.write_u32(val.len() as u32); + self.write_bytes(val); } fn write_value(&mut self, val: &T) { @@ -142,6 +146,10 @@ impl ByteBufferExtWrite for ByteBuffer { } } + fn write_value_array(&mut self, val: &[T; N]) { + val.iter().for_each(|v| self.write_value(v)); + } + fn write_value_vec(&mut self, val: &[T]) { self.write_u32(val.len() as u32); for elem in val.iter() { @@ -149,6 +157,10 @@ impl ByteBufferExtWrite for ByteBuffer { } } + fn write_enum, B: Encodable>(&mut self, val: E) { + self.write_value(&val.into()) + } + fn write_color3(&mut self, val: cocos::Color3B) { self.write_value(&val) } @@ -165,10 +177,10 @@ impl ByteBufferExtWrite for ByteBuffer { macro_rules! impl_extread { ($decode_fn:ident) => { fn read_bool(&mut self) -> Result { - Ok(self.read_u8()? == 1u8) + Ok(self.read_u8()? != 0u8) } - fn read_byte_array(&mut self) -> Result { + fn read_byte_array(&mut self) -> Result> { let length = self.read_u32()? as usize; Ok(self.read_bytes(length)?) } @@ -184,6 +196,10 @@ macro_rules! impl_extread { }) } + fn read_value_array(&mut self) -> Result<[T; N]> { + array_init::try_array_init(|_| self.read_value::()) + } + fn read_value_vec(&mut self) -> Result> { let mut out = Vec::new(); let length = self.read_u32()? as usize; @@ -194,6 +210,13 @@ macro_rules! impl_extread { Ok(out) } + fn read_enum, B: Decodable>(&mut self) -> Result { + let val = self.read_value::()?; + let val: Result = val.try_into(); + + val.map_err(|_| anyhow!("failed to decode enum")) + } + fn read_color3(&mut self) -> Result { self.read_value() } @@ -215,3 +238,61 @@ impl ByteBufferExtRead for ByteBuffer { impl<'a> ByteBufferExtRead for ByteReader<'a> { impl_extread!(decode_from_reader); } + +/* Implementations for common types */ + +macro_rules! impl_primitive { + ($typ:ty,$read:ident,$write:ident) => { + impl Encodable for $typ { + fn encode(&self, buf: &mut ByteBuffer) { + buf.$write(*self); + } + } + + impl Decodable for $typ { + fn decode(buf: &mut ByteBuffer) -> Result + where + Self: Sized, + { + Ok(buf.$read()?) + } + fn decode_from_reader(buf: &mut ByteReader) -> Result + where + Self: Sized, + { + Ok(buf.$read()?) + } + } + }; +} + +impl_primitive!(u8, read_u8, write_u8); +impl_primitive!(u16, read_u16, write_u16); +impl_primitive!(u32, read_u32, write_u32); +impl_primitive!(u64, read_u64, write_u64); +impl_primitive!(i8, read_i8, write_i8); +impl_primitive!(i16, read_i16, write_i16); +impl_primitive!(i32, read_i32, write_i32); +impl_primitive!(i64, read_i64, write_i64); + +impl Encodable for Vec { + fn encode(&self, buf: &mut ByteBuffer) { + buf.write_byte_array(self); + } +} + +impl Decodable for Vec { + fn decode(buf: &mut ByteBuffer) -> Result + where + Self: Sized, + { + buf.read_byte_array() + } + + fn decode_from_reader(buf: &mut ByteReader) -> Result + where + Self: Sized, + { + buf.read_byte_array() + } +} diff --git a/server/game/src/data/packets/mod.rs b/server/game/src/data/packets/mod.rs index 46178129..7e428a8a 100644 --- a/server/game/src/data/packets/mod.rs +++ b/server/game/src/data/packets/mod.rs @@ -1,8 +1,6 @@ pub mod client; pub mod server; -use std::any::Any; - use crate::bytebufferext::{Decodable, Encodable}; type PacketId = u16; @@ -14,7 +12,6 @@ type PacketId = u16; * packet!(PacketName, id, enc) * * followed by packet_encode! and packet_decode! or their _unimpl versions -* the empty_impl! also must be added which is basically Default but less silly i guess? */ macro_rules! packet { @@ -27,10 +24,6 @@ macro_rules! packet { fn get_encrypted(&self) -> bool { $encrypted } - - fn as_any(&self) -> &dyn std::any::Any { - self - } } impl crate::data::packets::PacketWithId for $packet_type { @@ -51,10 +44,6 @@ macro_rules! packet { fn get_encrypted(&self) -> bool { $encrypted } - - fn as_any(&self) -> &dyn std::any::Any { - self - } } impl crate::data::packets::PacketWithId for $packet_type { @@ -90,7 +79,6 @@ pub(crate) use packet; pub trait Packet: Encodable + Decodable + Send + Sync { fn get_packet_id(&self) -> PacketId; fn get_encrypted(&self) -> bool; - fn as_any(&self) -> &dyn Any; } // god i hate this diff --git a/server/game/src/data/types/audio_frame.rs b/server/game/src/data/types/audio_frame.rs index 59434d44..19ec31ab 100644 --- a/server/game/src/data/types/audio_frame.rs +++ b/server/game/src/data/types/audio_frame.rs @@ -1,33 +1,20 @@ -use anyhow::bail; - use crate::bytebufferext::{decode_impl, encode_impl, ByteBufferExtRead, ByteBufferExtWrite}; +const VOICE_OPUS_FRAMES_IN_AUDIO_FRAME: usize = 20; + type EncodedOpusData = Vec; -#[derive(Clone, Default)] + +#[derive(Clone)] pub struct EncodedAudioFrame { - pub opus_frames: Vec, + pub opus_frames: [EncodedOpusData; VOICE_OPUS_FRAMES_IN_AUDIO_FRAME], } encode_impl!(EncodedAudioFrame, buf, self, { - buf.write_u16(self.opus_frames.len() as u16); - - for frame in self.opus_frames.iter() { - buf.write_byte_array(frame); - } + buf.write_value_array(&self.opus_frames); }); decode_impl!(EncodedAudioFrame, buf, { - let frames = buf.read_u16()?; - if frames > 64 { - bail!("failed to decode EncodedAudioFrame, way too many frames ({frames})"); - } - - let mut opus_frames = Vec::with_capacity(frames as usize); - - for _ in 0..frames { - let frame = buf.read_byte_array()?; - opus_frames.push(frame); - } - - Ok(Self { opus_frames }) + Ok(Self { + opus_frames: buf.read_value_array()?, + }) }); diff --git a/server/game/src/main.rs b/server/game/src/main.rs index 125c4b52..2a149326 100644 --- a/server/game/src/main.rs +++ b/server/game/src/main.rs @@ -4,6 +4,7 @@ use anyhow::anyhow; use globed_shared::{GameServerBootData, PROTOCOL_VERSION}; use log::{error, info, LevelFilter}; use logger::Logger; +use server::GameServerConfiguration; use state::ServerState; use server::GameServer; @@ -83,16 +84,20 @@ async fn main() -> Result<(), Box> { .build() .unwrap(); - let state = ServerState::new(client, central_url.clone(), central_pw.clone()); + let config = GameServerConfiguration { + http_client: client, + central_url, + central_pw, + }; - info!("Retreiving config from the central server.."); + let state = ServerState::new(); - let state_inner = state.read().await; + info!("Retreiving config from the central server.."); - let response = state_inner + let response = config .http_client - .post(format!("{}{}", state_inner.central_url, "gs/boot")) - .query(&[("pw", state_inner.central_pw.clone())]) + .post(format!("{}{}", config.central_url, "gs/boot")) + .query(&[("pw", config.central_pw.clone())]) .send() .await? .error_for_status() @@ -110,9 +115,7 @@ async fn main() -> Result<(), Box> { panic!("aborting due to incompatible protocol versions"); } - drop(state_inner); - - let server = Box::leak(Box::new(GameServer::new(host_address, state, boot_data).await)); + let server = Box::leak(Box::new(GameServer::new(host_address, state, boot_data, config).await)); server.run().await?; diff --git a/server/game/src/server.rs b/server/game/src/server.rs index 59eb4b3b..8b272f68 100644 --- a/server/game/src/server.rs +++ b/server/game/src/server.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use parking_lot::RwLock as SyncRwLock; +use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock}; use anyhow::anyhow; use crypto_box::{aead::OsRng, SecretKey}; @@ -19,21 +19,35 @@ use tokio::net::UdpSocket; use crate::{ data::{packets::server::VoiceBroadcastPacket, types::PlayerAccountData}, - server_thread::{GameServerThread, ServerThreadMessage}, + server_thread::{GameServerThread, ServerThreadMessage, SMALL_PACKET_LIMIT}, state::ServerState, }; +const MAX_PACKET_SIZE: usize = 8192; + +pub struct GameServerConfiguration { + pub http_client: reqwest::Client, + pub central_url: String, + pub central_pw: String, +} + pub struct GameServer { pub address: String, pub state: ServerState, pub socket: UdpSocket, pub threads: SyncRwLock>>, pub secret_key: SecretKey, - pub central_conf: SyncRwLock, + pub central_conf: SyncMutex, + pub config: GameServerConfiguration, } impl GameServer { - pub async fn new(address: String, state: ServerState, central_conf: GameServerBootData) -> Self { + pub async fn new( + address: String, + state: ServerState, + central_conf: GameServerBootData, + config: GameServerConfiguration, + ) -> Self { let secret_key = SecretKey::generate(&mut OsRng); Self { @@ -42,7 +56,8 @@ impl GameServer { socket: UdpSocket::bind(&address).await.unwrap(), threads: SyncRwLock::new(FxHashMap::default()), secret_key, - central_conf: SyncRwLock::new(central_conf), + central_conf: SyncMutex::new(central_conf), + config, } } @@ -74,12 +89,11 @@ impl GameServer { /* various calls for other threads */ - pub async fn broadcast_voice_packet(&'static self, vpkt: &VoiceBroadcastPacket) -> anyhow::Result<()> { + pub async fn broadcast_voice_packet(&'static self, vpkt: Arc) -> anyhow::Result<()> { // TODO dont send it to every single thread in existence let threads: Vec<_> = self.threads.read().values().cloned().collect(); for thread in threads { - let packet = vpkt.clone(); - thread.send_message(ServerThreadMessage::BroadcastVoice(packet)).await?; + thread.send_message(ServerThreadMessage::BroadcastVoice(vpkt.clone())).await?; } Ok(()) @@ -99,13 +113,13 @@ impl GameServer { } pub fn chat_blocked(&'static self, user_id: i32) -> bool { - self.central_conf.read().no_chat.contains(&user_id) + self.central_conf.lock().no_chat.contains(&user_id) } /* private handling stuff */ async fn recv_and_handle(&'static self) -> anyhow::Result<()> { - let mut buf = [0u8; 65536]; + let mut buf = [0u8; MAX_PACKET_SIZE]; let (len, peer) = self.socket.recv_from(&mut buf).await?; let peer = match peer { @@ -140,7 +154,17 @@ impl GameServer { thread_cl }; - thread.send_message(ServerThreadMessage::Packet(buf[..len].to_vec())).await?; + // don't heap allocate for small packets + thread + .send_message(if len <= SMALL_PACKET_LIMIT { + let mut smallbuf = [0u8; SMALL_PACKET_LIMIT]; + smallbuf[..len].copy_from_slice(&buf[..len]); + + ServerThreadMessage::SmallPacket(smallbuf) + } else { + ServerThreadMessage::Packet(buf[..len].to_vec()) + }) + .await?; Ok(()) } @@ -172,15 +196,11 @@ impl GameServer { } async fn refresh_bootdata(&'static self) -> anyhow::Result<()> { - let state_inner = self.state.read().await; - let http_client = state_inner.http_client.clone(); - let central_url = state_inner.central_url.clone(); - let central_pw = state_inner.central_pw.clone(); - drop(state_inner); - - let response = http_client - .post(format!("{}{}", central_url, "gs/boot")) - .query(&[("pw", central_pw)]) + let response = self + .config + .http_client + .post(format!("{}{}", self.config.central_url, "gs/boot")) + .query(&[("pw", self.config.central_pw.clone())]) .send() .await? .error_for_status() @@ -189,7 +209,7 @@ impl GameServer { let configuration = response.text().await?; let boot_data: GameServerBootData = serde_json::from_str(&configuration)?; - *self.central_conf.write() = boot_data; + *self.central_conf.lock() = boot_data; Ok(()) } diff --git a/server/game/src/server_thread.rs b/server/game/src/server_thread.rs index 5bca528b..826cbf78 100644 --- a/server/game/src/server_thread.rs +++ b/server/game/src/server_thread.rs @@ -1,6 +1,9 @@ use std::{ net::SocketAddrV4, - sync::atomic::{AtomicBool, AtomicI32, Ordering}, + sync::{ + atomic::{AtomicBool, AtomicI32, Ordering}, + Arc, + }, time::{Duration, SystemTime}, }; @@ -28,9 +31,13 @@ use crate::{ server::GameServer, }; +pub const SMALL_PACKET_LIMIT: usize = 128; +const CHANNEL_BUFFER: usize = 4; + pub enum ServerThreadMessage { Packet(Vec), - BroadcastVoice(VoiceBroadcastPacket), + SmallPacket([u8; SMALL_PACKET_LIMIT]), + BroadcastVoice(Arc), } pub struct GameServerThread { @@ -60,7 +67,6 @@ macro_rules! gs_require { macro_rules! gs_handler { ($self:ident,$name:ident,$pktty:ty,$pkt:ident,$code:expr) => { - // Insanity if you ask me async fn $name(&$self, buf: &mut ByteReader<'_>) -> anyhow::Result<()> { let $pkt = <$pktty>::decode_from_reader(buf)?; $code @@ -95,7 +101,7 @@ impl GameServerThread { /* public api for the main server */ pub fn new(peer: SocketAddrV4, game_server: &'static GameServer) -> Self { - let (tx, rx) = mpsc::channel::(8); + let (tx, rx) = mpsc::channel::(CHANNEL_BUFFER); Self { tx, rx: Mutex::new(rx), @@ -188,12 +194,17 @@ impl GameServerThread { async fn handle_message(&self, message: ServerThreadMessage) -> anyhow::Result<()> { match message { - ServerThreadMessage::Packet(data) => match self.handle_packet(data).await { + ServerThreadMessage::Packet(data) => match self.handle_packet(&data).await { Ok(_) => {} Err(err) => bail!("failed to handle packet: {err}"), }, - ServerThreadMessage::BroadcastVoice(voice_packet) => match self.send_packet(&voice_packet).await { + ServerThreadMessage::SmallPacket(data) => match self.handle_packet(&data).await { + Ok(_) => {} + Err(err) => bail!("failed to handle packet: {err}"), + }, + + ServerThreadMessage::BroadcastVoice(voice_packet) => match self.send_packet(&*voice_packet).await { Ok(_) => {} Err(err) => bail!("failed to broadcast voice packet: {err}"), }, @@ -204,11 +215,11 @@ impl GameServerThread { /* packet handlers */ - async fn handle_packet(&self, message: Vec) -> anyhow::Result<()> { + async fn handle_packet(&self, message: &[u8]) -> anyhow::Result<()> { #[cfg(debug_assertions)] gs_require!(message.len() >= PACKET_HEADER_LEN, "packet is missing a header"); - let mut data = ByteReader::from_bytes(&message); + let mut data = ByteReader::from_bytes(message); let packet_id = data.read_u16()?; let encrypted = data.read_bool()?; @@ -336,20 +347,17 @@ impl GameServerThread { gs_handler!(self, handle_login, LoginPacket, packet, { // lets verify the given token - let state = self.game_server.state.read().await; - let client = state.http_client.clone(); - let central_url = state.central_url.clone(); - let pw = state.central_pw.clone(); - drop(state); - - let url = format!("{central_url}gs/verify"); + let url = format!("{}gs/verify", self.game_server.config.central_url); - let response = client + let response = self + .game_server + .config + .http_client .post(url) .query(&[ ("account_id", packet.account_id.to_string()), ("token", packet.token.clone()), - ("pw", pw), + ("pw", self.game_server.config.central_pw.clone()), ]) .send() .await? @@ -381,7 +389,7 @@ impl GameServerThread { let special_user_data = self .game_server .central_conf - .read() + .lock() .special_users .get(&packet.account_id) .cloned(); @@ -521,12 +529,12 @@ impl GameServerThread { } } - let vpkt = VoiceBroadcastPacket { + let vpkt = Arc::new(VoiceBroadcastPacket { player_id: accid, data: packet.data.clone(), - }; + }); - self.game_server.broadcast_voice_packet(&vpkt).await?; + self.game_server.broadcast_voice_packet(vpkt).await?; Ok(()) }); diff --git a/server/game/src/state.rs b/server/game/src/state.rs index 2d20e0f5..b8dfb577 100644 --- a/server/game/src/state.rs +++ b/server/game/src/state.rs @@ -1,40 +1,25 @@ use crate::managers::PlayerManager; use parking_lot::Mutex as SyncMutex; use std::sync::atomic::AtomicU32; -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -pub struct ServerStateInner { - pub http_client: reqwest::Client, - pub central_url: String, - pub central_pw: String, -} pub struct ServerState { pub player_count: AtomicU32, // make player_manager SyncRwLock if there will be read-only operations // for now everything requires write access so it's a waste pub player_manager: SyncMutex, - inner: RwLock, } impl ServerState { - pub fn new(http_client: reqwest::Client, central_url: String, central_pw: String) -> Self { + pub fn new() -> Self { Self { player_count: AtomicU32::new(0u32), player_manager: SyncMutex::new(PlayerManager::new()), - inner: RwLock::new(ServerStateInner { - http_client, - central_url, - central_pw, - }), } } +} - pub async fn read(&self) -> RwLockReadGuard<'_, ServerStateInner> { - self.inner.read().await - } - - pub async fn write(&self) -> RwLockWriteGuard<'_, ServerStateInner> { - self.inner.write().await +impl Default for ServerState { + fn default() -> Self { + Self::new() } } diff --git a/src/audio/audio_frame.cpp b/src/audio/audio_frame.cpp index 9b74a67d..968974cb 100644 --- a/src/audio/audio_frame.cpp +++ b/src/audio/audio_frame.cpp @@ -2,52 +2,46 @@ #if GLOBED_VOICE_SUPPORT -const size_t VOICE_MAX_BYTES_IN_FRAME = 1500; // TODO ??? adjust later - using namespace util::data; -EncodedAudioFrame::EncodedAudioFrame() {} +EncodedAudioFrame::EncodedAudioFrame() { + // TODO remove tracking + geode::log::debug("constructing audio frame"); +} + EncodedAudioFrame::~EncodedAudioFrame() { - for (const EncodedOpusData& frame : frames) { - OpusCodec::freeData(frame); + // TODO remove tracking + geode::log::debug("destructing audio frame ({} frames)", frameCount); + for (size_t i = 0; i < frameCount; i++) { + OpusCodec::freeData(frames[i]); } } -bool EncodedAudioFrame::pushOpusFrame(EncodedOpusData&& frame) { - frames.push_back(std::move(frame)); - return frames.size() >= VOICE_OPUS_FRAMES_IN_AUDIO_FRAME; +void EncodedAudioFrame::pushOpusFrame(EncodedOpusData&& frame) { + if (frameCount >= VOICE_OPUS_FRAMES_IN_AUDIO_FRAME) { + geode::log::warn("tried to push an extra frame into EncodedAudioFrame, 20 is the max"); + return; + } + + frames[frameCount++] = std::move(frame); } -const std::vector& EncodedAudioFrame::extractFrames() const { +// riveting +const std::array& EncodedAudioFrame::getFrames() const { return frames; } void EncodedAudioFrame::encode(ByteBuffer& buf) const { - buf.writeU16(frames.size()); - for (const EncodedOpusData& frame : frames) { - buf.writeByteArray(frame.ptr, frame.length); - } + GLOBED_REQUIRE( + frameCount == VOICE_OPUS_FRAMES_IN_AUDIO_FRAME, + fmt::format("tried to encode an EncodedAudioFrame with {} frames when {} is needed", VOICE_OPUS_FRAMES_IN_AUDIO_FRAME) + ) + + buf.writeValueArray(frames); } void EncodedAudioFrame::decode(ByteBuffer& buf) { - // when it comes to arbitrary allocation, DO NOT trust the other clients' data - size_t length = buf.readU16(); - - GLOBED_REQUIRE(length < (VOICE_OPUS_FRAMES_IN_AUDIO_FRAME + 2), fmt::format("Rejecting audio packet, too many frames ({})", length)); - - for (size_t i = 0; i < length; i++) { - EncodedOpusData frame; - size_t frameDataSize = buf.readU32(); - - GLOBED_REQUIRE(frameDataSize <= VOICE_MAX_BYTES_IN_FRAME, fmt::format("Rejecting audio packet, frame size too large ({})", length)); - - frame.ptr = new byte[frameDataSize]; - frame.length = frameDataSize; - - buf.readBytesInto(frame.ptr, frame.length); - - frames.push_back(std::move(frame)); - } + frames = buf.readValueArray(); } #endif // GLOBED_VOICE_SUPPORT \ No newline at end of file diff --git a/src/audio/audio_frame.hpp b/src/audio/audio_frame.hpp index d83c3761..64553434 100644 --- a/src/audio/audio_frame.hpp +++ b/src/audio/audio_frame.hpp @@ -4,9 +4,9 @@ #if GLOBED_VOICE_SUPPORT -const size_t VOICE_TARGET_SAMPLERATE = 24000; -const size_t VOICE_TARGET_FRAMESIZE = 1440; // for opus, 60ms per single opus frame -const float VOICE_CHUNK_RECORD_TIME = 1.2f; // the audio buffer that will be sent in a single packet in seconds +constexpr size_t VOICE_TARGET_SAMPLERATE = 24000; +constexpr size_t VOICE_TARGET_FRAMESIZE = 1440; // for opus, 60ms per single opus frame +constexpr float VOICE_CHUNK_RECORD_TIME = 1.2f; // the audio buffer that will be sent in a single packet in seconds #include "opus_codec.hpp" #include @@ -14,7 +14,7 @@ const float VOICE_CHUNK_RECORD_TIME = 1.2f; // the audio buffer that will be sen // Represents an audio frame (usually around a second long) that contains multiple opus frames class EncodedAudioFrame { public: - static const size_t VOICE_OPUS_FRAMES_IN_AUDIO_FRAME = + static constexpr size_t VOICE_OPUS_FRAMES_IN_AUDIO_FRAME = static_cast( VOICE_CHUNK_RECORD_TIME * static_cast(VOICE_TARGET_SAMPLERATE) / static_cast(VOICE_TARGET_FRAMESIZE) @@ -31,10 +31,10 @@ class EncodedAudioFrame { EncodedAudioFrame& operator=(EncodedAudioFrame&&) noexcept = default; // adds this audio frame to the list, returns 'true' if we are above the threshold of frames - bool pushOpusFrame(EncodedOpusData&& frame); + void pushOpusFrame(EncodedOpusData&& frame); // extract all frames - const std::vector& extractFrames() const; + const std::array& getFrames() const; // implement Serializable @@ -42,7 +42,8 @@ class EncodedAudioFrame { void decode(ByteBuffer& buf); protected: - std::vector frames; + std::array frames; + size_t frameCount = 0; }; diff --git a/src/audio/audio_stream.cpp b/src/audio/audio_stream.cpp index 6c2e2aaa..0f6ab6c4 100644 --- a/src/audio/audio_stream.cpp +++ b/src/audio/audio_stream.cpp @@ -101,7 +101,7 @@ void AudioStream::writeData(const EncodedAudioFrame& frame) { auto& vm = GlobedAudioManager::get(); FMOD_RESULT res; - const auto& frames = frame.extractFrames(); + const auto& frames = frame.getFrames(); for (const auto& opusFrame : frames) { auto decodedFrame = vm.decodeSound(opusFrame); diff --git a/src/audio/opus_codec.hpp b/src/audio/opus_codec.hpp index 3c904cb2..1270c1d4 100644 --- a/src/audio/opus_codec.hpp +++ b/src/audio/opus_codec.hpp @@ -4,11 +4,29 @@ #if GLOBED_VOICE_SUPPORT #include +#include #include -struct EncodedOpusData { +const size_t VOICE_MAX_BYTES_IN_FRAME = 1500; // TODO ??? adjust later + +class EncodedOpusData { +public: util::data::byte* ptr; int64_t length; + + GLOBED_ENCODE { + buf.writeByteArray(ptr, length); + } + + GLOBED_DECODE { + // when it comes to arbitrary allocation, DO NOT trust the data + length = buf.readU32(); + GLOBED_REQUIRE(length <= VOICE_MAX_BYTES_IN_FRAME, fmt::format("Rejecting audio frame, size too large ({})", length)) + + ptr = new util::data::byte[length]; + + buf.readBytesInto(ptr, length); + } }; struct DecodedOpusData { diff --git a/src/data/bytebuffer.hpp b/src/data/bytebuffer.hpp index 82132f50..3f1d7a04 100644 --- a/src/data/bytebuffer.hpp +++ b/src/data/bytebuffer.hpp @@ -2,10 +2,10 @@ #include "bitbuffer.hpp" #include #include +#include class ByteBuffer; - // Represents a data type that can be easily written to a ByteBuffer template concept Encodable = requires(const T t, ByteBuffer& buf) { @@ -22,11 +22,10 @@ concept Decodable = requires(T t, ByteBuffer& buf) { template concept Serializable = Encodable && Decodable; +// helper macros so you can do GLOBED_ENCODE {...} in serializable structs or packets #define GLOBED_ENCODE void encode(ByteBuffer& buf) const #define GLOBED_DECODE void decode(ByteBuffer& buf) -// helper macros so you can do GLOBED_ENCODE {...} in serializable structs or packets - class ByteBuffer { public: // Constructs an empty ByteBuffer @@ -213,6 +212,8 @@ class ByteBuffer { for (size_t i = 0; i < Count; i++) { out[i] = this->readValue(); } + + return out; } // Write a list of `Encodable` objects, prefixed with 4 bytes indicating the count. @@ -231,6 +232,81 @@ class ByteBuffer { } } + // Read a generic primitive number (like read but with endianness checks) + template + T readPrimitive() { + static_assert(util::data::IsPrimitive, "Unsupported type for readPrimitive, must be a primitive"); + + if constexpr (std::is_same_v) { + return readU16(); + } else if constexpr (std::is_same_v) { + return readU32(); + } else if constexpr (std::is_same_v) { + return readU64(); + } else if constexpr (std::is_same_v) { + return readF32(); + } else if constexpr (std::is_same_v) { + return readF64(); + } else if constexpr (std::is_same_v) { + return readI16(); + } else if constexpr (std::is_same_v) { + return readI32(); + } else if constexpr (std::is_same_v) { + return readI64(); + } else if constexpr (std::is_same_v) { + return readU8(); + } else if constexpr(std::is_same_v) { + return readI8(); + } + } + + // Write a generic primitive number (like read but with endianness checks) + template + void writePrimitive(T value) { + static_assert(util::data::IsPrimitive, "Unsupported type for writePrimitive, must be a primitive"); + + if constexpr (std::is_same_v) { + writeU16(value); + } else if constexpr (std::is_same_v) { + writeU32(value); + } else if constexpr (std::is_same_v) { + writeU64(value); + } else if constexpr (std::is_same_v) { + writeF32(value); + } else if constexpr (std::is_same_v) { + writeF64(value); + } else if constexpr (std::is_same_v) { + writeI16(value); + } else if constexpr (std::is_same_v) { + writeI32(value); + } else if constexpr (std::is_same_v) { + writeI64(value); + } else if constexpr (std::is_same_v) { + writeU8(value); + } else if constexpr(std::is_same_v) { + writeI8(value); + } + } + + // Write an enum + template + void writeEnum(const E& val) { + using P = std::underlying_type::type; + static_assert(util::data::IsPrimitive

, "enum underlying type must be a primitive"); + + this->writePrimitive

(static_cast

(val)); + } + + // Read an enum + // TODO no validation, likely not planning to add but still important to know + template + E readEnum() { + using P = std::underlying_type::type; + static_assert(util::data::IsPrimitive

, "enum underlying type must be a primitive"); + + return static_cast(this->readPrimitive

()); + } + #ifndef GLOBED_ROOT_NO_GEODE /* * Cocos/GD serializable methods diff --git a/src/main.cpp b/src/main.cpp index 5825bb89..5578271c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -116,7 +116,7 @@ void testFmod2() { for (size_t i = 0; i < *total; i++) { log::debug("Reading frame {}", i); auto encFrame = bb->readValue(); - const auto& opusFrames = encFrame.extractFrames(); + const auto& opusFrames = encFrame.getFrames(); for (const auto& opusFrame : opusFrames) { auto rawFrame = vm.decodeSound(opusFrame); diff --git a/src/util/data.hpp b/src/util/data.hpp index e045ab1e..9d59e450 100644 --- a/src/util/data.hpp +++ b/src/util/data.hpp @@ -12,6 +12,14 @@ namespace util::data { template using bytearray = std::array; + + template + concept IsPrimitive = std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v; + uint16_t byteswapU16(uint16_t val); uint32_t byteswapU32(uint32_t val); uint64_t byteswapU64(uint64_t val); @@ -25,13 +33,7 @@ namespace util::data { template inline T byteswap(T val) { - // I am so sorry - static_assert(std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v, - "Unsupported type for byteswap"); + static_assert(IsPrimitive, "Unsupported type for byteswap"); if constexpr (std::is_same_v) { return byteswapU16(val);