I've ignored most of the changes and the projects still compile and test
fine. Changes I did commit:
Add `+ use<>` after functions.
Remove `ref mut` from some patterns.
Bump MSRV.
Apply new rustfmt format.
This PR starts with supporting specialized elements (i.e. more than just
the `Element` DOM interface), starting with boolean attributes in
`HtmlInputElement` (`checked`, `default_checked`, `disabled`,
`required`, `multiple`).
With a general `OverwriteBool` modifier which is optimized by encoding
boolean flags into a `u32`, thus not requiring allocations (compared to
the other modifiers we currently have), but is limited to max 32
overwrites, which I don't think should be a real-world limitation (i.e.
`input_el.checked().checked()...checked()` 32 times makes no sense), I
also started adding tests for this, as it juggles around with
bit-operations (and we should generally start writing more tests).
I have originally planned to feature-flag this (i.e. have a feature like
`HtmlInputElement`).
But I'd like to see how far we can go without this, I haven't yet
noticed significant (binary-size) regressions in the todomvc example
(which uses `input` elements) that justifies the worse DX that
additional features introduce.
Having features for this is also not optimal for another reason: It
changes the API (e.g. `DomNode::Props` is `props::HtmlInputElement`
instead of `props::Element`.
It also factors the element state flags (`was_created`, `in_hydration`)
into a separate `ElementFlags` which is shared within a `Modifier<M>`
struct (similar as `PodMut` or `WidgetMut` in masonry), so that we don't
have to duplicate that state in every modifier. Additionally a new flag
`needs_update` is introduced, which indicates that the element in
general needs to update any modifier, and is entirely an optimization to
avoid checking every modifier whether it has changed (not yet that
important, but when we have a lot of modifiers per element, having to
check every modifier is less efficient, it's also already *slightly*
visible in the js-framework-benchmark).
For this, it unfortunately suffers similar as #705 from the free-form
syntax by being a little bit more verbose (which may be reverted after
`arbitrary_self_types` are stable).
It should also fix#716
Previously the modifier systems had design issues i.e. bugs (non-deleted
styles/classes/attributes), and were unnecessary complex.
This aims to solve this (partly) by not using separate traits, but
concrete types and a different mechanism that is closer to how
`ElementSplice` works.
There's a few fundamental properties that composable type-based
modifiers need to support to avoid surprising/buggy behavior:
* Minimize actual changes to the underlying element, as DOM traffic is
expensive.
* Be compatible to memoization: e.g. a `Rotate` view should still be
applicable to possibly memoized transform values of the underlying
element.
* Recreation when the underlying element has changed (e.g. with a change
of variants of a `OneOf`).
To support all this, the modifier system needs to retain modifiers for
each modifier-view, and track its changes of the corresponding view.
Previously all elements were directly written and separated with markers
into a `Vec` to limit the boundaries of such views, but this had issues,
when e.g. all modifiers were deleted (e.g. clearing a `Vec` of classes),
by not reacting to this (I noticed that issue in the todomvc example
with the footer).
With this PR, the count of modifiers of a modifier-view are directly
stored either (hardcoded) in the view impl or its view state, which
cleans up the concrete modifier elements (such as `AttributeModifier`,
not including a separate `Marker` variant), and makes it less prone for
errors (and is slightly less memory-intensive).
The API to use these modifiers in modifier-views was also redesigned to
hopefully be more straight-forward/idiomatic. But as mentioned above
there's still challenges, which introduce complexity (which I'd like to
hide at least for simpler cases than these modifiers, likely in a future
PR).
All of this should now be documented in the new `modifier` module, where
now the modifiers `Attributes`, `Classes` and `Styles` reside. Other
views (like events) may also end up there...
One interesting aspect compared to the previous system is the use of a
new trait `With` for modifiers.
Instead of (roughly) `Element: WithStyle`, it works with `Element:
With<Styles>`.
This prevents all kinds of reimplementations of something like
`WithStyle` for elements.
This gets especially visible in the `one_of` module, which now can be
covered by a single blanket implementation.
Further the cargo-feature "hydration" was deleted, as it causes more
headaches to maintain than it really brings benefits (minimally less
binary size), depending on the future, it may or may not make sense to
reintroduce this.
It's possible to avoid returning the `ViewElement::Mut` in
`View::rebuild`, while having access to the element *after* a child view
was rebuilt.
This PR removes that again making the signature a little bit cleaner,
and also cleans up a lot of other stuff:
* `Mut<'_, Self::Element>` can be shortened to `Mut<Self::Element>`
* `use xilem::core::*` instead of `use xilem_core::*`, this is
especially useful in the examples, to avoid newcomers giving the
impression, that an additional dependency xilem_core is needed.
* Various cosmetic stuff, like one typo fix, empty line between
doc-comment (as (nightly) clippy currently bleats for me)
For more context see
https://xi.zulipchat.com/#narrow/stream/354396-xilem/topic/.E2.9C.94.20.60.26mut.20ViewElement.3A.3AMut.3C'_.3E.60.20in.20.60View.3A.3Arebuild.60.3F
With the risk of wasting time with micro-optimizations...
Honestly the diff bigger and more complex than I would like it to be for
the effect that this change does, so I'm sorry to reviewers.
FWIW, I tested all the examples with the feature "hydration" disabled
and enabled.
In effect this reduces the amount of allocs and the size of allocs of
all the modifiers (since Vec allocs at least 4 elements) by:
* Adding a size hint in modifier views, to give the actual element props
a hint how much memory is needed in the relevant modifier, so that only
one allocation occurs when the actual modifier is added/created.
* Using bitflags encoded in the `start_idx` to avoid extra 4 bytes for
just two booleans in each modifier, and also use `u16` instead of
`usize` to save another 4 bytes.
* Avoiding `updated_modifiers` allocations when building the element
(with the `CREATING` bitflag).
* Reduce allocations when rebuilding, by skipping non-changed modifiers
(before this, `Cow` was cloned, which for example with a `String` as an
attribute value resulted in an additional allocation).
This minimally increases the wasm binary size (though I'm pretty sure in
a constant fashion, roughly 500bytes compressed) in exchange for faster
creation of elements and less memory usage (in the 10k
js-framework-bench roughly 10% less memory usage, and 7-8% faster).
I've also noticed that all the kurbo SVG views weren't hydrated yet,
which I think results in buggy behavior when using it e.g. in a
`Templated` view, this is kind of drive-by fix (which I didn't want to
separate because of additional work...)
This was the initial plan of #403, I can't remember anymore why I didn't
made it to work back then. Anyway this should simplify the type
signature of `Pod` and `PodMut` a little bit.
This also includes a little bit of refactoring otherwise (mostly
aesthetic + removal of unnecessary wrapper `DynNode`)
I think I've covered every pub item at the top level.
It also renames (taking `Attributes` as example)
`start_attribute_modifier` -> `rebuild_attribute_modifier`,
and `end_attribute_modifier` -> `mark_end_of_attribute_modifier`, as I
think that makes it more clear what these methods do.
`start_attribute_modifier` was a noop in `View::build` and may lead to
buggy behavior when `DomNode::apply_props` is called before every parent
`View::build` has done modifying the props. This should make
`DomNode::apply_props` robust. And `AfterBuild` is fixed now with that.
This ports xilem_web to the new xilem_core.
There's also a lot of cleanup internally:
* Get rid of all of the complex macros to support DOM interfaces, and
instead use associated type bounds on the `View::Element`.
* Introduce an extendable modifier based system, which should also work
on top of memoization (`Arc`, `Memoize`) and `OneOf` views with an
intersection of the modifiable properties.
* This modifier based system gets rid of the hacky way to propagate
attributes to elements, and takes inspiration by masonrys `WidgetMut`
type to apply changes.
* Currently that means `Attributes`, `Classes` and `Styles` to reflect
what xilem_web previously offered.
Downsides (currently, needs some investigation):
~~Due to more internal type complexity via associated types this suffers
from https://github.com/rust-lang/rust/issues/105900. The new trait
solver should hopefully mitigate some of that, but it seems currently it
completely stalls in the todomvc example (not in other examples).~~
~~The deep, possibly completely static composition via associated
type-bounds of the view and element tree unfortunately can take some
time to compile, this gets (already) obvious in the todomvc example. The
other examples don't seem to suffer that bad yet from that issue,
probably because they're quite simple.~~
~~I really hope we can mitigate this mostly, because I think this is the
idiomatic (and more correct) way to implement what the previous API has
offered.~~
One idea is to add a `Box<dyn AnyViewSequence>`, as every element takes
a "type-erased" `ViewSequence` as parameter, so this may solve most of
the issues (at the slight cost of dynamic dispatch/allocations).
Edit: idea was mostly successful, see comment right below.
I think it also closes#274
It's a draft, as there's a lot of changes in xilem_core that should be
upstreamed (and cleaned up) via separate PRs and I would like to
(mostly) fix the slow-compile time issue.
---------
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>