Skip to content
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

Precise Widget Recreation #125

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
9 changes: 8 additions & 1 deletion crates/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,18 @@ fn load_texture(bytes: &[u8], filter: TextureFilter) -> Texture {
/// Initialize our logging, adjusting the default log levels of some of our
/// noisier dependencies.
fn init_logging() {
let debug = std::env::var_os("YAKUI_LOG_DEBUG").is_some();
let level = if debug {
log::LevelFilter::Trace
} else {
log::LevelFilter::Info
};

env_logger::builder()
.filter_module("wgpu_hal::auxil::dxgi", log::LevelFilter::Off)
.filter_module("wgpu_core", log::LevelFilter::Warn)
.filter_module("wgpu_hal", log::LevelFilter::Warn)
.filter_level(log::LevelFilter::Info)
.filter_level(level)
.init();
}

Expand Down
120 changes: 92 additions & 28 deletions crates/yakui-core/src/dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ impl Dom {

let mut nodes = self.inner.nodes.borrow_mut();
let mut removed_nodes = self.inner.removed_nodes.borrow_mut();
removed_nodes.clear();

let root = self.inner.root;
trim_children(&mut nodes, &mut removed_nodes, root);
}
Expand Down Expand Up @@ -135,6 +137,13 @@ impl Dom {
Ref::map(nodes, |nodes| nodes.get(index).unwrap())
}

/// Tells whether the DOM contains a widget with the given ID.
pub fn contains(&self, id: WidgetId) -> bool {
let nodes = self.inner.nodes.borrow();
let index = id.index();
nodes.contains(index)
}

/// Get the node with the given widget ID.
pub fn get(&self, id: WidgetId) -> Option<Ref<'_, DomNode>> {
let nodes = self.inner.nodes.borrow();
Expand Down Expand Up @@ -187,27 +196,47 @@ impl Dom {
pub fn begin_widget<T: Widget>(&self, props: T::Props<'_>) -> Response<T::Response> {
log::trace!("begin_widget::<{}>({props:#?}", type_name::<T>());

let parent_id = self.current();

let (id, mut widget) = {
let mut nodes = self.inner.nodes.borrow_mut();
let id = next_widget(&mut nodes, self.current());
self.inner.stack.borrow_mut().push(id);

// Component::update needs mutable access to both the widget and the
// DOM, so we need to rip the widget out of the tree so we can
// release our lock.
let node = nodes.get_mut(id.index()).unwrap();
let widget = replace(&mut node.widget, Box::new(DummyWidget));
if let Some(id) = next_existing_widget(&mut nodes, parent_id) {
// There is an existing child in this slot. It may or may not
// match up with the widget we're starting here.

node.next_child = 0;
(id, widget)
};
// Component::update needs mutable access to both the widget and the
// DOM, so we need to rip the widget out of the tree so we can
// release our lock.
let node = nodes.get_mut(id.index()).unwrap();
let widget = replace(&mut node.widget, Box::new(DummyWidget));

// Potentially recreate the widget, then update it.
let response = {
if widget.as_ref().type_id() != TypeId::of::<T>() {
widget = Box::new(T::new());
if widget.as_ref().type_id() == TypeId::of::<T>() {
// happy path! we can update our widget in place.

node.next_child = 0;
(id, widget)
} else {
// sad path! the widget changed type, so we have to burn
// down the world and try again.

let mut removed_nodes = self.inner.removed_nodes.borrow_mut();
remove_recursive(&mut nodes, &mut removed_nodes, id);

new_widget::<T>(&mut nodes, parent_id)
}
} else {
// we're in uncharted territory!

new_widget::<T>(&mut nodes, parent_id)
}
};

// After this point, we've officially entered our widget.
self.inner.stack.borrow_mut().push(id);

// Update the widget now that we've released our locks.
let response = {
let widget = widget.downcast_mut::<T>().unwrap();
widget.update(props)
};
Expand Down Expand Up @@ -262,27 +291,43 @@ impl DomInner {
}
}

fn next_widget(nodes: &mut Arena<DomNode>, parent_id: WidgetId) -> WidgetId {
fn next_existing_widget(nodes: &mut Arena<DomNode>, parent_id: WidgetId) -> Option<WidgetId> {
let parent = nodes.get_mut(parent_id.index()).unwrap();
if parent.next_child < parent.children.len() {
let id = parent.children[parent.next_child];

if let Some(&id) = parent.children.get(parent.next_child) {
parent.next_child += 1;
id
Some(id)
} else {
let index = nodes.insert(DomNode {
widget: Box::new(DummyWidget),
parent: Some(parent_id),
children: Vec::new(),
next_child: 0,
});
None
}
}

let id = WidgetId::new(index);
fn new_widget<T: Widget>(
nodes: &mut Arena<DomNode>,
parent_id: WidgetId,
) -> (WidgetId, Box<dyn ErasedWidget>) {
let index = nodes.insert(DomNode {
widget: Box::new(DummyWidget),
parent: Some(parent_id),
children: Vec::new(),
next_child: 0,
});

let id = WidgetId::new(index);

let parent = nodes.get_mut(parent_id.index()).unwrap();

let parent = nodes.get_mut(parent_id.index()).unwrap();
if parent.next_child < parent.children.len() {
parent.children[parent.next_child] = id;
} else {
parent.children.push(id);
parent.next_child += 1;
id
}

parent.next_child += 1;

let widget = Box::new(T::new());

(id, widget)
}

/// Remove children from the given node that weren't present in the latest
Expand All @@ -305,3 +350,22 @@ fn trim_children(nodes: &mut Arena<DomNode>, removed_nodes: &mut Vec<WidgetId>,
}
}
}

/// Remove a widget and all of its descendants recursively.
fn remove_recursive(nodes: &mut Arena<DomNode>, removed_nodes: &mut Vec<WidgetId>, id: WidgetId) {
let mut queue = VecDeque::new();
queue.push_back(id);

while let Some(id) = queue.pop_front() {
removed_nodes.push(id);

let Some(node) = nodes.get(id.index()) else {
continue;
};

let to_drop = node.children.as_slice();
queue.extend(to_drop);

nodes.remove(id.index());
}
}
10 changes: 10 additions & 0 deletions crates/yakui/examples/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use yakui::{Color, Vec2};
use yakui_widgets::colored_box;

pub fn run() {
colored_box(Color::RED, Vec2::splat(100.0));
}

fn main() {
bootstrap::start(run as fn());
}
Loading