mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
I/O paths: plain buffers instead of STL containers and new
The line reader's window, the disk cache's append and alignment buffers, and the DVD thread's result buffers are malloc'd and grow to the largest request, then stay. Pending lent DVD reads are a fixed array, and results are plain structs taken from and given back to the free stack. A memory card's dirty blocks are a bitmap walked a word at a time. A GCI flush job is one malloc - the job, its blocks, its name - and the flush walks the drained chain itself. A page programmed over EXI is written a run at a time, not a byte at a time.
This commit is contained in:
1 parent
7ac17da8f9
commit
49eb4ab516
10 files changed
+241
-123
No files matched your search
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -588,9 +589,19 @@ LineReader::LineReader(const std::string& filename) : m_file(filename, "rb")
|
||||
return;
|
||||
m_map = reinterpret_cast<const char*>(m_file.GetMappedPtr(&size));
|
||||
if (m_map)
|
||||
{
|
||||
m_size = size_t(size);
|
||||
else
|
||||
m_window.resize(64 * 1024);
|
||||
return;
|
||||
}
|
||||
m_window_size = 64 * 1024;
|
||||
m_window = static_cast<char*>(std::malloc(m_window_size));
|
||||
if (!m_window)
|
||||
m_file.Close();
|
||||
}
|
||||
|
||||
LineReader::~LineReader()
|
||||
{
|
||||
std::free(m_window);
|
||||
}
|
||||
|
||||
bool LineReader::Next(const char** text, size_t* length)
|
||||
@@ -604,21 +615,30 @@ bool LineReader::Next(const char** text, size_t* length)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
base = m_map ? m_map : m_window.data();
|
||||
base = m_map ? m_map : m_window;
|
||||
nl = static_cast<const char*>(std::memchr(base + m_pos, '\n', m_size - m_pos));
|
||||
if (nl || m_map || m_eof)
|
||||
break;
|
||||
|
||||
// keep the partial line, then fill behind it; the window grows only
|
||||
// for a line longer than it
|
||||
std::memmove(m_window.data(), m_window.data() + m_pos, m_size - m_pos);
|
||||
std::memmove(m_window, m_window + m_pos, m_size - m_pos);
|
||||
m_size -= m_pos;
|
||||
m_pos = 0;
|
||||
if (m_size == m_window.size())
|
||||
m_window.resize(m_window.size() * 2);
|
||||
if (m_size == m_window_size)
|
||||
{
|
||||
char* const grown = static_cast<char*>(std::realloc(m_window, m_window_size * 2));
|
||||
if (!grown)
|
||||
{
|
||||
m_eof = true;
|
||||
continue;
|
||||
}
|
||||
m_window = grown;
|
||||
m_window_size *= 2;
|
||||
}
|
||||
{
|
||||
size_t got = 0;
|
||||
m_file.ReadArray(m_window.data() + m_size, m_window.size() - m_size, &got);
|
||||
m_file.ReadArray(m_window + m_size, m_window_size - m_size, &got);
|
||||
m_file.ClearError();
|
||||
if (!got)
|
||||
m_eof = true;
|
||||
|
||||
@@ -217,15 +217,19 @@ class LineReader final
|
||||
{
|
||||
public:
|
||||
explicit LineReader(const std::string& filename);
|
||||
~LineReader();
|
||||
LineReader(const LineReader&) = delete;
|
||||
LineReader& operator=(const LineReader&) = delete;
|
||||
bool IsOpen() const { return m_file.IsOpen(); }
|
||||
bool Next(const char** text, size_t* length);
|
||||
|
||||
private:
|
||||
IOFile m_file;
|
||||
std::vector<char> m_window;
|
||||
const char* m_map = nullptr;
|
||||
size_t m_size = 0; // mapped length, or octets held in the window
|
||||
size_t m_pos = 0; // the next line's start
|
||||
char* m_window = nullptr; // malloc'd, when the file is not mapped
|
||||
size_t m_window_size = 0;
|
||||
size_t m_size = 0; // mapped length, or octets held in the window
|
||||
size_t m_pos = 0; // the next line's start
|
||||
bool m_eof = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
@@ -75,15 +74,16 @@ public:
|
||||
File::IOFile in(filename, "rb");
|
||||
u64 size = 0;
|
||||
const u8* map = in.GetMappedPtr(&size);
|
||||
std::unique_ptr<u8[]> owned;
|
||||
if (in && !map && (size = in.GetSize()) != 0)
|
||||
u8* owned = nullptr;
|
||||
if (in && !map && (size = in.GetSize()) != 0 &&
|
||||
(owned = static_cast<u8*>(std::malloc(size_t(size)))) != nullptr &&
|
||||
in.ReadBytes(owned, size_t(size)))
|
||||
{
|
||||
owned = std::make_unique_for_overwrite<u8[]>(size);
|
||||
if (in.ReadBytes(owned.get(), size))
|
||||
map = owned.get();
|
||||
map = owned;
|
||||
}
|
||||
if (map && size >= sizeof(Header) && !std::memcmp(&m_header, map, sizeof(Header)))
|
||||
valid_end = ReadEntries(map, size, reader);
|
||||
std::free(owned);
|
||||
}
|
||||
|
||||
if (valid_end)
|
||||
@@ -111,8 +111,13 @@ public:
|
||||
LinearDiskCache cache;
|
||||
const size_t value_bytes = size_t(value_size) * sizeof(V);
|
||||
const u32 entry_number = 1;
|
||||
std::vector<u8> out(sizeof(Header) + sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32));
|
||||
u8* p = out.data();
|
||||
const size_t out_size = sizeof(Header) + sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32);
|
||||
u8* const out = static_cast<u8*>(std::malloc(out_size));
|
||||
u8* p = out;
|
||||
bool ok;
|
||||
|
||||
if (!out)
|
||||
return false;
|
||||
|
||||
cache.m_header.Init();
|
||||
std::memcpy(p, &cache.m_header, sizeof(Header));
|
||||
@@ -122,11 +127,17 @@ public:
|
||||
if (value_bytes)
|
||||
std::memcpy(p + sizeof(u32) + sizeof(K), value, value_bytes);
|
||||
std::memcpy(p + sizeof(u32) + sizeof(K) + value_bytes, &entry_number, sizeof(u32));
|
||||
if (filestream_matches_buf(filename.c_str(), out.data(), out.size()))
|
||||
return true;
|
||||
return filestream_write_file_atomic(filename.c_str(), out.data(), s64(out.size()));
|
||||
ok = filestream_matches_buf(filename.c_str(), out, out_size) ||
|
||||
filestream_write_file_atomic(filename.c_str(), out, s64(out_size));
|
||||
std::free(out);
|
||||
return ok;
|
||||
}
|
||||
|
||||
LinearDiskCache() = default;
|
||||
~LinearDiskCache() { std::free(m_append_buffer); }
|
||||
LinearDiskCache(const LinearDiskCache&) = delete;
|
||||
LinearDiskCache& operator=(const LinearDiskCache&) = delete;
|
||||
|
||||
void Sync() { m_file.Flush(); }
|
||||
void Close()
|
||||
{
|
||||
@@ -141,9 +152,16 @@ public:
|
||||
const size_t entry_bytes = sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32);
|
||||
u8* out;
|
||||
|
||||
if (m_append_buffer.size() < entry_bytes)
|
||||
m_append_buffer.resize(entry_bytes);
|
||||
out = m_append_buffer.data();
|
||||
if (m_append_capacity < entry_bytes)
|
||||
{
|
||||
// grows to the largest entry, then stays
|
||||
u8* const grown = static_cast<u8*>(std::realloc(m_append_buffer, entry_bytes));
|
||||
if (!grown)
|
||||
return;
|
||||
m_append_buffer = grown;
|
||||
m_append_capacity = entry_bytes;
|
||||
}
|
||||
out = m_append_buffer;
|
||||
m_num_entries++;
|
||||
std::memcpy(out, &value_size, sizeof(u32));
|
||||
std::memcpy(out + sizeof(u32), &key, sizeof(K));
|
||||
@@ -158,7 +176,7 @@ private:
|
||||
// the offset where the last whole one ends.
|
||||
u64 ReadEntries(const u8* base, u64 size, LinearDiskCacheReader<K, V>& reader)
|
||||
{
|
||||
std::unique_ptr<V[]> aligned;
|
||||
V* aligned = nullptr;
|
||||
u32 aligned_capacity = 0;
|
||||
u64 pos = sizeof(Header);
|
||||
|
||||
@@ -183,17 +201,21 @@ private:
|
||||
{
|
||||
if (aligned_capacity < value_size)
|
||||
{
|
||||
aligned = std::make_unique_for_overwrite<V[]>(value_size);
|
||||
V* const grown = static_cast<V*>(std::realloc(aligned, size_t(value_bytes)));
|
||||
if (!grown)
|
||||
break;
|
||||
aligned = grown;
|
||||
aligned_capacity = value_size;
|
||||
}
|
||||
std::memcpy(aligned.get(), value, value_bytes);
|
||||
value = reinterpret_cast<const u8*>(aligned.get());
|
||||
std::memcpy(aligned, value, size_t(value_bytes));
|
||||
value = reinterpret_cast<const u8*>(aligned);
|
||||
}
|
||||
reader.Read(key, reinterpret_cast<const V*>(value), value_size);
|
||||
|
||||
m_num_entries++;
|
||||
pos += sizeof(u32) + sizeof(K) + value_bytes + sizeof(u32);
|
||||
}
|
||||
std::free(aligned);
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -216,7 +238,8 @@ private:
|
||||
} m_header;
|
||||
|
||||
File::IOFile m_file;
|
||||
std::vector<u8> m_append_buffer;
|
||||
u8* m_append_buffer = nullptr;
|
||||
size_t m_append_capacity = 0;
|
||||
u32 m_num_entries = 0;
|
||||
};
|
||||
} // namespace Common
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Core/HW/DVD/DVDThread.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -45,28 +46,25 @@ DVDThread::~DVDThread()
|
||||
retro_eventcount_free(&m_result_ready);
|
||||
}
|
||||
|
||||
void DVDThread::FreeResults()
|
||||
void DVDThread::FreeResultList(mpsc_stack_node_t* node)
|
||||
{
|
||||
mpsc_stack_node_t* node = m_result_chain;
|
||||
m_result_chain = nullptr;
|
||||
while (node)
|
||||
{
|
||||
mpsc_stack_node_t* const next = node->next;
|
||||
delete reinterpret_cast<ResultNode*>(node);
|
||||
std::free(reinterpret_cast<ResultNode*>(node)->data);
|
||||
std::free(node);
|
||||
node = next;
|
||||
}
|
||||
mpsc_stack_node_t* lists[3] = {mpsc_stack_drain(&m_result_queue),
|
||||
mpsc_stack_drain(&m_free_results), m_worker_free};
|
||||
}
|
||||
|
||||
void DVDThread::FreeResults()
|
||||
{
|
||||
FreeResultList(m_result_chain);
|
||||
m_result_chain = nullptr;
|
||||
FreeResultList(mpsc_stack_drain(&m_result_queue));
|
||||
FreeResultList(mpsc_stack_drain(&m_free_results));
|
||||
FreeResultList(m_worker_free);
|
||||
m_worker_free = nullptr;
|
||||
for (mpsc_stack_node_t* queued : lists)
|
||||
{
|
||||
while (queued)
|
||||
{
|
||||
mpsc_stack_node_t* const next = queued->next;
|
||||
delete reinterpret_cast<ResultNode*>(queued);
|
||||
queued = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CPU thread. Unlinks and returns the result with this id, or every result taken so far when
|
||||
@@ -85,7 +83,7 @@ DVDThread::ResultNode* DVDThread::TakeResult(u64 id)
|
||||
for (mpsc_stack_node_t** link = &m_result_chain; *link; link = &(*link)->next)
|
||||
{
|
||||
ResultNode* const node = reinterpret_cast<ResultNode*>(*link);
|
||||
if (id == ~u64{0} || node->result.first.id == id)
|
||||
if (id == ~u64{0} || node->request.id == id)
|
||||
{
|
||||
*link = node->link.next;
|
||||
return node;
|
||||
@@ -103,7 +101,6 @@ void DVDThread::Start()
|
||||
m_next_id = 0;
|
||||
|
||||
m_dvd_thread.Reset("DVD thread", std::bind_front(&DVDThread::ProcessReadRequest, this));
|
||||
m_lent_reads.reserve(16);
|
||||
}
|
||||
|
||||
void DVDThread::Stop()
|
||||
@@ -113,7 +110,7 @@ void DVDThread::Stop()
|
||||
|
||||
FreeResults();
|
||||
m_result_map.clear();
|
||||
m_lent_reads.clear();
|
||||
m_num_lent_reads = 0;
|
||||
|
||||
m_disc.reset();
|
||||
}
|
||||
@@ -128,13 +125,15 @@ void DVDThread::DoState(PointerWrap& p)
|
||||
// This won't affect the behavior of FinishRead.
|
||||
while (ResultNode* const node = TakeResult(~u64{0}))
|
||||
{
|
||||
m_result_map.emplace(node->result.first.id, std::move(node->result));
|
||||
delete node;
|
||||
m_result_map.emplace(node->request.id,
|
||||
ReadResult(node->request, std::vector<u8>(node->data,
|
||||
node->data + node->size)));
|
||||
mpsc_stack_push(&m_free_results, &node->link);
|
||||
}
|
||||
// Lent reads are saved as the results the DVD thread would have made; a state loaded has
|
||||
// only those.
|
||||
if (p.IsReadMode())
|
||||
m_lent_reads.clear();
|
||||
m_num_lent_reads = 0;
|
||||
else
|
||||
MaterializeLentReads();
|
||||
|
||||
@@ -282,11 +281,11 @@ void DVDThread::StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_
|
||||
|
||||
request.time_started_ticks = core_timing.GetTicks();
|
||||
|
||||
if (m_disc && m_disc->View(dvd_offset, length, partition))
|
||||
if (m_num_lent_reads < MAX_LENT_READS && m_disc && m_disc->View(dvd_offset, length, partition))
|
||||
{
|
||||
m_file_logger.Log(*m_disc, partition, dvd_offset);
|
||||
m_disc->Prefetch(dvd_offset, length, partition);
|
||||
m_lent_reads.push_back(request);
|
||||
m_lent_reads[m_num_lent_reads++] = request;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -297,8 +296,9 @@ void DVDThread::StartReadInternal(bool copy_to_ram, u32 output_address, u64 dvd_
|
||||
|
||||
void DVDThread::MaterializeLentReads()
|
||||
{
|
||||
for (const ReadRequest& request : m_lent_reads)
|
||||
for (int i = 0; i < m_num_lent_reads; i++)
|
||||
{
|
||||
const ReadRequest& request = m_lent_reads[i];
|
||||
const u8* data = m_disc ? m_disc->View(request.dvd_offset, request.length, request.partition) :
|
||||
nullptr;
|
||||
std::vector<u8> buffer;
|
||||
@@ -306,7 +306,7 @@ void DVDThread::MaterializeLentReads()
|
||||
buffer.assign(data, data + request.length);
|
||||
m_result_map.emplace(request.id, ReadResult(request, std::move(buffer)));
|
||||
}
|
||||
m_lent_reads.clear();
|
||||
m_num_lent_reads = 0;
|
||||
}
|
||||
|
||||
void DVDThread::GlobalFinishRead(Core::System& system, u64 id, s64 cycles_late)
|
||||
@@ -316,14 +316,13 @@ void DVDThread::GlobalFinishRead(Core::System& system, u64 id, s64 cycles_late)
|
||||
|
||||
void DVDThread::FinishRead(u64 id, s64 cycles_late)
|
||||
{
|
||||
for (size_t i = 0; i < m_lent_reads.size(); i++)
|
||||
for (int i = 0; i < m_num_lent_reads; i++)
|
||||
{
|
||||
if (m_lent_reads[i].id != id)
|
||||
continue;
|
||||
|
||||
const ReadRequest request = m_lent_reads[i];
|
||||
m_lent_reads[i] = m_lent_reads.back();
|
||||
m_lent_reads.pop_back();
|
||||
m_lent_reads[i] = m_lent_reads[--m_num_lent_reads];
|
||||
|
||||
// the disc is the one the read started on: a change materializes lent reads first
|
||||
const u8* data = m_disc->View(request.dvd_offset, request.length, request.partition);
|
||||
@@ -360,8 +359,9 @@ void DVDThread::FinishRead(u64 id, s64 cycles_late)
|
||||
}
|
||||
// We have now obtained the right ReadResult.
|
||||
|
||||
const ReadRequest& request = node ? node->result.first : result.first;
|
||||
const std::vector<u8>& buffer = node ? node->result.second : result.second;
|
||||
const ReadRequest& request = node ? node->request : result.first;
|
||||
const u8* const data = node ? node->data : result.second.data();
|
||||
const size_t size = node ? node->size : result.second.size();
|
||||
|
||||
DEBUG_LOG_FMT(DVDINTERFACE, "Disc has been read. Emulated time including delay: {} us.",
|
||||
(m_system.GetCoreTiming().GetTicks() - request.time_started_ticks) /
|
||||
@@ -369,7 +369,7 @@ void DVDThread::FinishRead(u64 id, s64 cycles_late)
|
||||
|
||||
auto& dvd_interface = m_system.GetDVDInterface();
|
||||
DVD::DIInterruptType interrupt;
|
||||
if (buffer.size() != request.length)
|
||||
if (size != request.length)
|
||||
{
|
||||
PanicAlertFmtT("The disc could not be read (at {0:#x} - {1:#x}).", request.dvd_offset,
|
||||
request.dvd_offset + request.length);
|
||||
@@ -382,14 +382,15 @@ void DVDThread::FinishRead(u64 id, s64 cycles_late)
|
||||
if (request.copy_to_ram)
|
||||
{
|
||||
auto& memory = m_system.GetMemory();
|
||||
memory.CopyToEmu(request.output_address, buffer.data(), request.length);
|
||||
memory.CopyToEmu(request.output_address, data, request.length);
|
||||
}
|
||||
|
||||
interrupt = DVD::DIInterruptType::TCINT;
|
||||
}
|
||||
|
||||
// Notify the emulated software that the command has been executed
|
||||
dvd_interface.FinishExecutingCommand(request.reply_type, interrupt, cycles_late, buffer);
|
||||
dvd_interface.FinishExecutingCommand(request.reply_type, interrupt, cycles_late,
|
||||
std::span<const u8>(data, size));
|
||||
|
||||
if (node)
|
||||
mpsc_stack_push(&m_free_results, &node->link);
|
||||
@@ -403,7 +404,7 @@ DVDThread::ResultNode* DVDThread::TakeFreeResult()
|
||||
m_worker_free = mpsc_stack_drain(&m_free_results);
|
||||
node = m_worker_free;
|
||||
if (!node)
|
||||
return new ResultNode{{nullptr}, ReadResult()};
|
||||
return static_cast<ResultNode*>(std::calloc(1, sizeof(ResultNode)));
|
||||
m_worker_free = node->next;
|
||||
return reinterpret_cast<ResultNode*>(node);
|
||||
}
|
||||
@@ -413,11 +414,23 @@ void DVDThread::ProcessReadRequest(ReadRequest&& request)
|
||||
m_file_logger.Log(*m_disc, request.partition, request.dvd_offset);
|
||||
|
||||
ResultNode* const node = TakeFreeResult();
|
||||
std::vector<u8>& buffer = node->result.second;
|
||||
buffer.resize(request.length);
|
||||
if (!m_disc->Read(request.dvd_offset, request.length, buffer.data(), request.partition))
|
||||
buffer.resize(0);
|
||||
node->result.first = std::move(request);
|
||||
node->request = request;
|
||||
node->size = 0;
|
||||
if (node->capacity < request.length)
|
||||
{
|
||||
// grows to the largest read, then stays
|
||||
u8* const grown = static_cast<u8*>(std::realloc(node->data, request.length));
|
||||
if (grown)
|
||||
{
|
||||
node->data = grown;
|
||||
node->capacity = request.length;
|
||||
}
|
||||
}
|
||||
if (node->capacity >= request.length &&
|
||||
m_disc->Read(request.dvd_offset, request.length, node->data, request.partition))
|
||||
{
|
||||
node->size = request.length;
|
||||
}
|
||||
|
||||
mpsc_stack_push(&m_result_queue, &node->link);
|
||||
retro_eventcount_notify(&m_result_ready);
|
||||
|
||||
@@ -132,10 +132,14 @@ private:
|
||||
// takes them all at once into m_result_chain, oldest first, and wakes on m_result_ready.
|
||||
struct ResultNode
|
||||
{
|
||||
mpsc_stack_node_t link;
|
||||
ReadResult result;
|
||||
mpsc_stack_node_t link; // first: the drained chain is cast back
|
||||
ReadRequest request;
|
||||
u8* data; // malloc'd, kept across uses
|
||||
u32 size; // octets read; not request.length when the read failed
|
||||
u32 capacity;
|
||||
};
|
||||
ResultNode* TakeResult(u64 id);
|
||||
static void FreeResultList(mpsc_stack_node_t* node);
|
||||
void FreeResults();
|
||||
|
||||
// Results go back for the DVD thread to fill again, buffers and all.
|
||||
@@ -150,7 +154,12 @@ private:
|
||||
// Reads the disc image can lend: no DVD thread and no buffer, the bytes copied straight
|
||||
// from the image when they are due. Their pages are prefetched when the read starts.
|
||||
void MaterializeLentReads();
|
||||
std::vector<ReadRequest> m_lent_reads; // CPU thread only
|
||||
enum
|
||||
{
|
||||
MAX_LENT_READS = 16
|
||||
};
|
||||
ReadRequest m_lent_reads[MAX_LENT_READS]; // CPU thread only
|
||||
int m_num_lent_reads = 0;
|
||||
|
||||
std::unique_ptr<DiscIO::Volume> m_disc;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -308,15 +309,18 @@ void CEXIMemoryCard::SetCS(int cs)
|
||||
case Command::PageProgram:
|
||||
if (m_position >= 5)
|
||||
{
|
||||
int count = m_position - 5;
|
||||
int i = 0;
|
||||
u32 count = m_position - 5;
|
||||
u32 i = 0;
|
||||
m_status &= ~MC_STATUS_BUSY;
|
||||
|
||||
while (count--)
|
||||
// a run at a time, up to the end of the buffer or of the 512-byte page it wraps in
|
||||
while (count)
|
||||
{
|
||||
m_memory_card->Write(m_address, 1, &(m_programming_buffer[i++]));
|
||||
i &= 127;
|
||||
m_address = (m_address & ~0x1FF) | ((m_address + 1) & 0x1FF);
|
||||
const u32 run = std::min({count, 128 - i, 0x200 - (m_address & 0x1FF)});
|
||||
m_memory_card->Write(m_address, run, &m_programming_buffer[i]);
|
||||
i = (i + run) & 127;
|
||||
m_address = (m_address & ~0x1FF) | ((m_address + run) & 0x1FF);
|
||||
count -= run;
|
||||
}
|
||||
|
||||
CmdDoneLater(5000);
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -590,9 +593,31 @@ void GCMemcardDirectory::FlushToFile()
|
||||
WriteQueuedJobs();
|
||||
}
|
||||
|
||||
void GCMemcardDirectory::PostFlushJob(const Memcard::GCIFile& save, bool retire)
|
||||
{
|
||||
const u32 num_blocks = retire ? 0 : static_cast<u32>(save.m_save_data.size());
|
||||
const size_t blocks_size = size_t(num_blocks) * Memcard::BLOCK_SIZE;
|
||||
const size_t name_size = save.m_filename.size() + 1;
|
||||
auto* job =
|
||||
static_cast<FlushJob*>(std::malloc(offsetof(FlushJob, tail) + blocks_size + name_size));
|
||||
if (!job)
|
||||
return;
|
||||
static_assert(sizeof(Memcard::GCMBlock) == Memcard::BLOCK_SIZE);
|
||||
static_assert(std::is_trivially_copyable_v<Memcard::DEntry>);
|
||||
job->num_blocks = num_blocks;
|
||||
job->retire = retire;
|
||||
if (!retire)
|
||||
{
|
||||
job->header = save.m_gci_header;
|
||||
std::memcpy(job->tail, save.m_save_data.data(), blocks_size);
|
||||
}
|
||||
std::memcpy(job->tail + blocks_size, save.m_filename.c_str(), name_size);
|
||||
job->filename = reinterpret_cast<const char*>(job->tail + blocks_size);
|
||||
mpsc_stack_push(&m_flush_jobs, &job->link);
|
||||
}
|
||||
|
||||
void GCMemcardDirectory::QueueDirtySaves()
|
||||
{
|
||||
const auto post = [this](FlushJob* job) { mpsc_stack_push(&m_flush_jobs, &job->link); };
|
||||
|
||||
for (Memcard::GCIFile& save : m_saves)
|
||||
{
|
||||
@@ -629,12 +654,12 @@ void GCMemcardDirectory::QueueDirtySaves()
|
||||
}
|
||||
save.m_filename = default_save_name;
|
||||
}
|
||||
post(new FlushJob{{}, save.m_filename, false, save.m_gci_header, save.m_save_data});
|
||||
PostFlushJob(save, false);
|
||||
}
|
||||
else if (save.m_filename.length() != 0)
|
||||
{
|
||||
save.m_dirty = false;
|
||||
post(new FlushJob{{}, save.m_filename, true, {}, {}});
|
||||
PostFlushJob(save, true);
|
||||
save.m_filename.clear();
|
||||
save.m_save_data.clear();
|
||||
save.m_used_blocks.clear();
|
||||
@@ -658,47 +683,51 @@ void GCMemcardDirectory::QueueDirtySaves()
|
||||
void GCMemcardDirectory::WriteQueuedJobs()
|
||||
{
|
||||
// Oldest first, so a later job for the same file wins.
|
||||
std::vector<std::unique_ptr<FlushJob>> jobs;
|
||||
for (mpsc_stack_node_t* node = mpsc_stack_reverse(mpsc_stack_drain(&m_flush_jobs)); node;)
|
||||
{
|
||||
auto* job = reinterpret_cast<FlushJob*>(node);
|
||||
node = node->next;
|
||||
jobs.emplace_back(job);
|
||||
}
|
||||
mpsc_stack_node_t* const jobs = mpsc_stack_reverse(mpsc_stack_drain(&m_flush_jobs));
|
||||
mpsc_stack_node_t* node;
|
||||
|
||||
for (size_t i = 0; i < jobs.size(); ++i)
|
||||
for (node = jobs; node; node = node->next)
|
||||
{
|
||||
const FlushJob& job = *jobs[i];
|
||||
const bool superseded = std::any_of(jobs.begin() + i + 1, jobs.end(),
|
||||
[&job](const auto& later) {
|
||||
return later->filename == job.filename;
|
||||
});
|
||||
if (superseded)
|
||||
const FlushJob* const job = reinterpret_cast<const FlushJob*>(node);
|
||||
const mpsc_stack_node_t* later;
|
||||
for (later = node->next; later; later = later->next)
|
||||
{
|
||||
if (!std::strcmp(reinterpret_cast<const FlushJob*>(later)->filename, job->filename))
|
||||
break;
|
||||
}
|
||||
if (later)
|
||||
continue;
|
||||
|
||||
if (job.retire)
|
||||
if (job->retire)
|
||||
{
|
||||
const std::string deleted_name = job.filename + ".deleted";
|
||||
const std::string name = job->filename;
|
||||
const std::string deleted_name = name + ".deleted";
|
||||
if (File::Exists(deleted_name))
|
||||
File::Delete(deleted_name);
|
||||
File::Rename(job.filename, deleted_name);
|
||||
File::Rename(name, deleted_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// replaced whole or left as it was: a save is never half written
|
||||
RFILE* gci = filestream_open_atomic(job.filename.c_str());
|
||||
RFILE* gci = filestream_open_atomic(job->filename);
|
||||
if (!gci)
|
||||
{
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to open file at {} for writing", job.filename);
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to open file at {} for writing", job->filename);
|
||||
continue;
|
||||
}
|
||||
static_assert(sizeof(Memcard::GCMBlock) == Memcard::BLOCK_SIZE);
|
||||
const s64 blocks_size = s64(job.blocks.size()) * Memcard::BLOCK_SIZE;
|
||||
const s64 blocks_size = s64(job->num_blocks) * Memcard::BLOCK_SIZE;
|
||||
const bool ok =
|
||||
filestream_write(gci, &job.header, Memcard::DENTRY_SIZE) == Memcard::DENTRY_SIZE &&
|
||||
(!blocks_size || filestream_write(gci, job.blocks.data(), blocks_size) == blocks_size);
|
||||
if (filestream_commit_atomic(gci, job.filename.c_str(), ok) != 0)
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to save data to {}", job.filename);
|
||||
filestream_write(gci, &job->header, Memcard::DENTRY_SIZE) == Memcard::DENTRY_SIZE &&
|
||||
(!blocks_size || filestream_write(gci, job->tail, blocks_size) == blocks_size);
|
||||
if (filestream_commit_atomic(gci, job->filename, ok) != 0)
|
||||
ERROR_LOG_FMT(EXPANSIONINTERFACE, "Failed to save data to {}", job->filename);
|
||||
}
|
||||
|
||||
for (node = jobs; node;)
|
||||
{
|
||||
mpsc_stack_node_t* const next = node->next;
|
||||
std::free(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,14 +53,18 @@ private:
|
||||
|
||||
// A file to write or retire, made by the CPU thread from its saves and written by the
|
||||
// flush thread, so the two never touch the same data.
|
||||
// One malloc'd block: the job, then its save blocks, then its file name.
|
||||
struct FlushJob
|
||||
{
|
||||
mpsc_stack_node_t link; // first: the drained chain is cast back
|
||||
std::string filename;
|
||||
const char* filename; // in the tail
|
||||
u32 num_blocks;
|
||||
bool retire; // rename to .deleted instead of writing
|
||||
Memcard::DEntry header;
|
||||
std::vector<Memcard::GCMBlock> blocks;
|
||||
u8 tail[1]; // num_blocks * BLOCK_SIZE octets, then the name
|
||||
};
|
||||
// CPU thread: hands a job for the save to the flush thread.
|
||||
void PostFlushJob(const Memcard::GCIFile& save, bool retire);
|
||||
// CPU thread: turns the dirty saves into jobs for the flush thread.
|
||||
void QueueDirtySaves();
|
||||
// Flush thread (or the destructor): writes the queued jobs, the newest per file.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -82,7 +83,8 @@ MemoryCard::MemoryCard(std::string filename, ExpansionInterface::Slot card_slot,
|
||||
// it's safe to startup the flush thread (which reads them).
|
||||
m_flush_buffer = std::make_unique<u8[]>(m_memory_card_size);
|
||||
memcpy(&m_flush_buffer[0], &m_memcard_data[0], m_memory_card_size);
|
||||
m_flush_dirty.assign((m_memory_card_size + Memcard::BLOCK_SIZE - 1) / Memcard::BLOCK_SIZE, 0);
|
||||
m_num_blocks = std::min<u32>((m_memory_card_size + Memcard::BLOCK_SIZE - 1) / Memcard::BLOCK_SIZE,
|
||||
MAX_BLOCKS);
|
||||
m_flush_thread = sthread_create(Common::ThreadEntry<&MemoryCard::FlushThread>, this);
|
||||
}
|
||||
|
||||
@@ -128,8 +130,11 @@ void MemoryCard::ApplyChanges()
|
||||
if (change->length)
|
||||
{
|
||||
const u32 last = (change->offset + change->length - 1) / Memcard::BLOCK_SIZE;
|
||||
for (u32 block = change->offset / Memcard::BLOCK_SIZE; block <= last; block++)
|
||||
m_flush_dirty[block] = 1;
|
||||
for (u32 block = change->offset / Memcard::BLOCK_SIZE; block <= last && block < MAX_BLOCKS;
|
||||
block++)
|
||||
{
|
||||
m_flush_dirty[block >> 5] |= 1u << (block & 31);
|
||||
}
|
||||
}
|
||||
std::free(change);
|
||||
}
|
||||
@@ -137,7 +142,6 @@ void MemoryCard::ApplyChanges()
|
||||
|
||||
bool MemoryCard::WriteChanges(File::IOFile& file, bool whole)
|
||||
{
|
||||
const u32 blocks = static_cast<u32>(m_flush_dirty.size());
|
||||
u32 block = 0;
|
||||
|
||||
// a block stays marked until it is written
|
||||
@@ -145,19 +149,22 @@ bool MemoryCard::WriteChanges(File::IOFile& file, bool whole)
|
||||
{
|
||||
if (!file.WriteBytes(&m_flush_buffer[0], m_memory_card_size))
|
||||
return false;
|
||||
std::fill(m_flush_dirty.begin(), m_flush_dirty.end(), u8(0));
|
||||
std::memset(m_flush_dirty, 0, sizeof(m_flush_dirty));
|
||||
return true;
|
||||
}
|
||||
|
||||
while (block < blocks)
|
||||
while (block < m_num_blocks)
|
||||
{
|
||||
u32 end;
|
||||
if (!m_flush_dirty[block])
|
||||
u32 word = m_flush_dirty[block >> 5] >> (block & 31);
|
||||
if (!word)
|
||||
{
|
||||
block++;
|
||||
// the rest of this word is clean
|
||||
block = (block | 31) + 1;
|
||||
continue;
|
||||
}
|
||||
for (end = block; end < blocks && m_flush_dirty[end]; end++)
|
||||
block += static_cast<u32>(std::countr_zero(word));
|
||||
for (end = block + 1; end < m_num_blocks && (m_flush_dirty[end >> 5] >> (end & 31)) & 1; end++)
|
||||
{
|
||||
}
|
||||
const u32 offset = block * Memcard::BLOCK_SIZE;
|
||||
@@ -167,8 +174,8 @@ bool MemoryCard::WriteChanges(File::IOFile& file, bool whole)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::fill(m_flush_dirty.begin() + block, m_flush_dirty.begin() + end, u8(0));
|
||||
block = end;
|
||||
for (; block < end; block++)
|
||||
m_flush_dirty[block >> 5] &= ~(1u << (block & 31));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -203,7 +210,8 @@ void MemoryCard::FlushThread()
|
||||
// file doesn't disappear out from under us after the first check.
|
||||
// Only the blocks that changed are written over a card of the right size.
|
||||
File::IOFile file(m_filename, "r+b");
|
||||
bool whole = !file || file.GetSize() != m_memory_card_size;
|
||||
bool whole = !file || file.GetSize() != m_memory_card_size ||
|
||||
m_memory_card_size > MAX_BLOCKS * Memcard::BLOCK_SIZE;
|
||||
|
||||
if (!file)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <queues/mpsc_stack.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
@@ -63,8 +62,13 @@ private:
|
||||
// The flush thread's own copy, brought up to date from m_changes before each write.
|
||||
std::unique_ptr<u8[]> m_flush_buffer;
|
||||
mpsc_stack_t m_changes{};
|
||||
// Flush thread: one flag per block, set where m_flush_buffer differs from the file.
|
||||
std::vector<u8> m_flush_dirty;
|
||||
// Flush thread: one bit per block, set where m_flush_buffer differs from the file.
|
||||
enum
|
||||
{
|
||||
MAX_BLOCKS = 2048 // the largest card, 128 Mbit, in 8 KiB blocks
|
||||
};
|
||||
u32 m_flush_dirty[MAX_BLOCKS / 32] = {};
|
||||
u32 m_num_blocks = 0;
|
||||
sthread_t* m_flush_thread = nullptr;
|
||||
Common::Flag m_flush_running{true};
|
||||
Common::Flag m_dirty;
|
||||
|
||||
Reference in new issue
Block a user