use std::collections::HashSet;
use std::error::Error;
use std::fmt;
use crate::object::{MaterializedCE, Value};
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ComponentHandle(u64);
impl ComponentHandle {
pub fn from_raw(raw: u64) -> Self {
Self(raw)
}
pub fn into_raw(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CallbackHandle(u64);
impl CallbackHandle {
pub fn from_raw(raw: u64) -> Self { Self(raw) }
pub fn into_raw(self) -> u64 { self.0 }
}
#[derive(Debug, Clone, PartialEq)]
pub enum TransportValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<TransportValue>),
Table(Vec<(String, TransportValue)>),
Component(ComponentHandle),
Callback(CallbackHandle),
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct HostCapabilities {
pub components: HashSet<String>,
pub component_operations: HashSet<String>,
pub api_ids: HashSet<String>,
}
impl HostCapabilities {
pub fn supports_component(mut self, name: impl Into<String>) -> Self {
self.components.insert(name.into().to_lowercase()); self
}
pub fn supports_operation(mut self, operation: impl Into<String>) -> Self {
self.component_operations.insert(operation.into()); self
}
pub fn supports_api(mut self, id: impl Into<String>) -> Self {
self.api_ids.insert(id.into()); self
}
}
#[derive(Debug)]
pub struct HostContext {
session_tag: u32,
next_component: u32,
next_callback: u32,
components: HashSet<ComponentHandle>,
callbacks: HashSet<CallbackHandle>,
}
impl HostContext {
pub(crate) fn new(session_tag: u32) -> Self {
Self { session_tag, next_component: 1, next_callback: 1,
components: HashSet::new(), callbacks: HashSet::new() }
}
pub fn allocate_component(&mut self) -> ComponentHandle {
let handle = ComponentHandle::from_raw(
((self.session_tag as u64) << 32) | self.next_component as u64);
self.next_component = self.next_component.checked_add(1).expect("component handle space exhausted");
self.components.insert(handle); handle
}
pub fn adopt_component(&mut self, handle: ComponentHandle) {
self.components.insert(handle);
}
pub fn allocate_callback(&mut self) -> CallbackHandle {
let handle = CallbackHandle::from_raw(
((self.session_tag as u64) << 32) | self.next_callback as u64);
self.next_callback = self.next_callback.checked_add(1).expect("callback handle space exhausted");
self.callbacks.insert(handle); handle
}
pub fn owns_component(&self, handle: ComponentHandle) -> bool { self.components.contains(&handle) }
pub fn owns_callback(&self, handle: CallbackHandle) -> bool { self.callbacks.contains(&handle) }
}
impl fmt::Debug for ComponentHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ComponentHandle").field(&self.0).finish()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum HostRequest {
RegisterComponent { tree: MaterializedCE },
Emit { tree: MaterializedCE },
Spawn {
tree: MaterializedCE,
},
Register {
tree: MaterializedCE,
},
Attach {
parent: Option<ComponentHandle>,
child: ComponentHandle,
},
Query {
selector: String,
scope: Option<ComponentHandle>,
multiple: bool,
},
RegisterHandler {
scope: ComponentHandle,
signal: String,
name: Option<String>,
handler: Value,
},
InvokeComponentMethod {
component: ComponentHandle,
component_type: String,
method: String,
args: Vec<Value>,
},
CallApi {
api_id: String,
args: Vec<TransportValue>,
},
AudioClipInstance {
source: ComponentHandle,
start_beat: Option<f64>,
stop_beat: Option<f64>,
},
AudioOperation {
operation: String,
target: Option<ComponentHandle>,
args: Vec<Value>,
},
ReplTree {
value: Value,
max_depth: Option<usize>,
},
ReplDump {
value: Value,
},
ReplHelp,
ReplClear,
EngineMutation {
operation: String,
targets: Vec<ComponentHandle>,
args: Vec<Value>,
},
}
impl HostRequest {
pub fn operation_name(&self) -> &str {
match self {
Self::RegisterComponent { .. } => "register_component",
Self::Emit { .. } => "emit",
Self::Spawn { .. } => "spawn",
Self::Register { .. } => "register",
Self::Attach { .. } => "attach",
Self::Query { .. } => "query",
Self::RegisterHandler { .. } => "register_handler",
Self::InvokeComponentMethod { .. } => "invoke_component_method",
Self::CallApi { api_id, .. } => api_id,
Self::AudioClipInstance { .. } => "audio_clip_instance",
Self::AudioOperation { operation, .. } | Self::EngineMutation { operation, .. } => {
operation
}
Self::ReplTree { .. } => "repl_tree",
Self::ReplDump { .. } => "repl_dump",
Self::ReplHelp => "repl_help",
Self::ReplClear => "repl_clear",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum HostResponse {
Unit,
Value(Value),
Component {
handle: ComponentHandle,
component_type: String,
},
Components(Vec<(ComponentHandle, String)>),
Transport(TransportValue),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HostErrorKind {
UnsupportedHostOperation,
InvalidRequest,
HostFailure,
ForeignHandle,
Conversion,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostError {
pub kind: HostErrorKind,
pub operation: String,
pub message: String,
}
impl HostError {
pub fn unsupported(operation: impl Into<String>) -> Self {
let operation = operation.into();
Self {
kind: HostErrorKind::UnsupportedHostOperation,
message: format!("host operation '{operation}' is unavailable"),
operation,
}
}
pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
Self {
kind: HostErrorKind::HostFailure,
operation: operation.into(),
message: message.into(),
}
}
}
impl fmt::Display for HostError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.operation, self.message)
}
}
impl Error for HostError {}
pub trait Host {
fn dispatch(&mut self, request: HostRequest) -> Result<HostResponse, HostError> {
Err(HostError::unsupported(request.operation_name()))
}
fn capabilities(&self) -> HostCapabilities { HostCapabilities::default() }
fn dispatch_with_context(
&mut self,
_context: &mut HostContext,
request: HostRequest,
) -> Result<HostResponse, HostError> {
self.dispatch(request)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Hostless;
impl Host for Hostless {
fn dispatch(&mut self, request: HostRequest) -> Result<HostResponse, HostError> {
Err(HostError::unsupported(request.operation_name()))
}
}