Support for signal with arguments in the interpreter

This commit is contained in:
Olivier Goffart 2020-09-09 11:27:01 +02:00
parent 2ffef9b31a
commit 42aa91e3eb
7 changed files with 137 additions and 55 deletions

View file

@ -38,7 +38,7 @@ require.extensions['.60'] =
}); });
c.signals().forEach(x => { c.signals().forEach(x => {
Object.defineProperty(ret, x, { Object.defineProperty(ret, x, {
get() { return function () { comp.emit_signal(x); } }, get() { return function () { comp.emit_signal(x, [...arguments]); } },
enumerable: true, enumerable: true,
}) })
}); });

View file

@ -104,19 +104,20 @@ fn create<'cx>(
.set_signal_handler( .set_signal_handler(
component.borrow(), component.borrow(),
prop_name.as_str(), prop_name.as_str(),
Box::new(move |()| { Box::new(move |args| {
GLOBAL_CONTEXT.with(|cx_fn| { let args = args.iter().cloned().collect::<Vec<_>>();
GLOBAL_CONTEXT.with(move |cx_fn| {
cx_fn(&move |cx, presistent_context| { cx_fn(&move |cx, presistent_context| {
let args = args
.iter()
.map(|a| to_js_value(a.clone(), cx).unwrap())
.collect::<Vec<_>>();
presistent_context presistent_context
.get(cx, fun_idx) .get(cx, fun_idx)
.unwrap() .unwrap()
.downcast::<JsFunction>() .downcast::<JsFunction>()
.unwrap() .unwrap()
.call::<_, _, JsValue, _>( .call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
cx,
JsUndefined::new(),
std::iter::empty(),
)
.unwrap(); .unwrap();
}) })
}) })
@ -302,13 +303,33 @@ declare_types! {
} }
method emit_signal(mut cx) { method emit_signal(mut cx) {
let signal_name = cx.argument::<JsString>(0)?.value(); let signal_name = cx.argument::<JsString>(0)?.value();
let arguments = cx.argument::<JsArray>(1)?.to_vec(&mut cx)?;
let this = cx.this(); let this = cx.this();
let lock = cx.lock(); let lock = cx.lock();
let x = this.borrow(&lock).0.clone(); let x = this.borrow(&lock).0.clone();
let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?; let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
let ty = component.description().properties().get(&signal_name)
.ok_or(())
.or_else(|()| {
cx.throw_error(format!("Signal {} not found in the component", signal_name))
})?
.clone();
let args = if let Type::Signal {args} = ty {
let count = args.len();
let args = arguments.into_iter().zip(args.into_iter()).map(|(a, ty)| to_eval_value(a, ty, &mut cx)).collect::<Result<Vec<_>, _>>()?;
if args.len() != count {
cx.throw_error(format!("{} expect {} arguments, but {} where provided", signal_name, count, args.len()))?;
}
args
} else {
cx.throw_error(format!("{} is not a signal", signal_name))?;
unreachable!()
};
run_scoped(&mut cx,this.downcast().unwrap(), || { run_scoped(&mut cx,this.downcast().unwrap(), || {
component.description() component.description()
.emit_signal(component.borrow(), signal_name.as_str()) .emit_signal(component.borrow(), signal_name.as_str(), args.as_slice())
.map_err(|()| "Cannot emit signal".to_string()) .map_err(|()| "Cannot emit signal".to_string())
})?; })?;
Ok(JsUndefined::new().as_value(&mut cx)) Ok(JsUndefined::new().as_value(&mut cx))

View file

@ -22,14 +22,19 @@ use core::cell::Cell;
/// ///
/// The Arg represents the argument. It should always be a tuple /// The Arg represents the argument. It should always be a tuple
/// ///
#[derive(Default)]
#[repr(C)] #[repr(C)]
pub struct Signal<Arg> { pub struct Signal<Arg: ?Sized> {
/// FIXME: Box<dyn> is a fat object and we probaly want to put an erased type in there /// FIXME: Box<dyn> is a fat object and we probaly want to put an erased type in there
handler: Cell<Option<Box<dyn Fn(&Arg)>>>, handler: Cell<Option<Box<dyn Fn(&Arg)>>>,
} }
impl<Arg> Signal<Arg> { impl<Arg: ?Sized> Default for Signal<Arg> {
fn default() -> Self {
Self { handler: Default::default() }
}
}
impl<Arg: ?Sized> Signal<Arg> {
/// Emit the signal with the given argument. /// Emit the signal with the given argument.
pub fn emit(&self, a: &Arg) { pub fn emit(&self, a: &Arg) {
if let Some(h) = self.handler.take() { if let Some(h) = self.handler.take() {

View file

@ -185,7 +185,7 @@ pub struct ComponentDescription<'id> {
item_tree: Vec<ItemTreeNode<crate::dynamic_type::Instance<'id>>>, item_tree: Vec<ItemTreeNode<crate::dynamic_type::Instance<'id>>>,
pub(crate) items: HashMap<String, ItemWithinComponent>, pub(crate) items: HashMap<String, ItemWithinComponent>,
pub(crate) custom_properties: HashMap<String, PropertiesWithinComponent>, pub(crate) custom_properties: HashMap<String, PropertiesWithinComponent>,
pub(crate) custom_signals: HashMap<String, FieldOffset<Instance<'id>, Signal<()>>>, pub(crate) custom_signals: HashMap<String, FieldOffset<Instance<'id>, Signal<[eval::Value]>>>,
repeater: Vec<ErasedRepeaterWithinComponent<'id>>, repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
/// Map the Element::id of the repeater to the index in the `repeater` vec /// Map the Element::id of the repeater to the index in the `repeater` vec
pub repeater_names: HashMap<String, usize>, pub repeater_names: HashMap<String, usize>,
@ -471,7 +471,8 @@ fn generate_component<'id>(
Type::Resource => property_info::<Resource>(), Type::Resource => property_info::<Resource>(),
Type::Bool => property_info::<bool>(), Type::Bool => property_info::<bool>(),
Type::Signal { .. } => { Type::Signal { .. } => {
custom_signals.insert(name.clone(), builder.add_field_type::<Signal<()>>()); custom_signals
.insert(name.clone(), builder.add_field_type::<Signal<[eval::Value]>>());
continue; continue;
} }
Type::Object(_) => property_info::<eval::Value>(), Type::Object(_) => property_info::<eval::Value>(),
@ -611,18 +612,6 @@ pub fn instantiate<'id>(
for (prop, expr) in &elem.bindings { for (prop, expr) in &elem.bindings {
let ty = elem.lookup_property(prop.as_str()); let ty = elem.lookup_property(prop.as_str());
if let Type::Signal { .. } = ty { if let Type::Signal { .. } = ty {
let signal = item_within_component
.rtti
.signals
.get(prop.as_str())
.map(|o| &*(item.as_ptr().add(*o) as *const Signal<()>))
.or_else(|| {
component_type
.custom_signals
.get(prop.as_str())
.map(|o| o.apply(instance_ref.as_ref()))
})
.unwrap_or_else(|| panic!("unkown signal {}", prop));
let expr = expr.clone(); let expr = expr.clone();
let component_type = component_type.clone(); let component_type = component_type.clone();
let instance = component_box.instance.as_ptr(); let instance = component_box.instance.as_ptr();
@ -630,14 +619,36 @@ pub fn instantiate<'id>(
NonNull::from(&component_type.ct).cast(), NonNull::from(&component_type.ct).cast(),
instance.cast(), instance.cast(),
)); ));
signal.set_handler(move |_| { if let Some(signal_offset) =
generativity::make_guard!(guard); item_within_component.rtti.signals.get(prop.as_str())
eval::eval_expression( {
&expr, let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>);
InstanceRef::from_pin_ref(c, guard), signal.set_handler(move |_: &()| {
&mut Default::default(), generativity::make_guard!(guard);
); eval::eval_expression(
}) &expr,
InstanceRef::from_pin_ref(c, guard),
&mut Default::default(),
);
})
} else if let Some(signal_offset) =
component_type.custom_signals.get(prop.as_str())
{
let signal = signal_offset.apply(instance_ref.as_ref());
signal.set_handler(move |args| {
generativity::make_guard!(guard);
let mut local_context = eval::EvalLocalContext::from_function_arguments(
args.iter().cloned().collect(),
);
eval::eval_expression(
&expr,
InstanceRef::from_pin_ref(c, guard),
&mut local_context,
);
})
} else {
panic!("unkown signal {}", prop)
}
} else { } else {
if let Some(prop_rtti) = if let Some(prop_rtti) =
item_within_component.rtti.properties.get(prop.as_str()) item_within_component.rtti.properties.get(prop.as_str())

View file

@ -18,7 +18,7 @@ use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type};
use sixtyfps_corelib as corelib; use sixtyfps_corelib as corelib;
use sixtyfps_corelib::{ use sixtyfps_corelib::{
graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource, graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource,
SharedArray, SharedString, SharedArray, SharedString, Signal,
}; };
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, rc::Rc};
@ -164,7 +164,14 @@ declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVertic
#[derive(Default)] #[derive(Default)]
pub struct EvalLocalContext { pub struct EvalLocalContext {
local_variables: HashMap<String, Value>, local_variables: HashMap<String, Value>,
function_parameters: Vec<Value>, function_arguments: Vec<Value>,
}
impl EvalLocalContext {
/// Create a context for a function and passing the arguments
pub fn from_function_arguments(function_arguments: Vec<Value>) -> Self {
Self { function_arguments, ..Default::default() }
}
} }
/// Evaluate an expression and return a Value as the result of this expression /// Evaluate an expression and return a Value as the result of this expression
@ -197,7 +204,7 @@ pub fn eval_expression(
"model_data", "model_data",
), ),
Expression::FunctionParameterReference { index, .. } => { Expression::FunctionParameterReference { index, .. } => {
local_context.function_parameters[*index].clone() local_context.function_arguments[*index].clone()
} }
Expression::ObjectAccess { base, name } => { Expression::ObjectAccess { base, name } => {
if let Value::Object(mut o) = eval_expression(base, component, local_context) { if let Value::Object(mut o) = eval_expression(base, component, local_context) {
@ -224,7 +231,8 @@ pub fn eval_expression(
} }
v v
} }
Expression::FunctionCall { function, .. } => { Expression::FunctionCall { function, arguments } => {
let a = arguments.iter().map(|e| eval_expression(e, component, local_context));
if let Expression::SignalReference(NamedReference { element, name }) = &**function { if let Expression::SignalReference(NamedReference { element, name }) = &**function {
let element = element.upgrade().unwrap(); let element = element.upgrade().unwrap();
generativity::make_guard!(guard); generativity::make_guard!(guard);
@ -234,19 +242,19 @@ pub fn eval_expression(
let item_info = &component_type.items[element.borrow().id.as_str()]; let item_info = &component_type.items[element.borrow().id.as_str()];
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
let signal = item_info
.rtti if let Some(signal_offset) = item_info.rtti.signals.get(name.as_str()) {
.signals let signal =
.get(name.as_str()) unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) };
.map(|o| unsafe { &*(item.as_ptr().add(*o) as *const corelib::Signal<()>) }) signal.emit(&());
.or_else(|| { } else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str())
component_type {
.custom_signals let signal = signal_offset.apply(&*enclosing_component.instance);
.get(name.as_str()) signal.emit(a.collect::<Vec<_>>().as_slice())
.map(|o| o.apply(&*enclosing_component.instance)) } else {
}) panic!("unkown signal {}", name)
.unwrap_or_else(|| panic!("unkown signal {}", name)); }
signal.emit(&());
Value::Void Value::Void
} else if let Expression::BuiltinFunctionReference(funcref) = &**function { } else if let Expression::BuiltinFunctionReference(funcref) = &**function {
match funcref { match funcref {
@ -254,7 +262,7 @@ pub fn eval_expression(
Value::Number(window_ref(component).unwrap().scale_factor() as _) Value::Number(window_ref(component).unwrap().scale_factor() as _)
} }
BuiltinFunction::Debug => { BuiltinFunction::Debug => {
println!("FIXME: the debug statement in the interpreter should print its argument"); println!("{:?}", a);
Value::Void Value::Void
} }
} }

View file

@ -126,7 +126,7 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
&self, &self,
component: Pin<ComponentRef>, component: Pin<ComponentRef>,
name: &str, name: &str,
handler: Box<dyn Fn(&())>, handler: Box<dyn Fn(&[Value])>,
) -> Result<(), ()> { ) -> Result<(), ()> {
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) { if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
return Err(()); return Err(());
@ -141,13 +141,18 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
/// ///
/// Returns an error if the component is not an instance corresponding to this ComponentDescription, /// Returns an error if the component is not an instance corresponding to this ComponentDescription,
/// or if the signal with this name does not exist in this component /// or if the signal with this name does not exist in this component
pub fn emit_signal(&self, component: ComponentRefPin, name: &str) -> Result<(), ()> { pub fn emit_signal(
&self,
component: ComponentRefPin,
name: &str,
args: &[Value],
) -> Result<(), ()> {
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) { if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
return Err(()); return Err(());
} }
let x = self.custom_signals.get(name).ok_or(())?; let x = self.custom_signals.get(name).ok_or(())?;
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) }); let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
sig.emit(&()); sig.emit(args);
Ok(()) Ok(())
} }
} }

View file

@ -58,5 +58,37 @@ assert_eq!(instance.get_signal_emission_count(), 88);
assert_eq!(*signal_3_emited.borrow(), (55, "hello".into())); assert_eq!(*signal_3_emited.borrow(), (55, "hello".into()));
``` ```
```js
var signal_3_emited = 0;
var signal_3_string_value;
var signal_3_int_value;
var instance = new sixtyfps.TestCase({
test_signal3: function(a, b) {
signal_3_emited++;
signal_3_string_value = b;
signal_3_int_value = a;
}
});
instance.signal_emission_count = 0;
assert.equal(instance.signal_emission_count, 0);
instance.test_signal(42);
assert.equal(instance.signal_emission_count, 1);
instance.test_signal2("hello");
assert.equal(instance.signal_emission_count, 88);
assert.equal(signal_3_emited, 1);
assert.equal(signal_3_string_value, "hello");
assert.equal(signal_3_int_value, 55);
instance.signal_emission_count = 0;
// Calling a signal with a wrong number of arg
try {
instance.test_signal();
assert(false);
} catch(e) {
assert.equal(e.toString(), "Error: test_signal expect 1 arguments, but 0 where provided");
}
assert.equal(instance.signal_emission_count, 0);
```
*/ */