diff --git a/libopenage/curve/base_curve.h b/libopenage/curve/base_curve.h index 0e8d2d8100..dc58885b98 100644 --- a/libopenage/curve/base_curve.h +++ b/libopenage/curve/base_curve.h @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -40,7 +39,7 @@ class BaseCurve : public event::EventEntity { _id{id}, _idstr{idstr}, loop{loop}, - last_element{this->container.begin()} {} + last_element{this->container.size()} {} virtual ~BaseCurve() = default; @@ -193,9 +192,9 @@ class BaseCurve : public event::EventEntity { const std::shared_ptr loop; /** - * Cache the iterator for quickly finding the last accessed element (usually the end) + * Cache the index of the last accessed element (usually the end). */ - mutable typename KeyframeContainer::iterator last_element; + mutable typename KeyframeContainer::elem_ptr last_element; }; @@ -204,7 +203,7 @@ void BaseCurve::set_last(const time::time_t &at, const T &value) { auto hint = this->container.last(at, this->last_element); // erase max one same-time value - if (hint->time == at) { + if (this->container.get(hint).time() == at) { hint--; } @@ -221,7 +220,7 @@ template void BaseCurve::set_insert(const time::time_t &at, const T &value) { auto hint = this->container.insert_after(at, value, this->last_element); // check if this is now the final keyframe - if (hint->time > this->last_element->time) { + if (this->container.get(hint).time() > this->container.get(this->last_element).time()) { this->last_element = hint; } this->changes(at); @@ -244,16 +243,18 @@ void BaseCurve::erase(const time::time_t &at) { template std::pair BaseCurve::frame(const time::time_t &time) const { - auto e = this->container.last(time, this->container.end()); - return std::make_pair(e->time, e->value); + auto e = this->container.last(time, this->container.size()); + auto elem = this->container.get(e); + return std::make_pair(elem.time(), elem.val()); } template std::pair BaseCurve::next_frame(const time::time_t &time) const { - auto e = this->container.last(time, this->container.end()); + auto e = this->container.last(time, this->container.size()); e++; - return std::make_pair(e->time, e->value); + auto elem = this->container.get(e); + return std::make_pair(elem.time(), elem.val()); } template @@ -261,7 +262,7 @@ std::string BaseCurve::str() const { std::stringstream ss; ss << "Curve[" << this->idstr() << "]{" << std::endl; for (const auto &keyframe : this->container) { - ss << " " << keyframe.time << ": " << keyframe.value << "," << std::endl; + ss << " " << keyframe.time() << ": " << keyframe.val() << "," << std::endl; } ss << "}"; @@ -272,10 +273,10 @@ template void BaseCurve::check_integrity() const { time::time_t last_time = time::TIME_MIN; for (const auto &keyframe : this->container) { - if (keyframe.time < last_time) { + if (keyframe.time() < last_time) { throw Error{MSG(err) << "curve is broken after t=" << last_time << ": " << this->str()}; } - last_time = keyframe.time; + last_time = keyframe.time(); } } diff --git a/libopenage/curve/continuous.h b/libopenage/curve/continuous.h index 3eb7170ddb..0cf438f237 100644 --- a/libopenage/curve/continuous.h +++ b/libopenage/curve/continuous.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -48,7 +48,7 @@ void Continuous::set_last(const time::time_t &at, const T &value) { auto hint = this->container.last(at, this->last_element); // erase all same-time entries - while (hint->time == at) { + while (this->container.get(hint).time() == at) { hint--; } diff --git a/libopenage/curve/discrete.h b/libopenage/curve/discrete.h index cdcb3daa31..44fe132de6 100644 --- a/libopenage/curve/discrete.h +++ b/libopenage/curve/discrete.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -55,7 +55,7 @@ template T Discrete::get(const time::time_t &time) const { auto e = this->container.last(time, this->last_element); this->last_element = e; // TODO if Caching? - return e->value; + return this->container.get(e).val(); } @@ -78,7 +78,9 @@ template std::pair Discrete::get_time(const time::time_t &time) const { auto e = this->container.last(time, this->last_element); this->last_element = e; - return std::make_pair(e->time, e->value); + + auto elem = this->container.get(e); + return std::make_pair(elem.time, elem.value); } @@ -89,12 +91,13 @@ std::optional> Discrete::get_previous(const time:: // if we're not at the container head // go back one entry. - if (e == std::begin(this->container)) { + if (e == 0) { return {}; } e--; - return std::make_pair(e->time, e->value); + auto elem = this->container.get(e); + return std::make_pair(elem.time(), elem.val()); } } // namespace openage::curve diff --git a/libopenage/curve/discrete_mod.h b/libopenage/curve/discrete_mod.h index de036bbf10..953939f975 100644 --- a/libopenage/curve/discrete_mod.h +++ b/libopenage/curve/discrete_mod.h @@ -93,7 +93,7 @@ void DiscreteMod::erase(const time::time_t &at) { BaseCurve::erase(at); if (this->time_length == at) { - this->time_length = this->last_element->time; + this->time_length = this->container.get(this->container.size() - 1).time(); } } diff --git a/libopenage/curve/interpolated.h b/libopenage/curve/interpolated.h index d5d4a1dedf..564ba1d0af 100644 --- a/libopenage/curve/interpolated.h +++ b/libopenage/curve/interpolated.h @@ -40,7 +40,7 @@ class Interpolated : public BaseCurve { template T Interpolated::get(const time::time_t &time) const { - const auto &e = this->container.last(time, this->last_element); + const auto e = this->container.last(time, this->last_element); this->last_element = e; auto nxt = e; @@ -48,21 +48,21 @@ T Interpolated::get(const time::time_t &time) const { time::time_t interval = 0; - auto offset = time - e->time; + auto offset = time - this->container.get(e).time(); - if (nxt != this->container.end()) { - interval = nxt->time - e->time; + if (nxt != this->container.size()) { + interval = this->container.get(nxt).time() - this->container.get(e).time(); } // here, offset > interval will never hold. // otherwise the underlying storage is broken. // If the next element is at the same time, just return the value of this one. - if (nxt == this->container.end() // use the last curve value - || offset == 0 // values equal -> don't need to interpolate - || interval == 0) { // values at the same time -> division-by-zero-error + if (nxt == this->container.size() // use the last curve value + || offset == 0 // values equal -> don't need to interpolate + || interval == 0) { // values at the same time -> division-by-zero-error - return e->value; + return this->container.get(e).val(); } else { // Interpolation between time(now) and time(next) that has elapsed @@ -72,7 +72,8 @@ T Interpolated::get(const time::time_t &time) const { // TODO: nxt->value - e->value will produce wrong results if // the nxt->value < e->value and curve element type is unsigned // Example: nxt = 2, e = 4; type = uint8_t ==> 2 - 4 = 254 - return e->value + (nxt->value - e->value) * elapsed_frac; + auto diff_value = (this->container.get(nxt).val() - this->container.get(e).val()) * elapsed_frac; + return this->container.get(e).val() + diff_value; } } diff --git a/libopenage/curve/keyframe.h b/libopenage/curve/keyframe.h index 484085cc70..e868783cbb 100644 --- a/libopenage/curve/keyframe.h +++ b/libopenage/curve/keyframe.h @@ -27,17 +27,46 @@ class Keyframe { * New, default-constructed element at the given time */ Keyframe(const time::time_t &time) : - time{time} {} + timestamp{time} {} /** * New element fron time and value */ Keyframe(const time::time_t &time, const T &value) : - time{time}, - value{value} {} + value{value}, + timestamp{time} {} - const time::time_t time = time::TIME_MIN; + /** + * Get the time of this keyframe. + * + * @return Keyframe time. + */ + const time::time_t &time() const { + return this->timestamp; + } + + /** + * Get the value of this keyframe. + * + * @return Keyframe value. + */ + const T &val() const { + return this->value; + } + +public: + /** + * Value of the keyframe. + * + * Can be modified by the curve if necessary. + */ T value = T{}; + +private: + /** + * Time of the keyframe. + */ + time::time_t timestamp = time::TIME_MIN; }; } // namespace openage::curve diff --git a/libopenage/curve/keyframe_container.h b/libopenage/curve/keyframe_container.h index b9e6850257..d9bb9a0bbd 100644 --- a/libopenage/curve/keyframe_container.h +++ b/libopenage/curve/keyframe_container.h @@ -34,17 +34,18 @@ class KeyframeContainer { /** * The underlaying container type. - * - * The most important property of this container is the iterator validity on - * insert and remove. */ - using container_t = std::list; + using container_t = std::vector; + + /** + * The index type to access elements in the container + */ + using elem_ptr = typename container_t::size_type; /** * The iterator type to access elements in the container */ using iterator = typename container_t::const_iterator; - using const_iterator = typename container_t::const_iterator; /** * Create a new container. @@ -75,12 +76,16 @@ class KeyframeContainer { */ size_t size() const; + const keyframe_t &get(const elem_ptr &idx) const { + return this->container.at(idx); + } + /** * Get the last element in the curve which is at or before the given time. * (i.e. elem->time <= time). Given a hint where to start the search. */ - iterator last(const time::time_t &time, - const iterator &hint) const; + elem_ptr last(const time::time_t &time, + const elem_ptr &hint) const; /** * Get the last element with elem->time <= time, without a hint where to start @@ -90,23 +95,23 @@ class KeyframeContainer { * no chance for you to have a hint (or the container is known to be nearly * empty) */ - iterator last(const time::time_t &time) const { - return this->last(time, std::end(this->container)); + elem_ptr last(const time::time_t &time) const { + return this->last(time, this->container.size()); } /** * Get the last element in the curve which is before the given time. * (i.e. elem->time < time). Given a hint where to start the search. */ - iterator last_before(const time::time_t &time, - const iterator &hint) const; + elem_ptr last_before(const time::time_t &time, + const elem_ptr &hint) const; /** * Get the last element with elem->time < time, without a hint where to start * searching. */ - iterator last_before(const time::time_t &time) const { - return this->last_before(time, std::end(this->container)); + elem_ptr last_before(const time::time_t &time) const { + return this->last_before(time, this->container.size()); } /** @@ -116,8 +121,8 @@ class KeyframeContainer { * This function is not recommended for use, whenever possible, keep a hint * to insert the data. */ - iterator insert_before(const keyframe_t &value) { - return this->insert_before(value, std::end(this->container)); + elem_ptr insert_before(const keyframe_t &value) { + return this->insert_before(value, this->container.size()); } /** @@ -127,7 +132,7 @@ class KeyframeContainer { * history size. If there is a keyframe with identical time, this will * insert the new keyframe before the old one. */ - iterator insert_before(const keyframe_t &value, const iterator &hint); + elem_ptr insert_before(const keyframe_t &value, const elem_ptr &hint); /** * Create and insert a new element without submitting a hint. The search is @@ -135,8 +140,8 @@ class KeyframeContainer { * discouraged, use it only, if your really do not have the possibility to * get a hint. */ - iterator insert_before(const time::time_t &time, const T &value) { - return this->insert_before(keyframe_t(time, value), std::end(this->container)); + elem_ptr insert_before(const time::time_t &time, const T &value) { + return this->insert_before(keyframe_t(time, value), this->container.size()); } /** @@ -144,7 +149,7 @@ class KeyframeContainer { * If there is a value with identical time, this will insert the new value * before the old one. */ - iterator insert_before(const time::time_t &time, const T &value, const iterator &hint) { + elem_ptr insert_before(const time::time_t &time, const T &value, const elem_ptr &hint) { return this->insert_before(keyframe_t(time, value), hint); } @@ -155,8 +160,8 @@ class KeyframeContainer { * `overwrite_all` == true -> overwrite all same-time elements. * `overwrite_all` == false -> overwrite the last of the time-conflict elements. */ - iterator insert_overwrite(const keyframe_t &value, - const iterator &hint, + elem_ptr insert_overwrite(const keyframe_t &value, + const elem_ptr &hint, bool overwrite_all = false); /** @@ -165,9 +170,9 @@ class KeyframeContainer { * from the end of the data. The use of this function is discouraged, use it * only, if your really do not have the possibility to get a hint. */ - iterator insert_overwrite(const time::time_t &time, const T &value) { + elem_ptr insert_overwrite(const time::time_t &time, const T &value) { return this->insert_overwrite(keyframe_t(time, value), - std::end(this->container)); + this->container.size()); } /** @@ -177,9 +182,9 @@ class KeyframeContainer { * Provide a insertion hint to abbreviate the search for the * insertion point. */ - iterator insert_overwrite(const time::time_t &time, + elem_ptr insert_overwrite(const time::time_t &time, const T &value, - const iterator &hint, + const elem_ptr &hint, bool overwrite_all = false) { return this->insert_overwrite(keyframe_t(time, value), hint, overwrite_all); } @@ -189,7 +194,7 @@ class KeyframeContainer { * conflict. Give an approximate insertion location to minimize runtime on * big-history curves. */ - iterator insert_after(const keyframe_t &value, const iterator &hint); + elem_ptr insert_after(const keyframe_t &value, const elem_ptr &hint); /** * Insert a new value at given time which will be prepended to the block of @@ -197,37 +202,37 @@ class KeyframeContainer { * time from the end of the data. The use of this function is discouraged, * use it only, if your really do not have the possibility to get a hint. */ - iterator insert_after(const time::time_t &time, const T &value) { + elem_ptr insert_after(const time::time_t &time, const T &value) { return this->insert_after(keyframe_t(time, value), - std::end(this->container)); + this->container.size()); } /** * Create and insert a new element, which is added after a previous element with * identical time. Provide a insertion hint to abbreviate the search for the insertion point. */ - iterator insert_after(const time::time_t &time, const T &value, const iterator &hint) { + elem_ptr insert_after(const time::time_t &time, const T &value, const elem_ptr &hint) { return this->insert_after(keyframe_t(time, value), hint); } /** * Erase all elements that come after this last valid element. */ - iterator erase_after(iterator last_valid); + elem_ptr erase_after(elem_ptr last_valid); /** * Erase a single element from the curve. * Returns the element after the deleted one. */ - iterator erase(iterator it); + elem_ptr erase(elem_ptr it); /** * Erase all elements with given time. * Variant without hint, starts the search at the end of the container. * Returns the iterator after the deleted elements. */ - iterator erase(const time::time_t &time) { - return this->erase(time, std::end(this->container)); + elem_ptr erase(const time::time_t &time) { + return this->erase(time, this->container.size()); } /** @@ -239,8 +244,8 @@ class KeyframeContainer { * Or, if no elements with this time exist, * the iterator to the first element after the requested time is returned */ - iterator erase(const time::time_t &time, - const iterator &hint) { + elem_ptr erase(const time::time_t &time, + const elem_ptr &hint) { return this->erase_group(time, this->last(time, hint)); } @@ -280,7 +285,7 @@ class KeyframeContainer { * Using the default value replaces ALL keyframes of \p this with * the keyframes of \p other. */ - iterator sync(const KeyframeContainer &other, + elem_ptr sync(const KeyframeContainer &other, const time::time_t &start = time::TIME_MIN); /** @@ -296,7 +301,7 @@ class KeyframeContainer { * the keyframes of \p other. */ template - iterator sync(const KeyframeContainer &other, + elem_ptr sync(const KeyframeContainer &other, const std::function &converter, const time::time_t &start = time::TIME_MIN); @@ -314,8 +319,8 @@ class KeyframeContainer { * Erase elements with this time. * The iterator has to point to the last element of the same-time group. */ - iterator erase_group(const time::time_t &time, - const iterator &last_elem); + elem_ptr erase_group(const time::time_t &time, + const elem_ptr &last_elem); /** * The data store. @@ -355,28 +360,28 @@ size_t KeyframeContainer::size() const { * that determines the curve value for a searched time. */ template -typename KeyframeContainer::iterator KeyframeContainer::last(const time::time_t &time, - const iterator &hint) const { - iterator e = hint; - auto end = std::end(this->container); +typename KeyframeContainer::elem_ptr +KeyframeContainer::last(const time::time_t &time, + const KeyframeContainer::elem_ptr &hint) const { + elem_ptr at = hint; + const elem_ptr end = this->container.size(); - if (e != end and e->time <= time) { + if (at != end and this->container.at(at).time() <= time) { // walk to the right until the time is larget than the searched - // then go one to the left to get the last item with <= requested time - while (e != end && e->time <= time) { - e++; + while (at != end and this->container.at(at).time() <= time) { + ++at; } - e--; + // go one back, because we want the last element that is <= time + --at; } - else { // e == end or e->time > time - // walk to the left until the element time is smaller than or equal to the searched time - auto begin = std::begin(this->container); - while (e != begin and (e == end or e->time > time)) { - e--; + else { // idx == end or idx->time > time + // walk to the left until the element time is smaller than the searched time + while (at > 0 and (at == end or this->container.at(at).time() > time)) { + --at; } } - return e; + return at; } @@ -389,28 +394,28 @@ typename KeyframeContainer::iterator KeyframeContainer::last(const time::t * first element that matches the search time. */ template -typename KeyframeContainer::iterator KeyframeContainer::last_before(const time::time_t &time, - const iterator &hint) const { - iterator e = hint; - auto end = std::end(this->container); +typename KeyframeContainer::elem_ptr +KeyframeContainer::last_before(const time::time_t &time, + const KeyframeContainer::elem_ptr &hint) const { + elem_ptr at = hint; + const elem_ptr end = this->container.size(); - if (e != end and e->time < time) { + if (at != end and this->container.at(at).time() < time) { // walk to the right until the time is larget than the searched - // then go one to the left to get the last item with <= requested time - while (e != end && e->time <= time) { - e++; + while (at != end and this->container.at(at).time() <= time) { + ++at; } - e--; + // go one back, because we want the last element that is <= time + --at; } - else { // e == end or e->time > time + else { // idx == end or idx->time > time // walk to the left until the element time is smaller than the searched time - auto begin = std::begin(this->container); - while (e != begin and (e == end or e->time >= time)) { - e--; + while (at > 0 and (at == end or this->container.at(at).time() >= time)) { + --at; } } - return e; + return at; } @@ -418,21 +423,25 @@ typename KeyframeContainer::iterator KeyframeContainer::last_before(const * Determine where to insert based on time, and insert. */ template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint) { - iterator at = this->last(e.time, hint); - // seek over all same-time elements, so we can insert before the first one - while (at != std::begin(this->container) and at->time == e.time) { - --at; + const KeyframeContainer::elem_ptr &hint) { + elem_ptr at = this->last(e.time(), hint); + + if (at == this->container.size()) { + this->container.push_back(e); + return at; } - // the above while-loop overshoots and selects the first non-equal element. - // set iterator one to the right, i.e. on the first same-value - if (at != std::end(this->container)) { - ++at; + // seek over all same-time elements, so we can insert before the first one + while (this->container.at(at).time() == e.time() and at > 0) { + at--; } - return this->container.insert(at, e); + + ++at; + + this->container.insert(this->begin() + at, e); + return at; } @@ -440,26 +449,28 @@ KeyframeContainer::insert_before(const KeyframeContainer::keyframe_t &e, * Determine where to insert based on time, and insert, overwriting value(s) with same time. */ template -typename KeyframeContainer::iterator -KeyframeContainer::insert_overwrite( - const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint, - bool overwrite_all) { - iterator at = this->last(e.time, hint); +typename KeyframeContainer::elem_ptr +KeyframeContainer::insert_overwrite(const KeyframeContainer::keyframe_t &e, + const KeyframeContainer::elem_ptr &hint, + bool overwrite_all) { + elem_ptr at = this->last(e.time(), hint); + const elem_ptr end = this->container.size(); if (overwrite_all) { - at = this->erase_group(e.time, at); + at = this->erase_group(e.time(), at); } - else if (at != std::end(this->container)) { + else if (at != end) { // overwrite the same-time element - if (at->time == e.time) { - at = this->container.erase(at); + if (this->get(at).time() == e.time()) { + this->container.erase(this->begin() + at); } else { ++at; } } - return this->container.insert(at, e); + + this->container.insert(this->begin() + at, e); + return at; } @@ -468,16 +479,18 @@ KeyframeContainer::insert_overwrite( * If there is a time conflict, insert after the existing element. */ template -typename KeyframeContainer::iterator -KeyframeContainer::insert_after( - const KeyframeContainer::keyframe_t &e, - const KeyframeContainer::iterator &hint) { - iterator at = this->last(e.time, hint); +typename KeyframeContainer::elem_ptr +KeyframeContainer::insert_after(const KeyframeContainer::keyframe_t &e, + const KeyframeContainer::elem_ptr &hint) { + elem_ptr at = this->last(e.time(), hint); + const elem_ptr end = this->container.size(); - if (at != std::end(this->container)) { + if (at != end) { ++at; } - return this->container.insert(at, e); + + this->container.insert(this->begin() + at, e); + return at; } @@ -485,17 +498,15 @@ KeyframeContainer::insert_after( * Go from the end to the last_valid element, and call erase on all of them */ template -typename KeyframeContainer::iterator -KeyframeContainer::erase_after(KeyframeContainer::iterator last_valid) { +typename KeyframeContainer::elem_ptr +KeyframeContainer::erase_after(KeyframeContainer::elem_ptr last_valid) { // exclude the last_valid element from deletion - if (last_valid != this->container.end()) { - ++last_valid; + if (last_valid != this->container.size()) { + // Delete everything to the end. + const elem_ptr delete_start = last_valid + 1; + this->container.erase(this->begin() + delete_start, this->end()); } - // Delete everything to the end. - while (last_valid != this->container.end()) { - last_valid = this->container.erase(last_valid); - } return last_valid; } @@ -504,79 +515,73 @@ KeyframeContainer::erase_after(KeyframeContainer::iterator last_valid) { * Delete the element from the list and call delete on it. */ template -typename KeyframeContainer::iterator -KeyframeContainer::erase(KeyframeContainer::iterator e) { - return this->container.erase(e); +typename KeyframeContainer::elem_ptr +KeyframeContainer::erase(KeyframeContainer::elem_ptr e) { + this->container.erase(this->begin() + e); + return e; } template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const time::time_t &start) { // Delete elements after start time - iterator at = this->last_before(start, this->end()); + elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = other.begin(); - ++at_other; // always skip the first element (because it's the default value) + auto at_other = 1; // always skip the first element (because it's the default value) // Copy all elements from other with time >= start - while (at_other != other.end()) { - if (at_other->time >= start) { - at = this->insert_after(*at_other, at); + for (size_t i = at_other; i < other.size(); i++) { + if (other.get(i).time() >= start) { + at = this->insert_after(other.get(i), at); } - ++at_other; } - return at; + return this->container.size(); } template template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::sync(const KeyframeContainer &other, const std::function &converter, const time::time_t &start) { // Delete elements after start time - iterator at = this->last_before(start, this->end()); + elem_ptr at = this->last_before(start, this->container.size()); at = this->erase_after(at); - auto at_other = other.begin(); - ++at_other; // always skip the first element (because it's the default value) + auto at_other = 1; // always skip the first element (because it's the default value) // Copy all elements from other with time >= start - while (at_other != other.end()) { - if (at_other->time >= start) { - // Convert the value to the type of this container - at = this->insert_after(at_other->time, converter(at_other->value), at); + for (size_t i = at_other; i < other.size(); i++) { + if (other.get(i).time() >= start) { + at = this->insert_after(keyframe_t(other.get(i).time(), converter(other.get(i).val())), at); } - ++at_other; } - return at; + return this->container.size(); } template -typename KeyframeContainer::iterator +typename KeyframeContainer::elem_ptr KeyframeContainer::erase_group(const time::time_t &time, - const iterator &last_elem) { - iterator at = last_elem; + const KeyframeContainer::elem_ptr &last_elem) { + size_t at = last_elem; // if the time what we're looking for // erase elements until all element with that time are purged - while (at != std::end(this->container) and at->time == time) { - at = this->container.erase(at); - if (at != std::begin(this->container)) [[likely]] { - --at; - } + while (at != this->container.size() and this->container.at(at).time() == time) { + this->container.erase(this->container.begin() + at); + --at; } // we have to cancel one --at in order to return // the element after the group we deleted. - if (at != std::end(this->container)) { + if (at != this->container.size()) { ++at; } diff --git a/libopenage/curve/queue.h b/libopenage/curve/queue.h index 7698c18c5c..9604a76308 100644 --- a/libopenage/curve/queue.h +++ b/libopenage/curve/queue.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -60,7 +61,19 @@ class Queue : public event::EventEntity { }; public: - using container_t = typename std::list; + /** + * The underlaying container type. + */ + using container_t = typename std::vector; + + /** + * The index type to access elements in the container + */ + using elem_ptr = typename container_t::size_type; + + /** + * The iterator type to access elements in the container + */ using const_iterator = typename container_t::const_iterator; using iterator = typename container_t::iterator; @@ -71,7 +84,7 @@ class Queue : public event::EventEntity { _id{id}, _idstr{idstr}, last_change{time::TIME_ZERO}, - front_start{this->container.end()} {} + front_start{0} {} // prevent accidental copy of queue Queue(const Queue &) = delete; @@ -208,21 +221,24 @@ class Queue : public event::EventEntity { private: /** - * Erase an element from the queue at the given time. + * Kill an element from the queue at the given time. * - * @param time The time to erase at. - * @param it The iterator to the element to erase. + * The element is set to dead at the given time and is not accessible + * for pops with t > time. + * + * @param time The time to kill at. + * @param at The index of the element to kill. */ - void erase(const time::time_t &time, const_iterator &it); + void kill(const time::time_t &time, elem_ptr at); /** * Get the first alive element inserted at t <= time. * * @param time The time to get the element at. * - * @return Iterator to the first alive element or end() if no such element exists. + * @return Index of the first alive element or end() if no such element exists. */ - const_iterator first_alive(const time::time_t &time) const; + elem_ptr first_alive(const time::time_t &time) const; /** * Identifier for the container @@ -247,59 +263,74 @@ class Queue : public event::EventEntity { /** * Caches the search start position for the next front() call. * - * All iterators before are guaranteed to be dead at t >= last_change. + * All positions before the index are guaranteed to be dead at t >= last_change. */ - mutable typename Queue::const_iterator front_start; + elem_ptr front_start; }; template -typename Queue::const_iterator Queue::first_alive(const time::time_t &time) const { - auto hint = this->container.begin(); +typename Queue::elem_ptr Queue::first_alive(const time::time_t &time) const { + elem_ptr hint = 0; // check if the access is later than the last change if (this->last_change <= time) { // start searching from the last front position hint = this->front_start; } + // else search from the beginning // Iterate until we find an alive element - while (hint->alive() <= time - and hint != this->container.end()) { - if (hint->dead() > time) { + while (hint != this->container.size() + and this->container.at(hint).alive() <= time) { + if (this->container.at(hint).dead() > time) { return hint; } ++hint; } - return this->container.end(); + return this->container.size(); } template const T &Queue::front(const time::time_t &time) const { - auto it = this->first_alive(time); - ENSURE(it != this->container.end(), "Tried accessing front at " << time << " but queue is empty."); - - return it->value; + elem_ptr at = this->first_alive(time); + ENSURE(at < this->container.size(), + "Tried accessing front at " << time << " but index " << at << " is invalid. " + << "The queue may be empty." + << "(last_change: " << this->last_change + << ", front_start: " << this->front_start + << ", container size: " << this->container.size() + << ")"); + + return this->container.at(at).value; } template const T &Queue::pop_front(const time::time_t &time) { - Queue::const_iterator it = this->first_alive(time); - ENSURE(it != this->container.end(), "Tried accessing front at " << time << " but queue is empty."); + elem_ptr at = this->first_alive(time); + ENSURE(at < this->container.size(), + "Tried accessing front at " << time << " but index " << at << " is invalid. " + << "The queue may be empty." + << "(last_change: " << this->last_change + << ", front_start: " << this->front_start + << ", container size: " << this->container.size() + << ")"); + + // kill the element at time t + this->kill(time, at); // cache the search start position for the next front() call - this->front_start = it; + // for pop time t, there should be no more elements alive before t + // so we can advance the front to the next element this->last_change = time; - - // erase the element - this->erase(time, it); + this->front_start = at + 1; this->changes(time); - return it->value; + return this->container.at(at).value; } @@ -309,7 +340,7 @@ bool Queue::empty(const time::time_t &time) const { return true; } - return this->first_alive(time) == this->container.end(); + return this->first_alive(time) == this->container.size(); } @@ -361,23 +392,26 @@ void Queue::erase(const CurveIterator> &it) { template -void Queue::erase(const time::time_t &time, - const_iterator &it) { - it->set_dead(time); +void Queue::kill(const time::time_t &time, + elem_ptr at) { + this->container[at].set_dead(time); } template QueueFilterIterator> Queue::insert(const time::time_t &time, const T &e) { - const_iterator insertion_point = this->container.end(); - while (insertion_point != this->container.begin()) { - --insertion_point; - if (insertion_point->alive() <= time) { - ++insertion_point; + elem_ptr at = this->container.size(); + while (at > 0) { + --at; + if (this->container.at(at).alive() <= time) { + ++at; break; } } + + // Get the iterator to the insertion point + iterator insertion_point = std::next(this->container.begin(), at); insertion_point = this->container.insert(insertion_point, queue_wrapper{time, e}); // TODO: Inserting before any dead elements shoud reset their death time @@ -385,18 +419,11 @@ QueueFilterIterator> Queue::insert(const time::time_t &time, // cache the insertion time this->last_change = time; - if (this->front_start == this->container.end()) [[unlikely]] { - // only true if the container is empty - // or all elements are dead - this->front_start = insertion_point; - } - else { - // if there are more alive elements, only cache if the - // insertion time is before the current front - if (time < this->front_start->alive()) { - // cache the search start position for the next front() call - this->front_start = insertion_point; - } + + // if the new element is inserted before the current front element + // cache it as the new front element + if (at < this->front_start) { + this->front_start = at; } auto ct = QueueFilterIterator>( @@ -417,25 +444,26 @@ QueueFilterIterator> Queue::insert(const time::time_t &time, template void Queue::clear(const time::time_t &time) { - Queue::const_iterator it = this->first_alive(time); + elem_ptr at = this->first_alive(time); // no elements alive at t <= time // so we don't have any changes - if (it == this->container.end()) { + if (at == this->container.size()) { return; } // erase all elements alive at t <= time - while (it->alive() <= time and it != this->container.end()) { - if (it->dead() > time) { - it->set_dead(time); + while (this->container.at(at).alive() <= time + and at != this->container.size()) { + if (this->container.at(at).dead() > time) { + this->container[at].set_dead(time); } - ++it; + ++at; } // cache the search start position for the next front() call this->last_change = time; - this->front_start = it; + this->front_start = at; this->changes(time); } diff --git a/libopenage/curve/segmented.h b/libopenage/curve/segmented.h index e02572060f..98add2995e 100644 --- a/libopenage/curve/segmented.h +++ b/libopenage/curve/segmented.h @@ -1,4 +1,4 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once @@ -62,7 +62,7 @@ void Segmented::set_last_jump(const time::time_t &at, const T &leftval, const auto hint = this->container.last(at, this->last_element); // erase all one same-time values - while (hint->time == at) { + while (this->container.get(hint).time() == at) { hint--; } diff --git a/libopenage/curve/tests/curve_types.cpp b/libopenage/curve/tests/curve_types.cpp index 40f20395ee..421d3fb4d7 100644 --- a/libopenage/curve/tests/curve_types.cpp +++ b/libopenage/curve/tests/curve_types.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #include #include @@ -27,8 +27,8 @@ void curve_types() { auto p0 = c.insert_before(0, 0); auto p1 = c.insert_before(1, 1); auto p2 = c.insert_before(10, 2); - auto pa = std::begin(c); - auto pe = std::end(c); + auto ib = 0; + auto ie = c.size(); // now contains: [-inf: 0, 0:0, 1:1, 10:2] @@ -36,133 +36,133 @@ void curve_types() { { auto it = c.begin(); - TESTEQUALS(it->value, 0); - TESTEQUALS(it->time, time::TIME_MIN); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 0); - TESTEQUALS((++it)->time, 1); - TESTEQUALS(it->value, 1); - TESTEQUALS((++it)->time, 10); - TESTEQUALS(it->value, 2); + TESTEQUALS(it->val(), 0); + TESTEQUALS(it->time(), time::TIME_MIN); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 0); + TESTEQUALS((++it)->time(), 1); + TESTEQUALS(it->val(), 1); + TESTEQUALS((++it)->time(), 10); + TESTEQUALS(it->val(), 2); } // last function tests without hints - TESTEQUALS(c.last(0)->value, 0); - TESTEQUALS(c.last(1)->value, 1); - TESTEQUALS(c.last(5)->value, 1); - TESTEQUALS(c.last(10)->value, 2); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(0)).val(), 0); + TESTEQUALS(c.get(c.last(1)).val(), 1); + TESTEQUALS(c.get(c.last(5)).val(), 1); + TESTEQUALS(c.get(c.last(10)).val(), 2); + TESTEQUALS(c.get(c.last(47)).val(), 2); // last() with hints. - TESTEQUALS(c.last(0, pa)->value, 0); - TESTEQUALS(c.last(1, pa)->value, 1); - TESTEQUALS(c.last(5, pa)->value, 1); - TESTEQUALS(c.last(10, pa)->value, 2); - TESTEQUALS(c.last(47, pa)->value, 2); - - TESTEQUALS(c.last(0, p0)->value, 0); - TESTEQUALS(c.last(1, p0)->value, 1); - TESTEQUALS(c.last(5, p0)->value, 1); - TESTEQUALS(c.last(10, p0)->value, 2); - TESTEQUALS(c.last(47, p0)->value, 2); - - TESTEQUALS(c.last(0, p1)->value, 0); - TESTEQUALS(c.last(1, p1)->value, 1); - TESTEQUALS(c.last(5, p1)->value, 1); - TESTEQUALS(c.last(10, p1)->value, 2); - TESTEQUALS(c.last(47, p1)->value, 2); - - TESTEQUALS(c.last(0, p2)->value, 0); - TESTEQUALS(c.last(1, p2)->value, 1); - TESTEQUALS(c.last(5, p2)->value, 1); - TESTEQUALS(c.last(10, p2)->value, 2); - TESTEQUALS(c.last(47, p2)->value, 2); - - TESTEQUALS(c.last(0, pe)->value, 0); - TESTEQUALS(c.last(1, pe)->value, 1); - TESTEQUALS(c.last(5, pe)->value, 1); - TESTEQUALS(c.last(10, pe)->value, 2); - TESTEQUALS(c.last(47, pe)->value, 2); + TESTEQUALS(c.get(c.last(0, ib)).val(), 0); + TESTEQUALS(c.get(c.last(1, ib)).val(), 1); + TESTEQUALS(c.get(c.last(5, ib)).val(), 1); + TESTEQUALS(c.get(c.last(10, ib)).val(), 2); + TESTEQUALS(c.get(c.last(47, ib)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p0)).val(), 0); + TESTEQUALS(c.get(c.last(1, p0)).val(), 1); + TESTEQUALS(c.get(c.last(5, p0)).val(), 1); + TESTEQUALS(c.get(c.last(10, p0)).val(), 2); + TESTEQUALS(c.get(c.last(47, p0)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p1)).val(), 0); + TESTEQUALS(c.get(c.last(1, p1)).val(), 1); + TESTEQUALS(c.get(c.last(5, p1)).val(), 1); + TESTEQUALS(c.get(c.last(10, p1)).val(), 2); + TESTEQUALS(c.get(c.last(47, p1)).val(), 2); + + TESTEQUALS(c.get(c.last(0, p2)).val(), 0); + TESTEQUALS(c.get(c.last(1, p2)).val(), 1); + TESTEQUALS(c.get(c.last(5, p2)).val(), 1); + TESTEQUALS(c.get(c.last(10, p2)).val(), 2); + TESTEQUALS(c.get(c.last(47, p2)).val(), 2); + + TESTEQUALS(c.get(c.last(0, ie)).val(), 0); + TESTEQUALS(c.get(c.last(1, ie)).val(), 1); + TESTEQUALS(c.get(c.last(5, ie)).val(), 1); + TESTEQUALS(c.get(c.last(10, ie)).val(), 2); + TESTEQUALS(c.get(c.last(47, ie)).val(), 2); // Now test the basic erase() function // Delete the 1-element, new values should be [-inf:0, 0:0, 10:2] c.erase(c.last(1)); - TESTEQUALS(c.last(1)->value, 0); - TESTEQUALS(c.last(5)->value, 0); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(1)).val(), 0); + TESTEQUALS(c.get(c.last(5)).val(), 0); + TESTEQUALS(c.get(c.last(47)).val(), 2); // should do nothing, since we delete all at > 99, // but the last element is at 10. should still be [-inf:0, 0:0, 10:2] c.erase_after(c.last(99)); - TESTEQUALS(c.last(47)->value, 2); + TESTEQUALS(c.get(c.last(47)).val(), 2); // now since 5 < 10, element with value 2 has to be gone // result should be [-inf:0, 0:0] c.erase_after(c.last(5)); - TESTEQUALS(c.last(47)->value, 0); + TESTEQUALS(c.get(c.last(47)).val(), 0); c.insert_overwrite(0, 42); - TESTEQUALS(c.last(100)->value, 42); - TESTEQUALS(c.last(100)->time, 0); + TESTEQUALS(c.get(c.last(100)).val(), 42); + TESTEQUALS(c.get(c.last(100)).time(), 0); // the curve now contains [-inf:0, 0:42] // let's change/add some more elements c.insert_overwrite(0, 10); - TESTEQUALS(c.last(100)->value, 10); + TESTEQUALS(c.get(c.last(100)).val(), 10); c.insert_after(0, 11); c.insert_after(0, 12); // now: [-inf:0, 0:10, 0:11, 0:12] - TESTEQUALS(c.last(0)->value, 12); - TESTEQUALS(c.last(10)->value, 12); + TESTEQUALS(c.get(c.last(0)).val(), 12); + TESTEQUALS(c.get(c.last(10)).val(), 12); c.insert_before(0, 2); // all the values at t=0 should be 2, 10, 11, 12 c.insert_after(1, 15); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(10)->value, 15); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(10)).val(), 15); c.insert_overwrite(2, 20); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(2)->value, 20); - TESTEQUALS(c.last(10)->value, 20); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(2)).val(), 20); + TESTEQUALS(c.get(c.last(10)).val(), 20); c.insert_before(3, 25); - TESTEQUALS(c.last(1)->value, 15); - TESTEQUALS(c.last(2)->value, 20); - TESTEQUALS(c.last(3)->value, 25); - TESTEQUALS(c.last(10)->value, 25); + TESTEQUALS(c.get(c.last(1)).val(), 15); + TESTEQUALS(c.get(c.last(2)).val(), 20); + TESTEQUALS(c.get(c.last(3)).val(), 25); + TESTEQUALS(c.get(c.last(10)).val(), 25); // now it should be [-inf: 0, 0: 2, 0: 10, 0: 11, 0: 12, 1: 15, 2: 20, // 3: 25] { auto it = c.begin(); - TESTEQUALS(it->time, time::TIME_MIN); - TESTEQUALS(it->value, 0); + TESTEQUALS(it->time(), time::TIME_MIN); + TESTEQUALS(it->val(), 0); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 2); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 2); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 10); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 10); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 11); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 11); - TESTEQUALS((++it)->time, 0); - TESTEQUALS(it->value, 12); + TESTEQUALS((++it)->time(), 0); + TESTEQUALS(it->val(), 12); - TESTEQUALS((++it)->time, 1); - TESTEQUALS(it->value, 15); + TESTEQUALS((++it)->time(), 1); + TESTEQUALS(it->val(), 15); - TESTEQUALS((++it)->time, 2); - TESTEQUALS(it->value, 20); + TESTEQUALS((++it)->time(), 2); + TESTEQUALS(it->val(), 20); - TESTEQUALS((++it)->time, 3); - TESTEQUALS(it->value, 25); + TESTEQUALS((++it)->time(), 3); + TESTEQUALS(it->val(), 25); } // TODO: test c.insert_overwrite and c.insert_after @@ -170,17 +170,17 @@ void curve_types() { KeyframeContainer c2; c2.sync(c, 1); // now c2 should be [-inf: 0, 1: 15, 2: 20, 3: 25] - TESTEQUALS(c2.last(0)->value, 0); - TESTEQUALS(c2.last(1)->value, 15); - TESTEQUALS(c2.last(2)->value, 20); - TESTEQUALS(c2.last(3)->value, 25); - TESTEQUALS(c2.last(10)->value, 25); + TESTEQUALS(c2.get(c2.last(0)).val(), 0); + TESTEQUALS(c2.get(c2.last(1)).val(), 15); + TESTEQUALS(c2.get(c2.last(2)).val(), 20); + TESTEQUALS(c2.get(c2.last(3)).val(), 25); + TESTEQUALS(c2.get(c2.last(10)).val(), 25); TESTEQUALS(c2.size(), 4); c.clear(); // now it should be [-inf: 0] - TESTEQUALS(c.last(0)->value, 0); - TESTEQUALS(c.last(1)->value, 0); + TESTEQUALS(c.get(c.last(0)).val(), 0); + TESTEQUALS(c.get(c.last(1)).val(), 0); TESTEQUALS(c.size(), 1); }