From 3335ff8da5376a987b5a9d78c77486ae11aada91 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Thu, 18 Mar 2021 12:13:40 +0100 Subject: [PATCH] C++ API to put a model in the interpreter::Value --- .../include/sixtyfps_interpreter.h | 58 +++++++++- api/sixtyfps-cpp/tests/tests.cpp | 32 +++++- helper_crates/vtable/src/lib.rs | 2 +- sixtyfps_runtime/interpreter/api.rs | 100 ++++++++++++++++++ 4 files changed, 189 insertions(+), 3 deletions(-) diff --git a/api/sixtyfps-cpp/include/sixtyfps_interpreter.h b/api/sixtyfps-cpp/include/sixtyfps_interpreter.h index 0b5ccde3d..10be2ca4a 100644 --- a/api/sixtyfps-cpp/include/sixtyfps_interpreter.h +++ b/api/sixtyfps-cpp/include/sixtyfps_interpreter.h @@ -206,4 +206,60 @@ inline std::optional> Value::to_array() const return {}; } } -} \ No newline at end of file +inline Value::Value(const std::shared_ptr> &model) { + using cbindgen_private::ModelAdaptorVTable; + using vtable::VRef; + struct ModelWrapper : AbstractRepeaterView { + std::shared_ptr> model; + cbindgen_private::ModelNotifyOpaque notify; + // This kind of mean that the rust code has ownership of "this" until the drop funciton is called + std::shared_ptr self; + ~ModelWrapper() { + cbindgen_private::sixtyfps_interpreter_model_notify_destructor(¬ify); + } + + void row_added(int index, int count) override + { + cbindgen_private::sixtyfps_interpreter_model_notify_row_added(¬ify, index, count); + } + void row_changed(int index) override + { + cbindgen_private::sixtyfps_interpreter_model_notify_row_changed(¬ify, index); + } + void row_removed(int index, int count) override + { + cbindgen_private::sixtyfps_interpreter_model_notify_row_removed(¬ify, index, count); + } + }; + + auto wrapper = std::make_shared(); + wrapper->model = model; + wrapper->self = wrapper; + cbindgen_private::sixtyfps_interpreter_model_notify_new(&wrapper->notify); + model->attach_peer(wrapper); + + auto row_count = [](VRef self) -> uintptr_t { + return reinterpret_cast(self.instance)->model->row_count(); + }; + auto row_data = [](VRef self, uintptr_t row, ValueOpaque *out) { + Value v = reinterpret_cast(self.instance)->model->row_data(row); + *out = v.inner; + cbindgen_private::sixtyfps_interpreter_value_new(&v.inner); + }; + auto set_row_data = [](VRef self, uintptr_t row, const ValueOpaque *value) { + Value v = *reinterpret_cast(value); + reinterpret_cast(self.instance)->model->set_row_data(row, v); + }; + auto get_notify = [](VRef self) -> const cbindgen_private::ModelNotifyOpaque* { + return &reinterpret_cast(self.instance)->notify; + }; + auto drop = [](vtable::VRefMut self) { + reinterpret_cast(self.instance)->self = nullptr; + }; + + static const ModelAdaptorVTable vt { row_count, row_data, set_row_data, get_notify, drop }; + vtable::VBox wrap{ &vt, wrapper.get() }; + cbindgen_private::sixtyfps_interpreter_value_new_model(wrap, &inner); +} + +} diff --git a/api/sixtyfps-cpp/tests/tests.cpp b/api/sixtyfps-cpp/tests/tests.cpp index e93101cb2..367c04614 100644 --- a/api/sixtyfps-cpp/tests/tests.cpp +++ b/api/sixtyfps-cpp/tests/tests.cpp @@ -124,4 +124,34 @@ SCENARIO("Value API") auto struct_opt = value.to_struct(); REQUIRE(struct_opt.has_value()); } -} \ No newline at end of file + + SECTION("Construct a model") + { + // And test that it is properly destroyed when the value is destroyed + struct M : sixtyfps::VectorModel { + bool *destroyed; + explicit M(bool *destroyed) : destroyed(destroyed) {} + void play() { + this->push_back(Value(4.)); + this->set_row_data(0, Value(9.)); + } + ~M() { *destroyed = true; } + }; + bool destroyed = false; + auto m = std::make_shared(&destroyed); + { + Value value(m); + REQUIRE(value.type() == Value::Type::Model); + REQUIRE(!destroyed); + m->play(); + m = nullptr; + REQUIRE(!destroyed); + // play a bit with the value to test the copy and move + Value v2 = value; + Value v3 = std::move(v2); + REQUIRE(!destroyed); + } + REQUIRE(destroyed); + } +} + diff --git a/helper_crates/vtable/src/lib.rs b/helper_crates/vtable/src/lib.rs index 9ddd41292..35e5fdd9d 100644 --- a/helper_crates/vtable/src/lib.rs +++ b/helper_crates/vtable/src/lib.rs @@ -152,7 +152,7 @@ impl Inner { /// /// The VBox implements Deref so one can access all the members of the vtable. /// -/// This is only valid if the VTable has a `drop` type (so that the `#[vtable]` macro +/// This is only valid if the VTable has a `drop` function (so that the `#[vtable]` macro /// implements the `VTableMetaDrop` trait for it) #[repr(transparent)] pub struct VBox { diff --git a/sixtyfps_runtime/interpreter/api.rs b/sixtyfps_runtime/interpreter/api.rs index 2b0e427d2..00b3be450 100644 --- a/sixtyfps_runtime/interpreter/api.rs +++ b/sixtyfps_runtime/interpreter/api.rs @@ -744,8 +744,10 @@ pub mod testing { #[allow(missing_docs)] pub(crate) mod ffi { use super::*; + use sixtyfps_corelib::model::{Model, ModelNotify, ModelPeer}; use sixtyfps_corelib::slice::Slice; use std::ffi::c_void; + use vtable::VRef; #[repr(C)] pub struct ValueOpaque([usize; 7]); @@ -841,6 +843,15 @@ pub(crate) mod ffi { std::ptr::write(val as *mut Value, Value::Struct(struc.as_struct().clone())) } + /// Construct a new Value containing a model in the given memory location + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_value_new_model( + model: vtable::VBox, + val: *mut ValueOpaque, + ) { + std::ptr::write(val as *mut Value, Value::Model(Rc::new(ModelAdaptorWrapper(model)))) + } + #[repr(i8)] pub enum ValueType { Void, @@ -1114,4 +1125,93 @@ pub(crate) mod ffi { inst.set_callback(std::str::from_utf8(&name).unwrap(), callback).is_ok() } + + #[vtable::vtable] + #[repr(C)] + pub struct ModelAdaptorVTable { + pub row_count: extern "C" fn(VRef) -> usize, + pub row_data: + unsafe extern "C" fn(VRef, row: usize, out: *mut ValueOpaque), + pub set_row_data: extern "C" fn(VRef, row: usize, value: &ValueOpaque), + pub get_notify: extern "C" fn(VRef) -> &ModelNotifyOpaque, + pub drop: extern "C" fn(VRefMut), + } + + struct ModelAdaptorWrapper(vtable::VBox); + impl Model for ModelAdaptorWrapper { + type Data = Value; + + fn row_count(&self) -> usize { + self.0.row_count() + } + + fn row_data(&self, row: usize) -> Value { + unsafe { + let mut v = std::mem::MaybeUninit::::uninit(); + self.0.row_data(row, v.as_mut_ptr() as *mut ValueOpaque); + v.assume_init() + } + } + + fn attach_peer(&self, peer: ModelPeer) { + self.0.get_notify().as_model_notify().attach(peer); + } + + fn set_row_data(&self, row: usize, data: Value) { + let val: &ValueOpaque = unsafe { std::mem::transmute::<&Value, &ValueOpaque>(&data) }; + self.0.set_row_data(row, val); + } + } + + #[repr(C)] + pub struct ModelNotifyOpaque([usize; 6]); + /// Asserts that ModelNotifyOpaque is at least as large as ModelNotify, otherwise this would overflow + const _: usize = std::mem::size_of::() - std::mem::size_of::(); + + impl ModelNotifyOpaque { + fn as_model_notify(&self) -> &ModelNotify { + // Safety: there should be no way to construct a ModelNotifyOpaque without it holding an actual ModelNotify + unsafe { std::mem::transmute::<&ModelNotifyOpaque, &ModelNotify>(self) } + } + } + + /// Construct a new ModelNotifyNotify in the given memory region + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_model_notify_new(val: *mut ModelNotifyOpaque) { + std::ptr::write(val as *mut ModelNotify, ModelNotify::default()); + } + + /// Destruct the value in that memory location + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_model_notify_destructor( + val: *mut ModelNotifyOpaque, + ) { + drop(std::ptr::read(val as *mut ModelNotify)) + } + + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_model_notify_row_changed( + notify: &ModelNotifyOpaque, + row: usize, + ) { + notify.as_model_notify().row_changed(row); + } + + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_model_notify_row_added( + notify: &ModelNotifyOpaque, + row: usize, + count: usize, + ) { + notify.as_model_notify().row_added(row, count); + } + + #[no_mangle] + pub unsafe extern "C" fn sixtyfps_interpreter_model_notify_row_removed( + notify: &ModelNotifyOpaque, + row: usize, + count: usize, + ) { + notify.as_model_notify().row_removed(row, count); + } }