#[macro_export]
macro_rules! merge_var {
($($tt:tt)+) => {
$crate::__merge_var! {
$crate,
$($tt)+
}
};
}
use core::fmt;
use std::{any::TypeId, fmt::Write as _, marker::PhantomData, ops, sync::Arc};
use parking_lot::Mutex;
use zng_clone_move::clmv;
use zng_txt::Txt;
#[doc(hidden)]
pub use zng_var_proc_macros::merge_var as __merge_var;
use crate::{
AnyVar, AnyVarModify, AnyVarValue, BoxAnyVarValue, ContextVar, Response, ResponseVar, Var, VarImpl, VarInstanceTag, VarModify,
VarValue, WeakAnyVar, any_contextual_var, any_var,
};
#[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(merge_var_impl(inputs, Arc::new(Mutex::new(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 merge_var_impl(inputs: Box<[AnyVar]>, merge: MergeFn, value_type: TypeId) -> AnyVar {
if inputs.iter().any(|i| i.capabilities().is_contextual()) {
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();
}
}
merge_var_tail(inputs, merge.clone())
},
value_type,
);
}
merge_var_tail(inputs, merge)
}
fn merge_var_tail(inputs: Box<[AnyVar]>, merge: MergeFn) -> AnyVar {
let output = any_var(merge.lock()(&inputs));
struct InputData {
inputs: Box<[AnyVar]>,
merge: MergeFn,
output_wk: WeakAnyVar,
}
let input_data = Arc::new(InputData {
inputs,
merge,
output_wk: output.downgrade(),
});
for input in &input_data.inputs {
let input_data_wk = Arc::downgrade(&input_data);
input
.hook(move |a| {
if let Some(input_data) = input_data_wk.upgrade()
&& let Some(output) = input_data.output_wk.upgrade()
{
let update = a.update();
output.modify(clmv!(input_data_wk, |output| if let Some(input_data) = input_data_wk.upgrade() {
let new_value = input_data.merge.lock()(&input_data.inputs);
output.set(new_value);
if update {
output.update();
}
}));
true
} else {
false
}
})
.perm();
}
output.hold(input_data).perm();
output.read_only()
}
fn merge_var_bidi_impl(inputs: Box<[AnyVar]>, merge: MergeFn, map_back: MapBackFn, value_type: TypeId) -> AnyVar {
if inputs.iter().any(|i| i.capabilities().is_contextual()) {
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();
}
}
merge_var_bidi_tail(inputs, merge.clone(), map_back.clone())
},
value_type,
);
}
merge_var_bidi_tail(inputs, merge, map_back)
}
fn merge_var_bidi_tail(inputs: Box<[AnyVar]>, merge: MergeFn, map_back: MapBackFn) -> AnyVar {
let output = any_var(merge.lock()(&inputs));
struct InputData {
inputs: Box<[AnyVar]>,
merge: MergeFn,
map_back: MapBackFn,
output_wk: WeakAnyVar,
}
let input_data = Arc::new(InputData {
inputs,
merge,
map_back,
output_wk: output.downgrade(),
});
for input in &input_data.inputs {
let input_data_wk = Arc::downgrade(&input_data);
input
.hook(move |a| {
if let Some(input_data) = input_data_wk.upgrade()
&& let Some(output) = input_data.output_wk.upgrade()
{
let update = a.update();
output.modify(clmv!(input_data_wk, |output| if let Some(input_data) = input_data_wk.upgrade() {
let new_value = input_data.merge.lock()(&input_data.inputs);
output.set(new_value);
if update {
output.update();
}
}));
true
} else {
false
}
})
.perm();
}
output
.hook(move |a| {
let mut map_back = input_data.map_back.lock();
for (i, input) in input_data.inputs.iter().enumerate() {
if input.capabilities().can_modify() {
input.set(map_back(a.value(), i));
}
}
true
})
.perm();
output
}
fn merge_var_bidi_modify_impl(inputs: Box<[AnyVar]>, merge: MergeFn, modify_back: ModifyBackFn, value_type: TypeId) -> AnyVar {
if inputs.iter().any(|i| i.capabilities().is_contextual()) {
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();
}
}
merge_var_bidi_modify_tail(inputs, merge.clone(), modify_back.clone())
},
value_type,
);
}
merge_var_bidi_modify_tail(inputs, merge, modify_back)
}
fn merge_var_bidi_modify_tail(inputs: Box<[AnyVar]>, merge: MergeFn, modify_back: ModifyBackFn) -> AnyVar {
let output = any_var(merge.lock()(&inputs));
struct InputData {
inputs: Box<[AnyVar]>,
merge: MergeFn,
modify_back: ModifyBackFn,
output_wk: WeakAnyVar,
}
let input_data = Arc::new(InputData {
inputs,
merge,
modify_back,
output_wk: output.downgrade(),
});
#[derive(Debug, PartialEq, Clone, Copy)]
struct InputToOutputTag(VarInstanceTag);
#[derive(Debug, PartialEq, Clone, Copy)]
struct OutputToInputsTag(VarInstanceTag);
let input_to_output_tag = InputToOutputTag(output.var_instance_tag());
let output_to_inputs_tag = OutputToInputsTag(output.var_instance_tag());
for input in &input_data.inputs {
let input_data_wk = Arc::downgrade(&input_data);
input
.hook(move |a| {
if let Some(input_data) = input_data_wk.upgrade()
&& let Some(output) = input_data.output_wk.upgrade()
{
if a.contains_tag(&output_to_inputs_tag) {
return true;
}
let update = a.update();
output.modify(clmv!(input_data_wk, |output| if let Some(input_data) = input_data_wk.upgrade() {
let new_value = input_data.merge.lock()(&input_data.inputs);
let changed = output.set(new_value);
if update {
output.update();
}
if changed || update {
output.push_tag(input_to_output_tag);
}
}));
true
} else {
false
}
})
.perm();
}
output
.hook(move |a| {
if a.contains_tag(&input_to_output_tag) {
return true;
}
for (i, input) in input_data.inputs.iter().enumerate() {
if input.capabilities().can_modify() {
let output_wk = input_data.output_wk.clone();
let modify_back = input_data.modify_back.clone();
let update = a.update();
input.modify(move |m| {
if let Some(output) = output_wk.upgrade() {
let has_updated = m.check_update(|m| {
output.with(|o| {
modify_back.lock()(o, i, m);
});
if update {
m.update();
}
});
if has_updated {
m.push_tag(output_to_inputs_tag);
}
}
});
}
}
true
})
.perm();
output
}
type MergeFn = Arc<Mutex<dyn FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static>>;
type MapBackFn = Arc<Mutex<dyn FnMut(&dyn AnyVarValue, usize) -> BoxAnyVarValue + Send + 'static>>;
type ModifyBackFn = Arc<Mutex<dyn FnMut(&dyn AnyVarValue, usize, &mut AnyVarModify) + Send + 'static>>;
#[derive(Clone)]
pub struct AnyMergeVarBuilder {
inputs: Vec<AnyVar>,
}
impl Default for AnyMergeVarBuilder {
fn default() -> Self {
Self::new()
}
}
impl AnyMergeVarBuilder {
pub fn new() -> Self {
Self { inputs: vec![] }
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inputs: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, input: AnyVar) {
self.inputs.push(input);
}
fn read_only_inputs(self) -> Box<[AnyVar]> {
let mut inputs = self.inputs;
for input in &mut inputs {
if !input.capabilities().is_always_read_only() {
let v = input.read_only();
*input = v;
}
}
inputs.into_boxed_slice()
}
pub fn build_any(self, merge: impl FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static, output_type: TypeId) -> AnyVar {
merge_var_impl(self.read_only_inputs(), Arc::new(Mutex::new(merge)), output_type)
}
pub fn build<O: VarValue>(self, mut merge: impl FnMut(&[AnyVar]) -> O + Send + 'static) -> Var<O> {
Var::new_any(self.build_any(move |inputs| BoxAnyVarValue::new(merge(inputs)), TypeId::of::<O>()))
}
pub fn build_bidi_any(
self,
merge: impl FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static,
map_back: impl FnMut(&dyn AnyVarValue, usize) -> BoxAnyVarValue + Send + 'static,
output_type: TypeId,
) -> AnyVar {
merge_var_bidi_impl(
self.inputs.into_boxed_slice(),
Arc::new(Mutex::new(merge)),
Arc::new(Mutex::new(map_back)),
output_type,
)
}
pub fn build_bidi_any_modify(
self,
merge: impl FnMut(&[AnyVar]) -> BoxAnyVarValue + Send + 'static,
modify_back: impl FnMut(&dyn AnyVarValue, usize, &mut AnyVarModify) + Send + 'static,
output_type: TypeId,
) -> AnyVar {
merge_var_bidi_modify_impl(
self.inputs.into_boxed_slice(),
Arc::new(Mutex::new(merge)),
Arc::new(Mutex::new(modify_back)),
output_type,
)
}
pub fn build_bidi<O: VarValue>(
self,
mut merge: impl FnMut(&[AnyVar]) -> O + Send + 'static,
mut map_back: impl FnMut(&O, usize) -> BoxAnyVarValue + Send + 'static,
) -> Var<O> {
Var::new_any(self.build_bidi_any(
move |inputs| BoxAnyVarValue::new(merge(inputs)),
move |output, input_idx| map_back(output.downcast_ref::<O>().unwrap(), input_idx),
TypeId::of::<O>(),
))
}
pub fn build_bidi_modify<O: VarValue>(
self,
mut merge: impl FnMut(&[AnyVar]) -> O + Send + 'static,
mut modify_back: impl FnMut(&O, usize, &mut AnyVarModify) + Send + 'static,
) -> Var<O> {
Var::new_any(self.build_bidi_any_modify(
move |inputs| BoxAnyVarValue::new(merge(inputs)),
move |output, input_idx, m| modify_back(output.downcast_ref::<O>().unwrap(), input_idx, m),
TypeId::of::<O>(),
))
}
pub fn into_typed<I: VarValue>(self) -> MergeVarBuilder<I> {
let i_id = TypeId::of::<I>();
for input in &self.inputs {
assert_eq!(i_id, input.value_type());
}
MergeVarBuilder {
builder: self,
_type: PhantomData,
}
}
}
#[derive(Clone)]
pub struct MergeVarBuilder<I: VarValue> {
builder: AnyMergeVarBuilder,
_type: PhantomData<fn() -> I>,
}
impl<I: VarValue> MergeVarBuilder<I> {
pub fn new() -> Self {
Self {
builder: AnyMergeVarBuilder::new(),
_type: PhantomData,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
builder: AnyMergeVarBuilder::with_capacity(capacity),
_type: PhantomData,
}
}
pub fn push(&mut self, input: impl MergeInput<I>) {
self.builder.push(input.into_merge_input().into())
}
pub fn build<O: VarValue>(self, mut merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static) -> Var<O> {
self.builder.build(move |inputs| {
let values: Vec<_> = inputs.iter().map(AnyVar::get).collect();
merge(VarMergeInputs {
inputs: &values,
_type: PhantomData,
})
})
}
pub fn build_bidi<O: VarValue>(
self,
mut merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static,
mut map_back: impl FnMut(&O, usize) -> I + Send + 'static,
) -> Var<O> {
self.builder.build_bidi(
move |inputs| {
let values: Vec<_> = inputs.iter().map(AnyVar::get).collect();
merge(VarMergeInputs {
inputs: &values,
_type: PhantomData,
})
},
move |output, input_idx| BoxAnyVarValue::new(map_back(output, input_idx)),
)
}
pub fn build_bidi_modify<O: VarValue>(
self,
mut merge: impl FnMut(VarMergeInputs<I>) -> O + Send + 'static,
mut modify_back: impl FnMut(&O, usize, VarModify<I>) + Send + 'static,
) -> Var<O> {
self.builder.build_bidi_modify(
move |inputs| {
let values: Vec<_> = inputs.iter().map(AnyVar::get).collect();
merge(VarMergeInputs {
inputs: &values,
_type: PhantomData,
})
},
move |output, input_idx, m| modify_back(output, input_idx, m.downcast().unwrap()),
)
}
}
impl<I: VarValue> Default for MergeVarBuilder<I> {
fn default() -> Self {
Self::new()
}
}
impl<T: VarValue + AsRef<str>> MergeVarBuilder<T> {
pub fn join_txt(self, separator: impl Into<Txt>) -> Var<Txt> {
self.join_txt_impl(separator.into())
}
fn join_txt_impl(self, separator: Txt) -> Var<Txt> {
self.build(move |t| {
let mut s = String::new();
let mut sep = "";
for t in t.iter() {
write!(&mut s, "{sep}{}", t.as_ref()).unwrap();
sep = &separator;
}
s.into()
})
}
}
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()
}
}
impl<I: VarValue> fmt::Debug for VarMergeInputs<'_, I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt::Debug::fmt(self.inputs, f)
}
}