diff --git a/Source/Core/Common/Buffer.h b/Source/Core/Common/Buffer.h new file mode 100644 index 0000000000..216d0c5fe5 --- /dev/null +++ b/Source/Core/Common/Buffer.h @@ -0,0 +1,88 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// UniqueBuffer and SharedBuffer are a lighter alternative to std::vector. +// The main benefit is that elements are not value-initialized like in vector. +// That can be quite a bit of unecessary overhead when allocating a large buffer. + +namespace Common +{ + +namespace detail +{ +template +class BufferBase final +{ +public: + using PtrType = decltype(MakeFunc(1)); + + using value_type = PtrType::element_type; + using size_type = std::size_t; + + BufferBase() {} + BufferBase(PtrType ptr, size_type new_size) : m_ptr{std::move(ptr)}, m_size{new_size} {} + explicit BufferBase(size_type new_size) : BufferBase{MakeFunc(new_size), new_size} {} + + BufferBase(const BufferBase&) = default; + BufferBase& operator=(const BufferBase&) = default; + + BufferBase(BufferBase&& other) { swap(other); } + BufferBase& operator=(BufferBase&& other) + { + reset(); + swap(other); + return *this; + } + + void assign(PtrType ptr, size_type new_size) { BufferBase{std::move(ptr), new_size}.swap(*this); } + void reset(size_type new_size = 0) { BufferBase{new_size}.swap(*this); } + + std::pair extract() + { + std::pair result = {std::move(m_ptr), m_size}; + reset(); + return result; + } + + void swap(BufferBase& other) + { + std::swap(m_ptr, other.m_ptr); + std::swap(m_size, other.m_size); + } + + size_type size() const { return m_size; } + bool empty() const { return m_size == 0; } + + value_type* get() { return m_ptr.get(); } + const value_type* get() const { return m_ptr.get(); } + + value_type* data() { return m_ptr.get(); } + const value_type* data() const { return m_ptr.get(); } + + value_type* begin() { return m_ptr.get(); } + value_type* end() { return m_ptr.get() + m_size; } + + const value_type* begin() const { return m_ptr.get(); } + const value_type* end() const { return m_ptr.get() + m_size; } + + value_type& operator[](size_type index) { return m_ptr[index]; } + const value_type& operator[](size_type index) const { return m_ptr[index]; } + +private: + PtrType m_ptr; + size_type m_size = 0; +}; +} // namespace detail + +template +using UniqueBuffer = detail::BufferBase>; + +// TODO: std::make_shared_for_overwrite requires GCC 12.1+ +// template +// using SharedBuffer = detail::BufferBase>; + +} // namespace Common diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index e72617ed87..608e2f270a 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(common BitSet.h BitUtils.h BlockingLoop.h + Buffer.h ChunkFile.h CodeBlock.h ColorUtil.cpp diff --git a/Source/Core/Common/LinearDiskCache.h b/Source/Core/Common/LinearDiskCache.h index f0a4815143..061f7d4b19 100644 --- a/Source/Core/Common/LinearDiskCache.h +++ b/Source/Core/Common/LinearDiskCache.h @@ -85,8 +85,7 @@ public: if (next_extent > file_size) break; - // TODO: use make_unique_for_overwrite in C++20 - value = std::unique_ptr(new V[value_size]); + value = std::make_unique_for_overwrite(value_size); // read key/value and pass to reader if (m_file.ReadArray(&key, 1) && m_file.ReadArray(value.get(), value_size) && diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 9620cfccd4..052a3ed1ff 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -4,7 +4,6 @@ #include "Core/State.h" #include -#include #include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include @@ -31,12 +29,10 @@ #include "Common/MsgHandler.h" #include "Common/Thread.h" #include "Common/TimeUtil.h" -#include "Common/Timer.h" #include "Common/Version.h" #include "Common/WorkQueueThread.h" #include "Core/AchievementManager.h" -#include "Core/Config/AchievementSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" @@ -46,7 +42,7 @@ #include "Core/HW/Wiimote.h" #include "Core/Host.h" #include "Core/Movie.h" -#include "Core/NetPlayClient.h" +#include "Core/NetPlayProto.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" @@ -71,14 +67,14 @@ static unsigned char __LZO_MMODEL out[OUT_LEN]; static AfterLoadCallbackFunc s_on_after_load_callback; // Temporary undo state buffer -static std::vector s_undo_load_buffer; +static Common::UniqueBuffer s_undo_load_buffer; static std::mutex s_undo_load_buffer_mutex; static std::mutex s_load_or_save_in_progress_mutex; struct CompressAndDumpState_args { - std::vector buffer_vector; + Common::UniqueBuffer buffer; std::string filename; std::shared_ptr state_write_done_event; }; @@ -205,7 +201,7 @@ static void DoState(Core::System& system, PointerWrap& p) #endif // USE_RETRO_ACHIEVEMENTS } -void LoadFromBuffer(Core::System& system, std::vector& buffer) +void LoadFromBuffer(Core::System& system, Common::UniqueBuffer& buffer) { if (NetPlay::IsNetPlayRunning()) { @@ -229,20 +225,21 @@ void LoadFromBuffer(Core::System& system, std::vector& buffer) true); } -void SaveToBuffer(Core::System& system, std::vector& buffer) +void SaveToBuffer(Core::System& system, Common::UniqueBuffer& buffer) { Core::RunOnCPUThread( system, [&] { u8* ptr = nullptr; PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure); - DoState(system, p_measure); - const size_t buffer_size = reinterpret_cast(ptr); - buffer.resize(buffer_size); + + const size_t new_buffer_size = ptr - (u8*)(nullptr); + if (new_buffer_size > buffer.size()) + buffer.reset(new_buffer_size); ptr = buffer.data(); - PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write); + PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Write); DoState(system, p); }, true); @@ -318,15 +315,14 @@ static void CompressBufferToFile(const u8* raw_buffer, u64 size, File::IOFile& f while (true) { - u64 bytes_left_to_compress = size - total_bytes_compressed; + const u64 bytes_left_to_compress = size - total_bytes_compressed; - int bytes_to_compress = + const int bytes_to_compress = static_cast(std::min(static_cast(LZ4_MAX_INPUT_SIZE), bytes_left_to_compress)); - int compressed_buffer_size = LZ4_compressBound(bytes_to_compress); - auto compressed_buffer = std::make_unique(compressed_buffer_size); - s32 compressed_len = - LZ4_compress_default(reinterpret_cast(raw_buffer) + total_bytes_compressed, - compressed_buffer.get(), bytes_to_compress, compressed_buffer_size); + Common::UniqueBuffer compressed_buffer(LZ4_compressBound(bytes_to_compress)); + const int compressed_len = LZ4_compress_default( + reinterpret_cast(raw_buffer) + total_bytes_compressed, compressed_buffer.get(), + bytes_to_compress, int(compressed_buffer.size())); if (compressed_len == 0) { @@ -380,8 +376,8 @@ static void WriteHeadersToFile(size_t uncompressed_size, File::IOFile& f) static void CompressAndDumpState(Core::System& system, CompressAndDumpState_args& save_args) { - const u8* const buffer_data = save_args.buffer_vector.data(); - const size_t buffer_size = save_args.buffer_vector.size(); + const u8* const buffer_data = save_args.buffer.data(); + const size_t buffer_size = save_args.buffer.size(); const std::string& filename = save_args.filename; // Find free temporary filename. @@ -482,11 +478,10 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait) u8* ptr = nullptr; PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure); DoState(system, p_measure); - const size_t buffer_size = reinterpret_cast(ptr); + const size_t buffer_size = ptr - (u8*)(nullptr); // Then actually do the write. - std::vector current_buffer; - current_buffer.resize(buffer_size); + Common::UniqueBuffer current_buffer(buffer_size); ptr = current_buffer.data(); PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write); DoState(system, p); @@ -498,7 +493,7 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait) std::shared_ptr sync_event; CompressAndDumpState_args save_args; - save_args.buffer_vector = std::move(current_buffer); + save_args.buffer = std::move(current_buffer); save_args.filename = filename; if (wait) { @@ -531,8 +526,7 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f) // Just read the first block, since it will contain the full revision string lzo_uint32 cur_len = 0; // size of compressed bytes lzo_uint new_len = 0; // size of uncompressed bytes - std::vector buffer; - buffer.resize(header.legacy_header.lzo_size); + Common::UniqueBuffer buffer(header.legacy_header.lzo_size); if (!f.ReadArray(&cur_len, 1) || !f.ReadBytes(out, cur_len)) return false; @@ -563,11 +557,9 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f) // Read in the string if (buffer.size() >= sizeof(StateHeaderVersion) + header.version_header.version_string_length) { - auto version_buffer = std::make_unique(header.version_header.version_string_length); - memcpy(version_buffer.get(), buffer.data() + sizeof(StateHeaderVersion), - header.version_header.version_string_length); - header.version_string = - std::string(version_buffer.get(), header.version_header.version_string_length); + header.version_string.assign( + reinterpret_cast(buffer.data() + sizeof(StateHeaderVersion)), + header.version_header.version_string_length); } else { @@ -613,15 +605,14 @@ static bool ReadStateHeaderFromFile(StateHeader& header, File::IOFile& f, return false; } - auto version_buffer = std::make_unique(header.version_header.version_string_length); - if (!f.ReadBytes(version_buffer.get(), header.version_header.version_string_length)) + std::string version_buffer(header.version_header.version_string_length, '\0'); + if (!f.ReadBytes(version_buffer.data(), version_buffer.size())) { Core::DisplayMessage("Failed to read state version string", 2000); return false; } - header.version_string = - std::string(version_buffer.get(), header.version_header.version_string_length); + header.version_string = std::move(version_buffer); } return true; @@ -661,9 +652,9 @@ u64 GetUnixTimeOfSlot(int slot) (DOUBLE_TIME_OFFSET * MS_PER_SEC); } -static bool DecompressLZ4(std::vector& raw_buffer, u64 size, File::IOFile& f) +static bool DecompressLZ4(Common::UniqueBuffer& raw_buffer, u64 size, File::IOFile& f) { - raw_buffer.resize(size); + raw_buffer.reset(size); u64 total_bytes_read = 0; while (true) @@ -681,7 +672,7 @@ static bool DecompressLZ4(std::vector& raw_buffer, u64 size, File::IOFile& f return false; } - auto compressed_data = std::make_unique(compressed_data_len); + Common::UniqueBuffer compressed_data(compressed_data_len); if (!f.ReadBytes(compressed_data.get(), compressed_data_len)) { PanicAlertFmt("Could not read state data"); @@ -764,7 +755,7 @@ static bool ValidateHeaders(const StateHeader& header) return success; } -static void LoadFileStateData(const std::string& filename, std::vector& ret_data) +static void LoadFileStateData(const std::string& filename, Common::UniqueBuffer& ret_data) { File::IOFile f; @@ -802,7 +793,7 @@ static void LoadFileStateData(const std::string& filename, std::vector& ret_ return; } - std::vector buffer; + Common::UniqueBuffer buffer; switch (extended_header.base_header.compression_type) { @@ -828,7 +819,7 @@ static void LoadFileStateData(const std::string& filename, std::vector& ret_ } const auto size = static_cast(file_size - header_len); - buffer.resize(size); + buffer.reset(size); if (!f.ReadBytes(buffer.data(), size)) { @@ -888,7 +879,7 @@ void LoadAs(Core::System& system, const std::string& filename) // brackets here are so buffer gets freed ASAP { - std::vector buffer; + Common::UniqueBuffer buffer; LoadFileStateData(filename, buffer); if (!buffer.empty()) @@ -954,13 +945,8 @@ void Shutdown() { s_save_thread.Shutdown(); - // swapping with an empty vector, rather than clear()ing - // this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually, - // never) - { - std::lock_guard lk(s_undo_load_buffer_mutex); - std::vector().swap(s_undo_load_buffer); - } + std::lock_guard lk(s_undo_load_buffer_mutex); + s_undo_load_buffer.reset(); } static std::string MakeStateFilename(int number) diff --git a/Source/Core/Core/State.h b/Source/Core/Core/State.h index 626a0ecca4..1e4ba0029b 100644 --- a/Source/Core/Core/State.h +++ b/Source/Core/Core/State.h @@ -9,8 +9,8 @@ #include #include #include -#include +#include "Common/Buffer.h" #include "Common/CommonTypes.h" namespace Core @@ -106,8 +106,8 @@ void Load(Core::System& system, int slot); void SaveAs(Core::System& system, const std::string& filename, bool wait = false); void LoadAs(Core::System& system, const std::string& filename); -void SaveToBuffer(Core::System& system, std::vector& buffer); -void LoadFromBuffer(Core::System& system, std::vector& buffer); +void SaveToBuffer(Core::System& system, Common::UniqueBuffer& buffer); +void LoadFromBuffer(Core::System& system, const Common::UniqueBuffer& buffer); void LoadLastSaved(Core::System& system, int i = 1); void SaveFirstSaved(Core::System& system); diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index f5b5d4f673..67e3a15f4c 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -26,6 +26,7 @@ +