#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_STORAGE_SOURCE 1 #include "Nuclex/Storage/MemoryBlob.h" #include // for std::copy_n() #include // for std::numeric_limits #include // for std::out_of_range #include // for assert() namespace { // ------------------------------------------------------------------------------------------- // /// Grows the vector to at least the specified capacity /// Vector that will be grown /// Minimum capacity the vector will be grown to /// /// /// The std::vector<T>::reserve() method is free to do this itself, /// but most implementations grow to the exact size, probably in the assumption that if /// the developer goes through the effort of calling reserve(), he knows exactly /// how much memory will be required (I'm not complaining). /// /// /// This behavior could wreak havoc on performance if a binary blob writer was to /// append small chunks of data (eg. 4 byte integers) to an already large blob. /// Thus we ensure the reserve() behavior matches the scheme employed by push_back() /// in most implementations of std::vector<T>. /// /// template void growVector(std::vector &vector, std::size_t requiredCapacity) { std::size_t capacity = vector.capacity(); if(capacity < requiredCapacity) { capacity += capacity / 2; if(capacity < requiredCapacity) { capacity = requiredCapacity; } vector.reserve(capacity); } } // ------------------------------------------------------------------------------------------- // /// Scoped unlocker for a shared lock on a mutex class SharedMutexLockScope { /// Initializes the mutex unlocking scope on the specified mutex public: SharedMutexLockScope(std::shared_mutex &mutex) : mutex(mutex) {} /// Releases the shared lock on the scope's mutex public: ~SharedMutexLockScope() { this->mutex.unlock_shared(); } /// Mutex that will be unlocked when the scope is destroyed private: std::shared_mutex &mutex; }; // ------------------------------------------------------------------------------------------- // /// Scoped unlocker for an exclusive lock on a mutex class ExclusiveMutexLockScope { /// Initializes the mutex unlocking scope on the specified mutex public: ExclusiveMutexLockScope(std::shared_mutex &mutex) : mutex(mutex) {} /// Releases the exclusive lock on the scope's mutex public: ~ExclusiveMutexLockScope() { this->mutex.unlock(); } /// Mutex that will be unlocked when the scope is destroyed private: std::shared_mutex &mutex; }; // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Storage { // ------------------------------------------------------------------------------------------- // void MemoryBlob::ReadAt(std::uint64_t location, void *buffer, std::size_t count) const { assert( (location < std::numeric_limits::max()) && u8"Read location must be within range of the platform's std::size_t type" ); assert( ((location + count) < std::numeric_limits::max()) && u8"Read must be end within range of the platform's std::size_t type" ); this->mutex.lock_shared(); { SharedMutexLockScope sharedMutexUnlocker(this->mutex); std::copy_n( &this->memory.at(static_cast(location)), count, static_cast(buffer) ); } } // ------------------------------------------------------------------------------------------- // void MemoryBlob::WriteAt(std::uint64_t location, const void *buffer, std::size_t count) { assert( (location < std::numeric_limits::max()) && u8"Write location must be within range of the platform's std::size_t type" ); assert( ((location + count) < std::numeric_limits::max()) && u8"Write must be end within range of the platform's std::size_t type" ); std::size_t start = static_cast(location); std::size_t end = start + count; this->mutex.lock_shared(); // Look up the size of the vector. We assume that this will not throw. std::size_t blobSize = this->memory.size(); if(unlikely(start > blobSize)) { throw std::out_of_range( u8"Attempted write past the end of the memory blob (would create undefined gap)" ); } // If the data to be written fits within the vector's size, just write // (synchronizing reads and writes is the caller's task, we're just making // sure that we don't segfault and never return undefined data) if(likely(end < blobSize)) { SharedMutexLockScope sharedMutexUnlocker(this->mutex); std::copy_n( static_cast(buffer), count, this->memory.data() + static_cast(location) ); } else { // Resize needed, shared mutex lock is not enough! this->mutex.unlock_shared(); this->mutex.lock(); { ExclusiveMutexLockScope exclusiveMutexUnlocker(this->mutex); growVector(this->memory, end); // Copy those bytes that overwrite existing data in the vector directly std::size_t overwriteCount = std::min(blobSize, end) - start; if(overwriteCount > 0) { std::copy_n( static_cast(buffer), overwriteCount, this->memory.data() + static_cast(location) ); } // Append those bytes that grow the buffer using the insert() method if(count > overwriteCount) { this->memory.insert( this->memory.end(), static_cast(buffer) + overwriteCount, static_cast(buffer) + count ); } } // exclusive mutex lock } } // ------------------------------------------------------------------------------------------- // }} // namespace Nuclex::Storage