Make window adapter creation lazy in the interpreter

The main change is that the window adapter is not copied into
sub-components anymore - instead it is accessed through the root.  This
is especially important for globals.

In order to add the root, a little extra dance had to be performance on
instantiation: The self_rc/self_weak is created first and the
ComponentExtraData's fields are populated via once_cells.
This commit is contained in:
Simon Hausmann 2023-06-07 14:56:08 +02:00 committed by Simon Hausmann
parent fd2bb7fe73
commit d98c6773e1
6 changed files with 197 additions and 109 deletions

View file

@ -18,7 +18,7 @@ pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel};
pub use i_slint_core::api::*;
use crate::dynamic_component::ErasedComponentBox;
use crate::dynamic_component::{ErasedComponentBox, WindowOptions};
/// This enum represents the different public variants of the [`Value`] enum, without
/// the contained values.
@ -592,14 +592,9 @@ impl ComponentDefinition {
/// Creates a new instance of the component and returns a shared handle to it.
pub fn create(&self) -> Result<ComponentInstance, PlatformError> {
generativity::make_guard!(guard);
self.inner
.unerase(guard)
.clone()
.create(
#[cfg(target_arch = "wasm32")]
"canvas".into(),
)
.map(|inner| ComponentInstance { inner })
Ok(ComponentInstance {
inner: self.inner.unerase(guard).clone().create(Default::default()),
})
}
/// Instantiate the component for wasm using the given canvas id
@ -609,11 +604,13 @@ impl ComponentDefinition {
canvas_id: &str,
) -> Result<ComponentInstance, PlatformError> {
generativity::make_guard!(guard);
self.inner
.unerase(guard)
.clone()
.create(canvas_id.into())
.map(|inner| ComponentInstance { inner })
Ok(ComponentInstance {
inner: self
.inner
.unerase(guard)
.clone()
.create(WindowOptions::CreateWithCanvasId(canvas_id.into())),
})
}
/// Instantiate the component using an existing window.
@ -621,11 +618,9 @@ impl ComponentDefinition {
pub fn create_with_existing_window(&self, window: &Window) -> ComponentInstance {
generativity::make_guard!(guard);
ComponentInstance {
inner: self
.inner
.unerase(guard)
.clone()
.create_with_existing_window(WindowInner::from_pub(window).window_adapter()),
inner: self.inner.unerase(guard).clone().create(WindowOptions::UseExistingWindow(
WindowInner::from_pub(window).window_adapter(),
)),
}
}
@ -1066,15 +1061,11 @@ impl ComponentHandle for ComponentInstance {
}
fn show(&self) -> Result<(), PlatformError> {
generativity::make_guard!(guard);
let comp = self.inner.unerase(guard);
comp.borrow_instance().window_adapter().window().show()
self.inner.window_adapter()?.window().show()
}
fn hide(&self) -> Result<(), PlatformError> {
generativity::make_guard!(guard);
let comp = self.inner.unerase(guard);
comp.borrow_instance().window_adapter().window().hide()
self.inner.window_adapter()?.window().hide()
}
fn run(&self) -> Result<(), PlatformError> {
@ -1084,7 +1075,7 @@ impl ComponentHandle for ComponentInstance {
}
fn window(&self) -> &Window {
self.inner.window_adapter().window()
self.inner.window_adapter().unwrap().window()
}
fn global<'a, T: Global<'a, Self>>(&'a self) -> T

View file

@ -32,6 +32,7 @@ use i_slint_core::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo};
use i_slint_core::slice::Slice;
use i_slint_core::window::{WindowAdapter, WindowInner};
use i_slint_core::{Brush, Color, Property, SharedString, SharedVector};
use once_cell::unsync::OnceCell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::{pin::Pin, rc::Rc};
@ -56,13 +57,11 @@ impl<'id> ComponentBox<'id> {
InstanceRef { instance: self.instance.as_pin_ref(), component_type: &self.component_type }
}
pub fn window_adapter(&self) -> &Rc<dyn WindowAdapter> {
self.component_type
.window_adapter_offset
.apply(self.instance.as_pin_ref().get_ref())
.as_ref()
.as_ref()
.unwrap()
pub fn window_adapter(&self) -> Result<&Rc<dyn WindowAdapter>, PlatformError> {
InstanceRef::get_or_init_window_adapter_ref(
&self.component_type,
&self.instance.as_pin_ref().get_ref(),
)
}
}
@ -235,11 +234,12 @@ pub type DynamicComponentVRc = vtable::VRc<ComponentVTable, ErasedComponentBox>;
#[derive(Default)]
pub(crate) struct ComponentExtraData {
pub(crate) globals: crate::global_component::GlobalStorage,
pub(crate) self_weak:
once_cell::unsync::OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>>,
pub(crate) globals: OnceCell<crate::global_component::GlobalStorage>,
pub(crate) self_weak: OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>>,
// resource id -> file path
pub(crate) embedded_file_resources: HashMap<usize, String>,
pub(crate) embedded_file_resources: OnceCell<HashMap<usize, String>>,
#[cfg(target_arch = "wasm32")]
pub(crate) canvas_id: OnceCell<String>,
}
struct ErasedRepeaterWithinComponent<'id>(RepeaterWithinComponent<'id, 'static>);
@ -330,9 +330,11 @@ pub struct ComponentDescription<'id> {
pub repeater_names: HashMap<String, usize>,
/// Offset to a Option<ComponentPinRef>
pub(crate) parent_component_offset:
Option<FieldOffset<Instance<'id>, Option<ComponentRefPin<'id>>>>,
Option<FieldOffset<Instance<'id>, OnceCell<ComponentRefPin<'id>>>>,
pub(crate) root_offset:
FieldOffset<Instance<'id>, OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>>>,
/// Offset to the window reference
pub(crate) window_adapter_offset: FieldOffset<Instance<'id>, Option<Rc<dyn WindowAdapter>>>,
pub(crate) window_adapter_offset: FieldOffset<Instance<'id>, OnceCell<Rc<dyn WindowAdapter>>>,
/// Offset of a ComponentExtraData
pub(crate) extra_data_offset: FieldOffset<Instance<'id>, ComponentExtraData>,
/// Keep the Rc alive
@ -366,6 +368,15 @@ fn internal_properties_to_public<'a>(
})
}
#[derive(Default)]
pub enum WindowOptions {
#[default]
CreateNewWindow,
UseExistingWindow(Rc<dyn WindowAdapter>),
#[cfg(target_arch = "wasm32")]
CreateWithCanvasId(String),
}
impl<'id> ComponentDescription<'id> {
/// The name of this Component as written in the .slint file
pub fn id(&self) -> &str {
@ -400,28 +411,12 @@ impl<'id> ComponentDescription<'id> {
}
/// Instantiate a runtime component from this ComponentDescription
pub fn create(
self: Rc<Self>,
#[cfg(target_arch = "wasm32")] canvas_id: &str,
) -> Result<DynamicComponentVRc, PlatformError> {
let window_adapter = i_slint_backend_selector::with_platform(|_b| {
#[cfg(not(target_arch = "wasm32"))]
return _b.create_window_adapter();
#[cfg(target_arch = "wasm32")]
i_slint_backend_winit::create_gl_window_with_canvas_id(canvas_id)
})?;
Ok(self.create_with_existing_window(window_adapter))
}
#[doc(hidden)]
pub fn create_with_existing_window(
self: Rc<Self>,
window_adapter: Rc<dyn WindowAdapter>,
) -> DynamicComponentVRc {
let component_ref = instantiate(self, None, window_adapter, Default::default());
WindowInner::from_pub(component_ref.as_pin_ref().window_adapter().window())
.set_component(&vtable::VRc::into_dyn(component_ref.clone()));
pub fn create(self: Rc<Self>, options: WindowOptions) -> DynamicComponentVRc {
let component_ref = instantiate(self, None, None, Some(&options), Default::default());
if let WindowOptions::UseExistingWindow(existing_adapter) = options {
WindowInner::from_pub(existing_adapter.window())
.set_component(&vtable::VRc::into_dyn(component_ref.clone()));
}
component_ref.run_setup_code();
component_ref
}
@ -614,7 +609,7 @@ impl<'id> ComponentDescription<'id> {
// Safety: we just verified that the component has the right vtable
let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
let extra_data = c.component_type.extra_data_offset.apply(c.instance.get_ref());
extra_data.globals.get(global_name).cloned().ok_or(())
extra_data.globals.get().unwrap().get(global_name).cloned().ok_or(())
}
}
@ -652,11 +647,11 @@ fn ensure_repeater_updated<'id>(
) {
let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance);
let init = || {
let window_adapter = instance_ref.window_adapter();
let instance = instantiate(
rep_in_comp.component_to_repeat.clone(),
Some(instance_ref.borrow()),
window_adapter,
None,
None,
Default::default(),
);
instance
@ -1036,13 +1031,17 @@ pub(crate) fn generate_component<'id>(
}
let parent_component_offset = if component.parent_element.upgrade().is_some() {
Some(builder.type_builder.add_field_type::<Option<ComponentRefPin>>())
Some(builder.type_builder.add_field_type::<OnceCell<ComponentRefPin>>())
} else {
None
};
let root_offset = builder
.type_builder
.add_field_type::<OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>>>();
let window_adapter_offset =
builder.type_builder.add_field_type::<Option<Rc<dyn WindowAdapter>>>();
builder.type_builder.add_field_type::<OnceCell<Rc<dyn WindowAdapter>>>();
let extra_data_offset = builder.type_builder.add_field_type::<ComponentExtraData>();
@ -1104,6 +1103,7 @@ pub(crate) fn generate_component<'id>(
repeater: builder.repeater,
repeater_names: builder.repeater_names,
parent_component_offset,
root_offset,
window_adapter_offset,
extra_data_offset,
public_properties,
@ -1206,39 +1206,32 @@ fn make_binding_eval_closure(
pub fn instantiate(
component_type: Rc<ComponentDescription>,
parent_ctx: Option<ComponentRefPin>,
window_adapter: Rc<dyn WindowAdapter>,
root: Option<vtable::VWeak<ComponentVTable, ErasedComponentBox>>,
window_options: Option<&WindowOptions>,
mut globals: crate::global_component::GlobalStorage,
) -> DynamicComponentVRc {
let mut instance = component_type.dynamic_type.clone().create_instance();
if let Some(parent) = parent_ctx {
*component_type.parent_component_offset.unwrap().apply_mut(instance.as_mut()) =
Some(parent);
} else {
for g in &component_type.compiled_globals {
crate::global_component::instantiate(g, &mut globals, window_adapter.clone());
}
let extra_data = component_type.extra_data_offset.apply_mut(instance.as_mut());
extra_data.globals = globals;
extra_data.embedded_file_resources = component_type
.original
.embedded_file_resources
.borrow()
.iter()
.map(|(path, er)| (er.id, path.clone()))
.collect();
}
*component_type.window_adapter_offset.apply_mut(instance.as_mut()) = Some(window_adapter);
let instance = component_type.dynamic_type.clone().create_instance();
let component_box = ComponentBox { instance, component_type: component_type.clone() };
let instance_ref = component_box.borrow_instance();
if !component_type.original.is_global() {
let maybe_window_adapter =
if let Some(WindowOptions::UseExistingWindow(adapter)) = window_options.as_ref() {
Some(adapter.clone())
} else {
root.as_ref().and_then(|root| root.upgrade()).and_then(|root| {
generativity::make_guard!(guard);
let comp = root.unerase(guard);
let instance = comp.borrow_instance();
instance.maybe_window_adapter()
})
};
i_slint_core::component::register_component(
instance_ref.instance,
instance_ref.component_type.item_array.as_slice(),
instance_ref.maybe_window_adapter(),
maybe_window_adapter,
);
}
@ -1251,6 +1244,55 @@ pub fn instantiate(
instance_ref.self_weak().set(self_weak.clone()).ok();
let component_type = comp.description();
if let Some(parent) = parent_ctx {
component_type
.parent_component_offset
.unwrap()
.apply(instance_ref.as_ref())
.set(parent)
.ok()
.unwrap();
} else {
for g in &component_type.compiled_globals {
crate::global_component::instantiate(g, &mut globals, self_weak.clone());
}
let extra_data = component_type.extra_data_offset.apply(instance_ref.as_ref());
extra_data.globals.set(globals).ok().unwrap();
extra_data
.embedded_file_resources
.set(
component_type
.original
.embedded_file_resources
.borrow()
.iter()
.map(|(path, er)| (er.id, path.clone()))
.collect(),
)
.ok()
.unwrap();
#[cfg(target_arch = "wasm32")]
if let Some(WindowOptions::CreateWithCanvasId(canvas_id)) = window_options {
extra_data.canvas_id.set(canvas_id.clone()).unwrap();
}
}
let root = root
.or_else(|| instance_ref.parent_instance().map(|parent| parent.root_weak().clone()))
.unwrap_or_else(|| self_weak.clone());
component_type.root_offset.apply(instance_ref.as_ref()).set(root).ok().unwrap();
if let Some(WindowOptions::UseExistingWindow(window_adapter)) = window_options {
component_type
.window_adapter_offset
.apply(instance_ref.as_ref())
.set(window_adapter.clone())
.ok()
.unwrap();
}
// Some properties are generated as Value, but for which the default constructed Value must be initialized
for (prop_name, decl) in &component_type.original.root_element.borrow().property_declarations {
if !matches!(decl.property_type, Type::Struct { .. } | Type::Array(_))
@ -1456,10 +1498,14 @@ impl ErasedComponentBox {
self.0.borrow()
}
pub fn window_adapter(&self) -> &Rc<dyn WindowAdapter> {
pub fn window_adapter(&self) -> Result<&Rc<dyn WindowAdapter>, PlatformError> {
self.0.window_adapter()
}
pub fn maybe_window_adapter(&self) -> Option<Rc<dyn WindowAdapter>> {
self.0.borrow_instance().maybe_window_adapter()
}
pub fn run_setup_code(&self) {
generativity::make_guard!(guard);
let compo_box = self.unerase(guard);
@ -1595,7 +1641,7 @@ unsafe extern "C" fn parent_node(component: ComponentRefPin, result: &mut ItemWe
if let (Some(parent_offset), Some(parent_index)) =
(instance_ref.component_type.parent_component_offset, parent_item_index)
{
if let Some(parent) = parent_offset.apply(instance_ref.as_ref()) {
if let Some(parent) = parent_offset.apply(instance_ref.as_ref()).get() {
generativity::make_guard!(new_guard);
let parent_instance = InstanceRef::from_pin_ref(*parent, new_guard);
let parent_rc =
@ -1696,19 +1742,55 @@ impl<'a, 'id> InstanceRef<'a, 'id> {
}
}
pub fn self_weak(
&self,
) -> &once_cell::unsync::OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>> {
pub fn self_weak(&self) -> &OnceCell<vtable::VWeak<ComponentVTable, ErasedComponentBox>> {
let extra_data = self.component_type.extra_data_offset.apply(self.as_ref());
&extra_data.self_weak
}
pub fn root_weak(&self) -> &vtable::VWeak<ComponentVTable, ErasedComponentBox> {
self.component_type.root_offset.apply(self.as_ref()).get().unwrap()
}
pub fn window_adapter(&self) -> Rc<dyn WindowAdapter> {
self.component_type.window_adapter_offset.apply(self.as_ref()).as_ref().unwrap().clone()
let root = self.root_weak().upgrade().unwrap();
generativity::make_guard!(guard);
let comp = root.unerase(guard);
Self::get_or_init_window_adapter_ref(
&comp.component_type,
&comp.instance.as_pin_ref().get_ref(),
)
.unwrap()
.clone()
}
// Call this only on root components!
pub fn get_or_init_window_adapter_ref<'b, 'id2>(
component_type: &'b ComponentDescription<'id2>,
instance: &'b Instance<'id2>,
) -> Result<&'b Rc<dyn WindowAdapter>, PlatformError> {
component_type.window_adapter_offset.apply(instance).get_or_try_init(|| {
let extra_data = component_type.extra_data_offset.apply(instance);
let window_adapter = i_slint_backend_selector::with_platform(|_b| {
#[cfg(not(target_arch = "wasm32"))]
return _b.create_window_adapter();
#[cfg(target_arch = "wasm32")]
i_slint_backend_winit::create_gl_window_with_canvas_id(
extra_data.canvas_id.get().map_or("canvas", |s| s.as_str()),
)
})?;
let comp_rc = extra_data.self_weak.get().unwrap().upgrade().unwrap();
WindowInner::from_pub(window_adapter.window())
.set_component(&vtable::VRc::into_dyn(comp_rc));
Ok(window_adapter)
})
}
pub fn maybe_window_adapter(&self) -> Option<Rc<dyn WindowAdapter>> {
self.component_type.window_adapter_offset.apply(self.as_ref()).clone()
let root = self.root_weak().upgrade()?;
generativity::make_guard!(guard);
let comp = root.unerase(guard);
let instance = comp.borrow_instance();
instance.component_type.window_adapter_offset.apply(instance.as_ref()).get().cloned()
}
pub fn access_window<R>(
@ -1720,7 +1802,7 @@ impl<'a, 'id> InstanceRef<'a, 'id> {
pub fn parent_instance(&self) -> Option<InstanceRef<'a, 'id>> {
if let Some(parent_offset) = self.component_type.parent_component_offset {
if let Some(parent) = parent_offset.apply(self.as_ref()) {
if let Some(parent) = parent_offset.apply(self.as_ref()).get() {
let parent_instance = unsafe {
Self {
instance: Pin::new_unchecked(
@ -1758,8 +1840,13 @@ pub fn show_popup(
generativity::make_guard!(guard);
// FIXME: we should compile once and keep the cached compiled component
let compiled = generate_component(&popup.component, guard);
let inst =
instantiate(compiled, Some(parent_comp), parent_window_adapter.clone(), Default::default());
let inst = instantiate(
compiled,
Some(parent_comp),
None,
Some(&WindowOptions::UseExistingWindow(parent_window_adapter.clone())),
Default::default(),
);
inst.run_setup_code();
WindowInner::from_pub(parent_window_adapter.window()).show_popup(
&vtable::VRc::into_dyn(inst),

View file

@ -284,7 +284,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
ComponentInstance::GlobalComponent(_) => unimplemented!(),
};
let extra_data = toplevel_instance.component_type.extra_data_offset.apply(toplevel_instance.as_ref());
let path = extra_data.embedded_file_resources.get(resource_id).expect("internal error: invalid resource id");
let path = extra_data.embedded_file_resources.get().unwrap().get(resource_id).expect("internal error: invalid resource id");
let virtual_file = i_slint_compiler::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists
@ -1278,7 +1278,7 @@ fn root_component_instance<'a, 'old_id, 'new_id>(
) -> InstanceRef<'a, 'new_id> {
if let Some(parent_offset) = component.component_type.parent_component_offset {
let parent_component =
if let Some(parent) = parent_offset.apply(&*component.instance.get_ref()) {
if let Some(parent) = parent_offset.apply(&*component.instance.get_ref()).get() {
*parent
} else {
panic!("invalid parent ptr");
@ -1318,9 +1318,10 @@ pub fn enclosing_component_for_element<'a, 'old_id, 'new_id>(
.parent_component_offset
.unwrap()
.apply(component.as_ref())
.get()
.unwrap();
generativity::make_guard!(new_guard);
let parent_instance = unsafe { InstanceRef::from_pin_ref(parent_component, new_guard) };
let parent_instance = unsafe { InstanceRef::from_pin_ref(*parent_component, new_guard) };
let parent_instance = unsafe {
core::mem::transmute::<InstanceRef, InstanceRef<'a, 'static>>(parent_instance)
};
@ -1345,8 +1346,13 @@ pub(crate) fn enclosing_component_instance_for_element<'a, 'old_id, 'new_id>(
unsafe { generativity::Guard::new(generativity::Id::<'static>::new()) };
let root = root_component_instance(component, static_guard);
ComponentInstance::GlobalComponent(
&root.component_type.extra_data_offset.apply(&*root.instance.get_ref()).globals
[enclosing.root_element.borrow().id.as_str()],
&root
.component_type
.extra_data_offset
.apply(&*root.instance.get_ref())
.globals
.get()
.unwrap()[enclosing.root_element.borrow().id.as_str()],
)
} else {
ComponentInstance::InstanceRef(enclosing_component_for_element(

View file

@ -202,7 +202,8 @@ fn box_layout_data(
let instance = crate::dynamic_component::instantiate(
rep.1.clone(),
Some(component.borrow()),
window_adapter.clone(),
None,
None,
Default::default(),
);
instance

View file

@ -579,7 +579,10 @@ pub unsafe extern "C" fn slint_interpreter_component_instance_window(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<i_slint_core::window::ffi::WindowAdapterRcOpaque>()
);
core::ptr::write(out as *mut *const Rc<dyn WindowAdapter>, inst.window_adapter() as *const _)
core::ptr::write(
out as *mut *const Rc<dyn WindowAdapter>,
inst.window_adapter().unwrap() as *const _,
)
}
/// Instantiate an instance from a definition.

View file

@ -14,7 +14,6 @@ use i_slint_compiler::object_tree::Component;
use i_slint_compiler::object_tree::PropertyDeclaration;
use i_slint_core::component::ComponentVTable;
use i_slint_core::rtti;
use i_slint_core::window::WindowAdapter;
pub type GlobalStorage = HashMap<String, Pin<Rc<dyn GlobalComponent>>>;
@ -99,7 +98,7 @@ pub trait GlobalComponent {
pub fn instantiate(
description: &CompiledGlobal,
globals: &mut GlobalStorage,
window_adapter: Rc<dyn WindowAdapter>,
root: vtable::VWeak<ComponentVTable, ErasedComponentBox>,
) {
let instance = match description {
CompiledGlobal::Builtin { element, .. } => {
@ -128,7 +127,8 @@ pub fn instantiate(
Rc::pin(GlobalComponentInstance(crate::dynamic_component::instantiate(
description.clone(),
None,
window_adapter,
Some(root),
None,
globals.clone(),
)))
}