use std::sync::{Arc, Mutex};
use bevy::ecs::component::Mutable;
use bevy::prelude::*;
pub use noesis_runtime::plain_vm::{
PlainInstance, PlainSetHandler, PlainType, PlainValue, PlainValueRef, PlainVmBuilder,
PlainVmClass,
};
use crate::render::{NoesisRenderState, NoesisSet, add_reap_system};
use crate::viewmodel::AttachTarget;
pub(crate) type SetSink = Arc<Mutex<Vec<(u32, PlainValue)>>>;
pub(crate) fn unbox(kind: PlainType, value: &PlainValueRef) -> PlainValue {
if value.is_none() {
return PlainValue::Null;
}
let decoded = match kind {
PlainType::Int32 => value.as_i32().map(PlainValue::Int32),
PlainType::Double => value.as_f64().map(PlainValue::Double),
PlainType::Bool => value.as_bool().map(PlainValue::Bool),
PlainType::String => value.as_str().map(|s| PlainValue::String(s.to_owned())),
PlainType::U64 => value.as_u64().map(PlainValue::U64),
PlainType::BaseComponent => None,
};
decoded.unwrap_or(PlainValue::Null)
}
pub trait NoesisViewModel: Send + Sync + 'static {
fn noesis_type_name() -> &'static str
where
Self: Sized;
fn noesis_properties() -> &'static [(&'static str, PlainType)]
where
Self: Sized;
fn noesis_snapshot(&self) -> Vec<PlainValue>;
fn noesis_apply(&mut self, prop_index: u32, value: &PlainValue);
}
pub(crate) struct PlainVmEntry {
instance: PlainInstance,
_class: PlainVmClass,
type_name: String,
prop_names: Vec<String>,
target: AttachTarget,
attached_for_uri: Option<String>,
set_sink: SetSink,
}
impl PlainVmEntry {
pub(crate) fn build(
type_name: &str,
entity: Entity,
props: &[(&'static str, PlainType)],
target: AttachTarget,
) -> Option<Self> {
let prop_names: Vec<String> = props.iter().map(|(n, _)| (*n).to_owned()).collect();
let kinds: Vec<PlainType> = props.iter().map(|(_, k)| *k).collect();
let set_sink: SetSink = Arc::new(Mutex::new(Vec::new()));
let registered_name = format!("{type_name}#{}", entity.to_bits());
let mut builder = PlainVmBuilder::new(®istered_name);
for (name, kind) in props {
builder.add_property(name, *kind);
}
let sink_for_handler = Arc::clone(&set_sink);
let class = builder
.on_set(move |idx: u32, value: &PlainValueRef| {
let kind = kinds
.get(idx as usize)
.copied()
.unwrap_or(PlainType::BaseComponent);
let owned = unbox(kind, value);
if let Ok(mut queue) = sink_for_handler.lock() {
queue.push((idx, owned));
}
})
.register()?;
let instance = class.create_instance()?;
Some(Self {
instance,
_class: class,
type_name: type_name.to_owned(),
prop_names,
target,
attached_for_uri: None,
set_sink,
})
}
pub(crate) fn apply_snapshot(&self, snapshot: &[PlainValue]) {
for (idx, value) in snapshot.iter().enumerate() {
if let Some(name) = self.prop_names.get(idx) {
let _ = self
.instance
.set_and_notify(idx as u32, name, value.clone());
}
}
}
pub(crate) fn drain_writebacks(&self) -> Vec<(u32, PlainValue)> {
let mut guard = self.set_sink.lock().expect("plain VM set sink poisoned");
if guard.is_empty() {
Vec::new()
} else {
std::mem::take(&mut *guard)
}
}
pub(crate) fn reset_attach(&mut self) {
self.attached_for_uri = None;
}
pub(crate) fn target(&self) -> &AttachTarget {
&self.target
}
pub(crate) fn type_name(&self) -> &str {
&self.type_name
}
pub(crate) fn needs_attach(&self, uri: &str) -> bool {
self.attached_for_uri.as_deref() != Some(uri)
}
pub(crate) fn attach_to(
&mut self,
element: &mut noesis_runtime::view::FrameworkElement,
uri: &str,
) -> bool {
if self.instance.set_data_context(element) {
self.attached_for_uri = Some(uri.to_owned());
true
} else {
false
}
}
}
#[derive(Resource)]
pub struct PlainVmConfig<T: Send + Sync + 'static> {
target: AttachTarget,
_marker: std::marker::PhantomData<fn() -> T>,
}
#[allow(clippy::needless_pass_by_value)]
fn sync_plain_vm_system<T: NoesisViewModel + Component<Mutability = Mutable>>(
mut views: Query<(Entity, &mut T)>,
config: Res<PlainVmConfig<T>>,
state: Option<NonSendMut<NoesisRenderState>>,
) {
let Some(mut state) = state else {
return;
};
for (entity, mut vm) in &mut views {
let snapshot = vm.is_changed().then(|| vm.noesis_snapshot());
let writebacks = state.sync_plain_vm(
entity,
std::any::TypeId::of::<T>(),
T::noesis_type_name(),
T::noesis_properties(),
&config.target,
snapshot,
);
for (index, value) in writebacks {
vm.noesis_apply(index, &value);
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn reap_plain_vm_system<T: NoesisViewModel + Component<Mutability = Mutable>>(
mut removed: RemovedComponents<T>,
state: Option<NonSendMut<NoesisRenderState>>,
) {
let Some(mut state) = state else {
return;
};
for entity in removed.read() {
state.reap_plain_vm_for(entity, std::any::TypeId::of::<T>());
}
}
pub trait NoesisViewModelAppExt {
fn add_noesis_view_model<T: NoesisViewModel + Component<Mutability = Mutable>>(
&mut self,
) -> &mut Self;
fn add_noesis_view_model_at<T: NoesisViewModel + Component<Mutability = Mutable>>(
&mut self,
x_name: impl Into<String>,
) -> &mut Self;
}
impl NoesisViewModelAppExt for App {
fn add_noesis_view_model<T: NoesisViewModel + Component<Mutability = Mutable>>(
&mut self,
) -> &mut Self {
register_plain_vm::<T>(self, AttachTarget::Root)
}
fn add_noesis_view_model_at<T: NoesisViewModel + Component<Mutability = Mutable>>(
&mut self,
x_name: impl Into<String>,
) -> &mut Self {
register_plain_vm::<T>(self, AttachTarget::Named(x_name.into()))
}
}
fn register_plain_vm<T: NoesisViewModel + Component<Mutability = Mutable>>(
app: &mut App,
target: AttachTarget,
) -> &mut App {
app.insert_resource(PlainVmConfig::<T> {
target,
_marker: std::marker::PhantomData,
})
.add_systems(
PostUpdate,
sync_plain_vm_system::<T>.in_set(NoesisSet::Apply),
);
add_reap_system(app, reap_plain_vm_system::<T>);
app
}