use std::any::TypeId;
use std::collections::BTreeMap;
use std::marker::PhantomData;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use serde::Serialize;
use crate::bridge::{OutboundResource, ReactNode};
use crate::protocol::outbound::Outbound;
use crate::ts_codegen::TsCollector;
pub struct ElementEvent<T: 'static> {
pub name: &'static str,
pub always: bool,
ts_name: fn() -> String,
ts_collect: fn(&mut TsCollector),
_payload: PhantomData<fn(T)>,
}
impl<T: Serialize + ts_rs::TS + 'static> ElementEvent<T> {
pub const fn new(name: &'static str) -> Self {
Self {
name,
always: false,
ts_name: crate::ts_codegen::ts_name::<T>,
ts_collect: crate::ts_codegen::ts_collect::<T>,
_payload: PhantomData,
}
}
pub const fn unconditional(self) -> Self {
Self {
always: true,
..self
}
}
}
mod sealed {
pub trait Sealed {}
}
impl<T> sealed::Sealed for ElementEvent<T> {}
pub trait AnyElementEvent: sealed::Sealed + Send + Sync + 'static {
fn name(&self) -> &'static str;
fn is_unit(&self) -> bool;
fn ts_payload(&self) -> String;
fn ts_decls(&self, decls: &mut BTreeMap<String, String>);
}
impl<T: 'static> AnyElementEvent for ElementEvent<T> {
fn name(&self) -> &'static str {
self.name
}
fn is_unit(&self) -> bool {
TypeId::of::<T>() == TypeId::of::<()>()
}
fn ts_payload(&self) -> String {
(self.ts_name)()
}
fn ts_decls(&self, decls: &mut BTreeMap<String, String>) {
if self.is_unit() {
return;
}
let mut collector = TsCollector::default();
(self.ts_collect)(&mut collector);
decls.extend(collector.decls);
}
}
impl std::fmt::Debug for dyn AnyElementEvent {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ElementEvent({:?})", self.name())
}
}
pub fn handler_prop(name: &str) -> String {
let mut chars = name.chars();
match chars.next() {
Some(first) => format!("on{}{}", first.to_uppercase(), chars.as_str()),
None => "on".to_owned(),
}
}
#[derive(Component, Debug, Default, Clone)]
pub struct EventSubscriptions(pub(crate) Vec<&'static dyn AnyElementEvent>);
impl EventSubscriptions {
pub fn contains(&self, event: &dyn AnyElementEvent) -> bool {
self.0.iter().any(|e| same_event(*e, event))
}
}
fn same_event(a: &dyn AnyElementEvent, b: &dyn AnyElementEvent) -> bool {
std::ptr::addr_eq(
a as *const dyn AnyElementEvent,
b as *const dyn AnyElementEvent,
)
}
#[derive(SystemParam)]
pub struct ElementEvents<'w, 's> {
out: Option<Res<'w, OutboundResource>>,
nodes: Query<'w, 's, (&'static ReactNode, Option<&'static EventSubscriptions>)>,
}
impl ElementEvents<'_, '_> {
pub fn send<T: Serialize + 'static>(
&self,
entity: Entity,
event: &'static ElementEvent<T>,
payload: &T,
) -> bool {
let Ok((node, subscriptions)) = self.nodes.get(entity) else {
return false;
};
if !event.always && !subscriptions.is_some_and(|s| s.contains(event)) {
return false;
}
let Some(out) = self.out.as_ref() else {
return false;
};
send_to(&out.0, node.0, event, payload)
}
pub fn subscribed<T: 'static>(&self, entity: Entity, event: &'static ElementEvent<T>) -> bool {
self.nodes
.get(entity)
.is_ok_and(|(_, s)| s.is_some_and(|s| s.contains(event)))
}
}
pub(crate) fn send_to<T: Serialize + 'static>(
out: &crate::bridge::OutboundSender,
id: crate::protocol::NodeId,
event: &'static ElementEvent<T>,
payload: &T,
) -> bool {
let payload = if TypeId::of::<T>() == TypeId::of::<()>() {
serde_json::Value::Null
} else {
match serde_json::to_value(payload) {
Ok(v) => v,
Err(e) => {
tracing::error!("serialize element event {:?}: {e}", event.name);
return false;
}
}
};
out.send(Outbound::ElementEvent {
id,
event: event.name.to_owned(),
payload,
})
.is_ok()
}