Refactor the C++ Repeater to do the same as the Rust one

This commit is contained in:
Olivier Goffart 2020-09-29 14:17:32 +02:00
parent 2337eb097b
commit f4f4775a19
3 changed files with 75 additions and 34 deletions

View file

@ -262,7 +262,13 @@ using cbindgen_private::solve_grid_layout;
using cbindgen_private::solve_path_layout; using cbindgen_private::solve_path_layout;
// models // models
using ModelPeer = std::weak_ptr<bool>; struct AbstractRepeaterView {
~AbstractRepeaterView() = default;
virtual void row_added(int index, int count) = 0;
virtual void row_removed(int index, int count) = 0;
virtual void row_changed(int index) = 0;
};
using ModelPeer = std::weak_ptr<AbstractRepeaterView>;
template<typename ModelData> template<typename ModelData>
class Model class Model
@ -291,29 +297,27 @@ protected:
/// Notify the views that a specific row was changed /// Notify the views that a specific row was changed
void row_changed(int row) void row_changed(int row)
{ {
(void)row; for_each_peers([=](auto peer) { peer->row_changed(row); });
notify();
} }
/// Notify the views that rows were added /// Notify the views that rows were added
void row_added(int index, int count) void row_added(int index, int count)
{ {
(void)(index + count); for_each_peers([=](auto peer) { peer->row_added(index, count); });
notify();
} }
/// Notify the views that rows were removed /// Notify the views that rows were removed
void row_removed(int index, int count) void row_removed(int index, int count)
{ {
(void)(index + count); for_each_peers([=](auto peer) { peer->row_removed(index, count); });
notify();
} }
private: private:
void notify() template<typename F>
void for_each_peers(const F &f)
{ {
peers.erase(std::remove_if(peers.begin(), peers.end(), peers.erase(std::remove_if(peers.begin(), peers.end(),
[](const auto &p) { [&](const auto &p) {
if (auto pp = p.lock()) { if (auto pp = p.lock()) {
*pp = true; f(pp);
return false; return false;
} }
return true; return true;
@ -376,12 +380,34 @@ public:
template<typename C, typename ModelData> template<typename C, typename ModelData>
class Repeater class Repeater
{ {
mutable std::shared_ptr<bool> is_dirty;
Property<std::shared_ptr<Model<ModelData>>> model; Property<std::shared_ptr<Model<ModelData>>> model;
struct RepeaterInner : AbstractRepeaterView {
enum class State { Clean, Dirty };
struct ComponentWithState {
State state = State::Dirty;
std::unique_ptr<C> ptr;
};
std::vector<ComponentWithState> data;
bool is_dirty = true;
void row_added(int index, int count) override {
is_dirty = true;
data.resize(data.size() + count);
std::rotate(data.begin() + index, data.end() - count, data.end());
}
void row_changed(int index) override {
is_dirty = true;
data[index].state = State::Dirty;
}
void row_removed(int index, int count) override {
data.erase(data.begin() + index, data.begin() + index + count);
}
};
public: public:
// FIXME: should be private, but compute_layout uses it. // FIXME: should be private, but compute_layout uses it.
std::vector<std::unique_ptr<C>> data; mutable std::shared_ptr<RepeaterInner> inner;
template<typename F> template<typename F>
void set_model_binding(F &&binding) const void set_model_binding(F &&binding) const
@ -393,30 +419,37 @@ public:
void ensure_updated(const Parent *parent) const void ensure_updated(const Parent *parent) const
{ {
if (model.is_dirty()) { if (model.is_dirty()) {
is_dirty = std::make_shared<bool>(true); inner = std::make_shared<RepeaterInner>();
if (auto m = model.get()) { if (auto m = model.get()) {
m->attach_peer(is_dirty); m->attach_peer(inner);
} }
} }
if (is_dirty && *is_dirty) {
auto &data = const_cast<Repeater *>(this)->data; if (inner && inner->is_dirty) {
data.clear(); inner->is_dirty = false;
auto m = model.get(); if (auto m = model.get()) {
auto count = m->row_count(); int count = m->row_count();
for (auto i = 0; i < count; ++i) { inner->data.resize(count);
auto x = std::make_unique<C>(); for (int i = 0; i < count; ++i) {
x->parent = parent; auto &c = inner->data[i];
x->update_data(i, m->row_data(i)); if (c.state == RepeaterInner::State::Dirty) {
data.push_back(std::move(x)); if (!c.ptr) {
c.ptr = std::make_unique<C>();
c.ptr->parent = parent;
}
c.ptr->update_data(i, m->row_data(i));
}
}
} else {
inner->data.clear();
} }
*is_dirty = false;
} }
} }
intptr_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const intptr_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const
{ {
for (std::size_t i = 0; i < data.size(); ++i) { for (std::size_t i = 0; i < inner->data.size(); ++i) {
int index = order == TraversalOrder::BackToFront ? i : data.size() - 1 - i; int index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i;
auto ref = item_at(index); auto ref = item_at(index);
if (ref.vtable->visit_children_item(ref, -1, order, visitor) != -1) { if (ref.vtable->visit_children_item(ref, -1, order, visitor) != -1) {
return index; return index;
@ -427,14 +460,16 @@ public:
VRef<private_api::ComponentVTable> item_at(int i) const VRef<private_api::ComponentVTable> item_at(int i) const
{ {
const auto &x = data.at(i); const auto &x = inner->data.at(i);
return { &C::component_type, x.get() }; return { &C::component_type, x.ptr.get() };
} }
void compute_layout() const void compute_layout() const
{ {
for (auto &x : data) { if (!inner)
x->compute_layout({ &C::component_type, x.get() }); return;
for (auto &x : inner->data) {
x.ptr->compute_layout({ &C::component_type, x.ptr.get() });
} }
} }
}; };

View file

@ -1483,15 +1483,15 @@ impl<'a> LayoutTreeItem<'a> {
let root_element = let root_element =
elem.borrow().base_type.as_component().root_element.clone(); elem.borrow().base_type.as_component().root_element.clone();
code_stream.push(format!( code_stream.push(format!(
" for (auto &&sub_comp : self->repeater_{}.data)", " for (auto &&sub_comp : self->repeater_{}.inner->data)",
elem.borrow().id elem.borrow().id
)); ));
code_stream.push(format!( code_stream.push(format!(
" items.push_back({});", " items.push_back({});",
path_layout_item_data( path_layout_item_data(
&root_element, &root_element,
&format!("sub_comp->{}", root_element.borrow().id), &format!("sub_comp.ptr->{}", root_element.borrow().id),
"sub_comp", "sub_comp.ptr",
) )
)); ));
} else { } else {

View file

@ -99,9 +99,11 @@ TestCase instance;
// there should be nothing there // there should be nothing there
sixtyfps::testing::send_mouse_click(instance, 25., 5.); sixtyfps::testing::send_mouse_click(instance, 25., 5.);
assert_eq(instance.get_clicked_score(), 0); assert_eq(instance.get_clicked_score(), 0);
assert_eq(instance.get_clicked_internal_state(), 0);
sixtyfps::testing::send_mouse_click(instance, 15., 5.); sixtyfps::testing::send_mouse_click(instance, 15., 5.);
assert_eq(instance.get_clicked_score(), 789000); assert_eq(instance.get_clicked_score(), 789000);
assert_eq(instance.get_clicked_internal_state(), 1);
using ModelData = std::tuple<sixtyfps::SharedString, sixtyfps::SharedString, float>; using ModelData = std::tuple<sixtyfps::SharedString, sixtyfps::SharedString, float>;
sixtyfps::SharedArray<ModelData> array; sixtyfps::SharedArray<ModelData> array;
@ -113,20 +115,24 @@ instance.set_model(another_model);
sixtyfps::testing::send_mouse_click(instance, 25., 5.); sixtyfps::testing::send_mouse_click(instance, 25., 5.);
assert_eq(instance.get_clicked_score(), 333000); assert_eq(instance.get_clicked_score(), 333000);
assert_eq(instance.get_clicked_internal_state(), 1);
sixtyfps::testing::send_mouse_click(instance, 15., 5.); sixtyfps::testing::send_mouse_click(instance, 15., 5.);
assert_eq(instance.get_clicked_score(), 222000); assert_eq(instance.get_clicked_score(), 222000);
assert_eq(instance.get_clicked_name(), "cruel"); assert_eq(instance.get_clicked_name(), "cruel");
assert_eq(instance.get_clicked_internal_state(), 1);
another_model->push_back({"a4", "!", 444.}); another_model->push_back({"a4", "!", 444.});
sixtyfps::testing::send_mouse_click(instance, 35., 5.); sixtyfps::testing::send_mouse_click(instance, 35., 5.);
assert_eq(instance.get_clicked_score(), 444000); assert_eq(instance.get_clicked_score(), 444000);
assert_eq(instance.get_clicked_name(), "!"); assert_eq(instance.get_clicked_name(), "!");
assert_eq(instance.get_clicked_internal_state(), 1);
another_model->set_row_data(1, {"a2", "idyllic", 555.}); another_model->set_row_data(1, {"a2", "idyllic", 555.});
sixtyfps::testing::send_mouse_click(instance, 15., 5.); sixtyfps::testing::send_mouse_click(instance, 15., 5.);
assert_eq(instance.get_clicked_score(), 555000); assert_eq(instance.get_clicked_score(), 555000);
assert_eq(instance.get_clicked_name(), "idyllic"); assert_eq(instance.get_clicked_name(), "idyllic");
assert_eq(instance.get_clicked_internal_state(), 2);
``` ```
*/ */