split property parsing from event parsing:
Build and test / build (pull_request) Successful in 1m56s Details
Build and test / check (pull_request) Successful in 1m55s Details
Build and test / test (pull_request) Failing after 2m34s Details
Build and test / docs (pull_request) Successful in 6m3s Details

High-level properties are now optional, considering there are about a
thousand of them to parse. The high-level properties are a few chosen
ones that I suspect might be useful for most people, with catch-all enum
variants for the less common ones.
This commit is contained in:
Oystein Kristoffer Tveit 2024-05-02 20:59:30 +02:00
parent f5ca2ebde9
commit 643a13a93b
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
8 changed files with 467 additions and 174 deletions

View File

@ -75,6 +75,7 @@ pub enum MpvDataType {
Double(f64),
HashMap(HashMap<String, MpvDataType>),
Null,
MinusOne,
Playlist(Playlist),
String(String),
Usize(usize),

View File

@ -1,29 +1,67 @@
//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc).
use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::{ipc::MpvIpcEvent, Error, ErrorCode, MpvDataType};
use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, Error, ErrorCode, MpvDataType};
/// All possible properties that can be observed through the event system.
///
/// Not all properties are guaranteed to be implemented.
/// If something is missing, please open an issue.
///
/// Otherwise, the property will be returned as a `Property::Unknown` variant.
///
/// See <https://mpv.io/manual/master/#properties> for
/// the upstream list of properties.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Property {
Path(Option<String>),
Pause(bool),
PlaybackTime(Option<f64>),
Duration(Option<f64>),
Metadata(Option<HashMap<String, MpvDataType>>),
Unknown { name: String, data: MpvDataType },
#[serde(rename_all = "kebab-case")]
pub enum EventEndFileReason {
Eof,
Stop,
Quit,
Error,
Redirect,
Unknown,
Unimplemented(String),
}
impl FromStr for EventEndFileReason {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"eof" => Ok(EventEndFileReason::Eof),
"stop" => Ok(EventEndFileReason::Stop),
"quit" => Ok(EventEndFileReason::Quit),
"error" => Ok(EventEndFileReason::Error),
"redirect" => Ok(EventEndFileReason::Redirect),
reason => Ok(EventEndFileReason::Unimplemented(reason.to_string())),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum EventLogMessageLevel {
Info,
Warn,
Error,
Fatal,
Verbose,
Debug,
Trace,
Unimplemented(String),
}
impl FromStr for EventLogMessageLevel {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"info" => Ok(EventLogMessageLevel::Info),
"warn" => Ok(EventLogMessageLevel::Warn),
"error" => Ok(EventLogMessageLevel::Error),
"fatal" => Ok(EventLogMessageLevel::Fatal),
"verbose" => Ok(EventLogMessageLevel::Verbose),
"debug" => Ok(EventLogMessageLevel::Debug),
"trace" => Ok(EventLogMessageLevel::Trace),
level => Ok(EventLogMessageLevel::Unimplemented(level.to_string())),
}
}
}
/// All possible events that can be sent by mpv.
@ -36,29 +74,90 @@ pub enum Property {
/// See <https://mpv.io/manual/master/#list-of-events> for
/// the upstream list of events.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Event {
Shutdown,
StartFile,
EndFile,
StartFile {
playlist_entry_id: usize,
},
EndFile {
reason: EventEndFileReason,
playlist_entry_id: usize,
file_error: Option<String>,
playlist_insert_id: Option<usize>,
playlist_insert_num_entries: Option<usize>,
},
FileLoaded,
TracksChanged,
TrackSwitched,
Idle,
Pause,
Unpause,
Tick,
VideoReconfig,
AudioReconfig,
MetadataUpdate,
Seek,
PlaybackRestart,
PropertyChange { id: usize, property: Property },
Shutdown,
LogMessage {
prefix: String,
level: EventLogMessageLevel,
text: String,
},
Hook {
hook_id: usize,
},
GetPropertyReply,
SetPropertyReply,
CommandReply {
result: String,
},
ClientMessage {
args: Vec<String>,
},
VideoReconfig,
AudioReconfig,
PropertyChange {
id: usize,
name: String,
data: MpvDataType,
},
EventQueueOverflow,
None,
/// Deprecated since mpv v0.33.0
Idle,
/// Deprecated since mpv v0.31.0
Tick,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
TracksChanged,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
TrackSwitched,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
Pause,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
Unpause,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
MetadataUpdate,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
ChapterChange,
ClientMessage { args: Vec<String> },
Unimplemented,
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
ScriptInputDispatch,
/// Catch-all for unimplemented events
Unimplemented(Map<String, Value>),
}
// NOTE: I have not been able to test all of these events,
// so some of the parsing logic might be incorrect.
// In particular, I have not been able to make mpv
// produce any of the commented out events, and since
// the documentation for the most part just says
// "See C API", I have not pursued this further.
//
// If you need this, please open an issue or a PR.
/// Parse a highlevel [`Event`] objects from json.
#[allow(deprecated)]
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
let MpvIpcEvent(event) = raw_event;
@ -73,25 +172,113 @@ pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
match event_name {
"shutdown" => Ok(Event::Shutdown),
"start-file" => Ok(Event::StartFile),
"end-file" => Ok(Event::EndFile),
"start-file" => parse_start_file(event),
"end-file" => parse_end_file(event),
"file-loaded" => Ok(Event::FileLoaded),
"tracks-changed" => Ok(Event::TracksChanged),
"track-switched" => Ok(Event::TrackSwitched),
"idle" => Ok(Event::Idle),
"pause" => Ok(Event::Pause),
"unpause" => Ok(Event::Unpause),
"tick" => Ok(Event::Tick),
"video-reconfig" => Ok(Event::VideoReconfig),
"audio-reconfig" => Ok(Event::AudioReconfig),
"metadata-update" => Ok(Event::MetadataUpdate),
"seek" => Ok(Event::Seek),
"playback-restart" => Ok(Event::PlaybackRestart),
"property-change" => parse_event_property(event)
.map(|(id, property)| Event::PropertyChange { id, property }),
"shutdown" => Ok(Event::Shutdown),
"log-message" => parse_log_message(event),
"hook" => parse_hook(event),
// "get-property-reply" =>
// "set-property-reply" =>
// "command-reply" =>
"client-message" => parse_client_message(event),
"video-reconfig" => Ok(Event::VideoReconfig),
"audio-reconfig" => Ok(Event::AudioReconfig),
"property-change" => parse_property_change(event),
"tick" => Ok(Event::Tick),
"idle" => Ok(Event::Idle),
"tracks-changed" => Ok(Event::TracksChanged),
"track-switched" => Ok(Event::TrackSwitched),
"pause" => Ok(Event::Pause),
"unpause" => Ok(Event::Unpause),
"metadata-update" => Ok(Event::MetadataUpdate),
"chapter-change" => Ok(Event::ChapterChange),
"client-message" => {
_ => Ok(Event::Unimplemented(event.to_owned())),
}
})
}
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, Error> {
let playlist_entry_id = event
.get("playlist_entry_id")
.ok_or(Error(ErrorCode::MissingValue))?
.as_u64()
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
Ok(Event::StartFile { playlist_entry_id })
}
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, Error> {
let reason = event
.get("reason")
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
let playlist_entry_id = event
.get("playlist_entry_id")
.ok_or(Error(ErrorCode::MissingValue))?
.as_u64()
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
let file_error = event
.get("file_error")
.and_then(|v| v.as_str().map(|s| s.to_string()));
let playlist_insert_id = event
.get("playlist_insert_id")
.and_then(|v| v.as_u64().map(|u| u as usize));
let playlist_insert_num_entries = event
.get("playlist_insert_num_entries")
.and_then(|v| v.as_u64().map(|u| u as usize));
Ok(Event::EndFile {
reason: reason
.parse()
.unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
playlist_entry_id,
file_error,
playlist_insert_id,
playlist_insert_num_entries,
})
}
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, Error> {
let prefix = event
.get("prefix")
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
.to_string();
let level = event
.get("level")
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
let text = event
.get("text")
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
.to_string();
Ok(Event::LogMessage {
prefix,
level: level
.parse()
.unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
text,
})
}
fn parse_hook(event: &Map<String, Value>) -> Result<Event, Error> {
let hook_id = event
.get("hook_id")
.ok_or(Error(ErrorCode::MissingValue))?
.as_u64()
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
Ok(Event::Hook { hook_id })
}
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, Error> {
let args = event
.get("args")
.ok_or(Error(ErrorCode::MissingValue))?
@ -106,13 +293,8 @@ pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
.collect::<Result<Vec<String>, Error>>()?;
Ok(Event::ClientMessage { args })
}
_ => Ok(Event::Unimplemented),
}
})
}
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
fn parse_event_property(event: &Map<String, Value>) -> Result<(usize, Property), Error> {
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, Error> {
let id = event
.get("id")
.ok_or(Error(ErrorCode::MissingValue))?
@ -123,38 +305,14 @@ fn parse_event_property(event: &Map<String, Value>) -> Result<(usize, Property),
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
match property_name {
"path" => {
let path = event
.get("data")
.ok_or(Error(ErrorCode::MissingValue))?
.as_str()
.map(|s| s.to_string());
Ok((id, Property::Path(path)))
}
"pause" => {
let pause = event
.get("data")
.ok_or(Error(ErrorCode::MissingValue))?
.as_bool()
.ok_or(Error(ErrorCode::ValueDoesNotContainBool))?;
Ok((id, Property::Pause(pause)))
}
// TODO: missing cases
_ => {
let data = event
.get("data")
.ok_or(Error(ErrorCode::MissingValue))?
.clone();
Ok((
Ok(Event::PropertyChange {
id,
Property::Unknown {
name: property_name.to_string(),
// TODO: fix
data: MpvDataType::Double(data.as_f64().unwrap_or(0.0)),
},
))
}
}
data: json_to_value(&data)?,
})
}

View File

@ -0,0 +1,171 @@
//! JSON parsing logic for properties returned in [`Event::PropertyChange`]
//!
//! This module is used to parse the json data from the `data` field of the
//! [`Event::PropertyChange`] variant. Mpv has about 1000 different properties
//! as of `v0.38.0`, so this module will only implement the most common ones.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{Error, ErrorCode, Event, MpvDataType, PlaylistEntry};
/// All possible properties that can be observed through the event system.
///
/// Not all properties are guaranteed to be implemented.
/// If something is missing, please open an issue.
///
/// Otherwise, the property will be returned as a `Property::Unknown` variant.
///
/// See <https://mpv.io/manual/master/#properties> for
/// the upstream list of properties.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Property {
Path(Option<String>),
Pause(bool),
PlaybackTime(Option<f64>),
Duration(Option<f64>),
Metadata(Option<HashMap<String, MpvDataType>>),
Playlist(Vec<PlaylistEntry>),
PlaylistPos(Option<usize>),
LoopFile(LoopProperty),
LoopPlaylist(LoopProperty),
Speed(f64),
Volume(f64),
Mute(bool),
Unknown { name: String, data: MpvDataType },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LoopProperty {
N(usize),
Inf,
No,
}
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
pub fn parse_event_property(event: Event) -> Result<(usize, Property), Error> {
let (id, name, data) = match event {
Event::PropertyChange { id, name, data } => (id, name, data),
// TODO: return proper error
_ => {
panic!("Event is not a PropertyChange event")
}
};
match name.as_str() {
"path" => {
let path = match data {
MpvDataType::String(s) => Some(s),
MpvDataType::Null => None,
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
};
Ok((id, Property::Path(path)))
}
"pause" => {
let pause = match data {
MpvDataType::Bool(b) => b,
_ => return Err(Error(ErrorCode::ValueDoesNotContainBool)),
};
Ok((id, Property::Pause(pause)))
}
"playback-time" => {
let playback_time = match data {
MpvDataType::Double(d) => Some(d),
MpvDataType::Null => None,
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
};
Ok((id, Property::PlaybackTime(playback_time)))
}
"duration" => {
let duration = match data {
MpvDataType::Double(d) => Some(d),
MpvDataType::Null => None,
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
};
Ok((id, Property::Duration(duration)))
}
"metadata" => {
let metadata = match data {
MpvDataType::HashMap(m) => Some(m),
MpvDataType::Null => None,
_ => return Err(Error(ErrorCode::ValueDoesNotContainHashMap)),
};
Ok((id, Property::Metadata(metadata)))
}
// "playlist" => {
// let playlist = match data {
// MpvDataType::Array(a) => json_array_to_playlist(&a),
// MpvDataType::Null => Vec::new(),
// _ => return Err(Error(ErrorCode::ValueDoesNotContainPlaylist)),
// };
// Ok((id, Property::Playlist(playlist)))
// }
"playlist-pos" => {
let playlist_pos = match data {
MpvDataType::Usize(u) => Some(u),
MpvDataType::MinusOne => None,
MpvDataType::Null => None,
_ => return Err(Error(ErrorCode::ValueDoesNotContainUsize)),
};
Ok((id, Property::PlaylistPos(playlist_pos)))
}
"loop-file" => {
let loop_file = match data {
MpvDataType::Usize(n) => LoopProperty::N(n),
MpvDataType::Bool(b) => match b {
true => LoopProperty::Inf,
false => LoopProperty::No,
},
MpvDataType::String(s) => match s.as_str() {
"inf" => LoopProperty::Inf,
"no" => LoopProperty::No,
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
},
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
};
Ok((id, Property::LoopFile(loop_file)))
}
"loop-playlist" => {
let loop_playlist = match data {
MpvDataType::Usize(n) => LoopProperty::N(n),
MpvDataType::Bool(b) => match b {
true => LoopProperty::Inf,
false => LoopProperty::No,
},
MpvDataType::String(s) => match s.as_str() {
"inf" => LoopProperty::Inf,
"no" => LoopProperty::No,
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
},
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
};
Ok((id, Property::LoopPlaylist(loop_playlist)))
}
"speed" => {
let speed = match data {
MpvDataType::Double(d) => d,
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
};
Ok((id, Property::Speed(speed)))
}
"volume" => {
let volume = match data {
MpvDataType::Double(d) => d,
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
};
Ok((id, Property::Volume(volume)))
}
"mute" => {
let mute = match data {
MpvDataType::Bool(b) => b,
_ => return Err(Error(ErrorCode::ValueDoesNotContainBool)),
};
Ok((id, Property::Mute(mute)))
}
// TODO: add missing cases
_ => Ok((id, Property::Unknown { name, data })),
}
}

View File

@ -51,9 +51,6 @@ impl MpvIpc {
}
pub(crate) async fn send_command(&mut self, command: &[Value]) -> Result<Option<Value>, Error> {
// let lock = self.socket_lock.lock().await;
// START CRITICAL SECTION
let ipc_command = json!({ "command": command });
let ipc_command_str = serde_json::to_string(&ipc_command)
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
@ -87,8 +84,6 @@ impl MpvIpc {
break parsed_response;
}
};
// END CRITICAL SECTION
// mem::drop(lock);
log::trace!("Received response: {:?}", response);

View File

@ -3,6 +3,7 @@
mod core_api;
mod error;
mod event_parser;
mod event_property_parser;
mod highlevel_api_extension;
mod ipc;
mod message_parser;
@ -10,4 +11,5 @@ mod message_parser;
pub use core_api::*;
pub use error::*;
pub use event_parser::*;
pub use event_property_parser::*;
pub use highlevel_api_extension::*;

View File

@ -91,45 +91,36 @@ impl TypeHandler for Vec<PlaylistEntry> {
}
}
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, Error> {
match value {
Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array))),
Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
Value::Number(n) => {
if n.is_i64() && n.as_i64().unwrap() == -1 {
Ok(MpvDataType::MinusOne)
} else if n.is_u64() {
Ok(MpvDataType::Usize(n.as_u64().unwrap() as usize))
} else if n.is_f64() {
Ok(MpvDataType::Double(n.as_f64().unwrap()))
} else {
// TODO: proper error handling
panic!("Unexpected number type");
}
}
Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map))),
Value::String(s) => Ok(MpvDataType::String(s.to_string())),
Value::Null => Ok(MpvDataType::Null),
}
}
pub(crate) fn json_map_to_hashmap(
map: &serde_json::map::Map<String, Value>,
) -> HashMap<String, MpvDataType> {
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
for (ref key, value) in map.iter() {
match *value {
Value::Array(ref array) => {
output_map.insert(
key.to_string(),
MpvDataType::Array(json_array_to_vec(array)),
);
}
Value::Bool(ref b) => {
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
}
Value::Number(ref n) => {
if n.is_u64() {
output_map.insert(
key.to_string(),
MpvDataType::Usize(n.as_u64().unwrap() as usize),
);
} else if n.is_f64() {
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
} else {
panic!("unimplemented number");
}
}
Value::String(ref s) => {
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
}
Value::Object(ref m) => {
output_map.insert(
key.to_string(),
MpvDataType::HashMap(json_map_to_hashmap(m)),
);
}
Value::Null => {
unimplemented!();
}
// TODO: proper error handling
if let Ok(value) = json_to_value(value) {
output_map.insert(key.to_string(), value);
}
}
output_map
@ -138,24 +129,8 @@ pub(crate) fn json_map_to_hashmap(
pub(crate) fn json_array_to_vec(array: &[Value]) -> Vec<MpvDataType> {
array
.iter()
.map(|entry| match entry {
Value::Array(a) => MpvDataType::Array(json_array_to_vec(a)),
Value::Bool(b) => MpvDataType::Bool(*b),
Value::Number(n) => {
if n.is_u64() {
MpvDataType::Usize(n.as_u64().unwrap() as usize)
} else if n.is_f64() {
MpvDataType::Double(n.as_f64().unwrap())
} else {
panic!("unimplemented number");
}
}
Value::Object(ref o) => MpvDataType::HashMap(json_map_to_hashmap(o)),
Value::String(s) => MpvDataType::String(s.to_owned()),
Value::Null => {
unimplemented!();
}
})
// TODO: proper error handling
.filter_map(|entry| json_to_value(entry).ok())
.collect()
}

View File

@ -1,14 +1,12 @@
use std::panic;
use futures::{stream::StreamExt, SinkExt};
use mpvipc::{Mpv, MpvDataType, MpvExt, Property};
use mpvipc::{parse_event_property, Mpv, MpvDataType, MpvExt, Property};
use serde_json::json;
use test_log::test;
use tokio::{net::UnixStream, task::JoinHandle};
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
use mpvipc::Event;
fn test_socket(
answers: Vec<(bool, String)>,
) -> (UnixStream, JoinHandle<Result<(), LinesCodecError>>) {
@ -53,19 +51,13 @@ async fn test_observe_event_successful() {
tokio::spawn(async move {
let event = mpv2.get_event_stream().await.next().await.unwrap().unwrap();
let property = match event {
Event::PropertyChange { id, property } => {
assert_eq!(id, 1);
property
}
err => panic!("{:?}", err),
};
let data = match property {
Property::Unknown { name, data } => {
let data = match parse_event_property(event) {
Ok((_, Property::Unknown { name, data })) => {
assert_eq!(name, "volume");
data
}
err => panic!("{:?}", err),
Ok((_, property)) => panic!("{:?}", property),
Err(err) => panic!("{:?}", err),
};
match data {
MpvDataType::Double(data) => assert_eq!(data, 64.0),

View File

@ -73,24 +73,23 @@ async fn test_events() {
let event_checking_thread = tokio::spawn(async move {
loop {
let event = events.next().await.unwrap().unwrap();
if let mpvipc::Event::PropertyChange { id, property } = event {
if id == 1337 {
if let (1337, property) = mpvipc::parse_event_property(event).unwrap() {
assert_eq!(property, mpvipc::Property::Pause(true));
break;
}
}
}
});
tokio::time::sleep(Duration::from_millis(10)).await;
mpv.set_property("pause", true).await.unwrap();
if let Err(_) = tokio::time::timeout(
if tokio::time::timeout(
tokio::time::Duration::from_millis(500),
event_checking_thread,
)
.await
.is_err()
{
panic!("Event checking thread timed out");
}