Put the component in a Pin<>

Removed the drop and create from the ComponentVTable:
since we are not using VBox<ComponentVTable>, this simplifies a bit
the code of the interpreter and everything else.

But there is still a lot of changes everywhere to support that the Component
is pinned.
This is just for the component. Which would be required if later we want
to access the properties as Pin<Property<_>>. But we have not yet ability
to do projections
This commit is contained in:
Olivier Goffart 2020-06-24 14:13:27 +02:00
parent de74ae4534
commit caca0d0ba4
31 changed files with 356 additions and 241 deletions

View file

@ -51,11 +51,6 @@ using internal::Rectangle;
using internal::Text; using internal::Text;
using internal::TouchArea; using internal::TouchArea;
// the component has static lifetime so it does not need to be destroyed
// FIXME: we probably need some kind of way to dinstinguish static component and
// these on the heap
inline void dummy_destory(ComponentRef) { }
constexpr inline ItemTreeNode<uint8_t> make_item_node(std::uintptr_t offset, constexpr inline ItemTreeNode<uint8_t> make_item_node(std::uintptr_t offset,
const internal::ItemVTable *vtable, const internal::ItemVTable *vtable,
uint32_t child_count, uint32_t child_index) uint32_t child_count, uint32_t child_index)

View file

@ -13,6 +13,9 @@ using VRef = VRefMut<T>;
template<typename T> template<typename T>
using VBox = VRefMut<T>; using VBox = VRefMut<T>;
template<typename T>
using Pin = T;
/* /*
template<typename T> template<typename T>
struct VBox { struct VBox {

View file

@ -1,18 +1,15 @@
use core::cell::RefCell; use core::cell::RefCell;
use neon::prelude::*; use neon::prelude::*;
use sixtyfps_compilerlib::typeregister::Type; use sixtyfps_compilerlib::typeregister::Type;
use sixtyfps_corelib::{ use sixtyfps_corelib::abi::datastructures::Resource;
abi::datastructures::{ComponentBox, ComponentRef, Resource}, use sixtyfps_corelib::{ComponentRefPin, EvaluationContext};
EvaluationContext,
};
use std::rc::Rc; use std::rc::Rc;
mod persistent_context; mod persistent_context;
struct WrappedComponentType(Option<Rc<sixtyfps_interpreter::ComponentDescription>>); struct WrappedComponentType(Option<Rc<sixtyfps_interpreter::ComponentDescription>>);
struct WrappedComponentBox( struct WrappedComponentBox(Option<Rc<sixtyfps_interpreter::ComponentBox>>);
Option<(Rc<ComponentBox>, Rc<sixtyfps_interpreter::ComponentDescription>)>,
);
/// We need to do some gymnastic with closures to pass the ExecuteContext with the right lifetime /// We need to do some gymnastic with closures to pass the ExecuteContext with the right lifetime
type GlobalContextCallback = type GlobalContextCallback =
@ -98,7 +95,7 @@ fn create<'cx>(
let mut obj = SixtyFpsComponent::new::<_, JsValue, _>(cx, std::iter::empty())?; let mut obj = SixtyFpsComponent::new::<_, JsValue, _>(cx, std::iter::empty())?;
persistent_context.save_to_object(cx, obj.downcast().unwrap()); persistent_context.save_to_object(cx, obj.downcast().unwrap());
cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some((Rc::new(component), component_type))); cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some(Rc::new(component)));
Ok(obj.as_value(cx)) Ok(obj.as_value(cx))
} }
@ -158,7 +155,7 @@ fn to_js_value<'cx>(
fn show<'cx>( fn show<'cx>(
cx: &mut CallContext<'cx, impl neon::object::This>, cx: &mut CallContext<'cx, impl neon::object::This>,
component: ComponentRef, component: ComponentRefPin,
presistent_context: persistent_context::PersistentContext<'cx>, presistent_context: persistent_context::PersistentContext<'cx>,
) -> JsResult<'cx, JsUndefined> { ) -> JsResult<'cx, JsUndefined> {
cx.execute_scoped(|cx| { cx.execute_scoped(|cx| {
@ -229,7 +226,7 @@ declare_types! {
method show(mut cx) { method show(mut cx) {
let mut this = cx.this(); let mut this = cx.this();
let component = cx.borrow(&mut this, |x| x.0.clone()); let component = cx.borrow(&mut this, |x| x.0.clone());
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?.0; let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
let persistent_context = persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?; let persistent_context = persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?;
show(&mut cx, component.borrow(), persistent_context)?; show(&mut cx, component.borrow(), persistent_context)?;
Ok(JsUndefined::new().as_value(&mut cx)) Ok(JsUndefined::new().as_value(&mut cx))
@ -239,8 +236,8 @@ declare_types! {
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, component_ty) = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?; let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
let value = component_ty let value = component.description()
.get_property(&EvaluationContext::for_root_component(component.borrow()), prop_name.as_str()) .get_property(&EvaluationContext::for_root_component(component.borrow()), prop_name.as_str())
.or_else(|_| cx.throw_error(format!("Cannot read property")))?; .or_else(|_| cx.throw_error(format!("Cannot read property")))?;
to_js_value(value, &mut cx) to_js_value(value, &mut cx)
@ -250,8 +247,8 @@ declare_types! {
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, component_ty) = 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_ty.properties() let ty = component.description().properties()
.get(&prop_name) .get(&prop_name)
.ok_or(()) .ok_or(())
.or_else(|()| { .or_else(|()| {
@ -260,7 +257,7 @@ declare_types! {
.clone(); .clone();
let value = to_eval_value(cx.argument::<JsValue>(1)?, ty, &mut cx)?; let value = to_eval_value(cx.argument::<JsValue>(1)?, ty, &mut cx)?;
component_ty component.description()
.set_property(component.borrow(), prop_name.as_str(), value) .set_property(component.borrow(), prop_name.as_str(), value)
.or_else(|_| cx.throw_error(format!("Cannot assign property")))?; .or_else(|_| cx.throw_error(format!("Cannot assign property")))?;
@ -271,8 +268,8 @@ declare_types! {
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, component_ty) = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?; let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
component_ty component.description()
.emit_signal(&EvaluationContext::for_root_component(component.borrow()), signal_name.as_str()) .emit_signal(&EvaluationContext::for_root_component(component.borrow()), signal_name.as_str())
.or_else(|_| cx.throw_error(format!("Cannot emit signal")))?; .or_else(|_| cx.throw_error(format!("Cannot emit signal")))?;

View file

@ -12,6 +12,7 @@ once_cell = "1.4"
sixtyfps-rs-macro= { path = "sixtyfps-rs-macro" } sixtyfps-rs-macro= { path = "sixtyfps-rs-macro" }
const-field-offset = { path = "../../helper_crates/const-field-offset" } const-field-offset = { path = "../../helper_crates/const-field-offset" }
vtable = { path = "../../helper_crates/vtable" } vtable = { path = "../../helper_crates/vtable" }
pin-utils = "0.1"
sixtyfps_corelib = { path="../../sixtyfps_runtime/corelib" } sixtyfps_corelib = { path="../../sixtyfps_runtime/corelib" }
# FIXME: should be under a feature or somehow done differently # FIXME: should be under a feature or somehow done differently
sixtyfps_rendering_backend_gl = { path="../../sixtyfps_runtime/rendering_backends/gl" } sixtyfps_rendering_backend_gl = { path="../../sixtyfps_runtime/rendering_backends/gl" }

View file

@ -74,6 +74,7 @@ pub mod re_exports {
pub use crate::repeater::*; pub use crate::repeater::*;
pub use const_field_offset::{self, FieldOffsets}; pub use const_field_offset::{self, FieldOffsets};
pub use once_cell::sync::Lazy; pub use once_cell::sync::Lazy;
pub use pin_utils::pin_mut;
pub use sixtyfps_corelib::abi::datastructures::*; pub use sixtyfps_corelib::abi::datastructures::*;
pub use sixtyfps_corelib::abi::primitives::*; pub use sixtyfps_corelib::abi::primitives::*;
pub use sixtyfps_corelib::abi::properties::Property; pub use sixtyfps_corelib::abi::properties::Property;

View file

@ -1,5 +1,5 @@
/// Component that can be instantiated by a repeater. /// Component that can be instantiated by a repeater.
pub trait RepeatedComponent: sixtyfps_corelib::abi::datastructures::Component { pub trait RepeatedComponent: sixtyfps_corelib::abi::datastructures::Component + Default {
/// The data corresponding to the model /// The data corresponding to the model
type Data; type Data;
@ -11,7 +11,7 @@ pub trait RepeatedComponent: sixtyfps_corelib::abi::datastructures::Component {
/// It helps instantiating the components `C` /// It helps instantiating the components `C`
#[derive(Default)] #[derive(Default)]
pub struct Repeater<C> { pub struct Repeater<C> {
components: Vec<Box<C>>, components: Vec<core::pin::Pin<Box<C>>>,
} }
impl<Data, C> Repeater<C> impl<Data, C> Repeater<C>
@ -25,16 +25,16 @@ where
{ {
self.components.clear(); self.components.clear();
for (i, d) in data.enumerate() { for (i, d) in data.enumerate() {
let c = C::create(); let c = C::default();
c.update(i, d); c.update(i, d);
self.components.push(Box::new(c)); self.components.push(Box::pin(c));
} }
} }
/// Call the visitor for each component /// Call the visitor for each component
pub fn visit(&self, mut visitor: sixtyfps_corelib::abi::datastructures::ItemVisitorRefMut) { pub fn visit(&self, mut visitor: sixtyfps_corelib::abi::datastructures::ItemVisitorRefMut) {
for c in &self.components { for c in &self.components {
c.visit_children_item(-1, visitor.borrow_mut()); c.as_ref().visit_children_item(-1, visitor.borrow_mut());
} }
} }
} }

View file

@ -110,11 +110,11 @@ fn main() {
let mut app = Hello::default(); let mut app = Hello::default();
app.plus_clicked.set_handler(|context, ()| { app.plus_clicked.set_handler(|context, ()| {
let app = context.component.downcast::<Hello>().unwrap(); let app = context.get_component::<Hello>().unwrap();
app.counter.set(app.counter.get(context) + 1); app.counter.set(app.counter.get(context) + 1);
}); });
app.minus_clicked.set_handler(|context, ()| { app.minus_clicked.set_handler(|context, ()| {
let app = context.component.downcast::<Hello>().unwrap(); let app = context.get_component::<Hello>().unwrap();
app.counter.set(app.counter.get(context) - 1); app.counter.set(app.counter.get(context) - 1);
}); });

View file

@ -6,11 +6,11 @@ fn main() {
let mut app = Hello::default(); let mut app = Hello::default();
app.plus_clicked.set_handler(|context, ()| { app.plus_clicked.set_handler(|context, ()| {
let app = context.component.downcast::<Hello>().unwrap(); let app = context.get_component::<Hello>().unwrap();
app.counter.set(app.counter.get(context) + 1); app.counter.set(app.counter.get(context) + 1);
}); });
app.minus_clicked.set_handler(|context, ()| { app.minus_clicked.set_handler(|context, ()| {
let app = context.component.downcast::<Hello>().unwrap(); let app = context.get_component::<Hello>().unwrap();
app.counter.set(app.counter.get(context) - 1); app.counter.set(app.counter.get(context) - 1);
}); });
app.run(); app.run();

View file

@ -26,6 +26,23 @@ fn match_generic_type(ty: &Type, container: &str, containee: &Ident) -> bool {
false false
} }
/// Returns Some(type) if the type is `Pin<type>`
fn is_pin<'a>(ty: &'a Type) -> Option<&'a Type> {
if let Type::Path(pat) = ty {
if let Some(seg) = pat.path.segments.last() {
if seg.ident != "Pin" {
return None;
}
if let PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(GenericArgument::Type(t)) = args.args.last() {
return Some(t);
}
}
}
}
None
}
/** /**
This macro need to be applied to a VTable structure This macro need to be applied to a VTable structure
@ -56,6 +73,8 @@ For fields whose type is a function:
- If a field is called `drop` it is understood that this is the destructor for a VBox - If a field is called `drop` it is understood that this is the destructor for a VBox
- If the first argument of the function is `VRef<MyVTable>` or `VRefMut<MyVTable>` this is - If the first argument of the function is `VRef<MyVTable>` or `VRefMut<MyVTable>` this is
understood as a `&self` or `&mut self` argument in the trait. understood as a `&self` or `&mut self` argument in the trait.
- Similarily, if it is a `Pin<VRef<MyVTable>>` or `Pin<VRefMut<MyVTable>>`, self is mapped
to `Pin<Self>` or `Pin<&Self>`
For the other fields For the other fields
- They are considered assotiated const of the MyTraitConsts - They are considered assotiated const of the MyTraitConsts
@ -239,10 +258,15 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
} }
} }
let (is_pin, self_ty) = match is_pin(&param.ty) {
Some(t) => (true, t),
None => (false, &param.ty),
};
// check for self // check for self
if let (true, mutability) = if match_generic_type(&param.ty, "VRef", &vtable_name) { if let (true, mutability) = if match_generic_type(self_ty, "VRef", &vtable_name) {
(true, None) (true, None)
} else if match_generic_type(&param.ty, "VRefMut", &vtable_name) { } else if match_generic_type(self_ty, "VRefMut", &vtable_name) {
(true, Some(Default::default())) (true, Some(Default::default()))
} else { } else {
(false, None) (false, None)
@ -252,19 +276,36 @@ pub fn vtable(_attr: TokenStream, item: TokenStream) -> TokenStream {
.to_compile_error() .to_compile_error()
.into(); .into();
} }
let const_or_mut = mutability.map_or_else(|| quote!(const), |x| quote!(#x));
has_self = true;
if !is_pin {
sig.inputs.push(FnArg::Receiver(Receiver { sig.inputs.push(FnArg::Receiver(Receiver {
attrs: param.attrs.clone(), attrs: param.attrs.clone(),
reference: Some(Default::default()), reference: Some(Default::default()),
mutability, mutability,
self_token: Default::default(), self_token: Default::default(),
})); }));
let self_ty = &param.ty;
let const_or_mut = mutability.map_or_else(|| quote!(const), |x| quote!(#x));
call_code = call_code =
Some(quote!(#call_code <#self_ty>::from_raw(self.vtable, self.ptr),)); Some(quote!(#call_code <#self_ty>::from_raw(self.vtable, self.ptr),));
self_call = self_call =
Some(quote!(&#mutability (*(#arg_name.as_ptr() as *#const_or_mut T)),)); Some(quote!(&#mutability (*(#arg_name.as_ptr() as *#const_or_mut T)),));
has_self = true; } else {
// Pinned
sig.inputs.push(FnArg::Typed(PatType {
attrs: param.attrs.clone(),
pat: Box::new(parse2(quote!(self)).unwrap()),
colon_token: Default::default(),
ty: parse2(quote!(core::pin::Pin<& #mutability Self>)).unwrap(),
}));
call_code = Some(
quote!(#call_code core::pin::Pin::new_unchecked(<#self_ty>::from_raw(self.vtable, self.ptr)),),
);
self_call = Some(
quote!(core::pin::Pin::new_unchecked(&#mutability (*(#arg_name.as_ptr() as *#const_or_mut T))),),
);
}
continue; continue;
} }
sig.inputs.push(typed_arg); sig.inputs.push(typed_arg);

View file

@ -57,7 +57,7 @@ that `cbindgen` can see the actual vtable.
pub use const_field_offset::FieldOffset; pub use const_field_offset::FieldOffset;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::{Deref, DerefMut, Drop}; use core::ops::{Deref, DerefMut, Drop};
use core::ptr::NonNull; use core::{pin::Pin, ptr::NonNull};
#[doc(inline)] #[doc(inline)]
pub use vtable_macro::*; pub use vtable_macro::*;
@ -230,6 +230,20 @@ impl<'a, T: ?Sized + VTableMeta> VRef<'a, T> {
} }
} }
/// Create a new Pin<VRef<_>> from a pinned reference. This is similar to `VRef::new`
pub fn new_pin<X: HasStaticVTable<T>>(value: core::pin::Pin<&'a X>) -> Pin<Self> {
// Since Value is pinned, this means it is safe to construct a Pin
unsafe {
Pin::new_unchecked(Self {
inner: Inner {
vtable: X::static_vtable() as *const T::VTable as *const u8,
ptr: value.get_ref() as *const X as *const u8,
},
phantom: PhantomData,
})
}
}
unsafe fn from_inner(inner: Inner) -> Self { unsafe fn from_inner(inner: Inner) -> Self {
Self { inner, phantom: PhantomData } Self { inner, phantom: PhantomData }
} }
@ -251,6 +265,17 @@ impl<'a, T: ?Sized + VTableMeta> VRef<'a, T> {
None None
} }
} }
/// Return to a reference of the given type if the type is actually matching
pub fn downcast_pin<X: HasStaticVTable<T>>(this: Pin<Self>) -> Option<Pin<&'a X>> {
let inner = unsafe { Pin::into_inner_unchecked(this).inner };
if inner.vtable == X::static_vtable() as *const _ as *const u8 {
// Safety: We just checked that the vtable fits
unsafe { Some(Pin::new_unchecked(&*(inner.ptr as *const X))) }
} else {
None
}
}
} }
/// `VRefMut<'a MyTraitVTable>` can be thought as a `&'a mut dyn MyTrait` /// `VRefMut<'a MyTraitVTable>` can be thought as a `&'a mut dyn MyTrait`

View file

@ -1,3 +1,4 @@
use core::pin::Pin;
use vtable::*; use vtable::*;
#[vtable] #[vtable]
/// This is the actual doc /// This is the actual doc
@ -157,3 +158,32 @@ fn test3() {
new_vref!(let mut re_mut : VRefMut<XxxVTable> for Xxx = &mut p); new_vref!(let mut re_mut : VRefMut<XxxVTable> for Xxx = &mut p);
assert_eq!(re_mut.ret_int(), 55); assert_eq!(re_mut.ret_int(), 55);
} }
#[test]
fn pin() {
#[vtable]
struct PinnedVTable {
my_func: fn(core::pin::Pin<VRef<PinnedVTable>>, u32) -> u32,
my_func2: fn(std::pin::Pin<VRef<'_, PinnedVTable>>) -> u32,
my_func3: fn(Pin<VRefMut<PinnedVTable>>, u32) -> u32,
}
struct P(String, std::marker::PhantomPinned);
impl Pinned for P {
fn my_func(self: Pin<&Self>, p: u32) -> u32 {
self.0.len() as u32 + p
}
fn my_func2(self: Pin<&Self>) -> u32 {
self.0.len() as u32
}
fn my_func3(self: Pin<&mut Self>, _p: u32) -> u32 {
self.0.len() as u32
}
}
PinnedVTable_static!(static PVT for P);
let b = Box::pin(P("hello".to_owned(), std::marker::PhantomPinned));
let r = VRef::new_pin(b.as_ref());
assert_eq!(r.as_ref().my_func(44), 44 + 5);
assert_eq!(r.as_ref().my_func2(), 5);
}

View file

@ -463,10 +463,7 @@ fn generate_component(file: &mut File, component: &Rc<Component>, diag: &mut Dia
declarations.push(Declaration::Var(Var { declarations.push(Declaration::Var(Var {
ty: "const sixtyfps::ComponentVTable".to_owned(), ty: "const sixtyfps::ComponentVTable".to_owned(),
name: format!("{}::component_type", component_id), name: format!("{}::component_type", component_id),
init: Some( init: Some("{ visit_children, nullptr, compute_layout }".to_owned()),
"{ nullptr, sixtyfps::dummy_destory, visit_children, nullptr, compute_layout }"
.to_owned(),
),
})); }));
declarations.append(&mut file.declarations); declarations.append(&mut file.declarations);

View file

@ -51,9 +51,9 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
property_and_signal_accessors.push( property_and_signal_accessors.push(
quote!( quote!(
#[allow(dead_code)] #[allow(dead_code)]
fn #emitter_ident(&self) { fn #emitter_ident(self: ::core::pin::Pin<&Self>) {
let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component( let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component(
sixtyfps::re_exports::ComponentRef::new(self) sixtyfps::re_exports::ComponentRef::new_pin(self)
); );
self.#prop_ident.emit(&eval_context, ()) self.#prop_ident.emit(&eval_context, ())
} }
@ -78,9 +78,9 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
property_and_signal_accessors.push( property_and_signal_accessors.push(
quote!( quote!(
#[allow(dead_code)] #[allow(dead_code)]
fn #getter_ident(&self) -> #rust_property_type { fn #getter_ident(self: ::core::pin::Pin<&Self>) -> #rust_property_type {
let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component( let eval_context = sixtyfps::re_exports::EvaluationContext::for_root_component(
sixtyfps::re_exports::ComponentRef::new(self) sixtyfps::re_exports::ComponentRef::new_pin(self)
); );
self.#prop_ident.get(&eval_context) self.#prop_ident.get(&eval_context)
} }
@ -186,7 +186,7 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
if matches!(item.lookup_property(k.as_str()), Type::Signal) { if matches!(item.lookup_property(k.as_str()), Type::Signal) {
init.push(quote!( init.push(quote!(
self_.#rust_property.set_handler(|context, ()| { self_.#rust_property.set_handler(|context, ()| {
let _self = context.component.downcast::<#component_id>().unwrap(); let _self = context.get_component::<#component_id>().unwrap();
#tokens_for_expression; #tokens_for_expression;
}); });
)); ));
@ -198,7 +198,7 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
} else { } else {
init.push(quote!( init.push(quote!(
self_.#rust_property.set_binding(|context| { self_.#rust_property.set_binding(|context| {
let _self = context.component.downcast::<#component_id>().unwrap(); let _self = context.get_component::<#component_id>().unwrap();
(#tokens_for_expression) as _ (#tokens_for_expression) as _
}); });
)); ));
@ -239,10 +239,12 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
); );
} else { } else {
property_and_signal_accessors.push(quote! { property_and_signal_accessors.push(quote! {
fn run(&self) { fn run(self) {
use sixtyfps::re_exports::*; use sixtyfps::re_exports::*;
let window = sixtyfps::create_window(); let window = sixtyfps::create_window();
window.run(VRef::new(self)); let self_pined = self;
pin_mut!(self_pined);
window.run(VRef::new_pin(self_pined.as_ref()));
} }
}); });
}; };
@ -277,10 +279,10 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
} }
impl sixtyfps::re_exports::Component for #component_id { impl sixtyfps::re_exports::Component for #component_id {
fn visit_children_item(&self, index: isize, visitor: sixtyfps::re_exports::ItemVisitorRefMut) { fn visit_children_item(self: core::pin::Pin<&Self>, index: isize, visitor: sixtyfps::re_exports::ItemVisitorRefMut) {
use sixtyfps::re_exports::*; use sixtyfps::re_exports::*;
let tree = &[#(#item_tree_array),*]; let tree = &[#(#item_tree_array),*];
sixtyfps::re_exports::visit_item_tree(self, VRef::new(self), tree, index, visitor, visit_dynamic); sixtyfps::re_exports::visit_item_tree(self.get_ref(), VRef::new_pin(self), tree, index, visitor, visit_dynamic);
#[allow(unused)] #[allow(unused)]
fn visit_dynamic(base: &#component_id, visitor: ItemVisitorRefMut, dyn_index: usize) { fn visit_dynamic(base: &#component_id, visitor: ItemVisitorRefMut, dyn_index: usize) {
match dyn_index { match dyn_index {
@ -289,9 +291,6 @@ pub fn generate(component: &Rc<Component>, diag: &mut Diagnostics) -> Option<Tok
} }
} }
} }
fn create() -> Self {
Default::default()
}
#layouts #layouts
} }
@ -323,7 +322,7 @@ fn component_id(component: &Component) -> proc_macro2::Ident {
/// to be used like: /// to be used like:
/// ```ignore /// ```ignore
/// let (access, context) = access_member(...) /// let (access, context) = access_member(...)
/// quote!(context.#access.get(#context)) /// quote!(#access.get(#context))
/// ``` /// ```
fn access_member( fn access_member(
element: &ElementRc, element: &ElementRc,
@ -337,7 +336,7 @@ fn access_member(
let component_id = component_id(&enclosing_component); let component_id = component_id(&enclosing_component);
if Rc::ptr_eq(component, &enclosing_component) { if Rc::ptr_eq(component, &enclosing_component) {
let name_ident = quote::format_ident!("{}", name); let name_ident = quote::format_ident!("{}", name);
let comp = quote!(component.downcast::<#component_id>().unwrap()); let comp = quote!(#context.get_component::<#component_id>().unwrap());
if e.property_declarations.contains_key(name) { if e.property_declarations.contains_key(name) {
(quote!(#comp.#name_ident), context) (quote!(#comp.#name_ident), context)
} else { } else {
@ -345,13 +344,7 @@ fn access_member(
(quote!(#comp.#elem_ident.#name_ident), context) (quote!(#comp.#elem_ident.#name_ident), context)
} }
} else { } else {
let (access, new_context) = access_member( access_member(element, name, &enclosing_component, quote!(#context.parent_context.unwrap()))
element,
name,
&enclosing_component,
quote!(#context.parent_context.unwrap()),
);
(quote!(parent_context.unwrap().#access), new_context)
} }
} }
@ -377,7 +370,7 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
component, component,
quote!(context), quote!(context),
); );
quote!(context.#access.get(#context)) quote!(#access.get(#context))
} }
Expression::RepeaterIndexReference { element } => { Expression::RepeaterIndexReference { element } => {
if element.upgrade().unwrap().borrow().base_type == Type::Component(component.clone()) { if element.upgrade().unwrap().borrow().base_type == Type::Component(component.clone()) {
@ -416,7 +409,7 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
component, component,
quote!(context), quote!(context),
); );
quote!(context.#access.emit(#context, ())) quote!(#access.emit(#context, ()))
} }
Expression::FunctionCall { function } => { Expression::FunctionCall { function } => {
if matches!(function.ty(), Type::Signal) { if matches!(function.ty(), Type::Signal) {
@ -436,7 +429,7 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
); );
let rhs = compile_expression(&*rhs, &component); let rhs = compile_expression(&*rhs, &component);
let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone);
quote!( context.#lhs.set(context.#lhs.get(#context) #op &((#rhs) as _) )) quote!( #lhs.set(#lhs.get(#context) #op &((#rhs) as _) ))
} }
_ => panic!("typechecking should make sure this was a PropertyReference"), _ => panic!("typechecking should make sure this was a PropertyReference"),
}, },
@ -547,10 +540,10 @@ fn compute_layout(component: &Component) -> TokenStream {
} }
quote! { quote! {
fn layout_info(&self) -> sixtyfps::re_exports::LayoutInfo { fn layout_info(self: core::pin::Pin<&Self>) -> sixtyfps::re_exports::LayoutInfo {
todo!("Implement in rust.rs") todo!("Implement in rust.rs")
} }
fn compute_layout(&self, eval_context: &sixtyfps::re_exports::EvaluationContext) { fn compute_layout(self: core::pin::Pin<&Self>, eval_context: &sixtyfps::re_exports::EvaluationContext) {
#![allow(unused)] #![allow(unused)]
use sixtyfps::re_exports::*; use sixtyfps::re_exports::*;
let dummy = Property::<f32>::default(); let dummy = Property::<f32>::default();

View file

@ -2,6 +2,7 @@
use super::slice::Slice; use super::slice::Slice;
use crate::EvaluationContext; use crate::EvaluationContext;
use core::pin::Pin;
use std::cell::Cell; use std::cell::Cell;
use vtable::*; use vtable::*;
@ -34,23 +35,21 @@ struct Point {
#[vtable] #[vtable]
#[repr(C)] #[repr(C)]
pub struct ComponentVTable { pub struct ComponentVTable {
/// Allocate an instance of this component
pub create: extern "C" fn(&ComponentVTable) -> VBox<ComponentVTable>,
/// Destruct this component.
pub drop: extern "C" fn(VRefMut<ComponentVTable>),
/// Visit the children of the item at index `index`. /// Visit the children of the item at index `index`.
/// Note that the root item is at index 0, so passing 0 would visit the item under root (the children of root). /// Note that the root item is at index 0, so passing 0 would visit the item under root (the children of root).
/// If you want to visit the root item, you need to pass -1 as an index /// If you want to visit the root item, you need to pass -1 as an index
pub visit_children_item: pub visit_children_item: extern "C" fn(
extern "C" fn(VRef<ComponentVTable>, index: isize, visitor: VRefMut<ItemVisitorVTable>), core::pin::Pin<VRef<ComponentVTable>>,
index: isize,
visitor: VRefMut<ItemVisitorVTable>,
),
/// Returns the layout info for this component /// Returns the layout info for this component
pub layout_info: extern "C" fn(VRef<ComponentVTable>) -> LayoutInfo, pub layout_info: extern "C" fn(core::pin::Pin<VRef<ComponentVTable>>) -> LayoutInfo,
/// Will compute the layout of /// Will compute the layout of
pub compute_layout: extern "C" fn(VRef<ComponentVTable>, eval_context: &EvaluationContext), pub compute_layout:
extern "C" fn(core::pin::Pin<VRef<ComponentVTable>>, eval_context: &EvaluationContext),
} }
/// This structure must be present in items that are Rendered and contains information. /// This structure must be present in items that are Rendered and contains information.
@ -279,7 +278,7 @@ impl ComponentWindow {
Self(window_impl) Self(window_impl)
} }
/// Spins an event loop and renders the items of the provided component in this window. /// Spins an event loop and renders the items of the provided component in this window.
pub fn run(&self, component: VRef<ComponentVTable>) { pub fn run(&self, component: Pin<VRef<ComponentVTable>>) {
let event_loop = crate::eventloop::EventLoop::new(); let event_loop = crate::eventloop::EventLoop::new();
self.0.clone().map_window(&event_loop); self.0.clone().map_window(&event_loop);
@ -309,7 +308,7 @@ pub unsafe extern "C" fn sixtyfps_component_window_drop(handle: *mut ComponentWi
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sixtyfps_component_window_run( pub unsafe extern "C" fn sixtyfps_component_window_run(
handle: *mut ComponentWindowOpaque, handle: *mut ComponentWindowOpaque,
component: vtable::VRef<ComponentVTable>, component: Pin<VRef<ComponentVTable>>,
) { ) {
let window = &*(handle as *const ComponentWindow); let window = &*(handle as *const ComponentWindow);
window.run(component); window.run(component);
@ -327,7 +326,7 @@ pub struct ItemVisitorVTable {
/// and `item` is a reference to the item itself /// and `item` is a reference to the item itself
visit_item: fn( visit_item: fn(
VRefMut<ItemVisitorVTable>, VRefMut<ItemVisitorVTable>,
component: VRef<ComponentVTable>, component: Pin<VRef<ComponentVTable>>,
index: isize, index: isize,
item: VRef<ItemVTable>, item: VRef<ItemVTable>,
), ),
@ -335,10 +334,10 @@ pub struct ItemVisitorVTable {
drop: fn(VRefMut<ItemVisitorVTable>), drop: fn(VRefMut<ItemVisitorVTable>),
} }
impl<T: FnMut(VRef<ComponentVTable>, isize, VRef<ItemVTable>)> ItemVisitor for T { impl<T: FnMut(crate::ComponentRefPin, isize, VRef<ItemVTable>)> ItemVisitor for T {
fn visit_item( fn visit_item(
&mut self, &mut self,
component: VRef<ComponentVTable>, component: crate::ComponentRefPin,
index: isize, index: isize,
item: VRef<ItemVTable>, item: VRef<ItemVTable>,
) { ) {
@ -351,7 +350,7 @@ impl<T: FnMut(VRef<ComponentVTable>, isize, VRef<ItemVTable>)> ItemVisitor for T
/// Safety: Assume a correct implementation of the item_tree array /// Safety: Assume a correct implementation of the item_tree array
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn sixtyfps_visit_item_tree( pub unsafe extern "C" fn sixtyfps_visit_item_tree(
component: VRef<ComponentVTable>, component: Pin<VRef<ComponentVTable>>,
item_tree: Slice<ItemTreeNode<u8>>, item_tree: Slice<ItemTreeNode<u8>>,
index: isize, index: isize,
visitor: VRefMut<ItemVisitorVTable>, visitor: VRefMut<ItemVisitorVTable>,

View file

@ -5,7 +5,7 @@
thin dst container, and intrusive linked list thin dst container, and intrusive linked list
*/ */
use crate::abi::datastructures::ComponentRef; use crate::ComponentRefPin;
use core::cell::*; use core::cell::*;
use core::ops::DerefMut; use core::ops::DerefMut;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
@ -101,7 +101,7 @@ impl<T> PropertyNotify for RefCell<PropertyImpl<T>> {
#[repr(C)] #[repr(C)]
pub struct EvaluationContext<'a> { pub struct EvaluationContext<'a> {
/// The component which contains the Property or the Signal /// The component which contains the Property or the Signal
pub component: vtable::VRef<'a, crate::abi::datastructures::ComponentVTable>, pub component: core::pin::Pin<vtable::VRef<'a, crate::abi::datastructures::ComponentVTable>>,
/// The context of the parent component /// The context of the parent component
pub parent_context: Option<&'a EvaluationContext<'a>>, pub parent_context: Option<&'a EvaluationContext<'a>>,
@ -112,15 +112,24 @@ impl<'a> EvaluationContext<'a> {
/// ///
/// The component need to be a root component, otherwise fetching properties /// The component need to be a root component, otherwise fetching properties
/// might panic. /// might panic.
pub fn for_root_component(component: ComponentRef<'a>) -> Self { pub fn for_root_component(component: ComponentRefPin<'a>) -> Self {
Self { component, parent_context: None } Self { component, parent_context: None }
} }
/// Create a context for a child component of a component within the current /// Create a context for a child component of a component within the current
/// context. /// context.
pub fn child_context(&'a self, child: ComponentRef<'a>) -> Self { pub fn child_context(&'a self, child: ComponentRefPin<'a>) -> Self {
Self { component: child, parent_context: Some(self) } Self { component: child, parent_context: Some(self) }
} }
/// Attempt to cast the component to the given type
pub fn get_component<
T: vtable::HasStaticVTable<crate::abi::datastructures::ComponentVTable>,
>(
&'a self,
) -> Option<core::pin::Pin<&'a T>> {
vtable::VRef::downcast_pin(self.component)
}
} }
type PropertyHandle<T> = Rc<RefCell<PropertyImpl<T>>>; type PropertyHandle<T> = Rc<RefCell<PropertyImpl<T>>>;
@ -275,7 +284,10 @@ fn properties_simple_test() {
area: Property<i32>, area: Property<i32>,
} }
let dummy_eval_context = EvaluationContext::for_root_component(unsafe { let dummy_eval_context = EvaluationContext::for_root_component(unsafe {
vtable::VRef::from_raw(core::ptr::NonNull::dangling(), core::ptr::NonNull::dangling()) core::pin::Pin::new_unchecked(vtable::VRef::from_raw(
core::ptr::NonNull::dangling(),
core::ptr::NonNull::dangling(),
))
}); });
let compo = Rc::new(Component::default()); let compo = Rc::new(Component::default());
let w = Rc::downgrade(&compo); let w = Rc::downgrade(&compo);
@ -596,10 +608,10 @@ mod test {
fn properties_test_animation_triggered_by_set() { fn properties_test_animation_triggered_by_set() {
let dummy_eval_context = EvaluationContext { let dummy_eval_context = EvaluationContext {
component: unsafe { component: unsafe {
vtable::VRef::from_raw( core::pin::Pin::new_unchecked(vtable::VRef::from_raw(
core::ptr::NonNull::dangling(), core::ptr::NonNull::dangling(),
core::ptr::NonNull::dangling(), core::ptr::NonNull::dangling(),
) ))
}, },
parent_context: None, parent_context: None,
}; };
@ -638,10 +650,10 @@ mod test {
fn properties_test_animation_triggered_by_binding() { fn properties_test_animation_triggered_by_binding() {
let dummy_eval_context = EvaluationContext { let dummy_eval_context = EvaluationContext {
component: unsafe { component: unsafe {
vtable::VRef::from_raw( core::pin::Pin::new_unchecked(vtable::VRef::from_raw(
core::ptr::NonNull::dangling(), core::ptr::NonNull::dangling(),
core::ptr::NonNull::dangling(), core::ptr::NonNull::dangling(),
) ))
}, },
parent_context: None, parent_context: None,
}; };

View file

@ -38,22 +38,23 @@ impl<Arg> Signal<Arg> {
#[test] #[test]
fn signal_simple_test() { fn signal_simple_test() {
use std::pin::Pin;
#[derive(Default)] #[derive(Default)]
struct Component { struct Component {
pressed: core::cell::Cell<bool>, pressed: core::cell::Cell<bool>,
clicked: Signal<()>, clicked: Signal<()>,
} }
impl crate::abi::datastructures::Component for Component { impl crate::abi::datastructures::Component for Component {
fn create() -> Self { fn visit_children_item(
Default::default() self: Pin<&Self>,
_: isize,
_: crate::abi::datastructures::ItemVisitorRefMut,
) {
} }
fn visit_children_item(&self, _: isize, _: crate::abi::datastructures::ItemVisitorRefMut) {} fn layout_info(self: Pin<&Self>) -> crate::abi::datastructures::LayoutInfo {
fn layout_info(&self) -> crate::abi::datastructures::LayoutInfo {
unimplemented!()
}
fn compute_layout(&self, _: &crate::EvaluationContext) {
unimplemented!() unimplemented!()
} }
fn compute_layout(self: Pin<&Self>, _: &crate::EvaluationContext) {}
} }
use crate::abi::datastructures::ComponentVTable; use crate::abi::datastructures::ComponentVTable;
let mut c = Component::default(); let mut c = Component::default();
@ -62,10 +63,10 @@ fn signal_simple_test() {
}); });
let vtable = ComponentVTable::new::<Component>(); let vtable = ComponentVTable::new::<Component>();
let ctx = super::properties::EvaluationContext::for_root_component(unsafe { let ctx = super::properties::EvaluationContext::for_root_component(unsafe {
vtable::VRef::from_raw( Pin::new_unchecked(vtable::VRef::from_raw(
core::ptr::NonNull::from(&vtable), core::ptr::NonNull::from(&vtable),
core::ptr::NonNull::from(&c).cast(), core::ptr::NonNull::from(&c).cast(),
) ))
}); });
c.clicked.emit(&ctx, ()); c.clicked.emit(&ctx, ());
assert_eq!(c.pressed.get(), true); assert_eq!(c.pressed.get(), true);

View file

@ -5,12 +5,12 @@ use std::rc::{Rc, Weak};
use winit::platform::desktop::EventLoopExtDesktop; use winit::platform::desktop::EventLoopExtDesktop;
pub trait GenericWindow { pub trait GenericWindow {
fn draw(&self, component: vtable::VRef<crate::abi::datastructures::ComponentVTable>); fn draw(&self, component: core::pin::Pin<crate::abi::datastructures::ComponentRef>);
fn process_mouse_input( fn process_mouse_input(
&self, &self,
pos: winit::dpi::PhysicalPosition<f64>, pos: winit::dpi::PhysicalPosition<f64>,
state: winit::event::ElementState, state: winit::event::ElementState,
component: vtable::VRef<crate::abi::datastructures::ComponentVTable>, component: core::pin::Pin<crate::abi::datastructures::ComponentRef>,
); );
fn window_handle(&self) -> std::cell::Ref<'_, winit::window::Window>; fn window_handle(&self) -> std::cell::Ref<'_, winit::window::Window>;
fn map_window(self: Rc<Self>, event_loop: &EventLoop); fn map_window(self: Rc<Self>, event_loop: &EventLoop);
@ -42,7 +42,7 @@ impl EventLoop {
Self { winit_loop: winit::event_loop::EventLoop::new() } Self { winit_loop: winit::event_loop::EventLoop::new() }
} }
#[allow(unused_mut)] // mut need changes for wasm #[allow(unused_mut)] // mut need changes for wasm
pub fn run(mut self, component: vtable::VRef<crate::abi::datastructures::ComponentVTable>) { pub fn run(mut self, component: core::pin::Pin<crate::abi::datastructures::ComponentRef>) {
use winit::event::Event; use winit::event::Event;
use winit::event_loop::{ControlFlow, EventLoopWindowTarget}; use winit::event_loop::{ControlFlow, EventLoopWindowTarget};

View file

@ -143,9 +143,9 @@ impl<Backend: GraphicsBackend> Drop for GraphicsWindow<Backend> {
impl<Backend: GraphicsBackend> crate::eventloop::GenericWindow impl<Backend: GraphicsBackend> crate::eventloop::GenericWindow
for RefCell<GraphicsWindow<Backend>> for RefCell<GraphicsWindow<Backend>>
{ {
fn draw(&self, component: vtable::VRef<crate::abi::datastructures::ComponentVTable>) { fn draw(&self, component: crate::ComponentRefPin) {
// FIXME: we should do that only if some property change // FIXME: we should do that only if some property change
component.compute_layout(&crate::EvaluationContext::for_root_component(component)); component.as_ref().compute_layout(&crate::EvaluationContext::for_root_component(component));
let mut this = self.borrow_mut(); let mut this = self.borrow_mut();
@ -189,7 +189,7 @@ impl<Backend: GraphicsBackend> crate::eventloop::GenericWindow
&self, &self,
pos: winit::dpi::PhysicalPosition<f64>, pos: winit::dpi::PhysicalPosition<f64>,
state: winit::event::ElementState, state: winit::event::ElementState,
component: vtable::VRef<crate::abi::datastructures::ComponentVTable>, component: crate::ComponentRefPin,
) { ) {
crate::input::process_mouse_event( crate::input::process_mouse_event(
component, component,

View file

@ -3,10 +3,11 @@
TODO: Keyboard events TODO: Keyboard events
*/ */
use crate::abi::datastructures::{ComponentRef, MouseEvent}; use crate::abi::datastructures::MouseEvent;
use crate::ComponentRefPin;
use euclid::default::Vector2D; use euclid::default::Vector2D;
pub fn process_mouse_event(component: ComponentRef<'_>, event: MouseEvent) { pub fn process_mouse_event(component: ComponentRefPin, event: MouseEvent) {
let offset = Vector2D::new(0., 0.); let offset = Vector2D::new(0., 0.);
crate::item_tree::visit_items( crate::item_tree::visit_items(

View file

@ -38,7 +38,7 @@ pub(crate) fn update_item_rendering_data<Backend: GraphicsBackend>(
} }
pub(crate) fn render_component_items<Backend: GraphicsBackend>( pub(crate) fn render_component_items<Backend: GraphicsBackend>(
component: vtable::VRef<'_, crate::abi::datastructures::ComponentVTable>, component: crate::ComponentRefPin,
frame: &mut Backend::Frame, frame: &mut Backend::Frame,
rendering_cache: &RenderingCache<Backend>, rendering_cache: &RenderingCache<Backend>,
) { ) {

View file

@ -1,13 +1,12 @@
use crate::abi::datastructures::{ use crate::abi::datastructures::{ItemRef, ItemTreeNode, ItemVisitor, ItemVisitorVTable};
ComponentRef, ItemRef, ItemTreeNode, ItemVisitor, ItemVisitorVTable, use crate::ComponentRefPin;
};
use crate::EvaluationContext; use crate::EvaluationContext;
/// Visit each items recursively /// Visit each items recursively
/// ///
/// The state parametter returned by the visitor is passed to each children. /// The state parametter returned by the visitor is passed to each children.
pub fn visit_items<State>( pub fn visit_items<State>(
component: ComponentRef, component: ComponentRefPin,
mut visitor: impl FnMut(&EvaluationContext, ItemRef, &State) -> State, mut visitor: impl FnMut(&EvaluationContext, ItemRef, &State) -> State,
state: State, state: State,
) { ) {
@ -21,7 +20,7 @@ fn visit_internal<State>(
index: isize, index: isize,
state: &State, state: &State,
) { ) {
let mut actual_visitor = |component: ComponentRef, index: isize, item: ItemRef| { let mut actual_visitor = |component: ComponentRefPin, index: isize, item: ItemRef| {
if component.as_ptr() == context.component.as_ptr() { if component.as_ptr() == context.component.as_ptr() {
let s = visitor(context, item, state); let s = visitor(context, item, state);
visit_internal(context, visitor, index, &s); visit_internal(context, visitor, index, &s);
@ -32,7 +31,7 @@ fn visit_internal<State>(
} }
}; };
vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor); vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
context.component.visit_children_item(index, actual_visitor); context.component.as_ref().visit_children_item(index, actual_visitor);
} }
/// Visit the children within an array of ItemTreeNode /// Visit the children within an array of ItemTreeNode
@ -45,7 +44,7 @@ fn visit_internal<State>(
/// Possibly we should generate code that directly call the visitor instead /// Possibly we should generate code that directly call the visitor instead
pub fn visit_item_tree<Base>( pub fn visit_item_tree<Base>(
base: &Base, base: &Base,
component: ComponentRef, component: ComponentRefPin,
item_tree: &[ItemTreeNode<Base>], item_tree: &[ItemTreeNode<Base>],
index: isize, index: isize,
mut visitor: vtable::VRefMut<ItemVisitorVTable>, mut visitor: vtable::VRefMut<ItemVisitorVTable>,

View file

@ -45,5 +45,8 @@ pub use abi::properties::{EvaluationContext, Property};
#[doc(inline)] #[doc(inline)]
pub use abi::signals::Signal; pub use abi::signals::Signal;
/// Type alias to the commonly use `Pin<VRef<ComponentVTable>>>`
pub type ComponentRefPin<'a> = core::pin::Pin<abi::datastructures::ComponentRef<'a>>;
pub mod eventloop; pub mod eventloop;
mod item_rendering; mod item_rendering;

View file

@ -2,19 +2,50 @@ use crate::{dynamic_type, eval};
use core::convert::TryInto; use core::convert::TryInto;
use core::ptr::NonNull; use core::ptr::NonNull;
use dynamic_type::Instance; use dynamic_type::{Instance, InstanceBox};
use object_tree::ElementRc; use object_tree::ElementRc;
use sixtyfps_compilerlib::typeregister::Type; use sixtyfps_compilerlib::typeregister::Type;
use sixtyfps_compilerlib::*; use sixtyfps_compilerlib::*;
use sixtyfps_corelib::abi::datastructures::{ use sixtyfps_corelib::abi::datastructures::{
ComponentBox, ComponentRef, ComponentVTable, ItemTreeNode, ItemVTable, ItemVisitorRefMut, ComponentVTable, ItemTreeNode, ItemVTable, ItemVisitorRefMut, Resource,
Resource,
}; };
use sixtyfps_corelib::abi::slice::Slice; use sixtyfps_corelib::abi::slice::Slice;
use sixtyfps_corelib::rtti::PropertyInfo; use sixtyfps_corelib::rtti::PropertyInfo;
use sixtyfps_corelib::ComponentRefPin;
use sixtyfps_corelib::{rtti, EvaluationContext, Property, SharedString, Signal}; use sixtyfps_corelib::{rtti, EvaluationContext, Property, SharedString, Signal};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::{pin::Pin, rc::Rc};
pub struct ComponentBox {
instance: InstanceBox,
component_type: Rc<ComponentDescription>,
}
impl ComponentBox {
/// Borrow this component as a `Pin<ComponentRef>`
pub fn borrow(&self) -> ComponentRefPin {
unsafe {
Pin::new_unchecked(vtable::VRef::from_raw(
NonNull::from(&self.component_type.ct).cast(),
self.instance.as_ptr().cast(),
))
}
}
/// Borrow this component as a `Pin<ComponentRefMut>`
pub fn borrow_mut(&mut self) -> Pin<sixtyfps_corelib::abi::datastructures::ComponentRefMut> {
unsafe {
Pin::new_unchecked(vtable::VRefMut::from_raw(
NonNull::from(&self.component_type.ct).cast(),
self.instance.as_ptr().cast(),
))
}
}
pub fn description(&self) -> Rc<ComponentDescription> {
return self.component_type.clone();
}
}
pub(crate) struct ItemWithinComponent { pub(crate) struct ItemWithinComponent {
offset: usize, offset: usize,
@ -47,18 +78,6 @@ pub(crate) struct RepeaterWithinComponent {
type RepeaterVec = Vec<ComponentBox>; type RepeaterVec = Vec<ComponentBox>;
/// A wrapper around a pointer to a Component, and its description.
///
/// Safety: the `mem` member must be a component instantiated with the description of
/// this document.
///
/// It is similar to ComponentBox, but instead of a ComponentVTable pointer, we
/// have direct access to the ComponentDescription, so we do not have to cast
pub struct ComponentImpl {
pub(crate) mem: *mut u8,
pub(crate) component_type: Rc<ComponentDescription>,
}
/// 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.
@ -81,7 +100,7 @@ pub struct ComponentDescription {
} }
unsafe extern "C" fn visit_children_item( unsafe extern "C" fn visit_children_item(
component: ComponentRef, component: ComponentRefPin,
index: isize, index: isize,
v: ItemVisitorRefMut, v: ItemVisitorRefMut,
) { ) {
@ -98,7 +117,7 @@ unsafe extern "C" fn visit_children_item(
let rep_in_comp = &component_type.repeater[index]; let rep_in_comp = &component_type.repeater[index];
let vec = &*(component.as_ptr().add(rep_in_comp.offset) as *const RepeaterVec); let vec = &*(component.as_ptr().add(rep_in_comp.offset) as *const RepeaterVec);
for x in vec { for x in vec {
x.visit_children_item(-1, visitor.borrow_mut()); x.borrow().as_ref().visit_children_item(-1, visitor.borrow_mut());
} }
}, },
); );
@ -254,18 +273,12 @@ fn generate_component(
} }
extern "C" fn layout_info( extern "C" fn layout_info(
_: ComponentRef, _: ComponentRefPin,
) -> sixtyfps_corelib::abi::datastructures::LayoutInfo { ) -> sixtyfps_corelib::abi::datastructures::LayoutInfo {
todo!() todo!()
} }
let t = ComponentVTable { let t = ComponentVTable { visit_children_item, layout_info, compute_layout };
create: component_create,
drop: component_destroy,
visit_children_item,
layout_info,
compute_layout,
};
let t = ComponentDescription { let t = ComponentDescription {
ct: t, ct: t,
dynamic_type: builder.build(), dynamic_type: builder.build(),
@ -280,36 +293,13 @@ fn generate_component(
Rc::new(t) Rc::new(t)
} }
/// Safety: Can only be called for ComponentVTable which are in `MyComponentType`
unsafe extern "C" fn component_create(s: &ComponentVTable) -> ComponentBox {
// This is safe because we have an instance of ComponentVTable which is the first field of MyComponentType
// And the only way to get a MyComponentType is through the load function which returns a Rc
let component_type = Rc::<ComponentDescription>::from_raw(
s as *const ComponentVTable as *const ComponentDescription,
);
// We need to increment the ref-count, as from_raw doesn't do that.
std::mem::forget(component_type.clone());
instentiate(component_type, None)
}
pub fn instentiate( pub fn instentiate(
component_type: Rc<ComponentDescription>, component_type: Rc<ComponentDescription>,
parent_ctx: Option<&EvaluationContext>, parent_ctx: Option<&EvaluationContext>,
) -> ComponentBox { ) -> ComponentBox {
let instance = component_type.dynamic_type.clone().create_instance(); let instance = component_type.dynamic_type.clone().create_instance();
let mem = instance as *mut u8; let mem = instance.as_ptr().as_ptr() as *mut u8;
let component_box = ComponentBox { instance, component_type: component_type.clone() };
let ctx = Rc::new(ComponentImpl { mem, component_type: component_type.clone() });
let component_box = unsafe {
ComponentBox::from_raw(
NonNull::from(&ctx.component_type.ct).cast(),
NonNull::new(mem).unwrap().cast(),
)
};
// The destructor of ComponentBox will take care of reducing the count
Rc::into_raw(component_type);
let eval_context = if let Some(parent) = parent_ctx { let eval_context = if let Some(parent) = parent_ctx {
parent.child_context(component_box.borrow()) parent.child_context(component_box.borrow())
@ -317,7 +307,7 @@ pub fn instentiate(
EvaluationContext::for_root_component(component_box.borrow()) EvaluationContext::for_root_component(component_box.borrow())
}; };
for item_within_component in ctx.component_type.items.values() { for item_within_component in component_type.items.values() {
unsafe { unsafe {
let item = item_within_component.item_from_component(mem); let item = item_within_component.item_from_component(mem);
let elem = item_within_component.elem.borrow(); let elem = item_within_component.elem.borrow();
@ -330,47 +320,47 @@ pub fn instentiate(
.get(prop.as_str()) .get(prop.as_str())
.map(|o| item.as_ptr().add(*o) as *mut u8) .map(|o| item.as_ptr().add(*o) as *mut u8)
.or_else(|| { .or_else(|| {
ctx.component_type component_type.custom_signals.get(prop.as_str()).map(|o| mem.add(*o))
.custom_signals
.get(prop.as_str())
.map(|o| mem.add(*o))
}) })
.unwrap_or_else(|| panic!("unkown signal {}", prop)) .unwrap_or_else(|| panic!("unkown signal {}", prop))
as *mut Signal<()>); as *mut Signal<()>);
let expr = expr.clone(); let expr = expr.clone();
let ctx = ctx.clone(); let component_type = component_type.clone();
signal.set_handler(move |eval_context, _| { signal.set_handler(move |eval_context, _| {
eval::eval_expression(&expr, &*ctx, &eval_context); eval::eval_expression(&expr, &*component_type, &eval_context);
}) })
} 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())
{ {
if expr.is_constant() { if expr.is_constant() {
prop_rtti.set(item, eval::eval_expression(expr, &*ctx, &eval_context)); prop_rtti.set(
item,
eval::eval_expression(expr, &*component_type, &eval_context),
);
} else { } else {
let expr = expr.clone(); let expr = expr.clone();
let ctx = ctx.clone(); let component_type = component_type.clone();
prop_rtti.set_binding( prop_rtti.set_binding(
item, item,
Box::new(move |eval_context| { Box::new(move |eval_context| {
eval::eval_expression(&expr, &*ctx, eval_context) eval::eval_expression(&expr, &*component_type, eval_context)
}), }),
); );
} }
} else if let Some(PropertiesWithinComponent { offset, prop, .. }) = } else if let Some(PropertiesWithinComponent { offset, prop, .. }) =
ctx.component_type.custom_properties.get(prop.as_str()) component_type.custom_properties.get(prop.as_str())
{ {
if expr.is_constant() { if expr.is_constant() {
let v = eval::eval_expression(expr, &*ctx, &eval_context); let v = eval::eval_expression(expr, &*component_type, &eval_context);
prop.set(&*mem.add(*offset), v).unwrap(); prop.set(&*mem.add(*offset), v).unwrap();
} else { } else {
let expr = expr.clone(); let expr = expr.clone();
let ctx = ctx.clone(); let component_type = component_type.clone();
prop.set_binding( prop.set_binding(
&*mem.add(*offset), &*mem.add(*offset),
Box::new(move |eval_context| { Box::new(move |eval_context| {
eval::eval_expression(&expr, &*ctx, eval_context) eval::eval_expression(&expr, &*component_type, eval_context)
}), }),
); );
} }
@ -382,9 +372,9 @@ pub fn instentiate(
} }
} }
for rep_in_comp in &ctx.component_type.repeater { for rep_in_comp in &component_type.repeater {
let vec = unsafe { &mut *(mem.add(rep_in_comp.offset) as *mut RepeaterVec) }; let vec = unsafe { &mut *(mem.add(rep_in_comp.offset) as *mut RepeaterVec) };
match eval::eval_expression(&rep_in_comp.model, &*ctx, &eval_context) { match eval::eval_expression(&rep_in_comp.model, &*component_type, &eval_context) {
crate::Value::Number(count) => { crate::Value::Number(count) => {
vec.resize_with(count.round() as usize, || { vec.resize_with(count.round() as usize, || {
instentiate(rep_in_comp.component_to_repeat.clone(), Some(&eval_context)) instentiate(rep_in_comp.component_to_repeat.clone(), Some(&eval_context))
@ -422,16 +412,7 @@ pub fn instentiate(
component_box component_box
} }
unsafe extern "C" fn component_destroy(component_ref: vtable::VRefMut<ComponentVTable>) { unsafe extern "C" fn compute_layout(component: ComponentRefPin, eval_context: &EvaluationContext) {
// Take the reference count that the instentiate function leaked
let _vtable_rc = Rc::<ComponentDescription>::from_raw(component_ref.get_vtable()
as *const ComponentVTable
as *const ComponentDescription);
dynamic_type::TypeInfo::delete_instance(component_ref.as_ptr() as *mut dynamic_type::Instance);
}
unsafe extern "C" fn compute_layout(component: ComponentRef, eval_context: &EvaluationContext) {
debug_assert!(component.as_ptr() == eval_context.component.as_ptr()); debug_assert!(component.as_ptr() == eval_context.component.as_ptr());
// This is fine since we can only be called with a component that with our vtable which is a ComponentDescription // This is fine since we can only be called with a component that with our vtable which is a ComponentDescription
@ -500,6 +481,7 @@ unsafe extern "C" fn compute_layout(component: ComponentRef, eval_context: &Eval
/// Get the component description from a ComponentRef /// Get the component description from a ComponentRef
/// ///
/// Safety: the component must have been created by the interpreter /// Safety: the component must have been created by the interpreter
pub unsafe fn get_component_type<'a>(component: ComponentRef<'a>) -> &'a ComponentDescription { pub unsafe fn get_component_type<'a>(component: ComponentRefPin<'a>) -> &'a ComponentDescription {
&*(component.get_vtable() as *const ComponentVTable as *const ComponentDescription) &*(Pin::into_inner_unchecked(component).get_vtable() as *const ComponentVTable
as *const ComponentDescription)
} }

View file

@ -117,7 +117,7 @@ impl TypeInfo {
/// ///
/// The instance will be allocated on the heap. /// The instance will be allocated on the heap.
/// The instance must be freed with `delete_instance` /// The instance must be freed with `delete_instance`
pub fn create_instance(self: Rc<Self>) -> *mut Instance { pub fn create_instance(self: Rc<Self>) -> InstanceBox {
// Safety: the TypeInfo invariant means that the constructor can be called // Safety: the TypeInfo invariant means that the constructor can be called
unsafe { unsafe {
let mem = std::alloc::alloc(self.mem_layout); let mem = std::alloc::alloc(self.mem_layout);
@ -127,14 +127,14 @@ impl TypeInfo {
ctor(mem.add(f.offset)); ctor(mem.add(f.offset));
} }
} }
mem as *mut Instance InstanceBox(core::ptr::NonNull::new_unchecked(mem as *mut Instance))
} }
} }
/// Drop and free the memory of this instance /// Drop and free the memory of this instance
/// ///
/// Saferty, the instance must have been created by `TypeInfo::create_instance` /// Saferty, the instance must have been created by `TypeInfo::create_instance`
pub unsafe fn delete_instance(instance: *mut Instance) { unsafe fn delete_instance(instance: *mut Instance) {
let type_info = (*instance).type_info.clone(); let type_info = (*instance).type_info.clone();
let mem = instance as *mut u8; let mem = instance as *mut u8;
for f in &type_info.fields { for f in &type_info.fields {
@ -152,3 +152,19 @@ pub struct Instance {
type_info: Rc<TypeInfo>, type_info: Rc<TypeInfo>,
_opaque: [u8; 0], _opaque: [u8; 0],
} }
/// A pointer to an Instance that automaticaly frees the memory after use
pub struct InstanceBox(core::ptr::NonNull<Instance>);
impl InstanceBox {
// return a pointer to the instance
pub fn as_ptr(&self) -> core::ptr::NonNull<Instance> {
self.0
}
}
impl Drop for InstanceBox {
fn drop(&mut self) {
unsafe { TypeInfo::delete_instance(self.0.as_mut()) }
}
}

View file

@ -98,7 +98,7 @@ declare_value_conversion!(Object => [HashMap<String, Value>] );
/// 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
pub fn eval_expression( pub fn eval_expression(
e: &Expression, e: &Expression,
ctx: &crate::ComponentImpl, component_type: &crate::ComponentDescription,
eval_context: &corelib::EvaluationContext, eval_context: &corelib::EvaluationContext,
) -> Value { ) -> Value {
match e { match e {
@ -129,33 +129,47 @@ pub fn eval_expression(
} }
Expression::RepeaterIndexReference { element } => { Expression::RepeaterIndexReference { element } => {
if element.upgrade().unwrap().borrow().base_type if element.upgrade().unwrap().borrow().base_type
== Type::Component(ctx.component_type.original.clone()) == Type::Component(component_type.original.clone())
{ {
let x = &ctx.component_type.custom_properties["index"]; let x = &component_type.custom_properties["index"];
unsafe { x.prop.get(&*ctx.mem.offset(x.offset as isize), &eval_context).unwrap() } unsafe {
x.prop
.get(
&*eval_context.component.as_ptr().offset(x.offset as isize),
&eval_context,
)
.unwrap()
}
} else { } else {
todo!(); todo!();
} }
} }
Expression::RepeaterModelReference { element } => { Expression::RepeaterModelReference { element } => {
if element.upgrade().unwrap().borrow().base_type if element.upgrade().unwrap().borrow().base_type
== Type::Component(ctx.component_type.original.clone()) == Type::Component(component_type.original.clone())
{ {
let x = &ctx.component_type.custom_properties["model_data"]; let x = &component_type.custom_properties["model_data"];
unsafe { x.prop.get(&*ctx.mem.offset(x.offset as isize), &eval_context).unwrap() } unsafe {
x.prop
.get(
&*eval_context.component.as_ptr().offset(x.offset as isize),
&eval_context,
)
.unwrap()
}
} else { } else {
todo!(); todo!();
} }
} }
Expression::ObjectAccess { base, name } => { Expression::ObjectAccess { base, name } => {
if let Value::Object(mut o) = eval_expression(base, ctx, eval_context) { if let Value::Object(mut o) = eval_expression(base, component_type, eval_context) {
o.remove(name).unwrap_or(Value::Void) o.remove(name).unwrap_or(Value::Void)
} else { } else {
Value::Void Value::Void
} }
} }
Expression::Cast { from, to } => { Expression::Cast { from, to } => {
let v = eval_expression(&*from, ctx, eval_context); let v = eval_expression(&*from, component_type, eval_context);
match (v, to) { match (v, to) {
(Value::Number(n), Type::Int32) => Value::Number(n.round()), (Value::Number(n), Type::Int32) => Value::Number(n.round()),
(Value::Number(n), Type::String) => { (Value::Number(n), Type::String) => {
@ -167,7 +181,7 @@ pub fn eval_expression(
Expression::CodeBlock(sub) => { Expression::CodeBlock(sub) => {
let mut v = Value::Void; let mut v = Value::Void;
for e in sub { for e in sub {
v = eval_expression(e, ctx, eval_context); v = eval_expression(e, component_type, eval_context);
} }
v v
} }
@ -203,7 +217,7 @@ pub fn eval_expression(
Expression::SelfAssignment { lhs, rhs, op } => match &**lhs { Expression::SelfAssignment { lhs, rhs, op } => match &**lhs {
Expression::PropertyReference(NamedReference { element, name }) => { Expression::PropertyReference(NamedReference { element, name }) => {
let eval = |lhs| { let eval = |lhs| {
let rhs = eval_expression(&**rhs, ctx, eval_context); let rhs = eval_expression(&**rhs, component_type, eval_context);
match (lhs, rhs, op) { match (lhs, rhs, op) {
(Value::Number(a), Value::Number(b), '+') => Value::Number(a + b), (Value::Number(a), Value::Number(b), '+') => Value::Number(a + b),
(Value::Number(a), Value::Number(b), '-') => Value::Number(a - b), (Value::Number(a), Value::Number(b), '-') => Value::Number(a - b),
@ -236,8 +250,8 @@ pub fn eval_expression(
_ => panic!("typechecking should make sure this was a PropertyReference"), _ => panic!("typechecking should make sure this was a PropertyReference"),
}, },
Expression::BinaryExpression { lhs, rhs, op } => { Expression::BinaryExpression { lhs, rhs, op } => {
let lhs = eval_expression(&**lhs, ctx, eval_context); let lhs = eval_expression(&**lhs, component_type, eval_context);
let rhs = eval_expression(&**rhs, ctx, eval_context); let rhs = eval_expression(&**rhs, component_type, eval_context);
match (lhs, rhs, op) { match (lhs, rhs, op) {
(Value::Number(a), Value::Number(b), '+') => Value::Number(a + b), (Value::Number(a), Value::Number(b), '+') => Value::Number(a + b),
@ -251,19 +265,21 @@ pub fn eval_expression(
Value::Resource(Resource::AbsoluteFilePath(absolute_source_path.as_str().into())) Value::Resource(Resource::AbsoluteFilePath(absolute_source_path.as_str().into()))
} }
Expression::Condition { condition, true_expr, false_expr } => { Expression::Condition { condition, true_expr, false_expr } => {
match eval_expression(&**condition, ctx, eval_context).try_into() as Result<bool, _> { match eval_expression(&**condition, component_type, eval_context).try_into()
Ok(true) => eval_expression(&**true_expr, ctx, eval_context), as Result<bool, _>
Ok(false) => eval_expression(&**false_expr, ctx, eval_context), {
Ok(true) => eval_expression(&**true_expr, component_type, eval_context),
Ok(false) => eval_expression(&**false_expr, component_type, eval_context),
_ => panic!("conditional expression did not evaluate to boolean"), _ => panic!("conditional expression did not evaluate to boolean"),
} }
} }
Expression::Array { values, .. } => { Expression::Array { values, .. } => Value::Array(
Value::Array(values.iter().map(|e| eval_expression(e, ctx, eval_context)).collect()) values.iter().map(|e| eval_expression(e, component_type, eval_context)).collect(),
} ),
Expression::Object { values, .. } => Value::Object( Expression::Object { values, .. } => Value::Object(
values values
.iter() .iter()
.map(|(k, v)| (k.clone(), eval_expression(v, ctx, eval_context))) .map(|(k, v)| (k.clone(), eval_expression(v, component_type, eval_context)))
.collect(), .collect(),
), ),
} }

View file

@ -14,12 +14,10 @@ pub use dynamic_component::load;
pub use dynamic_component::ComponentDescription; pub use dynamic_component::ComponentDescription;
pub use eval::Value; pub use eval::Value;
pub(crate) use dynamic_component::ComponentImpl; pub use dynamic_component::ComponentBox;
use sixtyfps_corelib::{ use sixtyfps_corelib::abi::datastructures::{ComponentRef, ComponentRefMut};
abi::datastructures::{ComponentBox, ComponentRef, ComponentRefMut}, use sixtyfps_corelib::{ComponentRefPin, EvaluationContext, Signal};
EvaluationContext, Signal, use std::{collections::HashMap, pin::Pin, rc::Rc};
};
use std::{collections::HashMap, rc::Rc};
impl ComponentDescription { impl ComponentDescription {
/// The name of this Component as written in the .60 file /// The name of this Component as written in the .60 file
@ -49,7 +47,7 @@ impl ComponentDescription {
/// or if the property with this name does not exist in this component /// or if the property with this name does not exist in this component
pub fn set_property( pub fn set_property(
&self, &self,
component: ComponentRef, component: ComponentRefPin,
name: &str, name: &str,
value: Value, value: Value,
) -> Result<(), ()> { ) -> Result<(), ()> {
@ -96,7 +94,7 @@ impl ComponentDescription {
/// or if the property with this name does not exist in this component /// or if the property with this name does not exist in this component
pub fn set_signal_handler( pub fn set_signal_handler(
&self, &self,
component: ComponentRefMut, component: Pin<ComponentRefMut>,
name: &str, name: &str,
handler: Box<dyn Fn(&EvaluationContext, ())>, handler: Box<dyn Fn(&EvaluationContext, ())>,
) -> Result<(), ()> { ) -> Result<(), ()> {

View file

@ -16,7 +16,8 @@ assert(instance.get_t3() == 42 - (3 + 2 * (42 + 2)));
```rust ```rust
let instance = TestCase::default(); let instance = Box::pin(TestCase::default());
let instance = instance.as_ref();
assert_eq!(instance.get_t1(), 4 + 3 * 2 + 2 - 50 - 2); assert_eq!(instance.get_t1(), 4 + 3 * 2 + 2 - 50 - 2);
assert_eq!(instance.get_t2(), 500 / 2 * 30 - 1); assert_eq!(instance.get_t2(), 500 / 2 * 30 - 1);
instance.set_a(42); instance.set_a(42);

View file

@ -21,7 +21,8 @@ assert(instance.get_s1() == "123");
```rust ```rust
let instance = TestCase::default(); let instance = Box::pin(TestCase::default());
let instance = instance.as_ref();
instance.set_condition(true); instance.set_condition(true);
assert_eq!(instance.get_s1(), "abc"); assert_eq!(instance.get_s1(), "abc");
assert_eq!(instance.get_s2(), "123"); assert_eq!(instance.get_s2(), "123");

View file

@ -27,7 +27,8 @@ assert(t.get_b1() != t.get_r5());
```rust ```rust
let t = Test::default(); let t = Box::pin(Test::default());
let t = t.as_ref();
assert_eq!(t.get_r1(), t.get_r2()); assert_eq!(t.get_r1(), t.get_r2());
assert_eq!(t.get_r1(), t.get_r3()); assert_eq!(t.get_r1(), t.get_r3());
assert_eq!(t.get_r1(), t.get_r4()); assert_eq!(t.get_r1(), t.get_r4());

View file

@ -20,7 +20,8 @@ assert(instance.get_test_value2() == 3);
```rust ```rust
let instance = TestCase::default(); let instance = Box::pin(TestCase::default());
let instance = instance.as_ref();
instance.set_condition(true); instance.set_condition(true);
assert_eq!(instance.get_test_value(), 1); assert_eq!(instance.get_test_value(), 1);
assert_eq!(instance.get_test_value2(), 2); assert_eq!(instance.get_test_value2(), 2);

View file

@ -14,7 +14,8 @@ assert(instance.get_signal_emission_count() == 1);
```rust ```rust
let instance = TestCase::default(); let instance = Box::pin(TestCase::default());
let instance = instance.as_ref();
instance.set_signal_emission_count(0); instance.set_signal_emission_count(0);
assert_eq!(instance.get_signal_emission_count(), 0); assert_eq!(instance.get_signal_emission_count(), 0);
instance.emit_test_signal(); instance.emit_test_signal();