diff --git a/common/entdata.cc b/common/entdata.cc index b6498e51..34cce427 100644 --- a/common/entdata.cc +++ b/common/entdata.cc @@ -74,7 +74,13 @@ int32_t entdict_t::get_int(const std::string_view &key) const int32_t entdict_t::get_vector(const std::string_view &key, qvec3d &vec) const { - const std::string &value = get(key); + std::string value = get(key); + + // FIXME: this fixes ASan triggering on some entities... + if (*(value.data() + value.size()) != 0) { + *(value.data() + value.size()) = 0; + } + vec = {}; return sscanf(value.data(), "%lf %lf %lf", &vec[0], &vec[1], &vec[2]); } diff --git a/include/common/polylib.hh b/include/common/polylib.hh index fcd1626c..356f3a05 100644 --- a/include/common/polylib.hh +++ b/include/common/polylib.hh @@ -35,26 +35,402 @@ inline bool PointInWindingEdges(const winding_edges_t &wi, const qvec3d &point) return true; } -// Polygonal winding; uses stack allocation for the first N +#if 0 + +// Hybrid storage; uses stack allocation for the first N // points, and uses a dynamic vector for storage after that. template -struct winding_base_t +struct winding_storage_hybrid_t +{ + // default constructor does nothing + inline winding_storage_hybrid_t(); + + // construct winding with initial size; may allocate + // memory, and sets size, but does not initialize any + // of them. + inline winding_storage_hybrid_t(const size_t &initial_size) : count(initial_size); + + // construct winding from range. + // iterators must have operator+ and operator-. + template, int> = 0> + inline winding_storage_hybrid_t(Iter begin, Iter end) : winding_storage_hybrid_t(end - begin); + + // initializer list constructor + inline winding_storage_hybrid_t(std::initializer_list l) : winding_storage_hybrid_t(l.begin(), l.end()); + + // copy constructor; uses optimized method of copying + // data over. + inline winding_storage_hybrid_t(const winding_storage_hybrid_t ©) : winding_storage_hybrid_t(copy.size()); + + // move constructor + inline winding_storage_hybrid_t(winding_storage_hybrid_t &&move) noexcept : count(move.count); + + // assignment copy + inline winding_storage_hybrid_t &operator=(const winding_storage_hybrid_t ©); + + // assignment move + inline winding_storage_hybrid_t &operator=(winding_storage_hybrid_t &&move) noexcept; + + inline bool empty() const; + + inline explicit operator bool() const; + + inline const size_t &size() const; + + inline qvec3d &at(const size_t &index); + inline const qvec3d &at(const size_t &index) const; + + // un-bounds-checked + inline qvec3d &operator[](const size_t &index); + inline const qvec3d &operator[](const size_t &index) const; + + const const_iterator begin() const; + const const_iterator end() const; + + iterator begin(); + iterator end(); + + template + qvec3d &emplace_back(Args &&...vec); + void resize(const size_t &new_size); + void clear(); +}; +#endif + +// Stack storage; uses stack allocation. Throws if it can't insert +// a new member. +template +struct winding_storage_stack_t +{ +protected: + using array_type = std::array; + array_type array; + +public: + size_t count = 0; + + // default constructor does nothing + inline winding_storage_stack_t() { } + + // construct winding with initial size; may allocate + // memory, and sets size, but does not initialize any + // of them. + inline winding_storage_stack_t(const size_t &initial_size) : count(initial_size) + { + if (initial_size > N) { + throw std::bad_alloc(); + } + } + + // construct winding from range. + // iterators must have operator+ and operator-. + template, int> = 0> + inline winding_storage_stack_t(Iter begin, Iter end) : winding_storage_stack_t(end - begin) + { + std::copy_n(begin, count, array.begin()); + } + + // copy constructor; uses optimized method of copying + // data over. + inline winding_storage_stack_t(const winding_storage_stack_t ©) : winding_storage_stack_t(copy.size()) + { + std::copy_n(copy.array.begin(), copy.count, array.begin()); + } + + // move constructor + inline winding_storage_stack_t(winding_storage_stack_t &&move) noexcept : count(move.count) + { + count = move.count; + + std::copy_n(move.array.begin(), move.count, array.begin()); + + move.count = 0; + } + + // assignment copy + inline winding_storage_stack_t &operator=(const winding_storage_stack_t ©) + { + count = copy.count; + + // copy array range + std::copy_n(copy.array.begin(), copy.count, array.begin()); + + return *this; + } + + // assignment move + inline winding_storage_stack_t &operator=(winding_storage_stack_t &&move) noexcept + { + count = move.count; + + // blit over array data + std::copy_n(move.array.begin(), move.count, array.begin()); + + move.count = 0; + + return *this; + } + + inline const size_t &size() const { return count; } + + inline qvec3d &at(const size_t &index) + { +#ifdef _DEBUG + if (index >= count) + throw std::invalid_argument("index"); +#endif + + return array[index]; + } + + inline const qvec3d &at(const size_t &index) const + { +#ifdef _DEBUG + if (index >= count) + throw std::invalid_argument("index"); +#endif + + return array[index]; + } + + // un-bounds-checked + inline qvec3d &operator[](const size_t &index) + { + return array[index]; + } + + // un-bounds-checked + inline const qvec3d &operator[](const size_t &index) const + { + return array[index]; + } + + using const_iterator = typename array_type::const_iterator; + + inline const const_iterator begin() const + { + return array.begin(); + } + + inline const const_iterator end() const + { + return array.begin() + count; + } + + using iterator = typename array_type::iterator; + + inline iterator begin() + { + return array.begin(); + } + + inline iterator end() + { + return array.begin() + count; + } + + inline qvec3d &emplace_back(const qvec3d &vec) + { + count++; + + if (count > N) { + throw std::bad_alloc(); + } + + return (array[count - 1] = vec); + } + + inline void resize(const size_t &new_size) + { + if (new_size > N) { + throw std::bad_alloc(); + } + + count = new_size; + } + + inline void clear() + { + count = 0; + } +}; + + +// Stack storage; uses a solid heap allocation. Throws if it can't insert +// a new member. +struct winding_storage_heap_t +{ +protected: + qvec3d *heap = nullptr; + +public: + size_t count = 0; + + // default constructor does nothing + inline winding_storage_heap_t() { } + + // destructor + ~winding_storage_heap_t() { free(heap); heap = nullptr; } + + // construct winding with initial size; may allocate + // memory, and sets size, but does not initialize any + // of them. + inline winding_storage_heap_t(const size_t &initial_size) : count(initial_size) + { + if (initial_size) { + heap = reinterpret_cast(malloc(sizeof(qvec3d) * initial_size)); + } + } + + // construct winding from range. + // iterators must have operator+ and operator-. + template, int> = 0> + inline winding_storage_heap_t(Iter begin, Iter end) : winding_storage_heap_t(end - begin) + { + std::copy_n(begin, count, heap); + } + + // copy constructor; uses optimized method of copying + // data over. + inline winding_storage_heap_t(const winding_storage_heap_t ©) : winding_storage_heap_t(copy.size()) + { + std::copy_n(copy.heap, copy.count, heap); + } + + // move constructor + inline winding_storage_heap_t(winding_storage_heap_t &&move) noexcept : count(move.count) + { + // take ownership of heap pointer + heap = move.heap; + count = move.count; + + move.heap = nullptr; + move.count = 0; + } + + // assignment copy + inline winding_storage_heap_t &operator=(const winding_storage_heap_t ©) + { + resize(copy.count); + + // copy array range + std::copy_n(copy.heap, copy.count, heap); + + return *this; + } + + // assignment move + inline winding_storage_heap_t &operator=(winding_storage_heap_t &&move) noexcept + { + // take ownership of heap pointer + heap = move.heap; + count = move.count; + + move.heap = nullptr; + move.count = 0; + + return *this; + } + + inline const size_t &size() const { return count; } + + inline qvec3d &at(const size_t &index) + { +#ifdef _DEBUG + if (index >= count) + throw std::invalid_argument("index"); +#endif + + return heap[index]; + } + + inline const qvec3d &at(const size_t &index) const + { +#ifdef _DEBUG + if (index >= count) + throw std::invalid_argument("index"); +#endif + + return heap[index]; + } + + // un-bounds-checked + inline qvec3d &operator[](const size_t &index) + { + return heap[index]; + } + + // un-bounds-checked + inline const qvec3d &operator[](const size_t &index) const + { + return heap[index]; + } + + inline const auto begin() const + { + return heap; + } + + inline const auto end() const + { + return heap + count; + } + + inline auto begin() + { + return heap; + } + + inline auto end() + { + return heap + count; + } + + inline qvec3d &emplace_back(const qvec3d &vec) + { + resize(count + 1); + return (heap[count - 1] = vec); + } + + inline void resize(const size_t &new_size) + { + count = new_size; + + if (new_size == 0) { + free(heap); + heap = nullptr; + } else { + heap = reinterpret_cast(realloc(heap, new_size * sizeof(qvec3d))); + } + } + + inline void clear() + { + count = 0; + } +}; + +// Hybrid storage; uses stack allocation for the first N +// points, and uses a dynamic vector for storage after that. +template +struct winding_storage_hybrid_t { protected: using array_type = std::array; using vector_type = std::vector; using variant_type = std::variant; - size_t count = 0; array_type array; vector_type vector; public: + size_t count = 0; + template class iterator_base { - friend struct winding_base_t; + friend struct winding_storage_hybrid_t; - using container_type = typename std::conditional_t; + using container_type = typename std::conditional_t; size_t index; container_type w; @@ -138,12 +514,12 @@ public: }; // default constructor does nothing - inline winding_base_t() { } + inline winding_storage_hybrid_t() { } // construct winding with initial size; may allocate // memory, and sets size, but does not initialize any // of them. - inline winding_base_t(const size_t &initial_size) : count(initial_size) + inline winding_storage_hybrid_t(const size_t &initial_size) : count(initial_size) { if (count > N) { vector.reserve(count); @@ -154,7 +530,7 @@ public: // construct winding from range. // iterators must have operator+ and operator-. template, int> = 0> - inline winding_base_t(Iter begin, Iter end) : winding_base_t(end - begin) + inline winding_storage_hybrid_t(Iter begin, Iter end) : winding_storage_hybrid_t(end - begin) { // copy the array range std::copy_n(begin, min(count, N), array.begin()); @@ -165,12 +541,9 @@ public: } } - // initializer list constructor - inline winding_base_t(std::initializer_list l) : winding_base_t(l.begin(), l.end()) { } - // copy constructor; uses optimized method of copying // data over. - inline winding_base_t(const winding_base_t ©) : winding_base_t(copy.size()) + inline winding_storage_hybrid_t(const winding_storage_hybrid_t ©) : winding_storage_hybrid_t(copy.size()) { // copy array range memcpy(&array.front(), ©.array.front(), min(count, N) * sizeof(qvec3d)); @@ -182,7 +555,7 @@ public: } // move constructor - inline winding_base_t(winding_base_t &&move) noexcept : count(move.count) + inline winding_storage_hybrid_t(winding_storage_hybrid_t &&move) noexcept : count(move.count) { count = move.count; @@ -198,7 +571,7 @@ public: } // assignment copy - inline winding_base_t &operator=(const winding_base_t ©) + inline winding_storage_hybrid_t &operator=(const winding_storage_hybrid_t ©) { count = copy.count; @@ -216,7 +589,7 @@ public: } // assignment move - inline winding_base_t &operator=(winding_base_t &&move) noexcept + inline winding_storage_hybrid_t &operator=(winding_storage_hybrid_t &&move) noexcept { count = move.count; @@ -233,12 +606,10 @@ public: return *this; } - inline bool empty() const { return count == 0; } - - inline explicit operator bool() const { return count != 0; } - inline const size_t &size() const { return count; } + inline size_t vector_size() const { return vector.size(); } + inline qvec3d &at(const size_t &index) { #ifdef _DEBUG @@ -289,51 +660,40 @@ public: using const_iterator = iterator_base; - const const_iterator begin() const + inline const const_iterator begin() const { return const_iterator(0, this); } - const const_iterator end() const + inline const const_iterator end() const { return const_iterator(count, this); } using iterator = iterator_base; - iterator begin() + inline iterator begin() { return iterator(0, this); } - iterator end() + inline iterator end() { return iterator(count, this); } - template - qvec3d &emplace_back(Args &&...vec) + inline qvec3d &emplace_back(const qvec3d &vec) { count++; if (count > N) { - return vector.emplace_back(std::forward(vec)...); + return vector.emplace_back(vec); } - return (array[count - 1] = qvec3d(std::forward(vec)...)); + return (array[count - 1] = vec); } - void push_back(qvec3d &&vec) - { - emplace_back(std::move(vec)); - } - - void push_back(const qvec3d &vec) - { - emplace_back(vec); - } - - void resize(const size_t &new_size) + inline void resize(const size_t &new_size) { // resize vector if necessary if (new_size > N) { @@ -343,19 +703,134 @@ public: count = new_size; } - void clear() + inline void clear() { count = 0; } +}; + +// Winding type, with storage template. Doesn't inherit the storage, +// since that might slow things down with virtual destructor. +template +struct winding_base_t +{ +protected: + TStorage storage; + +public: + // default constructor does nothing + inline winding_base_t() { } + + // construct winding with initial size; may allocate + // memory, and sets size, but does not initialize any + // of them. + inline winding_base_t(const size_t &initial_size) : storage(initial_size) { } + + // construct winding from range. + // iterators must have operator+ and operator-. + template, int> = 0> + inline winding_base_t(Iter begin, Iter end) : storage(begin, end) { } + + // initializer list constructor + inline winding_base_t(std::initializer_list l) : storage(l.begin(), l.end()) { } + + // copy constructor; uses optimized method of copying + // data over. + inline winding_base_t(const winding_base_t ©) : storage(copy.storage) { } + + // move constructor + inline winding_base_t(winding_base_t &&move) noexcept : storage(std::move(move.storage)) { } + + // assignment copy + inline winding_base_t &operator=(const winding_base_t ©) + { + storage = copy.storage; + return *this; + } + + // assignment move + inline winding_base_t &operator=(winding_base_t &&move) noexcept + { + storage = std::move(move.storage); + return *this; + } + + inline bool empty() const { return size() == 0; } + + inline explicit operator bool() const { return !empty(); } + + inline const size_t &size() const { return storage.size(); } + + inline qvec3d &at(const size_t &index) + { + return storage.at(index); + } + + inline const qvec3d &at(const size_t &index) const + { + return storage.at(index); + } + + // un-bounds-checked + inline qvec3d &operator[](const size_t &index) + { + return storage[index]; + } + + // un-bounds-checked + inline const qvec3d &operator[](const size_t &index) const + { + return storage[index]; + } + + const auto begin() const + { + return storage.begin(); + } + + const auto end() const + { + return storage.end(); + } + + auto begin() + { + return storage.begin(); + } + + auto end() + { + return storage.end(); + } + + template + qvec3d &emplace_back(Args &&...vec) + { + return storage.emplace_back(qvec3d(std::forward(vec)...)); + } + + void push_back(const qvec3d &vec) + { + storage.emplace_back(vec); + } + + void resize(const size_t &new_size) + { + storage.resize(new_size); + } + + void clear() + { + storage.clear(); + } + + // non-storage functions vec_t area() const { - // if (count < 3) - // throw std::domain_error("count"); - vec_t total = 0; - for (size_t i = 2; i < count; i++) { + for (size_t i = 2; i < size(); i++) { qvec3d d1 = at(i - 1) - at(0); qvec3d d2 = at(i) - at(0); total += 0.5 * qv::length(qv::cross(d1, d2)); @@ -371,7 +846,7 @@ public: for (auto &point : *this) center += point; - return center * (1.0 / count); + return center * (1.0 / size()); } aabb3d bounds() const @@ -396,9 +871,9 @@ public: // CHECK: would this be more efficient to instead store a running // list of indices that *are* collinear, so we can return sooner // before copying points over? - for (size_t i = 0; i < count; i++) { - size_t j = (i + 1) % count; - size_t k = (i + count - 1) % count; + for (size_t i = 0; i < size(); i++) { + size_t j = (i + 1) % size(); + size_t k = (i + size() - 1) % size(); qvec3d v1 = qv::normalize(at(j) - at(i)); qvec3d v2 = qv::normalize(at(i) - at(k)); @@ -406,8 +881,9 @@ public: temp.push_back(at(i)); } - if (count != temp.count) + if (size() != temp.size()) { *this = std::move(temp); + } } qplane3d plane() const @@ -467,8 +943,8 @@ public: void check(const vec_t &bogus_range = DEFAULT_BOGUS_RANGE, const vec_t &on_epsilon = DEFAULT_ON_EPSILON) const { - if (count < 3) - FError("{} points", count); + if (size() < 3) + FError("{} points", size()); vec_t a = area(); if (a < 1) @@ -476,7 +952,7 @@ public: qplane3d face = plane(); - for (size_t i = 0; i < count; i++) { + for (size_t i = 0; i < size(); i++) { const qvec3d &p1 = at(i); size_t j = 0; @@ -490,7 +966,7 @@ public: FError("point off plane"); /* check the edge isn't degenerate */ - const qvec3d &p2 = at((i + 1) % count); + const qvec3d &p2 = at((i + 1) % size()); qvec3d dir = p2 - p1; if (qv::length(dir) < on_epsilon) @@ -500,7 +976,7 @@ public: vec_t edgedist = qv::dot(p1, edgenormal) + on_epsilon; /* all other points must be on front side */ - for (size_t j = 0; j < count; j++) { + for (size_t j = 0; j < size(); j++) { if (j == i) continue; d = qv::dot(at(j), edgenormal); @@ -525,11 +1001,11 @@ public: qplane3d p = plane(); winding_edges_t result; - result.reserve(count); + result.reserve(size()); - for (size_t i = 0; i < count; i++) { + for (size_t i = 0; i < size(); i++) { const qvec3d &v0 = at(i); - const qvec3d &v1 = at((i + 1) % count); + const qvec3d &v1 = at((i + 1) % size()); qvec3d edgevec = qv::normalize(v1 - v0); qvec3d normal = qv::cross(edgevec, p.normal); @@ -549,7 +1025,7 @@ public: /* determine sides for each point */ size_t i; - for (i = 0; i < count; i++) { + for (i = 0; i < size(); i++) { vec_t dot = plane.distance_to(at(i)); if (dists) { @@ -594,8 +1070,8 @@ public: std::array, 2> clip( const qplane3d &plane, const vec_t &on_epsilon = DEFAULT_ON_EPSILON, const bool &keepon = false) const { - vec_t *dists = (vec_t *)alloca(sizeof(vec_t) * (count + 1)); - planeside_t *sides = (planeside_t *)alloca(sizeof(planeside_t) * (count + 1)); + vec_t *dists = (vec_t *)alloca(sizeof(vec_t) * (size() + 1)); + planeside_t *sides = (planeside_t *)alloca(sizeof(planeside_t) * (size() + 1)); std::array counts = calc_sides(plane, dists, sides, on_epsilon); @@ -609,7 +1085,7 @@ public: std::array results{}; - for (size_t i = 0; i < count; i++) { + for (size_t i = 0; i < size(); i++) { const qvec3d &p1 = at(i); if (sides[i] == SIDE_ON) { @@ -626,7 +1102,7 @@ public: continue; /* generate a split point */ - const qvec3d &p2 = at((i + 1) % count); + const qvec3d &p2 = at((i + 1) % size()); vec_t dot = dists[i] / (dists[i] - dists[i + 1]); qvec3d mid; @@ -644,7 +1120,7 @@ public: results[SIDE_BACK].push_back(mid); } - if (results[SIDE_FRONT].count > MAX_POINTS_ON_WINDING || results[SIDE_BACK].count > MAX_POINTS_ON_WINDING) + if (results[SIDE_FRONT].size() > MAX_POINTS_ON_WINDING || results[SIDE_BACK].size() > MAX_POINTS_ON_WINDING) FError("MAX_POINTS_ON_WINDING"); return {std::move(results[SIDE_FRONT]), std::move(results[SIDE_BACK])}; @@ -652,7 +1128,7 @@ public: void dice(vec_t subdiv, std::function save_fn) { - if (!count) + if (!size()) return; aabb3d b = bounds(); @@ -715,7 +1191,7 @@ public: winding_base_t flip() const { - winding_base_t result(count); + winding_base_t result(size()); std::reverse_copy(begin(), end(), result.begin()); @@ -773,6 +1249,6 @@ public: // the default amount of points to keep on stack constexpr size_t STACK_POINTS_ON_WINDING = MAX_POINTS_ON_WINDING / 4; -using winding_t = winding_base_t; +using winding_t = winding_base_t; }; // namespace polylib diff --git a/include/common/prtfile.hh b/include/common/prtfile.hh index e41d1ce1..e3509141 100644 --- a/include/common/prtfile.hh +++ b/include/common/prtfile.hh @@ -26,7 +26,7 @@ constexpr size_t PRT_MAX_WINDING_FIXED = 24; -using prtfile_winding_t = polylib::winding_base_t; +using prtfile_winding_t = polylib::winding_base_t>; struct prtfile_portal_t { diff --git a/include/qbsp/winding.hh b/include/qbsp/winding.hh index bf02c0f6..cb5149e9 100644 --- a/include/qbsp/winding.hh +++ b/include/qbsp/winding.hh @@ -23,10 +23,6 @@ #include "common/polylib.hh" -// now much a winding will use on the stack -// before needing an overflow. -constexpr size_t STACK_WINDING_SIZE = 64; - -using winding_t = polylib::winding_base_t; +using winding_t = polylib::winding_t; winding_t BaseWindingForPlane(const qplane3d &p); diff --git a/include/vis/vis.hh b/include/vis/vis.hh index 92cef7f7..05f2166c 100644 --- a/include/vis/vis.hh +++ b/include/vis/vis.hh @@ -39,23 +39,23 @@ enum pstatus_t pstat_done }; -struct winding_t : polylib::winding_base_t +struct winding_t : polylib::winding_base_t> { qvec3d origin; // Bounding sphere for fast clipping tests vec_t radius; // Not updated, so won't shrink when clipping - inline winding_t() : polylib::winding_base_t() { } + inline winding_t() : polylib::winding_base_t>() { } // construct winding from range. // iterators must have operator+ and operator-. template, int> = 0> - inline winding_t(Iter begin, Iter end) : polylib::winding_base_t(begin, end) + inline winding_t(Iter begin, Iter end) : polylib::winding_base_t>(begin, end) { set_winding_sphere(); } // initializer list constructor - inline winding_t(std::initializer_list l) : polylib::winding_base_t(l) + inline winding_t(std::initializer_list l) : polylib::winding_base_t>(l) { set_winding_sphere(); } diff --git a/tests/test.cc b/tests/test.cc index 8a39feb7..b7e2c7dc 100644 --- a/tests/test.cc +++ b/tests/test.cc @@ -350,10 +350,10 @@ TEST_CASE("resetContainer", "[settings]") #include "common/polylib.hh" -struct winding_check_t : polylib::winding_base_t<4> +struct winding_check_t : polylib::winding_base_t> { public: - inline size_t vector_size() { return vector.size(); } + inline size_t vector_size() { return storage.vector_size(); } }; TEST_CASE("winding iterators", "[winding_base_t]") @@ -410,7 +410,7 @@ TEST_CASE("winding iterators", "[winding_base_t]") // check that constructors work { - polylib::winding_base_t<4> winding_other(winding); + polylib::winding_base_t> winding_other(winding); { auto it = winding_other.begin(); @@ -430,7 +430,7 @@ TEST_CASE("winding iterators", "[winding_base_t]") { - polylib::winding_base_t<4> winding_other({ { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 }, { 4, 4, 4 } }); + polylib::winding_base_t> winding_other({ { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 }, { 3, 3, 3 }, { 4, 4, 4 } }); { auto it = winding_other.begin(); @@ -449,7 +449,7 @@ TEST_CASE("winding iterators", "[winding_base_t]") } { - polylib::winding_base_t<4> winding_other(std::move(winding)); + polylib::winding_base_t> winding_other(std::move(winding)); CHECK(winding.size() == 0); CHECK(winding.begin() == winding.end()); diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index 30977e6b..74161981 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -1788,7 +1788,7 @@ TEST_CASE("winding", "[benchmark][.releaseonly]") { ankerl::nanobench::doNotOptimizeAway(temp); }); bench.run("polylib::winding_base_t<6> construct", [&] { - polylib::winding_base_t<6> temp; + polylib::winding_base_t> temp; ankerl::nanobench::doNotOptimizeAway(temp); }); }