Merge pull request #648 from Demonthos/fix-non-str-attributes

fix custom attribute value support
This commit is contained in:
Jon Kelley authored and GitHub committed 2023-01-02 23:41:45 -05:00
commit 07e71956d4
36 files changed
+806 -711

No files matched your search

+9 -3
View File
@@ -153,11 +153,17 @@ impl VirtualDom {
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
let mut listeners = scope.listeners.borrow_mut();
let mut listeners = scope.attributes_to_drop.borrow_mut();
listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener };
if let AttributeValue::Listener(l) = &listener.value {
_ = l.take();
match &listener.value {
AttributeValue::Listener(l) => {
_ = l.take();
}
AttributeValue::Any(a) => {
_ = a.take();
}
_ => (),
}
});
}
+12 -20
View File
@@ -1,5 +1,5 @@
use crate::any_props::AnyProps;
use crate::innerlude::{VComponent, VPlaceholder, VText};
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@@ -285,7 +285,7 @@ impl<'b> VirtualDom {
}
}
fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
// Make sure we set the attribute's associated id
attribute.mounted_element.set(id);
@@ -293,9 +293,17 @@ impl<'b> VirtualDom {
let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
match &attribute.value {
AttributeValue::Text(value) => {
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
name: &unbounded_name[2..],
id,
})
}
_ => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value: &str = unsafe { std::mem::transmute(*value) };
let value: BorrowedAttributeValue<'b> = (&attribute.value).into();
let unbounded_value = unsafe { std::mem::transmute(value) };
self.mutations.push(SetAttribute {
name: unbounded_name,
@@ -304,22 +312,6 @@ impl<'b> VirtualDom {
id,
})
}
AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
name: unbounded_name,
value: *value,
id,
}),
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
name: &unbounded_name[2..],
id,
})
}
AttributeValue::Float(_) => todo!(),
AttributeValue::Int(_) => todo!(),
AttributeValue::Any(_) => todo!(),
AttributeValue::None => todo!(),
}
}
+11 -17
View File
@@ -1,7 +1,7 @@
use crate::{
any_props::AnyProps,
arena::ElementId,
innerlude::{DirtyScope, VComponent, VPlaceholder, VText},
innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
mutations::Mutation,
nodes::RenderReturn,
nodes::{DynamicNode, VNode},
@@ -150,22 +150,16 @@ impl<'b> VirtualDom {
};
}
fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
// todo: add more types of attribute values
match right_attr.value {
AttributeValue::Text(text) => {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value = unsafe { std::mem::transmute(text) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
// todo: more types of attribute values
_ => todo!("other attribute types"),
}
fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
let value = unsafe { std::mem::transmute(value) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
name,
value,
});
}
fn diff_vcomponent(
+7 -7
View File
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
Throw, VNode, VirtualDom,
fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
};
}
+4 -15
View File
@@ -1,6 +1,6 @@
use rustc_hash::FxHashSet;
use crate::{arena::ElementId, ScopeId, Template};
use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template};
/// A container for all the relevant steps to modify the Real DOM
///
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq)]
pub enum Mutation<'a> {
/// Add these m children to the target element
AppendChildren {
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
SetAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: &'a str,
value: BorrowedAttributeValue<'a>,
/// The ID of the node to set the attribute of.
id: ElementId,
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
ns: Option<&'a str>,
},
/// Set the value of a node's attribute.
SetBoolAttribute {
/// The name of the attribute to set.
name: &'a str,
/// The value of the attribute.
value: bool,
/// The ID of the node to set the attribute of.
id: ElementId,
},
/// Set the textcontent of a node.
SetText {
/// The textcontent of the node
+133 -15
View File
@@ -5,8 +5,8 @@ use bumpalo::boxed::Box as BumpBox;
use bumpalo::Bump;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell, UnsafeCell},
fmt::Arguments,
cell::{self, Cell, RefCell, UnsafeCell},
fmt::{Arguments, Debug},
future::Future,
};
@@ -523,13 +523,108 @@ pub enum AttributeValue<'a> {
Listener(RefCell<Option<ListenerCb<'a>>>),
/// An arbitrary value that implements PartialEq and is static
Any(BumpBox<'a, dyn AnyValue>),
Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
///
/// These varients are used to communicate what the value of an attribute is that needs to be updated
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
pub enum BorrowedAttributeValue<'a> {
/// Text attribute
Text(&'a str),
/// A float
Float(f64),
/// Signed integer
Int(i64),
/// Boolean
Bool(bool),
/// An arbitrary value that implements PartialEq and is static
#[cfg_attr(
feature = "serialize",
serde(
deserialize_with = "deserialize_any_value",
serialize_with = "serialize_any_value"
)
)]
Any(std::cell::Ref<'a, dyn AnyValue>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
impl<'a> From<&'a AttributeValue<'a>> for BorrowedAttributeValue<'a> {
fn from(value: &'a AttributeValue<'a>) -> Self {
match value {
AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
AttributeValue::Listener(_) => {
panic!("A listener cannot be turned into a borrowed value")
}
AttributeValue::Any(value) => {
let value = value.borrow();
BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| {
&**value.as_ref().unwrap()
}))
}
AttributeValue::None => BorrowedAttributeValue::None,
}
}
}
impl Debug for BorrowedAttributeValue<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(),
Self::None => write!(f, "None"),
}
}
}
impl PartialEq for BorrowedAttributeValue<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[cfg(feature = "serialize")]
fn serialize_any_value<S>(_: &cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("Any cannot be serialized")
}
#[cfg(feature = "serialize")]
fn deserialize_any_value<'de, 'a, D>(_: D) -> Result<cell::Ref<'a, dyn AnyValue>, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("Any cannot be deserialized")
}
impl<'a> std::fmt::Debug for AttributeValue<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -553,29 +648,36 @@ impl<'a> PartialEq for AttributeValue<'a> {
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Listener(_), Self::Listener(_)) => true,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
(Self::Any(l0), Self::Any(r0)) => {
let l0 = l0.borrow();
let r0 = r0.borrow();
l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
}
_ => false,
}
}
}
#[doc(hidden)]
pub trait AnyValue {
pub trait AnyValue: 'static {
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
fn our_typeid(&self) -> TypeId;
fn as_any(&self) -> &dyn Any;
fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
}
impl<T: PartialEq + Any> AnyValue for T {
impl<T: Any + PartialEq + 'static> AnyValue for T {
fn any_cmp(&self, other: &dyn AnyValue) -> bool {
if self.type_id() != other.our_typeid() {
return false;
if let Some(other) = other.as_any().downcast_ref() {
self == other
} else {
false
}
self == unsafe { &*(other as *const _ as *const T) }
}
fn our_typeid(&self) -> TypeId {
self.type_id()
fn as_any(&self) -> &dyn Any {
self
}
}
@@ -742,26 +844,36 @@ pub trait IntoAttributeValue<'a> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
}
impl<'a> IntoAttributeValue<'a> for AttributeValue<'a> {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
self
}
}
impl<'a> IntoAttributeValue<'a> for &'a str {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(self)
}
}
impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
}
}
impl<'a> IntoAttributeValue<'a> for i64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Int(self)
}
}
impl<'a> IntoAttributeValue<'a> for bool {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Bool(self)
}
}
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
use bumpalo::core_alloc::fmt::Write;
@@ -770,3 +882,9 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
AttributeValue::Text(str_buf.into_bump_str())
}
}
impl<'a> IntoAttributeValue<'a> for BumpBox<'a, dyn AnyValue> {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Any(RefCell::new(Some(self)))
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ impl VirtualDom {
hook_idx: Default::default(),
shared_contexts: Default::default(),
borrowed_props: Default::default(),
listeners: Default::default(),
attributes_to_drop: Default::default(),
}))
}
+21 -6
View File
@@ -7,7 +7,7 @@ use crate::{
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
lazynodes::LazyNodes,
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
Attribute, AttributeValue, Element, Event, Properties, TaskId,
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -88,7 +88,7 @@ pub struct ScopeState {
pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) listeners: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
pub(crate) placeholder: Cell<Option<ElementId>>,
@@ -374,11 +374,15 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
let mut listeners = self.listeners.borrow_mut();
let mut listeners = self.attributes_to_drop.borrow_mut();
for attr in element.dynamic_attrs {
if let AttributeValue::Listener(_) = attr.value {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
match attr.value {
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
_ => (),
}
}
@@ -509,6 +513,17 @@ impl<'src> ScopeState {
AttributeValue::Listener(RefCell::new(Some(boxed)))
}
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue<'src> {
// safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
// This is the suggested way to build a bumpbox
//
// In theory, we could just use regular boxes
let boxed: BumpBox<'src, dyn AnyValue> =
unsafe { BumpBox::from_raw(self.bump().alloc(value)) };
AttributeValue::Any(RefCell::new(Some(boxed)))
}
/// Inject an error into the nearest error boundary and quit rendering
///
/// The error doesn't need to implement Error or any specific traits since the boundary
+1 -4
View File
@@ -641,10 +641,7 @@ impl VirtualDom {
/// Swap the current mutations with a new
fn finalize(&mut self) -> Mutations {
// todo: make this a routine
let mut out = Mutations::default();
std::mem::swap(&mut self.mutations, &mut out);
out
std::mem::take(&mut self.mutations)
}
}