use std::{
io::{self, Read, Write},
sync::Arc,
thread,
};
use mio::Interest;
use crate::{
RLBotConnection, RLBotError, StartingInfo, flat::*, parse_core_message, pkanal,
util::PacketQueue,
};
use super::AgentError;
#[allow(unused_variables)]
pub trait BotAgent {
fn new(
team: u32,
controllable_info: ControllableInfo,
match_configuration: Arc<MatchConfiguration>,
field_info: Arc<FieldInfo>,
packet_queue: &mut PacketQueue,
) -> Self;
fn tick(&mut self, game_packet: &GamePacket, packet_queue: &mut PacketQueue);
fn on_match_comm(&mut self, match_comm: &MatchComm, packet_queue: &mut PacketQueue) {}
fn on_ball_prediction(
&mut self,
ball_prediction: &BallPrediction,
packet_queue: &mut PacketQueue,
) {
}
fn on_rendering_status(
&mut self,
rendering_status: &RenderingStatus,
packet_queue: &mut PacketQueue,
) {
}
fn on_ping_response(&mut self, ping: &PingResponse, packet_queue: &mut PacketQueue) {}
}
pub fn run_bot_agents<T: BotAgent>(
agent_id: String,
wants_ball_predictions: bool,
wants_comms: bool,
mut connection: RLBotConnection,
) -> Result<(), AgentError> {
connection.send_packet(ConnectionSettings {
agent_id,
wants_ball_predictions,
wants_comms,
close_between_matches: true,
})?;
let StartingInfo {
controllable_team_info,
match_configuration,
field_info,
} = connection.get_starting_info()?;
if controllable_team_info.controllables.is_empty() {
return Ok(());
}
let match_configuration = Arc::new(match_configuration);
let field_info = Arc::new(field_info);
let num_threads = controllable_team_info.controllables.len();
let mut threads = Vec::with_capacity(num_threads);
connection
.stream
.set_nonblocking(true)
.expect("to set nonblocking");
let mut mio_stream = mio::net::TcpStream::from_std(
connection
.stream
.try_clone()
.expect("failed to clone connection stream"),
);
let mut poll = mio::Poll::new().expect("couldn't create mio::Poll");
const INCOMING: mio::Token = mio::Token(0);
const OUTGOING: mio::Token = mio::Token(1);
poll.registry()
.register(&mut mio_stream, INCOMING, Interest::READABLE)
.expect("couldn't register tcp stream as readable");
let (outgoing_sender, outgoing_recver) =
pkanal::unbounded::<Vec<InterfaceMessage>>(poll.registry(), OUTGOING);
for (i, controllable_info) in controllable_team_info.controllables.into_iter().enumerate() {
let (incoming_sender, incoming_recver) = kanal::unbounded::<Arc<CoreMessage>>();
let match_configuration = match_configuration.clone();
let field_info = field_info.clone();
let outgoing_sender = outgoing_sender.clone();
threads.push((
incoming_sender,
thread::Builder::new()
.name(format!(
"Agent thread {i} (index {})",
controllable_info.index,
))
.spawn(move || {
run_bot_agent::<T>(
incoming_recver,
controllable_team_info.team,
controllable_info,
match_configuration,
field_info,
outgoing_sender,
);
})
.unwrap(),
));
}
drop(outgoing_sender);
connection.send_packet(InitComplete {})?;
let mut events = mio::Events::with_capacity(128);
let mut read_buf: Vec<u8> = Vec::with_capacity(1024);
let mut out_buf: Vec<u8> = Vec::new();
let mut writable_registered = false;
'main: loop {
poll.poll(&mut events, None)
.expect("couldn't poll with mio");
for event in &events {
match event.token() {
INCOMING => {
if event.is_writable() && !out_buf.is_empty() {
match flush_pending(&mut mio_stream, &mut out_buf) {
Ok(true) => {
poll.registry()
.reregister(&mut mio_stream, INCOMING, Interest::READABLE)
.expect("couldn't reregister tcp stream");
writable_registered = false;
}
Ok(false) => {}
Err(e) => return Err(RLBotError::Connection(e).into()),
}
}
if event.is_readable() {
'incoming: loop {
match drain_socket(&mut mio_stream, &mut read_buf) {
Ok(false) => {}
Ok(true) => break 'incoming,
Err(e) => return Err(RLBotError::Connection(e).into()),
}
}
'packets: loop {
if read_buf.len() < 2 {
break 'packets;
}
let data_len = u16::from_be_bytes([read_buf[0], read_buf[1]]);
let frame_len = data_len as usize + 2;
if read_buf.len() < frame_len {
break 'packets;
}
let frame: Vec<u8> = read_buf.drain(..frame_len).collect();
let packet = Arc::new(parse_core_message(&frame[2..])?);
for (incoming_sender, _) in &threads {
if incoming_sender.send(packet.clone()).is_err() {
return Err(AgentError::AgentPanic);
}
}
if matches!(&*packet, CoreMessage::DisconnectSignal(_)) {
break 'main;
}
}
}
}
OUTGOING => 'outgoing: loop {
let Ok(maybe_msgs) = outgoing_recver.try_recv() else {
break 'main;
};
let Some(p) = maybe_msgs else {
break 'outgoing;
};
out_buf.extend(connection.build_interface_messages(p.into_iter()));
if !out_buf.is_empty() && !writable_registered {
poll.registry()
.reregister(
&mut mio_stream,
INCOMING,
Interest::READABLE | Interest::WRITABLE,
)
.expect("couldn't reregister tcp stream");
writable_registered = true;
}
},
_ => unreachable!(),
}
}
}
for (_, handle) in threads {
handle.join().unwrap();
}
Ok(())
}
fn drain_socket(mio_stream: &mut mio::net::TcpStream, read_buf: &mut Vec<u8>) -> io::Result<bool> {
let mut scratch = [0u8; 8192];
match mio_stream.read(&mut scratch) {
Ok(n) if n > 0 => {
read_buf.extend_from_slice(&scratch[..n]);
Ok(false)
}
Ok(_) => Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"connection to core closed",
)),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(true),
Err(e) => Err(e),
}
}
fn flush_pending(mio_stream: &mut mio::net::TcpStream, out_buf: &mut Vec<u8>) -> io::Result<bool> {
let mut sent = 0;
while sent < out_buf.len() {
match mio_stream.write(&out_buf[sent..]) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"socket stopped accepting data",
));
}
Ok(n) => sent += n,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(e) => return Err(e),
}
}
if sent == out_buf.len() {
out_buf.clear();
Ok(true)
} else {
out_buf.drain(..sent);
Ok(false)
}
}
fn run_bot_agent<T: BotAgent>(
incoming_recver: kanal::Receiver<Arc<CoreMessage>>,
team: u32,
controllable_info: ControllableInfo,
match_configuration: Arc<MatchConfiguration>,
field_info: Arc<FieldInfo>,
outgoing_sender: pkanal::Sender<Vec<InterfaceMessage>>,
) {
let mut outgoing_queue = PacketQueue::default();
let mut agent = T::new(
team,
controllable_info,
match_configuration,
field_info,
&mut outgoing_queue,
);
outgoing_sender
.send(outgoing_queue.empty())
.expect("Couldn't send outgoing");
loop {
let Ok(packet) = incoming_recver.recv() else {
panic!("channel recv failed")
};
match &*packet {
CoreMessage::DisconnectSignal(_) => break,
CoreMessage::GamePacket(x) => {
agent.tick(x, &mut outgoing_queue);
}
CoreMessage::MatchComm(x) => {
agent.on_match_comm(x, &mut outgoing_queue);
}
CoreMessage::BallPrediction(x) => {
agent.on_ball_prediction(x, &mut outgoing_queue);
}
CoreMessage::RenderingStatus(x) => {
agent.on_rendering_status(x, &mut outgoing_queue);
}
CoreMessage::PingResponse(x) => {
agent.on_ping_response(x, &mut outgoing_queue);
}
CoreMessage::PingRequest(x) => {
outgoing_queue.push(PingResponse { cookie: x.cookie });
}
CoreMessage::FieldInfo(_)
| CoreMessage::MatchConfiguration(_)
| CoreMessage::ControllableTeamInfo(_) => {
unreachable!("Unexpected packet; should not be able to receive this packet type.")
}
}
if outgoing_queue.internal_queue.is_empty() {
continue; }
outgoing_sender
.send(outgoing_queue.empty())
.expect("Couldn't send outgoing");
}
drop(incoming_recver);
outgoing_sender.drop_and_wake();
}