5#include "aria/detail/list_signal_mixin.hpp"
6#include "aria/detail/list_signal.hpp"
16#include <shared_mutex>
19#include <unordered_map>
41 friend detail::ListSignalMixin<ObservableList<T>, T>;
44 std::size_t index = 0;
45 std::size_t count = 0;
46 bool installing =
false;
50 mutable std::shared_mutex mutex;
51 std::recursive_mutex writer;
52 std::vector<std::shared_ptr<T>>
items;
53 std::unordered_map<const T*, Record> records;
54 std::shared_ptr<detail::ListSignal<T>> signal = std::make_shared<detail::ListSignal<T>>();
59 using Signal = detail::ListSignal<T>;
66 [[nodiscard]] std::size_t
size()
const {
return size_(state_); }
67 [[nodiscard]]
bool empty()
const {
return size() == 0; }
68 [[nodiscard]] std::shared_ptr<T>
at(std::size_t index)
const {
69 std::shared_lock lock(state_->mutex);
70 return state_->items.at(index);
72 [[nodiscard]] std::vector<std::shared_ptr<T>>
snapshot()
const {
73 std::shared_lock lock(state_->mutex);
85 : data_(std::move(data)) {}
89 [[nodiscard]] std::size_t
size() const noexcept {
return data_.size(); }
90 [[nodiscard]]
bool empty() const noexcept {
return data_.empty(); }
91 [[nodiscard]]
const std::shared_ptr<T>&
operator[](std::size_t i)
const {
96 std::vector<std::shared_ptr<T>> data_;
105 std::lock_guard sequence(state->writer);
108 std::unique_lock lock(state->mutex);
109 index = state->items.size();
110 state->items.push_back(item);
111 auto& record = state->records[item.get()];
112 record.index = index;
116 [state, item] { install_(state, item); });
119 template<
typename... Args>
121 auto item = std::make_shared<T>(std::forward<Args>(args)...);
126 void insert(std::size_t index, std::shared_ptr<T> item) {
128 std::lock_guard sequence(state->writer);
130 std::unique_lock lock(state->mutex);
131 index = std::min(index, state->items.size());
132 state->items.insert(state->items.begin() +
static_cast<std::ptrdiff_t
>(index), item);
133 ++state->records[item.get()].count;
134 reindex_(state, index);
137 [state, item] { install_(state, item); });
141 template<
typename InputIt>
144 std::lock_guard sequence(state->writer);
145 std::vector<std::shared_ptr<T>> pending(first, last);
146 if (pending.empty())
return;
147 std::vector<Event> events;
148 events.reserve(pending.size());
150 std::unique_lock lock(state->mutex);
151 index = std::min(index, state->items.size());
152 state->items.insert(state->items.begin() +
static_cast<std::ptrdiff_t
>(index),
153 pending.begin(), pending.end());
154 for (std::size_t i = 0; i < pending.size(); ++i) {
155 ++state->records[pending[i].get()].count;
158 reindex_(state, index);
160 emit_batch_(state, std::move(events), size_(state), [&] {
161 for (
const auto& item : pending) install_(state, item);
167 std::lock_guard sequence(state->writer);
168 std::shared_ptr<T> removed;
171 std::unique_lock lock(state->mutex);
172 if (index >= state->items.size())
return;
173 removed = state->items[index];
174 state->items.erase(state->items.begin() +
static_cast<std::ptrdiff_t
>(index));
175 detached = drop_(state, removed.get());
176 reindex_(state, index);
184 std::lock_guard sequence(state->writer);
185 std::vector<Event> events;
186 std::vector<Subscription> detached;
188 std::unique_lock lock(state->mutex);
189 if (index >= state->items.size())
return;
190 count = std::min(count, state->items.size() - index);
191 if (count == 0)
return;
192 events.reserve(count);
193 for (std::size_t i = 0; i < count; ++i) {
196 bool repair_prefix =
false;
197 for (
const auto& event : events) {
198 const auto record = state->records.find(event.item.get());
199 if (--record->second.count == 0) {
200 if (record->second.subscription) detached.push_back(std::move(record->second.subscription));
201 state->records.erase(record);
202 }
else if (record->second.index >= index && record->second.index < index + count) {
203 record->second.index = std::numeric_limits<std::size_t>::max();
204 repair_prefix =
true;
207 const auto begin = state->items.begin() +
static_cast<std::ptrdiff_t
>(index);
208 state->items.erase(begin, begin +
static_cast<std::ptrdiff_t
>(count));
209 reindex_(state, index);
211 for (std::size_t i = index; i-- > 0;) {
212 auto& record = state->records.at(state->items[i].get());
213 if (record.index == std::numeric_limits<std::size_t>::max()) record.index = i;
217 emit_batch_(state, std::move(events), size_(state));
220 template<std::predicate<const T&> Pred>
223 std::lock_guard sequence(state->writer);
226 std::shared_lock lock(state->mutex);
228 while (index < state->
items.
size() && !predicate(*state->items[index])) ++index;
229 if (index == state->items.size())
return false;
236 template<std::predicate<const T&> Pred>
239 std::lock_guard sequence(state->writer);
240 std::vector<Event> events;
241 std::vector<Subscription> detached;
243 std::unique_lock lock(state->mutex);
244 std::vector<bool> remove;
245 remove.reserve(state->items.size());
248 std::size_t removed_count = 0;
249 std::size_t subscriptions = 0;
250 for (
const auto& item : state->items) {
251 const bool selected = predicate(*item);
252 remove.push_back(selected);
255 if constexpr (
requires(T& value) { value.on_changed(std::declval<std::function<
void(
const T&)>>()); }) {
256 if (state->records.at(item.get()).subscription) ++subscriptions;
261 events.reserve(removed_count);
262 detached.reserve(subscriptions);
263 std::size_t write = 0;
264 for (std::size_t read = 0; read < state->items.size(); ++read) {
267 const auto record = state->records.find(state->items[read].get());
268 if (--record->second.count == 0) {
269 if (record->second.subscription) detached.push_back(std::move(record->second.subscription));
270 state->records.erase(record);
273 state->records.at(state->items[read].get()).index = write;
274 if (read != write) state->items[write] = std::move(state->items[read]);
278 state->items.erase(state->items.begin() +
static_cast<std::ptrdiff_t
>(write), state->items.end());
280 const auto count = events.size();
281 emit_batch_(state, std::move(events), size_(state));
285 void replace_at(std::size_t index, std::shared_ptr<T> item) {
287 std::lock_guard sequence(state->writer);
288 std::shared_ptr<T> previous;
291 std::unique_lock lock(state->mutex);
292 if (index >= state->items.size())
return;
293 previous = std::exchange(state->items[index], item);
294 if (previous != item) {
295 detached = drop_(state, previous.get());
296 auto& record = state->records[item.get()];
297 record.index = record.count ? std::max(record.index, index) : index;
302 [state, item] { install_(state, item); });
305 void move(std::size_t from, std::size_t to) {
307 std::lock_guard sequence(state->writer);
308 std::shared_ptr<T> item;
310 std::unique_lock lock(state->mutex);
311 auto&
items = state->items;
314 const auto first =
items.
begin() +
static_cast<std::ptrdiff_t
>(std::min(from, to));
315 const auto last =
items.
begin() +
static_cast<std::ptrdiff_t
>(std::max(from, to)) + 1;
316 std::rotate(first, from < to ? first + 1 : last - 1, last);
317 reindex_(state, std::min(from, to));
324 std::lock_guard sequence(state->writer);
325 std::vector<std::shared_ptr<T>> removed;
326 std::unordered_map<const T*, Record> detached;
328 std::unique_lock lock(state->mutex);
329 removed.swap(state->items);
330 detached.swap(state->records);
336 const void*
operator()(
const T& value)
const noexcept {
return &value; }
343 template<
typename KeyFn = AddressIdentity>
344 std::size_t
reconcile(std::vector<std::shared_ptr<T>> next, KeyFn key_of = {}) {
345 using Key = std::decay_t<std::invoke_result_t<KeyFn, const T&>>;
347 std::lock_guard sequence(state->writer);
349 std::erase(next, std::shared_ptr<T>{});
350 std::unordered_map<Key, std::size_t> wanted;
351 wanted.reserve(next.size());
352 bool duplicates =
false;
353 for (std::size_t i = 0; i < next.size(); ++i) {
354 if (!wanted.emplace(key_of(*next[i]), i).second) duplicates =
true;
356 std::size_t count = 0;
357 state->signal->batch([&] {
361 count = next.size() + 1;
363 const auto before = current.
snapshot();
364 for (std::size_t i = before.size(); i-- > 0;) {
365 if (!wanted.contains(key_of(*before[i]))) { current.
remove_at(i); ++count; }
367 for (std::size_t target = 0; target < next.size(); ++target) {
368 const auto key = key_of(*next[target]);
371 std::shared_ptr<T> item;
373 std::shared_lock lock(state->mutex);
374 length = state->items.size();
376 while (found < length && key_of(*state->items[found]) != key) ++found;
377 if (found < length) item = state->items[found];
379 if (found == length) { current.
insert(target, next[target]); ++count; }
381 if (found != target) { current.
move(found, target); ++count; }
382 if (item != next[target]) { current.
replace_at(target, next[target]); ++count; }
385 while (current.
size() > next.size()) { current.
remove_at(current.
size() - 1); ++count; }
391 [[nodiscard]] std::size_t
index_of(
const T* item)
const {
392 std::shared_lock lock(state_->mutex);
393 const auto found = state_->records.find(item);
394 return found == state_->records.end() ? state_->items.size() : found->second.index;
397 std::shared_lock lock(state_->mutex);
398 return state_->records.contains(item);
402 std::shared_ptr<SharedState> state_ = std::make_shared<SharedState>();
403 std::shared_ptr<Signal> signal_ = state_->signal;
405 : state_(
std::
move(state)), signal_(state_->signal) {}
407 static std::size_t size_(
const std::shared_ptr<SharedState>& state) {
408 std::shared_lock lock(state->mutex);
409 return state->items.size();
411 static void reindex_(
const std::shared_ptr<SharedState>& state, std::size_t from) {
412 for (std::size_t i = from; i < state->items.size(); ++i) state->records.at(state->items[i].get()).index = i;
415 static Subscription drop_(
const std::shared_ptr<SharedState>& state,
const T* item) {
416 const auto found = state->records.find(item);
417 if (--found->second.count == 0) {
418 auto subscription = std::move(found->second.subscription);
419 state->records.erase(found);
424 for (std::size_t i = state->items.size(); i-- > 0;) {
425 if (state->items[i].get() == item) { found->second.index = i;
break; }
429 template<
typename U = T>
430 static auto subscribe_(
const std::shared_ptr<SharedState>& owner, U* item)
431 ->
decltype(item->on_changed(std::declval<std::function<
void(
const U&)>>()), Subscription{}) {
432 std::weak_ptr<SharedState> weak = owner;
434 return item->on_changed([weak, raw](
const U&) {
435 const auto state = weak.lock();
437 std::lock_guard sequence(state->writer);
439 std::vector<Event> repeated;
441 std::shared_lock lock(state->mutex);
442 const auto found = state->records.find(raw);
443 if (found == state->records.end())
return;
444 if (found->second.count == 1) {
445 const auto index = found->second.index;
448 repeated.reserve(found->second.count);
449 for (std::size_t i = 0; i < state->items.size(); ++i) {
454 if (repeated.empty()) emit_(state, std::move(event), size_(state));
455 else emit_batch_(state, std::move(repeated), size_(state));
458 static Subscription subscribe_(
const std::shared_ptr<SharedState>&, ...) {
return {}; }
460 static void install_(
const std::shared_ptr<SharedState>& state,
const std::shared_ptr<T>& item) {
463 std::unique_lock lock(state->mutex);
464 const auto found = state->records.find(item.get());
465 if (found == state->records.end() || found->second.subscription || found->second.installing)
return;
466 found->second.installing =
true;
468 Subscription subscription;
469 try { subscription = subscribe_(state, item.get()); }
471 std::unique_lock lock(state->mutex);
472 const auto found = state->records.find(item.get());
473 if (found != state->records.end()) found->second.installing =
false;
476 std::unique_lock lock(state->mutex);
477 const auto found = state->records.find(item.get());
478 if (found == state->records.end())
return;
479 found->second.installing =
false;
480 found->second.subscription = std::move(subscription);
483 static void trace_(
const Event& event, std::size_t length) {
485 const char* name =
"Reset";
486 switch (event.kind) {
494 ::aria::trace::List payload{std::string{name},
event.index,
event.from_index, length};
497 struct NoPreparation {
498 void operator()() const noexcept {}
501 template<
typename Prepare = NoPreparation>
502 static void emit_(
const std::shared_ptr<SharedState>& state, Event event,
503 std::size_t length, Prepare prepare = {}) {
504 state->signal->emit(event, std::move(prepare));
505 trace_(event, length);
507 template<
typename Prepare = NoPreparation>
508 static void emit_batch_(
const std::shared_ptr<SharedState>& state, std::vector<Event> events,
509 std::size_t length, Prepare prepare = {}) {
511 state->signal->emit_batch(events, std::move(prepare));
512 for (
const auto& event : events) trace_(event, length);
513 }
else state->signal->emit_batch(std::move(events), std::move(prepare));
Definition observable_list.hpp:77
SnapshotRange(std::vector< std::shared_ptr< T > > data) noexcept
Definition observable_list.hpp:84
bool empty() const noexcept
Definition observable_list.hpp:90
std::shared_ptr< T > value_type
Definition observable_list.hpp:79
const std::shared_ptr< T > & operator[](std::size_t i) const
Definition observable_list.hpp:91
const_iterator begin() const noexcept
Definition observable_list.hpp:87
std::size_t size() const noexcept
Definition observable_list.hpp:89
const_iterator end() const noexcept
Definition observable_list.hpp:88
typename std::vector< std::shared_ptr< T > >::const_iterator const_iterator
Definition observable_list.hpp:80
const_iterator iterator
Definition observable_list.hpp:82
Observable sequence of owning element handles.
Definition observable_list.hpp:40
std::vector< std::shared_ptr< T > > snapshot() const
Definition observable_list.hpp:72
std::size_t reconcile(std::vector< std::shared_ptr< T > > next, KeyFn key_of={})
Reconcile by unique key.
Definition observable_list.hpp:344
SnapshotRange items() const
Return a thread-safe, std::ranges-compatible snapshot range over the element handles.
Definition observable_list.hpp:101
std::size_t remove_all(Pred &&predicate)
Definition observable_list.hpp:237
std::size_t size() const
Definition observable_list.hpp:66
void insert_range(std::size_t index, InputIt first, InputIt last)
One O(n + k) insertion and k owning Insert events, in forward order.
Definition observable_list.hpp:142
ObservableList(const ObservableList &)=delete
void push_back(std::shared_ptr< T > item)
Definition observable_list.hpp:103
std::shared_ptr< T > emplace_back(Args &&... args)
Definition observable_list.hpp:120
bool remove_first(Pred &&predicate)
Definition observable_list.hpp:221
void replace_at(std::size_t index, std::shared_ptr< T > item)
Definition observable_list.hpp:285
void remove_range(std::size_t index, std::size_t count)
Removes in forward event order, each at the same replay pivot.
Definition observable_list.hpp:182
void remove_at(std::size_t index)
Definition observable_list.hpp:165
void clear()
Definition observable_list.hpp:322
std::shared_ptr< T > at(std::size_t index) const
Definition observable_list.hpp:68
detail::ListSignal< T > Signal
Definition observable_list.hpp:59
void move(std::size_t from, std::size_t to)
Definition observable_list.hpp:305
std::size_t index_of(const T *item) const
Definition observable_list.hpp:391
ObservableList & operator=(const ObservableList &)=delete
bool empty() const
Definition observable_list.hpp:67
bool contains(const T *item) const
Definition observable_list.hpp:396
ObservableList & operator=(ObservableList &&)=delete
void insert(std::size_t index, std::shared_ptr< T > item)
Definition observable_list.hpp:126
T value_type
Definition observable_list.hpp:58
ObservableList(ObservableList &&)=delete
RAII handle to a single subscription.
Definition subscription.hpp:44
void publish_trace_unchecked(const TraceEvent &event) noexcept
Publish an already-built event using one owning sink snapshot.
Definition diagnostics.hpp:293
bool has_trace_sink() noexcept
True iff a sink is currently installed.
Definition diagnostics.hpp:286
@ List
ObservableList / FilteredList / SortedList / MappedList.
Definition diagnostics.hpp:72
@ Replace
Definition list_change.hpp:10
@ Remove
Definition list_change.hpp:10
@ Reset
Definition list_change.hpp:10
@ Move
Definition list_change.hpp:10
@ ItemChanged
Definition list_change.hpp:10
@ Insert
Definition list_change.hpp:10
Definition validation_key.hpp:110
An owning event in a sequential list edit stream.
Definition list_change.hpp:16
static ListChange cleared()
Definition list_change.hpp:29
Definition observable_list.hpp:335
const void * operator()(const T &value) const noexcept
Definition observable_list.hpp:336