|
|
|
@ -2,7 +2,6 @@ use rumqttc::{MqttOptions, Client, QoS, LastWill};
|
|
|
|
|
use bytes::Bytes;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
use std::thread;
|
|
|
|
|
use std::iter::Enumerate;
|
|
|
|
|
use std::collections::hash_map::HashMap;
|
|
|
|
|
use prost::Message;
|
|
|
|
|
use std::io::Cursor;
|
|
|
|
@ -143,17 +142,49 @@ struct MQTTMessage {
|
|
|
|
|
payload: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct Event {
|
|
|
|
|
divisions: HashMap<i32, Division>,
|
|
|
|
|
field_sets: HashMap<i32, FieldSet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Event {
|
|
|
|
|
fn from_match_list(msg: BackendMessage) -> Event {
|
|
|
|
|
let mut divisions: HashMap<i32, Division> = HashMap::new();
|
|
|
|
|
match msg.data.match_list {
|
|
|
|
|
Some(matches) => {
|
|
|
|
|
for m in matches.matches.iter() {
|
|
|
|
|
fn new() -> Event {
|
|
|
|
|
Event{
|
|
|
|
|
divisions: HashMap::new(),
|
|
|
|
|
field_sets: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: remove extra entries instead of just adding new ones
|
|
|
|
|
fn parse_field_sets(self: &mut Event, sets: tm::FieldSetList) {
|
|
|
|
|
for set in sets.field_sets {
|
|
|
|
|
let mut fields = Vec::new();
|
|
|
|
|
for field in &set.fields {
|
|
|
|
|
fields.push(Field{
|
|
|
|
|
name: String::from(field.name()),
|
|
|
|
|
id: field.id(),
|
|
|
|
|
last_known_match: None,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
self.field_sets.insert(set.id(), FieldSet{
|
|
|
|
|
fields,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_division_list(self: &mut Event, division_list: tm::DivisionList) {
|
|
|
|
|
for division in division_list.divisions {
|
|
|
|
|
self.divisions.insert(division.id() as i32, Division{
|
|
|
|
|
name: String::from(division.name()),
|
|
|
|
|
matches: Vec::new(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_match_list(self: &mut Event, match_list: tm::MatchList) {
|
|
|
|
|
let mut matches: HashMap<i32, Vec<Match>> = HashMap::new();
|
|
|
|
|
for m in match_list.matches.iter() {
|
|
|
|
|
let match_tuple = MatchTuple{
|
|
|
|
|
division: m.division.unwrap(),
|
|
|
|
|
round: int_to_round(m.round.unwrap()),
|
|
|
|
@ -161,53 +192,49 @@ impl Event {
|
|
|
|
|
match_num: m.r#match.unwrap(),
|
|
|
|
|
session: m.session.unwrap(),
|
|
|
|
|
};
|
|
|
|
|
match divisions.get_mut(&match_tuple.division) {
|
|
|
|
|
Some(division) => {
|
|
|
|
|
division.matches.push(Match{
|
|
|
|
|
match matches.get_mut(&match_tuple.division) {
|
|
|
|
|
Some(match_list) => {
|
|
|
|
|
match_list.push(Match{
|
|
|
|
|
match_tuple: match_tuple.clone(),
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
None => {
|
|
|
|
|
let mut new_division = Division{
|
|
|
|
|
name: String::from(""),
|
|
|
|
|
matches: Vec::new(),
|
|
|
|
|
field_set: None,
|
|
|
|
|
};
|
|
|
|
|
new_division.matches.push(Match{
|
|
|
|
|
let mut new_match_list = Vec::new();
|
|
|
|
|
new_match_list.push(Match{
|
|
|
|
|
match_tuple: match_tuple.clone(),
|
|
|
|
|
});
|
|
|
|
|
divisions.insert(match_tuple.division, new_division);
|
|
|
|
|
matches.insert(match_tuple.division, new_match_list);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
None => log::warn!("Parsed match list without match_list"),
|
|
|
|
|
};
|
|
|
|
|
return Event{
|
|
|
|
|
divisions,
|
|
|
|
|
};
|
|
|
|
|
for (id, match_list) in matches {
|
|
|
|
|
match self.divisions.get_mut(&id) {
|
|
|
|
|
None => log::warn!("parsed match list with nonexistant division {}", id),
|
|
|
|
|
Some(division) => division.matches = match_list,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct Division {
|
|
|
|
|
name: String,
|
|
|
|
|
matches: Vec<Match>,
|
|
|
|
|
field_set: Option<FieldSet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct FieldSet {
|
|
|
|
|
fields: Vec<Field>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct Field {
|
|
|
|
|
name: String,
|
|
|
|
|
current_match: u32,
|
|
|
|
|
id: i32,
|
|
|
|
|
last_known_match: Option<MatchTuple>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
|
|
|
struct Match {
|
|
|
|
|
match_tuple: MatchTuple,
|
|
|
|
|
}
|
|
|
|
@ -420,13 +447,6 @@ struct TMClient {
|
|
|
|
|
connected: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TMConnection {
|
|
|
|
|
notices: mpsc::Receiver<Box<tm::Notice>>,
|
|
|
|
|
responses: mpsc::Receiver<Box<BackendMessage>>,
|
|
|
|
|
requests: mpsc::Sender<Box<BackendMessage>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TCP_BUFFER_SIZE: usize = 10000;
|
|
|
|
|
impl TMClient {
|
|
|
|
|
fn new(uuid: [u8; 16], client_name: [u8; 32], password: String, username: [u8; 16]) -> (TMClient, TMConnection) {
|
|
|
|
@ -566,7 +586,20 @@ impl TMClient {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NoticeCallback = fn(tm::Notice, Event, &TMConnection) -> (Vec<MQTTMessage>, Event);
|
|
|
|
|
struct TMConnection {
|
|
|
|
|
notices: mpsc::Receiver<Box<tm::Notice>>,
|
|
|
|
|
responses: mpsc::Receiver<Box<BackendMessage>>,
|
|
|
|
|
requests: mpsc::Sender<Box<BackendMessage>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TMConnection {
|
|
|
|
|
fn request(self: &TMConnection, request_id: u32, data: tm::BackendMessageData) -> BackendMessage {
|
|
|
|
|
self.requests.send(Box::new(BackendMessage::new(request_id, data))).unwrap();
|
|
|
|
|
return *self.responses.recv().unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type NoticeCallback = fn(tm::Notice, &mut Event, &TMConnection) -> Vec<MQTTMessage>;
|
|
|
|
|
|
|
|
|
|
fn get_affected_match(notice: &tm::Notice) -> Option<MatchTuple> {
|
|
|
|
|
match ¬ice.affected_match {
|
|
|
|
@ -707,14 +740,14 @@ fn get_game_score(notice: &tm::Notice) -> Option<GameScore> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_score_change(notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
fn on_score_change(notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
match get_affected_match(¬ice) {
|
|
|
|
|
None => (Vec::new(), event),
|
|
|
|
|
None => Vec::new(),
|
|
|
|
|
Some(tuple) => {
|
|
|
|
|
// Use `event` to figure out which arena topic to publish to
|
|
|
|
|
// Also add the match score topic based on the tuple
|
|
|
|
|
match get_game_score(¬ice) {
|
|
|
|
|
None => (Vec::new(), event),
|
|
|
|
|
None => Vec::new(),
|
|
|
|
|
Some(score) => {
|
|
|
|
|
let serialized = serde_json::to_string(&score).unwrap();
|
|
|
|
|
let game_topic = format!("division/{}/{:?}/{}/score", tuple.division, tuple.round, tuple.match_num);
|
|
|
|
@ -723,78 +756,65 @@ fn on_score_change(notice: tm::Notice, event: Event, _connection: &TMConnection)
|
|
|
|
|
topic: game_topic,
|
|
|
|
|
payload: serialized,
|
|
|
|
|
});
|
|
|
|
|
return (out, event);
|
|
|
|
|
return out;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_start(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_match_start(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_cancel(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_match_cancel(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_reset(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_match_reset(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_assigned(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_match_assigned(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_active_field_changed(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_active_field_changed(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_rankings_updated(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_rankings_updated(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_event_status_updated(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_event_status_updated(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_elim_alliance_update(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_elim_alliance_update(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_elim_unavail_teams_update(_notice: tm::Notice, event: Event, _connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return (Vec::new(), event);
|
|
|
|
|
fn on_elim_unavail_teams_update(_notice: tm::Notice, event: &mut Event, _connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
return Vec::new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn _on_match_list_update(connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
let mut get_match_list_tuple = tm::MatchTuple::default();
|
|
|
|
|
get_match_list_tuple.division = Some(0);
|
|
|
|
|
get_match_list_tuple.round = None;
|
|
|
|
|
get_match_list_tuple.instance = Some(0);
|
|
|
|
|
get_match_list_tuple.r#match = Some(0);
|
|
|
|
|
get_match_list_tuple.session = Some(0);
|
|
|
|
|
|
|
|
|
|
let mut get_match_list_data = tm::BackendMessageData::default();
|
|
|
|
|
get_match_list_data.match_tuple = Some(get_match_list_tuple);
|
|
|
|
|
|
|
|
|
|
let get_match_list_req = BackendMessage::new(1002, get_match_list_data.clone());
|
|
|
|
|
connection.requests.send(Box::new(get_match_list_req)).unwrap();
|
|
|
|
|
|
|
|
|
|
let get_match_list_resp = connection.responses.recv().unwrap();
|
|
|
|
|
|
|
|
|
|
let new_event = Event::from_match_list(*get_match_list_resp);
|
|
|
|
|
fn on_match_list_update(_notice: tm::Notice, event: &mut Event, connection: &TMConnection) -> Vec<MQTTMessage> {
|
|
|
|
|
let mut messages = Vec::new();
|
|
|
|
|
for (division_id, division) in new_event.divisions.iter() {
|
|
|
|
|
let match_list_resp = connection.request(1002, tm::BackendMessageData::default());
|
|
|
|
|
match match_list_resp.data.match_list {
|
|
|
|
|
None => {},
|
|
|
|
|
Some(match_list) => {
|
|
|
|
|
event.parse_match_list(match_list);
|
|
|
|
|
for (division_id, division) in &event.divisions {
|
|
|
|
|
messages.push(MQTTMessage{
|
|
|
|
|
topic: format!("division/{}/schedule", division_id),
|
|
|
|
|
payload: serde_json::to_string(&division.matches).unwrap(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return (messages, new_event);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_match_list_update(_notice: tm::Notice, _event: Event, connection: &TMConnection) -> (Vec<MQTTMessage>, Event) {
|
|
|
|
|
return _on_match_list_update(connection);
|
|
|
|
|
return messages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
@ -845,15 +865,30 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let (messages, mut event) = _on_match_list_update(&tm_connection);
|
|
|
|
|
for message in messages {
|
|
|
|
|
let result = client.publish(message.topic, QoS::AtMostOnce, true, message.payload);
|
|
|
|
|
match result {
|
|
|
|
|
Ok(_) => {},
|
|
|
|
|
Err(error) => log::error!("Publish error: {}", error),
|
|
|
|
|
let mut event = Event::new();
|
|
|
|
|
let division_list_resp = tm_connection.request(200, tm::BackendMessageData::default());
|
|
|
|
|
event.parse_division_list(division_list_resp.data.division_list.unwrap());
|
|
|
|
|
|
|
|
|
|
let field_set_resp = tm_connection.request(300, tm::BackendMessageData::default());
|
|
|
|
|
event.parse_field_sets(field_set_resp.data.field_set_list.unwrap());
|
|
|
|
|
|
|
|
|
|
let match_list_resp = tm_connection.request(1002, tm::BackendMessageData::default());
|
|
|
|
|
event.parse_match_list(match_list_resp.data.match_list.unwrap());
|
|
|
|
|
|
|
|
|
|
for (field_set_id, field_set) in &event.field_sets {
|
|
|
|
|
for field in &field_set.fields {
|
|
|
|
|
let mut field_req = tm::BackendMessageData::default();
|
|
|
|
|
let mut field_data = tm::OnFieldMatch::default();
|
|
|
|
|
let mut f = tm::Field::default();
|
|
|
|
|
f.id = Some(field.id);
|
|
|
|
|
f.field_set_id = Some(*field_set_id);
|
|
|
|
|
field_data.field = Some(f);
|
|
|
|
|
field_req.on_field_match = Some(field_data);
|
|
|
|
|
|
|
|
|
|
let field_resp = tm_connection.request(309, field_req);
|
|
|
|
|
println!("Field {}/{}: {:#?}", field_set_id, field.id, field_resp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log::debug!("Initial event: {:?}", event);
|
|
|
|
|
|
|
|
|
|
while running {
|
|
|
|
|
thread::sleep(Duration::from_millis(1000));
|
|
|
|
@ -868,8 +903,7 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Some(callback) => {
|
|
|
|
|
let (messages, next_event) = callback(*notice, event, &tm_connection);
|
|
|
|
|
event = next_event;
|
|
|
|
|
let messages = callback(*notice, &mut event, &tm_connection);
|
|
|
|
|
for message in messages {
|
|
|
|
|
let result = client.publish(message.topic, QoS::AtMostOnce, true, message.payload);
|
|
|
|
|
match result {
|
|
|
|
|