/****************************************************************************** * Spine Runtimes License Agreement * Last updated April 5, 2025. Replaces all prior versions. * * Copyright (c) 2013-2025, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #ifndef Spine_Map_h #define Spine_Map_h #include #include #include #include #include #include #include namespace spine { template class MapHash { public: size_t operator()(const K &key) const { return (size_t) key; } }; template class MapHash { public: size_t operator()(T *const &key) const { uintptr_t value = (uintptr_t) key; return (size_t) (value >> 4); } }; template<> class MapHash { public: size_t operator()(const long long &key) const { unsigned long long value = (unsigned long long) key; return (size_t) (value ^ (value >> 32)); } }; template<> class MapHash { public: size_t operator()(const unsigned long long &key) const { return (size_t) (key ^ (key >> 32)); } }; template<> class MapHash { public: size_t operator()(const String &key) const { const unsigned char *buffer = (const unsigned char *) key.buffer(); size_t length = key.length(); uint64_t hash = 14695981039346656037ULL; for (size_t i = 0; i < length; i++) { hash ^= buffer[i]; hash *= 1099511628211ULL; } return (size_t) (hash ^ (hash >> 32)); } }; template class MapEquals { public: bool operator()(const K &a, const K &b) const { return a == b; } }; /// A compact open-addressed hash map. Unlike HashMap, this implementation does not allocate per entry. /// Iteration order is unspecified and may change when the map grows or entries are removed. template, typename Equals = MapEquals> class Map : public SpineObject { private: enum State { Empty = 0, Occupied = 1, Deleted = 2 }; struct Entry { K key; V value; }; public: class Pair { public: explicit Pair(K &k, V &v) : key(k), value(v) { } K &key; V &value; }; class Entries { public: friend class Map; bool hasNext() { while (_index < _map->_capacity) { if (_map->_states[_index] == Occupied) return true; _index++; } return false; } Pair next() { assert(_index < _map->_capacity); assert(_map->_states[_index] == Occupied); Entry &entry = _map->_entries[_index++]; return Pair(entry.key, entry.value); } private: explicit Entries(Map &map) : _map(&map), _index(0) { } Map *_map; size_t _index; }; Map() : _entries(NULL), _states(NULL), _capacity(0), _size(0), _deleted(0), _hash(), _equals() { } explicit Map(size_t capacity) : _entries(NULL), _states(NULL), _capacity(0), _size(0), _deleted(0), _hash(), _equals() { ensureCapacity(capacity); } Map(const Map &other) : _entries(NULL), _states(NULL), _capacity(0), _size(0), _deleted(0), _hash(other._hash), _equals(other._equals) { ensureCapacity(other._size); for (size_t i = 0; i < other._capacity; i++) { if (other._states[i] == Occupied) put(other._entries[i].key, other._entries[i].value); } } Map &operator=(const Map &other) { if (this == &other) return *this; clear(); _hash = other._hash; _equals = other._equals; ensureCapacity(other._size); for (size_t i = 0; i < other._capacity; i++) { if (other._states[i] == Occupied) put(other._entries[i].key, other._entries[i].value); } return *this; } ~Map() { release(); } size_t size() const { return _size; } size_t getCapacity() const { return _capacity; } bool isEmpty() const { return _size == 0; } void ensureCapacity(size_t expectedSize) { size_t needed = capacityFor(expectedSize); if (_capacity < needed) rehash(needed); } void clear() { for (size_t i = 0; i < _capacity; i++) { if (_states[i] == Occupied) resetEntry(i); _states[i] = Empty; } _size = 0; _deleted = 0; } void release() { if (!_entries) return; clear(); for (size_t i = 0; i < _capacity; i++) _entries[i].~Entry(); SpineExtension::free(_entries, __FILE__, __LINE__); SpineExtension::free(_states, __FILE__, __LINE__); _entries = NULL; _states = NULL; _capacity = 0; } bool containsKey(const K &key) const { return findIndex(key) >= 0; } V *get(const K &key) { int index = findIndex(key); return index >= 0 ? &_entries[index].value : NULL; } const V *get(const K &key) const { int index = findIndex(key); return index >= 0 ? &_entries[index].value : NULL; } V operator[](const K &key) const { const V *value = get(key); assert(value); return *value; } void put(const K &key, const V &value) { ensureLoadForOneMore(); bool found = false; size_t index = findSlot(key, found); if (found) { _entries[index].value = value; return; } if (_states[index] == Deleted) _deleted--; _states[index] = Occupied; _entries[index].key = key; _entries[index].value = value; _size++; } /// Adds the key and value only if the key is not already present. /// @return The existing value, or V() if the key was not present and the new value was added. V putMissing(const K &key, const V &value) { ensureLoadForOneMore(); bool found = false; size_t index = findSlot(key, found); if (found) return _entries[index].value; if (_states[index] == Deleted) _deleted--; _states[index] = Occupied; _entries[index].key = key; _entries[index].value = value; _size++; return V(); } bool addAll(Array &keys, const V &value) { size_t oldSize = _size; ensureCapacity(_size + keys.size()); for (size_t i = 0; i < keys.size(); i++) put(keys[i], value); return _size != oldSize; } bool remove(const K &key) { int index = findIndex(key); if (index < 0) return false; resetEntry((size_t) index); _states[index] = Deleted; _size--; _deleted++; if (_size == 0) { memset(_states, Empty, _capacity); _deleted = 0; } return true; } Entries getEntries() { return Entries(*this); } private: Entry *_entries; unsigned char *_states; size_t _capacity; size_t _size; size_t _deleted; Hash _hash; Equals _equals; static size_t mix(size_t h) { h ^= h >> 16; h *= (size_t) 0x7feb352d; h ^= h >> 15; h *= (size_t) 0x846ca68b; h ^= h >> 16; return h; } static size_t nextPowerOfTwo(size_t value) { size_t result = 8; while (result < value) result <<= 1; return result; } static size_t capacityFor(size_t expectedSize) { if (expectedSize < 6) return 8; return nextPowerOfTwo((expectedSize * 4 + 2) / 3); } void ensureLoadForOneMore() { if (_capacity == 0) { rehash(8); return; } if ((_size + _deleted + 1) * 4 > _capacity * 3) { size_t newCapacity = _deleted > _size ? _capacity : _capacity << 1; rehash(newCapacity); } } int findIndex(const K &key) const { if (_capacity == 0) return -1; size_t mask = _capacity - 1; size_t index = mix(_hash(key)) & mask; while (true) { unsigned char state = _states[index]; if (state == Empty) return -1; if (state == Occupied && _equals(_entries[index].key, key)) return (int) index; index = (index + 1) & mask; } } /// Returns an index containing key, an empty slot, or the first deleted slot encountered. size_t findSlot(const K &key, bool &found) const { size_t mask = _capacity - 1; size_t index = mix(_hash(key)) & mask; size_t firstDeleted = (size_t) -1; while (true) { unsigned char state = _states[index]; if (state == Empty) { found = false; return firstDeleted != (size_t) -1 ? firstDeleted : index; } if (state == Deleted) { if (firstDeleted == (size_t) -1) firstDeleted = index; } else if (_equals(_entries[index].key, key)) { found = true; return index; } index = (index + 1) & mask; } } void rehash(size_t newCapacity) { newCapacity = nextPowerOfTwo(newCapacity); Entry *oldEntries = _entries; unsigned char *oldStates = _states; size_t oldCapacity = _capacity; _entries = SpineExtension::alloc(newCapacity, __FILE__, __LINE__); _states = SpineExtension::calloc(newCapacity, __FILE__, __LINE__); assert(_entries); assert(_states); _capacity = newCapacity; _size = 0; _deleted = 0; for (size_t i = 0; i < _capacity; i++) new (_entries + i) Entry(); if (oldEntries) { for (size_t i = 0; i < oldCapacity; i++) { if (oldStates[i] == Occupied) put(oldEntries[i].key, oldEntries[i].value); oldEntries[i].~Entry(); } SpineExtension::free(oldEntries, __FILE__, __LINE__); SpineExtension::free(oldStates, __FILE__, __LINE__); } } void resetEntry(size_t index) { _entries[index].key = K(); _entries[index].value = V(); } }; } #endif /* Spine_Map_h */