-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move xilem
onto a new xilem_core
, which uses a generic View trait
#310
Conversation
xilem
onto a new xilem_core
, which uses a generic View trait
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some thoughts which might be helpful for reviewers
let ret = f(child); | ||
self.element.remove_child(self.ix); | ||
// Also remove the implicit spacer | ||
// TODO: Make the spacers be explicit? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we want two forms of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I lean towards only one, and more explicit, we may want some good helper views/types then probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will probably be a follow-up PR
/// state remained the same. It is expected that this type won't be used very | ||
/// often. | ||
#[allow(unused)] | ||
/// This allows for sub-sections of your app to use an elm-like architecture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How useful is this really?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure yet about it exactly, but I guess it (also) allows to modularize views, possibly in external libraries. Maybe we don't need it, when we have found a satisfying solution for local/modularized state.
MessageResult::Stale(event) => MessageResult::Stale(event), | ||
MessageResult::Nop => MessageResult::Nop, | ||
} | ||
impl<T> Message for T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has some footguns around e.g. Box(Message)
also implementing these same methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's indeed suboptimal...
Having Message
as generic View
param has certainly some advantages, since I have the feeling that View
will not really be used directly unless for implementing it for types maybe worth exploring? But yeah, yet another additional generic parameter...
xilem_core/src/sequence.rs
Outdated
@@ -1,363 +1,650 @@ | |||
// Copyright 2023 the Xilem Authors | |||
// Copyright 2024 the Xilem Authors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about these year changes; they're mostly accidental
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean most of the code here is rewritten, so 2024 makes sense? But maybe we also want to have year-ranges to be more accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a first round of comments, I'll go deeper into this later.
/// state remained the same. It is expected that this type won't be used very | ||
/// often. | ||
#[allow(unused)] | ||
/// This allows for sub-sections of your app to use an elm-like architecture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure yet about it exactly, but I guess it (also) allows to modularize views, possibly in external libraries. Maybe we don't need it, when we have found a satisfying solution for local/modularized state.
xilem_core/src/sequence.rs
Outdated
@@ -1,363 +1,650 @@ | |||
// Copyright 2023 the Xilem Authors | |||
// Copyright 2024 the Xilem Authors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean most of the code here is rewritten, so 2024 makes sense? But maybe we also want to have year-ranges to be more accurate.
prev: &Self, | ||
seq_state: &mut Self::SeqState, | ||
ctx: &mut Context, | ||
elements: &mut impl ElementSplice<Element>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this makes the trait "object-unsafe".
Just noting, I see ElementSplice
is already not object-safe, I remember working around that, to make it possible to use dyn
, but maybe it's not necessary to have this object-safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - it would be nice to have an AnyViewSequence
(and we'll probably need it for hot reloading?), but it should still be a relatively straightforward adaptation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we've really nailed "React architecture in Rust" yet, but this PR gets us one step closer.
Blocking merge on tests; will need review again once those are added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew... not the easiest to digest code, but indeed better than #235 (when it can cover the functionality introduced there without the reborrowing)
I do think the added complexity is worth it though to avoid the macros and being more flexible with different view contexts.
And it seems, that not every contributor of xilem does need to understand all this code for (non-core) contributions...
I hope, that due to all the added typing, composing views will not let the compiler explode (in compilation time mostly).
Great, well-thought work!
I don't think we've really nailed "React architecture in Rust" yet
I'm not sure we even want that, having to deal with a lot of React ugliness in my job currently (e.g. useEffect
or magic (unwanted) reconciliations).
I'm not the biggest fan of distributed "reactive" state as it's done in React (nor the "centralized state" solutions that exist there)...
We still want to find a satisfying and simple solution for modularized views and state anyway, to support convenient library crates for (composed?) views. (but that's orthogonal to this PR)
let ret = f(child); | ||
self.element.remove_child(self.ix); | ||
// Also remove the implicit spacer | ||
// TODO: Make the spacers be explicit? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I lean towards only one, and more explicit, we may want some good helper views/types then probably.
MessageResult::Stale(event) => MessageResult::Stale(event), | ||
MessageResult::Nop => MessageResult::Nop, | ||
} | ||
impl<T> Message for T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's indeed suboptimal...
Having Message
as generic View
param has certainly some advantages, since I have the feeling that View
will not really be used directly unless for implementing it for types maybe worth exploring? But yeah, yet another additional generic parameter...
/// In order to support the open-ended [`DynMessage`] type, this trait requires an | ||
/// allocator to be available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another reason to make it generic then I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The allocator story isn't great, but a lot of views need it - i.e. sequences and AnyView
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, in that case we may want to wait for Allocator traits etc. anyways before committing to something like that.
I don't think there are good ways for these cases without allocators?
#[doc(hidden)] | ||
pub struct WasAView; | ||
|
||
impl<State, Action, Context, V, Element> ViewSequence<State, Action, Context, Element, WasAView> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impl<State, Action, Context, V, Element> ViewSequence<State, Action, Context, Element, WasAView> | |
impl<State, Action, Context, V, Element> ViewSequence<State, Action, Context, Element, fn() -> WasAView> |
Otherwise it's e.g. not possible to Clone
a ViewSequence (I just ran into that).
Alternatively we could also do
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Copy)]
pub struct WasAView;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me how this impacts Clone
. Can you explain please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider all the views would implement Clone
(which I have tested with either xilem_web and xilem right now, they currently do not, probably something for a follow-up PR, because I don't see a reason against implementing all these derive traits).
This doesn't work without this addition:
fn my_clonable_view() -> impl WidgetView<()> + Clone {
flex((button(...), button(...)))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, so it's a perfect derive issue?
I'd say that you should just implement Clone
for Flex
manually; it's a logical mistake that Flex: Clone
would require Marker: Clone
, since that implementation is never used - working around it here isn't very principled.
That the same issue arises with Debug
is unfortunate, and was a hazard I didn't see before. Overall, I'm happy to add the Debug
derive here, but I'm going to push back on the fixes to make Clone
work like that here.
xilem_core/src/sequence.rs
Outdated
/// Insert a new element at the current index in the resulting collection. | ||
fn push(&mut self, element: Element); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this, when there's with_scratch
(which I'm not super happy with the naming, maybe it should be called append
or this should be the new push
instead)?
If we do keep this, it should either push at the end (this was the behavior before IIRC) or be called insert
as the doc comment already implies IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's needed to avoid additional e.g. allocations when they're not needed.
It's also a bit simpler to use. Although really, manual implementation of ViewSequence
seem so unlikely that it wouldn't be unreasonable to remove this
// The `View` trait could have been made generic over the message type, | ||
// primarily to enable flexibility around Send/Sync and avoid the need | ||
// for allocation. | ||
pub trait Message: 'static + Send { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm currently running into the issue while rewriting xilem_web event listeners, that this requires Send
. web_sys::Event
doesn't implement Send
.
So the Send
bound should be optional (another reason to make it generic?).
Hmmm, I just discovered one major issue because of the orphan rule (in hindsight it's obvious): impl<State, Action> View<State, Action, ViewCtx> for &'static str {..} |
I mean I'm honest, I really don't like it, as it convolutes everything with wrapper types such as I'm asking myself, if there's a general way to provide such an implementation in core and have different contexts define which implementation/element is used or something like that... But I'm not sure if that's possible... That makes this even more relevant |
Those general implementations can be added, as shown in DJMcNab#4 They do lead to some jankiness however. In particular, you can't have:
because of the new required marker |
* xilem: Port rest of examples and add `Memoize` to `xilem_core` * cargo fmt * Address review comments * Add License header
…iewElement::Mut` It's not possible to modify the element otherwise, after descendent `View`s have used the element in `View::rebuild`
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
xilem_core: return element in `View::rebuild`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, certainly a big improvement in code quality.
Nice job also with all those exhaustive tests (seems like they surfaced some issues as well?)
@@ -17,6 +17,8 @@ pub enum MessageResult<Action> { | |||
/// This allows for sub-sections of your app to use an elm-like architecture | |||
Action(Action), | |||
// TODO: What does this mean? | |||
/// This message's handler needs a rebuild to happen. | |||
/// The exact semantics of this method haven't been determined. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it has been, but that was when we had async in xilem, it was used when a future has completed, sent a message and in View::message
a rebuild was requested (e.g. when a list view entry in the async list has finished loading)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But my question is how is that different from the implied result of an action handler at the root?
The semantics here are clearly not very principled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean via MessageResult:Action
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, exactly.
This:
xilem_core
toxilem_web_core
and moves it to thexilem_web/xilem_web_core
folderxilem_core
, which does not use (non-tuple) macros and instead contains aView
trait which is generic over theContext
typexilem
to thisxilem_core
, but with some functionality missing (namely a few of the extra views; I expect these to straightforward to port)mason
andmason_android
examples to this newxilem
, with less functionality.This continues ideas first explored in #235
The advantages of this new View trait are:
This will be very useful for layout algorithms, and will also enable native good multi-window (and potentially menus?)
Possible disadvantages:
Self::Element::Mut
is in therebuild
function, because of how the resolution worksView
, you need to specify the context (i.e.impl<State, Action> View<State, Action, [new] ViewCtx> for Button<State, Action>
.