use std::fmt;
use web_time::Duration;
pub const INTERSTITIAL_TIMEOUT: Duration = Duration::from_secs(10);
pub const REWARDED_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AdKind {
Interstitial,
Rewarded,
}
impl AdKind {
pub fn timeout(self) -> Duration {
match self {
Self::Interstitial => INTERSTITIAL_TIMEOUT,
Self::Rewarded => REWARDED_TIMEOUT,
}
}
pub fn is_rewarded(self) -> bool {
matches!(self, Self::Rewarded)
}
}
impl fmt::Display for AdKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Interstitial => "interstitial",
Self::Rewarded => "rewarded",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Refusal {
NoPortal,
NotSupported,
InFlight,
Host {
code: Option<String>,
message: String,
},
NotText,
EmptyPayload,
}
impl Refusal {
pub fn code(&self) -> Option<&str> {
match self {
Self::Host { code, .. } => code.as_deref(),
_ => None,
}
}
}
impl fmt::Display for Refusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoPortal => f.write_str("no portal is installed"),
Self::NotSupported => f.write_str("the host does not support this"),
Self::InFlight => f.write_str("a request of the same kind is already in flight"),
Self::Host {
code: Some(code),
message,
} => write!(f, "{code}: {message}"),
Self::Host {
code: None,
message,
} => f.write_str(message),
Self::NotText => f.write_str("the cloud keeps text, and these bytes are not text"),
Self::EmptyPayload => {
f.write_str("an empty payload is a delete for the platform's cloud")
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Availability {
Available,
Unavailable(Refusal),
}
impl Availability {
pub fn is_available(&self) -> bool {
matches!(self, Self::Available)
}
pub fn reason(&self) -> Option<&Refusal> {
match self {
Self::Available => None,
Self::Unavailable(reason) => Some(reason),
}
}
}
impl fmt::Display for Availability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Available => f.write_str("available"),
Self::Unavailable(reason) => write!(f, "unavailable: {reason}"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct AdSchedule {
pub initial_delay: Duration,
pub minimum_delay: Duration,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HostNotice {
GameReady,
GameplayStarted,
GameplayStopped,
}
impl fmt::Display for HostNotice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::GameReady => "game_ready",
Self::GameplayStarted => "gameplay_started",
Self::GameplayStopped => "gameplay_stopped",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PortalRequest {
Notify(HostNotice),
ShowAd {
kind: AdKind,
placement: Option<String>,
},
CloudPull,
CloudPush { payload: Vec<u8> },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdEnding {
Closed,
Failed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PortalEvent {
AudioEnabled(bool),
HostPaused(bool),
AdOpened(AdKind),
AdRewarded,
AdEnded { kind: AdKind, ending: AdEnding },
CloudPulled(Result<Option<Vec<u8>>, Refusal>),
CloudStored(Result<(), Refusal>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PortalStatus {
pub hosted: bool,
pub audio_enabled: bool,
pub host_paused: bool,
pub interstitial: Availability,
pub rewarded: Availability,
pub cloud: Availability,
pub interstitial_schedule: AdSchedule,
}
impl Default for PortalStatus {
fn default() -> Self {
Self {
hosted: false,
audio_enabled: true,
host_paused: false,
interstitial: Availability::Unavailable(Refusal::NoPortal),
rewarded: Availability::Unavailable(Refusal::NoPortal),
cloud: Availability::Unavailable(Refusal::NoPortal),
interstitial_schedule: AdSchedule::default(),
}
}
}
pub trait Portal {
fn request(&mut self, request: PortalRequest) -> Result<(), Refusal>;
fn poll(&mut self) -> Vec<PortalEvent>;
fn status(&self) -> PortalStatus;
fn label(&self) -> &str;
}