use std::any::Any;
use std::marker::PhantomData;
use std::time::{Duration, Instant};
use tokio::sync::oneshot;
use crate::error::SteamError;
use crate::steam::steam_client::callback_mgr::callback_msg::CallbackMsg;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const HEARTBEAT_TIMEOUT_EXTENSION: Duration = Duration::from_secs(10);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct JobID(u64);
impl JobID {
pub const INVALID: JobID = JobID(u64::MAX);
pub fn new() -> JobID {
JobID(u64::MAX)
}
pub fn from_value(value: u64) -> JobID {
JobID(value)
}
pub fn value(&self) -> u64 {
self.0
}
pub fn set_value(&mut self, value: u64) {
self.0 = value;
}
pub fn sequential_count(&self) -> u32 {
((self.0 >> 0) & 0xF_FFFF) as u32
}
pub fn set_sequential_count(&mut self, value: u32) {
let mask = 0xF_FFFF_u64;
self.0 = (self.0 & !mask) | (value as u64 & mask);
}
pub fn start_time(&self) -> u64 {
(self.0 >> 20) & 0x3_FFFF_FFFF
}
pub fn set_start_time(&mut self, value: u64) {
let mask = 0x3_FFFF_FFFF_u64 << 20;
self.0 = (self.0 & !mask) | ((value & 0x3_FFFF_FFFF) << 20);
}
pub fn process_id(&self) -> u32 {
((self.0 >> 50) & 0xF) as u32
}
pub fn set_process_id(&mut self, value: u32) {
let mask = 0xF_u64 << 50;
self.0 = (self.0 & !mask) | ((value as u64 & 0xF) << 50);
}
pub fn box_id(&self) -> u32 {
((self.0 >> 54) & 0x3_FF) as u32
}
pub fn set_box_id(&mut self, value: u32) {
let mask = 0x3_FF_u64 << 54;
self.0 = (self.0 & !mask) | ((value as u64 & 0x3_FF) << 54);
}
}
impl std::fmt::Display for JobID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value())
}
}
impl std::fmt::Debug for JobID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl From<JobID> for u64 {
fn from(job_id: JobID) -> u64 {
job_id.value()
}
}
impl From<u64> for JobID {
fn from(value: u64) -> JobID {
JobID::from_value(value)
}
}
type JobResult = Result<Box<dyn CallbackMsg>, SteamError>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct AnyCallback;
pub struct AsyncJob<T = AnyCallback> {
job_id: JobID,
timeout: Duration,
job_start: Instant,
sender: Option<oneshot::Sender<JobResult>>,
receiver: JobReceiver,
_marker: PhantomData<fn() -> T>,
}
impl<T> AsyncJob<T> {
pub fn new(job_id: JobID) -> AsyncJob<T> {
let (sender, receiver) = oneshot::channel();
AsyncJob {
job_id,
timeout: DEFAULT_TIMEOUT,
job_start: Instant::now(),
sender: Some(sender),
receiver: JobReceiver { receiver },
_marker: PhantomData,
}
}
pub fn job_id(&self) -> JobID {
self.job_id
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
pub fn is_timedout(&self) -> bool {
self.job_start.elapsed() >= self.timeout
}
pub fn heartbeat(&mut self) {
self.timeout += HEARTBEAT_TIMEOUT_EXTENSION;
}
pub fn add_result(&mut self, callback: Box<dyn CallbackMsg>) -> bool {
if let Some(sender) = self.sender.take() {
let _ = sender.send(Ok(callback));
}
true
}
pub fn set_failed(&mut self, due_to_remote_failure: bool) {
if let Some(sender) = self.sender.take() {
let error = if due_to_remote_failure {
SteamError::JobFailed
} else {
SteamError::Timeout(self.timeout)
};
let _ = sender.send(Err(error));
}
}
pub fn take_sender(&mut self) -> Option<oneshot::Sender<JobResult>> {
self.sender.take()
}
pub fn into_receiver(self) -> JobReceiver {
self.receiver
}
}
impl<T> From<&AsyncJob<T>> for JobID {
fn from(job: &AsyncJob<T>) -> JobID {
job.job_id()
}
}
impl<T> std::fmt::Debug for AsyncJob<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncJob")
.field("job_id", &self.job_id)
.field("timeout", &self.timeout)
.finish()
}
}
impl<T: CallbackMsg> AsyncJob<T> {
pub fn to_task(self) -> TypedJobReceiver<T> {
TypedJobReceiver {
receiver: self.receiver,
_marker: PhantomData,
}
}
pub async fn wait(self) -> Result<T, SteamError> {
self.to_task().wait().await
}
}
pub struct JobReceiver {
receiver: oneshot::Receiver<JobResult>,
}
impl JobReceiver {
pub async fn wait(self) -> Result<Box<dyn CallbackMsg>, SteamError> {
match self.receiver.await {
Ok(result) => result,
Err(_) => Err(SteamError::Cancelled),
}
}
}
impl std::fmt::Debug for JobReceiver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("JobReceiver")
}
}
pub struct TypedJobReceiver<T: CallbackMsg> {
receiver: JobReceiver,
_marker: PhantomData<fn() -> T>,
}
impl<T: CallbackMsg> TypedJobReceiver<T> {
pub async fn wait(self) -> Result<T, SteamError> {
let message = self.receiver.wait().await?;
let any: Box<dyn Any> = message;
match any.downcast::<T>() {
Ok(typed) => Ok(*typed),
Err(_) => Err(SteamError::Malformed(
"callback message does not match the expected type".to_string(),
)),
}
}
}
impl<T: CallbackMsg> std::fmt::Debug for TypedJobReceiver<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("TypedJobReceiver")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResultSet<T> {
pub complete: bool,
pub failed: bool,
pub results: Option<Vec<T>>,
}
pub struct AsyncJobMultiple<T: CallbackMsg> {
job_id: JobID,
timeout: Duration,
job_start: Instant,
sender: Option<oneshot::Sender<Result<ResultSet<T>, SteamError>>>,
receiver: MultiJobReceiver<T>,
finish_condition: Box<dyn FnMut(&T) -> bool + Send + Sync>,
results: Vec<T>,
_marker: PhantomData<fn() -> T>,
}
impl<T: CallbackMsg> AsyncJobMultiple<T> {
pub fn new(
job_id: JobID,
finish_condition: impl FnMut(&T) -> bool + Send + Sync + 'static,
) -> AsyncJobMultiple<T> {
let (sender, receiver) = oneshot::channel();
AsyncJobMultiple {
job_id,
timeout: DEFAULT_TIMEOUT,
job_start: Instant::now(),
sender: Some(sender),
receiver: MultiJobReceiver {
receiver,
_marker: PhantomData,
},
finish_condition: Box::new(finish_condition),
results: Vec::new(),
_marker: PhantomData,
}
}
pub fn job_id(&self) -> JobID {
self.job_id
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
pub fn is_timedout(&self) -> bool {
self.job_start.elapsed() >= self.timeout
}
pub fn heartbeat(&mut self) {
self.timeout += HEARTBEAT_TIMEOUT_EXTENSION;
}
pub fn results(&self) -> &[T] {
&self.results
}
pub fn add_result(&mut self, callback: Box<dyn CallbackMsg>) -> bool {
let Some(callback) = downcast_callback::<T>(callback) else {
return false;
};
let is_complete = (self.finish_condition)(&callback);
self.results.push(callback);
if is_complete {
let result_set = ResultSet {
complete: true,
failed: false,
results: Some(std::mem::take(&mut self.results)),
};
if let Some(sender) = self.sender.take() {
let _ = sender.send(Ok(result_set));
}
return true;
}
self.heartbeat();
false
}
pub fn set_failed(&mut self, due_to_remote_failure: bool) {
if self.results.is_empty() {
let error = if due_to_remote_failure {
SteamError::JobFailed
} else {
SteamError::Timeout(self.timeout)
};
if let Some(sender) = self.sender.take() {
let _ = sender.send(Err(error));
}
} else {
let result_set = ResultSet {
complete: false,
failed: due_to_remote_failure,
results: Some(std::mem::take(&mut self.results)),
};
if let Some(sender) = self.sender.take() {
let _ = sender.send(Ok(result_set));
}
}
}
pub fn take_sender(&mut self) -> Option<oneshot::Sender<Result<ResultSet<T>, SteamError>>> {
self.sender.take()
}
pub fn to_task(self) -> MultiJobReceiver<T> {
self.receiver
}
pub async fn wait(self) -> Result<ResultSet<T>, SteamError> {
self.to_task().wait().await
}
}
impl<T: CallbackMsg> std::fmt::Debug for AsyncJobMultiple<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncJobMultiple")
.field("job_id", &self.job_id)
.field("timeout", &self.timeout)
.field("results", &self.results.len())
.finish()
}
}
pub struct MultiJobReceiver<T: CallbackMsg> {
receiver: oneshot::Receiver<Result<ResultSet<T>, SteamError>>,
_marker: PhantomData<fn() -> T>,
}
impl<T: CallbackMsg> MultiJobReceiver<T> {
pub async fn wait(self) -> Result<ResultSet<T>, SteamError> {
match self.receiver.await {
Ok(result) => result,
Err(_) => Err(SteamError::Cancelled),
}
}
}
impl<T: CallbackMsg> std::fmt::Debug for MultiJobReceiver<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("MultiJobReceiver")
}
}
pub type JobState = AsyncJob;
pub type JobHandle<T> = AsyncJob<T>;
pub type TypedJob<T> = TypedJobReceiver<T>;
pub type JobGroup<T> = AsyncJobMultiple<T>;
pub type MultiJob<T> = MultiJobReceiver<T>;
fn downcast_callback<T: CallbackMsg>(callback: Box<dyn CallbackMsg>) -> Option<T> {
let any: Box<dyn Any> = callback;
any.downcast::<T>().ok().map(|typed| *typed)
}