From a3af0e676b8717b633a8d2d266f9b598aa96bde0 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Wed, 1 Jan 2025 22:11:51 +0200 Subject: [PATCH 1/2] BSTArray: Added insert/emplace to any position BSTEvent: Added PrependEventSink. This allows to control the order at which you want to sinks to receive an event. --- include/RE/B/BSTArray.h | 78 +++++++++++++++++++++++++- include/RE/B/BSTEvent.h | 42 ++++++++++++++ include/RE/S/ScriptEventSourceHolder.h | 6 ++ 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/include/RE/B/BSTArray.h b/include/RE/B/BSTArray.h index 5c54a684e..974b1c1b5 100644 --- a/include/RE/B/BSTArray.h +++ b/include/RE/B/BSTArray.h @@ -406,6 +406,23 @@ namespace RE set_size(newSize); } + inline BSTArray(const std::initializer_list a_list) + { + if (a_list.size() == 0) { + return; + } + + const auto newSize = a_list.size(); + const auto newData = allocate(newSize); + size_type i = 0; + for (const auto& elem : a_list) { + std::construct_at(newData + i++, elem); + } + + set_allocator_traits(newData, newSize); + set_size(newSize); + } + BSTArray(BSTArray&&) = default; explicit inline BSTArray(size_type a_count) @@ -555,6 +572,50 @@ namespace RE return elem; } + inline void insert(const_iterator position, const value_type& a_value) { emplace(position, a_value); } + inline void insert(const_iterator position, value_type&& a_value) { emplace(position, std::move(a_value)); } + + template + inline reference emplace(const_iterator position, Args&&... a_args) + { + assert(position >= cbegin() && position <= cend()); + + if (position == cend()) { + return emplace_back(std::forward(a_args)...); + } + + pointer oldData; + pointer newData; + size_type newCapacity; + + if (size() == capacity()) { + newCapacity = next_capacity(); + newData = allocate(newCapacity); // manually grow capacity to avoid unnecessary memcpy from change_capacity + oldData = data(); + } else { + newData = data(); + oldData = nullptr; + newCapacity = capacity(); + } + + const auto headPartToCopy = position - cbegin(); + const auto tailPartToCopy = cend() - position; + const auto tailBytesToCopy = tailPartToCopy * sizeof(T); + std::memcpy(newData + headPartToCopy + 1, cend() - tailPartToCopy, tailBytesToCopy); + std::construct_at(newData + headPartToCopy, std::forward(a_args)...); + const auto headBytesToCopy = headPartToCopy * sizeof(T); + std::memcpy(newData, cbegin(), headBytesToCopy); + + if (oldData) { + deallocate(oldData); + set_allocator_traits(newData, newCapacity); + } + + set_size(size() + 1); + + return *(newData + headPartToCopy); + } + inline void pop_back() { assert(!empty()); @@ -630,13 +691,24 @@ namespace RE set_size(a_newSize); } - inline void grow_capacity() { grow_capacity(capacity()); } + /// Calculates the next value for the array capacity. + /// Capacity grows exponentially: hint * 2^level, where level is the number of times the capacity has grown. + [[nodiscard]] inline size_type next_capacity() const { return next_capacity(capacity()); } - inline void grow_capacity(size_type a_hint) + /// Calculates the next value for the array capacity. + /// Capacity grows exponentially: hint * 2^level, where level is the number of times the capacity has grown. + [[nodiscard]] inline size_type next_capacity(size_type a_hint) const { auto cap = a_hint; cap = cap > 0 ? static_cast(std::ceil(static_cast(cap) * GROWTH_FACTOR)) : DF_CAP; - change_capacity(cap); + return cap; + } + + inline void grow_capacity() { grow_capacity(capacity()); } + + inline void grow_capacity(size_type a_hint) + { + change_capacity(next_capacity(a_hint)); } inline void release() diff --git a/include/RE/B/BSTEvent.h b/include/RE/B/BSTEvent.h index eb73b18a2..191d0cd17 100644 --- a/include/RE/B/BSTEvent.h +++ b/include/RE/B/BSTEvent.h @@ -61,6 +61,48 @@ namespace RE AddEventSink(a_sink); } + /// Adds an event sink to the front of sinks list. + /// + /// When there is an ongoing notification, sinks are prepended to a pending list. + /// Consider the following case: Add(A), Add(B), notifying=true, Add(C), Prepend(D), Prepend(E), notifying = false + /// Sinks: A, B + /// Pending: E, D, C + /// Result: A, B, E, D, C + /// + /// However without notifying the same chain of calls will look like this: + /// Sinks: E, D, A, B, C + /// + /// The relative order of C, D, and E is guaranteed in both cases, but, previous sinks may appear both before and after the new sinks. + void PrependEventSink(Sink* a_eventSink) + { + if (!a_eventSink) { + return; + } + + BSSpinLockGuard locker(lock); + + if (notifying) { + if (std::find(pendingRegisters.begin(), pendingRegisters.end(), a_eventSink) == pendingRegisters.end()) { + pendingRegisters.insert(pendingRegisters.begin(), a_eventSink); + } + } else { + if (std::find(sinks.begin(), sinks.end(), a_eventSink) == sinks.end()) { + sinks.insert(sinks.begin(), a_eventSink); + } + } + + auto it = std::find(pendingUnregisters.begin(), pendingUnregisters.end(), a_eventSink); + if (it != pendingUnregisters.end()) { + pendingUnregisters.erase(it); + } + } + + template + inline void PrependEventSink(BSTEventSink* a_sink) + { + PrependEventSink(a_sink); + } + void RemoveEventSink(Sink* a_eventSink) { if (!a_eventSink) { diff --git a/include/RE/S/ScriptEventSourceHolder.h b/include/RE/S/ScriptEventSourceHolder.h index 23e3462da..ede330dbc 100644 --- a/include/RE/S/ScriptEventSourceHolder.h +++ b/include/RE/S/ScriptEventSourceHolder.h @@ -136,6 +136,12 @@ namespace RE GetEventSource()->AddEventSink(a_sink); } + template + inline void PrependEventSink(BSTEventSink* a_sink) + { + GetEventSource()->PrependEventSink(a_sink); + } + template inline void RemoveEventSink(BSTEventSink* a_sink) { From 06f80d11343ab5b2ce2ee4ccd77635b23001d595 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Wed, 1 Jan 2025 23:33:59 +0200 Subject: [PATCH 2/2] Added also push_front for convenience. --- include/RE/B/BSTArray.h | 8 ++++++-- include/RE/B/BSTEvent.h | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/RE/B/BSTArray.h b/include/RE/B/BSTArray.h index 974b1c1b5..edbe3340b 100644 --- a/include/RE/B/BSTArray.h +++ b/include/RE/B/BSTArray.h @@ -575,6 +575,9 @@ namespace RE inline void insert(const_iterator position, const value_type& a_value) { emplace(position, a_value); } inline void insert(const_iterator position, value_type&& a_value) { emplace(position, std::move(a_value)); } + inline void push_front(const value_type& a_value) { emplace(cbegin(), a_value); } + inline void push_front(value_type&& a_value) { emplace(cbegin(), std::move(a_value)); } + template inline reference emplace(const_iterator position, Args&&... a_args) { @@ -693,11 +696,12 @@ namespace RE /// Calculates the next value for the array capacity. /// Capacity grows exponentially: hint * 2^level, where level is the number of times the capacity has grown. - [[nodiscard]] inline size_type next_capacity() const { return next_capacity(capacity()); } + [[nodiscard]] + inline size_type next_capacity() const { return next_capacity(capacity()); } /// Calculates the next value for the array capacity. /// Capacity grows exponentially: hint * 2^level, where level is the number of times the capacity has grown. - [[nodiscard]] inline size_type next_capacity(size_type a_hint) const + inline size_type next_capacity(size_type a_hint) const { auto cap = a_hint; cap = cap > 0 ? static_cast(std::ceil(static_cast(cap) * GROWTH_FACTOR)) : DF_CAP; diff --git a/include/RE/B/BSTEvent.h b/include/RE/B/BSTEvent.h index 191d0cd17..919af5e8b 100644 --- a/include/RE/B/BSTEvent.h +++ b/include/RE/B/BSTEvent.h @@ -83,11 +83,11 @@ namespace RE if (notifying) { if (std::find(pendingRegisters.begin(), pendingRegisters.end(), a_eventSink) == pendingRegisters.end()) { - pendingRegisters.insert(pendingRegisters.begin(), a_eventSink); + pendingRegisters.push_front(a_eventSink); } } else { if (std::find(sinks.begin(), sinks.end(), a_eventSink) == sinks.end()) { - sinks.insert(sinks.begin(), a_eventSink); + sinks.push_front(a_eventSink); } }