Commit bdf4bd07 authored by Jack Andersen's avatar Jack Andersen

Use asynchronous I/O for Card access

parent 8052a637
......@@ -9,11 +9,14 @@ set(KABUFUDA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE PATH "kabufud
unset(PLAT_SRCS)
if(WIN32)
list(APPEND PLAT_SRCS lib/kabufuda/winsupport.cpp include/kabufuda/winsupport.hpp)
list(APPEND PLAT_SRCS lib/kabufuda/winsupport.cpp include/kabufuda/winsupport.hpp lib/kabufuda/AsyncIOWin32.cpp)
else()
list(APPEND PLAT_SRCS lib/kabufuda/AsyncIOPosix.cpp)
endif()
add_library(kabufuda STATIC
include/kabufuda/Constants.hpp
include/kabufuda/AsyncIO.hpp
include/kabufuda/BlockAllocationTable.hpp lib/kabufuda/BlockAllocationTable.cpp
include/kabufuda/Card.hpp lib/kabufuda/Card.cpp
include/kabufuda/Directory.hpp lib/kabufuda/Directory.cpp
......
#ifndef __KABU_ASYNCIO_HPP__
#define __KABU_ASYNCIO_HPP__
#ifndef _WIN32
#include <aio.h>
using SizeReturn = ssize_t;
#else
#include <windows.h>
using SizeReturn = SSIZE_T;
#endif
#include "Util.hpp"
#include <vector>
namespace kabufuda
{
class AsyncIO
{
#ifndef _WIN32
int m_fd = -1;
std::vector<std::pair<struct aiocb, SizeReturn>> m_queue;
#else
#endif
size_t m_maxBlock = 0;
public:
AsyncIO() = default;
AsyncIO(SystemStringView filename, bool truncate = false);
~AsyncIO();
AsyncIO(AsyncIO&& other);
AsyncIO& operator=(AsyncIO&& other);
AsyncIO(const AsyncIO* other) = delete;
AsyncIO& operator=(const AsyncIO& other) = delete;
void resizeQueue(size_t queueSz) { m_queue.resize(queueSz); }
SizeReturn syncRead(void* buf, size_t length, off_t offset);
bool asyncRead(size_t qIdx, void* buf, size_t length, off_t offset);
SizeReturn syncWrite(const void* buf, size_t length, off_t offset);
bool asyncWrite(size_t qIdx, const void* buf, size_t length, off_t offset);
ECardResult pollStatus(size_t qIdx, SizeReturn* szRet = nullptr) const;
ECardResult pollStatus() const;
void waitForCompletion() const;
operator bool() const { return m_fd != -1; }
};
}
#endif // __KABU_ASYNCIO_HPP__
......@@ -5,6 +5,7 @@
#include "Directory.hpp"
#include "File.hpp"
#include "Util.hpp"
#include "AsyncIO.hpp"
#include <string>
#include <vector>
......@@ -12,7 +13,6 @@
#define CARD_FILENAME_MAX 32
#define CARD_ICON_MAX 8
#undef NOFILE
namespace kabufuda
{
......@@ -29,24 +29,6 @@ public:
operator bool() const { return getFileNo() != -1; }
};
enum class ECardResult
{
CRC_MISMATCH = -1003, /* Extension enum for Retro's CRC check */
FATAL_ERROR = -128,
ENCODING = -13,
NAMETOOLONG = -12,
INSSPACE = -9,
NOENT = -8,
EXIST = -7,
BROKEN = -6,
IOERROR = -5,
NOFILE = -4,
NOCARD = -3,
WRONGDEVICE = -2,
BUSY = -1,
READY = 0
};
struct ProbeResults
{
ECardResult x0_error;
......@@ -102,31 +84,35 @@ struct CardStat
class Card
{
#pragma pack(push, 4)
struct CardHeader
{
uint8_t m_serial[12];
uint64_t m_formatTime;
int32_t m_sramBias;
uint32_t m_sramLanguage;
uint32_t m_unknown;
uint16_t m_deviceId; /* 0 for Slot A, 1 for Slot B */
uint16_t m_sizeMb;
uint16_t m_encoding;
uint8_t __padding[468];
uint16_t m_updateCounter;
uint16_t m_checksum;
uint16_t m_checksumInv;
void _swapEndian();
};
union {
struct
{
uint8_t m_serial[12];
uint64_t m_formatTime;
int32_t m_sramBias;
uint32_t m_sramLanguage;
uint32_t m_unknown;
uint16_t m_deviceId; /* 0 for Slot A, 1 for Slot B */
uint16_t m_sizeMb;
uint16_t m_encoding;
uint8_t __padding[468];
uint16_t m_updateCounter;
uint16_t m_checksum;
uint16_t m_checksumInv;
};
CardHeader m_ch;
uint8_t __raw[BlockSize];
};
CardHeader m_tmpCh;
#pragma pack(pop)
SystemString m_filename;
FILE* m_fileHandle = nullptr;
AsyncIO m_fileHandle;
Directory m_dirs[2];
BlockAllocationTable m_bats[2];
Directory m_tmpDirs[2];
BlockAllocationTable m_tmpBats[2];
uint8_t m_currentDir;
uint8_t m_currentBat;
......@@ -134,12 +120,15 @@ class Card
char m_game[5] = {'\0'};
char m_maker[3] = {'\0'};
void _swapEndian();
void _updateDirAndBat(const Directory& dir, const BlockAllocationTable& bat);
void _updateChecksum();
File* _fileFromHandle(const FileHandle& fh) const;
void _deleteFile(File& f, BlockAllocationTable& bat);
bool m_dirty = false;
bool m_opened = false;
ECardResult _pumpOpen();
public:
Card();
/**
......@@ -157,7 +146,7 @@ public:
* @param game
* @param maker
*/
Card(SystemStringView filepath, const char* game = nullptr, const char* maker = nullptr);
Card(const char* game = nullptr, const char* maker = nullptr);
~Card();
/**
......@@ -237,7 +226,7 @@ public:
* @param buf
* @param size
*/
ECardResult write(FileHandle& fh, const void* buf, size_t size);
ECardResult asyncWrite(FileHandle& fh, const void* buf, size_t size);
/**
* @brief read
......@@ -245,7 +234,7 @@ public:
* @param dst
* @param size
*/
ECardResult read(FileHandle& fh, void* dst, size_t size);
ECardResult asyncRead(FileHandle& fh, void* dst, size_t size);
/**
* @brief seek
......@@ -336,6 +325,7 @@ public:
*/
ECardResult setStatus(uint32_t fileNo, const CardStat& stat);
#if 0 // TODO: Async-friendly implementations
/**
* @brief Copies a file from the current Card instance to a specified Card instance
* @param fh The file to copy
......@@ -351,6 +341,7 @@ public:
* @return
*/
bool moveFileTo(FileHandle& fh, Card& dest);
#endif
/**
* @brief Sets the current game, if not null any openFile requests will only return files that match this game
......@@ -417,6 +408,11 @@ public:
*/
void commit();
/**
* @brief Opens card image (does nothing if currently open path matches)
*/
bool open(SystemStringView filepath);
/**
* @brief Commits changes to disk and closes host file
*/
......
......@@ -250,71 +250,6 @@ typedef struct stat Sstat;
uint64_t getGCTime();
enum class FileLockType
{
None = 0,
Read,
Write
};
static inline FILE* Fopen(const SystemChar* path, const SystemChar* mode, FileLockType lock = FileLockType::None)
{
#if CARD_UCS2
FILE* fp = _wfopen(path, mode);
if (!fp)
return nullptr;
#else
FILE* fp = fopen(path, mode);
if (!fp)
return nullptr;
#endif
if (lock != FileLockType::None)
{
#if _WIN32
OVERLAPPED ov = {};
LockFileEx((HANDLE)(uintptr_t)_fileno(fp), (lock == FileLockType::Write) ? LOCKFILE_EXCLUSIVE_LOCK : 0, 0, 0, 1,
&ov);
#else
if (flock(fileno(fp), ((lock == FileLockType::Write) ? LOCK_EX : LOCK_SH) | LOCK_NB))
fprintf(stderr, "flock %s: %s", path, strerror(errno));
#endif
}
return fp;
}
static inline int FSeek(FILE* fp, int64_t offset, int whence)
{
#if _WIN32
return _fseeki64(fp, offset, whence);
#elif __APPLE__ || __FreeBSD__
return fseeko(fp, offset, whence);
#else
return fseeko64(fp, offset, whence);
#endif
}
static inline int64_t FTell(FILE* fp)
{
#if _WIN32
return _ftelli64(fp);
#elif __APPLE__ || __FreeBSD__
return ftello(fp);
#else
return ftello64(fp);
#endif
}
static inline int Rename(const SystemChar* oldpath, const SystemChar* newpath)
{
#if CARD_UCS2
//return _wrename(oldpath, newpath);
return MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) == 0;
#else
return rename(oldpath, newpath);
#endif
}
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
......@@ -349,6 +284,26 @@ static inline int Stat(const SystemChar* path, Sstat* statOut)
* @param checksumInv
*/
void calculateChecksumBE(const uint16_t* data, size_t len, uint16_t* checksum, uint16_t* checksumInv);
#undef NOFILE
enum class ECardResult
{
CRC_MISMATCH = -1003, /* Extension enum for Retro's CRC check */
FATAL_ERROR = -128,
ENCODING = -13,
NAMETOOLONG = -12,
INSSPACE = -9,
NOENT = -8,
EXIST = -7,
BROKEN = -6,
IOERROR = -5,
NOFILE = -4,
NOCARD = -3,
WRONGDEVICE = -2,
BUSY = -1,
READY = 0
};
}
#endif // __KABU_UTIL_HPP__
#include "kabufuda/AsyncIO.hpp"
namespace kabufuda
{
AsyncIO::AsyncIO(SystemStringView filename, bool truncate)
{
m_fd = open(filename.data(), O_RDWR | O_CREAT | (truncate ? O_TRUNC : 0));
}
AsyncIO::~AsyncIO()
{
if (*this)
{
aio_cancel(m_fd, nullptr);
close(m_fd);
}
}
AsyncIO::AsyncIO(AsyncIO&& other)
{
m_fd = other.m_fd;
other.m_fd = -1;
m_queue = std::move(other.m_queue);
m_maxBlock = other.m_maxBlock;
}
AsyncIO& AsyncIO::operator=(AsyncIO&& other)
{
if (*this)
{
aio_cancel(m_fd, nullptr);
close(m_fd);
}
m_fd = other.m_fd;
other.m_fd = -1;
m_queue = std::move(other.m_queue);
m_maxBlock = other.m_maxBlock;
return *this;
}
SizeReturn AsyncIO::syncRead(void* buf, size_t length, off_t offset)
{
lseek(m_fd, offset, SEEK_SET);
return read(m_fd, buf, length);
}
bool AsyncIO::asyncRead(size_t qIdx, void* buf, size_t length, off_t offset)
{
struct aiocb& aio = m_queue[qIdx].first;
if (aio.aio_fildes)
{
#ifndef NDEBUG
fprintf(stderr, "WARNING: synchronous kabufuda fallback, check access polling\n");
#endif
const struct aiocb* aiop = &aio;
struct timespec ts = {2, 0};
while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {}
if (aio_error(&aio) == 0)
aio_return(&aio);
}
memset(&aio, 0, sizeof(struct aiocb));
aio.aio_fildes = m_fd;
aio.aio_offset = offset;
aio.aio_buf = buf;
aio.aio_nbytes = length;
m_maxBlock = std::max(m_maxBlock, qIdx + 1);
return aio_read(&aio) == 0;
}
SizeReturn AsyncIO::syncWrite(const void* buf, size_t length, off_t offset)
{
lseek(m_fd, offset, SEEK_SET);
return write(m_fd, buf, length);
}
bool AsyncIO::asyncWrite(size_t qIdx, const void* buf, size_t length, off_t offset)
{
struct aiocb& aio = m_queue[qIdx].first;
if (aio.aio_fildes)
{
#ifndef NDEBUG
fprintf(stderr, "WARNING: synchronous kabufuda fallback, check access polling\n");
#endif
const struct aiocb* aiop = &aio;
struct timespec ts = {2, 0};
while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {}
if (aio_error(&aio) == 0)
aio_return(&aio);
}
memset(&aio, 0, sizeof(struct aiocb));
aio.aio_fildes = m_fd;
aio.aio_offset = offset;
aio.aio_buf = const_cast<void*>(buf);
aio.aio_nbytes = length;
m_maxBlock = std::max(m_maxBlock, qIdx + 1);
return aio_write(&aio) == 0;
}
ECardResult AsyncIO::pollStatus(size_t qIdx, SizeReturn* szRet) const
{
auto& aio = const_cast<AsyncIO*>(this)->m_queue[qIdx];
if (aio.first.aio_fildes == 0)
{
if (szRet)
*szRet = aio.second;
return ECardResult::READY;
}
switch (aio_error(&aio.first))
{
case 0:
aio.second = aio_return(&aio.first);
aio.first.aio_fildes = 0;
if (szRet)
*szRet = aio.second;
return ECardResult::READY;
case EINPROGRESS:
return ECardResult::BUSY;
default:
return ECardResult::IOERROR;
}
}
ECardResult AsyncIO::pollStatus() const
{
ECardResult result = ECardResult::READY;
for (auto it = const_cast<AsyncIO*>(this)->m_queue.begin();
it != const_cast<AsyncIO*>(this)->m_queue.begin() + m_maxBlock;
++it)
{
auto& aio = *it;
if (aio.first.aio_fildes == 0)
continue;
switch (aio_error(&aio.first))
{
case 0:
aio.second = aio_return(&aio.first);
aio.first.aio_fildes = 0;
break;
case EINPROGRESS:
if (result > ECardResult::BUSY)
result = ECardResult::BUSY;
break;
default:
if (result > ECardResult::IOERROR)
result = ECardResult::IOERROR;
break;
}
}
if (result == ECardResult::READY)
const_cast<AsyncIO*>(this)->m_maxBlock = 0;
return result;
}
void AsyncIO::waitForCompletion() const
{
for (auto it = const_cast<AsyncIO*>(this)->m_queue.begin();
it != const_cast<AsyncIO*>(this)->m_queue.begin() + m_maxBlock;
++it)
{
auto& aio = *it;
if (aio.first.aio_fildes == 0)
continue;
switch (aio_error(&aio.first))
{
case 0:
aio.second = aio_return(&aio.first);
aio.first.aio_fildes = 0;
break;
case EINPROGRESS:
{
const struct aiocb* aiop = &aio.first;
struct timespec ts = {2, 0};
while (aio_suspend(&aiop, 1, &ts) && errno == EINTR) {}
break;
}
default:
break;
}
}
}
}
//
// Created by Jack Andersen on 2/5/18.
//
This diff is collapsed.
......@@ -23,9 +23,9 @@ int main()
mc.setCanMove(f, true);
kabufuda::CardStat stat = {};
mc.setStatus(f, stat);
mc.write(f, "Test\0", strlen("Test") + 1);
mc.asyncWrite(f, "Test\0", strlen("Test") + 1);
mc.seek(f, 32, kabufuda::SeekOrigin::Begin);
mc.write(f, "Test\0", strlen("Test") + 1);
mc.asyncWrite(f, "Test\0", strlen("Test") + 1);
}
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment