diff --git a/crn_linux.workspace b/crn_linux.workspace new file mode 100644 index 0000000..af9107b --- /dev/null +++ b/crn_linux.workspace @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/crnlib/crn_atomics.h b/crnlib/crn_atomics.h new file mode 100644 index 0000000..6eeedb5 --- /dev/null +++ b/crnlib/crn_atomics.h @@ -0,0 +1,208 @@ +// File: crn_atomics.h +#ifndef CRN_ATOMICS_H +#define CRN_ATOMICS_H + +#ifdef WIN32 +#pragma once +#endif + +#ifdef WIN32 +#include "crn_winhdr.h" +#endif + +#if defined(__GNUC__) && CRNLIB_PLATFORM_PC +extern __inline__ __attribute__((__always_inline__,__gnu_inline__)) void crnlib_yield_processor() +{ + __asm__ __volatile__("pause"); +} +#else +CRNLIB_FORCE_INLINE void crnlib_yield_processor() +{ +#if CRNLIB_USE_MSVC_INTRINSICS + #if CRNLIB_PLATFORM_PC_X64 + _mm_pause(); + #else + YieldProcessor(); + #endif +#else + // No implementation +#endif +} +#endif + +#if CRNLIB_USE_WIN32_ATOMIC_FUNCTIONS + extern "C" __int64 _InterlockedCompareExchange64(__int64 volatile * Destination, __int64 Exchange, __int64 Comperand); + #if defined(_MSC_VER) + #pragma intrinsic(_InterlockedCompareExchange64) + #endif +#endif // CRNLIB_USE_WIN32_ATOMIC_FUNCTIONS + +namespace crnlib +{ +#if CRNLIB_USE_WIN32_ATOMIC_FUNCTIONS + typedef LONG atomic32_t; + typedef LONGLONG atomic64_t; + + // Returns the original value. + inline atomic32_t atomic_compare_exchange32(atomic32_t volatile *pDest, atomic32_t exchange, atomic32_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedCompareExchange(pDest, exchange, comparand); + } + + // Returns the original value. + inline atomic64_t atomic_compare_exchange64(atomic64_t volatile *pDest, atomic64_t exchange, atomic64_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 7) == 0); + return _InterlockedCompareExchange64(pDest, exchange, comparand); + } + + // Returns the resulting incremented value. + inline atomic32_t atomic_increment32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedIncrement(pDest); + } + + // Returns the resulting decremented value. + inline atomic32_t atomic_decrement32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedDecrement(pDest); + } + + // Returns the original value. + inline atomic32_t atomic_exchange32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedExchange(pDest, val); + } + + // Returns the resulting value. + inline atomic32_t atomic_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedExchangeAdd(pDest, val) + val; + } + + // Returns the original value. + inline atomic32_t atomic_exchange_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return InterlockedExchangeAdd(pDest, val); + } +#elif CRNLIB_USE_GCC_ATOMIC_BUILTINS + typedef long atomic32_t; + typedef long long atomic64_t; + + // Returns the original value. + inline atomic32_t atomic_compare_exchange32(atomic32_t volatile *pDest, atomic32_t exchange, atomic32_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_val_compare_and_swap(pDest, comparand, exchange); + } + + // Returns the original value. + inline atomic64_t atomic_compare_exchange64(atomic64_t volatile *pDest, atomic64_t exchange, atomic64_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 7) == 0); + return __sync_val_compare_and_swap(pDest, comparand, exchange); + } + + // Returns the resulting incremented value. + inline atomic32_t atomic_increment32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_add_and_fetch(pDest, 1); + } + + // Returns the resulting decremented value. + inline atomic32_t atomic_decrement32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_sub_and_fetch(pDest, 1); + } + + // Returns the original value. + inline atomic32_t atomic_exchange32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_lock_test_and_set(pDest, val); + } + + // Returns the resulting value. + inline atomic32_t atomic_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_add_and_fetch(pDest, val); + } + + // Returns the original value. + inline atomic32_t atomic_exchange_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return __sync_fetch_and_add(pDest, val); + } +#else + #define CRNLIB_NO_ATOMICS 1 + + // Atomic ops not supported - but try to do something reasonable. Assumes no threading at all. + typedef long atomic32_t; + typedef long long atomic64_t; + + inline atomic32_t atomic_compare_exchange32(atomic32_t volatile *pDest, atomic32_t exchange, atomic32_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + atomic32_t cur = *pDest; + if (cur == comparand) + *pDest = exchange; + return cur; + } + + inline atomic64_t atomic_compare_exchange64(atomic64_t volatile *pDest, atomic64_t exchange, atomic64_t comparand) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 7) == 0); + atomic64_t cur = *pDest; + if (cur == comparand) + *pDest = exchange; + return cur; + } + + inline atomic32_t atomic_increment32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return (*pDest += 1); + } + + inline atomic32_t atomic_decrement32(atomic32_t volatile *pDest) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return (*pDest -= 1); + } + + inline atomic32_t atomic_exchange32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + atomic32_t cur = *pDest; + *pDest = val; + return cur; + } + + inline atomic32_t atomic_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + return (*pDest += val); + } + + inline atomic32_t atomic_exchange_add32(atomic32_t volatile *pDest, atomic32_t val) + { + CRNLIB_ASSERT((reinterpret_cast(pDest) & 3) == 0); + atomic32_t cur = *pDest; + *pDest += val; + return cur; + } +#endif + +} // namespace crnlib + +#endif // CRN_ATOMICS_H diff --git a/crnlib/crn_colorized_console.cpp b/crnlib/crn_colorized_console.cpp new file mode 100644 index 0000000..67a4891 --- /dev/null +++ b/crnlib/crn_colorized_console.cpp @@ -0,0 +1,119 @@ +// File: crn_colorized_console.cpp +// See Copyright Notice and license at the end of inc/crnlib.h +#include "crn_core.h" +#include "crn_colorized_console.h" +#ifdef CRNLIB_USE_WIN32_API +#include "crn_winhdr.h" +#endif + +namespace crnlib +{ + void colorized_console::init() + { + console::init(); + console::add_console_output_func(console_output_func, NULL); + } + + void colorized_console::deinit() + { + console::remove_console_output_func(console_output_func); + console::deinit(); + } + + void colorized_console::tick() + { + } + +#ifdef CRNLIB_USE_WIN32_API + bool colorized_console::console_output_func(eConsoleMessageType type, const char* pMsg, void* pData) + { + pData; + + if (console::get_output_disabled()) + return true; + + HANDLE cons = GetStdHandle(STD_OUTPUT_HANDLE); + + DWORD attr = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + switch (type) + { + case cDebugConsoleMessage: attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; + case cMessageConsoleMessage: attr = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; + case cWarningConsoleMessage: attr = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case cErrorConsoleMessage: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + default: break; + } + + if (INVALID_HANDLE_VALUE != cons) + SetConsoleTextAttribute(cons, (WORD)attr); + + if (console::get_prefixes()) + { + switch (type) + { + case cDebugConsoleMessage: + printf("Debug: %s", pMsg); + break; + case cWarningConsoleMessage: + printf("Warning: %s", pMsg); + break; + case cErrorConsoleMessage: + printf("Error: %s", pMsg); + break; + default: + printf("%s", pMsg); + break; + } + } + else + { + printf("%s", pMsg); + } + + if (console::get_crlf()) + printf("\n"); + + if (INVALID_HANDLE_VALUE != cons) + SetConsoleTextAttribute(cons, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + + return true; + } +#else + bool colorized_console::console_output_func(eConsoleMessageType type, const char* pMsg, void* pData) + { + pData; + if (console::get_output_disabled()) + return true; + + if (console::get_prefixes()) + { + switch (type) + { + case cDebugConsoleMessage: + printf("Debug: %s", pMsg); + break; + case cWarningConsoleMessage: + printf("Warning: %s", pMsg); + break; + case cErrorConsoleMessage: + printf("Error: %s", pMsg); + break; + default: + printf("%s", pMsg); + break; + } + } + else + { + printf("%s", pMsg); + } + + if (console::get_crlf()) + printf("\n"); + + return true; + } +#endif + +} // namespace crnlib + diff --git a/crnlib/crn_colorized_console.h b/crnlib/crn_colorized_console.h new file mode 100644 index 0000000..57c3a46 --- /dev/null +++ b/crnlib/crn_colorized_console.h @@ -0,0 +1,19 @@ +// File: crn_colorized_console.h +// See Copyright Notice and license at the end of inc/crnlib.h +#pragma once +#include "crn_console.h" + +namespace crnlib +{ + class colorized_console + { + public: + static void init(); + static void deinit(); + static void tick(); + + private: + static bool console_output_func(eConsoleMessageType type, const char* pMsg, void* pData); + }; + +} // namespace crnlib diff --git a/crnlib/crn_file_utils.cpp b/crnlib/crn_file_utils.cpp new file mode 100644 index 0000000..1d7731d --- /dev/null +++ b/crnlib/crn_file_utils.cpp @@ -0,0 +1,557 @@ +// File: crn_file_utils.cpp +// See Copyright Notice and license at the end of inc/crnlib.h +#include "crn_core.h" +#include "crn_file_utils.h" +#include "crn_strutils.h" + +#if CRNLIB_USE_WIN32_API +#include "crn_winhdr.h" +#endif + +#ifdef WIN32 +#include +#endif + +#ifdef __GNUC__ +#include +#include +#include +#endif + +namespace crnlib +{ +#if CRNLIB_USE_WIN32_API + bool file_utils::is_read_only(const char* pFilename) + { + uint32 dst_file_attribs = GetFileAttributesA(pFilename); + if (dst_file_attribs == INVALID_FILE_ATTRIBUTES) + return false; + if (dst_file_attribs & FILE_ATTRIBUTE_READONLY) + return true; + return false; + } + + bool file_utils::disable_read_only(const char* pFilename) + { + uint32 dst_file_attribs = GetFileAttributesA(pFilename); + if (dst_file_attribs == INVALID_FILE_ATTRIBUTES) + return false; + if (dst_file_attribs & FILE_ATTRIBUTE_READONLY) + { + dst_file_attribs &= ~FILE_ATTRIBUTE_READONLY; + if (SetFileAttributesA(pFilename, dst_file_attribs)) + return true; + } + return false; + } + + bool file_utils::is_older_than(const char* pSrcFilename, const char* pDstFilename) + { + WIN32_FILE_ATTRIBUTE_DATA src_file_attribs; + const BOOL src_file_exists = GetFileAttributesExA(pSrcFilename, GetFileExInfoStandard, &src_file_attribs); + + WIN32_FILE_ATTRIBUTE_DATA dst_file_attribs; + const BOOL dest_file_exists = GetFileAttributesExA(pDstFilename, GetFileExInfoStandard, &dst_file_attribs); + + if ((dest_file_exists) && (src_file_exists)) + { + LONG timeComp = CompareFileTime(&src_file_attribs.ftLastWriteTime, &dst_file_attribs.ftLastWriteTime); + if (timeComp < 0) + return true; + } + return false; + } + + bool file_utils::does_file_exist(const char* pFilename) + { + const DWORD fullAttributes = GetFileAttributesA(pFilename); + + if (fullAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + if (fullAttributes & FILE_ATTRIBUTE_DIRECTORY) + return false; + + return true; + } + + bool file_utils::does_dir_exist(const char* pDir) + { + //-- Get the file attributes. + DWORD fullAttributes = GetFileAttributesA(pDir); + + if (fullAttributes == INVALID_FILE_ATTRIBUTES) + return false; + + if (fullAttributes & FILE_ATTRIBUTE_DIRECTORY) + return true; + + return false; + } + + bool file_utils::get_file_size(const char* pFilename, uint64& file_size) + { + file_size = 0; + + WIN32_FILE_ATTRIBUTE_DATA attr; + + if (0 == GetFileAttributesExA(pFilename, GetFileExInfoStandard, &attr)) + return false; + + if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return false; + + file_size = static_cast(attr.nFileSizeLow) | (static_cast(attr.nFileSizeHigh) << 32U); + + return true; + } +#elif defined( __GNUC__ ) + bool file_utils::is_read_only(const char* pFilename) + { + pFilename; + // TODO + return false; + } + + bool file_utils::disable_read_only(const char* pFilename) + { + pFilename; + // TODO + return false; + } + + bool file_utils::is_older_than(const char *pSrcFilename, const char* pDstFilename) + { + pSrcFilename, pDstFilename; + // TODO + return false; + } + + bool file_utils::does_file_exist(const char* pFilename) + { + struct stat stat_buf; + int result = stat(pFilename, &stat_buf); + if (result) + return false; + if (S_ISREG(stat_buf.st_mode)) + return true; + return false; + } + + bool file_utils::does_dir_exist(const char* pDir) + { + struct stat stat_buf; + int result = stat(pDir, &stat_buf); + if (result) + return false; + if (S_ISDIR(stat_buf.st_mode) || S_ISLNK(stat_buf.st_mode)) + return true; + return false; + } + + bool file_utils::get_file_size(const char* pFilename, uint64& file_size) + { + file_size = 0; + struct stat stat_buf; + int result = stat(pFilename, &stat_buf); + if (result) + return false; + if (!S_ISREG(stat_buf.st_mode)) + return false; + file_size = stat_buf.st_size; + return true; + } +#else + bool file_utils::is_read_only(const char* pFilename) + { + return false; + } + + bool file_utils::disable_read_only(const char* pFilename) + { + pFilename; + // TODO + return false; + } + + bool file_utils::is_older_than(const char *pSrcFilename, const char* pDstFilename) + { + return false; + } + + bool file_utils::does_file_exist(const char* pFilename) + { + FILE* pFile; + crn_fopen(&pFile, pFilename, "rb"); + if (!pFile) + return false; + fclose(pFile); + return true; + } + + bool file_utils::does_dir_exist(const char* pDir) + { + return false; + } + + bool file_utils::get_file_size(const char* pFilename, uint64& file_size) + { + FILE* pFile; + crn_fopen(&pFile, pFilename, "rb"); + if (!pFile) + return false; + crn_fseek(pFile, 0, SEEK_END); + file_size = crn_ftell(pFile); + fclose(pFile); + return true; + } +#endif + + bool file_utils::get_file_size(const char* pFilename, uint32& file_size) + { + uint64 file_size64; + if (!get_file_size(pFilename, file_size64)) + { + file_size = 0; + return false; + } + + if (file_size64 > cUINT32_MAX) + file_size64 = cUINT32_MAX; + + file_size = static_cast(file_size64); + return true; + } + + bool file_utils::is_path_separator(char c) + { +#ifdef WIN32 + return (c == '/') || (c == '\\'); +#else + return (c == '/'); +#endif + } + + bool file_utils::is_path_or_drive_separator(char c) + { +#ifdef WIN32 + return (c == '/') || (c == '\\') || (c == ':'); +#else + return (c == '/'); +#endif + } + + bool file_utils::is_drive_separator(char c) + { +#ifdef WIN32 + return (c == ':'); +#else + c; + return false; +#endif + } + + bool file_utils::split_path(const char* p, dynamic_string* pDrive, dynamic_string* pDir, dynamic_string* pFilename, dynamic_string* pExt) + { + CRNLIB_ASSERT(p); + +#ifdef WIN32 + char drive_buf[_MAX_DRIVE]; + char dir_buf[_MAX_DIR]; + char fname_buf[_MAX_FNAME]; + char ext_buf[_MAX_EXT]; + +#ifdef _MSC_VER + // Compiling with MSVC + errno_t error = _splitpath_s(p, + pDrive ? drive_buf : NULL, pDrive ? _MAX_DRIVE : 0, + pDir ? dir_buf : NULL, pDir ? _MAX_DIR : 0, + pFilename ? fname_buf : NULL, pFilename ? _MAX_FNAME : 0, + pExt ? ext_buf : NULL, pExt ? _MAX_EXT : 0); + if (error != 0) + return false; +#else + // Compiling with MinGW + _splitpath(p, + pDrive ? drive_buf : NULL, + pDir ? dir_buf : NULL, + pFilename ? fname_buf : NULL, + pExt ? ext_buf : NULL); +#endif + + if (pDrive) *pDrive = drive_buf; + if (pDir) *pDir = dir_buf; + if (pFilename) *pFilename = fname_buf; + if (pExt) *pExt = ext_buf; +#else + char dirtmp[1024]; + char nametmp[1024]; + strcpy_safe(dirtmp, sizeof(dirtmp), p); + strcpy_safe(nametmp, sizeof(nametmp), p); + + if (pDrive) pDrive->clear(); + + const char *pDirName = dirname(dirtmp); + if (!pDirName) + return false; + + if (pDir) + { + pDir->set(pDirName); + if ((!pDir->is_empty()) && (pDir->back() != '/')) + pDir->append_char('/'); + } + + const char *pBaseName = basename(nametmp); + if (!pBaseName) + return false; + + if (pFilename) + { + pFilename->set(pBaseName); + remove_extension(*pFilename); + } + + if (pExt) + { + pExt->set(pBaseName); + get_extension(*pExt); + *pExt = "." + *pExt; + } +#endif // #ifdef WIN32 + + return true; + } + + bool file_utils::split_path(const char* p, dynamic_string& path, dynamic_string& filename) + { + dynamic_string temp_drive, temp_path, temp_ext; + if (!split_path(p, &temp_drive, &temp_path, &filename, &temp_ext)) + return false; + + filename += temp_ext; + + combine_path(path, temp_drive.get_ptr(), temp_path.get_ptr()); + return true; + } + + bool file_utils::get_pathname(const char* p, dynamic_string& path) + { + dynamic_string temp_drive, temp_path; + if (!split_path(p, &temp_drive, &temp_path, NULL, NULL)) + return false; + + combine_path(path, temp_drive.get_ptr(), temp_path.get_ptr()); + return true; + } + + bool file_utils::get_filename(const char* p, dynamic_string& filename) + { + dynamic_string temp_ext; + if (!split_path(p, NULL, NULL, &filename, &temp_ext)) + return false; + + filename += temp_ext; + return true; + } + + void file_utils::combine_path(dynamic_string& dst, const char* pA, const char* pB) + { + dynamic_string temp(pA); + if ((!temp.is_empty()) && (!is_path_separator(pB[0]))) + { + char c = temp[temp.get_len() - 1]; + if (!is_path_separator(c)) + temp.append_char(CRNLIB_PATH_SEPERATOR_CHAR); + } + temp += pB; + dst.swap(temp); + } + + void file_utils::combine_path(dynamic_string& dst, const char* pA, const char* pB, const char* pC) + { + combine_path(dst, pA, pB); + combine_path(dst, dst.get_ptr(), pC); + } + + bool file_utils::full_path(dynamic_string& path) + { +#ifdef WIN32 + char buf[1024]; + char* p = _fullpath(buf, path.get_ptr(), sizeof(buf)); + if (!p) + return false; +#else + char buf[PATH_MAX]; + char* p; + dynamic_string pn, fn; + split_path(path.get_ptr(), pn, fn); + if ((fn == ".") || (fn == "..")) + { + p = realpath(path.get_ptr(), buf); + if (!p) + return false; + path.set(buf); + } + else + { + if (pn.is_empty()) + pn = "./"; + p = realpath(pn.get_ptr(), buf); + if (!p) + return false; + combine_path(path, buf, fn.get_ptr()); + } +#endif + + return true; + } + + bool file_utils::get_extension(dynamic_string& filename) + { + int sep = -1; +#ifdef WIN32 + sep = filename.find_right('\\'); +#endif + if (sep < 0) + sep = filename.find_right('/'); + + int dot = filename.find_right('.'); + if (dot < sep) + { + filename.clear(); + return false; + } + + filename.right(dot + 1); + + return true; + } + + bool file_utils::remove_extension(dynamic_string& filename) + { + int sep = -1; +#ifdef WIN32 + sep = filename.find_right('\\'); +#endif + if (sep < 0) + sep = filename.find_right('/'); + + int dot = filename.find_right('.'); + if (dot < sep) + return false; + + filename.left(dot); + + return true; + } + + bool file_utils::create_path(const dynamic_string& fullpath) + { + bool got_unc = false; got_unc; + dynamic_string cur_path; + + const int l = fullpath.get_len(); + + int n = 0; + while (n < l) + { + const char c = fullpath.get_ptr()[n]; + + const bool sep = is_path_separator(c); + const bool back_sep = is_path_separator(cur_path.back()); + const bool is_last_char = (n == (l - 1)); + + if ( ((sep) && (!back_sep)) || (is_last_char) ) + { + if ((is_last_char) && (!sep)) + cur_path.append_char(c); + + bool valid = !cur_path.is_empty(); + +#ifdef WIN32 + // reject obvious stuff (drives, beginning of UNC paths): + // c:\b\cool + // \\machine\blah + // \cool\blah + if ((cur_path.get_len() == 2) && (cur_path[1] == ':')) + valid = false; + else if ((cur_path.get_len() >= 2) && (cur_path[0] == '\\') && (cur_path[1] == '\\')) + { + if (!got_unc) + valid = false; + got_unc = true; + } + else if (cur_path == "\\") + valid = false; +#endif + if (cur_path == "/") + valid = false; + + if ((valid) && (cur_path.get_len())) + { +#ifdef WIN32 + _mkdir(cur_path.get_ptr()); +#else + mkdir(cur_path.get_ptr(), S_IRWXU | S_IRWXG | S_IRWXO ); +#endif + } + } + + cur_path.append_char(c); + + n++; + } + + return true; + } + + void file_utils::trim_trailing_seperator(dynamic_string& path) + { + if ((path.get_len()) && (is_path_separator(path.back()))) + path.truncate(path.get_len() - 1); + } + + // See http://www.codeproject.com/KB/string/wildcmp.aspx + int file_utils::wildcmp(const char* pWild, const char* pString) + { + const char* cp = NULL, *mp = NULL; + + while ((*pString) && (*pWild != '*')) + { + if ((*pWild != *pString) && (*pWild != '?')) + return 0; + pWild++; + pString++; + } + + // Either *pString=='\0' or *pWild='*' here. + + while (*pString) + { + if (*pWild == '*') + { + if (!*++pWild) + return 1; + mp = pWild; + cp = pString+1; + } + else if ((*pWild == *pString) || (*pWild == '?')) + { + pWild++; + pString++; + } + else + { + pWild = mp; + pString = cp++; + } + } + + while (*pWild == '*') + pWild++; + + return !*pWild; + } + +} // namespace crnlib diff --git a/crnlib/crn_file_utils.h b/crnlib/crn_file_utils.h new file mode 100644 index 0000000..e71f223 --- /dev/null +++ b/crnlib/crn_file_utils.h @@ -0,0 +1,48 @@ +// File: crn_file_utils.h +// See Copyright Notice and license at the end of inc/crnlib.h +#pragma once + +namespace crnlib +{ + struct file_utils + { + // Returns true if pSrcFilename is older than pDstFilename + static bool is_read_only(const char* pFilename); + static bool disable_read_only(const char* pFilename); + static bool is_older_than(const char *pSrcFilename, const char* pDstFilename); + static bool does_file_exist(const char* pFilename); + static bool does_dir_exist(const char* pDir); + static bool get_file_size(const char* pFilename, uint64& file_size); + static bool get_file_size(const char* pFilename, uint32& file_size); + + static bool is_path_separator(char c); + static bool is_path_or_drive_separator(char c); + static bool is_drive_separator(char c); + + static bool split_path(const char* p, dynamic_string* pDrive, dynamic_string* pDir, dynamic_string* pFilename, dynamic_string* pExt); + + static bool split_path(const char* p, dynamic_string& path, dynamic_string& filename); + + static bool get_pathname(const char* p, dynamic_string& path); + + static bool get_filename(const char* p, dynamic_string& filename); + + static void combine_path(dynamic_string& dst, const char* pA, const char* pB); + + static void combine_path(dynamic_string& dst, const char* pA, const char* pB, const char* pC); + + static bool full_path(dynamic_string& path); + + static bool get_extension(dynamic_string& filename); + + static bool remove_extension(dynamic_string& filename); + + static bool create_path(const dynamic_string& path); + + static void trim_trailing_seperator(dynamic_string& path); + + static int wildcmp(const char* pWild, const char* pString); + + }; // struct file_utils + +} // namespace crnlib diff --git a/crnlib/crn_find_files.cpp b/crnlib/crn_find_files.cpp new file mode 100644 index 0000000..39a1324 --- /dev/null +++ b/crnlib/crn_find_files.cpp @@ -0,0 +1,287 @@ +// File: crn_win32_find_files.cpp +// See Copyright Notice and license at the end of inc/crnlib.h +#include "crn_core.h" +#include "crn_find_files.h" +#include "crn_file_utils.h" +#include "crn_strutils.h" + +#ifdef CRNLIB_USE_WIN32_API +#include "crn_winhdr.h" + +#elif defined(__GNUC__) +#include +#include +#endif + +namespace crnlib +{ +#ifdef CRNLIB_USE_WIN32_API + bool find_files::find(const char* pBasepath, const char* pFilespec, uint flags) + { + m_last_error = S_OK; + m_files.resize(0); + + return find_internal(pBasepath, "", pFilespec, flags, 0); + } + + bool find_files::find(const char* pSpec, uint flags) + { + dynamic_string find_name(pSpec); + + if (!file_utils::full_path(find_name)) + return false; + + dynamic_string find_pathname, find_filename; + if (!file_utils::split_path(find_name.get_ptr(), find_pathname, find_filename)) + return false; + + return find(find_pathname.get_ptr(), find_filename.get_ptr(), flags); + } + + bool find_files::find_internal(const char* pBasepath, const char* pRelpath, const char* pFilespec, uint flags, int level) + { + WIN32_FIND_DATAA find_data; + + dynamic_string filename; + + dynamic_string_array child_paths; + if (flags & cFlagRecursive) + { + if (strlen(pRelpath)) + file_utils::combine_path(filename, pBasepath, pRelpath, "*"); + else + file_utils::combine_path(filename, pBasepath, "*"); + + HANDLE handle = FindFirstFileA(filename.get_ptr(), &find_data); + if (handle == INVALID_HANDLE_VALUE) + { + HRESULT hres = GetLastError(); + if ((level == 0) && (hres != NO_ERROR) && (hres != ERROR_FILE_NOT_FOUND)) + { + m_last_error = hres; + return false; + } + } + else + { + do + { + const bool is_dir = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + + bool skip = !is_dir; + if (is_dir) + skip = (strcmp(find_data.cFileName, ".") == 0) || (strcmp(find_data.cFileName, "..") == 0); + + if (find_data.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY)) + skip = true; + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) + { + if ((flags & cFlagAllowHidden) == 0) + skip = true; + } + + if (!skip) + { + dynamic_string child_path(find_data.cFileName); + if ((!child_path.count_char('?')) && (!child_path.count_char('*'))) + child_paths.push_back(child_path); + } + + } while (FindNextFileA(handle, &find_data) != 0); + + HRESULT hres = GetLastError(); + + FindClose(handle); + handle = INVALID_HANDLE_VALUE; + + if (hres != ERROR_NO_MORE_FILES) + { + m_last_error = hres; + return false; + } + } + } + + if (strlen(pRelpath)) + file_utils::combine_path(filename, pBasepath, pRelpath, pFilespec); + else + file_utils::combine_path(filename, pBasepath, pFilespec); + + HANDLE handle = FindFirstFileA(filename.get_ptr(), &find_data); + if (handle == INVALID_HANDLE_VALUE) + { + HRESULT hres = GetLastError(); + if ((level == 0) && (hres != NO_ERROR) && (hres != ERROR_FILE_NOT_FOUND)) + { + m_last_error = hres; + return false; + } + } + else + { + do + { + const bool is_dir = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + + bool skip = false; + if (is_dir) + skip = (strcmp(find_data.cFileName, ".") == 0) || (strcmp(find_data.cFileName, "..") == 0); + + if (find_data.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY)) + skip = true; + + if (find_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) + { + if ((flags & cFlagAllowHidden) == 0) + skip = true; + } + + if (!skip) + { + if (((is_dir) && (flags & cFlagAllowDirs)) || ((!is_dir) && (flags & cFlagAllowFiles))) + { + m_files.resize(m_files.size() + 1); + file_desc& file = m_files.back(); + file.m_is_dir = is_dir; + file.m_base = pBasepath; + file.m_name = find_data.cFileName; + file.m_rel = pRelpath; + if (strlen(pRelpath)) + file_utils::combine_path(file.m_fullname, pBasepath, pRelpath, find_data.cFileName); + else + file_utils::combine_path(file.m_fullname, pBasepath, find_data.cFileName); + } + } + + } while (FindNextFileA(handle, &find_data) != 0); + + HRESULT hres = GetLastError(); + + FindClose(handle); + + if (hres != ERROR_NO_MORE_FILES) + { + m_last_error = hres; + return false; + } + } + + for (uint i = 0; i < child_paths.size(); i++) + { + dynamic_string child_path; + if (strlen(pRelpath)) + file_utils::combine_path(child_path, pRelpath, child_paths[i].get_ptr()); + else + child_path = child_paths[i]; + + if (!find_internal(pBasepath, child_path.get_ptr(), pFilespec, flags, level + 1)) + return false; + } + + return true; + } +#elif defined(__GNUC__) + bool find_files::find(const char* pBasepath, const char* pFilespec, uint flags) + { + m_files.resize(0); + return find_internal(pBasepath, "", pFilespec, flags, 0); + } + + bool find_files::find(const char* pSpec, uint flags) + { + dynamic_string find_name(pSpec); + + if (!file_utils::full_path(find_name)) + return false; + + dynamic_string find_pathname, find_filename; + if (!file_utils::split_path(find_name.get_ptr(), find_pathname, find_filename)) + return false; + + return find(find_pathname.get_ptr(), find_filename.get_ptr(), flags); + } + + bool find_files::find_internal(const char* pBasepath, const char* pRelpath, const char* pFilespec, uint flags, int level) + { + dynamic_string pathname; + if (strlen(pRelpath)) + file_utils::combine_path(pathname, pBasepath, pRelpath); + else + pathname = pBasepath; + + if (!pathname.is_empty()) + { + char c = pathname.back(); + if (c != '/') + pathname += "/"; + } + + DIR *dp = opendir(pathname.get_ptr()); + + if (!dp) + return level ? true : false; + + dynamic_string_array paths; + + for ( ; ; ) + { + struct dirent *ep = readdir(dp); + if (!ep) + break; + if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) + continue; + + const bool is_directory = (ep->d_type & DT_DIR) != 0; + const bool is_file = (ep->d_type & DT_REG) != 0; + + dynamic_string filename(ep->d_name); + + if (is_directory) + { + if (flags & cFlagRecursive) + { + paths.push_back(filename); + } + } + + if (((is_file) && (flags & cFlagAllowFiles)) || ((is_directory) && (flags & cFlagAllowDirs))) + { + if (0 == fnmatch(pFilespec, filename.get_ptr(), 0)) + { + m_files.resize(m_files.size() + 1); + file_desc& file = m_files.back(); + file.m_is_dir = is_directory; + file.m_base = pBasepath; + file.m_rel = pRelpath; + file.m_name = filename; + file.m_fullname = pathname + filename; + } + } + } + + closedir(dp); + dp = NULL; + + if (flags & cFlagRecursive) + { + for (uint i = 0; i < paths.size(); i++) + { + dynamic_string childpath; + if (strlen(pRelpath)) + file_utils::combine_path(childpath, pRelpath, paths[i].get_ptr()); + else + childpath = paths[i]; + + if (!find_internal(pBasepath, childpath.get_ptr(), pFilespec, flags, level + 1)) + return false; + } + } + + return true; + } +#else + #error Unimplemented +#endif + +} // namespace crnlib diff --git a/crnlib/crn_find_files.h b/crnlib/crn_find_files.h new file mode 100644 index 0000000..967ed55 --- /dev/null +++ b/crnlib/crn_find_files.h @@ -0,0 +1,60 @@ +// File: crn_win32_find_files.h +// See Copyright Notice and license at the end of inc/crnlib.h +#pragma once + +namespace crnlib +{ + class find_files + { + public: + struct file_desc + { + inline file_desc() : m_is_dir(false) { } + + dynamic_string m_fullname; + dynamic_string m_base; + dynamic_string m_rel; + dynamic_string m_name; + bool m_is_dir; + + inline bool operator== (const file_desc& other) const { return m_fullname == other.m_fullname; } + inline bool operator< (const file_desc& other) const { return m_fullname < other.m_fullname; } + + inline operator size_t() const { return static_cast(m_fullname); } + }; + + typedef crnlib::vector file_desc_vec; + + inline find_files() + { + m_last_error = 0; // S_OK; + } + + enum flags + { + cFlagRecursive = 1, + cFlagAllowDirs = 2, + cFlagAllowFiles = 4, + cFlagAllowHidden = 8 + }; + + bool find(const char* pBasepath, const char* pFilespec, uint flags = cFlagAllowFiles); + + bool find(const char* pSpec, uint flags = cFlagAllowFiles); + + // An HRESULT under Win32. FIXME: Abstract this better? + inline int64 get_last_error() const { return m_last_error; } + + const file_desc_vec& get_files() const { return m_files; } + + private: + file_desc_vec m_files; + + // A HRESULT under Win32 + int64 m_last_error; + + bool find_internal(const char* pBasepath, const char* pRelpath, const char* pFilespec, uint flags, int level); + + }; // class find_files + +} // namespace crnlib diff --git a/crnlib/crn_freeimage_image_utils.h b/crnlib/crn_freeimage_image_utils.h new file mode 100644 index 0000000..0597d80 --- /dev/null +++ b/crnlib/crn_freeimage_image_utils.h @@ -0,0 +1,158 @@ +// File: crn_freeimage_image_utils.h +// See Copyright Notice and license at the end of inc/crnlib.h +// Note: This header file requires FreeImage/FreeImagePlus. + +#include "crn_image_utils.h" + +#include "freeImagePlus.h" + +namespace crnlib +{ + namespace freeimage_image_utils + { + inline bool load_from_file(image_u8& dest, const wchar_t* pFilename, int fi_flag) + { + fipImage src_image; + + if (!src_image.loadU(pFilename, fi_flag)) + return false; + + const uint orig_bits_per_pixel = src_image.getBitsPerPixel(); + + const FREE_IMAGE_COLOR_TYPE orig_color_type = src_image.getColorType(); + + if (!src_image.convertTo32Bits()) + return false; + + if (src_image.getBitsPerPixel() != 32) + return false; + + uint width = src_image.getWidth(); + uint height = src_image.getHeight(); + + dest.resize(src_image.getWidth(), src_image.getHeight(), src_image.getWidth()); + + color_quad_u8* pDst = dest.get_ptr(); + + bool grayscale = true; + bool has_alpha = false; + for (uint y = 0; y < height; y++) + { + const BYTE* pSrc = src_image.getScanLine((WORD)(height - 1 - y)); + color_quad_u8* pD = pDst; + + for (uint x = width; x; x--) + { + color_quad_u8 c; + c.r = pSrc[FI_RGBA_RED]; + c.g = pSrc[FI_RGBA_GREEN]; + c.b = pSrc[FI_RGBA_BLUE]; + c.a = pSrc[FI_RGBA_ALPHA]; + + if (!c.is_grayscale()) + grayscale = false; + has_alpha |= (c.a < 255); + + pSrc += 4; + *pD++ = c; + } + + pDst += width; + } + + dest.reset_comp_flags(); + + if (grayscale) + dest.set_grayscale(true); + + dest.set_component_valid(3, has_alpha || (orig_color_type == FIC_RGBALPHA) || (orig_bits_per_pixel == 32)); + + return true; + } + + const int cSaveLuma = -1; + + inline bool save_to_grayscale_file(const wchar_t* pFilename, const image_u8& src, int component, int fi_flag) + { + fipImage dst_image(FIT_BITMAP, (WORD)src.get_width(), (WORD)src.get_height(), 8); + + RGBQUAD* p = dst_image.getPalette(); + for (uint i = 0; i < dst_image.getPaletteSize(); i++) + { + p[i].rgbRed = (BYTE)i; + p[i].rgbGreen = (BYTE)i; + p[i].rgbBlue = (BYTE)i; + p[i].rgbReserved = 255; + } + + for (uint y = 0; y < src.get_height(); y++) + { + const color_quad_u8* pSrc = src.get_scanline(y); + + for (uint x = 0; x < src.get_width(); x++) + { + BYTE v; + if (component == cSaveLuma) + v = (BYTE)(*pSrc).get_luma(); + else + v = (*pSrc)[component]; + dst_image.setPixelIndex(x, src.get_height() - 1 - y, &v); + + pSrc++; + } + } + + if (!dst_image.saveU(pFilename, fi_flag)) + return false; + + return true; + } + + inline bool save_to_file(const wchar_t* pFilename, const image_u8& src, int fi_flag, bool ignore_alpha = false) + { + const bool save_alpha = src.is_component_valid(3); + uint bpp = (save_alpha && !ignore_alpha) ? 32 : 24; + + if (bpp == 32) + { + dynamic_wstring ext(pFilename); + get_extension(ext); + + if ((ext == L"jpg") || (ext == L"jpeg") || (ext == L"gif") || (ext == L"jp2")) + bpp = 24; + } + + if ((bpp == 24) && (src.is_grayscale())) + return save_to_grayscale_file(pFilename, src, cSaveLuma, fi_flag); + + fipImage dst_image(FIT_BITMAP, (WORD)src.get_width(), (WORD)src.get_height(), (WORD)bpp); + + for (uint y = 0; y < src.get_height(); y++) + { + for (uint x = 0; x < src.get_width(); x++) + { + color_quad_u8 c(src(x, y)); + + RGBQUAD quad; + quad.rgbRed = c.r; + quad.rgbGreen = c.g; + quad.rgbBlue = c.b; + if (bpp == 32) + quad.rgbReserved = c.a; + else + quad.rgbReserved = 255; + + dst_image.setPixelColor(x, src.get_height() - 1 - y, &quad); + } + } + + if (!dst_image.saveU(pFilename, fi_flag)) + return false; + + return true; + } + + } // namespace freeimage_image_utils + +} // namespace crnlib + diff --git a/crnlib/crn_threading.h b/crnlib/crn_threading.h new file mode 100644 index 0000000..55463af --- /dev/null +++ b/crnlib/crn_threading.h @@ -0,0 +1,10 @@ +// File: crn_threading.h +// See Copyright Notice and license at the end of inc/crnlib.h + +#if CRNLIB_USE_WIN32_API + #include "crn_threading_win32.h" +#elif CRNLIB_USE_PTHREADS_API + #include "crn_threading_pthreads.h" +#else + #include "crn_threading_null.h" +#endif diff --git a/crnlib/crn_threading_null.h b/crnlib/crn_threading_null.h new file mode 100644 index 0000000..c52bef7 --- /dev/null +++ b/crnlib/crn_threading_null.h @@ -0,0 +1,192 @@ +// File: crn_threading_null.h +// See Copyright Notice and license at the end of include/crnlib.h +#pragma once + +#include "crn_atomics.h" + +namespace crnlib +{ + const uint g_number_of_processors = 1; + + inline void crn_threading_init() + { + } + + typedef uint64 crn_thread_id_t; + inline crn_thread_id_t crn_get_current_thread_id() + { + return 0; + } + + inline void crn_sleep(unsigned int milliseconds) + { + milliseconds; + } + + inline uint crn_get_max_helper_threads() + { + return 0; + } + + class mutex + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(mutex); + + public: + inline mutex(unsigned int spin_count = 0) + { + spin_count; + } + + inline ~mutex() + { + } + + inline void lock() + { + } + + inline void unlock() + { + } + + inline void set_spin_count(unsigned int count) + { + count; + } + }; + + class scoped_mutex + { + scoped_mutex(const scoped_mutex&); + scoped_mutex& operator= (const scoped_mutex&); + + public: + inline scoped_mutex(mutex& lock) : m_lock(lock) { m_lock.lock(); } + inline ~scoped_mutex() { m_lock.unlock(); } + + private: + mutex& m_lock; + }; + + // Simple non-recursive spinlock. + class spinlock + { + public: + inline spinlock() + { + } + + inline void lock(uint32 max_spins = 4096, bool yielding = true, bool memoryBarrier = true) + { + max_spins, yielding, memoryBarrier; + } + + inline void lock_no_barrier(uint32 max_spins = 4096, bool yielding = true) + { + max_spins, yielding; + } + + inline void unlock() + { + } + + inline void unlock_no_barrier() + { + } + }; + + class scoped_spinlock + { + scoped_spinlock(const scoped_spinlock&); + scoped_spinlock& operator= (const scoped_spinlock&); + + public: + inline scoped_spinlock(spinlock& lock) : m_lock(lock) { m_lock.lock(); } + inline ~scoped_spinlock() { m_lock.unlock(); } + + private: + spinlock& m_lock; + }; + + class semaphore + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(semaphore); + + public: + inline semaphore(long initialCount = 0, long maximumCount = 1, const char* pName = NULL) + { + initialCount, maximumCount, pName; + } + + inline ~semaphore() + { + } + + inline void release(long releaseCount = 1, long *pPreviousCount = NULL) + { + releaseCount, pPreviousCount; + } + + inline bool wait(uint32 milliseconds = cUINT32_MAX) + { + milliseconds; + return true; + } + }; + + class task_pool + { + public: + inline task_pool() { } + inline task_pool(uint num_threads) { num_threads; } + inline ~task_pool() { } + + inline bool init(uint num_threads) { num_threads; return true; } + inline void deinit() { } + + inline uint get_num_threads() const { return 0; } + inline uint get_num_outstanding_tasks() const { return 0; } + + // C-style task callback + typedef void (*task_callback_func)(uint64 data, void* pData_ptr); + inline bool queue_task(task_callback_func pFunc, uint64 data = 0, void* pData_ptr = NULL) + { + pFunc(data, pData_ptr); + return true; + } + + class executable_task + { + public: + virtual void execute_task(uint64 data, void* pData_ptr) = 0; + }; + + // It's the caller's responsibility to delete pObj within the execute_task() method, if needed! + inline bool queue_task(executable_task* pObj, uint64 data = 0, void* pData_ptr = NULL) + { + pObj->execute_task(data, pData_ptr); + return true; + } + + template + inline bool queue_object_task(S* pObject, T pObject_method, uint64 data = 0, void* pData_ptr = NULL) + { + (pObject->*pObject_method)(data, pData_ptr); + return true; + } + + template + inline bool queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr = NULL) + { + for (uint i = 0; i < num_tasks; i++) + { + (pObject->*pObject_method)(first_data + i, pData_ptr); + } + return true; + } + + inline void join() { } + }; + +} // namespace crnlib diff --git a/crnlib/crn_threading_pthreads.cpp b/crnlib/crn_threading_pthreads.cpp new file mode 100644 index 0000000..52f019c --- /dev/null +++ b/crnlib/crn_threading_pthreads.cpp @@ -0,0 +1,410 @@ +// File: crn_threading_pthreads.cpp +// See Copyright Notice and license at the end of include/crnlib.h +#include "crn_core.h" +#include "crn_threading_pthreads.h" +#include "crn_timer.h" + +#if CRNLIB_USE_PTHREADS_API + +#ifdef WIN32 +#pragma comment(lib, "../ext/libpthread/lib/pthreadVC2.lib") +#include "crn_winhdr.h" +#endif + +#ifdef __GNUC__ +#include +#endif + +#ifdef WIN32 +#include +#endif + +namespace crnlib +{ + uint g_number_of_processors = 1; + + void crn_threading_init() + { +#ifdef WIN32 + SYSTEM_INFO g_system_info; + GetSystemInfo(&g_system_info); + g_number_of_processors = math::maximum(1U, g_system_info.dwNumberOfProcessors); +#elif defined(__GNUC__) + g_number_of_processors = math::maximum(1, get_nprocs()); +#else + g_number_of_processors = 1; +#endif + } + + crn_thread_id_t crn_get_current_thread_id() + { + // FIXME: Not portable + return static_cast(pthread_self()); + } + + void crn_sleep(unsigned int milliseconds) + { +#ifdef WIN32 + struct timespec interval; + interval.tv_sec = milliseconds / 1000; + interval.tv_nsec = (milliseconds % 1000) * 1000000L; + pthread_delay_np(&interval); +#else + while (milliseconds) + { + int msecs_to_sleep = CRNLIB_MIN(milliseconds, 1000); + usleep(msecs_to_sleep * 1000); + milliseconds -= msecs_to_sleep; + } +#endif + } + + mutex::mutex(unsigned int spin_count) + { + spin_count; + + if (pthread_mutex_init(&m_mutex, NULL)) + crnlib_fail("mutex::mutex: pthread_mutex_init() failed", __FILE__, __LINE__); + +#ifdef CRNLIB_BUILD_DEBUG + m_lock_count = 0; +#endif + } + + mutex::~mutex() + { +#ifdef CRNLIB_BUILD_DEBUG + if (m_lock_count) + crnlib_assert("mutex::~mutex: mutex is still locked", __FILE__, __LINE__); +#endif + if (pthread_mutex_destroy(&m_mutex)) + crnlib_assert("mutex::~mutex: pthread_mutex_destroy() failed", __FILE__, __LINE__); + } + + void mutex::lock() + { + pthread_mutex_lock(&m_mutex); +#ifdef CRNLIB_BUILD_DEBUG + m_lock_count++; +#endif + } + + void mutex::unlock() + { +#ifdef CRNLIB_BUILD_DEBUG + if (!m_lock_count) + crnlib_assert("mutex::unlock: mutex is not locked", __FILE__, __LINE__); + m_lock_count--; +#endif + pthread_mutex_unlock(&m_mutex); + } + + void mutex::set_spin_count(unsigned int count) + { + count; + } + + semaphore::semaphore(long initialCount, long maximumCount, const char* pName) + { + maximumCount, pName; + CRNLIB_ASSERT(maximumCount >= initialCount); + if (sem_init(&m_sem, 0, initialCount)) + { + CRNLIB_FAIL("semaphore: sem_init() failed"); + } + } + + semaphore::~semaphore() + { + sem_destroy(&m_sem); + } + + void semaphore::release(long releaseCount) + { + CRNLIB_ASSERT(releaseCount >= 1); + + int status = 0; +#ifdef WIN32 + if (1 == releaseCount) + status = sem_post(&m_sem); + else + status = sem_post_multiple(&m_sem, releaseCount); +#else + while (releaseCount > 0) + { + status = sem_post(&m_sem); + if (status) + break; + releaseCount--; + } +#endif + + if (status) + { + CRNLIB_FAIL("semaphore: sem_post() or sem_post_multiple() failed"); + } + } + + void semaphore::try_release(long releaseCount) + { + CRNLIB_ASSERT(releaseCount >= 1); + +#ifdef WIN32 + if (1 == releaseCount) + sem_post(&m_sem); + else + sem_post_multiple(&m_sem, releaseCount); +#else + while (releaseCount > 0) + { + sem_post(&m_sem); + releaseCount--; + } +#endif + } + + bool semaphore::wait(uint32 milliseconds) + { + int status; + if (milliseconds == cUINT32_MAX) + { + status = sem_wait(&m_sem); + } + else + { + struct timespec interval; + interval.tv_sec = milliseconds / 1000; + interval.tv_nsec = (milliseconds % 1000) * 1000000L; + status = sem_timedwait(&m_sem, &interval); + } + + if (status) + { + if (errno != ETIMEDOUT) + { + CRNLIB_FAIL("semaphore: sem_wait() or sem_timedwait() failed"); + } + return false; + } + + return true; + } + + spinlock::spinlock() + { + if (pthread_spin_init(&m_spinlock, 0)) + { + CRNLIB_FAIL("spinlock: pthread_spin_init() failed"); + } + } + + spinlock::~spinlock() + { + pthread_spin_destroy(&m_spinlock); + } + + void spinlock::lock() + { + if (pthread_spin_lock(&m_spinlock)) + { + CRNLIB_FAIL("spinlock: pthread_spin_lock() failed"); + } + } + + void spinlock::unlock() + { + if (pthread_spin_unlock(&m_spinlock)) + { + CRNLIB_FAIL("spinlock: pthread_spin_unlock() failed"); + } + } + + task_pool::task_pool() : + m_num_threads(0), + m_tasks_available(0, 32767), + m_all_tasks_completed(0, 1), + m_total_submitted_tasks(0), + m_total_completed_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + } + + task_pool::task_pool(uint num_threads) : + m_num_threads(0), + m_tasks_available(0, 32767), + m_all_tasks_completed(0, 1), + m_total_submitted_tasks(0), + m_total_completed_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + + bool status = init(num_threads); + CRNLIB_VERIFY(status); + } + + task_pool::~task_pool() + { + deinit(); + } + + bool task_pool::init(uint num_threads) + { + CRNLIB_ASSERT(num_threads <= cMaxThreads); + num_threads = math::minimum(num_threads, cMaxThreads); + + deinit(); + + bool succeeded = true; + + m_num_threads = 0; + while (m_num_threads < num_threads) + { + int status = pthread_create(&m_threads[m_num_threads], NULL, thread_func, this); + if (status) + { + succeeded = false; + break; + } + + m_num_threads++; + } + + if (!succeeded) + { + deinit(); + return false; + } + + return true; + } + + void task_pool::deinit() + { + if (m_num_threads) + { + join(); + + atomic_exchange32(&m_exit_flag, true); + + m_tasks_available.release(m_num_threads); + + for (uint i = 0; i < m_num_threads; i++) + pthread_join(m_threads[i], NULL); + + m_num_threads = 0; + + atomic_exchange32(&m_exit_flag, false); + } + + m_task_stack.clear(); + m_total_submitted_tasks = 0; + m_total_completed_tasks = 0; + } + + bool task_pool::queue_task(task_callback_func pFunc, uint64 data, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pFunc); + + task tsk; + tsk.m_callback = pFunc; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = 0; + + atomic_increment32(&m_total_submitted_tasks); + if (!m_task_stack.try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + return false; + } + + m_tasks_available.release(1); + + return true; + } + + // It's the object's responsibility to delete pObj within the execute_task() method, if needed! + bool task_pool::queue_task(executable_task* pObj, uint64 data, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pObj); + + task tsk; + tsk.m_pObj = pObj; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + atomic_increment32(&m_total_submitted_tasks); + if (!m_task_stack.try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + return false; + } + + m_tasks_available.release(1); + + return true; + } + + void task_pool::process_task(task& tsk) + { + if (tsk.m_flags & cTaskFlagObject) + tsk.m_pObj->execute_task(tsk.m_data, tsk.m_pData_ptr); + else + tsk.m_callback(tsk.m_data, tsk.m_pData_ptr); + + if (atomic_increment32(&m_total_completed_tasks) == m_total_submitted_tasks) + { + // Try to signal the semaphore (the max count is 1 so this may actually fail). + m_all_tasks_completed.try_release(); + } + } + + void task_pool::join() + { + // Try to steal any outstanding tasks. This could cause one or more worker threads to wake up and immediately go back to sleep, which is wasteful but should be harmless. + task tsk; + while (m_task_stack.pop(tsk)) + process_task(tsk); + + // At this point the task stack is empty. + // Now wait for all concurrent tasks to complete. The m_all_tasks_completed semaphore has a max count of 1, so it's possible it could have saturated to 1 as the tasks + // where issued and asynchronously completed, so this loop may iterate a few times. + const int total_submitted_tasks = atomic_add32(&m_total_submitted_tasks, 0); + while (m_total_completed_tasks != total_submitted_tasks) + { + // If the previous (m_total_completed_tasks != total_submitted_tasks) check failed the semaphore MUST be eventually signalled once the last task completes. + // So I think this can actually be an INFINITE delay, but it shouldn't really matter if it's 1ms. + m_all_tasks_completed.wait(1); + } + } + + void * task_pool::thread_func(void *pContext) + { + task_pool* pPool = static_cast(pContext); + task tsk; + + for ( ; ; ) + { + if (!pPool->m_tasks_available.wait()) + break; + + if (pPool->m_exit_flag) + break; + + if (pPool->m_task_stack.pop(tsk)) + { + pPool->process_task(tsk); + } + } + + return NULL; + } + +} // namespace crnlib + +#endif // CRNLIB_USE_PTHREADS_API diff --git a/crnlib/crn_threading_pthreads.h b/crnlib/crn_threading_pthreads.h new file mode 100644 index 0000000..55caa88 --- /dev/null +++ b/crnlib/crn_threading_pthreads.h @@ -0,0 +1,347 @@ +// File: crn_threading_pthreads.h +// See Copyright Notice and license at the end of include/crnlib.h +#pragma once + +#if CRNLIB_USE_PTHREADS_API + +#include "crn_atomics.h" + +#if CRNLIB_NO_ATOMICS +#error No atomic operations defined in crn_platform.h! +#endif + +#include +#include +#include + +namespace crnlib +{ + // g_number_of_processors defaults to 1. Will be higher on multicore machines. + extern uint g_number_of_processors; + + void crn_threading_init(); + + typedef uint64 crn_thread_id_t; + crn_thread_id_t crn_get_current_thread_id(); + + void crn_sleep(unsigned int milliseconds); + + uint crn_get_max_helper_threads(); + + class mutex + { + mutex(const mutex&); + mutex& operator= (const mutex&); + + public: + mutex(unsigned int spin_count = 0); + ~mutex(); + void lock(); + void unlock(); + void set_spin_count(unsigned int count); + + private: + pthread_mutex_t m_mutex; + +#ifdef CRNLIB_BUILD_DEBUG + unsigned int m_lock_count; +#endif + }; + + class scoped_mutex + { + scoped_mutex(const scoped_mutex&); + scoped_mutex& operator= (const scoped_mutex&); + + public: + inline scoped_mutex(mutex& m) : m_mutex(m) { m_mutex.lock(); } + inline ~scoped_mutex() { m_mutex.unlock(); } + + private: + mutex& m_mutex; + }; + + class semaphore + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(semaphore); + + public: + semaphore(long initialCount = 0, long maximumCount = 1, const char* pName = NULL); + ~semaphore(); + + void release(long releaseCount = 1); + void try_release(long releaseCount = 1); + bool wait(uint32 milliseconds = cUINT32_MAX); + + private: + sem_t m_sem; + }; + + class spinlock + { + public: + spinlock(); + ~spinlock(); + + void lock(); + void unlock(); + + private: + pthread_spinlock_t m_spinlock; + }; + + class scoped_spinlock + { + scoped_spinlock(const scoped_spinlock&); + scoped_spinlock& operator= (const scoped_spinlock&); + + public: + inline scoped_spinlock(spinlock& lock) : m_lock(lock) { m_lock.lock(); } + inline ~scoped_spinlock() { m_lock.unlock(); } + + private: + spinlock& m_lock; + }; + + template + class tsstack + { + public: + inline tsstack() : + m_top(0) + { + } + + inline ~tsstack() + { + } + + inline void clear() + { + m_spinlock.lock(); + m_top = 0; + m_spinlock.unlock(); + } + + inline bool try_push(const T& obj) + { + bool result = false; + m_spinlock.lock(); + if (m_top < (int)cMaxSize) + { + m_stack[m_top++] = obj; + result = true; + } + m_spinlock.unlock(); + return result; + } + + inline bool pop(T& obj) + { + bool result = false; + m_spinlock.lock(); + if (m_top > 0) + { + obj = m_stack[--m_top]; + result = true; + } + m_spinlock.unlock(); + return result; + } + + private: + spinlock m_spinlock; + T m_stack[cMaxSize]; + int m_top; + }; + + class task_pool + { + public: + task_pool(); + task_pool(uint num_threads); + ~task_pool(); + + enum { cMaxThreads = 16 }; + bool init(uint num_threads); + void deinit(); + + inline uint get_num_threads() const { return m_num_threads; } + inline uint32 get_num_outstanding_tasks() const { return m_total_submitted_tasks - m_total_completed_tasks; } + + // C-style task callback + typedef void (*task_callback_func)(uint64 data, void* pData_ptr); + bool queue_task(task_callback_func pFunc, uint64 data = 0, void* pData_ptr = NULL); + + class executable_task + { + public: + virtual void execute_task(uint64 data, void* pData_ptr) = 0; + }; + + // It's the caller's responsibility to delete pObj within the execute_task() method, if needed! + bool queue_task(executable_task* pObj, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_object_task(S* pObject, T pObject_method, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr = NULL); + + void join(); + + private: + struct task + { + inline task() : m_data(0), m_pData_ptr(NULL), m_pObj(NULL), m_flags(0) { } + + uint64 m_data; + void* m_pData_ptr; + + union + { + task_callback_func m_callback; + executable_task* m_pObj; + }; + + uint m_flags; + }; + + tsstack m_task_stack; + + uint m_num_threads; + pthread_t m_threads[cMaxThreads]; + + // Signalled whenever a task is queued up. + semaphore m_tasks_available; + + // Signalled when all outstanding tasks are completed. + semaphore m_all_tasks_completed; + + enum task_flags + { + cTaskFlagObject = 1 + }; + + volatile atomic32_t m_total_submitted_tasks; + volatile atomic32_t m_total_completed_tasks; + volatile atomic32_t m_exit_flag; + + void process_task(task& tsk); + + static void* thread_func(void *pContext); + }; + + enum object_task_flags + { + cObjectTaskFlagDefault = 0, + cObjectTaskFlagDeleteAfterExecution = 1 + }; + + template + class object_task : public task_pool::executable_task + { + public: + object_task(uint flags = cObjectTaskFlagDefault) : + m_pObject(NULL), + m_pMethod(NULL), + m_flags(flags) + { + } + + typedef void (T::*object_method_ptr)(uint64 data, void* pData_ptr); + + object_task(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) : + m_pObject(pObject), + m_pMethod(pMethod), + m_flags(flags) + { + CRNLIB_ASSERT(pObject && pMethod); + } + + void init(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) + { + CRNLIB_ASSERT(pObject && pMethod); + + m_pObject = pObject; + m_pMethod = pMethod; + m_flags = flags; + } + + T* get_object() const { return m_pObject; } + object_method_ptr get_method() const { return m_pMethod; } + + virtual void execute_task(uint64 data, void* pData_ptr) + { + (m_pObject->*m_pMethod)(data, pData_ptr); + + if (m_flags & cObjectTaskFlagDeleteAfterExecution) + crnlib_delete(this); + } + + protected: + T* m_pObject; + + object_method_ptr m_pMethod; + + uint m_flags; + }; + + template + inline bool task_pool::queue_object_task(S* pObject, T pObject_method, uint64 data, void* pData_ptr) + { + object_task *pTask = crnlib_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!pTask) + return false; + return queue_task(pTask, data, pData_ptr); + } + + template + inline bool task_pool::queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pObject); + CRNLIB_ASSERT(num_tasks); + if (!num_tasks) + return true; + + bool status = true; + + uint i; + for (i = 0; i < num_tasks; i++) + { + task tsk; + + tsk.m_pObj = crnlib_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!tsk.m_pObj) + { + status = false; + break; + } + + tsk.m_data = first_data + i; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + atomic_increment32(&m_total_submitted_tasks); + + if (!m_task_stack.try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + + status = false; + break; + } + } + + if (i) + { + m_tasks_available.release(i); + } + + return status; + } + +} // namespace crnlib + +#endif // CRNLIB_USE_PTHREADS_API diff --git a/crnlib/crn_threading_win32.cpp b/crnlib/crn_threading_win32.cpp new file mode 100644 index 0000000..824eedd --- /dev/null +++ b/crnlib/crn_threading_win32.cpp @@ -0,0 +1,429 @@ +// File: crn_win32_threading.cpp +// See Copyright Notice and license at the end of inc/crnlib.h +#include "crn_core.h" +#include "crn_threading_win32.h" +#include "crn_winhdr.h" +#include + +namespace crnlib +{ + uint g_number_of_processors = 1; + + void crn_threading_init() + { + SYSTEM_INFO g_system_info; + GetSystemInfo(&g_system_info); + + g_number_of_processors = math::maximum(1U, g_system_info.dwNumberOfProcessors); + } + + crn_thread_id_t crn_get_current_thread_id() + { + return static_cast(GetCurrentThreadId()); + } + + void crn_sleep(unsigned int milliseconds) + { + Sleep(milliseconds); + } + + uint crn_get_max_helper_threads() + { + if (g_number_of_processors > 1) + { + // use all CPU's + return CRNLIB_MIN((int)task_pool::cMaxThreads, (int)g_number_of_processors - 1); + } + + return 0; + } + + mutex::mutex(unsigned int spin_count) + { + CRNLIB_ASSUME(sizeof(mutex) >= sizeof(CRITICAL_SECTION)); + + void *p = m_buf; + CRITICAL_SECTION &m_cs = *static_cast(p); + + BOOL status = true; + status = InitializeCriticalSectionAndSpinCount(&m_cs, spin_count); + if (!status) + crnlib_fail("mutex::mutex: InitializeCriticalSectionAndSpinCount failed", __FILE__, __LINE__); + +#ifdef CRNLIB_BUILD_DEBUG + m_lock_count = 0; +#endif + } + + mutex::~mutex() + { + void *p = m_buf; + CRITICAL_SECTION &m_cs = *static_cast(p); + +#ifdef CRNLIB_BUILD_DEBUG + if (m_lock_count) + crnlib_assert("mutex::~mutex: mutex is still locked", __FILE__, __LINE__); +#endif + DeleteCriticalSection(&m_cs); + } + + void mutex::lock() + { + void *p = m_buf; + CRITICAL_SECTION &m_cs = *static_cast(p); + + EnterCriticalSection(&m_cs); +#ifdef CRNLIB_BUILD_DEBUG + m_lock_count++; +#endif + } + + void mutex::unlock() + { + void *p = m_buf; + CRITICAL_SECTION &m_cs = *static_cast(p); + +#ifdef CRNLIB_BUILD_DEBUG + if (!m_lock_count) + crnlib_assert("mutex::unlock: mutex is not locked", __FILE__, __LINE__); + m_lock_count--; +#endif + LeaveCriticalSection(&m_cs); + } + + void mutex::set_spin_count(unsigned int count) + { + void *p = m_buf; + CRITICAL_SECTION &m_cs = *static_cast(p); + + SetCriticalSectionSpinCount(&m_cs, count); + } + + void spinlock::lock(uint32 max_spins, bool yielding) + { + if (g_number_of_processors <= 1) + max_spins = 1; + + uint32 spinCount = 0; + uint32 yieldCount = 0; + + for ( ; ; ) + { + CRNLIB_ASSUME(sizeof(long) == sizeof(int32)); + if (!InterlockedExchange((volatile long*)&m_flag, TRUE)) + break; + + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + YieldProcessor(); + + spinCount++; + if ((yielding) && (spinCount >= max_spins)) + { + switch (yieldCount) + { + case 0: + { + spinCount = 0; + + Sleep(0); + + yieldCount++; + break; + } + case 1: + { + if (g_number_of_processors <= 1) + spinCount = 0; + else + spinCount = max_spins / 2; + + Sleep(1); + + yieldCount++; + break; + } + case 2: + { + if (g_number_of_processors <= 1) + spinCount = 0; + else + spinCount = max_spins; + + Sleep(2); + break; + } + } + } + } + + CRNLIB_MEMORY_IMPORT_BARRIER + } + + void spinlock::unlock() + { + CRNLIB_MEMORY_EXPORT_BARRIER + + InterlockedExchange((volatile long*)&m_flag, FALSE); + } + + semaphore::semaphore(int32 initialCount, int32 maximumCount, const char* pName) + { + m_handle = CreateSemaphoreA(NULL, initialCount, maximumCount, pName); + if (NULL == m_handle) + { + CRNLIB_FAIL("semaphore: CreateSemaphore() failed"); + } + } + + semaphore::~semaphore() + { + if (m_handle) + { + CloseHandle(m_handle); + m_handle = NULL; + } + } + + void semaphore::release(int32 releaseCount, int32 *pPreviousCount) + { + CRNLIB_ASSUME(sizeof(LONG) == sizeof(int32)); + if (0 == ReleaseSemaphore(m_handle, releaseCount, (LPLONG)pPreviousCount)) + { + CRNLIB_FAIL("semaphore: ReleaseSemaphore() failed"); + } + } + + bool semaphore::try_release(int32 releaseCount, int32 *pPreviousCount) + { + CRNLIB_ASSUME(sizeof(LONG) == sizeof(int32)); + return ReleaseSemaphore(m_handle, releaseCount, (LPLONG)pPreviousCount) != 0; + } + + bool semaphore::wait(uint32 milliseconds) + { + uint32 result = WaitForSingleObject(m_handle, milliseconds); + + if (WAIT_FAILED == result) + { + CRNLIB_FAIL("semaphore: WaitForSingleObject() failed"); + } + + return WAIT_OBJECT_0 == result; + } + + task_pool::task_pool() : + m_pTask_stack(crnlib_new()), + m_num_threads(0), + m_tasks_available(0, 32767), + m_all_tasks_completed(0, 1), + m_total_submitted_tasks(0), + m_total_completed_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + } + + task_pool::task_pool(uint num_threads) : + m_pTask_stack(crnlib_new()), + m_num_threads(0), + m_tasks_available(0, 32767), + m_all_tasks_completed(0, 1), + m_total_submitted_tasks(0), + m_total_completed_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + + bool status = init(num_threads); + CRNLIB_VERIFY(status); + } + + task_pool::~task_pool() + { + deinit(); + crnlib_delete(m_pTask_stack); + } + + bool task_pool::init(uint num_threads) + { + CRNLIB_ASSERT(num_threads <= cMaxThreads); + num_threads = math::minimum(num_threads, cMaxThreads); + + deinit(); + + bool succeeded = true; + + m_num_threads = 0; + while (m_num_threads < num_threads) + { + m_threads[m_num_threads] = (HANDLE)_beginthreadex(NULL, 32768, thread_func, this, 0, NULL); + CRNLIB_ASSERT(m_threads[m_num_threads] != 0); + + if (!m_threads[m_num_threads]) + { + succeeded = false; + break; + } + + m_num_threads++; + } + + if (!succeeded) + { + deinit(); + return false; + } + + return true; + } + + void task_pool::deinit() + { + if (m_num_threads) + { + join(); + + // Set exit flag, then release all threads. Each should wakeup and exit. + atomic_exchange32(&m_exit_flag, true); + + m_tasks_available.release(m_num_threads); + + // Now wait for each thread to exit. + for (uint i = 0; i < m_num_threads; i++) + { + if (m_threads[i]) + { + for ( ; ; ) + { + // Can be an INFINITE delay, but set at 30 seconds so this function always provably exits. + DWORD result = WaitForSingleObject(m_threads[i], 30000); + if ((result == WAIT_OBJECT_0) || (result == WAIT_ABANDONED)) + break; + } + + CloseHandle(m_threads[i]); + m_threads[i] = NULL; + } + } + + m_num_threads = 0; + + atomic_exchange32(&m_exit_flag, false); + } + + if (m_pTask_stack) + m_pTask_stack->clear(); + m_total_submitted_tasks = 0; + m_total_completed_tasks = 0; + } + + bool task_pool::queue_task(task_callback_func pFunc, uint64 data, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pFunc); + + task tsk; + tsk.m_callback = pFunc; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = 0; + + atomic_increment32(&m_total_submitted_tasks); + + if (!m_pTask_stack->try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + return false; + } + + m_tasks_available.release(1); + + return true; + } + + // It's the object's responsibility to delete pObj within the execute_task() method, if needed! + bool task_pool::queue_task(executable_task* pObj, uint64 data, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pObj); + + task tsk; + tsk.m_pObj = pObj; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + atomic_increment32(&m_total_submitted_tasks); + + if (!m_pTask_stack->try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + return false; + } + + m_tasks_available.release(1); + + return true; + } + + void task_pool::process_task(task& tsk) + { + if (tsk.m_flags & cTaskFlagObject) + tsk.m_pObj->execute_task(tsk.m_data, tsk.m_pData_ptr); + else + tsk.m_callback(tsk.m_data, tsk.m_pData_ptr); + + if (atomic_increment32(&m_total_completed_tasks) == m_total_submitted_tasks) + { + // Try to signal the semaphore (the max count is 1 so this may actually fail). + m_all_tasks_completed.try_release(); + } + } + + void task_pool::join() + { + // Try to steal any outstanding tasks. This could cause one or more worker threads to wake up and immediately go back to sleep, which is wasteful but should be harmless. + task tsk; + while (m_pTask_stack->pop(tsk)) + process_task(tsk); + + // At this point the task stack is empty. + // Now wait for all concurrent tasks to complete. The m_all_tasks_completed semaphore has a max count of 1, so it's possible it could have saturated to 1 as the tasks + // where issued and asynchronously completed, so this loop may iterate a few times. + const int total_submitted_tasks = atomic_add32(&m_total_submitted_tasks, 0); + while (m_total_completed_tasks != total_submitted_tasks) + { + // If the previous (m_total_completed_tasks != total_submitted_tasks) check failed the semaphore MUST be eventually signalled once the last task completes. + // So I think this can actually be an INFINITE delay, but it shouldn't really matter if it's 1ms. + m_all_tasks_completed.wait(1); + } + } + + unsigned __stdcall task_pool::thread_func(void* pContext) + { + task_pool* pPool = static_cast(pContext); + + for ( ; ; ) + { + if (!pPool->m_tasks_available.wait()) + break; + + if (pPool->m_exit_flag) + break; + + task tsk; + if (pPool->m_pTask_stack->pop(tsk)) + pPool->process_task(tsk); + } + + _endthreadex(0); + return 0; + } + +} diff --git a/crnlib/crn_threading_win32.h b/crnlib/crn_threading_win32.h new file mode 100644 index 0000000..e11d020 --- /dev/null +++ b/crnlib/crn_threading_win32.h @@ -0,0 +1,418 @@ +// File: crn_win32_threading.h +// See Copyright Notice and license at the end of inc/crnlib.h +#pragma once + +#include "crn_atomics.h" +#if CRNLIB_NO_ATOMICS +#error No atomic operations defined in crn_platform.h! +#endif + +namespace crnlib +{ + // g_number_of_processors defaults to 1. Will be higher on multicore machines. + extern uint g_number_of_processors; + + void crn_threading_init(); + + typedef uint64 crn_thread_id_t; + crn_thread_id_t crn_get_current_thread_id(); + + void crn_sleep(unsigned int milliseconds); + + uint crn_get_max_helper_threads(); + + class mutex + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(mutex); + + public: + mutex(unsigned int spin_count = 0); + ~mutex(); + void lock(); + void unlock(); + void set_spin_count(unsigned int count); + + private: + int m_buf[12]; + +#ifdef CRNLIB_BUILD_DEBUG + unsigned int m_lock_count; +#endif + }; + + class scoped_mutex + { + scoped_mutex(const scoped_mutex&); + scoped_mutex& operator= (const scoped_mutex&); + + public: + inline scoped_mutex(mutex& m) : m_mutex(m) { m_mutex.lock(); } + inline ~scoped_mutex() { m_mutex.unlock(); } + + private: + mutex& m_mutex; + }; + + // Simple non-recursive spinlock. + class spinlock + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(spinlock); + public: + inline spinlock() : m_flag(0) { } + + void lock(uint32 max_spins = 4096, bool yielding = true); + + inline void lock_no_barrier(uint32 max_spins = 4096, bool yielding = true) { lock(max_spins, yielding); } + + void unlock(); + + inline void unlock_no_barrier() { m_flag = CRNLIB_FALSE; } + + private: + volatile int32 m_flag; + }; + + class scoped_spinlock + { + scoped_spinlock(const scoped_spinlock&); + scoped_spinlock& operator= (const scoped_spinlock&); + + public: + inline scoped_spinlock(spinlock& lock) : m_lock(lock) { m_lock.lock(); } + inline ~scoped_spinlock() { m_lock.unlock(); } + + private: + spinlock& m_lock; + }; + + class semaphore + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(semaphore); + + public: + semaphore(int32 initialCount = 0, int32 maximumCount = 1, const char* pName = NULL); + + ~semaphore(); + + inline HANDLE get_handle(void) const { return m_handle; } + + void release(int32 releaseCount = 1, int32 *pPreviousCount = NULL); + bool try_release(int32 releaseCount = 1, int32 *pPreviousCount = NULL); + + bool wait(uint32 milliseconds = cUINT32_MAX); + + private: + HANDLE m_handle; + }; + + template + class tsstack + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(tsstack); + public: + inline tsstack(bool use_freelist = true) : + m_use_freelist(use_freelist) + { + CRNLIB_VERIFY(((ptr_bits_t)this & (CRNLIB_GET_ALIGNMENT(tsstack) - 1)) == 0); + InitializeSListHead(&m_stack_head); + InitializeSListHead(&m_freelist_head); + } + + inline ~tsstack() + { + clear(); + } + + inline void clear() + { + for ( ; ; ) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_stack_head); + if (!pNode) + break; + + CRNLIB_MEMORY_IMPORT_BARRIER + + helpers::destruct(&pNode->m_obj); + + crnlib_free(pNode); + } + + flush_freelist(); + } + + inline void flush_freelist() + { + if (!m_use_freelist) + return; + + for ( ; ; ) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_freelist_head); + if (!pNode) + break; + + CRNLIB_MEMORY_IMPORT_BARRIER + + crnlib_free(pNode); + } + } + + inline bool try_push(const T& obj) + { + node* pNode = alloc_node(); + if (!pNode) + return false; + + helpers::construct(&pNode->m_obj, obj); + + CRNLIB_MEMORY_EXPORT_BARRIER + + InterlockedPushEntrySList(&m_stack_head, &pNode->m_slist_entry); + + return true; + } + + inline bool pop(T& obj) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_stack_head); + if (!pNode) + return false; + + CRNLIB_MEMORY_IMPORT_BARRIER + + obj = pNode->m_obj; + + helpers::destruct(&pNode->m_obj); + + free_node(pNode); + + return true; + } + + private: + SLIST_HEADER m_stack_head; + SLIST_HEADER m_freelist_head; + + struct node + { + SLIST_ENTRY m_slist_entry; + T m_obj; + }; + + bool m_use_freelist; + + inline node* alloc_node() + { + node* pNode = m_use_freelist ? (node*)InterlockedPopEntrySList(&m_freelist_head) : NULL; + + if (!pNode) + pNode = (node*)crnlib_malloc(sizeof(node)); + + return pNode; + } + + inline void free_node(node* pNode) + { + if (m_use_freelist) + InterlockedPushEntrySList(&m_freelist_head, &pNode->m_slist_entry); + else + crnlib_free(pNode); + } + }; + + // Simple multithreaded task pool. This class assumes a single global thread will be issuing tasks and joining. + class task_pool + { + CRNLIB_NO_COPY_OR_ASSIGNMENT_OP(task_pool); + public: + task_pool(); + task_pool(uint num_threads); + ~task_pool(); + + enum { cMaxThreads = 16 }; + bool init(uint num_threads); + void deinit(); + + inline uint get_num_threads() const { return m_num_threads; } + inline uint32 get_num_outstanding_tasks() const { return m_total_submitted_tasks - m_total_completed_tasks; } + + // C-style task callback + typedef void (*task_callback_func)(uint64 data, void* pData_ptr); + bool queue_task(task_callback_func pFunc, uint64 data = 0, void* pData_ptr = NULL); + + class executable_task + { + public: + virtual void execute_task(uint64 data, void* pData_ptr) = 0; + }; + + // It's the caller's responsibility to delete pObj within the execute_task() method, if needed! + bool queue_task(executable_task* pObj, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_object_task(S* pObject, T pObject_method, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr = NULL); + + // Waits for all outstanding tasks (if any) to complete. + // The calling thread will steal any outstanding tasks from worker threads, if possible. + void join(); + + private: + struct task + { + //inline task() : m_data(0), m_pData_ptr(NULL), m_pObj(NULL), m_flags(0) { } + + uint64 m_data; + void* m_pData_ptr; + + union + { + task_callback_func m_callback; + executable_task* m_pObj; + }; + + uint m_flags; + }; + + typedef tsstack ts_task_stack_t; + ts_task_stack_t* m_pTask_stack; + + uint m_num_threads; + HANDLE m_threads[cMaxThreads]; + + // Signalled whenever a task is queued up. + semaphore m_tasks_available; + + // Signalled when all outstanding tasks are completed. + semaphore m_all_tasks_completed; + + enum task_flags + { + cTaskFlagObject = 1 + }; + + volatile atomic32_t m_total_submitted_tasks; + volatile atomic32_t m_total_completed_tasks; + volatile atomic32_t m_exit_flag; + + void process_task(task& tsk); + + static unsigned __stdcall thread_func(void* pContext); + }; + + enum object_task_flags + { + cObjectTaskFlagDefault = 0, + cObjectTaskFlagDeleteAfterExecution = 1 + }; + + template + class object_task : public task_pool::executable_task + { + public: + object_task(uint flags = cObjectTaskFlagDefault) : + m_pObject(NULL), + m_pMethod(NULL), + m_flags(flags) + { + } + + typedef void (T::*object_method_ptr)(uint64 data, void* pData_ptr); + + object_task(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) : + m_pObject(pObject), + m_pMethod(pMethod), + m_flags(flags) + { + CRNLIB_ASSERT(pObject && pMethod); + } + + void init(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) + { + CRNLIB_ASSERT(pObject && pMethod); + + m_pObject = pObject; + m_pMethod = pMethod; + m_flags = flags; + } + + T* get_object() const { return m_pObject; } + object_method_ptr get_method() const { return m_pMethod; } + + virtual void execute_task(uint64 data, void* pData_ptr) + { + (m_pObject->*m_pMethod)(data, pData_ptr); + + if (m_flags & cObjectTaskFlagDeleteAfterExecution) + crnlib_delete(this); + } + + protected: + T* m_pObject; + + object_method_ptr m_pMethod; + + uint m_flags; + }; + + template + inline bool task_pool::queue_object_task(S* pObject, T pObject_method, uint64 data, void* pData_ptr) + { + object_task *pTask = crnlib_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!pTask) + return false; + return queue_task(pTask, data, pData_ptr); + } + + template + inline bool task_pool::queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr) + { + CRNLIB_ASSERT(m_num_threads); + CRNLIB_ASSERT(pObject); + CRNLIB_ASSERT(num_tasks); + if (!num_tasks) + return true; + + bool status = true; + + uint i; + for (i = 0; i < num_tasks; i++) + { + task tsk; + + tsk.m_pObj = crnlib_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!tsk.m_pObj) + { + status = false; + break; + } + + tsk.m_data = first_data + i; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + atomic_increment32(&m_total_submitted_tasks); + + if (!m_pTask_stack->try_push(tsk)) + { + atomic_increment32(&m_total_completed_tasks); + + status = false; + break; + } + } + + if (i) + { + m_tasks_available.release(i); + } + + return status; + } + +} // namespace crnlib + + diff --git a/crnlib/crn_timer.cpp b/crnlib/crn_timer.cpp new file mode 100644 index 0000000..1c6fc4a --- /dev/null +++ b/crnlib/crn_timer.cpp @@ -0,0 +1,155 @@ +// File: crn_win32_timer.cpp +// See Copyright Notice and license at the end of inc/crnlib.h +#include "crn_core.h" +#include "crn_timer.h" +#include + +#include "crn_timer.h" + +#if CRNLIB_USE_WIN32_API + #include "crn_winhdr.h" +#endif + +namespace crnlib +{ + unsigned long long timer::g_init_ticks; + unsigned long long timer::g_freq; + double timer::g_inv_freq; + +#if defined(CRNLIB_USE_WIN32_API) + inline void query_counter(timer_ticks *pTicks) + { + QueryPerformanceCounter(reinterpret_cast(pTicks)); + } + inline void query_counter_frequency(timer_ticks *pTicks) + { + QueryPerformanceFrequency(reinterpret_cast(pTicks)); + } +#elif defined(__GNUC__) + #include + inline void query_counter(timer_ticks *pTicks) + { + struct timeval cur_time; + gettimeofday(&cur_time, NULL); + *pTicks = static_cast(cur_time.tv_sec)*1000000ULL + static_cast(cur_time.tv_usec); + } + inline void query_counter_frequency(timer_ticks *pTicks) + { + *pTicks = 1000000; + } +#else + #error Unimplemented +#endif + + timer::timer() : + m_start_time(0), + m_stop_time(0), + m_started(false), + m_stopped(false) + { + if (!g_inv_freq) + init(); + } + + timer::timer(timer_ticks start_ticks) + { + if (!g_inv_freq) + init(); + + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; + } + + void timer::start(timer_ticks start_ticks) + { + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; + } + + void timer::start() + { + query_counter(&m_start_time); + + m_started = true; + m_stopped = false; + } + + void timer::stop() + { + CRNLIB_ASSERT(m_started); + + query_counter(&m_stop_time); + + m_stopped = true; + } + + double timer::get_elapsed_secs() const + { + CRNLIB_ASSERT(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return delta * g_inv_freq; + } + + timer_ticks timer::get_elapsed_us() const + { + CRNLIB_ASSERT(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return (delta * 1000000ULL + (g_freq >> 1U)) / g_freq; + } + + void timer::init() + { + if (!g_inv_freq) + { + query_counter_frequency(&g_freq); + g_inv_freq = 1.0f / g_freq; + + query_counter(&g_init_ticks); + } + } + + timer_ticks timer::get_init_ticks() + { + if (!g_inv_freq) + init(); + + return g_init_ticks; + } + + timer_ticks timer::get_ticks() + { + if (!g_inv_freq) + init(); + + timer_ticks ticks; + query_counter(&ticks); + return ticks - g_init_ticks; + } + + double timer::ticks_to_secs(timer_ticks ticks) + { + if (!g_inv_freq) + init(); + + return ticks * g_inv_freq; + } + +} // namespace crnlib diff --git a/crnlib/crn_timer.h b/crnlib/crn_timer.h new file mode 100644 index 0000000..4e895f5 --- /dev/null +++ b/crnlib/crn_timer.h @@ -0,0 +1,45 @@ +// File: crn_win32_timer.h +// See Copyright Notice and license at the end of inc/crnlib.h +#pragma once + +namespace crnlib +{ + typedef unsigned long long timer_ticks; + + class timer + { + public: + timer(); + timer(timer_ticks start_ticks); + + void start(); + void start(timer_ticks start_ticks); + + void stop(); + + double get_elapsed_secs() const; + inline double get_elapsed_ms() const { return get_elapsed_secs() * 1000.0f; } + timer_ticks get_elapsed_us() const; + + static void init(); + static inline timer_ticks get_ticks_per_sec() { return g_freq; } + static timer_ticks get_init_ticks(); + static timer_ticks get_ticks(); + static double ticks_to_secs(timer_ticks ticks); + static inline double ticks_to_ms(timer_ticks ticks) { return ticks_to_secs(ticks) * 1000.0f; } + static inline double get_secs() { return ticks_to_secs(get_ticks()); } + static inline double get_ms() { return ticks_to_ms(get_ticks()); } + + private: + static timer_ticks g_init_ticks; + static timer_ticks g_freq; + static double g_inv_freq; + + timer_ticks m_start_time; + timer_ticks m_stop_time; + + bool m_started : 1; + bool m_stopped : 1; + }; + +} // namespace crnlib diff --git a/crnlib/crnlib_linux.cbp b/crnlib/crnlib_linux.cbp new file mode 100644 index 0000000..963a9b0 --- /dev/null +++ b/crnlib/crnlib_linux.cbp @@ -0,0 +1,218 @@ + + + + + + diff --git a/crnlib/lzham_timer.cpp b/crnlib/lzham_timer.cpp new file mode 100644 index 0000000..90aa536 --- /dev/null +++ b/crnlib/lzham_timer.cpp @@ -0,0 +1,147 @@ +// File: lzham_timer.cpp +// See Copyright Notice and license at the end of include/lzham.h +#include "lzham_core.h" +#include "lzham_timer.h" + +#ifndef LZHAM_USE_WIN32_API + #include +#endif + +namespace lzham +{ + unsigned long long lzham_timer::g_init_ticks; + unsigned long long lzham_timer::g_freq; + double lzham_timer::g_inv_freq; + + #if LZHAM_USE_WIN32_API + inline void query_counter(timer_ticks *pTicks) + { + QueryPerformanceCounter(reinterpret_cast(pTicks)); + } + inline void query_counter_frequency(timer_ticks *pTicks) + { + QueryPerformanceFrequency(reinterpret_cast(pTicks)); + } + #else + inline void query_counter(timer_ticks *pTicks) + { + *pTicks = clock(); + } + inline void query_counter_frequency(timer_ticks *pTicks) + { + *pTicks = CLOCKS_PER_SEC; + } + #endif + + lzham_timer::lzham_timer() : + m_start_time(0), + m_stop_time(0), + m_started(false), + m_stopped(false) + { + if (!g_inv_freq) + init(); + } + + lzham_timer::lzham_timer(timer_ticks start_ticks) + { + if (!g_inv_freq) + init(); + + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; + } + + void lzham_timer::start(timer_ticks start_ticks) + { + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; + } + + void lzham_timer::start() + { + query_counter(&m_start_time); + + m_started = true; + m_stopped = false; + } + + void lzham_timer::stop() + { + LZHAM_ASSERT(m_started); + + query_counter(&m_stop_time); + + m_stopped = true; + } + + double lzham_timer::get_elapsed_secs() const + { + LZHAM_ASSERT(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return delta * g_inv_freq; + } + + timer_ticks lzham_timer::get_elapsed_us() const + { + LZHAM_ASSERT(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return (delta * 1000000ULL + (g_freq >> 1U)) / g_freq; + } + + void lzham_timer::init() + { + if (!g_inv_freq) + { + query_counter_frequency(&g_freq); + g_inv_freq = 1.0f / g_freq; + + query_counter(&g_init_ticks); + } + } + + timer_ticks lzham_timer::get_init_ticks() + { + if (!g_inv_freq) + init(); + + return g_init_ticks; + } + + timer_ticks lzham_timer::get_ticks() + { + if (!g_inv_freq) + init(); + + timer_ticks ticks; + query_counter(&ticks); + return ticks - g_init_ticks; + } + + double lzham_timer::ticks_to_secs(timer_ticks ticks) + { + if (!g_inv_freq) + init(); + + return ticks * g_inv_freq; + } + +} // namespace lzham \ No newline at end of file diff --git a/crnlib/lzham_timer.h b/crnlib/lzham_timer.h new file mode 100644 index 0000000..d011a2b --- /dev/null +++ b/crnlib/lzham_timer.h @@ -0,0 +1,99 @@ +// File: lzham_timer.h +// See Copyright Notice and license at the end of include/lzham.h +#pragma once + +namespace lzham +{ + typedef unsigned long long timer_ticks; + + class lzham_timer + { + public: + lzham_timer(); + lzham_timer(timer_ticks start_ticks); + + void start(); + void start(timer_ticks start_ticks); + + void stop(); + + double get_elapsed_secs() const; + inline double get_elapsed_ms() const { return get_elapsed_secs() * 1000.0f; } + timer_ticks get_elapsed_us() const; + + static void init(); + static inline timer_ticks get_ticks_per_sec() { return g_freq; } + static timer_ticks get_init_ticks(); + static timer_ticks get_ticks(); + static double ticks_to_secs(timer_ticks ticks); + static inline double ticks_to_ms(timer_ticks ticks) { return ticks_to_secs(ticks) * 1000.0f; } + static inline double get_secs() { return ticks_to_secs(get_ticks()); } + static inline double get_ms() { return ticks_to_ms(get_ticks()); } + + private: + static timer_ticks g_init_ticks; + static timer_ticks g_freq; + static double g_inv_freq; + + timer_ticks m_start_time; + timer_ticks m_stop_time; + + bool m_started : 1; + bool m_stopped : 1; + }; + + enum var_args_t { cVarArgs }; + +#if LZHAM_PERF_SECTIONS + class scoped_perf_section + { + public: + inline scoped_perf_section() : + m_start_ticks(lzham_timer::get_ticks()) + { + m_name[0] = '?'; + m_name[1] = '\0'; + } + + inline scoped_perf_section(const char *pName) : + m_start_ticks(lzham_timer::get_ticks()) + { + strcpy_s(m_name, pName); + + lzham_buffered_printf("Thread: 0x%08X, BEGIN Time: %3.3fms, Section: %s\n", GetCurrentThreadId(), lzham_timer::ticks_to_ms(m_start_ticks), m_name); + } + + inline scoped_perf_section(var_args_t, const char *pName, ...) : + m_start_ticks(lzham_timer::get_ticks()) + { + va_list args; + va_start(args, pName); + vsprintf_s(m_name, sizeof(m_name), pName, args); + va_end(args); + + lzham_buffered_printf("Thread: 0x%08X, BEGIN Time: %3.3fms, Section: %s\n", GetCurrentThreadId(), lzham_timer::ticks_to_ms(m_start_ticks), m_name); + } + + inline ~scoped_perf_section() + { + double end_ms = lzham_timer::get_ms(); + double start_ms = lzham_timer::ticks_to_ms(m_start_ticks); + + lzham_buffered_printf("Thread: 0x%08X, END Time: %3.3fms, Total: %3.3fms, Section: %s\n", GetCurrentThreadId(), end_ms, end_ms - start_ms, m_name); + } + + private: + char m_name[64]; + timer_ticks m_start_ticks; + }; +#else + class scoped_perf_section + { + public: + inline scoped_perf_section() { } + inline scoped_perf_section(const char *pName) { (void)pName; } + inline scoped_perf_section(var_args_t, const char *pName, ...) { (void)pName; } + }; +#endif // LZHAM_PERF_SECTIONS + +} // namespace lzham diff --git a/crnlib/lzham_win32_threading.cpp b/crnlib/lzham_win32_threading.cpp new file mode 100644 index 0000000..a3372a2 --- /dev/null +++ b/crnlib/lzham_win32_threading.cpp @@ -0,0 +1,220 @@ +// File: lzham_task_pool_win32.cpp +// See Copyright Notice and license at the end of include/lzham.h +#include "lzham_core.h" +#include "lzham_win32_threading.h" +#include "lzham_timer.h" +#include + +#if LZHAM_USE_WIN32_API + +namespace lzham +{ + task_pool::task_pool() : + m_num_threads(0), + m_tasks_available(0, 32767), + m_num_outstanding_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + } + + task_pool::task_pool(uint num_threads) : + m_num_threads(0), + m_tasks_available(0, 32767), + m_num_outstanding_tasks(0), + m_exit_flag(false) + { + utils::zero_object(m_threads); + + bool status = init(num_threads); + LZHAM_VERIFY(status); + } + + task_pool::~task_pool() + { + deinit(); + } + + bool task_pool::init(uint num_threads) + { + LZHAM_ASSERT(num_threads <= cMaxThreads); + num_threads = math::minimum(num_threads, cMaxThreads); + + deinit(); + + bool succeeded = true; + + m_num_threads = 0; + while (m_num_threads < num_threads) + { + m_threads[m_num_threads] = (HANDLE)_beginthreadex(NULL, 32768, thread_func, this, 0, NULL); + LZHAM_ASSERT(m_threads[m_num_threads] != 0); + + if (!m_threads[m_num_threads]) + { + succeeded = false; + break; + } + + m_num_threads++; + } + + if (!succeeded) + { + deinit(); + return false; + } + + return true; + } + + void task_pool::deinit() + { + if (m_num_threads) + { + join(); + + atomic_exchange32(&m_exit_flag, true); + + m_tasks_available.release(m_num_threads); + + for (uint i = 0; i < m_num_threads; i++) + { + if (m_threads[i]) + { + for ( ; ; ) + { + DWORD result = WaitForSingleObject(m_threads[i], 30000); + if ((result == WAIT_OBJECT_0) || (result == WAIT_ABANDONED)) + break; + } + + CloseHandle(m_threads[i]); + m_threads[i] = NULL; + } + } + + m_num_threads = 0; + + atomic_exchange32(&m_exit_flag, false); + } + + m_task_stack.clear(); + m_num_outstanding_tasks = 0; + } + + bool task_pool::queue_task(task_callback_func pFunc, uint64 data, void* pData_ptr) + { + LZHAM_ASSERT(m_num_threads); + LZHAM_ASSERT(pFunc); + + task tsk; + tsk.m_callback = pFunc; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = 0; + + if (!m_task_stack.try_push(tsk)) + return false; + + atomic_increment32(&m_num_outstanding_tasks); + + m_tasks_available.release(1); + + return true; + } + + // It's the object's responsibility to delete pObj within the execute_task() method, if needed! + bool task_pool::queue_task(executable_task* pObj, uint64 data, void* pData_ptr) + { + LZHAM_ASSERT(m_num_threads); + LZHAM_ASSERT(pObj); + + task tsk; + tsk.m_pObj = pObj; + tsk.m_data = data; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + if (!m_task_stack.try_push(tsk)) + return false; + + atomic_increment32(&m_num_outstanding_tasks); + + m_tasks_available.release(1); + + return true; + } + + void task_pool::process_task(task& tsk) + { + if (tsk.m_flags & cTaskFlagObject) + tsk.m_pObj->execute_task(tsk.m_data, tsk.m_pData_ptr); + else + tsk.m_callback(tsk.m_data, tsk.m_pData_ptr); + + atomic_decrement32(&m_num_outstanding_tasks); + } + + void task_pool::join() + { + while (atomic_add32(&m_num_outstanding_tasks, 0) > 0) + { + task tsk; + if (m_task_stack.pop(tsk)) + { + process_task(tsk); + } + else + { + lzham_sleep(1); + } + } + } + + unsigned __stdcall task_pool::thread_func(void* pContext) + { + task_pool* pPool = static_cast(pContext); + + for ( ; ; ) + { + if (!pPool->m_tasks_available.wait()) + break; + + if (pPool->m_exit_flag) + break; + + task tsk; + if (pPool->m_task_stack.pop(tsk)) + { + pPool->process_task(tsk); + } + } + + _endthreadex(0); + return 0; + } + + static uint g_num_processors; + + uint lzham_get_max_helper_threads() + { + if (!g_num_processors) + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + g_num_processors = system_info.dwNumberOfProcessors; + } + + if (g_num_processors > 1) + { + // use all CPU's + return LZHAM_MIN(task_pool::cMaxThreads, g_num_processors - 1); + } + + return 0; + } + +} // namespace lzham + +#endif // LZHAM_USE_WIN32_API diff --git a/crnlib/lzham_win32_threading.h b/crnlib/lzham_win32_threading.h new file mode 100644 index 0000000..640dc8f --- /dev/null +++ b/crnlib/lzham_win32_threading.h @@ -0,0 +1,368 @@ +// File: lzham_task_pool_win32.h +// See Copyright Notice and license at the end of include/lzham.h +#pragma once + +#if LZHAM_USE_WIN32_API + +#if LZHAM_NO_ATOMICS +#error No atomic operations defined in lzham_platform.h! +#endif + +namespace lzham +{ + class semaphore + { + LZHAM_NO_COPY_OR_ASSIGNMENT_OP(semaphore); + + public: + semaphore(long initialCount = 0, long maximumCount = 1, const char* pName = NULL) + { + m_handle = CreateSemaphoreA(NULL, initialCount, maximumCount, pName); + if (NULL == m_handle) + { + LZHAM_FAIL("semaphore: CreateSemaphore() failed"); + } + } + + ~semaphore() + { + if (m_handle) + { + CloseHandle(m_handle); + m_handle = NULL; + } + } + + inline HANDLE get_handle(void) const { return m_handle; } + + void release(long releaseCount = 1) + { + if (0 == ReleaseSemaphore(m_handle, releaseCount, NULL)) + { + LZHAM_FAIL("semaphore: ReleaseSemaphore() failed"); + } + } + + bool wait(uint32 milliseconds = UINT32_MAX) + { + LZHAM_ASSUME(INFINITE == UINT32_MAX); + + DWORD result = WaitForSingleObject(m_handle, milliseconds); + + if (WAIT_FAILED == result) + { + LZHAM_FAIL("semaphore: WaitForSingleObject() failed"); + } + + return WAIT_OBJECT_0 == result; + } + + private: + HANDLE m_handle; + }; + + template + class tsstack + { + public: + inline tsstack(bool use_freelist = true) : + m_use_freelist(use_freelist) + { + LZHAM_VERIFY(((ptr_bits_t)this & (LZHAM_GET_ALIGNMENT(tsstack) - 1)) == 0); + InitializeSListHead(&m_stack_head); + InitializeSListHead(&m_freelist_head); + } + + inline ~tsstack() + { + clear(); + } + + inline void clear() + { + for ( ; ; ) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_stack_head); + if (!pNode) + break; + + LZHAM_MEMORY_IMPORT_BARRIER + + helpers::destruct(&pNode->m_obj); + + lzham_free(pNode); + } + + flush_freelist(); + } + + inline void flush_freelist() + { + if (!m_use_freelist) + return; + + for ( ; ; ) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_freelist_head); + if (!pNode) + break; + + LZHAM_MEMORY_IMPORT_BARRIER + + lzham_free(pNode); + } + } + + inline bool try_push(const T& obj) + { + node* pNode = alloc_node(); + if (!pNode) + return false; + + helpers::construct(&pNode->m_obj, obj); + + LZHAM_MEMORY_EXPORT_BARRIER + + InterlockedPushEntrySList(&m_stack_head, &pNode->m_slist_entry); + + return true; + } + + inline bool pop(T& obj) + { + node* pNode = (node*)InterlockedPopEntrySList(&m_stack_head); + if (!pNode) + return false; + + LZHAM_MEMORY_IMPORT_BARRIER + + obj = pNode->m_obj; + + helpers::destruct(&pNode->m_obj); + + free_node(pNode); + + return true; + } + + private: + SLIST_HEADER m_stack_head; + SLIST_HEADER m_freelist_head; + + struct node + { + SLIST_ENTRY m_slist_entry; + T m_obj; + }; + + bool m_use_freelist; + + inline node* alloc_node() + { + node* pNode = m_use_freelist ? (node*)InterlockedPopEntrySList(&m_freelist_head) : NULL; + + if (!pNode) + pNode = (node*)lzham_malloc(sizeof(node)); + + return pNode; + } + + inline void free_node(node* pNode) + { + if (m_use_freelist) + InterlockedPushEntrySList(&m_freelist_head, &pNode->m_slist_entry); + else + lzham_free(pNode); + } + }; + + class task_pool + { + public: + task_pool(); + task_pool(uint num_threads); + ~task_pool(); + + enum { cMaxThreads = 16 }; + bool init(uint num_threads); + void deinit(); + + inline uint get_num_threads() const { return m_num_threads; } + inline uint get_num_outstanding_tasks() const { return m_num_outstanding_tasks; } + + // C-style task callback + typedef void (*task_callback_func)(uint64 data, void* pData_ptr); + bool queue_task(task_callback_func pFunc, uint64 data = 0, void* pData_ptr = NULL); + + class executable_task + { + public: + virtual void execute_task(uint64 data, void* pData_ptr) = 0; + }; + + // It's the caller's responsibility to delete pObj within the execute_task() method, if needed! + bool queue_task(executable_task* pObj, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_object_task(S* pObject, T pObject_method, uint64 data = 0, void* pData_ptr = NULL); + + template + inline bool queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr = NULL); + + void join(); + + private: + struct task + { + //inline task() : m_data(0), m_pData_ptr(NULL), m_pObj(NULL), m_flags(0) { } + + uint64 m_data; + void* m_pData_ptr; + + union + { + task_callback_func m_callback; + executable_task* m_pObj; + }; + + uint m_flags; + }; + + tsstack m_task_stack; + + uint m_num_threads; + HANDLE m_threads[cMaxThreads]; + + semaphore m_tasks_available; + + enum task_flags + { + cTaskFlagObject = 1 + }; + + volatile atomic32_t m_num_outstanding_tasks; + volatile atomic32_t m_exit_flag; + + void process_task(task& tsk); + + static unsigned __stdcall thread_func(void* pContext); + }; + + enum object_task_flags + { + cObjectTaskFlagDefault = 0, + cObjectTaskFlagDeleteAfterExecution = 1 + }; + + template + class object_task : public task_pool::executable_task + { + public: + object_task(uint flags = cObjectTaskFlagDefault) : + m_pObject(NULL), + m_pMethod(NULL), + m_flags(flags) + { + } + + typedef void (T::*object_method_ptr)(uint64 data, void* pData_ptr); + + object_task(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) : + m_pObject(pObject), + m_pMethod(pMethod), + m_flags(flags) + { + LZHAM_ASSERT(pObject && pMethod); + } + + void init(T* pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault) + { + LZHAM_ASSERT(pObject && pMethod); + + m_pObject = pObject; + m_pMethod = pMethod; + m_flags = flags; + } + + T* get_object() const { return m_pObject; } + object_method_ptr get_method() const { return m_pMethod; } + + virtual void execute_task(uint64 data, void* pData_ptr) + { + (m_pObject->*m_pMethod)(data, pData_ptr); + + if (m_flags & cObjectTaskFlagDeleteAfterExecution) + lzham_delete(this); + } + + protected: + T* m_pObject; + + object_method_ptr m_pMethod; + + uint m_flags; + }; + + template + inline bool task_pool::queue_object_task(S* pObject, T pObject_method, uint64 data, void* pData_ptr) + { + object_task *pTask = lzham_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!pTask) + return false; + return queue_task(pTask, data, pData_ptr); + } + + template + inline bool task_pool::queue_multiple_object_tasks(S* pObject, T pObject_method, uint64 first_data, uint num_tasks, void* pData_ptr) + { + LZHAM_ASSERT(m_num_threads); + LZHAM_ASSERT(pObject); + LZHAM_ASSERT(num_tasks); + if (!num_tasks) + return true; + + bool status = true; + + uint i; + for (i = 0; i < num_tasks; i++) + { + task tsk; + + tsk.m_pObj = lzham_new< object_task >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution); + if (!tsk.m_pObj) + { + status = false; + break; + } + + tsk.m_data = first_data + i; + tsk.m_pData_ptr = pData_ptr; + tsk.m_flags = cTaskFlagObject; + + if (!m_task_stack.try_push(tsk)) + { + status = false; + break; + } + } + + if (i) + { + atomic_add32(&m_num_outstanding_tasks, i); + + m_tasks_available.release(i); + } + + return status; + } + + inline void lzham_sleep(unsigned int milliseconds) + { + Sleep(milliseconds); + } + + uint lzham_get_max_helper_threads(); + +} // namespace lzham + +#endif // LZHAM_USE_WIN32_API diff --git a/crunch/crunch_linux.cbp b/crunch/crunch_linux.cbp new file mode 100644 index 0000000..ee84827 --- /dev/null +++ b/crunch/crunch_linux.cbp @@ -0,0 +1,75 @@ + + + + + +