Modules/Containers/public/Map.hpp
Шурупов Илья Викторович fca8fe0902 Render strokes at the shown level and paint full resolution in the background
Dabs now go only into preview tiles of the shown level at every zoom,
including a level 0 preview overlay that the compositor shows in
place of the layer tile. The layer tiles are painted by the engine,
and releasing a stroke commits history without waiting for them.
Stroke opacity coverage is shared per tile across a stroke's flushes,
each engine job only replays the dabs that touch its tile, and undo
drops an open preview instead of painting it. Readers that need final
pixels settle explicitly; the renderer no longer does.

tp::Map::remove rehashed the whole table on every call, which made
applying thousands of finished tiles quadratic; it now rehashes only
when the table is mostly empty or full of deleted slots.
2026-10-20 22:13:30 +03:00

414 lines
No EOL
9.4 KiB
C++

#pragma once
#include "Common.hpp"
#include "ContainersCommon.hpp"
namespace tp {
template <typename Key>
ualni DefaultHashFunc(SelectValueOrReference<Key> key) {
return hash(key);
}
template <
typename tKey,
typename tVal,
class tAllocator = DefaultAllocator,
ualni (*tHashFunc)(SelectValueOrReference<tKey>) = DefaultHashFunc<tKey>,
int tTableInitialSize = 4>
class Map {
enum {
MAP_PERTURB_SHIFT = 5,
MAP_MIN_SIZE = 4,
MAP_MAX_LOAD_PERCENTAGE = 66,
};
typedef SelectValueOrReference<tKey> KeyArg;
typedef SelectValueOrReference<tVal> ValArg;
public:
class Node {
friend Map;
Node(KeyArg aKey, ValArg aVal) :
key(aKey),
val(aVal) {}
public:
tKey key;
tVal val;
};
struct Idx {
alni idx = -1;
[[nodiscard]] bool isValid() const { return bool(*this); }
explicit operator bool() const { return idx != -1; }
};
private:
tAllocator mAlloc;
Node** mTable = nullptr;
ualni mNSlots = 0;
ualni mNDeleted = 0;
ualni mNEntries = 0;
private:
static constexpr halnf maxLoadFactor() { return halnf(MAP_MAX_LOAD_PERCENTAGE) / 100.f; }
inline Node** newTable(const ualni len) { return new (mAlloc.allocate(sizeof(Node*) * len)) Node*[len](); }
inline Node* newNode(KeyArg key, ValArg val) { return new (mAlloc.allocate(sizeof(Node))) Node(key, val); }
inline Node* newNodeNotConstructed() { return (Node*) mAlloc.allocate(sizeof(Node)); }
inline void deleteTable(Node** table) { mAlloc.deallocate(table); }
inline void deleteNode(Node* p) {
p->~Node();
mAlloc.deallocate(p);
}
void markDeletedSlot(ualni idx) const { mTable[idx] = (Node*) -1; }
static bool isDeletedNode(Node* node) { return node == (Node*) -1; }
void rehash() {
alni nSlotsOld = mNSlots;
Node** tableOld = mTable;
mNSlots = next2pow((uhalni) ((1.f / (maxLoadFactor())) * mNEntries + 1));
mTable = newTable(mNSlots);
mNEntries = 0;
mNDeleted = 0;
for (alni i = 0; i < nSlotsOld; i++) {
if (!tableOld[i] || isDeletedNode(tableOld[i])) {
continue;
}
alni idx = findSlotWrite(tableOld[i]->key);
mTable[idx] = tableOld[i];
mNEntries++;
}
deleteTable(tableOld);
}
alni findSlotRead(KeyArg key) const {
ualni const hashed_key = tHashFunc(key);
ualni const mask = mNSlots - 1;
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
alni idx = hashed_key & mask;
NEXT:
if (isDeletedNode(mTable[idx])) {
goto SKIP;
}
if (!mTable[idx]) {
return -1;
}
if (mTable[idx]->key == key) {
return idx;
}
SKIP:
idx = ((5 * idx) + 1 + shift) & mask;
goto NEXT;
}
// compares keys only when collisions occur
alni findSlotReadExisting(KeyArg key) const {
ualni const hashed_key = tHashFunc(key);
ualni const mask = mNSlots - 1;
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
alni idx = hashed_key & mask;
NEXT:
if (isDeletedNode(mTable[idx])) {
goto SKIP;
}
if (!mTable[idx]) {
return -1;
}
if (mTable[((5 * idx) + 1 + shift) & mask] == nullptr) {
return idx;
}
if (mTable[idx]->key == key) {
return idx;
}
SKIP:
idx = ((5 * idx) + 1 + shift) & mask;
goto NEXT;
}
ualni findSlotWrite(KeyArg key) const {
ualni const hashed_key = tHashFunc(key);
ualni const mask = mNSlots - 1;
ualni const shift = (hashed_key >> MAP_PERTURB_SHIFT) & ~1;
ualni idx = hashed_key & mask;
alni reusable = -1;
NEXT:
if (!mTable[idx]) {
return reusable >= 0 ? (ualni) reusable : idx;
}
if (isDeletedNode(mTable[idx])) {
if (reusable < 0) reusable = (alni) idx;
} else if (mTable[idx]->key == key) {
return idx;
}
idx = ((5 * idx) + 1 + shift) & mask;
goto NEXT;
}
void occupy(ualni idx) {
if (isDeletedNode(mTable[idx])) mNDeleted--;
mNEntries++;
}
[[nodiscard]] bool overloaded() const { return (halnf) (mNEntries + mNDeleted) / mNSlots > maxLoadFactor(); }
void put(Node* node) {
const ualni idx = findSlotWrite(node->key);
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
occupy(idx);
}
mTable[idx] = node;
if (overloaded()) {
rehash();
}
}
public:
Map() {
mNSlots = next2pow(uhalni(tTableInitialSize - 1));
mTable = newTable(mNSlots);
}
Map(const Map& in) { this->operator=(in); }
Node** buff() const { return mTable; }
[[nodiscard]] ualni size() const { return mNEntries; }
[[nodiscard]] ualni slotsSize() const { return mNEntries; }
[[nodiscard]] const tAllocator& getAllocator() const { return mAlloc; }
void put(KeyArg key, ValArg val) {
const ualni idx = findSlotWrite(key);
if (!mTable[idx] || isDeletedNode(mTable[idx])) {
occupy(idx);
mTable[idx] = newNode(key, val);
} else {
mTable[idx]->val = val;
}
if (overloaded()) {
rehash();
}
}
// undefined behavior if item is not presents
tVal& get(KeyArg key) {
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
return mTable[findSlotReadExisting(key)]->val;
}
const tVal& get(KeyArg key) const {
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
return mTable[findSlotReadExisting(key)]->val;
}
[[nodiscard]] Idx presents(KeyArg key) const { return { findSlotRead(key) }; }
void remove(KeyArg key) {
DEBUG_ASSERT(findSlotRead(key) != -1 && "Key Error")
auto idx = findSlotReadExisting(key);
deleteNode(mTable[idx]);
markDeletedSlot(idx);
mNEntries--;
mNDeleted++;
const bool sparse = mNSlots > MAP_MIN_SIZE * 4 && (halnf) mNEntries < (halnf) mNSlots * maxLoadFactor() * 0.25f;
if (sparse || overloaded()) {
rehash();
}
}
const tVal& getSlotVal(ualni slot) const {
DEBUG_ASSERT(slot < mNSlots && (mTable[slot] && !isDeletedNode(mTable[slot])) && "Key Error")
return mTable[slot]->val;
}
tVal& operator[](KeyArg key) {
auto idx = presents(key);
if (idx.isValid()) return getSlotVal(idx);
put(key, {});
return get(key);
}
tVal& getSlotVal(ualni slot) {
DEBUG_ASSERT(slot < mNSlots && (mTable[slot] && !isDeletedNode(mTable[slot])) && "Key Error")
return mTable[slot]->val;
}
const tVal& getSlotVal(Idx slot) const {
DEBUG_ASSERT(slot.idx < mNSlots && (mTable[slot.idx] && !isDeletedNode(mTable[slot.idx])) && "Key Error")
return mTable[slot.idx]->val;
}
tVal& getSlotVal(Idx slot) {
DEBUG_ASSERT(slot.idx < mNSlots && (mTable[slot.idx] && !isDeletedNode(mTable[slot.idx])) && "Key Error")
return mTable[slot.idx]->val;
}
Map& operator=(const Map& in) {
if (this == &in) {
return *this;
}
for (ualni i = 0; i < mNSlots; i++) {
if (mTable[i] && !isDeletedNode(mTable[i])) {
deleteNode(mTable[i]);
}
}
mNEntries = 0;
mNDeleted = 0;
mNSlots = in.mNSlots;
deleteTable(mTable);
mTable = newTable(mNSlots);
for (alni i = 0; i < mNSlots; i++) {
if (in.mTable[i] && !isDeletedNode(in.mTable[i])) {
put(in.mTable[i]->key, in.mTable[i]->val);
}
}
return *this;
}
[[nodiscard]] bool operator==(const Map& in) const {
if (this == &in) {
return true;
}
if (in.mNEntries != mNEntries) {
return false;
}
for (auto i : in) {
if (!presents(i->key) || get(i->key) != i->val) {
return false;
}
}
return true;
}
void removeAll() {
for (ualni i = 0; i < mNSlots; i++) {
if (mTable[i] && !isDeletedNode(mTable[i])) {
deleteNode(mTable[i]);
}
}
deleteTable(mTable);
mTable = newTable(tTableInitialSize);
mNSlots = tTableInitialSize;
mNEntries = 0;
mNDeleted = 0;
}
[[nodiscard]] alni slotIdx(alni entry_idx_in) const {
alni entry_idx = -1;
for (alni slot_idx = 0; slot_idx < mNSlots; slot_idx++) {
if (mTable[slot_idx]) {
entry_idx++;
}
if (entry_idx == entry_idx_in) {
return slot_idx;
}
}
return -1;
}
Node* GetEntry(ualni idx) {
auto slot = slotIdx(idx);
DEBUG_ASSERT(slot != -1 && "Key error")
return mTable[slot];
}
const Node* GetEntry(ualni idx) const {
auto slot = slotIdx(idx);
DEBUG_ASSERT(slot != -1 && "Key error")
return mTable[slot];
}
public:
class Iterator {
const Map* map;
Node* mIter;
alni mSlot;
alni mEntry;
friend Map;
explicit Iterator(const Map* _map) {
mSlot = -1;
mEntry = -1;
map = _map;
this->operator++();
}
public:
Node* operator->() { return mIter; }
const Node* operator->() const { return mIter; }
const Iterator& operator*() const { return *this; }
bool operator!=(ualni idx) const { return mSlot != (alni) idx; }
void operator++() {
mSlot++;
while (mSlot < (alni) map->mNSlots && (map->isDeletedNode(map->mTable[mSlot]) || !map->mTable[mSlot])) {
mSlot++;
}
if (mSlot < (alni) map->mNSlots) {
mIter = map->mTable[mSlot];
mEntry++;
} else {
mIter = nullptr;
}
}
};
[[nodiscard]] Iterator begin() const { return Iterator(this); }
[[nodiscard]] ualni end() const { return mNSlots; }
template <class Archiver>
void archiveWrite(Archiver& ar) const {
ar << mNEntries;
for (auto item : *this) {
ar << item->val;
ar << item->key;
}
}
template <class Archiver>
void archiveRead(Archiver& ar) {
removeAll();
decltype(mNSlots) len;
ar >> len;
for (auto i = len; i; i--) {
auto node = newNodeNotConstructed();
ar >> node->val;
ar >> node->key;
put(node);
}
}
~Map() {
for (ualni i = 0; i < mNSlots; i++) {
if (mTable[i] && !isDeletedNode(mTable[i])) {
deleteNode(mTable[i]);
}
}
deleteTable(mTable);
}
};
}