/* * Copyright (c) 2026, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace GC { // Cell-typed key and/or value slots are held as Weak so entries vanish // when their referent is collected; non-cell slots are stored directly. template class WeakHashMap { static constexpr bool key_is_cell = IsBaseOf; static constexpr bool value_is_cell = IsBaseOf; using KeyStorage = Conditional, K>; using ValueStorage = Conditional, V>; struct KeyTraits : public DefaultTraits { static unsigned hash(KeyStorage const& value) { if constexpr (key_is_cell) return Traits::hash(value.ptr()); else return Traits::hash(value); } }; using TableType = HashMap; public: WeakHashMap() = default; HashSetResult set(K const& key, V const& value) { maybe_prune(); return m_table.set(to_key_storage(key), to_value_storage(value)); } HashSetResult set(K const& key, V&& value) { maybe_prune(); return m_table.set(to_key_storage(key), to_value_storage(move(value))); } bool remove(K const& key) { maybe_prune(); auto it = find_iterator(key); if (it == m_table.end()) return false; m_table.remove(it); return true; } template V& ensure(K const& key, Callback initialization_callback) { maybe_prune(); auto it = find_iterator(key); if (it != m_table.end()) { if constexpr (value_is_cell) { if (auto value = it->value.ptr()) return *value; m_table.remove(it); } else { return it->value; } } if constexpr (value_is_cell) { auto value = initialization_callback(); [[maybe_unused]] auto result = m_table.set(to_key_storage(key), to_value_storage(value)); VERIFY(result == HashSetResult::InsertedNewEntry); return *value; } else { return m_table.ensure(to_key_storage(key), [&] { return to_value_storage(initialization_callback()); }); } } V& ensure(K const& key) requires(!value_is_cell) { return ensure(key, [] { return V(); }); } bool contains(K const& key) const { return find_iterator(key) != m_table.end(); } auto get(K const& key) { if constexpr (value_is_cell) { auto it = find_iterator(key); if (it == m_table.end()) return static_cast(nullptr); return static_cast(it->value.ptr()); } else { auto it = find_iterator(key); if (it == m_table.end()) return Optional {}; return Optional { it->value }; } } auto get(K const& key) const { if constexpr (value_is_cell) { auto it = find_iterator(key); if (it == m_table.end()) return static_cast(nullptr); return static_cast(it->value.ptr()); } else { auto it = find_iterator(key); if (it == m_table.end()) return Optional {}; return Optional { it->value }; } } bool is_empty() const { for (auto const& entry : m_table) { if constexpr (key_is_cell) { if (!entry.key.ptr()) continue; } if constexpr (value_is_cell) { if (!entry.value.ptr()) continue; } return false; } return true; } void clear() { m_table.clear(); } private: static KeyStorage to_key_storage(K const& key) { if constexpr (key_is_cell) return Weak(key); else return key; } template static ValueStorage to_value_storage(U&& value) { if constexpr (value_is_cell) return Weak(forward(value)); else return ValueStorage { forward(value) }; } auto find_iterator(K const& key) const -> typename TableType::ConstIteratorType { if constexpr (key_is_cell) { return m_table.find(Traits::hash(&key), [&](auto& entry) { return entry.key.ptr() == &key; }); } else { return m_table.find(key); } } auto find_iterator(K const& key) -> typename TableType::IteratorType { if constexpr (key_is_cell) { return m_table.find(Traits::hash(&key), [&](auto& entry) { return entry.key.ptr() == &key; }); } else { return m_table.find(key); } } void maybe_prune() { if (++m_mutations_since_last_prune < max(m_table.size(), static_cast(64))) return; m_table.remove_all_matching([](auto const& key, auto const& value) { if constexpr (key_is_cell) { if (!key.ptr()) return true; } if constexpr (value_is_cell) { if (!value.ptr()) return true; } return false; }); m_mutations_since_last_prune = 0; } TableType m_table; size_t m_mutations_since_last_prune { 0 }; }; }