1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-20 21:43:06 +01:00

Cleanup MemoryStream code (#22593)

* Deduplicate code, use std::bit_ceil to compute new capacity

* Remove unused constructor overload

* Remove more unused functions

* Fix memory leak using assignment operator with move

* Make access explicit via constness, do not allow arbitrary access

* Move the template specialized Write/Read to private section

* Simplify a lot of code by using the right types

* Fix copy constructor

* Directly copy the member in copy constructor

* Fix little mistake

* Pluck a memory leak on Android, fix the build

* Update changelog.txt
This commit is contained in:
Matt
2024-08-22 20:24:00 +03:00
committed by GitHub
parent 9105fe0804
commit 590ab65b2a
5 changed files with 112 additions and 116 deletions

View File

@@ -21,6 +21,7 @@
- Fix: [#22457] Potential crash opening the scenario select window.
- Fix: [#22520] Virtual floor no longer appears when holding modifier keys during track construction.
- Fix: [#22582] Lighting effects are not enabled/disabled correctly, making the game appear frozen.
- Fix: [#22593] Memory leak on Android builds when reading zip files.
0.4.13 (2024-08-04)
------------------------------------------------------------------------

View File

@@ -719,8 +719,8 @@ namespace OpenRCT2
{
if (String::IEquals(Path::GetExtension(path), ".sea"))
{
auto data = DecryptSea(fs::u8path(path));
auto ms = MemoryStream(data.data(), data.size(), MEMORY_ACCESS::READ);
const auto data = DecryptSea(fs::u8path(path));
auto ms = MemoryStream(data.data(), data.size());
if (!LoadParkFromStream(&ms, path, loadTitleScreenOnFail, asScenario))
{
throw std::runtime_error(".sea file may have been renamed.");
@@ -1060,14 +1060,14 @@ namespace OpenRCT2
{
#ifndef DISABLE_HTTP
// Download park and open it using its temporary filename
auto data = DownloadPark(gOpenRCT2StartupActionPath);
const auto data = DownloadPark(gOpenRCT2StartupActionPath);
if (data.empty())
{
nextScene = GetTitleScene();
break;
}
auto ms = MemoryStream(data.data(), data.size(), MEMORY_ACCESS::READ);
auto ms = MemoryStream(data.data(), data.size());
if (!LoadParkFromStream(&ms, gOpenRCT2StartupActionPath, true))
{
Console::Error::WriteLine("Failed to load '%s'", gOpenRCT2StartupActionPath);

View File

@@ -11,6 +11,7 @@
#include "Memory.hpp"
#include <bit>
#include <cstring>
namespace OpenRCT2
@@ -20,44 +21,53 @@ namespace OpenRCT2
_access = copy._access;
_dataCapacity = copy._dataCapacity;
_dataSize = copy._dataSize;
_position = copy._position;
if (_access & MEMORY_ACCESS::OWNER)
{
_data = Memory::Allocate<void>(_dataCapacity);
// Create a copy when the memory stream owns the memory.
_data = Memory::Allocate<std::byte>(_dataCapacity);
std::memcpy(_data, copy._data, _dataCapacity);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_data) + copy.GetPosition());
}
else
{
// External pointer.
_data = copy._data;
}
}
MemoryStream::MemoryStream(size_t capacity)
{
_dataCapacity = capacity;
_data = Memory::Allocate<void>(capacity);
_position = _data;
_data = Memory::Allocate<std::byte>(capacity);
_position = 0;
}
MemoryStream::MemoryStream(void* data, size_t dataSize, uint8_t access)
MemoryStream::MemoryStream(void* data, size_t dataSize)
: _access{ MEMORY_ACCESS::READ | MEMORY_ACCESS::WRITE }
, _dataCapacity{ dataSize }
, _dataSize{ dataSize }
, _data{ static_cast<std::byte*>(data) }
, _position{ 0 }
{
_access = access;
_dataCapacity = dataSize;
_dataSize = dataSize;
_data = data;
_position = _data;
}
MemoryStream::MemoryStream(const void* data, size_t dataSize)
: MemoryStream(const_cast<void*>(data), dataSize, MEMORY_ACCESS::READ)
: _access{ MEMORY_ACCESS::READ }
, _dataCapacity{ dataSize }
, _dataSize{ dataSize }
, _data{ static_cast<std::byte*>(const_cast<void*>(data)) }
, _position{ 0 }
{
}
MemoryStream::MemoryStream(std::vector<uint8_t>&& v)
MemoryStream::MemoryStream(const void* data, size_t dataSize, bool)
: _access{ MEMORY_ACCESS::READ | MEMORY_ACCESS::OWNER }
, _dataCapacity{ dataSize }
, _dataSize{ dataSize }
, _data{ static_cast<std::byte*>(const_cast<void*>(data)) }
, _position{ 0 }
{
_access = MEMORY_ACCESS::OWNER;
_dataCapacity = v.size();
_dataSize = v.size();
_data = Memory::Allocate<void>(v.size());
_position = _data;
std::memcpy(_data, v.data(), v.size());
}
MemoryStream::MemoryStream(MemoryStream&& mv) noexcept
@@ -68,7 +78,7 @@ namespace OpenRCT2
, _position(mv._position)
{
mv._data = nullptr;
mv._position = nullptr;
mv._position = 0;
mv._dataCapacity = 0;
mv._dataSize = 0;
}
@@ -88,6 +98,11 @@ namespace OpenRCT2
{
if (this != &mv)
{
if (_access & MEMORY_ACCESS::OWNER)
{
Memory::Free(_data);
}
_access = mv._access;
_dataCapacity = mv._dataCapacity;
_data = mv._data;
@@ -95,7 +110,7 @@ namespace OpenRCT2
_position = mv._position;
mv._data = nullptr;
mv._position = nullptr;
mv._position = 0;
mv._dataCapacity = 0;
mv._dataSize = 0;
}
@@ -105,20 +120,7 @@ namespace OpenRCT2
const void* MemoryStream::GetData() const
{
return _data;
}
void* MemoryStream::GetDataCopy() const
{
auto result = Memory::Allocate<void>(_dataSize);
std::memcpy(result, _data, _dataSize);
return result;
}
void* MemoryStream::TakeData()
{
_access &= ~MEMORY_ACCESS::OWNER;
return _data;
return static_cast<const void*>(_data);
}
bool MemoryStream::CanRead() const
@@ -138,7 +140,7 @@ namespace OpenRCT2
uint64_t MemoryStream::GetPosition() const
{
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(_position) - reinterpret_cast<uintptr_t>(_data));
return _position;
}
void MemoryStream::SetPosition(uint64_t position)
@@ -167,19 +169,18 @@ namespace OpenRCT2
{
throw IOException("New position out of bounds.");
}
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_data) + static_cast<uintptr_t>(newPosition));
_position = newPosition;
}
void MemoryStream::Read(void* buffer, uint64_t length)
{
uint64_t position = GetPosition();
if (position + length > _dataSize)
if (_position + length > _dataSize)
{
throw IOException("Attempted to read past end of stream.");
}
std::memcpy(buffer, _position, length);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_position) + length);
std::memcpy(buffer, _data + _position, length);
_position += length;
}
void MemoryStream::Read1(void* buffer)
@@ -217,23 +218,12 @@ namespace OpenRCT2
void MemoryStream::Write(const void* buffer, uint64_t length)
{
uint64_t position = GetPosition();
uint64_t nextPosition = position + length;
if (nextPosition > _dataCapacity)
{
if (_access & MEMORY_ACCESS::OWNER)
{
EnsureCapacity(static_cast<size_t>(nextPosition));
}
else
{
throw IOException("Attempted to write past end of stream.");
}
}
const auto nextPosition = _position + length;
EnsureCapacity(nextPosition);
std::memcpy(_position, buffer, length);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_position) + length);
_dataSize = std::max<size_t>(_dataSize, static_cast<size_t>(nextPosition));
std::memcpy(_data + _position, buffer, length);
_position += length;
_dataSize = std::max<size_t>(_dataSize, nextPosition);
}
void MemoryStream::Write1(const void* buffer)
@@ -264,24 +254,25 @@ namespace OpenRCT2
void MemoryStream::Clear()
{
_dataSize = 0;
SetPosition(0);
_position = 0;
}
void MemoryStream::EnsureCapacity(size_t capacity)
{
if (_dataCapacity < capacity)
if (capacity <= _dataCapacity)
{
size_t newCapacity = std::max<size_t>(8, _dataCapacity);
while (newCapacity < capacity)
{
newCapacity *= 2;
}
uint64_t position = GetPosition();
_dataCapacity = newCapacity;
_data = Memory::Reallocate(_data, _dataCapacity);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_data) + static_cast<uintptr_t>(position));
return;
}
if ((_access & MEMORY_ACCESS::OWNER) == 0)
{
throw IOException("Can not grow external memory");
}
size_t newCapacity = std::max<size_t>(32, std::bit_ceil(capacity));
_dataCapacity = newCapacity;
_data = Memory::Reallocate(_data, _dataCapacity);
}
} // namespace OpenRCT2

