module separation

This commit is contained in:
catvayor 2024-06-12 16:41:24 +02:00
parent 4439f6b4db
commit c4426de586
4 changed files with 602 additions and 560 deletions

88
src/admin.rs Normal file
View file

@ -0,0 +1,88 @@
use rocket::{
response::stream::{Event, EventStream},
serde::json::Json,
tokio::{
select,
time::{self},
},
Route, Shutdown, State,
};
use rocket_dyn_templates::{context, Template};
use crate::global::*;
#[get("/?<tok>&<dbg>")]
fn admin_page(
tok: Option<AdminKey>,
dbg: Option<bool>,
admin_key: &State<AdminKey>,
) -> Option<Template> {
if tok == Some(admin_key.to_string()) {
Some(Template::render(
"admin",
context! { tok: tok.unwrap(), dbg: dbg.unwrap_or(false) },
))
} else {
None
}
}
#[patch("/<id>?<tok>", data = "<nstate>")]
fn admin_set_state(
tok: Option<AdminKey>,
id: &str,
admin_key: &State<AdminKey>,
nstate: Json<TrackedState>,
tracking: &State<Tracking>,
evt_queue: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
if tok == Some(admin_key.to_string()) {
let tracking_lock = tracking.read().unwrap();
let tracked = tracking_lock.get(&id.to_string()).unwrap();
tracked.write().unwrap().state = nstate.into_inner();
state_update(&tracked.read().unwrap(), &evt_queue, &admin_queue);
Some(())
} else {
None
}
}
#[get("/events?<tok>")]
fn admin_events<'a>(
tok: Option<AdminKey>,
admin_key: &State<AdminKey>,
admin_queue: &'a State<AdminEventQueue>,
tracking: &State<Tracking>,
mut shutdown: Shutdown,
) -> Option<EventStream![Event + 'a]> {
if tok == Some(admin_key.to_string()) {
let full_info: Vec<AdminTrackedInfo> = tracking
.read()
.unwrap()
.iter()
.map(|(_, tracked)| admin_view(&tracked.read().unwrap()))
.collect();
Some(EventStream! {
yield Event::json(&full_info).event("full_update");
let mut interval = time::interval(EVENT_TIMEOUT);
loop {
select!{
_ = interval.tick() =>{
for evt in evts_to_send(admin_queue){
//println!("{:?}", evt);
yield evt;
}
},
_ = &mut shutdown => break
}
}
})
} else {
None
}
}
pub fn routes() -> Vec<Route> {
routes![admin_page, admin_set_state, admin_events]
}

269
src/global.rs Normal file
View file

