/*
* Copyright (C) 2010-2025 by the Widelands Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*
*/
#ifndef WL_LOGIC_COOKIE_PRIORITY_QUEUE_H
#define WL_LOGIC_COOKIE_PRIORITY_QUEUE_H
#include
#include
#include
#include
#include "base/macros.h"
#include "logic/map_objects/tribes/wareworker.h"
template struct DefaultCookieAccessor;
template struct CookiePriorityQueueBase {
using CookieType = CT;
using CookieTypeVector = std::vector;
using CookieSizeType = typename CookieTypeVector::size_type;
struct Cookie {
Cookie() : pos(bad_pos()) {
}
~Cookie() = default;
/** Returns \c true if the cookie is currently managed by a queue */
[[nodiscard]] bool is_active() const {
return pos != bad_pos();
}
private:
friend struct CookiePriorityQueueBase;
CookieSizeType pos;
DISALLOW_COPY_AND_ASSIGN(Cookie);
};
protected:
static CookieSizeType& cookie_pos(Cookie& cookie) {
return cookie.pos;
}
static CookieSizeType bad_pos() {
return std::numeric_limits::max();
}
};
/**
* A priority queue (heap) with back-links called cookies to allow
* key modification in logarithmic time.
*
* Since elements are treated as objects with an identity, we store pointers
* unlike the usual STL container behaviour.
*
* The comparison type must act like a "stricly less than" comparison.
* The "strictly" part is important.
*
* \ref top returns an element such that no other element in the container
* is strictly less than the element returned by \ref top
*
* If an element's position with respect to the ordering is changed,
* you have to call \ref decrease_key or \ref increase_key after the change,
* depending on whether the change caused a decrease or increase, respectively.
*/
template ,
typename Cas = DefaultCookieAccessor>
struct CookiePriorityQueue : CookiePriorityQueueBase {
using Compare = Cmp;
using CookieAccessor = Cas;
using CookieType = typename CookiePriorityQueueBase::CookieType;
using CookieTypeVector = typename CookiePriorityQueueBase::CookieTypeVector;
using CookieSizeType = typename CookiePriorityQueueBase::CookieSizeType;
using Cookie = typename CookiePriorityQueueBase::Cookie;
using BaseType = CookiePriorityQueueBase;
explicit CookiePriorityQueue(Widelands::WareWorker type,
const Compare& comparator = Compare(),
const CookieAccessor& accessor = CookieAccessor());
~CookiePriorityQueue();
[[nodiscard]] CookieSizeType size() const;
[[nodiscard]] bool empty() const;
[[nodiscard]] CookieType* top() const;
void push(CookieType* elt);
void pop(CookieType* elt);
void decrease_key(CookieType* elt);
void increase_key(CookieType* elt);
[[nodiscard]] Widelands::WareWorker type() const {
return type_;
}
private:
Widelands::WareWorker type_;
CookieTypeVector d;
Compare c;
CookieAccessor ca;
void swap(Cookie& a, Cookie& b);
void selftest();
static CookieSizeType& cookie_pos(Cookie& c) {
return BaseType::cookie_pos(c);
}
static CookieSizeType bad_pos() {
return BaseType::bad_pos();
}
static CookieSizeType parent_pos(CookieSizeType pos) {
return (pos - 1) / 2;
}
static CookieSizeType left_child_pos(CookieSizeType pos) {
return (2 * pos) + 1;
}
static CookieSizeType right_child_pos(CookieSizeType pos) {
return (2 * pos) + 2;
}
};
template struct DefaultCookieAccessor {
using Cookie = typename CookiePriorityQueueBase::Cookie;
Cookie& operator()(DCAType* t, Widelands::WareWorker type) {
return t->cookie(type);
}
};
template
CookiePriorityQueue::CookiePriorityQueue(
Widelands::WareWorker wwtype,
const typename CookiePriorityQueue::Compare& comparator,
const typename CookiePriorityQueue::CookieAccessor& accessor)
: type_(wwtype), c(comparator), ca(accessor) {
}
template
CookiePriorityQueue::~CookiePriorityQueue() {
for (typename CookieTypeVector::iterator it = d.begin(); it != d.end(); ++it) {
cookie_pos(ca(*it, type_)) = bad_pos();
}
}
template
typename CookiePriorityQueue::CookieSizeType
CookiePriorityQueue::size() const {
return d.size();
}
template
bool CookiePriorityQueue::empty() const {
return d.empty();
}
template
typename CookiePriorityQueue::CookieType*
CookiePriorityQueue::top() const {
return *d.begin();
}
template
void CookiePriorityQueue::push(
typename CookiePriorityQueue::CookieType* elt) {
Cookie& elt_cookie(ca(elt, type_));
assert(cookie_pos(elt_cookie) == BaseType::bad_pos());
d.push_back(elt);
cookie_pos(elt_cookie) = d.size() - 1;
decrease_key(elt);
}
template
void CookiePriorityQueue::pop(
typename CookiePriorityQueue::CookieType* elt) {
Cookie& elt_cookie(ca(elt, type_));
assert(cookie_pos(elt_cookie) < d.size());
while (cookie_pos(elt_cookie) > 0) {
Cookie& parent_cookie = ca(*(d.begin() + parent_pos(cookie_pos(elt_cookie))), type_);
assert(cookie_pos(parent_cookie) == parent_pos(cookie_pos(elt_cookie)));
swap(elt_cookie, parent_cookie);
}
swap(elt_cookie, ca(d.back(), type_));
d.pop_back();
cookie_pos(elt_cookie) = BaseType::bad_pos();
if (!d.empty()) {
increase_key(*d.begin());
}
}
template
void CookiePriorityQueue::decrease_key(
typename CookiePriorityQueue::CookieType* elt) {
Cookie& elt_cookie(ca(elt, type_));
assert(cookie_pos(elt_cookie) < d.size());
while (cookie_pos(elt_cookie) != 0) {
CookieSizeType parent = parent_pos(cookie_pos(elt_cookie));
if (!c(*elt, *d[parent], type_)) {
break;
}
Cookie& parent_cookie(ca(d[parent], type_));
assert(cookie_pos(parent_cookie) == parent);
swap(elt_cookie, parent_cookie);
}
#ifndef NDEBUG
selftest();
#endif
}
template
void CookiePriorityQueue::increase_key(
typename CookiePriorityQueue::CookieType* elt) {
Cookie& elt_cookie(ca(elt, type_));
assert(cookie_pos(elt_cookie) < d.size());
for (;;) {
CookieSizeType left_child = left_child_pos(cookie_pos(elt_cookie));
CookieSizeType right_child = right_child_pos(cookie_pos(elt_cookie));
if (left_child >= d.size()) {
break;
}
Cookie& left_cookie(ca(d[left_child], type_));
assert(cookie_pos(left_cookie) == left_child);
if (c(**(d.begin() + left_child), *elt, type_)) {
if (right_child >= d.size() || c(*d[left_child], *d[right_child], type_)) {
swap(elt_cookie, left_cookie);
continue;
}
}
if (right_child >= d.size()) {
break;
}
Cookie& right_cookie(ca(d[right_child], type_));
assert(cookie_pos(right_cookie) == right_child);
if (c(*d[right_child], *elt, type_)) {
swap(elt_cookie, right_cookie);
continue;
}
break;
}
#ifndef NDEBUG
selftest();
#endif
}
template
void CookiePriorityQueue::swap(
typename CookiePriorityQueue::Cookie& a,
typename CookiePriorityQueue::Cookie& b) {
std::swap(d[cookie_pos(a)], d[cookie_pos(b)]);
std::swap(cookie_pos(a), cookie_pos(b));
}
// Disable in release builds.
template
void CookiePriorityQueue::selftest() {
for (CookieSizeType pos = 0; pos < d.size(); ++pos) {
Cookie& elt_cookie(ca(d[pos], type_));
assert(cookie_pos(elt_cookie) == pos);
CookieSizeType left_child = left_child_pos(pos);
CookieSizeType right_child = right_child_pos(pos);
if (left_child < d.size()) {
assert(!c(*d[left_child], *d[pos], type_));
}
if (right_child < d.size()) {
assert(!c(*d[right_child], *d[pos], type_));
}
}
}
#endif // end of include guard: WL_LOGIC_COOKIE_PRIORITY_QUEUE_H