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/app.h',
|
||||||
'src/runtime/dynamic_libs.h',
|
'src/runtime/dynamic_libs.h',
|
||||||
'src/runtime/jobs.h',
|
'src/runtime/jobs.h',
|
||||||
|
'src/runtime/aio.h',
|
||||||
|
'src/runtime/file_tab.h',
|
||||||
|
|
||||||
'src/runtime/error_report.c',
|
'src/runtime/error_report.c',
|
||||||
'src/runtime/gfx_main.c',
|
'src/runtime/gfx_main.c',
|
||||||
@ -63,6 +65,8 @@ runtime_lib = library('vyrt',
|
|||||||
'src/runtime/app.c',
|
'src/runtime/app.c',
|
||||||
'src/runtime/dynamic_libs.c',
|
'src/runtime/dynamic_libs.c',
|
||||||
'src/runtime/jobs.c',
|
'src/runtime/jobs.c',
|
||||||
|
'src/runtime/aio.c',
|
||||||
|
'src/runtime/file_tab.c',
|
||||||
|
|
||||||
# Contrib Sources
|
# Contrib Sources
|
||||||
'contrib/xxhash/xxhash.c',
|
'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 "config.h"
|
||||||
#include "fio.h"
|
#include "fio.h"
|
||||||
#include "gfx.h"
|
#include "gfx.h"
|
||||||
|
#include "aio.h"
|
||||||
#include "renderer_api.h"
|
#include "renderer_api.h"
|
||||||
|
|
||||||
extern void __RegisterRuntimeCVars(void);
|
extern void __RegisterRuntimeCVars(void);
|
||||||
@ -43,6 +44,16 @@ VY_DLLEXPORT int vyWin32Entry(HINSTANCE hInstance,
|
|||||||
return 1;
|
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 = {
|
WNDCLASSEXW wndclass = {
|
||||||
.cbSize = sizeof(wndclass),
|
.cbSize = sizeof(wndclass),
|
||||||
.hInstance = hInstance,
|
.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 <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <xxhash/xxhash.h>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
@ -38,23 +36,7 @@ typedef struct {
|
|||||||
#endif
|
#endif
|
||||||
} vy_fio_queue;
|
} 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_fio_queue _queue;
|
||||||
static vy_file_tab _file_tab;
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
static pthread_t _thread;
|
static pthread_t _thread;
|
||||||
@ -96,43 +78,6 @@ static void ShutdownFIOQueue(void) {
|
|||||||
#endif
|
#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__
|
#ifdef __linux__
|
||||||
static void *linuxFIOThreadProc(void *);
|
static void *linuxFIOThreadProc(void *);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
@ -141,13 +86,8 @@ static DWORD WINAPI win32FIOThreadProc(_In_ LPVOID);
|
|||||||
|
|
||||||
VY_DLLEXPORT bool vyInitFIO(const vy_fio_config *config) {
|
VY_DLLEXPORT bool vyInitFIO(const vy_fio_config *config) {
|
||||||
unsigned int queue_size = (config->queue_size) ? config->queue_size : 512;
|
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))
|
if (!InitFIOQueue(queue_size))
|
||||||
return false;
|
return false;
|
||||||
if (!InitFileTab(max_file_count))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if (pthread_create(&_thread, NULL, linuxFIOThreadProc, NULL) != 0)
|
if (pthread_create(&_thread, NULL, linuxFIOThreadProc, NULL) != 0)
|
||||||
@ -180,110 +120,6 @@ VY_DLLEXPORT void vyShutdownFIO(void) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ShutdownFIOQueue();
|
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) {
|
VY_DLLEXPORT vy_fio_handle vyEnqueueRead(vy_file_id fid) {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "runtime.h"
|
#include "runtime.h"
|
||||||
|
#include "file_tab.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
VY_FILE_BUFFER_FLAG_FILE_NOT_FOUND = 0x1,
|
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;
|
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;
|
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 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 vy_fio_handle vyEnqueueRead(vy_file_id fid);
|
||||||
|
|
||||||
VY_DLLEXPORT void vyAbortFIO(vy_fio_handle fio);
|
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);
|
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
|
#endif
|
||||||
|
@ -14,8 +14,31 @@ struct vy_mutex_s {
|
|||||||
static vy_mutex _mutex[MAX_MUTEX];
|
static vy_mutex _mutex[MAX_MUTEX];
|
||||||
static ptrdiff_t _first_reusable = MAX_MUTEX;
|
static ptrdiff_t _first_reusable = MAX_MUTEX;
|
||||||
static ptrdiff_t _next = 0;
|
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) {
|
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) {
|
if (_first_reusable < MAX_MUTEX) {
|
||||||
vy_mutex *mtx = &_mutex[_first_reusable];
|
vy_mutex *mtx = &_mutex[_first_reusable];
|
||||||
_first_reusable = mtx->next_reusable;
|
_first_reusable = mtx->next_reusable;
|
||||||
@ -42,9 +65,10 @@ VY_DLLEXPORT void vyDestroyMutex(vy_mutex *mutex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VY_DLLEXPORT bool vyLockMutex(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;
|
return ReleaseMutex(mutex->handle) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,104 @@
|
|||||||
#include "threading.h"
|
#include "threading.h"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#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__)
|
#elif defined(__linux__)
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@ -51,7 +149,7 @@ VY_DLLEXPORT vy_thread *vySpawnThread(vy_thread_entry_fn *entry, void *param) {
|
|||||||
thrd->param = param;
|
thrd->param = param;
|
||||||
if (pthread_create(&thrd->handle, NULL, linuxThreadWrapper, thrd) !=
|
if (pthread_create(&thrd->handle, NULL, linuxThreadWrapper, thrd) !=
|
||||||
0) {
|
0) {
|
||||||
vyLog("core", "Mutex creation failed");
|
vyLog("core", "Thread creation failed");
|
||||||
thrd = NULL;
|
thrd = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user