Nui
range_event_context.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <nui/utility/assert.hpp>
4 
5 #include <interval-tree/interval_tree.hpp>
6 #include <interval-tree/tree_hooks.hpp>
7 
8 #include <cstddef>
9 #include <algorithm>
10 #include <stdexcept>
11 #include <vector>
12 #include <optional>
13 
14 namespace Nui
15 {
17  {
18  Keep = 0b0001,
19  Modify = 0b0010,
20  Insert = 0b0100,
21  Erase = 0b1000
22  };
23 
24  namespace Detail
25  {
26  template <typename ValueType, typename IntervalKind = lib_interval_tree::closed>
27  class RangeStateInterval;
28 
29  template <typename ValueType, typename IntervalKind>
31  {
32  public:
33  using value_type = ValueType;
34  using interval_kind = IntervalKind;
35 
36  public:
38  : low_{low}
39  , high_{high}
40  {}
42  {
43  low_ = low;
44  high_ = high;
45  }
46  friend bool operator==(RangeStateInterval const& lhs, RangeStateInterval const& rhs)
47  {
48  return lhs.start_ == rhs.start_ && lhs.end_ == rhs.end_ && lhs.type_ == rhs.type_;
49  }
50  friend bool operator!=(RangeStateInterval const& lhs, RangeStateInterval const& rhs)
51  {
52  return !(lhs == rhs);
53  }
54  value_type low() const
55  {
56  return low_;
57  }
58  value_type high() const
59  {
60  return high_;
61  }
62  void low(value_type value)
63  {
64  low_ = value;
65  }
66  void high(value_type value)
67  {
68  high_ = value;
69  }
70  bool overlaps(value_type l, value_type h) const
71  {
72  // return low_ <= h && l <= high_;
73  return overlapsOrIsAdjacent(l, h);
74  }
75  // looks inclusive, but inclusive now means adjacent:
77  {
78  return low_ <= h && l <= high_;
79  }
80  bool overlaps(RangeStateInterval const& other) const
81  {
82  // return overlaps(other.low_, other.high_);
83  return overlapsOrIsAdjacent(other);
84  }
86  {
87  return low_ <= h + 1 && l - 1 <= high_;
88  }
89  bool overlapsOrIsAdjacent(RangeStateInterval const& other) const
90  {
91  return low_ <= other.high_ + 1 && other.low_ - 1 <= high_;
92  }
93  bool overlaps_exclusive(RangeStateInterval const& other) const
94  {
95  return overlaps_exclusive(other.low_, other.high_);
96  }
97  bool within(value_type value) const
98  {
99  return interval_kind::within(low_, high_, value);
100  }
101  bool within(RangeStateInterval const& other) const
102  {
103  return low_ <= other.low_ && high_ >= other.high_;
104  }
105  void shiftRight(value_type offset)
106  {
107  low_ += offset;
108  high_ += offset;
109  }
110  void shiftLeft(value_type offset)
111  {
112  low_ -= offset;
113  high_ -= offset;
114  }
115  bool isLeftOf(RangeStateInterval const& other) const
116  {
117  return high_ < other.low_;
118  }
120  {
121  if (overlaps(other))
122  return 0;
123  if (high_ < other.low_)
124  return other.low_ - high_;
125  else
126  return low_ - other.high_;
127  }
128  value_type size() const
129  {
130  return high_ - low_;
131  }
132  // undefined if they do not overlap
134  {
135  const auto low = std::min(low_, other.low_);
136  // +1, because if they overlap they share a side, so its not double counted
137  // [0, 1] and [1, 2] -> [0, 2]
138  // [8, 8] and [8, 8] -> [8, 9]
139  const auto high = low + size() + other.size() + 1;
140  return {low, high};
141  }
143  {
144  return RangeStateInterval{std::min(low_, other.low_), std::max(high_, other.high_)};
145  }
146 
147  private:
148  value_type low_;
149  value_type high_;
150  };
151 
153  : public lib_interval_tree::node<long, RangeStateInterval<long>, CustomIntervalTreeNode>
154  {
155  using lib_interval_tree::node<long, RangeStateInterval<long>, CustomIntervalTreeNode>::node;
156 
157  void shiftRight(long offset)
158  {
159  this->interval_.low(this->interval_.low() + offset);
160  this->interval_.high(this->interval_.high() + offset);
161  this->max_ = this->max_ + offset;
162  }
163 
164  void shiftLeft(long offset)
165  {
166  this->interval_.low(this->interval_.low() - offset);
167  this->interval_.high(this->interval_.high() - offset);
168  this->max_ = this->max_ - offset;
169  }
170  };
171 
172  struct IntervalTreeHook : public lib_interval_tree::hooks::regular
173  {
175  };
176 
177  // stream iterator for intervals
178  template <typename T, typename Kind>
179  std::ostream& operator<<(std::ostream& os, RangeStateInterval<T, Kind> const& interval)
180  {
181  os << "[" << interval.low() << ", " << interval.high() << "]";
182  return os;
183  }
184  }
185 
187  {
188  public:
189  explicit RangeEventContext()
190  : RangeEventContext(false)
191  {}
192  RangeEventContext(bool disableOptimizations)
193  : trackedRanges_{}
194  , operationType_{RangeOperationType::Keep}
195  , nextEraseOverride_{std::nullopt}
196  , fullRangeUpdate_{false}
197  , disableOptimizations_{disableOptimizations}
198  {}
199  enum class InsertResult
200  {
201  Perform,
203  Accepted,
204  Rejected
205  };
207  {
208  fullRangeUpdate_ = true;
209  }
212  bool eraseNotify(long low, long high)
213  {
214  if (operationType_ == RangeOperationType::Keep || operationType_ == RangeOperationType::Erase ||
215  fullRangeUpdate_ || disableOptimizations_ || trackedRanges_.empty())
216  return false;
217  if (operationType_ == RangeOperationType::Modify)
218  return eraseModificationFixup(low, high);
219  else
220  return eraseInsertionFixup(low, high);
221  }
222  bool eraseNotify(std::size_t low, std::size_t high)
223  {
224  return eraseNotify(static_cast<long>(low), static_cast<long>(high));
225  }
227  {
228  if (disableOptimizations_)
229  {
230  fullRangeUpdate_ = true;
231  return InsertResult::Perform;
232  }
233 
234  if (operationType_ == RangeOperationType::Keep)
235  {
236  operationType_ = type;
237  }
238  else if (operationType_ != type)
240 
241  switch (type)
242  {
243  default:
244  return InsertResult::Rejected;
246  {
247  // Insert and merge interval only:
248  trackedRanges_.insert_overlap({low, high});
249  break;
250  }
252  {
253  insertInsertRange(low, high);
254  break;
255  }
257  {
258  if (nextEraseOverride_)
259  {
260  insertEraseRange(nextEraseOverride_->low(), nextEraseOverride_->high());
261  nextEraseOverride_ = std::nullopt;
262  }
263  else
264  insertEraseRange(low, high);
265  break;
266  }
267  }
268 
269  return InsertResult::Accepted;
270  }
271  InsertResult insertModificationRange(std::size_t low, std::size_t high, RangeOperationType type)
272  {
273  return insertModificationRange(static_cast<long>(low), static_cast<long>(high), type);
274  }
275  void reset(bool requireFullRangeUpdate = false)
276  {
277  trackedRanges_.clear();
278  fullRangeUpdate_ = requireFullRangeUpdate;
279  operationType_ = RangeOperationType::Keep;
280  }
281  bool isInDefaultState() const
282  {
283  return trackedRanges_.empty() && operationType_ == RangeOperationType::Keep;
284  }
285  bool isFullRangeUpdate() const noexcept
286  {
287  return fullRangeUpdate_;
288  }
289  auto begin() const
290  {
291  return trackedRanges_.begin();
292  }
293  auto end() const
294  {
295  return trackedRanges_.end();
296  }
297  auto rbegin() const
298  {
299  return trackedRanges_.rbegin();
300  }
301  auto rend() const
302  {
303  return trackedRanges_.rend();
304  }
306  {
307  return operationType_;
308  }
309 
310  private:
311  void insertInsertRange(long low, long high)
312  {
313  // TODO: perform optimization using interval tree specialization for shifting subtrees.
314  auto newRange = Detail::RangeStateInterval<long>{low, high};
315 
316  // find insert at same position first to move previous insert at that position over:
317  auto it = trackedRanges_.find(newRange, [](auto const& a, auto const& b) {
318  return a.low() == b.low();
319  });
320 
321  if (it != trackedRanges_.end())
322  {
323  newRange = it->expand(newRange);
324  trackedRanges_.erase(it);
325  it = trackedRanges_.insert(newRange);
326  }
327  else
328  {
329  it = trackedRanges_.insert(newRange);
330  }
331  if (it != std::end(trackedRanges_))
332  ++it;
333 
334  // move all subsequent intervals to the right:
335  // TODO: This is the expensive part and should be optimized.
336  for (; it != trackedRanges_.end(); ++it)
337  {
338  it.node()->shiftRight(high - low + 1);
339  }
340  }
341  void insertEraseRange(long low, long high)
342  {
343  auto newRange = Detail::RangeStateInterval<long>{low, high};
344  const bool processed = [&newRange, this]() {
345  for (auto it = trackedRanges_.begin(); it != trackedRanges_.end(); ++it)
346  {
347  // Shift erasure over when previous erasures shrank the range,
348  // since erasures are replayed from the end.
349  if (it->low() < newRange.low())
350  {
351  // +1 because the range includes its end
352  newRange.shiftRight(it->size() + 1);
353  }
354  else if (it->overlapsOrIsAdjacent(newRange))
355  {
356  do
357  {
358  newRange = it->expand(newRange);
359 
360  // Remove the old range and insert the new one
361  it = trackedRanges_.erase(it);
362  } while (it != trackedRanges_.end() && it->overlapsOrIsAdjacent(newRange));
363  trackedRanges_.insert(newRange);
364  return true;
365  }
366  else
367  {
368  return false;
369  }
370  }
371  return false;
372  }();
373  if (!processed)
374  {
375  trackedRanges_.insert_overlap(newRange);
376  }
377  }
378 
385  bool eraseInsertionFixup(long low, long high)
386  {
387  const auto lastInterval = *trackedRanges_.rbegin();
388 
389  // If the erase interval is left of the last insert interval, we must apply the changes and
390  // retry. The following optimization would make the insert positions invalid.
391  // TODO: Moving them might be possible, but expensive.
392  if (high < lastInterval.high())
393  return true;
394 
395  auto eraseRange = Detail::RangeStateInterval<long>{low, high};
396  auto eraseRangeOrig = eraseRange;
397 
398  auto iter = trackedRanges_.overlap_find(eraseRange, true);
399  // if erase is completely at the end of a previous insert, we can cut the inserted elements out.
400  for (; iter != trackedRanges_.end(); iter = trackedRanges_.overlap_find(eraseRangeOrig, true))
401  {
402  if (iter == trackedRanges_.end())
403  return true;
404  const auto insertInterval = *iter;
405 
406  // erase overlaps insert to the end or over:
407  if (eraseRangeOrig.high() >= insertInterval.high())
408  {
409  trackedRanges_.erase(iter);
410  // if beginning of insert is left of erase, we have to insert the left part of the insert
411  if (insertInterval.low() < eraseRangeOrig.low())
412  {
413  trackedRanges_.insert({insertInterval.low(), eraseRangeOrig.low() - 1});
414  eraseRangeOrig.high(-insertInterval.high() + eraseRangeOrig.low() + eraseRangeOrig.high() - 1);
415  }
416 
417  eraseRange.high(eraseRange.high() - insertInterval.size() - 1);
418  }
419  else
420  {
421  break; // bail out
422  }
423  }
424 
425  if (eraseRange.high() != high)
426  nextEraseOverride_ = eraseRange;
427 
428  // Other tracking cases are too complicated.
429  // The reason is that cutting the intervals is not enough, we also have to modify where these insertions are
430  // taken from.
431  return true;
432  }
433 
440  bool eraseModificationFixup(long low, long high)
441  {
442  auto range = Detail::RangeStateInterval<long>{low, high};
443 
444  auto iter = trackedRanges_.overlap_find(range, false);
445 
446  if (iter == trackedRanges_.end())
447  {
448  // If we dont have an overlap, there might still be a case where modifications exists
449  // after the erase. In that case we have to apply these changes immediately and retry the erase.
450  // (Shifting the previous modifications would work too, but is more expensive)
451  const auto lastInterval = *trackedRanges_.rbegin();
452 
453  // If the erase interval is left of the last modification interval, we must apply the changes and
454  // retry. An overlap would have been found otherwise.
455  if (high < lastInterval.low())
456  return true;
457 
458  return false;
459  }
460 
461  // find all overlapping modifications and cut them
462  for (; iter != trackedRanges_.end(); iter = trackedRanges_.overlap_find(range, false))
463  {
464  const auto modInterval = *iter;
465  trackedRanges_.erase(iter);
466 
467  // cut erasure from found modification interval:
468  // 1. erase is within modification interval:
469  if (modInterval.within(range))
470  {
471  if (modInterval.low() < range.low())
472  trackedRanges_.insert({modInterval.low(), range.low() - 1});
473  if (range.high() < modInterval.high())
474  trackedRanges_.insert({range.high() + 1, modInterval.high()});
475  return true; // cannot overlap any further
476  }
477  else if (modInterval.low() < range.low())
478  {
479  // 2. erase starts within modification interval:
480  trackedRanges_.insert({modInterval.low(), range.low() - 1});
481 
482  // reduce range to right part:
483  range.low(range.low() + 1);
484  if (range.low() > range.high())
485  return true;
486  }
487  else if (range.high() < modInterval.high())
488  {
489  // 3. erase ends within modification interval:
490  trackedRanges_.insert({range.high() + 1, modInterval.high()});
491 
492  // reduce range to left part:
493  range.high(range.high() - 1);
494  if (range.low() > range.high())
495  return true;
496  }
497  else if (range.within(modInterval))
498  {
499  // 4. erase encompasses modification interval, only deletion is necessary:
500  continue;
501  }
502  else
503  {
504  NUI_ASSERT(false, "Overlap without overlapping?");
505  }
506  }
507  return true;
508  }
509 
510  private:
511  lib_interval_tree::interval_tree<Detail::RangeStateInterval<long>, Detail::IntervalTreeHook> trackedRanges_;
512  RangeOperationType operationType_;
513  std::optional<Detail::RangeStateInterval<long>> nextEraseOverride_;
514  bool fullRangeUpdate_;
515  bool disableOptimizations_;
516  };
517 }
#define NUI_ASSERT(condition, message)
Definition: assert.hpp:31
Definition: range_event_context.hpp:31
bool overlaps(RangeStateInterval const &other) const
Definition: range_event_context.hpp:80
friend bool operator==(RangeStateInterval const &lhs, RangeStateInterval const &rhs)
Definition: range_event_context.hpp:46
bool overlaps_exclusive(RangeStateInterval const &other) const
Definition: range_event_context.hpp:93
bool within(RangeStateInterval const &other) const
Definition: range_event_context.hpp:101
RangeStateInterval join(RangeStateInterval const &other) const
Definition: range_event_context.hpp:142
bool isLeftOf(RangeStateInterval const &other) const
Definition: range_event_context.hpp:115
IntervalKind interval_kind
Definition: range_event_context.hpp:34
void high(value_type value)
Definition: range_event_context.hpp:66
value_type high() const
Definition: range_event_context.hpp:58
void low(value_type value)
Definition: range_event_context.hpp:62
bool overlaps(value_type l, value_type h) const
Definition: range_event_context.hpp:70
value_type operator-(RangeStateInterval const &other) const
Definition: range_event_context.hpp:119
ValueType value_type
Definition: range_event_context.hpp:33
friend bool operator!=(RangeStateInterval const &lhs, RangeStateInterval const &rhs)
Definition: range_event_context.hpp:50
bool overlapsOrIsAdjacent(value_type l, value_type h) const
Definition: range_event_context.hpp:85
bool within(value_type value) const
Definition: range_event_context.hpp:97
void reset(value_type low, value_type high)
Definition: range_event_context.hpp:41
void shiftLeft(value_type offset)
Definition: range_event_context.hpp:110
bool overlaps_exclusive(value_type l, value_type h) const
Definition: range_event_context.hpp:76
RangeStateInterval expand(RangeStateInterval const &other) const
Definition: range_event_context.hpp:133
bool overlapsOrIsAdjacent(RangeStateInterval const &other) const
Definition: range_event_context.hpp:89
value_type size() const
Definition: range_event_context.hpp:128
void shiftRight(value_type offset)
Definition: range_event_context.hpp:105
RangeStateInterval(value_type low, value_type high)
Definition: range_event_context.hpp:37
value_type low() const
Definition: range_event_context.hpp:54
Definition: range_event_context.hpp:187
auto begin() const
Definition: range_event_context.hpp:289
InsertResult insertModificationRange(long low, long high, RangeOperationType type)
Definition: range_event_context.hpp:226
bool eraseNotify(std::size_t low, std::size_t high)
Definition: range_event_context.hpp:222
RangeOperationType operationType() const noexcept
Definition: range_event_context.hpp:305
bool isFullRangeUpdate() const noexcept
Definition: range_event_context.hpp:285
auto rend() const
Definition: range_event_context.hpp:301
auto rbegin() const
Definition: range_event_context.hpp:297
bool isInDefaultState() const
Definition: range_event_context.hpp:281
void reset(bool requireFullRangeUpdate=false)
Definition: range_event_context.hpp:275
InsertResult insertModificationRange(std::size_t low, std::size_t high, RangeOperationType type)
Definition: range_event_context.hpp:271
RangeEventContext()
Definition: range_event_context.hpp:189
RangeEventContext(bool disableOptimizations)
Definition: range_event_context.hpp:192
auto end() const
Definition: range_event_context.hpp:293
void performFullRangeUpdate()
Definition: range_event_context.hpp:206
InsertResult
Definition: range_event_context.hpp:200
bool eraseNotify(long low, long high)
Done before the erasure is performed:
Definition: range_event_context.hpp:212
std::ostream & operator<<(std::ostream &os, RangeStateInterval< T, Kind > const &interval)
Definition: range_event_context.hpp:179
Definition: file_dialog.hpp:6
UnoptimizedRange< IteratorAccessor< ContainerT const >, std::decay_t< Detail::ObservedAddReference_t< Observed > >... > range(ContainerT const &container, Observed &&... observed)
Definition: range.hpp:97
RangeOperationType
Definition: range_event_context.hpp:17
@ Modify
Definition: range_event_context.hpp:19
@ Insert
Definition: range_event_context.hpp:20
@ Erase
Definition: range_event_context.hpp:21
@ Keep
Definition: range_event_context.hpp:18
Definition: range_event_context.hpp:154
void shiftRight(long offset)
Definition: range_event_context.hpp:157
void shiftLeft(long offset)
Definition: range_event_context.hpp:164
Definition: range_event_context.hpp:173