diff --git a/misc-natives/src/main/c/jagmisc.c b/misc-natives/src/main/c/jagmisc.c index 736ad004..946f3c6e 100644 --- a/misc-natives/src/main/c/jagmisc.c +++ b/misc-natives/src/main/c/jagmisc.c @@ -1,12 +1,227 @@ #include "jagex3_jagmisc_jagmisc.h" +#include +#include +#include +#include + +#define WAIT_OBJECT_1 (WAIT_OBJECT_0 + 1) + +#define JAGMISC_SPIN_COUNT 4000 +#define JAGMISC_NANOS_PER_SEC 1000000000LL + +// TODO(gpe): do any of these need to be volatile? +static bool jagmisc_unpinned; +static LARGE_INTEGER jagmisc_freq; +static CRITICAL_SECTION jagmisc_lock; +static HANDLE jagmisc_start_event; +static HANDLE jagmisc_time_request_event; +static HANDLE jagmisc_time_ready_event; +static HANDLE jagmisc_stop_event; +static HANDLE jagmisc_thread; +static int64_t jagmisc_time; + +static inline int64_t jagmisc_to_nanos(LARGE_INTEGER count) { + return count.QuadPart * JAGMISC_NANOS_PER_SEC / jagmisc_freq.QuadPart; +} + +static DWORD WINAPI jagmisc_run(LPVOID parameter) { + /* pin this thread to the system's first processor */ + HANDLE thread = GetCurrentThread(); + + DWORD_PTR affinity_mask = 0x1; + bool success = SetThreadAffinityMask(thread, affinity_mask); + + CloseHandle(thread); + + if (!success) { + return 1; + } + + /* notify Java_jagex3_jagmisc_jagmisc_init() that the thread has started successfully */ + if (!SetEvent(jagmisc_start_event)) { + return 1; + } + + /* serve time request and stop events */ + const HANDLE handles[] = { jagmisc_time_request_event, jagmisc_stop_event }; + for (;;) { + switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE)) { + case WAIT_OBJECT_0: + LARGE_INTEGER count; + if (QueryPerformanceCounter(&count)) { + jagmisc_time = jagmisc_to_nanos(count); + } else { + jagmisc_time = 0; + } + + if (!SetEvent(jagmisc_time_ready_event)) { + return 1; + } + break; + case WAIT_OBJECT_1: + return 0; + default: + return 1; + } + } +} + +static void jagmisc_stop(void) { + // TODO(gpe): think about what to do if we fail to stop the thread. We + // could call TerminateThread (which sounds dangerous based on the + // documentation) or throw a Java exception. The current behaviour of + // ignoring the failure is okay as long as jagmisc.init() is never called + // again in the same JVM. + if (SetEvent(jagmisc_stop_event)) { + WaitForSingleObject(jagmisc_thread, INFINITE); + } + + CloseHandle(jagmisc_thread); + jagmisc_thread = NULL; + + CloseHandle(jagmisc_stop_event); + jagmisc_stop_event = NULL; + + CloseHandle(jagmisc_time_ready_event); + jagmisc_time_ready_event = NULL; + + CloseHandle(jagmisc_time_request_event); + jagmisc_time_request_event = NULL; + + CloseHandle(jagmisc_start_event); + jagmisc_start_event = NULL; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + if (fdwReason == DLL_PROCESS_ATTACH) { + /* + * We only need to pin the timer thread to a single processor prior to + * Windows Vista. + */ + jagmisc_unpinned = IsWindowsVistaOrGreater(); + if (jagmisc_unpinned) { + return TRUE; + } + + if (!InitializeCriticalSectionAndSpinCount(&jagmisc_lock, JAGMISC_SPIN_COUNT)) { + return FALSE; + } + } + return TRUE; +} + JNIEXPORT jboolean JNICALL Java_jagex3_jagmisc_jagmisc_init(JNIEnv *env, jclass cls) { - return JNI_FALSE; + if (jagmisc_unpinned) { + return (jboolean) QueryPerformanceFrequency(&jagmisc_freq); + } + + EnterCriticalSection(&jagmisc_lock); + jboolean success = JNI_FALSE; + + if (jagmisc_thread) { + jagmisc_stop(); + } + + if (!QueryPerformanceFrequency(&jagmisc_freq)) { + goto unlock; + } + + jagmisc_start_event = CreateEventA(NULL, FALSE, FALSE, NULL); + if (!jagmisc_start_event) { + goto unlock; + } + + jagmisc_time_request_event = CreateEventA(NULL, FALSE, FALSE, NULL); + if (!jagmisc_time_request_event) { + goto create_time_request_event_failed; + } + + jagmisc_time_ready_event = CreateEventA(NULL, FALSE, FALSE, NULL); + if (!jagmisc_time_ready_event) { + goto create_time_ready_event_failed; + } + + jagmisc_stop_event = CreateEventA(NULL, FALSE, FALSE, NULL); + if (!jagmisc_stop_event) { + goto create_stop_event_failed; + } + + jagmisc_thread = CreateThread(NULL, 0, &jagmisc_run, NULL, 0, NULL); + if (!jagmisc_thread) { + goto create_thread_failed; + } + + const HANDLE handles[] = { jagmisc_start_event, jagmisc_thread }; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (result != WAIT_OBJECT_0) { + goto start_failed; + } + + success = JNI_TRUE; + goto unlock; + +start_failed: + CloseHandle(jagmisc_thread); + jagmisc_thread = NULL; +create_thread_failed: + CloseHandle(jagmisc_stop_event); + jagmisc_stop_event = NULL; +create_stop_event_failed: + CloseHandle(jagmisc_time_ready_event); + jagmisc_time_ready_event = NULL; +create_time_ready_event_failed: + CloseHandle(jagmisc_time_request_event); + jagmisc_time_request_event = NULL; +create_time_request_event_failed: + CloseHandle(jagmisc_start_event); + jagmisc_start_event = NULL; +unlock: + LeaveCriticalSection(&jagmisc_lock); + return success; } JNIEXPORT void JNICALL Java_jagex3_jagmisc_jagmisc_Quit0(JNIEnv *env, jclass cls) { + if (jagmisc_unpinned) { + return; + } + + EnterCriticalSection(&jagmisc_lock); + if (jagmisc_thread) { + jagmisc_stop(); + } + LeaveCriticalSection(&jagmisc_lock); } JNIEXPORT jlong JNICALL Java_jagex3_jagmisc_jagmisc_nanoTime(JNIEnv *env, jclass cls) { - return 0; + if (jagmisc_unpinned) { + LARGE_INTEGER count; + if (!QueryPerformanceCounter(&count)) { + return 0; + } + return jagmisc_to_nanos(count); + } + + EnterCriticalSection(&jagmisc_lock); + jlong time = 0; + + if (!jagmisc_thread) { + goto unlock; + } + + if (!SetEvent(jagmisc_time_request_event)) { + goto unlock; + } + + const HANDLE handles[] = { jagmisc_time_ready_event, jagmisc_thread }; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (result != WAIT_OBJECT_0) { + goto unlock; + } + + time = jagmisc_time; + +unlock: + LeaveCriticalSection(&jagmisc_lock); + return time; }