use std::any::{Any, TypeId};
pub trait ResponseData: Any + std::fmt::Debug + Send + Sync {
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
#[derive(Debug)]
pub struct DynResponse {
type_id: TypeId,
type_name: &'static str,
data: Box<dyn ResponseData>,
}
#[derive(Debug, Default)]
pub struct Responses {
entries: Vec<(Option<gantz_ca::Head>, DynResponse)>,
}
impl DynResponse {
pub fn new<T: ResponseData>(data: T) -> Self {
Self {
type_id: TypeId::of::<T>(),
type_name: std::any::type_name::<T>(),
data: Box::new(data),
}
}
pub fn type_id(&self) -> TypeId {
self.type_id
}
pub fn type_name(&self) -> &'static str {
self.type_name
}
pub fn downcast<T: ResponseData>(self) -> Result<T, Self> {
if self.type_id == TypeId::of::<T>() {
let data = self
.data
.into_any()
.downcast::<T>()
.expect("`type_id` matches `T`");
Ok(*data)
} else {
Err(self)
}
}
}
impl Responses {
pub fn push<T: ResponseData>(&mut self, head: Option<gantz_ca::Head>, data: T) {
self.entries.push((head, DynResponse::new(data)));
}
pub fn extend(
&mut self,
head: Option<&gantz_ca::Head>,
data: impl IntoIterator<Item = DynResponse>,
) {
self.entries
.extend(data.into_iter().map(|d| (head.cloned(), d)));
}
pub fn take<T: ResponseData>(&mut self) -> Vec<(Option<gantz_ca::Head>, T)> {
let mut taken = Vec::new();
let mut rest = Vec::with_capacity(self.entries.len());
for (head, payload) in self.entries.drain(..) {
match payload.downcast::<T>() {
Ok(data) => taken.push((head, data)),
Err(payload) => rest.push((head, payload)),
}
}
self.entries = rest;
taken
}
pub fn drain(&mut self) -> impl Iterator<Item = (Option<gantz_ca::Head>, DynResponse)> + '_ {
self.entries.drain(..)
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn type_names(&self) -> impl Iterator<Item = &'static str> + '_ {
self.entries.iter().map(|(_, p)| p.type_name())
}
}
impl<T> ResponseData for T
where
T: Any + std::fmt::Debug + Send + Sync,
{
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
pub struct NodeUiResponse {
pub framed: egui_graph::FramedResponse<egui::Response>,
pub changed: bool,
pub payloads: Vec<DynResponse>,
}
impl NodeUiResponse {
pub fn new(framed: egui_graph::FramedResponse<egui::Response>) -> Self {
Self {
framed,
changed: false,
payloads: Vec::new(),
}
}
}
#[derive(Debug, Default)]
pub struct InspectorRowsResponse {
pub changed: bool,
pub payloads: Vec<DynResponse>,
}
#[derive(Debug, Default)]
pub struct InspectorUiResponse {
pub inner: Option<egui::Response>,
pub changed: bool,
pub payloads: Vec<DynResponse>,
}
#[derive(Debug, Default)]
pub struct NodeViewResponse {
pub inner: Option<egui::Response>,
pub changed: bool,
pub payloads: Vec<DynResponse>,
}
#[derive(Debug, Default)]
pub struct ContextMenuResponse {
pub changed: bool,
pub payloads: Vec<DynResponse>,
}
macro_rules! impl_node_response_emit {
($($Ty:ty),* $(,)?) => {$(
impl $Ty {
pub fn mark_changed(&mut self) {
self.changed = true;
}
pub fn set_changed(&mut self, changed: bool) {
self.changed |= changed;
}
pub fn emit<T: ResponseData>(&mut self, data: T) {
self.payloads.push(DynResponse::new(data));
}
pub fn push_eval(&mut self, path: &[gantz_core::node::Id], n_outputs: u8) {
let ep = gantz_core::compile::entrypoint::push(path.to_vec(), n_outputs);
self.emit(crate::EvalEntry(ep));
}
pub fn pull_eval(&mut self, path: &[gantz_core::node::Id], n_inputs: u8) {
let ep = gantz_core::compile::entrypoint::pull(path.to_vec(), n_inputs);
self.emit(crate::EvalEntry(ep));
}
}
)*};
}
impl_node_response_emit!(
NodeUiResponse,
InspectorRowsResponse,
InspectorUiResponse,
NodeViewResponse,
ContextMenuResponse,
);
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct A(u32);
#[derive(Debug, PartialEq)]
struct B(&'static str);
#[test]
fn payload_identity_is_the_concrete_type() {
let p = DynResponse::new(A(1));
assert_eq!(p.type_id(), TypeId::of::<A>());
assert_eq!(p.type_name(), std::any::type_name::<A>());
assert_eq!(p.downcast::<A>().unwrap(), A(1));
}
#[test]
fn downcast_to_wrong_type_returns_the_payload() {
let p = DynResponse::new(A(1));
let p = p.downcast::<B>().unwrap_err();
assert_eq!(p.downcast::<A>().unwrap(), A(1));
}
#[test]
fn take_drains_matching_entries_in_order() {
let mut rs = Responses::default();
rs.push(None, A(1));
rs.push(None, B("x"));
rs.push(None, A(2));
let taken: Vec<_> = rs.take::<A>().into_iter().map(|(_, a)| a).collect();
assert_eq!(taken, vec![A(1), A(2)]);
let names: Vec<_> = rs.type_names().collect();
assert_eq!(names, vec![std::any::type_name::<B>()]);
rs.take::<B>();
assert!(rs.is_empty());
}
#[test]
fn extend_tags_payloads_with_the_head() {
let head = gantz_ca::Head::Branch("main".parse().unwrap());
let mut rs = Responses::default();
rs.extend(Some(&head), [DynResponse::new(A(1))]);
let mut taken = rs.take::<A>();
assert_eq!(taken.len(), 1);
let (tag, a) = taken.pop().unwrap();
assert_eq!(tag.as_ref(), Some(&head));
assert_eq!(a, A(1));
}
#[test]
fn drained_payloads_dispatch_by_concrete_type_id() {
let mut rs = Responses::default();
rs.push(None, A(1));
rs.push(None, B("x"));
let ids: Vec<_> = rs.drain().map(|(_, p)| p.type_id()).collect();
assert_eq!(ids, vec![TypeId::of::<A>(), TypeId::of::<B>()]);
}
}