#pragma once #include "basis/seadTypes.h" #include "container/seadFreeList.h" #include "container/seadTreeMap.h" #include "prim/seadDelegate.h" #include "prim/seadSafeString.h" namespace sead { /// Sorted associative container with fixed-length string keys. /// This is essentially std::map template class StrTreeMap : public TreeMapImpl { public: using MapImpl = TreeMapImpl; class Node : public MapImpl::Node { public: Node(StrTreeMap* map, const SafeString& key, const Value& value) : mValue(value), mMap(map) { BufferedSafeString buffer(mKeyData, MaxKeyLength + 1); buffer.copy(key); this->mKey = buffer; } void erase_() override; Value& value() { return mValue; } const Value& value() const { return mValue; } private: friend class StrTreeMap; Value mValue; StrTreeMap* mMap; char mKeyData[MaxKeyLength + 1]; }; ~StrTreeMap(); void allocBuffer(s32 node_max, Heap* heap, s32 alignment = sizeof(void*)); void setBuffer(s32 node_max, void* buffer); void freeBuffer(); Value* insert(const SafeString& key, const Value& value); void clear(); Node* find(const SafeString& key) const; // Callable must have the signature Key&, Value& template void forEach(const Callable& delegate) const; private: void eraseNodeForClear_(typename MapImpl::Node* node); FreeList mFreeList; s32 mSize = 0; s32 mCapacity = 0; }; template inline void StrTreeMap::Node::erase_() { StrTreeMap* const map = mMap; void* const this_ = this; // Note: Nintendo does not call the destructor, which is dangerous... map->mFreeList.free(this_); --map->mSize; } template inline StrTreeMap::~StrTreeMap() { void* work = mFreeList.work(); if (!work) return; clear(); freeBuffer(); } template inline void StrTreeMap::allocBuffer(s32 node_max, Heap* heap, s32 alignment) { SEAD_ASSERT(mFreeList.work() == nullptr); if (node_max <= 0) { SEAD_ASSERT_MSG(false, "node_max[%d] must be larger than zero", node_max); AllocFailAssert(heap, node_max * sizeof(Node), alignment); } void* work = AllocBuffer(node_max * sizeof(Node), heap, alignment); if (work) setBuffer(node_max, work); } template inline void StrTreeMap::setBuffer(s32 node_max, void* buffer) { mCapacity = node_max; mFreeList.setWork(buffer, sizeof(Node), node_max); } template inline void StrTreeMap::freeBuffer() { void* buffer = mFreeList.work(); if (!buffer) return; ::operator delete[](buffer); mCapacity = 0; mFreeList.reset(); } template inline Value* StrTreeMap::insert(const SafeString& key, const Value& value) { if (mSize >= mCapacity) { if (Node* node = find(key)) { node->value() = value; return &node->value(); } SEAD_ASSERT_MSG(false, "map is full."); return nullptr; } Node* node = new (mFreeList.alloc()) Node(this, key, value); ++mSize; MapImpl::insert(node); return &node->value(); } template inline void StrTreeMap::clear() { Delegate1, typename MapImpl::Node*> delegate( this, &StrTreeMap::eraseNodeForClear_); MapImpl::forEach(delegate); mSize = 0; MapImpl::clear(); } template inline typename StrTreeMap::Node* StrTreeMap::find(const SafeString& key) const { return static_cast(MapImpl::find(key)); } template template inline void StrTreeMap::forEach(const Callable& delegate) const { MapImpl::forEach([&delegate](auto* base_node) { auto* node = static_cast(base_node); delegate(node->key(), node->value()); }); } template inline void StrTreeMap::eraseNodeForClear_(typename MapImpl::Node* node) { // Note: Nintendo does not call the destructor, which is dangerous... mFreeList.free(node); } } // namespace sead