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 => {
Object.defineProperty(ret, x, {
get() { return function () { comp.emit_signal(x); } },
get() { return function () { comp.emit_signal(x, [...arguments]); } },
enumerable: true,
})
});

View file

@ -104,19 +104,20 @@ fn create<'cx>(
.set_signal_handler(
component.borrow(),
prop_name.as_str(),
Box::new(move |()| {
GLOBAL_CONTEXT.with(|cx_fn| {
Box::new(move |args| {
let args = args.iter().cloned().collect::<Vec<_>>();
GLOBAL_CONTEXT.with(move |cx_fn| {
cx_fn(&move |cx, presistent_context| {
let args = args
.iter()
.map(|a| to_js_value(a.clone(), cx).unwrap())
.collect::<Vec<_>>();
presistent_context
.get(cx, fun_idx)
.unwrap()
.downcast::<JsFunction>()
.unwrap()
.call::<_, _, JsValue, _>(
cx,
JsUndefined::new(),
std::iter::empty(),
)
.call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
.unwrap();
})
})
@ -302,13 +303,33 @@ declare_types! {
}
method emit_signal(mut cx) {
let signal_name = cx.argument::<JsString>(0)?.value();
let arguments = cx.argument::<JsArray>(1)?.to_vec(&mut cx)?;
let this = cx.this();
let lock = cx.lock();
let x = this.borrow(&lock).0.clone();
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(), || {
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())
})?;
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
///
#[derive(Default)]
#[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
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.
pub fn emit(&self, a: &Arg) {
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>>>,
pub(crate) items: HashMap<String, ItemWithinComponent>,
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>>,
/// Map the Element::id of the repeater to the index in the `repeater` vec
pub repeater_names: HashMap<String, usize>,
@ -471,7 +471,8 @@ fn generate_component<'id>(
Type::Resource => property_info::<Resource>(),
Type::Bool => property_info::<bool>(),
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;
}
Type::Object(_) => property_info::<eval::Value>(),
@ -611,18 +612,6 @@ pub fn instantiate<'id>(
for (prop, expr) in &elem.bindings {
let ty = elem.lookup_property(prop.as_str());
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 component_type = component_type.clone();
let instance = component_box.instance.as_ptr();
@ -630,7 +619,11 @@ pub fn instantiate<'id>(
NonNull::from(&component_type.ct).cast(),
instance.cast(),
));
signal.set_handler(move |_| {
if let Some(signal_offset) =
item_within_component.rtti.signals.get(prop.as_str())
{
let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>);
signal.set_handler(move |_: &()| {
generativity::make_guard!(guard);
eval::eval_expression(
&expr,
@ -638,6 +631,24 @@ pub fn instantiate<'id>(
&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 {
if let Some(prop_rtti) =
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::{
graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource,
SharedArray, SharedString,
SharedArray, SharedString, Signal,
};
use std::{collections::HashMap, rc::Rc};
@ -164,7 +164,14 @@ declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVertic
#[derive(Default)]
pub struct EvalLocalContext {
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
@ -197,7 +204,7 @@ pub fn eval_expression(
"model_data",
),
Expression::FunctionParameterReference { index, .. } => {
local_context.function_parameters[*index].clone()
local_context.function_arguments[*index].clone()
}
Expression::ObjectAccess { base, name } => {
if let Value::Object(mut o) = eval_expression(base, component, local_context) {
@ -224,7 +231,8 @@ pub fn eval_expression(
}
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 {
let element = element.upgrade().unwrap();
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 = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
let signal = item_info
.rtti
.signals
.get(name.as_str())
.map(|o| unsafe { &*(item.as_ptr().add(*o) as *const corelib::Signal<()>) })
.or_else(|| {
component_type
.custom_signals
.get(name.as_str())
.map(|o| o.apply(&*enclosing_component.instance))
})
.unwrap_or_else(|| panic!("unkown signal {}", name));
if let Some(signal_offset) = item_info.rtti.signals.get(name.as_str()) {
let signal =
unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) };
signal.emit(&());
} else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str())
{
let signal = signal_offset.apply(&*enclosing_component.instance);
signal.emit(a.collect::<Vec<_>>().as_slice())
} else {
panic!("unkown signal {}", name)
}
Value::Void
} else if let Expression::BuiltinFunctionReference(funcref) = &**function {
match funcref {
@ -254,7 +262,7 @@ pub fn eval_expression(
Value::Number(window_ref(component).unwrap().scale_factor() as _)
}
BuiltinFunction::Debug => {
println!("FIXME: the debug statement in the interpreter should print its argument");
println!("{:?}", a);
Value::Void
}
}

View file

@ -126,7 +126,7 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
&self,
component: Pin<ComponentRef>,
name: &str,
handler: Box<dyn Fn(&())>,
handler: Box<dyn Fn(&[Value])>,
) -> Result<(), ()> {
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
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,
/// 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 _) {
return Err(());
}
let x = self.custom_signals.get(name).ok_or(())?;
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
sig.emit(&());
sig.emit(args);
Ok(())
}
}

View file

@ -58,5 +58,37 @@ assert_eq!(instance.get_signal_emission_count(), 88);
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);
```
*/