Просмотр исходного кода

[5] refactor(memory): hold bounded build storage in vectors

Review: try to avoid direct dynamic heap allocation, prefer vectors or
arrays.
Resolution: all twenty-seven bounded banks are std::vector, and no raw
array new remains.

Every bounded build-time bank was a manual new (std::nothrow) T[] behind
a unique_ptr<T[]>, which put raw allocation and its paired null check
into code that only ever wanted a sized buffer. The surrounding module
already used std::vector for exactly this, so the two styles sat side by
side in the same files.

Converts the detail extractor's staging buffer, the package Storage
detail bank, the six SocketPlugBuild interning banks, the published
detail catalog, and the eighteen cache-snapshot scratch banks. Guards
that tested pointer null now test emptiness, and the scratch release
path drops its capacity through shrink_to_fit so the memory still
returns to the allocator at the same points as before.

This trades a nothrow failure return for a throwing allocation, which is
the behaviour the vectors already in these files have.

Thomas Shields 3 недель назад
Родитель
Сommit
5b00ba47e1

+ 3 - 8
Sunrise/src/client/content/items/details/configured_item_detail_extractor.cpp

@@ -4,8 +4,7 @@
 #include <bitset>
 #include <cstddef>
 #include <cstdint>
-#include <memory>
-#include <new>
+#include <vector>
 
 #include "../../../../state/build_data/items/item_catalog.h"
 #include "../../../../state/build_data/socket_entry_lists/definition.h"
@@ -95,11 +94,7 @@ bool extract(const investment::Source& source,
     }
 
     std::bitset<build_items::kDefinitionCapacity> seen{};
