/* * This file is part of OpenTTD. * OpenTTD 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, version 2. * OpenTTD 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 OpenTTD. If not, see . */ /** @file smallmatrix_type.hpp Simple matrix class that allows allocating an item without the need to copy this->data needlessly. */ #ifndef SMALLMATRIX_TYPE_HPP #define SMALLMATRIX_TYPE_HPP #include "alloc_func.hpp" #include "mem_func.hpp" /** * Simple matrix template class. * * Allocating a matrix in one piece reduces overhead in allocations compared * with allocating a vector of vectors and saves some pointer dereferencing. * However, you can only get rectangular matrixes like this and if you're * changing their height very often performance will probably be worse than * with a vector of vectors, due to more frequent copying of memory blocks. * * No iterators are provided as iterating the columns would require persistent * column objects. Those do not exist. Providing iterators with transient * column objects would tie each iterator to a column object, thus replacing * previously retrieved columns when iterating and defeating the point of * iteration. * * It's expected that the items don't need to be constructed or deleted by the * container. Only memory allocation and deallocation is performed. This is the * same for all openttd "SmallContainer" classes. * * @tparam T The type of the items stored */ template class SmallMatrix { protected: T *data; ///< The pointer to the first item uint width; ///< Number of items over first axis uint height; ///< Number of items over second axis uint capacity; ///< The available space for storing items public: SmallMatrix() : data(nullptr), width(0), height(0), capacity(0) {} /** * Copy constructor. * @param other The other matrix to copy. */ SmallMatrix(const SmallMatrix &other) : data(nullptr), width(0), height(0), capacity(0) { this->Assign(other); } ~SmallMatrix() { free(this->data); } /** * Assignment. * @param other The other matrix to assign. */ SmallMatrix &operator=(const SmallMatrix &other) { this->Assign(other); return *this; } /** * Assign items from other vector. */ inline void Assign(const SmallMatrix &other) { if (&other == this) return; this->height = other.Height(); this->width = other.Width(); uint num_items = this->width * this->height; if (num_items > this->capacity) { this->capacity = num_items; free(this->data); this->data = MallocT(num_items); MemCpyT(this->data, other[0], num_items); } else if (num_items > 0) { MemCpyT(this->data, other[0], num_items); } } /** * Remove all rows from the matrix. */ inline void Clear() { /* In fact we just reset the width avoiding the need to * probably reallocate the same amount of memory the matrix was * previously using. */ this->width = 0; } /** * Remove all items from the list and free allocated memory. */ inline void Reset() { this->height = 0; this->width = 0; this->capacity = 0; free(this->data); this->data = nullptr; } /** * Compact the matrix down to the smallest possible size. */ inline void Compact() { uint capacity = this->height * this->width; if (capacity >= this->capacity) return; this->capacity = capacity; this->data = ReallocT(this->data, this->capacity); } /** * Erase a column, replacing it with the last one. * @param x Position of the column. */ void EraseColumn(uint x) { if (x < --this->width) { MemCpyT(this->data + x * this->height, this->data + this->width * this->height, this->height); } } /** * Remove columns from the matrix while preserving the order of other columns. * @param x First column to remove. * @param count Number of consecutive columns to remove. */ void EraseColumnPreservingOrder(uint x, uint count = 1) { if (count == 0) return; assert(x < this->width); assert(x + count <= this->width); this->width -= count; uint to_move = (this->width - x) * this->height; if (to_move > 0) { MemMoveT(this->data + x * this->height, this->data + (x + count) * this->height, to_move); } } /** * Erase a row, replacing it with the last one. * @param y Position of the row. */ void EraseRow(uint y) { if (y < this->height - 1) { for (uint x = 0; x < this->width; ++x) { this->data[x * this->height + y] = this->data[(x + 1) * this->height - 1]; } } this->Resize(this->width, this->height - 1); } /** * Remove columns from the matrix while preserving the order of other columns. * @param y First column to remove. * @param count Number of consecutive columns to remove. */ void EraseRowPreservingOrder(uint y, uint count = 1) { if (this->height > count + y) { for (uint x = 0; x < this->width; ++x) { MemMoveT(this->data + x * this->height + y, this->data + x * this->height + y + count, this->height - count - y); } } this->Resize(this->width, this->height - count); } /** * Append rows. * @param to_add Number of rows to append. */ inline void AppendRow(uint to_add = 1) { this->Resize(this->width, to_add + this->height); } /** * Append rows. * @param to_add Number of rows to append. */ inline void AppendColumn(uint to_add = 1) { this->Resize(to_add + this->width, this->height); } /** * Set the size to a specific width and height, preserving item positions * as far as possible in the process. * @param new_width Target width. * @param new_height Target height. */ inline void Resize(uint new_width, uint new_height) { uint new_capacity = new_width * new_height; T *new_data = nullptr; void (*copy)(T *dest, const T *src, size_t count) = nullptr; if (new_capacity > this->capacity) { /* If the data doesn't fit into current capacity, resize and copy ... */ new_data = MallocT(new_capacity); copy = &MemCpyT; } else { /* ... otherwise just move the columns around, if necessary. */ new_data = this->data; copy = &MemMoveT; } if (this->height != new_height || new_data != this->data) { if (this->height > 0) { if (new_height > this->height) { /* If matrix is growing, copy from the back to avoid * overwriting uncopied data. */ for (uint x = this->width; x > 0; --x) { if (x * new_height > new_capacity) continue; (*copy)(new_data + (x - 1) * new_height, this->data + (x - 1) * this->height, std::min(this->height, new_height)); } } else { /* If matrix is shrinking copy from the front. */ for (uint x = 0; x < this->width; ++x) { if ((x + 1) * new_height > new_capacity) break; (*copy)(new_data + x * new_height, this->data + x * this->height, std::min(this->height, new_height)); } } } this->height = new_height; if (new_data != this->data) { free(this->data); this->data = new_data; this->capacity = new_capacity; } } this->width = new_width; } inline uint Height() const { return this->height; } inline uint Width() const { return this->width; } /** * Get item x/y (const). * * @param x X-position of the item. * @param y Y-position of the item. * @return Item at specified position. */ inline const T &Get(uint x, uint y) const { assert(x < this->width && y < this->height); return this->data[x * this->height + y]; } /** * Get item x/y. * * @param x X-position of the item. * @param y Y-position of the item. * @return Item at specified position. */ inline T &Get(uint x, uint y) { assert(x < this->width && y < this->height); return this->data[x * this->height + y]; } /** * Get column "number" (const) * * @param x Position of the column. * @return Column at "number". */ inline const T *operator[](uint x) const { assert(x < this->width); return this->data + x * this->height; } /** * Get column "number" (const) * * @param x Position of the column. * @return Column at "number". */ inline T *operator[](uint x) { assert(x < this->width); return this->data + x * this->height; } }; #endif /* SMALLMATRIX_TYPE_HPP */