64 using RoleFn = std::function<QVariant(
const T&,
int role)>;
70 requires ::aria::ListSourceOf<L, T>
74 QObject* parent =
nullptr)
75 : QAbstractListModel(parent),
76 roles_(
std::move(roles)),
78 snapshot_(source.snapshot()) {
79 check_size_(snapshot_.size());
80 delivery_->model =
this;
81 std::weak_ptr<Delivery> weak = delivery_;
82 sub_ = source.observe([weak](const ::aria::ListChange<T>& change) {
83 if (
auto delivery = weak.lock()) enqueue_(delivery, change);
88 Q_ASSERT(QThread::currentThread() == thread());
89 std::deque<::aria::ListChange<T>> retired;
91 std::lock_guard lock(delivery_->mutex);
92 delivery_->model =
nullptr;
93 retired.swap(delivery_->pending);
100 int rowCount(
const QModelIndex& parent = QModelIndex{})
const override {
101 if (parent.isValid())
return 0;
102 return static_cast<int>(snapshot_.size());
105 QVariant
data(
const QModelIndex& index,
106 int role = Qt::DisplayRole)
const override {
107 if (!index.isValid() || index.model() !=
this || index.column() != 0)
return {};
108 const auto row = index.row();
109 if (row < 0 || row >=
rowCount())
return {};
110 auto item = snapshot_[
static_cast<std::size_t
>(row)];
111 if (!item)
return {};
115 auto project = role_fn_;
116 return (*project)(*item, role);
130 Q_ASSERT(QThread::currentThread() == thread());
131 QPointer<ObservableListModel> alive(
this);
133 if (alive) endResetModel();
140 std::deque<::aria::ListChange<T>> pending;
141 bool scheduled =
false;
144 static void check_size_(std::size_t size) {
145 if (size >
static_cast<std::size_t
>(std::numeric_limits<int>::max()))
146 throw std::length_error(
"ObservableListModel: row count exceeds Qt's int range");
149 static void enqueue_(
const std::shared_ptr<Delivery>& delivery,
150 const ::aria::ListChange<T>& change) {
151 std::unique_lock lock(delivery->mutex);
152 auto* model = delivery->model;
154 delivery->pending.push_back(change);
155 if (delivery->scheduled)
return;
156 delivery->scheduled =
true;
157 if (QThread::currentThread() == model->thread()) {
163 QMetaObject::invokeMethod(model, [weak = std::weak_ptr<Delivery>(delivery)] {
164 if (
auto current = weak.lock()) drain_(current);
165 }, Qt::QueuedConnection);
169 static void drain_(
const std::shared_ptr<Delivery>& delivery)
noexcept {
171 ::aria::ListChange<T> change{};
174 std::lock_guard lock(delivery->mutex);
175 model = delivery->model;
176 if (!model || delivery->pending.empty()) {
177 delivery->scheduled =
false;
180 change = std::move(delivery->pending.front());
181 delivery->pending.pop_front();
185 try { model->apply_change_(change); }
192 void apply_change_(const ::aria::ListChange<T>& ch) {
193 Q_ASSERT(QThread::currentThread() == thread());
194 QPointer<ObservableListModel> alive(
this);
196 case ::aria::ListChangeKind::Insert: {
197 if (ch.index > snapshot_.size())
198 throw std::out_of_range(
"ObservableListModel: invalid insert index");
199 check_size_(snapshot_.size() + 1);
202 if (snapshot_.size() == snapshot_.capacity()) {
203 const auto capacity = snapshot_.capacity();
204 constexpr auto maximum =
static_cast<std::size_t
>(std::numeric_limits<int>::max());
205 snapshot_.reserve(capacity > maximum / 2
206 ? maximum : (capacity == 0 ? 1 : capacity * 2));
208 auto row =
static_cast<int>(ch.index);
209 beginInsertRows(QModelIndex{}, row, row);
211 snapshot_.insert(snapshot_.begin() +
static_cast<std::ptrdiff_t
>(ch.index),
216 case ::aria::ListChangeKind::Remove: {
217 auto row =
static_cast<int>(ch.index);
218 if (ch.index >= snapshot_.size())
return;
219 beginRemoveRows(QModelIndex{}, row, row);
221 snapshot_.erase(snapshot_.begin() +
static_cast<std::ptrdiff_t
>(ch.index));
225 case ::aria::ListChangeKind::Replace:
226 case ::aria::ListChangeKind::ItemChanged: {
227 if (ch.index >= snapshot_.size())
return;
228 auto retired = std::move(snapshot_[ch.index]);
229 snapshot_[ch.index] = ch.item;
230 auto idx = createIndex(
static_cast<int>(ch.index), 0);
231 Q_EMIT dataChanged(idx, idx, roles_.keys());
234 case ::aria::ListChangeKind::Move: {
240 if (ch.from_index >= snapshot_.size()
241 || ch.index >= snapshot_.size()
242 || ch.from_index == ch.index)
return;
244 const auto from =
static_cast<int>(ch.from_index);
245 const auto to =
static_cast<int>(ch.index);
246 const int dest = (to > from) ? to + 1 : to;
248 if (!beginMoveRows(QModelIndex{}, from, from, QModelIndex{}, dest) || !alive)
return;
249 auto moved = snapshot_[ch.from_index];
250 snapshot_.erase(snapshot_.begin()
251 +
static_cast<std::ptrdiff_t
>(ch.from_index));
252 snapshot_.insert(snapshot_.begin()
253 +
static_cast<std::ptrdiff_t
>(ch.index),
258 case ::aria::ListChangeKind::Reset: {
260 throw std::invalid_argument(
"ObservableListModel: Reset requires its snapshot");
261 check_size_(ch.snapshot->size());
262 auto next = *ch.snapshot;
265 snapshot_.swap(next);
273 std::shared_ptr<RoleFn> role_fn_;
274 std::vector<std::shared_ptr<T>> snapshot_;
275 std::shared_ptr<Delivery> delivery_ = std::make_shared<Delivery>();
276 ::aria::Subscription sub_;