-    std::unique_ptr<build_details::Definition[]> staged{
-        new (std::nothrow) build_details::Definition[requestedDefinitionIndices.size()]};
-    if (!staged) {
-        return false;
-    }
+    std::vector<build_details::Definition> staged(requestedDefinitionIndices.size());
     for (std::size_t position = 0; position < requestedDefinitionIndices.size(); ++position) {
         const std::uint16_t definitionIndex = requestedDefinitionIndices[position];
         if (static_cast<std::size_t>(definitionIndex) >= table.rowCount
@@ -117,7 +112,7 @@ bool extract(const investment::Source& source,
     if (!stable_count(source, table)) {
         return false;
     }
-    std::copy_n(staged.get(), requestedDefinitionIndices.size(), output.begin());
+    std::copy_n(staged.data(), requestedDefinitionIndices.size(), output.begin());
     count = requestedDefinitionIndices.size();
     return true;
 }

+ 1 - 2
Sunrise/src/client/content/items/packages/internal.h

@@ -4,7 +4,6 @@
 #include <bitset>
 #include <cstddef>
 #include <cstdint>
-#include <memory>
 #include <span>
 #include <vector>
 
@@ -73,7 +72,7 @@ struct Storage {
         equipmentSlotByBucket{};
     DetailRequests detailRequests{};
     std::array<std::uint16_t, kDetailCapacity> requestedDetailIndices{};
-    std::unique_ptr<state::build_data::items::details::Definition[]> details{};
+    std::vector<state::build_data::items::details::Definition> details{};
     AuthoredHashes authoredHashes{};
     std::vector<std::byte> abilityTable{};
     std::vector<std::byte> abilityPool{};

+ 4 - 6
Sunrise/src/client/content/items/packages/package_item_rows.cpp

@@ -1,5 +1,4 @@
 #include <array>
-#include <new>
 #include <span>
 #include <vector>
 
@@ -50,10 +49,10 @@ bool build_item_rows(const reader::Source& source,
     // must still revisit the table even when definitions and detail domains already published.
     const bool needRows = needDefinitions || needDetailRows || needBuckets;
     bool published = !needRows;
-    if (needDetails && !storage.details) {
-        storage.details.reset(new (std::nothrow) build_details::Definition[kDetailCapacity]);
+    if (needDetails && storage.details.size() != kDetailCapacity) {
+        storage.details.assign(kDetailCapacity, build_details::Definition{});
     }
-    const bool detailStorageReady = !needDetails || static_cast<bool>(storage.details);
+    const bool detailStorageReady = !needDetails || storage.details.size() == kDetailCapacity;
     const std::span<const std::byte> container{storage.child};
     reason = "rows";
     // The detail closure is gathered during this one walk. Collections can name any installed
@@ -145,8 +144,7 @@ bool build_item_rows(const reader::Source& source,
         }
         if (needDetails) {
             published = state::build_data::publish_configured_item_details(
-                std::span<build_details::Definition>{storage.details.get(), kDetailCapacity}.first(
-                    builtDetailCount));
+                std::span<build_details::Definition>{storage.details}.first(builtDetailCount));
             report_detail_count(detailCount, builtDetailCount);
         }
         if (published && needSocketPlugs) {

+ 36 - 38
Sunrise/src/client/content/items/packages/package_socket_plug_build.cpp

@@ -2,7 +2,6 @@
 
 #include <algorithm>
 #include <limits>
-#include <new>
 
 #include "../../../../state/build_data/runtime.h"
 
@@ -61,19 +60,12 @@ bool SocketPlugBuild::prepare(
         || itemDefinitions.size() > state::build_data::items::kDefinitionCapacity) {
         return false;
     }
-    rules_.reset(new (std::nothrow) socket_plugs::Rule[socket_plugs::kRuleCapacity]);
-    pools_.reset(new (std::nothrow) socket_plugs::Pool[socket_plugs::kPoolCapacity]);
-    members_.reset(new (std::nothrow) socket_plugs::Member[socket_plugs::kMemberCapacity]);
-    candidates_.reset(new (std::nothrow)
-                          socket_plugs::Member[state::build_data::items::kDefinitionCapacity]);
-    categoryMembers_.reset(
-        new (std::nothrow)
-            socket_plugs::Member[kCategoryCount * state::build_data::items::kDefinitionCapacity]);
-    lookup_.reset(new (std::nothrow) PoolLookup[kLookupCapacity]);
-    if (!rules_ || !pools_ || !members_ || !candidates_ || !categoryMembers_ || !lookup_) {
-        release();
-        return false;
-    }
+    rules_.assign(socket_plugs::kRuleCapacity, {});
+    pools_.assign(socket_plugs::kPoolCapacity, {});
+    members_.assign(socket_plugs::kMemberCapacity, {});
+    candidates_.assign(state::build_data::items::kDefinitionCapacity, {});
+    categoryMembers_.assign(kCategoryCount * state::build_data::items::kDefinitionCapacity, {});
+    lookup_.assign(kLookupCapacity, {});
     pools_[socket_plugs::kEmptyPoolIndex] = {};
     poolCount_ = 1;
     for (std::size_t item = 0; item < itemDefinitions.size(); ++item) {
@@ -100,7 +92,7 @@ bool SocketPlugBuild::prepare(
 /** Appends one package member after enforcing the installed item-table bound. */
 bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex,
                           std::size_t itemDefinitionCount) noexcept {
-    if (!candidates_ || itemDefinitionIndex >= itemDefinitionCount
+    if (candidates_.empty() || itemDefinitionIndex >= itemDefinitionCount
         || itemDefinitionIndex >= state::build_data::items::kDefinitionCapacity
         || candidateCount_ >= state::build_data::items::kDefinitionCapacity) {
         return false;
@@ -112,17 +104,17 @@ bool SocketPlugBuild::add(std::uint32_t itemDefinitionIndex,
 /** Expands special category seeds, sorts/deduplicates, then interns one exact pool. */
 bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
     poolIndex = socket_plugs::kEmptyPoolIndex;
-    if (!candidates_ || !categoryMembers_ || !lookup_) {
+    if (candidates_.empty() || categoryMembers_.empty() || lookup_.empty()) {
         return false;
     }
-    std::sort(candidates_.get(), candidates_.get() + candidateCount_);
+    std::sort(candidates_.data(), candidates_.data() + candidateCount_);
     candidateCount_ = static_cast<std::size_t>(
-        std::unique(candidates_.get(), candidates_.get() + candidateCount_) - candidates_.get());
+        std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
     std::array<bool, kCategoryCount> expand{};
     // Category codes were indexed by native item definition index during prepare().
     for (std::size_t family = 0; family < kCategoryCount; ++family) {
         const auto* familyMembers =
-            categoryMembers_.get() + family * state::build_data::items::kDefinitionCapacity;
+            categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
         for (std::size_t seed = 0; seed < candidateCount_ && !expand[family]; ++seed) {
             expand[family] = std::binary_search(
                 familyMembers, familyMembers + categoryCounts_[family], candidates_[seed]);
@@ -137,13 +129,13 @@ bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
             return false;
         }
         const auto* first =
-            categoryMembers_.get() + family * state::build_data::items::kDefinitionCapacity;
-        std::copy_n(first, categoryCounts_[family], candidates_.get() + candidateCount_);
+            categoryMembers_.data() + family * state::build_data::items::kDefinitionCapacity;
+        std::copy_n(first, categoryCounts_[family], candidates_.data() + candidateCount_);
         candidateCount_ += categoryCounts_[family];
     }
-    std::sort(candidates_.get(), candidates_.get() + candidateCount_);
+    std::sort(candidates_.data(), candidates_.data() + candidateCount_);
     candidateCount_ = static_cast<std::size_t>(
-        std::unique(candidates_.get(), candidates_.get() + candidateCount_) - candidates_.get());
+        std::unique(candidates_.data(), candidates_.data() + candidateCount_) - candidates_.data());
     if (candidateCount_ == 0) {
         return true;
     }
@@ -171,7 +163,7 @@ bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
             poolIndex = static_cast<std::uint32_t>(poolCount_);
             pools_[poolCount_++] = {static_cast<std::uint32_t>(memberCount_),
                                     static_cast<std::uint32_t>(candidateCount_)};
-            std::copy_n(candidates_.get(), candidateCount_, members_.get() + memberCount_);
+            std::copy_n(candidates_.data(), candidateCount_, members_.data() + memberCount_);
             memberCount_ += candidateCount_;
             slot = {fingerprint, poolIndex};
             return true;
@@ -181,9 +173,9 @@ bool SocketPlugBuild::intern(std::uint32_t& poolIndex) noexcept {
         }
         const socket_plugs::Pool& pool = pools_[slot.poolIndex];
         if (pool.memberCount == candidateCount_
-            && std::equal(candidates_.get(),
-                          candidates_.get() + candidateCount_,
-                          members_.get() + pool.memberOffset)) {
+            && std::equal(candidates_.data(),
+                          candidates_.data() + candidateCount_,
+                          members_.data() + pool.memberOffset)) {
             poolIndex = slot.poolIndex;
             return true;
         }
@@ -196,7 +188,7 @@ bool SocketPlugBuild::append(const tables::items::Row& item,
                              std::span<const std::byte> itemDefinition,
                              std::span<const std::byte> plugSetTable,
                              std::size_t itemDefinitionCount) noexcept {
-    if (!rules_ || item.definitionIndex >= itemDefinitionCount
+    if (rules_.empty() || item.definitionIndex >= itemDefinitionCount
         || item.socketCount > socket_plugs::kLaneCapacity) {
         return false;
     }
@@ -229,10 +221,10 @@ bool SocketPlugBuild::append(const tables::items::Row& item,
 /** Publishes the bounded relation and releases all transient interning memory. */
 bool SocketPlugBuild::publish() noexcept {
     const bool published =
-        rules_ && pools_ && members_
-        && state::build_data::publish_socket_plug_rules(std::span(rules_.get(), ruleCount_),
-                                                        std::span(pools_.get(), poolCount_),
-                                                        std::span(members_.get(), memberCount_));
+        !rules_.empty() && !pools_.empty() && !members_.empty()
+        && state::build_data::publish_socket_plug_rules(std::span(rules_.data(), ruleCount_),
+                                                        std::span(pools_.data(), poolCount_),
+                                                        std::span(members_.data(), memberCount_));
     release();
     return published;
 }
@@ -256,12 +248,18 @@ std::size_t SocketPlugBuild::member_count() const noexcept {
 
 /** Drops all heap-backed extraction scratch and resets every count. */
 void SocketPlugBuild::release() noexcept {
-    rules_.reset();
-    pools_.reset();
-    members_.reset();
-    candidates_.reset();
-    categoryMembers_.reset();
-    lookup_.reset();
+    rules_.clear();
+    rules_.shrink_to_fit();
+    pools_.clear();
+    pools_.shrink_to_fit();
+    members_.clear();
+    members_.shrink_to_fit();
+    candidates_.clear();
+    candidates_.shrink_to_fit();
+    categoryMembers_.clear();
+    categoryMembers_.shrink_to_fit();
+    lookup_.clear();
+    lookup_.shrink_to_fit();
     categoryCounts_ = {};
     trackerMembers_ = {};
     trackerCount_ = 0;

+ 7 - 7
Sunrise/src/client/content/items/packages/package_socket_plug_build.h

@@ -3,8 +3,8 @@
 #include <array>
 #include <cstddef>
 #include <cstdint>
-#include <memory>
 #include <span>
+#include <vector>
 
 #include "../../../../middleware/content/packages/tables/items.h"
 #include "../../../../state/build_data/items/item_catalog.h"
@@ -56,12 +56,12 @@ private:
     static constexpr std::size_t kCategoryCount = 3;
     static constexpr std::size_t kLookupCapacity = 1U << 19U;
 
-    std::unique_ptr<socket_plugs::Rule[]> rules_{};
-    std::unique_ptr<socket_plugs::Pool[]> pools_{};
-    std::unique_ptr<socket_plugs::Member[]> members_{};
-    std::unique_ptr<socket_plugs::Member[]> candidates_{};
-    std::unique_ptr<socket_plugs::Member[]> categoryMembers_{};
-    std::unique_ptr<PoolLookup[]> lookup_{};
+    std::vector<socket_plugs::Rule> rules_{};
+    std::vector<socket_plugs::Pool> pools_{};
+    std::vector<socket_plugs::Member> members_{};
+    std::vector<socket_plugs::Member> candidates_{};
+    std::vector<socket_plugs::Member> categoryMembers_{};
+    std::vector<PoolLookup> lookup_{};
     std::array<std::size_t, kCategoryCount> categoryCounts_{};
     std::array<socket_plugs::Member, 3> trackerMembers_{};
     std::size_t trackerCount_{};

+ 7 - 11
Sunrise/src/state/build_data/items/details/item_detail_catalog.cpp

@@ -4,9 +4,8 @@
 #include <array>
 #include <bitset>
 #include <limits>
-#include <memory>
-#include <new>
 #include <span>
+#include <vector>
 
 #include "../../table.h"
 #include "../item_catalog.h"
@@ -21,7 +20,7 @@ constexpr std::size_t kNativeDefinitionIndexCapacity =
 constexpr std::uint16_t kEmptyLookupRow = (std::numeric_limits<std::uint16_t>::max)();
 
 Lock g_lock;
-std::unique_ptr<Definition[]> g_definitions;
+std::vector<Definition> g_definitions;
 std::size_t g_definitionCount{};
 // Native definition index to detail row, rebuilt with the table under the same exclusive hold.
 std::array<std::uint16_t, kNativeDefinitionIndexCapacity> g_lookup{};
@@ -85,7 +84,8 @@ static_assert(kDefinitionCapacity < kEmptyLookupRow);
 /** Clears every generated configured item detail under the catalog lock. */
 void clear() noexcept {
     const Lock::Exclusive guard(g_lock);
-    g_definitions.reset();
+    g_definitions.clear();
+    g_definitions.shrink_to_fit();
     g_definitionCount = 0;
     std::fill(g_lookup.begin(), g_lookup.end(), kEmptyLookupRow);
 }
@@ -112,11 +112,7 @@ bool replace(std::span<const Definition> definitions) noexcept {
         return false;
     }
 
-    std::unique_ptr<Definition[]> staged{new (std::nothrow) Definition[definitions.size()]};
-    if (!staged) {
-        return false;
-    }
-    std::copy(definitions.begin(), definitions.end(), staged.get());
+    std::vector<Definition> staged(definitions.begin(), definitions.end());
 
     const Lock::Exclusive guard(g_lock);
     std::fill(g_lookup.begin(), g_lookup.end(), kEmptyLookupRow);
@@ -132,7 +128,7 @@ bool replace(std::span<const Definition> definitions) noexcept {
 bool find(std::uint16_t definitionIndex, Definition& definition) noexcept {
     definition = {};
     const Lock::Shared guard(g_lock);
-    const std::span<const Definition> rows{g_definitions.get(), g_definitionCount};
+    const std::span<const Definition> rows{g_definitions.data(), g_definitionCount};
     const std::uint16_t row = g_lookup[definitionIndex];
     const bool found = row != kEmptyLookupRow && row < rows.size();
     if (found) {
@@ -149,7 +145,7 @@ bool snapshot(std::span<Definition> output, std::size_t& count) noexcept {
         return false;
     }
     if (g_definitionCount != 0) {
-        std::copy_n(g_definitions.get(), g_definitionCount, output.begin());
+        std::copy_n(g_definitions.data(), g_definitionCount, output.begin());
     }
     count = g_definitionCount;
     return true;

+ 56 - 42
Sunrise/src/state/build_data/runtime/persistence/build_data_persistence.cpp

@@ -3,8 +3,8 @@
 #include <Windows.h>
 
 #include <algorithm>
-#include <new>
 #include <span>
+#include <vector>
 
 #include "../../../../core/ui/busy/busy.h"
 #include "../../abilities/ability_bucket_catalog.h"
@@ -30,13 +30,17 @@ namespace {
 
 Context g_context;
 
-/** Lazily allocates one bounded cache-snapshot bank and exposes all rows on success. */
+/**
+ * Lazily sizes one bounded cache-snapshot bank and exposes all rows.
+ * @param storage Bank held by the caller's context, resized on first use.
+ * @return The whole bank.
+ */
 template <typename Value, std::size_t Capacity>
-[[nodiscard]] std::span<Value> ensure_scratch(std::unique_ptr<Value[]>& storage) noexcept {
-    if (!storage) {
-        storage.reset(new (std::nothrow) Value[Capacity]);
+[[nodiscard]] std::span<Value> ensure_scratch(std::vector<Value>& storage) noexcept {
+    if (storage.size() != Capacity) {
+        storage.assign(Capacity, Value{});
     }
-    return {storage.get(), storage ? Capacity : 0};
+    return storage;
 }
 
 /** @param value Published runtime constants. @return The packed header form. */
@@ -177,26 +181,36 @@ cache::records::MutableDomains scratch_domains(Context& state) noexcept {
     };
 }
 
+/**
+ * Releases one transient cache snapshot bank without walking its capacity.
+ * @param storage Bank emptied and handed back to the allocator.
+ */
+template <typename Value>
+void release_bank(std::vector<Value>& storage) noexcept {
+    storage.clear();
+    storage.shrink_to_fit();
+}
+
 /** Releases transient cache snapshot banks without allocating or walking their capacities. */
 void release_scratch_locked(Context& state) noexcept {
-    state.namedScratch.reset();
-    state.itemScratch.reset();
-    state.collectibleScratch.reset();
-    state.materialRequirementSetScratch.reset();
-    state.itemDetailScratch.reset();
-    state.socketPlugRuleScratch.reset();
-    state.socketPlugPoolScratch.reset();
-    state.socketPlugMemberScratch.reset();
-    state.inventoryBucketScratch.reset();
-    state.socketEntryListScratch.reset();
-    state.socketEntryTableScratch.reset();
-    state.abilityBucketScratch.reset();
-    state.progressionScratch.reset();
-    state.scenarioScratch.reset();
-    state.rosterGroupScratch.reset();
-    state.spawnStemScratch.reset();
-    state.spawnNameHashScratch.reset();
-    state.hashNameScratch.reset();
+    release_bank(state.namedScratch);
+    release_bank(state.itemScratch);
+    release_bank(state.collectibleScratch);
+    release_bank(state.materialRequirementSetScratch);
+    release_bank(state.itemDetailScratch);
+    release_bank(state.socketPlugRuleScratch);
+    release_bank(state.socketPlugPoolScratch);
+    release_bank(state.socketPlugMemberScratch);
+    release_bank(state.inventoryBucketScratch);
+    release_bank(state.socketEntryListScratch);
+    release_bank(state.socketEntryTableScratch);
+    release_bank(state.abilityBucketScratch);
+    release_bank(state.progressionScratch);
+    release_bank(state.scenarioScratch);
+    release_bank(state.rosterGroupScratch);
+    release_bank(state.spawnStemScratch);
+    release_bank(state.spawnNameHashScratch);
+    release_bank(state.hashNameScratch);
     state.constantsScratch = {};
 }
 
@@ -214,43 +228,43 @@ void clear_locked(Context& state) noexcept {
 /** Gives read-only views over the used rows of one canonical snapshot. */
 cache::records::Domains occupied_domains(Context& state,
                                          const cache::records::DomainCounts& counts) noexcept {
-    const std::span<const items::details::Definition> itemDetails{state.itemDetailScratch.get(),
+    const std::span<const items::details::Definition> itemDetails{state.itemDetailScratch.data(),
                                                                   counts.itemDetails};
     const std::span<const items::socket_plugs::Rule> socketPlugRules{
-        state.socketPlugRuleScratch.get(), counts.socketPlugRules};
+        state.socketPlugRuleScratch.data(), counts.socketPlugRules};
     const std::span<const items::socket_plugs::Pool> socketPlugPools{
-        state.socketPlugPoolScratch.get(), counts.socketPlugPools};
+        state.socketPlugPoolScratch.data(), counts.socketPlugPools};
     const std::span<const items::socket_plugs::Member> socketPlugMembers{
-        state.socketPlugMemberScratch.get(), counts.socketPlugMembers};
+        state.socketPlugMemberScratch.data(), counts.socketPlugMembers};
     return {
         state.constantsScratch,
-        std::span<const content::Definition>{state.namedScratch.get(), counts.named},
-        std::span<const build_data::items::Definition>{state.itemScratch.get(), counts.items},
-        std::span<const build_data::collectibles::Definition>{state.collectibleScratch.get(),
+        std::span<const content::Definition>{state.namedScratch.data(), counts.named},
+        std::span<const build_data::items::Definition>{state.itemScratch.data(), counts.items},
+        std::span<const build_data::collectibles::Definition>{state.collectibleScratch.data(),
                                                               counts.collectibles},
         std::span<const material_requirements::Definition>{
-            state.materialRequirementSetScratch.get(), counts.materialRequirementSets},
+            state.materialRequirementSetScratch.data(), counts.materialRequirementSets},
         itemDetails,
         socketPlugRules,
         socketPlugPools,
         socketPlugMembers,
-        std::span<const inventory::buckets::Descriptor>{state.inventoryBucketScratch.get(),
+        std::span<const inventory::buckets::Descriptor>{state.inventoryBucketScratch.data(),
                                                         counts.inventoryBuckets},
-        std::span<const socket_entry_lists::Definition>{state.socketEntryListScratch.get(),
+        std::span<const socket_entry_lists::Definition>{state.socketEntryListScratch.data(),
                                                         counts.socketEntryLists},
-        std::span<const socket_entry_lists::EntryTable>{state.socketEntryTableScratch.get(),
+        std::span<const socket_entry_lists::EntryTable>{state.socketEntryTableScratch.data(),
                                                         counts.socketEntryTables},
-        std::span<const abilities::Definition>{state.abilityBucketScratch.get(),
+        std::span<const abilities::Definition>{state.abilityBucketScratch.data(),
                                                counts.abilityBuckets},
-        std::span<const progressions::Definition>{state.progressionScratch.get(),
+        std::span<const progressions::Definition>{state.progressionScratch.data(),
                                                   counts.progressions},
-        std::span<const scenarios::Definition>{state.scenarioScratch.get(), counts.scenarios},
-        std::span<const scenarios::RosterGroup>{state.rosterGroupScratch.get(),
+        std::span<const scenarios::Definition>{state.scenarioScratch.data(), counts.scenarios},
+        std::span<const scenarios::RosterGroup>{state.rosterGroupScratch.data(),
                                                 counts.rosterGroups},
-        std::span<const spawn_sets::Stem>{state.spawnStemScratch.get(), counts.spawnStems},
-        std::span<const spawn_sets::NameHash>{state.spawnNameHashScratch.get(),
+        std::span<const spawn_sets::Stem>{state.spawnStemScratch.data(), counts.spawnStems},
+        std::span<const spawn_sets::NameHash>{state.spawnNameHashScratch.data(),
                                               counts.spawnNameHashes},
-        std::span<const hash_names::Name>{state.hashNameScratch.get(), counts.hashNames},
+        std::span<const hash_names::Name>{state.hashNameScratch.data(), counts.hashNames},
     };
 }
 

+ 19 - 19
Sunrise/src/state/build_data/runtime/persistence/build_data_persistence.h

@@ -3,7 +3,7 @@
 #include <Windows.h>
 
 #include <array>
-#include <memory>
+#include <vector>
 
 #include "../../../../core/filesystem/path.h"
 #include "../../../content/content_catalog.h"
@@ -28,24 +28,24 @@ namespace sunrise::state::build_data::runtime::persistence {
 /** Fixed cache paths, identity, and canonical snapshot storage guarded by one State lock. */
 struct Context {
     SRWLOCK lock{SRWLOCK_INIT};
-    std::unique_ptr<content::Definition[]> namedScratch{};
-    std::unique_ptr<items::Definition[]> itemScratch{};
-    std::unique_ptr<collectibles::Definition[]> collectibleScratch{};
-    std::unique_ptr<material_requirements::Definition[]> materialRequirementSetScratch{};
-    std::unique_ptr<items::details::Definition[]> itemDetailScratch{};
-    std::unique_ptr<items::socket_plugs::Rule[]> socketPlugRuleScratch{};
-    std::unique_ptr<items::socket_plugs::Pool[]> socketPlugPoolScratch{};
-    std::unique_ptr<items::socket_plugs::Member[]> socketPlugMemberScratch{};
-    std::unique_ptr<inventory::buckets::Descriptor[]> inventoryBucketScratch{};
-    std::unique_ptr<socket_entry_lists::Definition[]> socketEntryListScratch{};
-    std::unique_ptr<socket_entry_lists::EntryTable[]> socketEntryTableScratch{};
-    std::unique_ptr<abilities::Definition[]> abilityBucketScratch{};
-    std::unique_ptr<progressions::Definition[]> progressionScratch{};
-    std::unique_ptr<scenarios::Definition[]> scenarioScratch{};
-    std::unique_ptr<scenarios::RosterGroup[]> rosterGroupScratch{};
-    std::unique_ptr<spawn_sets::Stem[]> spawnStemScratch{};
-    std::unique_ptr<spawn_sets::NameHash[]> spawnNameHashScratch{};
-    std::unique_ptr<hash_names::Name[]> hashNameScratch{};
+    std::vector<content::Definition> namedScratch{};
+    std::vector<items::Definition> itemScratch{};
+    std::vector<collectibles::Definition> collectibleScratch{};
+    std::vector<material_requirements::Definition> materialRequirementSetScratch{};
+    std::vector<items::details::Definition> itemDetailScratch{};
+    std::vector<items::socket_plugs::Rule> socketPlugRuleScratch{};
+    std::vector<items::socket_plugs::Pool> socketPlugPoolScratch{};
+    std::vector<items::socket_plugs::Member> socketPlugMemberScratch{};
+    std::vector<inventory::buckets::Descriptor> inventoryBucketScratch{};
+    std::vector<socket_entry_lists::Definition> socketEntryListScratch{};
+    std::vector<socket_entry_lists::EntryTable> socketEntryTableScratch{};
+    std::vector<abilities::Definition> abilityBucketScratch{};
+    std::vector<progressions::Definition> progressionScratch{};
+    std::vector<scenarios::Definition> scenarioScratch{};
+    std::vector<scenarios::RosterGroup> rosterGroupScratch{};
+    std::vector<spawn_sets::Stem> spawnStemScratch{};
+    std::vector<spawn_sets::NameHash> spawnNameHashScratch{};
+    std::vector<hash_names::Name> hashNameScratch{};
     cache::records::InvestmentConstants constantsScratch{};
     core::path::Buffer cacheDirectory;
     core::path::Buffer cachePath;