More work on signal returning value

This commit is contained in:
Olivier Goffart 2020-12-01 17:58:18 +01:00
parent 276e11a101
commit e73bbbcd10
8 changed files with 107 additions and 39 deletions

View file

@ -13,8 +13,46 @@ LICENSE END */
namespace sixtyfps { namespace sixtyfps {
template<typename = void()> struct Signal;
template<typename Ret, typename... Arg>
struct Signal<Ret(Arg...)>
{
Signal() { cbindgen_private::sixtyfps_signal_init(&inner); }
~Signal() { cbindgen_private::sixtyfps_signal_drop(&inner); }
Signal(const Signal &) = delete;
Signal(Signal &&) = delete;
Signal &operator=(const Signal &) = delete;
template<typename F>
void set_handler(F binding) const
{
cbindgen_private::sixtyfps_signal_set_handler(
&inner,
[](void *user_data, const void *arg) {
auto *p = reinterpret_cast<const Pair*>(arg);
*p->first = std::apply(*reinterpret_cast<F *>(user_data), p->second);
},
new F(std::move(binding)),
[](void *user_data) { delete reinterpret_cast<F *>(user_data); });
}
Ret emit(const Arg &...arg) const
{
Ret r{};
Pair p = std::pair{ &r, Tuple{arg...} };
cbindgen_private::sixtyfps_signal_emit(&inner, &p);
return r;
}
private:
using Tuple = std::tuple<Arg...>;
using Pair = std::pair<Ret *, Tuple>;
cbindgen_private::SignalOpaque inner;
};
template<typename... Arg> template<typename... Arg>
struct Signal struct Signal<void(Arg...)>
{ {
Signal() { cbindgen_private::sixtyfps_signal_init(&inner); } Signal() { cbindgen_private::sixtyfps_signal_init(&inner); }
~Signal() { cbindgen_private::sixtyfps_signal_drop(&inner); } ~Signal() { cbindgen_private::sixtyfps_signal_drop(&inner); }
@ -45,4 +83,8 @@ private:
using Tuple = std::tuple<Arg...>; using Tuple = std::tuple<Arg...>;
cbindgen_private::SignalOpaque inner; cbindgen_private::SignalOpaque inner;
}; };
} }

View file

