Start porting AsyncDownloader to C

This commit is contained in:
UnknownShadow200 2018-04-30 14:55:42 +10:00
parent fd1c9354c2
commit 206eb88036
14 changed files with 450 additions and 43 deletions

View File

@ -39,11 +39,7 @@ namespace ClassicalSharp.Network {
#if !LAUNCHER
void IGameComponent.Init(Game game) { Init(game.skinServer); }
void IGameComponent.Ready(Game game) { }
void IGameComponent.Reset(Game game) {
lock (pendingLocker)
pending.Clear();
handle.Set();
}
void IGameComponent.Reset(Game game) { Clear(); }
void IGameComponent.OnNewMap(Game game) { }
void IGameComponent.OnNewMapLoaded(Game game) { }

View File

@ -0,0 +1,353 @@
#include "AsyncDownloader.h"
#include "Platform.h"
void ASyncRequest_Free(AsyncRequest* request) {
switch (request->RequestType) {
case REQUEST_TYPE_IMAGE:
Platform_MemFree(&request->ResultBitmap.Scan0);
break;
case REQUEST_TYPE_DATA:
Platform_MemFree(&request->ResultData.Ptr);
break;
case REQUEST_TYPE_STRING:
Platform_MemFree(&request->ResultString.buffer);
break;
}
}
typedef struct AsyncRequestList_ {
UInt32 Count;
AsyncRequest* Requests;
AsyncRequest DefaultRequests[10];
} AsyncRequestList;
void AsyncRequestList_Append(AsyncRequestList* list, AsyncRequest* item);
void AsyncRequestList_Prepend(AsyncRequestList* list, AsyncRequest* item);
void AsyncRequestList_RemoveAt(AsyncRequestList* list, UInt32 i);
void AsyncRequestList_Init(AsyncRequestList* list) {
list->Count = 0;
list->Requests = list->DefaultRequests;
}
void AsyncRequestList_Free(AsyncRequestList* list) {
if (list->Requests != list->DefaultRequests) {
Platform_MemFree(&list->Requests);
}
AsyncDownloader_Init(list);
}
void* async_eventHandle;
void* async_workerThread;
void* async_pendingMutex;
void* async_processedMutex;
void* async_curRequestMutex;
volatile bool async_terminate;
AsyncRequestList async_pending;
AsyncRequestList async_processed;
String async_skinServer = String_FromConst("http://static.classicube.net/skins/");
AsyncRequest async_curRequest;
volatile Int32 async_curRequestProgress = -3;
bool ManageCookies;
bool KeepAlive;
void AsyncDownloader_Init(void) {
AsyncRequestList_Init(&async_pending);
AsyncRequestList_Init(&async_processed);
async_eventHandle = Platform_EventCreate();
async_pendingMutex = Platform_MutexCreate();
async_processedMutex = Platform_MutexCreate();
async_curRequestMutex = Platform_MutexCreate();
async_workerThread = Platform_ThreadStart(DownloadThreadWorker);
}
void AsyncDownloader_Reset(void) {
Platform_MutexLock(async_pendingMutex);
{
AsyncRequestList_Free(&async_pending);
}
Platform_MutexUnlock(async_pendingMutex);
Platform_EventSet(async_eventHandle);
}
void AsyncDownloader_Free(void) {
async_terminate = true;
AsyncDownloader_Reset();
Platform_ThreadJoin(async_workerThread);
Platform_ThreadFreeHandle(async_workerThread);
Platform_EventFree(async_eventHandle);
Platform_MutexFree(async_pendingMutex);
Platform_MutexFree(async_processedMutex);
Platform_MutexFree(async_curRequestMutex);
}
void AsyncDownloader_GetSkin(STRING_PURE String* id, STRING_PURE String* skinName) {
UInt8 urlBuffer[String_BufferSize(STRING_SIZE)];
String url = String_InitAndClearArray(urlBuffer);
if (Utils_IsUrlPrefix(skinName, 0)) {
String_Set(&url, &skinName);
} else {
String_AppendString(&url, &async_skinServer);
String_AppendColorless(&url, skinName);
String_AppendConst(&url, ".png");
}
AddRequest(&url, false, id, REQUEST_TYPE_IMAGE, NULL, NULL, NULL);
}
void AsyncDownloader_GetData(STRING_PURE String* url, bool priority, STRING_PURE String* id) {
AddRequest(url, priority, id, REQUEST_TYPE_DATA, NULL, NULL, NULL);
}
void AsyncDownloader_GetImage(STRING_PURE String* url, bool priority, STRING_PURE String* id) {
AddRequest(url, priority, id, REQUEST_TYPE_IMAGE, NULL, NULL, NULL);
}
void AsyncDownloader_GetString(STRING_PURE String* url, bool priority, STRING_PURE String* id) {
AddRequest(url, priority, id, REQUEST_TYPE_STRING, NULL, NULL, NULL);
}
void AsyncDownloader_GetContentLength(STRING_PURE String* url, bool priority, STRING_PURE String* id) {
AddRequest(url, priority, id, REQUEST_TYPE_CONTENT_LENGTH, NULL, NULL, NULL);
}
void AsyncDownloader_PostString(STRING_PURE String* url, bool priority, STRING_PURE String* id, STRING_PURE String* contents) {
AddRequest(url, priority, id, REQUEST_TYPE_STRING, NULL, NULL, contents);
}
void AsyncDownloader_GetDataEx(STRING_PURE String* url, bool priority, STRING_PURE String* id, DateTime* lastModified, STRING_PURE String* etag) {
AddRequest(url, priority, id, REQUEST_TYPE_DATA, lastModified, etag, NULL);
}
void AsyncDownloader_GetImageEx(STRING_PURE String* url, bool priority, STRING_PURE String* id, DateTime* lastModified, STRING_PURE String* etag) {
AddRequest(url, priority, id, REQUEST_TYPE_IMAGE, lastModified, etag, NULL);
}
void AddRequest(String* url, bool priority, String* id, UInt8 type, DateTime* lastModified, String* etag, object data) {
Platform_MutexLock(async_pendingMutex);
{
AsyncRequest request = { 0 };
String reqUrl = String_FromEmptyArray(request.URL); String_Set(&reqUrl, url);
String reqID = String_FromEmptyArray(request.ID); String_Set(&reqID, id);
request.RequestType = type;
request.LastModified = lastModified; // can be null
request.ETag = etag; // can be null
request.Data = data;
Platform_CurrentUTCTime(&request.TimeAdded);
if (priority) {
AsyncRequestList_Prepend(&async_pending, &request);
} else {
AsyncRequestList_Append(&async_pending, &request);
}
}
Platform_MutexUnlock(async_pendingMutex);
Platform_EventSet(async_eventHandle);
}
void PurgeOldEntriesTask(ScheduledTask* task) {
Platform_MutexLock(async_processedMutex);
{
DateTime now; Platform_CurrentUTCTime(&now);
Int32 i;
for (i = async_processed.Count - 1; i >= 0; i--) {
AsyncRequest* item = &async_processed.Requests[i];
if ((now - item.TimeDownloaded).TotalSeconds < 10) continue;
ASyncRequest_Free(item);
AsyncRequestList_RemoveAt(&async_processed, i);
}
}
Platform_MutexUnlock(async_processedMutex);
}
bool AsyncDownloader_Get(STRING_PURE String* id, AsyncRequest* item) {
bool success = false;
Platform_MutexLock(async_processedMutex);
{
Int32 i = FindRequest(id, item);
success = i >= 0;
if (success) AsyncRequestList_RemoveAt(&async_processed, i);
}
Platform_MutexUnlock(async_processedMutex);
return success;
}
void DownloadThreadWorker() {
while (true) {
AsyncRequest request;
bool hasRequest = false;
Platform_MutexLock(async_pendingMutex);
{
if (async_terminate) return;
if (async_pending.Count > 0) {
request = async_pending.Requests[0];
hasRequest = true;
AsyncRequestList_RemoveAt(&async_pending, 0);
}
}
Platform_MutexUnlock(async_pendingMutex);
if (hasRequest) {
Platform_MutexLock(async_curRequestMutex);
{
async_curRequest = request;
async_curRequestProgress = -2;
}
Platform_MutexUnlock(async_curRequestMutex);
ProcessRequest(request);
Platform_MutexLock(async_curRequestMutex);
{
async_curRequest.ID[0] = NULL;
async_curRequestProgress = -3;
}
Platform_MutexUnlock(async_curRequestMutex);
} else {
Platform_EventWait(async_eventHandle);
}
}
}
Int32 FindRequest(STRING_PURE String* id, AsyncRequest* item) {
Int32 i;
for (i = 0; i < async_processed.Count; i++) {
String requestID = String_FromRawArray(async_processed.Requests[i].ID);
if (!String_Equals(&requestID, id)) continue;
*item = async_processed.Requests[i];
return i;
}
return -1;
}
void ProcessRequest(AsyncRequest* request) {
String url = String_FromRawArray(request->URL);
Platform_Log2("Downloading from %s (type %b)", &url, &request->RequestType);
HttpStatusCode status = HttpStatusCode.OK;
try {
HttpWebRequest req = MakeRequest(request);
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse()) {
request.ETag = response.Headers.Get("ETag");
if (response.Headers.Get("Last-Modified") != null) {
request.LastModified = response.LastModified;
}
request.Data = DownloadContent(request, response);
}
} catch (Exception ex) {
if (!(ex is WebException || ex is ArgumentException || ex is UriFormatException || ex is IOException)) throw;
if (ex is WebException) {
WebException webEx = (WebException)ex;
if (webEx.Response != null) {
status = ((HttpWebResponse)webEx.Response).StatusCode;
webEx.Response.Close();
}
request.WebEx = webEx;
}
if (status != HttpStatusCode.OK) {
Utils.LogDebug("Failed to download (" + (int)status + ") from: " + url);
} else {
Utils.LogDebug("Failed to download from: " + url);
}
}
request.TimeDownloaded = DateTime.UtcNow;
Platform_MutexLock(async_processedMutex);
{
AsyncRequest older;
String id = String_FromRawArray(request->ID);
Int32 index = FindRequest(&id, &older);
if (index >= 0) {
/* very rare case - priority item was inserted, then inserted again (so put before first item), */
/* and both items got downloaded before an external function removed them from the queue */
if (older.TimeAdded > request.TimeAdded) {
AsyncRequest tmp = older; older = *request; *request = tmp;
}
ASyncRequest_Free(&older);
async_processed.Requests[index] = *request;
} else {
AsyncRequestList_Append(&async_processed, request);
}
}
Platform_MutexUnlock(async_processedMutex);
}
/*
object DownloadContent(Request request, HttpWebResponse response) {
if (request.Type == RequestType.Bitmap) {
MemoryStream data = DownloadBytes(response);
Bitmap bmp = Platform.ReadBmp32Bpp(drawer, data);
if (bmp == null) {
Utils.LogDebug("Failed to download from: " + request.Url);
}
return bmp;
} else if (request.Type == RequestType.String) {
MemoryStream data = DownloadBytes(response);
byte[] rawBuffer = data.GetBuffer();
return Encoding.UTF8.GetString(rawBuffer, 0, (int)data.Length);
} else if (request.Type == RequestType.ByteArray) {
MemoryStream data = DownloadBytes(response);
return data.ToArray();
} else if (request.Type == RequestType.ContentLength) {
return response.ContentLength;
}
return null;
}
HttpWebRequest MakeRequest(Request request) {
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(request.Url);
req.AutomaticDecompression = DecompressionMethods.GZip;
req.ReadWriteTimeout = 90 * 1000;
req.Timeout = 90 * 1000;
req.Proxy = null;
req.UserAgent = Program.AppName;
req.CookieContainer = Cookies;
req.KeepAlive = KeepAlive;
if (request.LastModified != DateTime.MinValue) {
req.IfModifiedSince = request.LastModified;
}
if (request.ETag != null) {
req.Headers["If-None-Match"] = request.ETag;
}
if (request.Data != null) {
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded; charset=UTF-8;";
byte[] encodedData = Encoding.UTF8.GetBytes((string)request.Data);
req.ContentLength = encodedData.Length;
using (Stream stream = req.GetRequestStream()) {
stream.Write(encodedData, 0, encodedData.Length);
}
}
return req;
}
static byte[] buffer = new byte[4096 * 8];
MemoryStream DownloadBytes(HttpWebResponse response) {
int length = (int)response.ContentLength;
MemoryStream dst = length > 0 ? new MemoryStream(length) : new MemoryStream();
CurrentItemProgress = length > 0 ? 0 : -1;
using (Stream src = response.GetResponseStream()) {
int read = 0;
while ((read = src.Read(buffer, 0, buffer.Length)) > 0) {
dst.Write(buffer, 0, read);
if (length <= 0) continue;
CurrentItemProgress = (int)(100 * (float)dst.Length / length);
}
}
return dst;
}
}*/

