use std::fmt;
use sim_kernel::{Cx, Error as KernelError, Result, Symbol};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceProfile {
pub device: Symbol,
pub streams: Vec<Symbol>,
pub inputs: Vec<Symbol>,
pub outputs: Vec<Symbol>,
pub sample_kinds: Vec<Symbol>,
}
impl DeviceProfile {
pub fn new(
device: Symbol,
streams: Vec<Symbol>,
inputs: Vec<Symbol>,
outputs: Vec<Symbol>,
sample_kinds: Vec<Symbol>,
) -> Self {
Self {
device,
streams,
inputs,
outputs,
sample_kinds,
}
}
pub fn modeled_edge() -> Self {
Self::new(
Symbol::qualified("device", "modeled-edge"),
vec![
Symbol::qualified("device/stream", "battery"),
Symbol::qualified("device/stream", "motion"),
],
vec![Symbol::qualified("device/input", "button")],
vec![
Symbol::qualified("device/output", "screen"),
Symbol::qualified("device/output", "haptic"),
],
vec![Symbol::qualified("device/sample", "caps")],
)
}
pub fn supports_sample_kind(&self, sample_kind: &Symbol) -> bool {
self.sample_kinds.contains(sample_kind)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeviceSiteLocality {
EdgeLocal,
HostLocal,
Remote,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceSite {
pub symbol: Symbol,
pub profile: DeviceProfile,
pub surface_codec_id: Symbol,
pub locality: DeviceSiteLocality,
}
impl DeviceSite {
pub fn new(
symbol: Symbol,
profile: DeviceProfile,
surface_codec_id: Symbol,
locality: DeviceSiteLocality,
) -> Self {
Self {
symbol,
profile,
surface_codec_id,
locality,
}
}
pub fn edge_local(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
Self::new(
symbol,
profile,
surface_codec_id,
DeviceSiteLocality::EdgeLocal,
)
}
pub fn host_local(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
Self::new(
symbol,
profile,
surface_codec_id,
DeviceSiteLocality::HostLocal,
)
}
pub fn remote(symbol: Symbol, profile: DeviceProfile, surface_codec_id: Symbol) -> Self {
Self::new(
symbol,
profile,
surface_codec_id,
DeviceSiteLocality::Remote,
)
}
pub fn is_edge_local(&self) -> bool {
self.locality == DeviceSiteLocality::EdgeLocal
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DevicePlacement {
pub encoder: DeviceSite,
pub adapter: DeviceSite,
}
impl DevicePlacement {
pub fn new(encoder: DeviceSite, adapter: DeviceSite) -> Self {
Self { encoder, adapter }
}
pub fn validate(&self) -> std::result::Result<(), DevicePlacementError> {
if self.adapter.is_edge_local() {
Ok(())
} else {
Err(DevicePlacementError::AdapterMustBeEdgeLocal)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DevicePlacementError {
AdapterMustBeEdgeLocal,
}
impl fmt::Display for DevicePlacementError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AdapterMustBeEdgeLocal => f.write_str("device adapter must be edge-local"),
}
}
}
impl std::error::Error for DevicePlacementError {}
pub trait DeviceProvider: Send {
fn open(&self) -> Result<Box<dyn DeviceSession>>;
}
pub trait DeviceSession: Send {
fn profile(&self) -> &DeviceProfile;
fn start(&mut self) -> Result<()>;
fn stop(&mut self) -> Result<()>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StubProvider {
profile: DeviceProfile,
}
impl StubProvider {
pub fn new(profile: DeviceProfile) -> Self {
Self { profile }
}
pub fn profile(&self) -> &DeviceProfile {
&self.profile
}
pub fn session(&self) -> StubSession {
StubSession::new(self.profile.clone())
}
}
impl DeviceProvider for StubProvider {
fn open(&self) -> Result<Box<dyn DeviceSession>> {
Ok(Box::new(self.session()))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StubSession {
profile: DeviceProfile,
started: bool,
}
impl StubSession {
pub fn new(profile: DeviceProfile) -> Self {
Self {
profile,
started: false,
}
}
pub fn is_started(&self) -> bool {
self.started
}
}
impl DeviceSession for StubSession {
fn profile(&self) -> &DeviceProfile {
&self.profile
}
fn start(&mut self) -> Result<()> {
self.started = true;
Ok(())
}
fn stop(&mut self) -> Result<()> {
self.started = false;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RouteArg {
symbol: Symbol,
}
impl RouteArg {
pub fn new(symbol: Symbol) -> Self {
Self { symbol }
}
pub fn headless() -> Self {
Self::new(Symbol::qualified("device/route", "headless"))
}
pub fn symbol(&self) -> &Symbol {
&self.symbol
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeviceHostStalePolicy {
HoldLast,
PredictClamp,
Blank,
Refuse,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceConsentPolicy {
Headless,
RequireReceipt {
subject: Symbol,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeviceRateClass {
Sparse,
Interactive,
Surface,
}
impl DeviceRateClass {
fn interval_ms(self) -> u64 {
match self {
Self::Sparse => 1_000,
Self::Interactive => 100,
Self::Surface => 50,
}
}
}
pub fn derive_device_rate_class(profile: &DeviceProfile) -> DeviceRateClass {
if !profile.outputs.is_empty() {
DeviceRateClass::Surface
} else if !profile.inputs.is_empty() {
DeviceRateClass::Interactive
} else {
DeviceRateClass::Sparse
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceHostSpec {
pub profile: DeviceProfile,
pub route: RouteArg,
pub placement: DevicePlacement,
pub stale: DeviceHostStalePolicy,
pub consent: DeviceConsentPolicy,
}
impl DeviceHostSpec {
pub fn new(
profile: DeviceProfile,
route: RouteArg,
placement: DevicePlacement,
stale: DeviceHostStalePolicy,
consent: DeviceConsentPolicy,
) -> Self {
Self {
profile,
route,
placement,
stale,
consent,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DeviceProviderKind {
Instance,
Stub,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AdapterTick {
sequence: u64,
interval_ms: u64,
}
impl AdapterTick {
pub fn sequence(&self) -> u64 {
self.sequence
}
pub fn interval_ms(&self) -> u64 {
self.interval_ms
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceAdapterLoopPlan {
rate_class: DeviceRateClass,
stale: DeviceHostStalePolicy,
route: RouteArg,
device: Symbol,
sequence: u64,
}
impl DeviceAdapterLoopPlan {
pub fn for_profile(
profile: &DeviceProfile,
route: RouteArg,
stale: DeviceHostStalePolicy,
) -> Self {
Self {
rate_class: derive_device_rate_class(profile),
stale,
route,
device: profile.device.clone(),
sequence: 0,
}
}
pub fn rate_class(&self) -> DeviceRateClass {
self.rate_class
}
pub fn stale_policy(&self) -> DeviceHostStalePolicy {
self.stale
}
pub fn route(&self) -> &RouteArg {
&self.route
}
pub fn device(&self) -> &Symbol {
&self.device
}
pub fn sequence(&self) -> u64 {
self.sequence
}
pub fn interval_ms(&self) -> u64 {
self.rate_class.interval_ms()
}
pub fn next_tick(&mut self) -> AdapterTick {
self.sequence += 1;
AdapterTick {
sequence: self.sequence,
interval_ms: self.interval_ms(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceSurfaceHubJoin {
route: RouteArg,
device: Symbol,
adapter_site: Symbol,
}
impl DeviceSurfaceHubJoin {
pub fn new(route: RouteArg, device: Symbol, adapter_site: Symbol) -> Self {
Self {
route,
device,
adapter_site,
}
}
pub fn route(&self) -> &RouteArg {
&self.route
}
pub fn device(&self) -> &Symbol {
&self.device
}
pub fn adapter_site(&self) -> &Symbol {
&self.adapter_site
}
}
pub struct DeviceEdgeSession {
spec: DeviceHostSpec,
provider_kind: DeviceProviderKind,
session: Box<dyn DeviceSession>,
adapter_loop: DeviceAdapterLoopPlan,
hub_join: DeviceSurfaceHubJoin,
live: bool,
}
impl DeviceEdgeSession {
pub fn is_live(&self) -> bool {
self.live
}
pub fn provider_kind(&self) -> DeviceProviderKind {
self.provider_kind
}
pub fn profile(&self) -> &DeviceProfile {
&self.spec.profile
}
pub fn route(&self) -> &RouteArg {
&self.spec.route
}
pub fn placement(&self) -> &DevicePlacement {
&self.spec.placement
}
pub fn stale_policy(&self) -> DeviceHostStalePolicy {
self.spec.stale
}
pub fn consent_policy(&self) -> &DeviceConsentPolicy {
&self.spec.consent
}
pub fn adapter_loop(&self) -> &DeviceAdapterLoopPlan {
&self.adapter_loop
}
pub fn adapter_loop_mut(&mut self) -> &mut DeviceAdapterLoopPlan {
&mut self.adapter_loop
}
pub fn hub_join(&self) -> &DeviceSurfaceHubJoin {
&self.hub_join
}
pub fn device_session(&self) -> &dyn DeviceSession {
self.session.as_ref()
}
pub fn device_session_mut(&mut self) -> &mut dyn DeviceSession {
self.session.as_mut()
}
}
pub fn install_device_bases(cx: &mut Cx) -> Result<()> {
cx.factory().nil().map(|_| ())
}
pub fn compose_device_host(cx: &mut Cx, spec: DeviceHostSpec) -> Result<DeviceEdgeSession> {
let provider = StubProvider::new(spec.profile.clone());
join_device_session(cx, spec, DeviceProviderKind::Stub, provider.open()?)
}
pub fn compose_device_host_with_provider<P>(
cx: &mut Cx,
spec: DeviceHostSpec,
provider: &P,
) -> Result<DeviceEdgeSession>
where
P: DeviceProvider + ?Sized,
{
join_device_session(cx, spec, DeviceProviderKind::Instance, provider.open()?)
}
fn join_device_session(
cx: &mut Cx,
spec: DeviceHostSpec,
provider_kind: DeviceProviderKind,
session: Box<dyn DeviceSession>,
) -> Result<DeviceEdgeSession> {
install_device_bases(cx)?;
spec.placement
.validate()
.map_err(|error| KernelError::HostError(error.to_string()))?;
let mut session = session;
if session.profile() != &spec.profile {
return Err(KernelError::HostError(format!(
"device provider profile {} did not match requested profile {}",
session.profile().device,
spec.profile.device
)));
}
session.start()?;
let adapter_loop =
DeviceAdapterLoopPlan::for_profile(&spec.profile, spec.route.clone(), spec.stale);
let hub_join = DeviceSurfaceHubJoin::new(
spec.route.clone(),
spec.profile.device.clone(),
spec.placement.adapter.symbol.clone(),
);
Ok(DeviceEdgeSession {
spec,
provider_kind,
session,
adapter_loop,
hub_join,
live: true,
})
}