@ -86,21 +86,31 @@ fn make_signal_handler<'cx>(
cx: &mut impl Context<'cx>, cx: &mut impl Context<'cx>,
persistent_context: &persistent_context::PersistentContext<'cx>, persistent_context: &persistent_context::PersistentContext<'cx>,
fun: Handle<'cx, JsFunction>, fun: Handle<'cx, JsFunction>,
) -> Box<dyn Fn(&[sixtyfps_interpreter::Value])> { return_type: Option<Box<Type>>,
) -> Box<dyn Fn(&[sixtyfps_interpreter::Value]) -> sixtyfps_interpreter::Value> {
let fun_value = fun.as_value(cx); let fun_value = fun.as_value(cx);
let fun_idx = persistent_context.allocate(cx, fun_value); let fun_idx = persistent_context.allocate(cx, fun_value);
Box::new(move |args| { Box::new(move |args| {
let args = args.iter().cloned().collect::<Vec<_>>(); let args = args.iter().cloned().collect::<Vec<_>>();
let ret = core::cell::Cell::new(sixtyfps_interpreter::Value::Void);
let borrow_ret = &ret;
let return_type = &return_type;
run_with_global_contect(&move |cx, persistent_context| { run_with_global_contect(&move |cx, persistent_context| {
let args = args.iter().map(|a| to_js_value(a.clone(), cx).unwrap()).collect::<Vec<_>>(); let args = args.iter().map(|a| to_js_value(a.clone(), cx).unwrap()).collect::<Vec<_>>();
persistent_context let ret = persistent_context
.get(cx, fun_idx) .get(cx, fun_idx)
.unwrap() .unwrap()
.downcast::<JsFunction>() .downcast::<JsFunction>()
.unwrap() .unwrap()
.call::<_, _, JsValue, _>(cx, JsUndefined::new(), args) .call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
.unwrap(); .unwrap();
}) if let Some(return_type) = return_type {
borrow_ret.set(
to_eval_value(ret, (**return_type).clone(), cx, persistent_context).unwrap(),
);
}
});
ret.into_inner()
}) })
} }
@ -123,13 +133,13 @@ fn create<'cx>(
cx.throw_error(format!("Property {} not found in the component", prop_name)) cx.throw_error(format!("Property {} not found in the component", prop_name))
})? })?
.clone(); .clone();
if let Type::Signal { .. } = ty { if let Type::Signal { return_type, .. } = ty {
let fun = value.downcast_or_throw::<JsFunction, _>(cx)?; let fun = value.downcast_or_throw::<JsFunction, _>(cx)?;
component_type component_type
.set_signal_handler( .set_signal_handler(
component.borrow(), component.borrow(),
prop_name.as_str(), prop_name.as_str(),
make_signal_handler(cx, &persistent_context, fun), make_signal_handler(cx, &persistent_context, fun, return_type),
) )
.or_else(|_| cx.throw_error(format!("Cannot set signal")))?; .or_else(|_| cx.throw_error(format!("Cannot set signal")))?;
} else { } else {
@ -425,12 +435,24 @@ declare_types! {
let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?; let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
generativity::make_guard!(guard); generativity::make_guard!(guard);
let component = component.unerase(guard); let component = component.unerase(guard);
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();
if let Type::Signal {return_type, ..} = ty {
component.description().set_signal_handler( component.description().set_signal_handler(
component.borrow(), component.borrow(),
signal_name.as_str(), signal_name.as_str(),
make_signal_handler(&mut cx, &persistent_context, handler) make_signal_handler(&mut cx, &persistent_context, handler, return_type)
).or_else(|_| cx.throw_error(format!("Cannot set signal")))?; ).or_else(|_| cx.throw_error(format!("Cannot set signal")))?;
Ok(JsUndefined::new().as_value(&mut cx)) Ok(JsUndefined::new().as_value(&mut cx))
} else {
cx.throw_error(format!("{} is not a signal", signal_name))?;
unreachable!()
}
} }
method send_mouse_click(mut cx) { method send_mouse_click(mut cx) {

View file

@ -658,6 +658,9 @@ fn generate_component(
.iter() .iter()
.map(|t| get_cpp_type(t, &property_decl.type_node, diag)) .map(|t| get_cpp_type(t, &property_decl.type_node, diag))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let return_type = return_type
.as_ref()
.map_or("void".into(), |t| get_cpp_type(&t, &property_decl.type_node, diag));
if property_decl.expose_in_public_api && is_root { if property_decl.expose_in_public_api && is_root {
let signal_emitter = vec![format!( let signal_emitter = vec![format!(
"return {}.emit({});", "return {}.emit({});",
@ -669,17 +672,13 @@ fn generate_component(
Declaration::Function(Function { Declaration::Function(Function {
name: format!("emit_{}", cpp_name), name: format!("emit_{}", cpp_name),
signature: format!( signature: format!(
"({}) -> {} const", "({}) const -> {}",
param_types param_types
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, ty)| format!("{} arg_{}", ty, i)) .map(|(i, ty)| format!("{} arg_{}", ty, i))
.join(", "), .join(", "),
return_type.as_ref().map_or("void".into(), |t| get_cpp_type( return_type
&t,
&property_decl.type_node,
diag
))
), ),
statements: Some(signal_emitter), statements: Some(signal_emitter),
..Default::default() ..Default::default()
@ -699,7 +698,7 @@ fn generate_component(
}), }),
)); ));
} }
format!("sixtyfps::Signal<{}>", param_types.join(", ")) format!("sixtyfps::Signal<{}({})>", return_type, param_types.join(", "))
} else { } else {
let cpp_type = let cpp_type =
get_cpp_type(&property_decl.property_type, &property_decl.type_node, diag); get_cpp_type(&property_decl.property_type, &property_decl.type_node, diag);

View file

@ -23,31 +23,34 @@ 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
/// ///
#[repr(C)] #[repr(C)]
pub struct Signal<Arg: ?Sized> { pub struct Signal<Arg: ?Sized, Ret = ()> {
/// 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) -> Ret>>>,
} }
impl<Arg: ?Sized> Default for Signal<Arg> { impl<Arg: ?Sized, Ret> Default for Signal<Arg, Ret> {
fn default() -> Self { fn default() -> Self {
Self { handler: Default::default() } Self { handler: Default::default() }
} }
} }
impl<Arg: ?Sized> Signal<Arg> { impl<Arg: ?Sized, Ret: Default> Signal<Arg, Ret> {
/// 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) -> Ret {
if let Some(h) = self.handler.take() { if let Some(h) = self.handler.take() {
h(a); let r = h(a);
assert!(self.handler.take().is_none(), "Signal Handler set while emitted"); assert!(self.handler.take().is_none(), "Signal Handler set while emitted");
self.handler.set(Some(h)) self.handler.set(Some(h));
r
} else {
Default::default()
} }
} }
/// Set an handler to be called when the signal is emited /// Set an handler to be called when the signal is emited
/// ///
/// There can only be one single handler per signal. /// There can only be one single handler per signal.
pub fn set_handler(&self, f: impl Fn(&Arg) + 'static) { pub fn set_handler(&self, f: impl Fn(&Arg) -> Ret + 'static) {
self.handler.set(Some(Box::new(f))); self.handler.set(Some(Box::new(f)));
} }
} }

View file

@ -31,7 +31,7 @@ use sixtyfps_corelib::model::Repeater;
use sixtyfps_corelib::properties::InterpolatedPropertyValue; use sixtyfps_corelib::properties::InterpolatedPropertyValue;
use sixtyfps_corelib::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo}; use sixtyfps_corelib::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo};
use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::slice::Slice;
use sixtyfps_corelib::{Color, Property, SharedString, Signal}; use sixtyfps_corelib::{Color, Property, SharedString};
use std::collections::HashMap; use std::collections::HashMap;
use std::{pin::Pin, rc::Rc}; use std::{pin::Pin, rc::Rc};
@ -268,6 +268,8 @@ impl<'id> ErasedRepeaterWithinComponent<'id> {
} }
} }
type Signal = sixtyfps_corelib::Signal<[eval::Value], eval::Value>;
/// ComponentDescription is a representation of a component suitable for interpretation /// ComponentDescription is a representation of a component suitable for interpretation
/// ///
/// It contains information about how to create and destroy the Component. /// It contains information about how to create and destroy the Component.
@ -282,7 +284,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<[eval::Value]>>>, pub(crate) custom_signals: HashMap<String, FieldOffset<Instance<'id>, Signal>>,
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>,
@ -612,8 +614,7 @@ 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 custom_signals.insert(name.clone(), builder.add_field_type::<Signal>());
.insert(name.clone(), builder.add_field_type::<Signal<[eval::Value]>>());
continue; continue;
} }
Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => { Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => {
@ -820,7 +821,8 @@ pub fn instantiate<'id>(
if let Some(signal_offset) = if let Some(signal_offset) =
item_within_component.rtti.signals.get(prop.as_str()) item_within_component.rtti.signals.get(prop.as_str())
{ {
let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>); let signal = &*(item.as_ptr().add(*signal_offset)
as *const sixtyfps_corelib::Signal<()>);
signal.set_handler(move |_: &()| { signal.set_handler(move |_: &()| {
generativity::make_guard!(guard); generativity::make_guard!(guard);
eval::eval_expression( eval::eval_expression(
@ -840,7 +842,7 @@ pub fn instantiate<'id>(
InstanceRef::from_pin_ref(c, guard), InstanceRef::from_pin_ref(c, guard),
args.iter().cloned().collect(), args.iter().cloned().collect(),
); );
eval::eval_expression(&expr, &mut local_context); eval::eval_expression(&expr, &mut local_context)
}) })
} else { } else {
panic!("unkown signal {}", prop) panic!("unkown signal {}", prop)

View file

@ -349,6 +349,7 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
let signal = let signal =
unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) }; unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) };
signal.emit(&()); signal.emit(&());
Value::Void
} else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str()) } else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str())
{ {
let signal = signal_offset.apply(&*enclosing_component.instance); let signal = signal_offset.apply(&*enclosing_component.instance);
@ -360,10 +361,9 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
} }
ComponentInstance::GlobalComponent(global) => { ComponentInstance::GlobalComponent(global) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)); let args = arguments.iter().map(|e| eval_expression(e, local_context));
global.as_ref().emit_signal(name.as_ref(), args.collect::<Vec<_>>().as_slice()); global.as_ref().emit_signal(name.as_ref(), args.collect::<Vec<_>>().as_slice())
}
} }
};
Value::Void
} }
Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor) => { Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor) => {
match local_context.component_instance { match local_context.component_instance {

View file

@ -18,7 +18,7 @@ use sixtyfps_corelib::{rtti, Property, Signal};
use crate::eval; use crate::eval;
pub trait GlobalComponent { pub trait GlobalComponent {
fn emit_signal(self: Pin<&Self>, _signal_name: &str, _args: &[eval::Value]) { fn emit_signal(self: Pin<&Self>, _signal_name: &str, _args: &[eval::Value]) -> eval::Value {
todo!("emit signal") todo!("emit signal")
} }

View file

@ -151,7 +151,7 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
&self, &self,
component: Pin<ComponentRef>, component: Pin<ComponentRef>,
name: &str, name: &str,
handler: Box<dyn Fn(&[Value])>, handler: Box<dyn Fn(&[Value]) -> 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(());