View File

@ -8,11 +8,10 @@
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
*/
#define REQUEST_TYPE_BITMAP 0
#define REQUEST_TYPE_STRING 1
#define REQUEST_TYPE_DATA 2
#define REQUEST_TYPE_CONTENT_LENGTH 3
enum REQUEST_TYPE {
REQUEST_TYPE_DATA, REQUEST_TYPE_IMAGE,
REQUEST_TYPE_STRING, REQUEST_TYPE_CONTENT_LENGTH,
};
typedef struct AsyncRequest_ {
UInt8 URL[String_BufferSize(STRING_SIZE)];
UInt8 ID[String_BufferSize(STRING_SIZE)];
@ -35,12 +34,19 @@ typedef struct AsyncRequest_ {
void ASyncRequest_Free(AsyncRequest* request);
IGameComponent AsyncDownloader_MakeComponent(void);
void AsyncDownloader_Init(STRING_PURE String* skinServer);
void AsyncDownloader_DownloadSkin(STRING_PURE String* identifier, STRING_PURE String* skinName);
void AsyncDownloader_Download(STRING_PURE String* url, bool priority, UInt8 type, STRING_PURE String* identifier);
void AsyncDownloader_Download2(STRING_PURE String* url, bool priority, UInt8 type, STRING_PURE String* identifier, DateTime* lastModified, STRING_PURE String* etag);
void AsyncDownloader_Init(void);
void AsyncDownloader_Free(void);
bool AsyncDownloader_Get(STRING_PURE String* identifier, AsyncRequest* item);
bool AsyncDownloader_GetInProgress(AsyncRequest* request, Int32* progress);
void AsyncDownloader_GetSkin(STRING_PURE String* id, STRING_PURE String* skinName);
void AsyncDownloader_GetData(STRING_PURE String* url, bool priority, STRING_PURE String* id);
void AsyncDownloader_GetImage(STRING_PURE String* url, bool priority, STRING_PURE String* id);
void AsyncDownloader_GetString(STRING_PURE String* url, bool priority, STRING_PURE String* id);
void AsyncDownloader_GetContentLength(STRING_PURE String* url, bool priority, STRING_PURE String* id);
void AsyncDownloader_PostString(STRING_PURE String* url, bool priority, STRING_PURE String* id, STRING_PURE String* contents);
void AsyncDownloader_GetDataEx(STRING_PURE String* url, bool priority, STRING_PURE String* id, DateTime* lastModified, STRING_PURE String* etag);
void AsyncDownloader_GetImageEx(STRING_PURE String* url, bool priority, STRING_PURE String* id, DateTime* lastModified, STRING_PURE String* etag);
bool AsyncDownloader_Get(STRING_PURE String* id, AsyncRequest* item);
bool AsyncDownloader_GetCurrent(AsyncRequest* request, Int32* progress);
void AsyncDownloader_PurgeOldEntriesTask(ScheduledTask* task);
#endif

View File

@ -255,6 +255,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="2DStructs.c" />
<ClCompile Include="AsyncDownloader.c" />
<ClCompile Include="Camera.c" />
<ClCompile Include="AxisLinesRenderer.c" />
<ClCompile Include="Block.c" />

View File

@ -572,5 +572,8 @@
<ClCompile Include="PacketHandlers.c">
<Filter>Source Files\Network</Filter>
</ClCompile>
<ClCompile Include="AsyncDownloader.c">
<Filter>Source Files\Network</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -454,8 +454,7 @@ void TabList_Reset(void) {
Platform_MemSet(TabList_ListNames, 0, sizeof(TabList_ListNames));
Platform_MemSet(TabList_GroupNames, 0, sizeof(TabList_GroupNames));
Platform_MemSet(TabList_GroupRanks, 0, sizeof(TabList_GroupRanks));
/* TODO: Should we be trying to free the buffer here? */
StringsBuffer_UNSAFE_Reset(&TabList_Buffer);
StringsBuffer_Free(&TabList_Buffer);
}
IGameComponent TabList_MakeComponent(void) {
@ -692,7 +691,7 @@ void Player_CheckSkin(Player* player) {
if (!player->FetchedSkin && entity->Model->UsesSkin) {
Player* first = Player_FirstOtherWithSameSkinAndFetchedSkin(player);
if (first == NULL) {
AsyncDownloader_DownloadSkin(&skin, &skin);
AsyncDownloader_GetSkin(&skin, &skin);
} else {
Player_ApplySkin(player, first);
}

View File

@ -767,16 +767,7 @@ void Audio_SetSounds(Int32 volume) { }
void Audio_PlayDigSound(UInt8 type) { }
void Audio_PlayStepSound(UInt8 type) { }
void ASyncRequest_Free(AsyncRequest* request) { }
IGameComponent AsyncDownloader_MakeComponent(void) { return IGameComponent_MakeEmpty(); }
void AsyncDownloader_Init(STRING_PURE String* skinServer) { }
void AsyncDownloader_DownloadSkin(STRING_PURE String* identifier, STRING_PURE String* skinName) { }
void AsyncDownloader_Download(STRING_PURE String* url, bool priority, UInt8 type, STRING_PURE String* identifier) { }
void AsyncDownloader_Download2(STRING_PURE String* url, bool priority, UInt8 type, STRING_PURE String* identifier, DateTime* lastModified, STRING_PURE String* etag) { }
void AsyncDownloader_Free(void) { }
bool AsyncDownloader_Get(STRING_PURE String* identifier, AsyncRequest* item) { return false; }
bool AsyncDownloader_GetInProgress(AsyncRequest* request, Int32* progress) { return false; }
void AsyncDownloader_PurgeOldEntriesTask(ScheduledTask* task) { }
/* TODO: needed for async downloading */
DateTime DateTime_FromTotalMs(Int64 ms) { DateTime time; return time; }
void Bitmap_EncodePng(Bitmap* bmp, Stream* stream) { }

View File

@ -214,14 +214,14 @@ void WoM_CheckMotd(void) {
applied in the new world if the async 'get env request' didn't complete before the old world was unloaded */
wom_counter++;
WoM_UpdateIdentifier();
AsyncDownloader_Download(&url, true, REQUEST_TYPE_STRING, &wom_identifier);
AsyncDownloader_GetString(&url, true, &wom_identifier);
wom_sendId = true;
}
void WoM_CheckSendWomID(void) {
if (wom_sendId && !wom_sentId) {
String msg = String_FromConst("/womid WoMClient-2.0.7")
ServerConnection_SendChat(&msg);
String msg = String_FromConst("/womid WoMClient-2.0.7");
ServerConnection_SendChat(&msg);
wom_sentId = true;
}
}

View File

@ -52,9 +52,20 @@ UInt32 Platform_FileLength(void* file);
void Platform_ThreadSleep(UInt32 milliseconds);
typedef void Platform_ThreadFunc(void);
void* Platform_ThreadStart(Platform_ThreadFunc* func);
void Platform_ThreadJoin(void* handle);
/* Frees handle to thread - NOT THE THREAD ITSELF */
void Platform_ThreadFreeHandle(void* handle);
void* Platform_MutexCreate(void);
void Platform_MutexFree(void* handle);
void Platform_MutexLock(void* handle);
void Platform_MutexUnlock(void* handle);
void* Platform_EventCreate(void);
void Platform_EventFree(void* handle);
void Platform_EventSet(void* handle);
void Platform_EventWait(void* handle);
typedef Int64 Stopwatch;
void Stopwatch_Start(Stopwatch* timer);
Int32 Stopwatch_ElapsedMicroseconds(Stopwatch* timer);

View File

@ -767,7 +767,7 @@ void ChatScreen_SetInitialMessages(ChatScreen* screen) {
void ChatScreen_CheckOtherStatuses(ChatScreen* screen) {
AsyncRequest request;
Int32 progress;
bool hasRequest = AsyncDownloader_GetInProgress(&request, &progress);
bool hasRequest = AsyncDownloader_GetCurrent(&request, &progress);
String id = String_FromRawArray(request.ID);
String terrain = String_FromConst("terrain");

View File

@ -63,10 +63,10 @@ void ServerConnection_DownloadTexturePack(STRING_PURE String* url) {
String zip = String_FromConst(".zip");
if (String_ContainsString(url, &zip)) {
String texPack = String_FromConst("texturePack");
AsyncDownloader_Download2(url, true, REQUEST_TYPE_DATA, &texPack, &lastModified, &etag);
AsyncDownloader_GetDataEx(url, true, &texPack, &lastModified, &etag);
} else {
String terrain = String_FromConst("terrain");
AsyncDownloader_Download2(url, true, REQUEST_TYPE_BITMAP, &terrain, &lastModified, &etag);
AsyncDownloader_GetImageEx(url, true, &terrain, &lastModified, &etag);
}
}

View File

@ -641,7 +641,8 @@ bool Convert_TryParseBool(STRING_PURE String* str, bool* value) {
#define STRINGSBUFFER_LEN_SHIFT 9
#define STRINGSBUFFER_LEN_MASK 0x1FFUL
void StringsBuffer_Init(StringsBuffer* buffer) {
StringsBuffer_UNSAFE_Reset(buffer);
buffer->Count = 0;
buffer->UsedElems = 0;
buffer->TextBuffer = buffer->DefaultBuffer;
buffer->FlagsBuffer = buffer->DefaultFlags;
buffer->TextBufferElems = STRINGSBUFFER_BUFFER_DEF_SIZE;
@ -655,12 +656,7 @@ void StringsBuffer_Free(StringsBuffer* buffer) {
if (buffer->FlagsBuffer != buffer->DefaultFlags) {
Platform_MemFree(&buffer->FlagsBuffer);
}
StringsBuffer_UNSAFE_Reset(buffer);
}
void StringsBuffer_UNSAFE_Reset(StringsBuffer* buffer) {
buffer->Count = 0;
buffer->UsedElems = 0;
StringsBuffer_Init(buffer);
}
void StringsBuffer_Get(StringsBuffer* buffer, UInt32 index, STRING_TRANSIENT String* text) {

View File

@ -115,7 +115,6 @@ typedef struct StringsBuffer_ {
void StringsBuffer_Init(StringsBuffer* buffer);
void StringsBuffer_Free(StringsBuffer* buffer);
void StringsBuffer_UNSAFE_Reset(StringsBuffer* buffer);
void StringsBuffer_Get(StringsBuffer* buffer, UInt32 index, STRING_TRANSIENT String* text);
STRING_REF String StringsBuffer_UNSAFE_Get(StringsBuffer* buffer, UInt32 index);
void StringsBuffer_Resize(void** buffer, UInt32* elems, UInt32 elemSize, UInt32 defElems, UInt32 expandElems);

View File

@ -4,6 +4,7 @@
#include "ExtMath.h"
#include "ErrorHandler.h"
#include "Drawer2D.h"
#include "Funcs.h"
#define WIN32_LEAN_AND_MEAN
#define NOSERVICE
#define NOMCX
@ -297,12 +298,63 @@ void* Platform_ThreadStart(Platform_ThreadFunc* func) {
return handle;
}
void Platform_ThreadJoin(void* handle) {
WaitForSingleObject((HANDLE)handle, INFINITE);
}
void Platform_ThreadFreeHandle(void* handle) {
if (!CloseHandle((HANDLE)handle)) {
ErrorHandler_FailWithCode(GetLastError(), "Freeing thread handle");
}
}
CRITICAL_SECTION mutexList[3];
Int32 mutexIndex;
void* Platform_MutexCreate(void) {
if (mutexIndex == Array_Elems(mutexList)) {
ErrorHandler_Fail("Cannot allocate another mutex");
return NULL;
} else {
CRITICAL_SECTION* ptr = &mutexList[mutexIndex];
InitializeCriticalSection(ptr); mutexIndex++;
return ptr;
}
}
void Platform_MutexFree(void* handle) {
DeleteCriticalSection((CRITICAL_SECTION*)handle);
}
void Platform_MutexLock(void* handle) {
EnterCriticalSection((CRITICAL_SECTION*)handle);
}
void Platform_MutexUnlock(void* handle) {
LeaveCriticalSection((CRITICAL_SECTION*)handle);
}
void* Platform_EventCreate(void) {
void* handle = CreateEventA(NULL, false, false, NULL);
if (handle == NULL) {
ErrorHandler_FailWithCode(GetLastError(), "Creating event");
}
return handle;
}
void Platform_EventFree(void* handle) {
if (!CloseHandle((HANDLE)handle)) {
ErrorHandler_FailWithCode(GetLastError(), "Freeing event");
}
}
void Platform_EventSet(void* handle) {
SetEvent((HANDLE)handle);
}
void Platform_EventWait(void* handle) {
WaitForSingleObject((HANDLE)handle, INFINITE);
}
void Stopwatch_Start(Stopwatch* timer) {
if (stopwatch_highResolution) {
QueryPerformanceCounter(timer);