View File

@@ -11,8 +11,6 @@
#include "IStream.hpp"
#include <vector>
namespace OpenRCT2
{
namespace MEMORY_ACCESS
@@ -31,24 +29,24 @@ namespace OpenRCT2
uint8_t _access = MEMORY_ACCESS::READ | MEMORY_ACCESS::WRITE | MEMORY_ACCESS::OWNER;
size_t _dataCapacity = 0;
size_t _dataSize = 0;
void* _data = nullptr;
void* _position = nullptr;
std::byte* _data = nullptr;
size_t _position = 0;
public:
MemoryStream() = default;
MemoryStream(const MemoryStream& copy);
MemoryStream(MemoryStream&& mv) noexcept;
explicit MemoryStream(size_t capacity);
MemoryStream(void* data, size_t dataSize, uint8_t access = MEMORY_ACCESS::READ);
MemoryStream(void* data, size_t dataSize);
MemoryStream(const void* data, size_t dataSize);
MemoryStream(std::vector<uint8_t>&& v);
// Constructor that takes ownership, the memory has to be allocated by Memory::Allocate, this function only exists for
// Android interop reasons.
MemoryStream(const void* data, size_t dataSize, bool);
virtual ~MemoryStream();
MemoryStream& operator=(MemoryStream&& mv) noexcept;
const void* GetData() const override;
void* GetDataCopy() const;
void* TakeData();
///////////////////////////////////////////////////////////////////////////
// ISteam methods
@@ -68,18 +66,6 @@ namespace OpenRCT2
void Read8(void* buffer) override;
void Read16(void* buffer) override;
template<size_t N> void Read(void* buffer)
{
uint64_t position = GetPosition();
if (position + N > _dataSize)
{
throw IOException("Attempted to read past end of stream.");
}
std::memcpy(buffer, _position, N);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_position) + N);
}
void Write(const void* buffer, uint64_t length) override;
void Write1(const void* buffer) override;
void Write2(const void* buffer) override;
@@ -87,33 +73,33 @@ namespace OpenRCT2
void Write8(const void* buffer) override;
void Write16(const void* buffer) override;
template<size_t N> void Write(const void* buffer)
{
uint64_t position = GetPosition();
uint64_t nextPosition = position + N;
if (nextPosition > _dataCapacity)
{
if (_access & MEMORY_ACCESS::OWNER)
{
EnsureCapacity(static_cast<size_t>(nextPosition));
}
else
{
throw IOException("Attempted to write past end of stream.");
}
}
std::memcpy(_position, buffer, N);
_position = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(_position) + N);
_dataSize = std::max<size_t>(_dataSize, static_cast<size_t>(nextPosition));
}
uint64_t TryRead(void* buffer, uint64_t length) override;
void Clear();
private:
void EnsureCapacity(size_t capacity);
template<size_t N> void Read(void* buffer)
{
if (_position + N > _dataSize)
{
throw IOException("Attempted to read past end of stream.");
}
std::memcpy(buffer, _data + _position, N);
_position += N;
}
template<size_t N> void Write(const void* buffer)
{
const auto nextPosition = _position + N;
EnsureCapacity(nextPosition);
std::memcpy(_data + _position, buffer, N);
_position += N;
_dataSize = std::max(_dataSize, nextPosition);
}
};
} // namespace OpenRCT2

