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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+