mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add basic server-only player harness
This commit is contained in:
99
rc_multiplayer/src/matches/fake/experimental.rs
Normal file
99
rc_multiplayer/src/matches/fake/experimental.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use rand::Rng;
|
||||
use byteserde::ser_heap::ByteSerializeHeap;
|
||||
|
||||
pub struct ExperimentalPlayer {
|
||||
me: tokio::sync::RwLock<Option<oj_rc_core::persist::user::PlayerDescriptor>>,
|
||||
is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
}
|
||||
|
||||
impl ExperimentalPlayer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
me: tokio::sync::RwLock::new(None),
|
||||
is_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::FakeUser for ExperimentalPlayer {
|
||||
async fn on_init(&self, descriptors: &Vec<oj_rc_core::persist::user::PlayerDescriptor>, player_id: u8) {
|
||||
if let Some(my_desc) = descriptors.iter().filter(|x| x.player_id == player_id).next() {
|
||||
*self.me.write().await = Some(my_desc.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||
let movement_rx = real_players.values().map(|x| x.to_owned()).collect();
|
||||
let is_complete = self.is_complete.clone();
|
||||
let player_id = self.me.read().await.as_ref().unwrap().player_id;
|
||||
tokio::task::spawn(erratic_behaviour(movement_rx, is_complete, player_id));
|
||||
}
|
||||
|
||||
async fn on_end(&self) {
|
||||
self.is_complete.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn random_map_coord(rng: &mut rand::rngs::ThreadRng) -> f32 {
|
||||
const CONVERSION_FACTOR: f32 = 300.0 / (i16::MAX as f32);
|
||||
let int = rng.random::<i16>();
|
||||
(int as f32) * CONVERSION_FACTOR
|
||||
}
|
||||
|
||||
fn random_height_coord(rng: &mut rand::rngs::ThreadRng) -> f32 {
|
||||
const CONVERSION_FACTOR: f32 = 50.0 / (i16::MAX as f32);
|
||||
let int = rng.random::<i16>();
|
||||
((int as f32) * CONVERSION_FACTOR) + 10.0
|
||||
}
|
||||
|
||||
async fn erratic_behaviour(send_to: Vec<crate::matches::generic::UserSender>, is_complete: std::sync::Arc<std::sync::atomic::AtomicBool>, player_id: u8) {
|
||||
const SLEEP_PERIOD: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
let mut fake_timestamp = 42.0;
|
||||
while !is_complete.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
let (pos_x, pos_y, pos_z) = {
|
||||
let mut rng = rand::rng();
|
||||
let pos_x: f32 = random_map_coord(&mut rng);
|
||||
let pos_y: f32 = random_height_coord(&mut rng);
|
||||
let pos_z: f32 = random_map_coord(&mut rng);
|
||||
(pos_x, pos_y, pos_z)
|
||||
};
|
||||
let motion = rlnl::machine_motion::MachineMotion {
|
||||
last_sent_seconds_a: 1.0,
|
||||
last_sent_seconds_b: 2.0,
|
||||
timestamp: fake_timestamp,
|
||||
player_id,
|
||||
target_point: rlnl::types::CompressedVec3::from((50.0, 50.0, 50.0)),
|
||||
rb_state: rlnl::machine_motion::RigidBodyState {
|
||||
rb_pos_rot: rlnl::types::PosQuatPair {
|
||||
pos: rlnl::types::CompressedVec3::from((pos_x, pos_y, pos_z)),
|
||||
rot: rlnl::types::CompressedQuat { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
angular_velocity: rlnl::types::CompressedVec3::from((0.0, 0.0, 0.0)),
|
||||
center_of_mass: rlnl::types::CompressedVec3::from((1.0, 1.0, 1.0)),
|
||||
},
|
||||
};
|
||||
let mut ser = byteserde::ser_heap::ByteSerializerHeap::default();
|
||||
if let Err(e) = motion.byte_serialize_heap(&mut ser) {
|
||||
log::error!("Failed to serialize motion data from experimental fake player: {}", e);
|
||||
} else {
|
||||
let motion_data = bytes::Bytes::copy_from_slice(ser.as_slice());
|
||||
for conn in send_to.iter() {
|
||||
send_motion_data_to(conn, motion_data.clone()).await;
|
||||
}
|
||||
log::info!("Moved experimental bot to ({}, {}, {})", pos_x, pos_y, pos_z);
|
||||
}
|
||||
fake_timestamp += 1.0;
|
||||
tokio::time::sleep(SLEEP_PERIOD).await;
|
||||
}
|
||||
is_complete.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
async fn send_motion_data_to(to: &crate::matches::generic::UserSender, motion: bytes::Bytes) {
|
||||
crate::events::log_lnl_send_failure(to.sender.send_data(crate::handler::EventData {
|
||||
message_ty: crate::data::MessageType::RobotMotion,
|
||||
variant: 0,
|
||||
data_size: motion.len() as _,
|
||||
data: motion,
|
||||
}, literustlib::packet::Property::Unreliable, &to.connection).await);
|
||||
}
|
||||
63
rc_multiplayer/src/matches/fake/handler.rs
Normal file
63
rc_multiplayer/src/matches/fake/handler.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
enum Message {
|
||||
Ready {
|
||||
real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>,
|
||||
},
|
||||
Stop,
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Message>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub async fn start(fakes: std::collections::HashMap<u8, Box<dyn super::FakeUser + 'static>>, descriptors: Vec<oj_rc_core::persist::user::PlayerDescriptor>) -> Self {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
tokio::task::spawn(handler_loop(rx, fakes, descriptors));
|
||||
Self {
|
||||
tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_ready(&self, real_players: std::collections::HashMap<u8, crate::matches::generic::UserSender>) {
|
||||
Self::log_failure(self.tx.send(Message::Ready { real_players }));
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
Self::log_failure(self.tx.send(Message::Stop));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn log_failure<T>(res: Result<(), tokio::sync::mpsc::error::SendError<T>>) {
|
||||
if let Err(_e) = res {
|
||||
log::warn!("Failed to send message to fake user handler thread");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handler_loop(mut rx: tokio::sync::mpsc::UnboundedReceiver<Message>, players: std::collections::HashMap<u8, Box<dyn super::FakeUser + 'static>>, descriptors: Vec<oj_rc_core::persist::user::PlayerDescriptor>) {
|
||||
for player in descriptors.iter() {
|
||||
if player.user_id.is_none() {
|
||||
let player_id = player.player_id;
|
||||
if let Some(fake_player) = players.get(&player_id) {
|
||||
fake_player.on_init(&descriptors, player_id).await;
|
||||
} else {
|
||||
log::warn!("No fake user implementation for player {}", player_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some(msg) = rx.recv().await {
|
||||
match msg {
|
||||
Message::Ready { real_players } => {
|
||||
for fake in players.values() {
|
||||
fake.on_ready(&real_players).await;
|
||||
}
|
||||
},
|
||||
Message::Stop => {
|
||||
for fake in players.values() {
|
||||
fake.on_end().await;
|
||||
}
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
8
rc_multiplayer/src/matches/fake/mod.rs
Normal file
8
rc_multiplayer/src/matches/fake/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod handler;
|
||||
pub use handler::Handler;
|
||||
|
||||
mod traits;
|
||||
pub use traits::FakeUser;
|
||||
|
||||
mod experimental;
|
||||
pub use experimental::ExperimentalPlayer;
|
||||
7
rc_multiplayer/src/matches/fake/traits.rs
Normal file
7
rc_multiplayer/src/matches/fake/traits.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[async_trait::async_trait]
|
||||
pub trait FakeUser: Send + Sync {
|
||||
async fn on_init(&self, descriptors: &Vec<oj_rc_core::persist::user::PlayerDescriptor>, player_id: u8);
|
||||
async fn on_ready(&self, real_players: &std::collections::HashMap<u8, crate::matches::generic::UserSender>);
|
||||
//fn on_damage(&self, data: &rlnl::events::ingame::DestroyCubesFull);
|
||||
async fn on_end(&self);
|
||||
}
|
||||
Reference in New Issue
Block a user