58 :
public detail::ListSignalMixin<PagedList<T, Source>, T> {
59 friend detail::ListSignalMixin<PagedList<T, Source>, T>;
63 using Signal = detail::ListSignal<T>;
69 std::size_t initial_page_size,
70 std::size_t initial_page_index = 0)
71 : page_size_prop_{initial_page_size == 0 ?
std::size_t{1}
73 page_index_prop_{initial_page_index},
74 source_(
std::move(source)),
76 state_(
std::make_shared<SharedState>())
80 std::weak_ptr<Source> weak_source{source_};
81 std::weak_ptr<bool> weak_alive = alive_;
82 source_sub_ = source_->observe(
84 auto alive = weak_alive.lock();
85 if (!alive || !*alive)
return;
86 if (!weak_source.lock())
return;
87 handle_source_change_(ch);
90 page_index_sub_ = page_index_prop_.on_changed(
91 [
this, weak_alive](std::size_t ) {
92 auto alive = weak_alive.lock();
93 if (alive && *alive) rebuild_and_emit_();
95 page_size_sub_ = page_size_prop_.on_changed(
96 [
this, weak_alive](std::size_t ) {
97 auto alive = weak_alive.lock();
98 if (alive && *alive) rebuild_and_emit_();
108 [[nodiscard]] std::size_t
size()
const {
109 std::shared_lock lk(state_->m);
110 return state_->window.size();
113 [[nodiscard]]
bool empty()
const {
return size() == 0; }
115 [[nodiscard]] std::shared_ptr<T>
at(std::size_t derived_pos)
const {
116 std::shared_lock lk(state_->m);
117 return state_->window.at(derived_pos);
120 [[nodiscard]] std::vector<std::shared_ptr<T>>
snapshot()
const {
121 std::shared_lock lk(state_->m);
122 return state_->window;
128 const std::size_t total = source_->size();
129 const std::size_t ps = std::max<std::size_t>(1, page_size_prop_.peek());
130 return total / ps + (total % ps != 0 ? 1 : 0);
135 return pc == 0 || page_index_prop_.peek() >= pc - 1;
141 return page_size_prop_;
144 return page_size_prop_;
149 return page_index_prop_;
152 return page_index_prop_;
157 std::optional<ListChange<T>> source_change;
163 mutable std::shared_mutex m;
164 std::vector<std::shared_ptr<T>> window;
168 std::vector<std::shared_ptr<T>> source_items;
173 Property<std::size_t> page_size_prop_;
174 Property<std::size_t> page_index_prop_;
176 std::shared_ptr<Source> source_;
177 std::shared_ptr<Signal> signal_;
178 std::shared_ptr<SharedState> state_;
179 std::shared_ptr<bool> alive_ = std::make_shared<bool>(
true);
181 Subscription source_sub_;
182 Subscription page_index_sub_;
183 Subscription page_size_sub_;
185 static std::vector<std::shared_ptr<T>> compute_window_(
186 const std::vector<std::shared_ptr<T>>& items,
190 if (items.empty() ||
page_index > (items.size() - 1) /
size)
return {};
192 const auto count = std::min(
size, items.size() - start);
193 const auto first = items.begin() +
static_cast<std::ptrdiff_t
>(start);
194 return {first, first +
static_cast<std::ptrdiff_t
>(count)};
197 void rebuild_window_() {
198 auto items = source_->snapshot();
199 const auto page_size = std::max<std::size_t>(1, page_size_prop_.peek());
200 const auto page_index = page_index_prop_.peek();
202 std::unique_lock lk(state_->m);
203 state_->source_items = std::move(items);
204 state_->window = std::move(window);
209 void rebuild_and_emit_() {
211 auto signal = signal_;
212 apply_(*state, *signal, InputChange{
213 {}, page_size_prop_.peek(), page_index_prop_.peek()});
216 void handle_source_change_(
const ListChange<T>& change) {
218 auto signal = signal_;
219 apply_(*state, *signal, InputChange{change, page_size_prop_.peek(), page_index_prop_.peek()});
222 static void apply_(SharedState& state,
Signal& signal, InputChange event) {
223 std::vector<std::shared_ptr<T>> before;
224 std::vector<std::shared_ptr<T>> after;
225 std::optional<ListChange<T>> direct;
226 bool same_page =
false;
228 std::unique_lock lk(state.m);
229 auto& items = state.source_items;
230 const auto page_size = std::max<std::size_t>(1, event.page_size);
231 same_page = state.page_size ==
page_size && state.page_index ==
event.page_index;
232 if (event.source_change &&
234 event.source_change->index == items.size() &&
241 items.push_back(event.source_change->item);
244 before = state.window;
245 if (event.source_change) {
246 const auto& ch = *
event.source_change;
247 const auto pos =
static_cast<std::ptrdiff_t
>(ch.index);
255 auto moved = items.at(ch.from_index);
256 items.erase(items.begin() +
static_cast<std::ptrdiff_t
>(ch.from_index));
257 items.insert(items.begin() + pos, std::move(moved));
262 after = compute_window_(items, event.page_size, event.page_index);
263 state.window = after;
265 state.page_index =
event.page_index;
266 if (event.source_change) {
267 const auto& ch = *
event.source_change;
272 const auto start =
event.page_index * std::max<std::size_t>(1, event.page_size);
273 if (ch.index >= start && ch.index - start < after.size()) {
274 direct = ListChange<T>{ch.kind, ch.index - start, ch.item, 0};
280 signal.emit(*direct);
282 emit_diff_(signal, before, after, std::move(direct));
287 static void emit_diff_(
Signal& sig,
288 const std::vector<std::shared_ptr<T>>& before,
289 const std::vector<std::shared_ptr<T>>& after,
290 std::optional<ListChange<T>> refresh = {}) {
291 std::vector<ListChange<T>> changes;
292 changes.reserve(before.size() + after.size());
293 std::unordered_set<const T*> in_after;
294 in_after.reserve(after.size());
295 for (
const auto& p : after) in_after.insert(p.get());
297 std::vector<std::shared_ptr<T>> work = before;
298 for (std::ptrdiff_t i =
static_cast<std::ptrdiff_t
>(work.size()) - 1;
300 const auto u =
static_cast<std::size_t
>(i);
301 if (!in_after.count(work[u].get())) {
304 work.erase(work.begin() + i);
307 for (std::size_t i = 0; i < after.size(); ++i) {
308 const T* want = after[i].get();
309 if (i < work.size() && work[i].get() == want)
continue;
311 const auto pos = work.begin() +
static_cast<std::ptrdiff_t
>(i);
312 const auto existing = std::find_if(pos, work.end(),
313 [want](
const auto& item) { return item.get() == want; });
314 if (existing != work.end()) {
315 const auto from =
static_cast<std::size_t
>(existing - work.begin());
316 std::rotate(pos, existing, existing + 1);
321 work.insert(work.begin() +
static_cast<std::ptrdiff_t
>(i),
329 while (work.size() > after.size()) {
330 const auto index = work.size() - 1;
331 auto removed = work.back();
339 if (refresh) changes.push_back(std::move(*refresh));
340 sig.emit_batch(std::move(changes));
Property< std::size_t > & page_size() noexcept
Window size in items per page. Set to drive a re-window.
Definition paged_list.hpp:140
const Property< std::size_t > & page_index() const noexcept
Definition paged_list.hpp:151
std::size_t page_count() const
Number of pages for the current source size + page_size.
Definition paged_list.hpp:127
const Property< std::size_t > & page_size() const noexcept
Definition paged_list.hpp:143
Property< std::size_t > & page_index() noexcept
0-based page index. Set to drive a re-window.
Definition paged_list.hpp:148
bool is_last_page() const
Definition paged_list.hpp:133
detail::ListSignal< T > Signal
Definition paged_list.hpp:63
std::shared_ptr< T > at(std::size_t derived_pos) const
Definition paged_list.hpp:115
std::size_t size() const
Definition paged_list.hpp:108
bool empty() const
Definition paged_list.hpp:113
std::vector< std::shared_ptr< T > > snapshot() const
Definition paged_list.hpp:120
PagedList(std::shared_ptr< Source > source, std::size_t initial_page_size, std::size_t initial_page_index=0)
Construct a PagedList.
Definition paged_list.hpp:68