View File

@@ -111,13 +111,31 @@ public:
auto dataPtr = reinterpret_cast<uint8_t*>(ptr);
auto dataSize = this->GetFileSize(index);
return std::vector<uint8_t>(dataPtr, dataPtr + dataSize);
auto res = std::vector<uint8_t>(dataPtr, dataPtr + dataSize);
Memory::Free(dataPtr);
return res;
}
std::unique_ptr<IStream> GetFileStream(std::string_view path) const override
{
auto data = GetFileData(path);
return std::make_unique<MemoryStream>(std::move(data));
// retrieve the JNI environment.
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jclass zipClass = env->GetObjectClass(_zip);
jstring javaPath = env->NewStringUTF(path.data());
jmethodID indexMethod = env->GetMethodID(zipClass, "getFileIndex", "(Ljava/lang/String;)I");
jint index = env->CallIntMethod(_zip, indexMethod, javaPath);
jmethodID fileMethod = env->GetMethodID(zipClass, "getFile", "(I)J");
jlong ptr = env->CallLongMethod(_zip, fileMethod, index);
auto dataPtr = reinterpret_cast<uint8_t*>(ptr);
auto dataSize = this->GetFileSize(index);
// The Java code for getFile uses Java_io_openrct2_ZipArchive_allocBytes which
// allocates memory using Memory::Allocate so its safe for MemoryStream to take full ownershp.
return std::make_unique<MemoryStream>(dataPtr, dataSize, true);
}
void SetFileData(std::string_view path, std::vector<uint8_t>&& data) override