#[macro_export]
macro_rules! merge_var {
($($tt:tt)+) => {
$crate::__merge_var! {
$crate,
$($tt)+
}
};
}
use core::fmt;
use std::{
any::TypeId,
marker::PhantomData,
ops,
sync::{Arc, Weak},
};
use parking_lot::Mutex;
use smallbox::{SmallBox, smallbox};
use zng_clone_move::clmv;
#[doc(hidden)]
pub use zng_var_proc_macros::merge_var as __merge_var;
use crate::{
AnyVar, AnyVarValue, BoxAnyVarValue, ContextVar, DynAnyVar, DynWeakAnyVar, Response, ResponseVar, Var, VarHandle, VarImpl,
VarInstanceTag, VarValue, WeakVarImpl, any_contextual_var, any_var, contextual_var,
};
use super::VarCapability;
#[doc(hidden)]
pub fn merge_var_input<I: VarValue>(input: impl MergeInput<I>) -> AnyVar {
input.into_merge_input().into()
}
#[doc(hidden)]
pub fn merge_var_with(var: &AnyVar, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
var.0.with(visitor);
}
#[doc(hidden)]
pub fn merge_var_output<O: VarValue>(output: O) -> BoxAnyVarValue {
BoxAnyVarValue::new(output)
}
#[doc(hidden)]
pub fn merge_var<O: VarValue>(inputs: Box<[AnyVar]>, merge: impl FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static) -> Var<O> {
Var::new_any(var_merge_impl(inputs, smallbox!(merge), TypeId::of::<O>()))
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(note = "merge_var! and expr_var! inputs can be: Var<T>, ContextVar<T> or ResponseVar<T>")]
pub trait MergeInput<T: VarValue> {
fn into_merge_input(self) -> Var<T>;
}
impl<T: VarValue> MergeInput<T> for Var<T> {
fn into_merge_input(self) -> Var<T> {
self
}
}
impl<T: VarValue> MergeInput<T> for ContextVar<T> {
fn into_merge_input(self) -> Var<T> {
self.into()
}
}
impl<T: VarValue> MergeInput<Response<T>> for ResponseVar<T> {
fn into_merge_input(self) -> Var<Response<T>> {
self.into()
}
}
fn var_merge_impl(inputs: Box<[AnyVar]>, merge: MergeFn, value_type: TypeId) -> AnyVar {
if inputs.iter().any(|i| i.capabilities().is_contextual()) {
let merge = Arc::new(Mutex::new(merge));
return any_contextual_var(
move || {
let mut inputs = inputs.clone();
for v in inputs.iter_mut() {
if v.capabilities().is_contextual() {
*v = v.current_context();
}
}
var_merge_tail(inputs, smallbox!(clmv!(merge, |inputs: &[AnyVar]| { merge.lock()(inputs) })))
},
value_type,
);
}
var_merge_tail(inputs, merge)
}
fn var_merge_tail(inputs: Box<[AnyVar]>, mut merge: MergeFn) -> AnyVar {
let output = any_var(merge(&inputs));
let data = Arc::new(MergeVarData {
inputs,
merge: Mutex::new((merge, 0)),
output,
});
for input in &data.inputs {
let weak = Arc::downgrade(&data);
input
.hook(move |_| {
if let Some(data) = weak.upgrade() {
let modify_id = data.merge.lock().1;
data.output.modify(clmv!(weak, |output| {
if let Some(data) = weak.upgrade() {
let mut m = data.merge.lock();
if m.1 != modify_id {
return;
}
let new_value = m.0(&data.inputs);
output.set(new_value);
}
}));
true
} else {
false
}
})
.perm();
}
AnyVar(DynAnyVar::Merge(MergeVar(data)))
}
type MergeFn = SmallBox<dyn FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static, smallbox::space::S4>;
struct MergeVarData {
inputs: Box<[AnyVar]>,
merge: Mutex<(MergeFn, usize)>,
output: AnyVar,
}
pub(crate) struct MergeVar(Arc<MergeVarData>);
impl fmt::Debug for MergeVar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut b = f.debug_struct("MergeVar");
b.field("var_instance_tag()", &self.var_instance_tag());
b.field("inputs", &self.0.inputs);
b.field("output", &self.0.output);
b.finish()
}
}
impl VarImpl for MergeVar {
fn clone_dyn(&self) -> DynAnyVar {
DynAnyVar::Merge(MergeVar(self.0.clone()))
}
fn value_type(&self) -> std::any::TypeId {
self.0.output.0.value_type()
}
#[cfg(feature = "type_names")]
fn value_type_name(&self) -> &'static str {
self.0.output.0.value_type_name()
}
fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
fn var_eq(&self, other: &DynAnyVar) -> bool {
match other {
DynAnyVar::Merge(o) => Arc::ptr_eq(&self.0, &o.0),
_ => false,
}
}
fn var_instance_tag(&self) -> VarInstanceTag {
VarInstanceTag(Arc::as_ptr(&self.0) as _)
}
fn downgrade(&self) -> DynWeakAnyVar {
DynWeakAnyVar::Merge(WeakMergeVar(Arc::downgrade(&self.0)))
}
fn capabilities(&self) -> VarCapability {
self.0.output.0.capabilities().as_always_read_only()
}
fn with(&self, visitor: &mut dyn FnMut(&dyn AnyVarValue)) {
self.0.output.0.with(visitor);
}
fn get(&self) -> BoxAnyVarValue {
self.0.output.0.get()
}
fn set(&self, _: BoxAnyVarValue) -> bool {
false
}
fn update(&self) -> bool {
false
}
fn modify(&self, _: SmallBox<dyn FnMut(&mut super::AnyVarModify) + Send + 'static, smallbox::space::S4>) -> bool {
false
}
fn hook(&self, on_new: SmallBox<dyn FnMut(&crate::AnyVarHookArgs) -> bool + Send + 'static, smallbox::space::S4>) -> super::VarHandle {
self.0.output.0.hook(on_new)
}
fn last_update(&self) -> crate::VarUpdateId {
self.0.output.0.last_update()
}
fn modify_info(&self) -> crate::animation::ModifyInfo {
self.0.output.0.modify_info()
}
fn modify_importance(&self) -> usize {
self.0.output.0.modify_importance()
}
fn is_animating(&self) -> bool {
self.0.output.0.is_animating()
}
fn hook_animation_stop(&self, handler: crate::animation::AnimationStopFn) -> VarHandle {
self.0.output.0.hook_animation_stop(handler)
}
fn current_context(&self) -> DynAnyVar {
self.clone_dyn()
}
}
pub(crate) struct WeakMergeVar(Weak<MergeVarData>);
impl fmt::Debug for WeakMergeVar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("WeakMergeVar").field(&self.0.as_ptr()).finish()
}
}
impl WeakVarImpl for WeakMergeVar {
fn clone_dyn(&self) -> DynWeakAnyVar {
DynWeakAnyVar::Merge(WeakMergeVar(self.0.clone()))
}
fn strong_count(&self) -> usize {
self.0.strong_count()
}
fn upgrade(&self) -> Option<DynAnyVar> {
Some(DynAnyVar::Merge(MergeVar(self.0.upgrade()?)))
}
fn var_eq(&self, other: &DynWeakAnyVar) -> bool {
match other {
DynWeakAnyVar::Merge(o) => self.0.ptr_eq(&o.0),
_ => false,
}
}
}
#[derive(Clone)]
pub struct MergeVarBuilder<I: VarValue> {
inputs: Vec<AnyVar>,
_type: PhantomData<fn() -> I>,
}
impl<I: VarValue> MergeVarBuilder<I> {
pub fn new() -> Self {
Self {
inputs: vec![],
_type: PhantomData,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inputs: Vec::with_capacity(capacity),
_type: PhantomData,
}
}
pub fn push(&mut self, input: impl MergeInput<I>) {
self.inputs.push(input.into_merge_input().into())
}
pub fn build<O: VarValue>(self, merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static) -> Var<O> {
if self.inputs.iter().any(|i| i.capabilities().is_contextual()) {
let merge = Arc::new(Mutex::new(merge));
return contextual_var(move || {
let builder = MergeVarBuilder {
inputs: self.inputs.iter().map(|v| v.current_context()).collect(),
_type: PhantomData,
};
builder.build_tail(clmv!(merge, |inputs| merge.lock()(inputs)))
});
}
self.build_tail(merge)
}
fn build_tail<O: VarValue>(self, mut merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static) -> Var<O> {
let any = var_merge_impl(
self.inputs.into_boxed_slice(),
smallbox!(move |vars: &[AnyVar]| {
let values: Box<[BoxAnyVarValue]> = vars.iter().map(|v| v.get()).collect();
let out = merge(VarMergeInputs {
inputs: &values[..],
_type: PhantomData,
});
BoxAnyVarValue::new(out)
}),
TypeId::of::<O>(),
);
Var::new_any(any)
}
}
impl<I: VarValue> Default for MergeVarBuilder<I> {
fn default() -> Self {
Self::new()
}
}
pub struct VarMergeInputs<'a, I: VarValue> {
inputs: &'a [BoxAnyVarValue],
_type: PhantomData<&'a I>,
}
impl<I: VarValue> VarMergeInputs<'_, I> {
#[expect(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.inputs.len()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &I> + '_ {
(0..self.len()).map(move |i| &self[i])
}
}
impl<I: VarValue> ops::Index<usize> for VarMergeInputs<'_, I> {
type Output = I;
fn index(&self, index: usize) -> &Self::Output {
self.inputs[index].downcast_ref().unwrap()
}
}