slint/internal/compiler/passes/lower_states.rs
Milian Wolff 69c68b22b2 Also wrap langtype::Type::Struct in an Rc
This makes copying such types much cheaper and will allow us to
intern common struct types in the future too. This further
drops the sample cost for langtype.rs from ~6.6% down to 4.0%.

We are now also able to share/intern common struct types.

Before:
```
  Time (mean ± σ):      1.073 s ±  0.021 s    [User: 0.759 s, System: 0.215 s]
  Range (min … max):    1.034 s …  1.105 s    10 runs

        allocations:            3074261
```

After:
```
  Time (mean ± σ):      1.034 s ±  0.026 s    [User: 0.733 s, System: 0.201 s]
  Range (min … max):    1.000 s …  1.078 s    10 runs

        allocations:            2917476
```
2024-10-28 09:39:54 +01:00

255 lines
9.8 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! Pass that create a state property, and change all the binding to depend on that property
use crate::diagnostics::BuildDiagnostics;
use crate::diagnostics::SourceLocation;
use crate::diagnostics::Spanned;
use crate::expression_tree::*;
use crate::langtype::ElementType;
use crate::langtype::Type;
use crate::object_tree::*;
use smol_str::SmolStr;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::{Rc, Weak};
pub fn lower_states(
component: &Rc<Component>,
tr: &crate::typeregister::TypeRegister,
diag: &mut BuildDiagnostics,
) {
let state_info_type = tr.lookup("StateInfo");
assert!(matches!(state_info_type, Type::Struct(ref s) if s.name.is_some()));
recurse_elem(&component.root_element, &(), &mut |elem, _| {
lower_state_in_element(elem, &state_info_type, diag)
});
}
fn lower_state_in_element(
root_element: &ElementRc,
state_info_type: &Type,
diag: &mut BuildDiagnostics,
) {
if root_element.borrow().states.is_empty() {
return;
}
let has_transitions = !root_element.borrow().transitions.is_empty();
let state_property_name = compute_state_property_name(root_element);
let state_property =
Expression::PropertyReference(NamedReference::new(root_element, &state_property_name));
let state_property_ref = if has_transitions {
Expression::StructFieldAccess {
base: Box::new(state_property.clone()),
name: "current-state".into(),
}
} else {
state_property.clone()
};
let mut affected_properties = HashSet::new();
// Maps State name string -> integer id
let mut states_id = HashMap::new();
let mut state_value = Expression::NumberLiteral(0., Unit::None);
let states = std::mem::take(&mut root_element.borrow_mut().states);
for (idx, state) in states.into_iter().enumerate().rev() {
if let Some(condition) = &state.condition {
state_value = Expression::Condition {
condition: Box::new(condition.clone()),
true_expr: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
false_expr: Box::new(std::mem::take(&mut state_value)),
};
}
for (ne, expr, node) in state.property_changes {
affected_properties.insert(ne.clone());
let e = ne.element();
let property_expr = match expression_for_property(&e, ne.name()) {
ExpressionForProperty::TwoWayBinding => {
diag.push_error(
format!("Cannot change the property '{}' in a state because it is initialized with a two-way binding", ne.name()),
&node
);
continue;
}
ExpressionForProperty::Expression(e) => e,
ExpressionForProperty::InvalidBecauseOfIssue1461 => {
diag.push_error(
format!("Internal error: The expression for the default state currently cannot be represented: https://github.com/slint-ui/slint/issues/1461\nAs a workaround, add a binding for property {}", ne.name()),
&node
);
continue;
}
};
let new_expr = Expression::Condition {
condition: Box::new(Expression::BinaryExpression {
lhs: Box::new(state_property_ref.clone()),
rhs: Box::new(Expression::NumberLiteral((idx + 1) as _, Unit::None)),
op: '=',
}),
true_expr: Box::new(expr),
false_expr: Box::new(property_expr),
};
match e.borrow_mut().bindings.entry(ne.name().into()) {
std::collections::btree_map::Entry::Occupied(mut e) => {
e.get_mut().get_mut().expression = new_expr
}
std::collections::btree_map::Entry::Vacant(e) => {
let mut r = BindingExpression::from(new_expr);
r.priority = 1;
e.insert(r.into());
}
};
}
states_id.insert(state.id, idx as i32 + 1);
}
root_element.borrow_mut().property_declarations.insert(
state_property_name.clone(),
PropertyDeclaration {
property_type: if has_transitions { state_info_type.clone() } else { Type::Int32 },
..PropertyDeclaration::default()
},
);
root_element
.borrow_mut()
.bindings
.insert(state_property_name, RefCell::new(state_value.into()));
lower_transitions_in_element(
root_element,
state_property,
states_id,
affected_properties,
diag,
);
}
fn lower_transitions_in_element(
elem: &ElementRc,
state_property: Expression,
states_id: HashMap<SmolStr, i32>,
affected_properties: HashSet<NamedReference>,
diag: &mut BuildDiagnostics,
) {
let transitions = std::mem::take(&mut elem.borrow_mut().transitions);
let mut props =
HashMap::<NamedReference, (SourceLocation, Vec<TransitionPropertyAnimation>)>::new();
for transition in transitions {
let state = states_id.get(&transition.state_id).unwrap_or_else(|| {
diag.push_error(
format!("State '{}' does not exist", transition.state_id),
transition
.node
.DeclaredIdentifier()
.as_ref()
.map(|x| x as &dyn Spanned)
.unwrap_or(&transition.node as &dyn Spanned),
);
&0
});
for (p, span, animation) in transition.property_animations {
if !affected_properties.contains(&p) {
diag.push_error(
"The property is not changed as part of this transition".into(),
&span,
);
continue;
}
let t = TransitionPropertyAnimation {
state_id: *state,
is_out: transition.is_out,
animation,
};
props.entry(p).or_insert_with(|| (span.clone(), vec![])).1.push(t);
}
}
for (ne, (span, animations)) in props {
let e = ne.element();
// We check earlier that the property is in the set of changed properties, so a binding bust have been assigned
let old_anim = e.borrow().bindings.get(ne.name()).unwrap().borrow_mut().animation.replace(
PropertyAnimation::Transition { state_ref: state_property.clone(), animations },
);
if old_anim.is_some() {
diag.push_error(
format!(
"The property '{}' cannot have transition because it already has an animation",
ne.name()
),
&span,
);
}
}
}
/// Returns a suitable unique name for the "state" property
fn compute_state_property_name(root_element: &ElementRc) -> SmolStr {
let mut property_name = "state".to_owned();
while root_element.borrow().lookup_property(property_name.as_ref()).property_type
!= Type::Invalid
{
property_name += "-";
}
property_name.into()
}
enum ExpressionForProperty {
TwoWayBinding,
Expression(Expression),
/// Workaround: the expression can't be represented with the current data structure, so make it an error for now.
InvalidBecauseOfIssue1461,
}
/// Return the expression binding currently associated to the given property
fn expression_for_property(element: &ElementRc, name: &str) -> ExpressionForProperty {
let mut element_it = Some(element.clone());
let mut in_base = false;
while let Some(elem) = element_it {
if let Some(e) = elem.borrow().bindings.get(name) {
let e = e.borrow();
if !e.two_way_bindings.is_empty() {
return ExpressionForProperty::TwoWayBinding;
}
let mut expr = e.expression.clone();
if !matches!(expr, Expression::Invalid) {
if in_base {
// Check that the expresison is valid in the new scope
let mut has_invalid = false;
expr.visit_recursive_mut(&mut |ex| match ex {
Expression::CallbackReference(nr, _)
| Expression::PropertyReference(nr)
| Expression::FunctionReference(nr, _) => {
let e = nr.element();
if Rc::ptr_eq(&e, &elem) {
*nr = NamedReference::new(element, nr.name());
} else if Weak::ptr_eq(
&e.borrow().enclosing_component,
&elem.borrow().enclosing_component,
) {
has_invalid = true;
}
}
_ => (),
});
if has_invalid {
return ExpressionForProperty::InvalidBecauseOfIssue1461;
}
}
return ExpressionForProperty::Expression(expr);
}
}
element_it = if let ElementType::Component(base) = &elem.borrow().base_type {
in_base = true;
Some(base.root_element.clone())
} else {
None
};
}
let expr = super::materialize_fake_properties::initialize(element, name).unwrap_or_else(|| {
Expression::default_value_for_type(&element.borrow().lookup_property(name).property_type)
});
ExpressionForProperty::Expression(expr)
}