Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions notes/architecture/06-RENDERERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Implements WriteMutations for wry-based rendering:
- Delegates to `WryQueue` managing mutation batch
- WebSocket server on random port for mutation transmission
- Binary protocol via Sledgehammer interpreter
- Each served page is numbered. It reports `initialize` with its number and opens the socket when the host answers, and the host ignores a page a newer one is replacing
- A later `initialize` from the same webview is a reloaded page, such as the one Android loads into a recreated activity. The queue moves to a fresh socket id and the window is redrawn with `VirtualDom::remount_render_target`, keeping component state

### IPC (Interprocess Communication)
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{{#if uses_cleartext_traffic}}android:usesCleartextTraffic="{{ uses_cleartext_traffic }}"{{/if}}
{{#if large_heap}}android:largeHeap="{{ large_heap }}"{{/if}}
android:networkSecurityConfig="@xml/network_security_config">
<activity android:configChanges="orientation|screenLayout|screenSize|keyboardHidden" android:exported="true"
<activity android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|navigation|touchscreen|density" android:exported="true"
android:label="@string/app_name" android:launchMode="singleTask" android:name="dev.dioxus.main.MainActivity">
<meta-data android:name="android.app.lib_name" android:value="{{ lib_name }}" />
<meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod nodes;
mod portal;
mod properties;
mod reactive_context;
mod remount;
mod render_driver;
mod render_error;
mod root_wrapper;
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ pub fn Portal(_: PortalProps) -> Element {
/// target instead of mounting at the scope's slot.
struct PortalDriver;

/// Whether `driver` renders a [`Portal`] scope.
pub(crate) fn is_portal_driver(driver: &dyn RenderDriver) -> bool {
driver.as_any().is::<PortalDriver>()
}

fn portal_props(dom: &VirtualDom, scope_id: ScopeId) -> (RenderTargetId, LastRenderedNode) {
let props = dom.scopes[scope_id.index()]
.props
Expand Down
133 changes: 133 additions & 0 deletions packages/core/src/remount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use crate::{
RenderTargetId, ScopeId, VirtualDom,
arena::{ElementId, RenderTargetState},
innerlude::{MountId, MultiWriter},
mount::MountedParent,
mutations::TargetRouter,
portal::is_portal_driver,
runtime::RuntimeGuard,
scopes::MountedOutput,
};

/// A scope whose rendered output sits at the root element of a render target.
struct TargetRoot {
scope: ScopeId,
logical_parent: Option<MountId>,
render_parent: Option<MountedParent>,
}

/// Every writer of the host except the one for `skipped`.
struct WithoutTarget<'a, M> {
to: &'a mut M,
skipped: RenderTargetId,
}

impl<M: MultiWriter> MultiWriter for WithoutTarget<'_, M> {
type Writer = M::Writer;

fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut M::Writer> {
if id == self.skipped {
None
} else {
self.to.writer_for(id)
}
}
}

impl VirtualDom {
/// Write the whole mounted tree of `target` into `to`, between render passes, for a renderer
/// that lost the target's nodes, such as a webview that reloaded its page.
///
/// Components keep their state and do not run. The [`ElementId`]s of `target` are reassigned,
/// and portals inside it rewrite their own targets through `to`.
pub fn remount_render_target(&mut self, target: RenderTargetId, to: &mut impl MultiWriter) {
let _runtime = RuntimeGuard::new(self.runtime.clone());
let roots = self.target_roots(target);
if roots.is_empty() {
return;
}

{
// The renderer of `target` has no nodes to remove, the other renderers do.
let mut others = WithoutTarget {
to: &mut *to,
skipped: target,
};
let mut router = TargetRouter::new(&mut others, self.runtime.clone());
for root in &roots {
self.remove_component_node(Some(&mut router), false, root.scope);
}
}
self.reset_render_target(target);

let mut router = TargetRouter::new(to, self.runtime.clone());
for root in roots {
if root.scope == ScopeId::ROOT {
self.rebuild_with_writer(&mut router);
continue;
}
let driver = self.runtime.get_state(root.scope).render_driver();
self.runtime.clone().while_rendering(|| {
driver.create(self, root.scope, root.logical_parent, Some(&mut router))
});
if let Some(render_parent) = root.render_parent {
let root_mount = self.scopes[root.scope.index()]
.last_rendered_node
.as_ref()
.map(MountedOutput::root_mount)
.expect("a remounted scope has rendered output");
self.set_mounted_render_parent(root_mount, render_parent);
}
}
}

/// The outermost rendered scopes that place their output at the root element of `target`, in
/// scope id order. Their subtrees hold every node of `target`.
fn target_roots(&self, target: RenderTargetId) -> Vec<TargetRoot> {
let places_at_target_root = |scope: ScopeId| {
self.runtime.try_get_state(scope).is_some_and(|state| {
state.target_id() == target
&& (scope == ScopeId::ROOT || is_portal_driver(&*state.render_driver()))
})
};
let parent = |scope: &ScopeId| self.runtime.get_state(*scope).parent_id();

self.scopes
.iter()
.map(|(index, _)| ScopeId::new(index))
.filter(|&scope| places_at_target_root(scope))
.filter(|scope| {
std::iter::successors(parent(scope), parent).all(|id| !places_at_target_root(id))
})
.filter(|&scope| self.runtime.scope_should_render(scope))
.filter_map(|scope| {
let root_mount = self.scopes[scope.index()]
.last_rendered_node
.as_ref()?
.root_mount();
Some(TargetRoot {
scope,
logical_parent: self.mounted_logical_parent(root_mount),
render_parent: self.mounted_render_parent(root_mount),
})
})
.collect()
}

/// Forget the renderer-local state of `target` once no mount holds one of its elements.
fn reset_render_target(&mut self, target: RenderTargetId) {
let mut targets = self.runtime.render_targets.borrow_mut();
let state = targets
.get_mut(target.index())
.expect("a remounted target is registered");
debug_assert!(
state
.elements
.iter()
.all(|(index, _)| index == ElementId::ROOT.index()
|| state.template_roots.values().any(|id| id.index() == index)),
"every mounted element of the target is reclaimed before it is reset"
);
*state = RenderTargetState::new();
}
}
2 changes: 1 addition & 1 deletion packages/core/src/virtual_dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ impl VirtualDom {
}
}

fn rebuild_with_writer(&mut self, to: &mut dyn WriteMutations) {
pub(crate) fn rebuild_with_writer(&mut self, to: &mut dyn WriteMutations) {
let driver = self.runtime.get_state(ScopeId::ROOT).render_driver();
append_children_to(to, ElementId::ROOT, self.runtime.clone(), |to| {
self.runtime.clone().while_rendering(|| {
Expand Down
Loading