@ -0,0 +1,269 @@
use rand::Rng;
use rocket::{
response::stream::Event,
serde::{Deserialize, Serialize},
tokio::time::{Duration, Instant},
};
use std::{
collections::{HashMap, VecDeque},
sync::{Arc, RwLock},
};
pub const BLURRED_MOVE: (f32, f32) = (0.0005, 0.0005);
pub const BONUS_TIMEOUT: Duration = Duration::from_millis(5000);
pub const EVENT_TIMEOUT: Duration = Duration::from_millis(100);
#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
pub enum TrackedState {
Conscrit {
invisible: bool,
blurred: bool,
captured: bool,
mallette: bool,
invisibility_codes: u32,
blur_codes: u32,
},
Vieux {
color: u8,
invisible: bool,
},
}
use TrackedState::{Conscrit, Vieux};
impl TrackedState {
pub fn invisible(&self) -> bool {
match self {
Conscrit { invisible, .. } => *invisible,
Vieux { invisible, .. } => *invisible,
}
}
pub fn blurred(&self) -> bool {
match self {
Conscrit { blurred, .. } => *blurred,
Vieux { .. } => false,
}
}
pub fn global_viewed(&self) -> bool {
match self {
Conscrit {
captured,
mallette,
invisible,
..
} => (*captured || *mallette) && !*invisible,
Vieux { invisible, .. } => !*invisible,
}
}
pub fn color(&self) -> u8 {
match self {
Vieux { color, .. } => *color,
Conscrit { captured, .. } => {
if *captured {
1
} else {
0
}
}
}
}
pub fn admin_color(&self) -> u8 {
match self {
Vieux { color, invisible } => {
if *invisible {
2
} else {
*color
}
}
Conscrit {
invisible,
captured,
..
} => {
if *invisible {
2
} else if *captured {
1
} else {
0
}
}
}
}
}
pub struct Tracked {
pub id: String,
pub name: String,
pub pos: (f32, f32),
pub state: TrackedState,
}
pub fn build_conscrit(id: String, name: String) -> Tracked {
Tracked {
id: id,
name: name,
pos: (0.0, 0.0),
state: TrackedState::Conscrit {
invisible: false,
blurred: false,
captured: false,
mallette: false,
invisibility_codes: 0,
blur_codes: 0,
},
}
}
pub fn build_vieux(id: String, name: String) -> Tracked {
Tracked {
id: id,
name: name,
pos: (0.0, 0.0),
state: TrackedState::Vieux {
invisible: true,
color: 1,
},
}
}
pub struct QueuedEvent {
pub date: Instant,
pub evt: Event,
}
impl QueuedEvent {
pub fn expired(&self) -> bool {
self.date.elapsed() >= EVENT_TIMEOUT
}
}
impl From<Event> for QueuedEvent {
fn from(evt: Event) -> QueuedEvent {
QueuedEvent {
date: Instant::now(),
evt,
}
}
}
impl From<QueuedEvent> for Event {
fn from(queued_evt: QueuedEvent) -> Event {
queued_evt.evt
}
}
pub type Tracking = Arc<RwLock<HashMap<String, RwLock<Tracked>>>>;
pub type TrackingEventQueue = Arc<RwLock<HashMap<String, RwLock<VecDeque<QueuedEvent>>>>>;
pub type AdminEventQueue = Arc<RwLock<VecDeque<QueuedEvent>>>;
pub type AdminKey = String;
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct TrackedInfo {
pub name: String,
pub pos: (f32, f32),
pub color: u8,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct AdminTrackedInfo {
pub name: String,
pub id: String,
pub pos: (f32, f32),
pub color: u8,
pub state: TrackedState,
}
impl From<AdminTrackedInfo> for TrackedInfo {
fn from(admin_info: AdminTrackedInfo) -> TrackedInfo {
TrackedInfo {
name: admin_info.name,
pos: admin_info.pos,
color: admin_info.color,
}
}
}
pub fn base_view(team: &Tracked) -> TrackedInfo {
TrackedInfo {
name: team.name.clone(),
pos: team.pos,
color: team.state.color(),
}
}
pub fn admin_view(team: &Tracked) -> AdminTrackedInfo {
AdminTrackedInfo {
name: team.name.clone(),
id: team.id.clone(),
pos: team.pos,
color: team.state.admin_color(),
state: team.state.clone(),
}
}
pub fn apparent_info(watcher: &Tracked, team: &Tracked) -> Option<TrackedInfo> {
if watcher.id == team.id {
None
} else if let Conscrit {
captured, mallette, ..
} = watcher.state
{
if captured {
if team.state.invisible() {
None
} else if team.state.blurred() {
let mut rng = rand::thread_rng();
let (lat, lon) = team.pos;
Some(TrackedInfo {
pos: (
lat + BLURRED_MOVE.0 * (rng.gen::<f32>() * 2.0 - 1.0),
lon + BLURRED_MOVE.1 * (rng.gen::<f32>() * 2.0 - 1.0),
),
..base_view(team)
})
} else {
Some(base_view(team))
}
} else {
if mallette || team.state.global_viewed() {
Some(base_view(team))
} else {
None
}
}
} else {
Some(admin_view(team).into())
}
}
pub fn evts_to_send(evt_queue: &RwLock<VecDeque<QueuedEvent>>) -> Vec<Event> {
evt_queue
.read()
.unwrap()
.iter()
.filter(|qevt| !qevt.expired())
.map(|qevt| qevt.evt.clone())
.collect()
}
pub fn state_update(
tracked: &Tracked,
evt_queues: &TrackingEventQueue,
admin_queue: &AdminEventQueue,
) {
evt_queues
.read()
.unwrap()
.get(&tracked.id)
.unwrap()
.write()
.unwrap()
.push_back(Event::json(&admin_view(tracked)).event("self_info").into());
admin_queue
.write()
.unwrap()
.push_back(Event::json(&admin_view(tracked)).event("update").into());
}

View file

@ -1,561 +1,23 @@
#[macro_use]
extern crate rocket;
use rand::Rng;
use rocket::{
fs::{relative, FileServer},
response::stream::{Event, EventStream},
serde::{json::Json, Deserialize, Serialize},
response::stream::Event,
tokio::{
self, select,
time::{self, sleep, Duration, Instant},
time::{self, Duration},
},
Shutdown, State,
};
use rocket_dyn_templates::{context, Template};
use rocket_dyn_templates::Template;
use std::{
collections::{HashMap, VecDeque},
sync::{Arc, RwLock},
};
#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
enum TrackedState {
Conscrit {
invisible: bool,
blurred: bool,
captured: bool,
mallette: bool,
invisibility_codes: u32,
blur_codes: u32,
},
Vieux {
color: u8,
invisible: bool,
},
}
use TrackedState::{Conscrit, Vieux};
const BLURRED_MOVE: (f32, f32) = (0.0005, 0.0005);
const BONUS_TIMEOUT: Duration = Duration::from_millis(5000);
const EVENT_TIMEOUT: Duration = Duration::from_millis(100);
impl TrackedState {
fn invisible(&self) -> bool {
match self {
Conscrit { invisible, .. } => *invisible,
Vieux { invisible, .. } => *invisible,
}
}
fn blurred(&self) -> bool {
match self {
Conscrit { blurred, .. } => *blurred,
Vieux { .. } => false,
}
}
fn global_viewed(&self) -> bool {
match self {
Conscrit {
captured,
mallette,
invisible,
..
} => (*captured || *mallette) && !*invisible,
Vieux { invisible, .. } => !*invisible,
}
}
fn color(&self) -> u8 {
match self {
Vieux { color, .. } => *color,
Conscrit { captured, .. } => {
if *captured {
1
} else {
0
}
}
}
}
fn admin_color(&self) -> u8 {
match self {
Vieux { color, invisible } => {
if *invisible {
2
} else {
*color
}
}
Conscrit {
invisible,
captured,
..
} => {
if *invisible {
2
} else if *captured {
1
} else {
0
}
}
}
}
}
struct Tracked {
id: String,
name: String,
pos: (f32, f32),
state: TrackedState,
}
fn build_conscrit(id: String, name: String) -> Tracked {
Tracked {
id: id,
name: name,
pos: (0.0, 0.0),
state: TrackedState::Conscrit {
invisible: false,
blurred: false,
captured: false,
mallette: false,
invisibility_codes: 0,
blur_codes: 0,
},
}
}
fn build_vieux(id: String, name: String) -> Tracked {
Tracked {
id: id,
name: name,
pos: (0.0, 0.0),
state: TrackedState::Vieux {
invisible: true,
color: 1,
},
}
}
struct QueuedEvent {
date: Instant,
evt: Event,
}
impl QueuedEvent {
fn expired(&self) -> bool {
self.date.elapsed() >= EVENT_TIMEOUT
}
}
impl From<Event> for QueuedEvent {
fn from(evt: Event) -> QueuedEvent {
QueuedEvent {
date: Instant::now(),
evt,
}
}
}
impl From<QueuedEvent> for Event {
fn from(queued_evt: QueuedEvent) -> Event {
queued_evt.evt
}
}
type Tracking = Arc<RwLock<HashMap<String, RwLock<Tracked>>>>;
type TrackingEventQueue = Arc<RwLock<HashMap<String, RwLock<VecDeque<QueuedEvent>>>>>;
type AdminEventQueue = Arc<RwLock<VecDeque<QueuedEvent>>>;
type AdminKey = String;
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct TrackedInfo {
name: String,
pos: (f32, f32),
color: u8,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct AdminTrackedInfo {
name: String,
id: String,
pos: (f32, f32),
color: u8,
state: TrackedState,
}
impl From<AdminTrackedInfo> for TrackedInfo {
fn from(admin_info: AdminTrackedInfo) -> TrackedInfo {
TrackedInfo {
name: admin_info.name,
pos: admin_info.pos,
color: admin_info.color,
}
}
}
fn base_view(team: &Tracked) -> TrackedInfo {
TrackedInfo {
name: team.name.clone(),
pos: team.pos,
color: team.state.color(),
}
}
fn admin_view(team: &Tracked) -> AdminTrackedInfo {
AdminTrackedInfo {
name: team.name.clone(),
id: team.id.clone(),
pos: team.pos,
color: team.state.admin_color(),
state: team.state.clone(),
}
}
fn apparent_info(watcher: &Tracked, team: &Tracked) -> Option<TrackedInfo> {
if watcher.id == team.id {
None
} else if let Conscrit {
captured, mallette, ..
} = watcher.state
{
if captured {
if team.state.invisible() {
None
} else if team.state.blurred() {
let mut rng = rand::thread_rng();
let (lat, lon) = team.pos;
Some(TrackedInfo {
pos: (
lat + BLURRED_MOVE.0 * (rng.gen::<f32>() * 2.0 - 1.0),
lon + BLURRED_MOVE.1 * (rng.gen::<f32>() * 2.0 - 1.0),
),
..base_view(team)
})
} else {
Some(base_view(team))
}
} else {
if mallette || team.state.global_viewed() {
Some(base_view(team))
} else {
None
}
}
} else {
Some(admin_view(team).into())
}
}
#[get("/track/<id>?<gpslog>&<dbg>")]
fn tracked_view(
id: &str,
gpslog: Option<bool>,
dbg: Option<bool>,
tracking: &State<Tracking>,
) -> Option<Template> {
if let Some(tracked) = tracking.read().unwrap().get(&id.to_string()) {
Some(Template::render(
match tracked.read().unwrap().state {
Vieux { .. } => "vieux",
Conscrit { .. } => "conscrit",
},
context! {
name: &tracked.read().unwrap().name,
id: &id,
gpslog: gpslog.unwrap_or(true),
dbg: dbg.unwrap_or(false),
},
))
} else {
None
}
}
fn evts_to_send(evt_queue: &RwLock<VecDeque<QueuedEvent>>) -> Vec<Event> {
evt_queue
.read()
.unwrap()
.iter()
.filter(|qevt| !qevt.expired())
.map(|qevt| qevt.evt.clone())
.collect()
}
fn evts_for(id: &str, evt_queues: &TrackingEventQueue) -> Vec<Event> {
evts_to_send(evt_queues.read().unwrap().get(&id.to_string()).unwrap())
}
#[get("/track/<id>/events")]
fn tracked_events<'a>(
id: &'a str,
evt_queue: &'a State<TrackingEventQueue>,
mut shutdown: Shutdown,
) -> Option<EventStream![Event + 'a]> {
if evt_queue.read().unwrap().contains_key(&id.to_string()) {
Some(EventStream! {
let mut interval = time::interval(EVENT_TIMEOUT);
loop {
select!{
_ = interval.tick() =>{
for evt in evts_for(id, evt_queue){
//println!("{:?}", evt);
yield evt;
}
},
_ = &mut shutdown => break
}
}
})
} else {
None
}
}
#[get("/admin?<tok>&<dbg>")]
fn admin_page(
tok: Option<AdminKey>,
dbg: Option<bool>,
admin_key: &State<AdminKey>,
) -> Option<Template> {
if tok == Some(admin_key.to_string()) {
Some(Template::render(
"admin",
context! { tok: tok.unwrap(), dbg: dbg.unwrap_or(false) },
))
} else {
None
}
}
#[patch("/admin/<id>?<tok>", data = "<nstate>")]
fn admin_set_state(
tok: Option<AdminKey>,
id: &str,
admin_key: &State<AdminKey>,
nstate: Json<TrackedState>,
tracking: &State<Tracking>,
evt_queue: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
if tok == Some(admin_key.to_string()) {
let tracking_lock = tracking.read().unwrap();
let tracked = tracking_lock.get(&id.to_string()).unwrap();
tracked.write().unwrap().state = nstate.into_inner();
state_update(&tracked.read().unwrap(), &evt_queue, &admin_queue);
Some(())
} else {
None
}
}
#[get("/admin/events?<tok>")]
fn admin_events<'a>(
tok: Option<AdminKey>,
admin_key: &State<AdminKey>,
admin_queue: &'a State<AdminEventQueue>,
tracking: &State<Tracking>,
mut shutdown: Shutdown,
) -> Option<EventStream![Event + 'a]> {
if tok == Some(admin_key.to_string()) {
let full_info: Vec<AdminTrackedInfo> = tracking
.read()
.unwrap()
.iter()
.map(|(_, tracked)| admin_view(&tracked.read().unwrap()))
.collect();
Some(EventStream! {
yield Event::json(&full_info).event("full_update");
let mut interval = time::interval(EVENT_TIMEOUT);
loop {
select!{
_ = interval.tick() =>{
for evt in evts_to_send(admin_queue){
//println!("{:?}", evt);
yield evt;
}
},
_ = &mut shutdown => break
}
}
})
} else {
None
}
}
fn state_update(tracked: &Tracked, evt_queues: &TrackingEventQueue, admin_queue: &AdminEventQueue) {
evt_queues
.read()
.unwrap()
.get(&tracked.id)
.unwrap()
.write()
.unwrap()
.push_back(Event::json(&admin_view(tracked)).event("self_info").into());
admin_queue
.write()
.unwrap()
.push_back(Event::json(&admin_view(tracked)).event("update").into());
}
#[put("/track/<id>/pos?<lat>&<long>")]
fn store_pos(
id: &str,
lat: f32,
long: f32,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) {
if let Some(tracked) = tracking.read().unwrap().get(&id.to_string()) {
tracked.write().unwrap().pos = (lat, long);
state_update(&tracked.read().unwrap(), &evt_queues, &admin_queue);
}
}
#[put("/track/<id>/state?<inv>&<col>")]
fn set_state(
id: &str,
inv: bool,
col: u8,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Vieux {
ref mut invisible,
ref mut color,
} = tracked.state
{
*invisible = inv;
*color = col;
state_update(&tracked, &evt_queues, &admin_queue);
Some(())
} else {
None
}
}
#[put("/track/<id>/vanish")]
async fn activate_invisibility(
id: &str,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Conscrit {
ref mut invisible,
ref mut invisibility_codes,
..
} = tracked.state
{
if *invisibility_codes > 0 {
*invisibility_codes -= 1;
*invisible = true;
state_update(&tracked, &evt_queues, &admin_queue);
let track_clone = (*tracking).clone();
let queue_clone = (*evt_queues).clone();
let admin_clone = (*admin_queue).clone();
let id_str = id.to_string();
tokio::spawn(async move {
sleep(BONUS_TIMEOUT).await;
if let Conscrit {
ref mut invisible, ..
} = track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.write()
.unwrap()
.state
{
*invisible = false;
}
state_update(
&track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.read()
.unwrap(),
&queue_clone,
&admin_clone,
);
});
Some(())
} else {
None
}
} else {
None
}
}
#[put("/track/<id>/blur")]
async fn activate_blur(
id: &str,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Conscrit {
ref mut blurred,
ref mut blur_codes,
..
} = tracked.state
{
if *blur_codes > 0 {
*blur_codes -= 1;
*blurred = true;
state_update(&tracked, &evt_queues, &admin_queue);
let track_clone = (*tracking).clone();
let queue_clone = (*evt_queues).clone();
let admin_clone = (*admin_queue).clone();
let id_str = id.to_string();
tokio::spawn(async move {
sleep(BONUS_TIMEOUT).await;
if let Conscrit {
ref mut blurred, ..
} = track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.write()
.unwrap()
.state
{
*blurred = false;
}
state_update(
&track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.read()
.unwrap(),
&queue_clone,
&admin_clone,
);
});
Some(())
} else {
None
}
} else {
None
}
}
mod admin;
mod global;
mod track;
use global::*;
#[get("/")]
fn index() -> &'static str {
@ -638,21 +100,9 @@ async fn rocket() -> _ {
.manage(evt_queue.clone())
.manage(admin_evt_queue.clone())
.manage(key)
.mount(
"/",
routes![
index,
store_pos,
tracked_view,
tracked_events,
set_state,
admin_page,
admin_events,
admin_set_state,
activate_invisibility,
activate_blur,
],
)
.mount("/", routes![index])
.mount("/track", track::routes())
.mount("/admin", admin::routes())
.mount("/", FileServer::from(relative!("static")));
tokio::spawn(async move {
let mut clean_interval = time::interval(5 * EVENT_TIMEOUT);

235
src/track.rs Normal file
View file

@ -0,0 +1,235 @@
use rocket::{
response::stream::{Event, EventStream},
tokio::{
self, select,
time::{self, sleep},
},
Route, Shutdown, State,
};
use rocket_dyn_templates::{context, Template};
use crate::global::{TrackedState::*, *};
#[get("/<id>?<gpslog>&<dbg>")]
fn tracked_view(
id: &str,
gpslog: Option<bool>,
dbg: Option<bool>,
tracking: &State<Tracking>,
) -> Option<Template> {
if let Some(tracked) = tracking.read().unwrap().get(&id.to_string()) {
Some(Template::render(
match tracked.read().unwrap().state {
Vieux { .. } => "vieux",
Conscrit { .. } => "conscrit",
},
context! {
name: &tracked.read().unwrap().name,
id: &id,
gpslog: gpslog.unwrap_or(true),
dbg: dbg.unwrap_or(false),
},
))
} else {
None
}
}
fn evts_for(id: &str, evt_queues: &TrackingEventQueue) -> Vec<Event> {
evts_to_send(evt_queues.read().unwrap().get(&id.to_string()).unwrap())
}
#[get("/<id>/events")]
fn tracked_events<'a>(
id: &'a str,
evt_queue: &'a State<TrackingEventQueue>,
mut shutdown: Shutdown,
) -> Option<EventStream![Event + 'a]> {
if evt_queue.read().unwrap().contains_key(&id.to_string()) {
Some(EventStream! {
let mut interval = time::interval(EVENT_TIMEOUT);
loop {
select!{
_ = interval.tick() =>{
for evt in evts_for(id, evt_queue){
//println!("{:?}", evt);
yield evt;
}
},
_ = &mut shutdown => break
}
}
})
} else {
None
}
}
#[put("/<id>/pos?<lat>&<long>")]
fn store_pos(
id: &str,
lat: f32,
long: f32,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) {
if let Some(tracked) = tracking.read().unwrap().get(&id.to_string()) {
tracked.write().unwrap().pos = (lat, long);
state_update(&tracked.read().unwrap(), &evt_queues, &admin_queue);
}
}
#[put("/<id>/state?<inv>&<col>")]
fn set_state(
id: &str,
inv: bool,
col: u8,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Vieux {
ref mut invisible,
ref mut color,
} = tracked.state
{
*invisible = inv;
*color = col;
state_update(&tracked, &evt_queues, &admin_queue);
Some(())
} else {
None
}
}
#[put("/<id>/vanish")]
pub async fn activate_invisibility(
id: &str,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Conscrit {
ref mut invisible,
ref mut invisibility_codes,
..
} = tracked.state
{
if *invisibility_codes > 0 {
*invisibility_codes -= 1;
*invisible = true;
state_update(&tracked, &evt_queues, &admin_queue);
let track_clone = (*tracking).clone();
let queue_clone = (*evt_queues).clone();
let admin_clone = (*admin_queue).clone();
let id_str = id.to_string();
tokio::spawn(async move {
sleep(BONUS_TIMEOUT).await;
if let Conscrit {
ref mut invisible, ..
} = track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.write()
.unwrap()
.state
{
*invisible = false;
}
state_update(
&track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.read()
.unwrap(),
&queue_clone,
&admin_clone,
);
});
Some(())
} else {
None
}
} else {
None
}
}
#[put("/<id>/blur")]
pub async fn activate_blur(
id: &str,
tracking: &State<Tracking>,
evt_queues: &State<TrackingEventQueue>,
admin_queue: &State<AdminEventQueue>,
) -> Option<()> {
let tracking_lock = tracking.read().unwrap();
let tracked = &mut tracking_lock.get(&id.to_string()).unwrap().write().unwrap();
if let Conscrit {
ref mut blurred,
ref mut blur_codes,
..
} = tracked.state
{
if *blur_codes > 0 {
*blur_codes -= 1;
*blurred = true;
state_update(&tracked, &evt_queues, &admin_queue);
let track_clone = (*tracking).clone();
let queue_clone = (*evt_queues).clone();
let admin_clone = (*admin_queue).clone();
let id_str = id.to_string();
tokio::spawn(async move {
sleep(BONUS_TIMEOUT).await;
if let Conscrit {
ref mut blurred, ..
} = track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.write()
.unwrap()
.state
{
*blurred = false;
}
state_update(
&track_clone
.read()
.unwrap()
.get(&id_str)
.unwrap()
.read()
.unwrap(),
&queue_clone,
&admin_clone,
);
});
Some(())
} else {
None
}
} else {
None
}
}
pub fn routes() -> Vec<Route> {
routes![
store_pos,
tracked_view,
tracked_events,
set_state,
activate_invisibility,
activate_blur,
]
}