feat: async io for windows
Submit async-io to the OS. Next step is replacing all FIO calls with async-io and removing the old code.
This commit is contained in:
parent
97adb0dffa
commit
1a4a2109ca
29
docs/NOTES_aio_assets.md
Normal file
29
docs/NOTES_aio_assets.md
Normal file
@ -0,0 +1,29 @@
|
||||
# AIO & Assets
|
||||
|
||||
## Async IO:
|
||||
- Batches of loads (vy_load_batch)
|
||||
- Each load: file-id, offset-in-file, num-bytes, destination buffer
|
||||
- SubmitLoadBatch() -> Handle for whole batch or handles for each file op?
|
||||
- Reason: Better saturation of disk interface
|
||||
- Batch interacts nicely with asset system described below:
|
||||
|
||||
## Assets
|
||||
- Have a tool that detects asset dependencies (maybe the same tool that bakes assets into their runtime format)
|
||||
- i.e. world-cell -> meshes -> textures
|
||||
- Bake into a single binary (asset_meta.bin)
|
||||
- Have that file loaded at runtime at all times
|
||||
- DetermineAssetDependencies() -> CreateLoadBatch(asset-id)
|
||||
- CreateLoadBatch() could take a cache into account
|
||||
|
||||
### File Storage in Memory
|
||||
- Linked lists of block-regions with fixed size blocks (bitmap allocator)
|
||||
- E.g. 1, 2, 3, 5 mb (maybe down to kb) blocks
|
||||
- Blocks have refcounts, explicitly increased/decreased
|
||||
- Either lock the whole region or lock individual blocks
|
||||
|
||||
### Cache
|
||||
- Hold one reference to storage
|
||||
- Track how much space is taken for cache
|
||||
- Evict LRU(?) once taken space exceeds threshold
|
||||
- Copies could be managers handed to the cache
|
||||
|
@ -50,6 +50,8 @@ runtime_lib = library('vyrt',
|
||||
'src/runtime/app.h',
|
||||
'src/runtime/dynamic_libs.h',
|
||||
'src/runtime/jobs.h',
|
||||
'src/runtime/aio.h',
|
||||
'src/runtime/file_tab.h',
|
||||
|
||||
'src/runtime/error_report.c',
|
||||
'src/runtime/gfx_main.c',
|
||||
@ -63,6 +65,8 @@ runtime_lib = library('vyrt',
|
||||
'src/runtime/app.c',
|
||||
'src/runtime/dynamic_libs.c',
|
||||
'src/runtime/jobs.c',
|
||||
'src/runtime/aio.c',
|
||||
'src/runtime/file_tab.c',
|
||||
|
||||
# Contrib Sources
|
||||
'contrib/xxhash/xxhash.c',
|
||||
|
247
src/runtime/aio.c
Normal file
247
src/runtime/aio.c
Normal file
@ -0,0 +1,247 @@
|
||||
#include "aio.h"
|
||||
#include "threading.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Maintain a ringbuffer of pending operations */
|
||||
|
||||
|
||||
typedef struct {
|
||||
#ifdef _WIN32
|
||||
HANDLE file_handle;
|
||||
OVERLAPPED overlapped;
|
||||
#endif
|
||||
volatile vy_aio_state state;
|
||||
} vy_aio;
|
||||
|
||||
typedef struct {
|
||||
vy_mutex *guard;
|
||||
|
||||
vy_aio *storage;
|
||||
uint32_t capacity;
|
||||
uint32_t head;
|
||||
uint32_t tail;
|
||||
} vy_aio_ringbuffer;
|
||||
|
||||
typedef struct {
|
||||
vy_aio *a;
|
||||
vy_aio *b;
|
||||
uint32_t a_count;
|
||||
} vy_ringbuffer_space;
|
||||
|
||||
static vy_aio_ringbuffer _ringbuffer;
|
||||
|
||||
static vy_ringbuffer_space ReserveRingbufferSpace(uint32_t count) {
|
||||
if (!vyLockMutex(_ringbuffer.guard)) {
|
||||
vy_ringbuffer_space failed = {NULL, NULL, 0};
|
||||
return failed;
|
||||
}
|
||||
|
||||
vy_ringbuffer_space result = {NULL, NULL, 0};
|
||||
|
||||
if (_ringbuffer.head >= _ringbuffer.tail) {
|
||||
if (_ringbuffer.head + count <= _ringbuffer.capacity) {
|
||||
result.a_count = count;
|
||||
result.a = &_ringbuffer.storage[_ringbuffer.head];
|
||||
_ringbuffer.head =
|
||||
(_ringbuffer.head + count) % _ringbuffer.capacity;
|
||||
} else {
|
||||
/* Check if enough space is free at the end */
|
||||
uint32_t a_count = _ringbuffer.capacity - _ringbuffer.head;
|
||||
uint32_t b_count = count - a_count;
|
||||
|
||||
if (b_count <= _ringbuffer.tail) {
|
||||
result.a_count = a_count;
|
||||
result.a = &_ringbuffer.storage[_ringbuffer.head];
|
||||
result.b = &_ringbuffer.storage[0];
|
||||
_ringbuffer.head = b_count;
|
||||
} else {
|
||||
/* Not enough space, we would overwrite the tail */
|
||||
vyLog("aio", "Ringbuffer is full.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Head is lower than tail */
|
||||
uint32_t num_free = _ringbuffer.tail - _ringbuffer.head;
|
||||
if (count < num_free) {
|
||||
result.a_count = count;
|
||||
result.a = &_ringbuffer.storage[_ringbuffer.head];
|
||||
_ringbuffer.head =
|
||||
(_ringbuffer.head + count) % _ringbuffer.capacity;
|
||||
} else {
|
||||
/* Not enough space, we would overwrite the tail */
|
||||
vyLog("aio", "Ringbuffer is full.");
|
||||
}
|
||||
}
|
||||
|
||||
vyUnlockMutex(_ringbuffer.guard);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static void win32CompletionRoutine(DWORD error_code,
|
||||
DWORD num_bytes_transfered,
|
||||
LPOVERLAPPED overlapped) {
|
||||
vy_aio *op = (vy_aio*)overlapped->hEvent;
|
||||
assert(op->state == VY_AIO_STATE_PENDING);
|
||||
|
||||
if (error_code != ERROR_SUCCESS) {
|
||||
op->state = VY_AIO_STATE_FAILED;
|
||||
vyLog("aio", "Async io failed: %u", error_code);
|
||||
} else {
|
||||
op->state = VY_AIO_STATE_FINISHED;
|
||||
}
|
||||
|
||||
CloseHandle(op->file_handle);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
VY_DLLEXPORT vy_result vyInitAIO(unsigned int max_concurrent_operations) {
|
||||
_ringbuffer.guard = vyCreateMutex();
|
||||
if (!_ringbuffer.guard) {
|
||||
return VY_AIO_OUT_OF_MEMORY;
|
||||
}
|
||||
if (max_concurrent_operations == 0)
|
||||
max_concurrent_operations = 1024;
|
||||
|
||||
_ringbuffer.storage = calloc(max_concurrent_operations, sizeof(vy_aio));
|
||||
if (!_ringbuffer.storage)
|
||||
return VY_AIO_OUT_OF_MEMORY;
|
||||
_ringbuffer.head = 0;
|
||||
_ringbuffer.tail = 0;
|
||||
_ringbuffer.capacity = max_concurrent_operations;
|
||||
return VY_SUCCESS;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyShutdownAIO(void) {
|
||||
vyDestroyMutex(_ringbuffer.guard);
|
||||
free(_ringbuffer.storage);
|
||||
_ringbuffer.capacity = 0;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_result vySubmitLoadBatch(const vy_load_batch *batch,
|
||||
vy_aio_handle *handles) {
|
||||
if (batch->num_loads > VY_LOAD_BATCH_MAX_SIZE) {
|
||||
return VY_AIO_LOAD_TOO_LARGE;
|
||||
}
|
||||
|
||||
vy_ringbuffer_space rbspace = ReserveRingbufferSpace(batch->num_loads);
|
||||
if (!rbspace.a) {
|
||||
vyReportError("aio", "Too many pending file operations");
|
||||
return VY_AIO_TOO_MANY_OPERATIONS;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < batch->num_loads; ++i) {
|
||||
vy_aio *op = (i < rbspace.a_count) ? &rbspace.a[i]
|
||||
: &rbspace.b[i - rbspace.a_count];
|
||||
op->state = VY_AIO_STATE_PENDING;
|
||||
const char *file_path = vyGetFilePath(batch->loads[i].file);
|
||||
if (!file_path) {
|
||||
vyReportError("aio",
|
||||
"Failed to resolve file path for a batched load");
|
||||
op->state = VY_AIO_STATE_INVALID;
|
||||
handles[i] = VY_AIO_INVALID_HANDLE;
|
||||
continue;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
op->overlapped = (OVERLAPPED){
|
||||
/* ReadFileEx does not use hEvent and we are free to use it for our own purposes. */
|
||||
.hEvent = (HANDLE)(op),
|
||||
.Internal = 0,
|
||||
.InternalHigh = 0,
|
||||
.Offset = (DWORD)(batch->loads[i].offset & MAXDWORD),
|
||||
.OffsetHigh = (DWORD)(batch->loads[i].offset >> 32),
|
||||
.Pointer = NULL,
|
||||
};
|
||||
|
||||
WCHAR wpath[MAX_PATH];
|
||||
if (MultiByteToWideChar(CP_UTF8,
|
||||
MB_PRECOMPOSED,
|
||||
file_path,
|
||||
-1,
|
||||
wpath,
|
||||
VY_ARRAY_COUNT(wpath)) == 0) {
|
||||
vyReportError("aio",
|
||||
"MultiByteToWideChar failed with error code: %u",
|
||||
GetLastError());
|
||||
op->state = VY_AIO_STATE_FINISHED;
|
||||
handles[i] = VY_AIO_INVALID_HANDLE;
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE file_handle = CreateFileW(wpath,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
vyReportError("aio",
|
||||
"CreateFileW failed for file: %s with error code: %u",
|
||||
file_path,
|
||||
GetLastError());
|
||||
op->state = VY_AIO_STATE_INVALID;
|
||||
handles[i] = VY_AIO_INVALID_HANDLE;
|
||||
continue;
|
||||
}
|
||||
op->file_handle = file_handle;
|
||||
BOOL result = ReadFileEx(file_handle,
|
||||
batch->loads[i].dest,
|
||||
(DWORD)batch->loads[i].num_bytes,
|
||||
&op->overlapped,
|
||||
win32CompletionRoutine);
|
||||
DWORD err = GetLastError();
|
||||
if (!result || err != ERROR_SUCCESS) {
|
||||
vyReportError("aio", "ReadFileEx failed with error code: %u", err);
|
||||
op->state = VY_AIO_STATE_FINISHED;
|
||||
handles[i] = VY_AIO_INVALID_HANDLE;
|
||||
CloseHandle(file_handle);
|
||||
op->file_handle = NULL;
|
||||
}
|
||||
|
||||
/* Handle is the index into the ringbuffer + 1 */
|
||||
ptrdiff_t op_idx = op - _ringbuffer.storage;
|
||||
handles[i] = (uint32_t)op_idx + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
return VY_SUCCESS;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT volatile vy_aio_state vyGetAIOState(vy_aio_handle handle) {
|
||||
if (handle == VY_AIO_INVALID_HANDLE || handle > _ringbuffer.capacity)
|
||||
return VY_AIO_STATE_INVALID;
|
||||
#ifdef _WIN32
|
||||
/* Give the compation function an opportunity to run */
|
||||
SleepEx(0, TRUE);
|
||||
#endif
|
||||
vyLockMutex(_ringbuffer.guard);
|
||||
vy_aio_state state = _ringbuffer.storage[handle - 1].state;
|
||||
vyUnlockMutex(_ringbuffer.guard);
|
||||
return state;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyReleaseAIO(vy_aio_handle handle) {
|
||||
if (handle == VY_AIO_INVALID_HANDLE || handle > _ringbuffer.capacity) {
|
||||
return;
|
||||
}
|
||||
vyLockMutex(_ringbuffer.guard);
|
||||
_ringbuffer.storage[handle - 1].state = VY_AIO_STATE_INVALID;
|
||||
if (handle - 1 == _ringbuffer.tail) {
|
||||
/* Advance the tail such that it points to the last used slot. (Or to head, if the ringbuffer is now empty) */
|
||||
uint32_t i = _ringbuffer.tail;
|
||||
while ((_ringbuffer.storage[i].state == VY_AIO_STATE_INVALID) && i != _ringbuffer.head) {
|
||||
i = (i + 1) % _ringbuffer.capacity;
|
||||
}
|
||||
_ringbuffer.tail = i;
|
||||
}
|
||||
vyUnlockMutex(_ringbuffer.guard);
|
||||
}
|
66
src/runtime/aio.h
Normal file
66
src/runtime/aio.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef VY_AIO_H
|
||||
#define VY_AIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "runtime.h"
|
||||
#include "file_tab.h"
|
||||
|
||||
typedef struct {
|
||||
size_t offset; /**< Starting offset inside the file in bytes */
|
||||
size_t num_bytes; /**< Number of bytes to load */
|
||||
/** Destination buffer with at least @ num_bytes bytes.
|
||||
* Must be valid until the load is finished.
|
||||
*/
|
||||
void *dest;
|
||||
vy_file_id file; /**< Unique identifier for the file */
|
||||
} vy_file_load;
|
||||
|
||||
#define VY_LOAD_BATCH_MAX_SIZE 64
|
||||
|
||||
/** A batch of loads that will be started together.
|
||||
*
|
||||
* The aio system will hand these to the OS.
|
||||
*/
|
||||
typedef struct {
|
||||
vy_file_load loads[VY_LOAD_BATCH_MAX_SIZE];
|
||||
|
||||
/** Must be smaller or equal to @c VY_LOAD_BATCH_MAX_SIZE */
|
||||
unsigned int num_loads;
|
||||
} vy_load_batch;
|
||||
|
||||
|
||||
#define VY_AIO_INVALID_HANDLE 0
|
||||
|
||||
/** Handle for an async io operation. Can be used to query the state and result. */
|
||||
typedef uint32_t vy_aio_handle;
|
||||
|
||||
enum {
|
||||
VY_AIO_LOAD_TOO_LARGE = (VY_SUCCESS + 1),
|
||||
VY_AIO_TOO_MANY_OPERATIONS,
|
||||
VY_AIO_OUT_OF_MEMORY,
|
||||
};
|
||||
|
||||
|
||||
typedef enum {
|
||||
VY_AIO_STATE_INVALID,
|
||||
VY_AIO_STATE_PENDING,
|
||||
VY_AIO_STATE_FINISHED,
|
||||
VY_AIO_STATE_FAILED,
|
||||
} vy_aio_state;
|
||||
|
||||
VY_DLLEXPORT vy_result vyInitAIO(unsigned int max_concurrent_operations);
|
||||
|
||||
VY_DLLEXPORT void vyShutdownAIO(void);
|
||||
|
||||
VY_DLLEXPORT vy_result vySubmitLoadBatch(const vy_load_batch *batch,
|
||||
vy_aio_handle *handles);
|
||||
|
||||
VY_DLLEXPORT volatile vy_aio_state vyGetAIOState(vy_aio_handle handle);
|
||||
|
||||
/* Releases the internal storage for the operation.
|
||||
* The system is allowed to re-use the same handle value for new operations after this was called.
|
||||
*/
|
||||
VY_DLLEXPORT void vyReleaseAIO(vy_aio_handle handle);
|
||||
|
||||
#endif
|
@ -2,6 +2,7 @@
|
||||
#include "config.h"
|
||||
#include "fio.h"
|
||||
#include "gfx.h"
|
||||
#include "aio.h"
|
||||
#include "renderer_api.h"
|
||||
|
||||
extern void __RegisterRuntimeCVars(void);
|
||||
@ -43,6 +44,16 @@ VY_DLLEXPORT int vyWin32Entry(HINSTANCE hInstance,
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (vyInitFileTab(1024) != VY_SUCCESS) {
|
||||
vyReportError("FTAB", "Init failed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (vyInitAIO(0) != VY_SUCCESS) {
|
||||
vyReportError("AIO", "Init failed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
WNDCLASSEXW wndclass = {
|
||||
.cbSize = sizeof(wndclass),
|
||||
.hInstance = hInstance,
|
||||
|
131
src/runtime/file_tab.c
Normal file
131
src/runtime/file_tab.c
Normal file
@ -0,0 +1,131 @@
|
||||
#include "file_tab.h"
|
||||
#include "threading.h"
|
||||
|
||||
#include <xxhash/xxhash.h>
|
||||
|
||||
#define NAME_CAP(cap) ((cap)*128)
|
||||
typedef struct {
|
||||
vy_file_id *ids;
|
||||
unsigned int *name_offsets;
|
||||
char *names;
|
||||
unsigned int capacity;
|
||||
unsigned int name_head;
|
||||
|
||||
vy_mutex *mutex;
|
||||
} vy_file_tab;
|
||||
|
||||
static vy_file_tab _file_tab;
|
||||
|
||||
vy_result vyInitFileTab(unsigned int max_files) {
|
||||
_file_tab.ids = calloc(max_files, sizeof(vy_file_id));
|
||||
if (!_file_tab.ids)
|
||||
return 1;
|
||||
_file_tab.name_offsets = calloc(max_files, sizeof(unsigned int));
|
||||
if (!_file_tab.name_offsets)
|
||||
return 1;
|
||||
_file_tab.names = malloc(NAME_CAP(max_files));
|
||||
if (!_file_tab.names)
|
||||
return 1;
|
||||
|
||||
_file_tab.capacity = max_files;
|
||||
_file_tab.name_head = 0;
|
||||
_file_tab.mutex = vyCreateMutex();
|
||||
return VY_SUCCESS;
|
||||
}
|
||||
|
||||
void vyShutdownFileTab(void) {
|
||||
free(_file_tab.ids);
|
||||
free(_file_tab.names);
|
||||
free(_file_tab.name_offsets);
|
||||
vyDestroyMutex(_file_tab.mutex);
|
||||
}
|
||||
|
||||
|
||||
vy_file_id vyGetFileId(const char *path) {
|
||||
vy_text_span span;
|
||||
span.start = path;
|
||||
span.length = (unsigned int)strlen(path);
|
||||
return vyGetFileIdFromSpan(span);
|
||||
}
|
||||
|
||||
vy_file_id vyGetFileIdFromSpan(vy_text_span path) {
|
||||
/* Randomly choosen, aka finger smash keyboard */
|
||||
XXH64_hash_t seed = 15340978;
|
||||
vy_file_id fid = (vy_file_id)XXH64(path.start, path.length, seed);
|
||||
if (fid == 0)
|
||||
fid = ~fid;
|
||||
return fid;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFileFromSpan(vy_text_span path) {
|
||||
vy_file_id fid = vyGetFileIdFromSpan(path);
|
||||
|
||||
if (!vyLockMutex(_file_tab.mutex)) {
|
||||
vyReportError("fio", "Failed to lock the guard mutex.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Hash Insert */
|
||||
unsigned int i = 0;
|
||||
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
|
||||
while (i < _file_tab.capacity) {
|
||||
unsigned int at = (base + i) % _file_tab.capacity;
|
||||
if (_file_tab.ids[at] == 0) {
|
||||
/* Insert */
|
||||
unsigned int slen = (unsigned int)path.length + 1;
|
||||
if ((_file_tab.name_head + slen) >= NAME_CAP(_file_tab.capacity)) {
|
||||
/* Out of name storage */
|
||||
fid = 0;
|
||||
break;
|
||||
}
|
||||
memcpy(_file_tab.names + _file_tab.name_head, path.start, slen);
|
||||
_file_tab.name_offsets[at] = _file_tab.name_head;
|
||||
_file_tab.ids[at] = fid;
|
||||
_file_tab.name_head += slen;
|
||||
break;
|
||||
} else if (_file_tab.ids[at] == fid) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
/* Out of space */
|
||||
if (i == _file_tab.capacity)
|
||||
fid = 0;
|
||||
|
||||
vyUnlockMutex(_file_tab.mutex);
|
||||
return fid;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFile(const char *path) {
|
||||
vy_text_span span;
|
||||
span.start = path;
|
||||
span.length = (unsigned int)strlen(path);
|
||||
return vyAddFileFromSpan(span);
|
||||
}
|
||||
|
||||
VY_DLLEXPORT const char *vyGetFilePath(vy_file_id fid) {
|
||||
/* Hash Lookup */
|
||||
if (fid == 0)
|
||||
return NULL;
|
||||
|
||||
if (!vyLockMutex(_file_tab.mutex)) {
|
||||
vyReportError("fio", "Failed to lock the guard mutex.");
|
||||
return 0;
|
||||
}
|
||||
const char *result = NULL;
|
||||
unsigned int i = 0;
|
||||
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
|
||||
while (i < _file_tab.capacity) {
|
||||
unsigned int at = (base + i) % _file_tab.capacity;
|
||||
if (_file_tab.ids[at] == fid) {
|
||||
result = _file_tab.names + _file_tab.name_offsets[at];
|
||||
break;
|
||||
} else if (_file_tab.ids[at] == 0) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
vyUnlockMutex(_file_tab.mutex);
|
||||
return result;
|
||||
}
|
26
src/runtime/file_tab.h
Normal file
26
src/runtime/file_tab.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef VY_FILE_TAB_H
|
||||
#define VY_FILE_TAB_H
|
||||
|
||||
#include "runtime.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* used to identify a file (XXH3 hash of the path) */
|
||||
typedef uint64_t vy_file_id;
|
||||
|
||||
VY_DLLEXPORT vy_result vyInitFileTab(unsigned int max_files);
|
||||
|
||||
VY_DLLEXPORT void vyShutdownFileTab(void);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyGetFileId(const char *path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyGetFileIdFromSpan(vy_text_span path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFile(const char *path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFileFromSpan(vy_text_span path);
|
||||
|
||||
VY_DLLEXPORT const char *vyGetFilePath(vy_file_id fid);
|
||||
|
||||
|
||||
#endif
|
@ -4,8 +4,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <xxhash/xxhash.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
#elif defined(_WIN32)
|
||||
@ -38,23 +36,7 @@ typedef struct {
|
||||
#endif
|
||||
} vy_fio_queue;
|
||||
|
||||
#define NAME_CAP(cap) ((cap)*128)
|
||||
typedef struct {
|
||||
vy_file_id *ids;
|
||||
unsigned int *name_offsets;
|
||||
char *names;
|
||||
unsigned int capacity;
|
||||
unsigned int name_head;
|
||||
|
||||
#ifdef __linux__
|
||||
pthread_mutex_t mutex;
|
||||
#elif defined(_WIN32)
|
||||
HANDLE mutex;
|
||||
#endif
|
||||
} vy_file_tab;
|
||||
|
||||
static vy_fio_queue _queue;
|
||||
static vy_file_tab _file_tab;
|
||||
|
||||
#ifdef __linux__
|
||||
static pthread_t _thread;
|
||||
@ -96,43 +78,6 @@ static void ShutdownFIOQueue(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool InitFileTab(unsigned int max_files) {
|
||||
_file_tab.ids = calloc(max_files, sizeof(vy_file_id));
|
||||
if (!_file_tab.ids)
|
||||
return false;
|
||||
_file_tab.name_offsets = calloc(max_files, sizeof(unsigned int));
|
||||
if (!_file_tab.name_offsets)
|
||||
return false;
|
||||
_file_tab.names = malloc(NAME_CAP(max_files));
|
||||
if (!_file_tab.names)
|
||||
return false;
|
||||
|
||||
_file_tab.capacity = max_files;
|
||||
_file_tab.name_head = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_init(&_file_tab.mutex, NULL) != 0)
|
||||
return false;
|
||||
#elif defined(_WIN32)
|
||||
_file_tab.mutex = CreateMutex(NULL, FALSE, NULL);
|
||||
if (!_file_tab.mutex)
|
||||
return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ShutdownFileTab(void) {
|
||||
free(_file_tab.ids);
|
||||
free(_file_tab.names);
|
||||
free(_file_tab.name_offsets);
|
||||
|
||||
#ifdef __linux__
|
||||
pthread_mutex_destroy(&_file_tab.mutex);
|
||||
#elif defined(_WIN32)
|
||||
CloseHandle(_file_tab.mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static void *linuxFIOThreadProc(void *);
|
||||
#elif defined(_WIN32)
|
||||
@ -141,13 +86,8 @@ static DWORD WINAPI win32FIOThreadProc(_In_ LPVOID);
|
||||
|
||||
VY_DLLEXPORT bool vyInitFIO(const vy_fio_config *config) {
|
||||
unsigned int queue_size = (config->queue_size) ? config->queue_size : 512;
|
||||
unsigned int max_file_count =
|
||||
(config->max_file_count) ? config->max_file_count : 512;
|
||||
|
||||
if (!InitFIOQueue(queue_size))
|
||||
return false;
|
||||
if (!InitFileTab(max_file_count))
|
||||
return false;
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_create(&_thread, NULL, linuxFIOThreadProc, NULL) != 0)
|
||||
@ -180,110 +120,6 @@ VY_DLLEXPORT void vyShutdownFIO(void) {
|
||||
}
|
||||
#endif
|
||||
ShutdownFIOQueue();
|
||||
ShutdownFileTab();
|
||||
}
|
||||
|
||||
vy_file_id vyGetFileId(const char *path) {
|
||||
vy_text_span span;
|
||||
span.start = path;
|
||||
span.length = (unsigned int)strlen(path);
|
||||
return vyGetFileIdFromSpan(span);
|
||||
}
|
||||
|
||||
vy_file_id vyGetFileIdFromSpan(vy_text_span path) {
|
||||
/* Randomly choosen, aka finger smash keyboard */
|
||||
XXH64_hash_t seed = 15340978;
|
||||
vy_file_id fid = (vy_file_id)XXH64(path.start, path.length, seed);
|
||||
if (fid == 0)
|
||||
fid = ~fid;
|
||||
return fid;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFileFromSpan(vy_text_span path) {
|
||||
vy_file_id fid = vyGetFileIdFromSpan(path);
|
||||
|
||||
#ifdef __linux__
|
||||
pthread_mutex_lock(&_file_tab.mutex);
|
||||
#elif defined(_WIN32)
|
||||
if (WaitForSingleObject(_file_tab.mutex, INFINITE) == WAIT_FAILED) {
|
||||
vyReportError("fio", "WaitForSingleObject failed with WAIT_FAILED!");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
/* Hash Insert */
|
||||
unsigned int i = 0;
|
||||
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
|
||||
while (i < _file_tab.capacity) {
|
||||
unsigned int at = (base + i) % _file_tab.capacity;
|
||||
if (_file_tab.ids[at] == 0) {
|
||||
/* Insert */
|
||||
unsigned int slen = (unsigned int)path.length + 1;
|
||||
if ((_file_tab.name_head + slen) >= NAME_CAP(_file_tab.capacity)) {
|
||||
/* Out of name storage */
|
||||
fid = 0;
|
||||
break;
|
||||
}
|
||||
memcpy(_file_tab.names + _file_tab.name_head, path.start, slen);
|
||||
_file_tab.name_offsets[at] = _file_tab.name_head;
|
||||
_file_tab.ids[at] = fid;
|
||||
_file_tab.name_head += slen;
|
||||
break;
|
||||
} else if (_file_tab.ids[at] == fid) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
/* Out of space */
|
||||
if (i == _file_tab.capacity)
|
||||
fid = 0;
|
||||
|
||||
#ifdef __linux__
|
||||
pthread_mutex_unlock(&_file_tab.mutex);
|
||||
#elif defined(_WIN32)
|
||||
ReleaseMutex(_file_tab.mutex);
|
||||
#endif
|
||||
return fid;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFile(const char *path) {
|
||||
vy_text_span span;
|
||||
span.start = path;
|
||||
span.length = (unsigned int)strlen(path);
|
||||
return vyAddFileFromSpan(span);
|
||||
}
|
||||
|
||||
VY_DLLEXPORT const char *vyGetFilePath(vy_file_id fid) {
|
||||
/* Hash Lookup */
|
||||
if (fid == 0)
|
||||
return NULL;
|
||||
#ifdef __linux__
|
||||
pthread_mutex_lock(&_file_tab.mutex);
|
||||
#elif defined(_WIN32)
|
||||
if (WaitForSingleObject(_file_tab.mutex, INFINITE) == WAIT_FAILED) {
|
||||
vyReportError("fio", "WaitForSingleObject failed with WAIT_FAILED!");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
const char *result = NULL;
|
||||
unsigned int i = 0;
|
||||
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
|
||||
while (i < _file_tab.capacity) {
|
||||
unsigned int at = (base + i) % _file_tab.capacity;
|
||||
if (_file_tab.ids[at] == fid) {
|
||||
result = _file_tab.names + _file_tab.name_offsets[at];
|
||||
break;
|
||||
} else if (_file_tab.ids[at] == 0) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
#ifdef __linux__
|
||||
pthread_mutex_unlock(&_file_tab.mutex);
|
||||
#elif defined(_WIN32)
|
||||
ReleaseMutex(_file_tab.mutex);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_fio_handle vyEnqueueRead(vy_file_id fid) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "runtime.h"
|
||||
#include "file_tab.h"
|
||||
|
||||
enum {
|
||||
VY_FILE_BUFFER_FLAG_FILE_NOT_FOUND = 0x1,
|
||||
@ -22,8 +23,6 @@ static inline bool vyWasFileBufferSuccessful(const vy_file_buffer *fb) {
|
||||
return fb->flags == 0;
|
||||
}
|
||||
|
||||
/* used to identify a file (XXH3 hash of the path) */
|
||||
typedef uint64_t vy_file_id;
|
||||
|
||||
typedef unsigned int vy_fio_handle;
|
||||
|
||||
@ -36,16 +35,6 @@ VY_DLLEXPORT bool vyInitFIO(const vy_fio_config *config);
|
||||
|
||||
VY_DLLEXPORT void vyShutdownFIO(void);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyGetFileId(const char *path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyGetFileIdFromSpan(vy_text_span path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFile(const char *path);
|
||||
|
||||
VY_DLLEXPORT vy_file_id vyAddFileFromSpan(vy_text_span path);
|
||||
|
||||
VY_DLLEXPORT const char *vyGetFilePath(vy_file_id fid);
|
||||
|
||||
VY_DLLEXPORT vy_fio_handle vyEnqueueRead(vy_file_id fid);
|
||||
|
||||
VY_DLLEXPORT void vyAbortFIO(vy_fio_handle fio);
|
||||
|
@ -65,4 +65,90 @@ VY_DLLEXPORT void vyWaitOnConditionVar(vy_condition_var *var) {
|
||||
pthread_cond_wait(&var->cond, &var->mutex);
|
||||
}
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
struct vy_condition_var_s {
|
||||
CRITICAL_SECTION critical_section;
|
||||
CONDITION_VARIABLE cond;
|
||||
ptrdiff_t next_reusable;
|
||||
};
|
||||
|
||||
#define MAX_CONDS 1024
|
||||
vy_condition_var _conds[MAX_CONDS];
|
||||
static ptrdiff_t _first_reusable = MAX_CONDS;
|
||||
static ptrdiff_t _next = 0;
|
||||
static HANDLE _guard;
|
||||
static INIT_ONCE _guard_init = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static BOOL CALLBACK InitGuardFn(PINIT_ONCE initOnce,
|
||||
PVOID parameter,
|
||||
PVOID *context) {
|
||||
VY_UNUSED(initOnce);
|
||||
VY_UNUSED(parameter);
|
||||
VY_UNUSED(context);
|
||||
|
||||
_guard = CreateMutexW(NULL, FALSE, NULL);
|
||||
return _guard != NULL;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_condition_var *vyCreateConditionVar(void) {
|
||||
if (!InitOnceExecuteOnce(&_guard_init, InitGuardFn, NULL, NULL)) {
|
||||
vyReportError("core", "Failed to initialize the guard mutex.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (WaitForSingleObjectEx(_guard, INFINITE, TRUE) != WAIT_OBJECT_0) {
|
||||
vyLog("core", "Failed to lock the guard variable: %u", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
if (_first_reusable < MAX_CONDS) {
|
||||
vy_condition_var *cond = &_conds[_first_reusable];
|
||||
_first_reusable = cond->next_reusable;
|
||||
ReleaseMutex(_guard);
|
||||
return cond;
|
||||
} else if (_next < MAX_CONDS) {
|
||||
vy_condition_var *cond = &_conds[_next];
|
||||
if (!InitializeCriticalSectionAndSpinCount(&cond->critical_section, 4000)) {
|
||||
vyLog("core", "Condition variable creation failed");
|
||||
ReleaseMutex(_guard);
|
||||
return NULL;
|
||||
}
|
||||
InitializeConditionVariable(&cond->cond);
|
||||
cond->next_reusable = MAX_CONDS;
|
||||
++_next;
|
||||
ReleaseMutex(_guard);
|
||||
return cond;
|
||||
}
|
||||
vyReportError("core", "Ran out of condition variable objects");
|
||||
ReleaseMutex(_guard);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyDestroyConditionVar(vy_condition_var *var) {
|
||||
ptrdiff_t index = var - &_conds[0];
|
||||
if (WaitForSingleObjectEx(_guard, INFINITE, TRUE) != WAIT_OBJECT_0) {
|
||||
vyLog("core", "Failed to lock the guard variable: %u", GetLastError());
|
||||
return;
|
||||
}
|
||||
var->next_reusable = _first_reusable;
|
||||
_first_reusable = index;
|
||||
ReleaseMutex(_guard);
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyLockConditionVar(vy_condition_var *var) {
|
||||
EnterCriticalSection(&var->critical_section);
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyUnlockConditionVar(vy_condition_var *var, bool signal) {
|
||||
LeaveCriticalSection(&var->critical_section);
|
||||
if (signal)
|
||||
WakeAllConditionVariable(&var->cond);
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyWaitOnConditionVar(vy_condition_var *var) {
|
||||
SleepConditionVariableCS(&var->cond, &var->critical_section, INFINITE);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -14,8 +14,31 @@ struct vy_mutex_s {
|
||||
static vy_mutex _mutex[MAX_MUTEX];
|
||||
static ptrdiff_t _first_reusable = MAX_MUTEX;
|
||||
static ptrdiff_t _next = 0;
|
||||
static HANDLE _guard;
|
||||
static INIT_ONCE _guard_init = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static BOOL CALLBACK InitGuardFn(PINIT_ONCE initOnce,
|
||||
PVOID parameter,
|
||||
PVOID *context) {
|
||||
VY_UNUSED(initOnce);
|
||||
VY_UNUSED(parameter);
|
||||
VY_UNUSED(context);
|
||||
|
||||
_guard = CreateMutexW(NULL, FALSE, NULL);
|
||||
return _guard != NULL;
|
||||
}
|
||||
|
||||
|
||||
VY_DLLEXPORT vy_mutex *vyCreateMutex(void) {
|
||||
if (!InitOnceExecuteOnce(&_guard_init, InitGuardFn, NULL, NULL)) {
|
||||
vyReportError("core", "Failed to initialize the guard mutex.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (WaitForSingleObjectEx(_guard, INFINITE, TRUE) != WAIT_OBJECT_0) {
|
||||
vyLog("core", "Failed to lock the guard variable: %u", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
if (_first_reusable < MAX_MUTEX) {
|
||||
vy_mutex *mtx = &_mutex[_first_reusable];
|
||||
_first_reusable = mtx->next_reusable;
|
||||
@ -42,9 +65,10 @@ VY_DLLEXPORT void vyDestroyMutex(vy_mutex *mutex) {
|
||||
}
|
||||
|
||||
VY_DLLEXPORT bool vyLockMutex(vy_mutex *mutex) {
|
||||
return WaitForSingleObject(mutex->handle, INFINITE) == WAIT_OBJECT_0;
|
||||
return WaitForSingleObjectEx(mutex->handle, INFINITE, TRUE) == WAIT_OBJECT_0;
|
||||
}
|
||||
v VY_DLLEXPORT bool vyUnlockMutex(vy_mutex *mutex) {
|
||||
|
||||
VY_DLLEXPORT bool vyUnlockMutex(vy_mutex *mutex) {
|
||||
return ReleaseMutex(mutex->handle) != 0;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,104 @@
|
||||
#include "threading.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
struct vy_thread_s {
|
||||
HANDLE handle;
|
||||
|
||||
ptrdiff_t next_reusable;
|
||||
|
||||
vy_thread_entry_fn *entry;
|
||||
void *param;
|
||||
|
||||
bool needs_join;
|
||||
};
|
||||
|
||||
#define MAX_THREADS 256
|
||||
static vy_thread _threads[MAX_THREADS];
|
||||
static ptrdiff_t _first_reusable = MAX_THREADS;
|
||||
static ptrdiff_t _next = 0;
|
||||
|
||||
static HANDLE _guard;
|
||||
static INIT_ONCE _guard_init = INIT_ONCE_STATIC_INIT;
|
||||
|
||||
static BOOL CALLBACK InitGuardFn(PINIT_ONCE initOnce,
|
||||
PVOID parameter,
|
||||
PVOID *context) {
|
||||
VY_UNUSED(initOnce);
|
||||
VY_UNUSED(parameter);
|
||||
VY_UNUSED(context);
|
||||
|
||||
_guard = CreateMutexW(NULL, FALSE, NULL);
|
||||
return _guard != NULL;
|
||||
}
|
||||
|
||||
static DWORD WINAPI win32ThreadWrapper(LPVOID arg) {
|
||||
vy_thread *user_thread = arg;
|
||||
user_thread->needs_join = false;
|
||||
user_thread->entry(user_thread->param);
|
||||
user_thread->needs_join = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT vy_thread *vySpawnThread(vy_thread_entry_fn *entry, void *param) {
|
||||
|
||||
if (!InitOnceExecuteOnce(&_guard_init, InitGuardFn, NULL, NULL)) {
|
||||
vyReportError("core", "Failed to initialize the guard mutex.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vy_thread *thrd = NULL;
|
||||
if (WaitForSingleObject(_guard, INFINITE) != WAIT_OBJECT_0) {
|
||||
vyLog("core", "Failed to lock the guard variable: %u", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
if (_first_reusable < MAX_THREADS) {
|
||||
thrd = &_threads[_first_reusable];
|
||||
_first_reusable = thrd->next_reusable;
|
||||
if (thrd->needs_join) {
|
||||
WaitForSingleObject(thrd->handle, INFINITE);
|
||||
CloseHandle(thrd->handle);
|
||||
thrd->needs_join = false;
|
||||
}
|
||||
} else if (_next < MAX_THREADS) {
|
||||
thrd = &_threads[_next];
|
||||
thrd->next_reusable = MAX_THREADS;
|
||||
++_next;
|
||||
}
|
||||
if (thrd) {
|
||||
thrd->entry = entry;
|
||||
thrd->param = param;
|
||||
thrd->handle = CreateThread(NULL, 0, win32ThreadWrapper, (LPVOID)thrd, 0, NULL);
|
||||
if (thrd->handle == NULL) {
|
||||
vyLog("core", "Thread creation failed");
|
||||
thrd = NULL;
|
||||
}
|
||||
} else {
|
||||
vyReportError("core", "Ran out of thread objects");
|
||||
}
|
||||
ReleaseMutex(_guard);
|
||||
return thrd;
|
||||
}
|
||||
|
||||
VY_DLLEXPORT void vyJoinThread(vy_thread *thread) {
|
||||
WaitForSingleObject(thread->handle, INFINITE);
|
||||
CloseHandle(thread->handle);
|
||||
thread->needs_join = false;
|
||||
ptrdiff_t index = thread - &_threads[0];
|
||||
|
||||
if (WaitForSingleObject(_guard, INFINITE) != WAIT_OBJECT_0) {
|
||||
vyLog("core", "Failed to lock the guard variable: %u", GetLastError());
|
||||
return;
|
||||
}
|
||||
thread->next_reusable = _first_reusable;
|
||||
_first_reusable = index;
|
||||
ReleaseMutex(_guard);
|
||||
}
|
||||
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#include <pthread.h>
|
||||
@ -51,7 +149,7 @@ VY_DLLEXPORT vy_thread *vySpawnThread(vy_thread_entry_fn *entry, void *param) {
|
||||
thrd->param = param;
|
||||
if (pthread_create(&thrd->handle, NULL, linuxThreadWrapper, thrd) !=
|
||||
0) {
|
||||
vyLog("core", "Mutex creation failed");
|
||||
vyLog("core", "Thread creation failed");
|
||||
thrd = NULL;
|
||||
}
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user