mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 14:51:15 +00:00
Be smarter at detecting the constant property and at not generating bindings
this implies that we need to make sure the property are initialized in order so that constant properties that depends on other constant properties are correctly computed
This commit is contained in:
parent
a92d1af03c
commit
a79a4351b5
5 changed files with 189 additions and 29 deletions
|
@ -121,6 +121,34 @@ impl BuiltinFunction {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// It is pure if the return value only depends on its argument and has no side effect
|
||||
fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
BuiltinFunction::GetWindowScaleFactor => false,
|
||||
// Even if it is not pure, we optimize it away anyway
|
||||
BuiltinFunction::Debug => true,
|
||||
BuiltinFunction::Mod
|
||||
| BuiltinFunction::Round
|
||||
| BuiltinFunction::Ceil
|
||||
| BuiltinFunction::Floor
|
||||
| BuiltinFunction::Sqrt
|
||||
| BuiltinFunction::Cos
|
||||
| BuiltinFunction::Sin
|
||||
| BuiltinFunction::Tan
|
||||
| BuiltinFunction::ACos
|
||||
| BuiltinFunction::ASin
|
||||
| BuiltinFunction::ATan => true,
|
||||
BuiltinFunction::SetFocusItem => false,
|
||||
BuiltinFunction::ShowPopupWindow => false,
|
||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||
BuiltinFunction::ColorBrighter | BuiltinFunction::ColorDarker => true,
|
||||
BuiltinFunction::Rgb => true,
|
||||
BuiltinFunction::ImplicitLayoutInfo => false,
|
||||
BuiltinFunction::RegisterCustomFontByPath
|
||||
| BuiltinFunction::RegisterCustomFontByMemory => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
@ -721,26 +749,33 @@ impl Expression {
|
|||
match self {
|
||||
Expression::Invalid => true,
|
||||
Expression::Uncompiled(_) => false,
|
||||
Expression::TwoWayBinding(..) => false,
|
||||
Expression::TwoWayBinding(nr, expr) => {
|
||||
nr.is_constant() && expr.as_ref().map_or(true, |e| e.is_constant())
|
||||
}
|
||||
Expression::StringLiteral(_) => true,
|
||||
Expression::NumberLiteral(_, _) => true,
|
||||
Expression::BoolLiteral(_) => true,
|
||||
Expression::CallbackReference { .. } => false,
|
||||
Expression::PropertyReference { .. } => false,
|
||||
Expression::BuiltinFunctionReference { .. } => false,
|
||||
Expression::PropertyReference(nr) => nr.is_constant(),
|
||||
Expression::BuiltinFunctionReference(func) => func.is_pure(),
|
||||
Expression::MemberFunction { .. } => false,
|
||||
Expression::ElementReference(_) => false,
|
||||
Expression::RepeaterIndexReference { .. } => false,
|
||||
Expression::RepeaterModelReference { .. } => false,
|
||||
Expression::FunctionParameterReference { .. } => false,
|
||||
Expression::BuiltinMacroReference { .. } => false,
|
||||
Expression::BuiltinMacroReference { .. } => true,
|
||||
Expression::StructFieldAccess { base, .. } => base.is_constant(),
|
||||
Expression::Cast { from, .. } => from.is_constant(),
|
||||
Expression::CodeBlock(sub) => sub.len() == 1 && sub.first().unwrap().is_constant(),
|
||||
Expression::FunctionCall { .. } => false,
|
||||
Expression::FunctionCall { function, arguments, .. } => {
|
||||
// Assume that constant function are, in fact, pure
|
||||
function.is_constant() && arguments.iter().all(|a| a.is_constant())
|
||||
}
|
||||
Expression::SelfAssignment { .. } => false,
|
||||
Expression::ImageReference { .. } => true,
|
||||
Expression::Condition { .. } => false,
|
||||
Expression::Condition { condition, false_expr, true_expr } => {
|
||||
condition.is_constant() && false_expr.is_constant() && true_expr.is_constant()
|
||||
}
|
||||
Expression::BinaryExpression { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(),
|
||||
Expression::UnaryOp { sub, .. } => sub.is_constant(),
|
||||
Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
|
||||
|
@ -755,6 +790,7 @@ impl Expression {
|
|||
}
|
||||
}
|
||||
Expression::StoreLocalVariable { .. } => false,
|
||||
// we should somehow find out if this is constant or not
|
||||
Expression::ReadLocalVariable { .. } => false,
|
||||
Expression::EasingCurve(_) => true,
|
||||
Expression::LinearGradient { angle, stops } => {
|
||||
|
@ -764,7 +800,7 @@ impl Expression {
|
|||
Expression::ReturnStatement(expr) => {
|
||||
expr.as_ref().map_or(true, |expr| expr.is_constant())
|
||||
}
|
||||
|
||||
// TODO: detect constant property within layouts
|
||||
Expression::LayoutCacheAccess { .. } => false,
|
||||
Expression::ComputeLayoutInfo(_) => false,
|
||||
Expression::SolveLayout(_) => false,
|
||||
|
|
|
@ -13,7 +13,12 @@ The module responsible for the code generation.
|
|||
There is one sub module for every language
|
||||
*/
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use crate::diagnostics::BuildDiagnostics;
|
||||
use crate::expression_tree::{BindingExpression, Expression};
|
||||
use crate::namedreference::NamedReference;
|
||||
use crate::object_tree::{Component, Document, ElementRc};
|
||||
|
||||
#[cfg(feature = "cpp")]
|
||||
|
@ -128,3 +133,62 @@ pub fn build_array_helper(component: &Component, mut visit_item: impl FnMut(&Ele
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Will wall the `handle_property` callback for every property that needs to be initialized.
|
||||
/// This function makes sure to call them in order so that if constant binding need to access
|
||||
/// constant properties, these are already initialized
|
||||
pub fn handle_property_bindings_init(
|
||||
component: &Rc<Component>,
|
||||
mut handle_property: impl FnMut(&ElementRc, &str, &BindingExpression),
|
||||
) {
|
||||
fn handle_property_inner(
|
||||
component: &Weak<Component>,
|
||||
elem: &ElementRc,
|
||||
prop_name: &str,
|
||||
binding_expression: &BindingExpression,
|
||||
handle_property: &mut impl FnMut(&ElementRc, &str, &BindingExpression),
|
||||
processed: &mut HashSet<NamedReference>,
|
||||
) {
|
||||
let nr = NamedReference::new(elem, prop_name);
|
||||
if processed.contains(&nr) {
|
||||
return;
|
||||
}
|
||||
processed.insert(nr);
|
||||
if binding_expression.analysis.borrow().as_ref().map_or(false, |a| a.is_const) {
|
||||
// We must first handle all dependent properties in case it is a constant property
|
||||
binding_expression.expression.visit_recursive(&mut |e| match e {
|
||||
Expression::PropertyReference(nr) => {
|
||||
let elem = nr.element();
|
||||
if Weak::ptr_eq(&elem.borrow().enclosing_component, component) {
|
||||
if let Some(be) = elem.borrow().bindings.get(nr.name()) {
|
||||
handle_property_inner(
|
||||
component,
|
||||
&elem,
|
||||
nr.name(),
|
||||
be,
|
||||
handle_property,
|
||||
processed,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
handle_property(elem, prop_name, binding_expression);
|
||||
}
|
||||
|
||||
let mut processed = HashSet::new();
|
||||
crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
|
||||
for (prop_name, binding_expression) in &elem.borrow().bindings {
|
||||
handle_property_inner(
|
||||
&Rc::downgrade(component),
|
||||
elem,
|
||||
prop_name,
|
||||
binding_expression,
|
||||
&mut handle_property,
|
||||
&mut processed,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -332,6 +332,7 @@ fn handle_property_binding(
|
|||
elem: &ElementRc,
|
||||
prop_name: &str,
|
||||
binding_expression: &Expression,
|
||||
is_constant: bool,
|
||||
init: &mut Vec<String>,
|
||||
) {
|
||||
let item = elem.borrow();
|
||||
|
@ -371,7 +372,7 @@ fn handle_property_binding(
|
|||
p2 = access_named_reference(nr, &component, "this")
|
||||
));
|
||||
if let Some(next) = next {
|
||||
handle_property_binding(elem, prop_name, next, init)
|
||||
handle_property_binding(elem, prop_name, next, is_constant || next.is_constant(), init)
|
||||
}
|
||||
} else {
|
||||
let component = &item.enclosing_component.upgrade().unwrap();
|
||||
|
@ -379,7 +380,7 @@ fn handle_property_binding(
|
|||
let init_expr = compile_expression_wrap_return(binding_expression, component);
|
||||
let cpp_prop = format!("{}{}", accessor_prefix, prop_name);
|
||||
|
||||
init.push(if binding_expression.is_constant() {
|
||||
init.push(if is_constant {
|
||||
format!("{}.set({});", cpp_prop, init_expr)
|
||||
} else {
|
||||
let binding_code = format!(
|
||||
|
@ -437,7 +438,7 @@ fn handle_property_binding(
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String>) {
|
||||
fn handle_item(elem: &ElementRc, main_struct: &mut Struct) {
|
||||
let item = elem.borrow();
|
||||
main_struct.members.push((
|
||||
Access::Private,
|
||||
|
@ -447,10 +448,6 @@ fn handle_item(elem: &ElementRc, main_struct: &mut Struct, init: &mut Vec<String
|
|||
init: Some("{}".to_owned()),
|
||||
}),
|
||||
));
|
||||
|
||||
for (prop_name, binding_expression) in &item.bindings {
|
||||
handle_property_binding(elem, prop_name, binding_expression, init);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_repeater(
|
||||
|
@ -993,9 +990,6 @@ fn generate_component(
|
|||
let item = item_rc.borrow();
|
||||
if item.base_type == Type::Void {
|
||||
assert!(component.is_global());
|
||||
for (prop_name, binding_expression) in &item.bindings {
|
||||
handle_property_binding(item_rc, prop_name, binding_expression, &mut init);
|
||||
}
|
||||
} else if let Some(repeated) = &item.repeated {
|
||||
tree_array.push(format!(
|
||||
"sixtyfps::private_api::make_dyn_node({}, {})",
|
||||
|
@ -1044,12 +1038,22 @@ fn generate_component(
|
|||
parent_index,
|
||||
));
|
||||
}
|
||||
handle_item(item_rc, &mut component_struct, &mut init);
|
||||
handle_item(item_rc, &mut component_struct);
|
||||
item_names_and_vt_symbols
|
||||
.push((item.id.clone(), item.base_type.as_native().cpp_vtable_getter.clone()));
|
||||
}
|
||||
});
|
||||
|
||||
super::handle_property_bindings_init(component, |elem, prop, binding| {
|
||||
handle_property_binding(
|
||||
elem,
|
||||
prop,
|
||||
binding,
|
||||
binding.analysis.borrow().as_ref().map_or(false, |a| a.is_const),
|
||||
&mut init,
|
||||
)
|
||||
});
|
||||
|
||||
if !component.is_global() {
|
||||
component_struct
|
||||
.friends
|
||||
|
|
|
@ -148,6 +148,7 @@ fn handle_property_binding(
|
|||
item_rc: &ElementRc,
|
||||
prop_name: &str,
|
||||
binding_expression: &Expression,
|
||||
is_constant: bool,
|
||||
init: &mut Vec<TokenStream>,
|
||||
) {
|
||||
let rust_property = access_member(item_rc, prop_name, component, quote!(_self), false);
|
||||
|
@ -188,11 +189,18 @@ fn handle_property_binding(
|
|||
Property::link_two_way(#rust_property, #p2);
|
||||
));
|
||||
if let Some(next) = next {
|
||||
handle_property_binding(component, item_rc, prop_name, next, init)
|
||||
handle_property_binding(
|
||||
component,
|
||||
item_rc,
|
||||
prop_name,
|
||||
next,
|
||||
is_constant || next.is_constant(),
|
||||
init,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let tokens_for_expression = compile_expression(binding_expression, &component);
|
||||
init.push(if binding_expression.is_constant() {
|
||||
init.push(if is_constant {
|
||||
let t = rust_type(&prop_type).unwrap_or(quote!(_));
|
||||
quote! { #rust_property.set((||-> #t { (#tokens_for_expression) as #t })()); }
|
||||
} else {
|
||||
|
@ -385,9 +393,6 @@ fn generate_component(
|
|||
let item = item_rc.borrow();
|
||||
if item.base_type == Type::Void {
|
||||
assert!(component.is_global());
|
||||
for (k, binding_expression) in &item.bindings {
|
||||
handle_property_binding(component, item_rc, k, binding_expression, &mut init);
|
||||
}
|
||||
} else if let Some(repeated) = &item.repeated {
|
||||
let base_component = item.base_type.as_component();
|
||||
let repeater_index = repeated_element_names.len();
|
||||
|
@ -561,9 +566,6 @@ fn generate_component(
|
|||
parent_index: #parent_index
|
||||
}
|
||||
));
|
||||
for (k, binding_expression) in &item.bindings {
|
||||
handle_property_binding(component, item_rc, k, binding_expression, &mut init);
|
||||
}
|
||||
} else {
|
||||
let field_name = format_ident!("{}", item.id);
|
||||
let children_count = item.children.len() as u32;
|
||||
|
@ -576,14 +578,22 @@ fn generate_component(
|
|||
parent_index: #parent_index,
|
||||
}
|
||||
));
|
||||
for (k, binding_expression) in &item.bindings {
|
||||
handle_property_binding(component, item_rc, k, binding_expression, &mut init);
|
||||
}
|
||||
item_names.push(field_name);
|
||||
item_types.push(format_ident!("{}", item.base_type.as_native().class_name));
|
||||
}
|
||||
});
|
||||
|
||||
super::handle_property_bindings_init(component, |elem, prop, binding| {
|
||||
handle_property_binding(
|
||||
component,
|
||||
elem,
|
||||
prop,
|
||||
binding,
|
||||
binding.analysis.borrow().as_ref().map_or(false, |a| a.is_const),
|
||||
&mut init,
|
||||
)
|
||||
});
|
||||
|
||||
let resource_symbols: Vec<proc_macro2::TokenStream> = component
|
||||
.embedded_file_resources
|
||||
.borrow()
|
||||
|
|
|
@ -56,6 +56,52 @@ impl NamedReference {
|
|||
pub fn ty(&self) -> Type {
|
||||
self.element().borrow().lookup_property(self.name()).property_type
|
||||
}
|
||||
|
||||
/// return true if the property has a constant value for the lifetime of the program
|
||||
pub fn is_constant(&self) -> bool {
|
||||
let mut elem = self.element();
|
||||
let e = elem.borrow();
|
||||
if e.property_analysis.borrow().get(self.name()).map_or(false, |a| a.is_set) {
|
||||
// if the property is set somewhere, it is not constant
|
||||
return false;
|
||||
}
|
||||
if let Some(decl) = e.property_declarations.get(self.name()) {
|
||||
if decl.expose_in_public_api {
|
||||
// could be set by the public API
|
||||
return false;
|
||||
}
|
||||
}
|
||||
drop(e);
|
||||
|
||||
let mut check_binding = true;
|
||||
loop {
|
||||
let e = elem.borrow();
|
||||
if check_binding {
|
||||
if let Some(b) = e.bindings.get(self.name()) {
|
||||
if !b.is_constant() {
|
||||
return false;
|
||||
}
|
||||
check_binding = false;
|
||||
}
|
||||
}
|
||||
if e.property_declarations.contains_key(self.name()) {
|
||||
return true;
|
||||
}
|
||||
match &e.base_type {
|
||||
Type::Native(_) => return false, // after resolving we don't know anymore if the property can be changed natively
|
||||
Type::Component(c) => {
|
||||
let next = c.root_element.clone();
|
||||
drop(e);
|
||||
elem = next;
|
||||
continue;
|
||||
}
|
||||
Type::Builtin(b) => {
|
||||
return b.properties.get(self.name()).map_or(true, |pi| !pi.is_native_output)
|
||||
}
|
||||
_ => return true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NamedReference {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue