Finish support for callback aliases

cc #111
This commit is contained in:
Olivier Goffart 2021-06-07 17:59:11 +02:00 committed by Olivier Goffart
parent 3ddc3c6ce5
commit ce34ff87d0
12 changed files with 224 additions and 73 deletions

View file

@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file.
- `z` property on items
- Mouse wheel work on the Flickable and derivatives
- One can now omit the type of a two way binding property
- One can declare callback aliases
### Fixed

View file

@ -224,6 +224,8 @@ fn to_eval_value<'cx>(
Type::Enumeration(_) => todo!(),
Type::Invalid
| Type::Void
| Type::InferredProperty
| Type::InferredCallback
| Type::Builtin(_)
| Type::Native(_)
| Type::Function { .. }

View file

@ -358,6 +358,16 @@ Example := Rectangle {
}
```
### Callback aliases
It is possible to declare callback aliases in a similar way to two-way bindings:
```60
Example := Rectangle {
callback clicked <=> area.clicked;
area := TouchArea {}
}
```
## Expressions

View file

@ -659,6 +659,12 @@ fn generate_component(
let mut init = vec!["[[maybe_unused]] auto self = this;".into()];
for (cpp_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
let access = if let Some(alias) = &property_decl.is_alias {
access_named_reference(alias, component, "this")
} else {
format!("this->{}", cpp_name)
};
let ty = if let Type::Callback { args, return_type } = &property_decl.property_type {
let param_types =
args.iter().map(|t| get_cpp_type(t, property_decl, diag)).collect::<Vec<_>>();
@ -668,7 +674,7 @@ fn generate_component(
if property_decl.expose_in_public_api && is_root {
let callback_emitter = vec![format!(
"return {}.call({});",
cpp_name,
access,
(0..args.len()).map(|i| format!("arg_{}", i)).join(", ")
)];
component_struct.members.push((
@ -696,7 +702,7 @@ fn generate_component(
signature: "(Functor && callback_handler) const".into(),
statements: Some(vec![format!(
"{}.set_handler(std::forward<Functor>(callback_handler));",
cpp_name
access
)]),
..Default::default()
}),
@ -707,12 +713,6 @@ fn generate_component(
let cpp_type = get_cpp_type(&property_decl.property_type, property_decl, diag);
if property_decl.expose_in_public_api && is_root {
let access = if let Some(alias) = &property_decl.is_alias {
access_named_reference(alias, component, "this")
} else {
format!("this->{}", cpp_name)
};
let prop_getter: Vec<String> = vec![format!("return {}.get();", access)];
component_struct.members.push((
Access::Public,

View file

@ -280,8 +280,13 @@ fn generate_component(
let mut property_and_callback_accessors: Vec<TokenStream> = vec![];
for (prop_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
let prop_ident = format_ident!("{}", prop_name);
let prop = if let Some(alias) = &property_decl.is_alias {
access_named_reference(alias, component, quote!(_self))
} else {
quote!(#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self))
};
if let Type::Callback { args, return_type } = &property_decl.property_type {
declared_callbacks.push(prop_ident.clone());
let callback_args = args
.iter()
.map(|a| get_rust_type(a, &property_decl.type_node(), diag))
@ -295,16 +300,13 @@ fn generate_component(
.map(|i| format_ident!("arg_{}", i))
.collect::<Vec<_>>();
let caller_ident = format_ident!("invoke_{}", prop_name);
property_and_callback_accessors.push(
quote!(
property_and_callback_accessors.push(quote!(
#[allow(dead_code)]
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
let _self = vtable::VRc::as_pin_ref(&self.0);
#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self).call(&(#(#args_name,)*))
#prop.call(&(#(#args_name,)*))
}
)
,
);
));
let on_ident = format_ident!("on_{}", prop_name);
let args_index =
@ -315,7 +317,7 @@ fn generate_component(
pub fn #on_ident(&self, f: impl Fn(#(#callback_args),*) -> #return_type + 'static) {
let _self = vtable::VRc::as_pin_ref(&self.0);
#[allow(unused)]
#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self).set_handler(
#prop.set_handler(
// FIXME: why do i need to clone here?
move |args| f(#(args.#args_index.clone()),*)
)
@ -324,8 +326,11 @@ fn generate_component(
,
);
}
if property_decl.is_alias.is_none() {
declared_callbacks.push(prop_ident.clone());
declared_callbacks_types.push(callback_args);
declared_callbacks_ret.push(return_type);
}
} else {
let rust_property_type =
get_rust_type(&property_decl.property_type, &property_decl.type_node(), diag);
@ -333,12 +338,6 @@ fn generate_component(
let getter_ident = format_ident!("get_{}", prop_name);
let setter_ident = format_ident!("set_{}", prop_name);
let prop = if let Some(alias) = &property_decl.is_alias {
access_named_reference(alias, component, quote!(_self))
} else {
quote!(#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self))
};
property_and_callback_accessors.push(quote!(
#[allow(dead_code)]
pub fn #getter_ident(&self) -> #rust_property_type {

View file

@ -638,7 +638,7 @@ impl Element {
let unresolved_name = unwrap_or_continue!(identifier_text(&con_node); diag);
let PropertyLookupResult { resolved_name, property_type } =
r.lookup_property(&unresolved_name);
if let Type::Callback { args, .. } = property_type {
if let Type::Callback { args, .. } = &property_type {
let num_arg = con_node.DeclaredIdentifier().count();
if num_arg > args.len() {
diag.push_error(
@ -651,6 +651,15 @@ impl Element {
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
} else if property_type == Type::InferredCallback {
// argument matching will happen later
} else {
diag.push_error(
format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
continue;
}
if r.bindings
.insert(
resolved_name.into_owned(),
@ -663,12 +672,6 @@ impl Element {
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
} else {
diag.push_error(
format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
}
for anim in node.PropertyAnimation() {

View file

@ -326,7 +326,6 @@ fn parse_callback_declaration(p: &mut impl Parser) {
if p.test(SyntaxKind::DoubleArrow) {
let mut p = p.start_node(SyntaxKind::TwoWayBinding);
p.consume();
parse_expression(&mut *p);
}

View file

@ -241,7 +241,7 @@ impl Expression {
Expression::TwoWayBinding(n, None)
}
Expression::CallbackReference(n) => {
if ctx.property_type != Type::InferredCallback {
if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
ctx.diag.push_error("Cannot bind to a callback".into(), &node);
Expression::Invalid
} else {

View file

@ -8,6 +8,11 @@
Please contact info@sixtyfps.io for more information.
LICENSE END */
Sub := Rectangle {
callback compute(int) -> int;
callback compute_alias <=> compute;
}
Xxx := Rectangle {
foo := Rectangle {
@ -26,4 +31,12 @@ Xxx := Rectangle {
// ^^error{The expression in a two way binding must be a property reference}
callback loop2 <=> loop3;
Sub {
compute_alias(a, b, c) => {
debug(b); // FIXME: one should actually check that the connection has the right amount of arguments
// ^error{Unknown unqualified identifier 'b'}
return "hello";
// ^error{Cannot convert string to int}
}
}
}

View file

@ -439,9 +439,38 @@ impl<'id> ComponentDescription<'id> {
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
return Err(());
}
if let Some(alias) = self
.original
.root_element
.borrow()
.property_declarations
.get(name)
.and_then(|d| d.is_alias.as_ref())
{
generativity::make_guard!(guard);
// Safety: we just verified that the component has the right vtable
let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
generativity::make_guard!(guard);
let element = alias.element();
let enclosing_component = eval::enclosing_component_for_element(&element, c, guard);
let component_type = enclosing_component.component_type;
let item_info = &component_type.items[element.borrow().id.as_str()];
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
if let Some(callback) = item_info.rtti.callbacks.get(alias.name()) {
callback.set_handler(item, handler)
} else if let Some(callback_offset) = component_type.custom_callbacks.get(alias.name())
{
let callback = callback_offset.apply(&*enclosing_component.instance);
callback.set_handler(handler)
} else {
return Err(());
}
} else {
let x = self.custom_callbacks.get(name).ok_or(())?;
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
sig.set_handler(handler);
}
Ok(())
}
@ -458,9 +487,31 @@ impl<'id> ComponentDescription<'id> {
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
return Err(());
}
let x = self.custom_callbacks.get(name).ok_or(())?;
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
Ok(sig.call(args))
generativity::make_guard!(guard);
// Safety: we just verified that the component has the right vtable
let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
if let Some(alias) = self
.original
.root_element
.borrow()
.property_declarations
.get(name)
.and_then(|d| d.is_alias.as_ref())
{
Ok(eval::invoke_callback(
eval::ComponentInstance::InstanceRef(c),
&alias.element(),
alias.name(),
args,
))
} else {
Ok(eval::invoke_callback(
eval::ComponentInstance::InstanceRef(c),
&self.original.root_element,
name,
args,
))
}
}
}

View file

@ -193,30 +193,8 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
}
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
Expression::CallbackReference(nr) => {
let element = nr.element();
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(&element, local_context.component_instance, guard) {
ComponentInstance::InstanceRef(enclosing_component) => {
let component_type = enclosing_component.component_type;
let item_info = &component_type.items[element.borrow().id.as_str()];
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
if let Some(callback) = item_info.rtti.callbacks.get(nr.name()) {
callback.call(item, args.as_slice())
} else if let Some(callback_offset) = component_type.custom_callbacks.get(nr.name())
{
let callback = callback_offset.apply(&*enclosing_component.instance);
callback.call(args.as_slice())
} else {
panic!("unkown callback {}", nr.name())
}
}
ComponentInstance::GlobalComponent(global) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context));
global.as_ref().invoke_callback(nr.name(), args.collect::<Vec<_>>().as_slice())
}
}
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args)
}
Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor, _) => {
match local_context.component_instance {
@ -720,6 +698,35 @@ pub fn store_property(
Ok(())
}
pub(crate) fn invoke_callback(
component_instance: ComponentInstance,
element: &ElementRc,
callback_name: &str,
args: &[Value],
) -> Value {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(element, component_instance, guard) {
ComponentInstance::InstanceRef(enclosing_component) => {
let component_type = enclosing_component.component_type;
let item_info = &component_type.items[element.borrow().id.as_str()];
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
if let Some(callback) = item_info.rtti.callbacks.get(callback_name) {
callback.call(item, args)
} else if let Some(callback_offset) = component_type.custom_callbacks.get(callback_name)
{
let callback = callback_offset.apply(&*enclosing_component.instance);
callback.call(args)
} else {
panic!("unkown callback {}", callback_name)
}
}
ComponentInstance::GlobalComponent(global) => {
global.as_ref().invoke_callback(callback_name, args)
}
}
}
fn root_component_instance<'a, 'old_id, 'new_id>(
component: InstanceRef<'a, 'old_id>,
guard: generativity::Guard<'new_id>,

View file

@ -0,0 +1,66 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
Foo := Rectangle {
callback hallo_alias <=> xxx.hallo;
callback clicked <=> are.clicked;
xxx := Rectangle {
callback hallo(int) -> int;
hallo(a) => { return a + 88; }
}
are := TouchArea { }
}
TestCase := Rectangle {
callback foo1_alias <=> foo1.hallo_alias;
callback foo2_alias <=> foo2.hallo_alias;
callback foo1_clicked <=> foo1.clicked;
callback call_foo2(int) -> int;
call_foo2(a) => { return foo2.hallo_alias(a); }
foo1 := Foo {
hallo_alias(a) => { return a + 22; }
}
foo2 := Foo {
clicked => { debug(42) }
}
property <bool> test: foo1_alias(100) == 122 && foo2_alias(100) == 188;
}
/*
```rust
let instance = TestCase::new();
assert_eq!(instance.invoke_foo1_alias(100), 122);
assert_eq!(instance.invoke_foo2_alias(100), 188);
assert_eq!(instance.invoke_call_foo2(100), 188);
```
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert_eq(instance.invoke_foo1_alias(100), 122);
assert_eq(instance.invoke_foo2_alias(100), 188);
assert_eq(instance.invoke_call_foo2(100), 188);
```
```js
var instance = new sixtyfps.TestCase();
assert.equal(instance.foo1_alias(100), 122);
assert.equal(instance.foo2_alias(100), 188);
assert.equal(instance.call_foo2(100), 188);
```
*/