diff --git a/libs/containers/include/wolv/container/soo_buffer.hpp b/libs/containers/include/wolv/container/soo_buffer.hpp new file mode 100644 index 0000000..69951b4 --- /dev/null +++ b/libs/containers/include/wolv/container/soo_buffer.hpp @@ -0,0 +1,158 @@ +#pragma once +// soo_buffer.hpp +/* + * Small Object Optimization Buffer + * + * A buffer for POD types that keeps small allocations on the stack and + * transparently moves to heap storage when the size exceeds a configurable + * limit. During reallocation, the contents of the previous buffer may + * optionally be copied to the new one. + */ + +#include +#include +#include +#include + +namespace { + + template + struct NewAlloc { + static T* alloc(T* old, size_t sz) { + delete [] old; + return new T[sz]; + } + + static void free(T* p) { + delete[] p; + } + + static void copy(T* desc, T* src, size_t sz) {} + }; + + template + struct RealloceAlloc { + static T* alloc(T* old, size_t sz) { + return static_cast(realloc(old, sz*sizeof(T))); + } + + static void free(T* p) { + ::free(p); + } + + static void copy(T* desc, T* src, size_t sz) { + memcpy(desc, src, sz*sizeof(T)); + } + }; + +} // namespace + +namespace wolv::util { + + // T - Type + // SZ - Size in Ts of stack buffer + // UseRealloc - If true copies the contents of the old buffer to the new on reallocation + template + class SOOBuffer { + static_assert(std::is_trivially_copyable_v&& std::is_standard_layout_v, + "SOOBuffer only supports trivial, standard-layout types"); + + using Alloc = std::conditional_t, NewAlloc>; + + public: + using elementType = T; + static const size_t smallSZ = SZ; + + SOOBuffer(size_t cap = 0) { + m_size = SZ; + grow(cap); + } + + SOOBuffer(const SOOBuffer&) = delete; + SOOBuffer& operator=(const SOOBuffer&) = delete; + SOOBuffer(SOOBuffer&&) = delete; + SOOBuffer& operator=(SOOBuffer&&) = delete; + + ~SOOBuffer() { + if (!isSmall()) + Alloc::free(m_heap); + } + + T* data() { + return isSmall() ? m_small : m_heap; + } + + const T* data() const { + return isSmall() ? m_small : m_heap; + } + + operator T* () { + return data(); + } + + operator const T* () const { + return data(); + } + + T& operator[](size_t i) { + return data()[i]; + } + + const T& operator[](size_t i) const { + return data()[i]; + } + + size_t small_size() const { + return smallSZ; + } + + bool isSmall() const { + return m_size <= SZ; + } + + size_t size() const { + return m_size; + } + + void grow(size_t sz) { + if (sz > m_size) { // we never get smaller + growBuffer(sz); + } + } + + void grow(size_t sz, std::initializer_list ptrs) { + if (sz <= m_size) { // we never get smaller + return; + } + + T *pOldBase = data(); + growBuffer(sz); + T *pNewBase = data(); + + for (T** p : ptrs) { + *p = (*p - pOldBase) + pNewBase; + } + } + + private: + void growBuffer(size_t sz) { + if (isSmall()) { + T *pHeap = Alloc::alloc(NULL, sz); + Alloc::copy(pHeap, m_small, SZ); + m_heap = pHeap; + } + else { + m_heap = Alloc::alloc(m_heap, sz); + } + m_size = sz; + } + + size_t m_size = 0; + + union { + T* m_heap; + T m_small[SZ]; + }; + }; + +} // namespace wolv::util diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index e1e04dd..d9b3151 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -3,12 +3,18 @@ project(libwolv-utils) # Add library add_library(${PROJECT_NAME} STATIC source/utils/string.cpp + source/utils/date_time_format.cpp ) add_subdirectory(lib/jthread) target_include_directories(${PROJECT_NAME} PUBLIC include) -target_link_libraries(${PROJECT_NAME} PUBLIC wolv::types jthread) +target_link_libraries(${PROJECT_NAME} + PUBLIC + wolv::types + wolv::containers + jthread +) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") string(REPLACE "libwolv-" "" PROJECT_NAME_SPACE ${PROJECT_NAME}) diff --git a/libs/utils/include/wolv/utils/date_time_format.hpp b/libs/utils/include/wolv/utils/date_time_format.hpp new file mode 100644 index 0000000..e1e5f2a --- /dev/null +++ b/libs/utils/include/wolv/utils/date_time_format.hpp @@ -0,0 +1,106 @@ +#pragma once +// date_time_format.hpp + +#include +#include +#include + +#include + +#if defined(OS_WINDOWS) +# include +#else +# include +#endif // #if defined(OS_WINDOWS) + +namespace wolv::util { + +#if defined(OS_WINDOWS) + + class Locale { + public: + Locale() = default; + explicit Locale(const char *str); + explicit Locale(const std::string &str); + Locale(const Locale ©Me) = default; + + ~Locale() = default; + + Locale& operator=(const Locale ©Me); + + void set(const char *str); + void set(const std::string &str); + + operator const char*() const { + return m_locale.c_str(); + } + + private: + std::string m_locale; + }; + +#else + + class Locale { + public: + Locale(); + explicit Locale(const char *str); + explicit Locale(const std::string &str); + Locale(const Locale ©Me); + + ~Locale(); + + Locale& operator=(const Locale ©Me); + + void set(const char *str); + void set(const std::string &str); + + operator locale_t() const { + return m_locale; + } + + private: + void setInvalid(); + void free(); + + bool m_valid; + locale_t m_locale; + }; + +#endif + + enum class DTOpts { + TT32 = 0b0000, // 32-bits + TT64 = 0b0001, // 64-bits + TTMask = 0b0001, + + DandT = 0b0110, // date and time + D = 0b0100, // date + T = 0b0010, // time + DTMask = 0b0110, + + ShortDate = 0b0000, + LongDate = 0b1000, + DateFmtMask = 0b1000 + }; + + constexpr DTOpts operator|(DTOpts a, DTOpts b) noexcept { + using T = std::underlying_type_t; + return static_cast(static_cast(a) | static_cast(b)); + } + + constexpr DTOpts operator&(DTOpts a, DTOpts b) noexcept { + using T = std::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); + } + +#if defined(OS_WINDOWS) + std::optional timeTToSystemTime(i64 t, DTOpts sz = DTOpts::TT64); + std::optional formatSystemTime(LPCSTR lc, const SYSTEMTIME* pss, DTOpts opts = DTOpts::LongDate); + + std::optional formatTT(const Locale &lc, wolv::i64 t, DTOpts opts = DTOpts::TT64|DTOpts::DandT|DTOpts::LongDate); +#else + std::optional formatTT(const Locale &lc, wolv::i64 t, DTOpts opts = DTOpts::TT64|DTOpts::DandT|DTOpts::LongDate); +#endif + +} // namespace wolv::util diff --git a/libs/utils/source/utils/date_time_format.cpp b/libs/utils/source/utils/date_time_format.cpp new file mode 100644 index 0000000..dd6b2b4 --- /dev/null +++ b/libs/utils/source/utils/date_time_format.cpp @@ -0,0 +1,339 @@ +// date_time_format.cpp + +#include + +#if defined(OS_WINDOWS) +# include +# include +# include +#else +# include +# include +#endif // #if defined(OS_WINDOWS) + +namespace wolv::util { + +#if defined(OS_WINDOWS) + + Locale::Locale(const char *str) { + set(str); + } + + Locale::Locale(const std::string &str) { + set(str); + } + + void Locale::set(const char *str) { + m_locale = str; + } + + void Locale::set(const std::string &str) { + m_locale = str; + } + +#else + + Locale::Locale() { + setInvalid(); + } + + Locale::Locale(const char *str) { + setInvalid(); + set(str); + } + + Locale::Locale(const std::string &str) { + setInvalid(); + set(str); + } + + Locale::Locale(const Locale ©Me) { + setInvalid(); + + m_locale = duplocale(copyMe); + if (!m_locale) { + return; + } + m_valid = true; + } + + Locale::~Locale() { + free(); + } + + Locale& Locale::operator=(const Locale ©Me) { + free(); + if (copyMe.m_valid) { + m_locale = duplocale(copyMe.m_locale); + if (m_locale) { + m_valid = true; + } + } + + return *this; + } + + void Locale::set(const char *str) { + free(); + m_locale = newlocale(LC_TIME_MASK, str, NULL); + if (!m_locale) { + m_locale = duplocale(LC_GLOBAL_LOCALE); + } + if (m_locale) { + m_valid = true; + } + } + + void Locale::set(const std::string &str) { + set(str.c_str()); + } + + void Locale::setInvalid() { + m_valid = false; + m_locale = 0; + } + + void Locale::free() { + if (m_valid) { + freelocale(m_locale); + setInvalid(); + } + } + +#endif + +#if defined(OS_WINDOWS) + +std::optional timeTToSystemTime(i64 t, DTOpts sz) { + // *** The types *** + // + // /----------------------------------------------------------------------------\ + // | Type | Bits | Signed | Resolution | Epoch | Range | + // |----------+------+--------+------------+------------+-----------------------| + // | time_t | 32 | Yes | 1s | 1970-01-01 | 1901 - 2038 | + // | time_t | 64 | Yes | 1s | 1970-01-01 | ~+/-292 billion years | + // | FILETIME | 64 | No | 100ns | 1601-01-01 | 1601 - ~586,000 AD | + // \----------------------------------------------------------------------------/ + // + // time_t_epoch - FILETIME_epoch (seconds): 11644473600 + // In 100 nanoseconds: 116444736000000000 + constexpr wolv::i64 sTo100ns = 10000000LL; // conversion factor from seconds to nanoseconds + constexpr wolv::i64 eDiffIn100ns = 116444736000000000LL; // epoch differene in 100 nanoseconds + + // *** We convert a time_t to a FILETIME like this *** + // + // ft = (tt*sTo100ns)+eDiffIn100ns; + // + // *** So what is the time_t range convertable to a FILETIME? *** + // + // We're going to have to do some algebra. + // + // The minimum time_t covertable to a FILETIME: + // (tt * sTo100ns) + eDiffIn100ns >= 0 + // tt * sTo100ns >= -eDiffIn100ns + // tt >= -eDiffIn100ns / sTo100ns + // + // The maximum time_t covertable to a FILETIME: + // (tt * sTo100ns) + eDiffIn100ns <= 2^64-1 + // tt*sTo100ns <= (2^64-1) - eDiffIn100ns + // tt <= ((2^64-1) - eDiffIn100ns) / sTo100ns + if ((sz&DTOpts::TTMask) == DTOpts::TT64) { + constexpr i64 firstConv = -eDiffIn100ns / sTo100ns; + constexpr i64 lastConv = (static_cast(-1) - eDiffIn100ns) / sTo100ns; + + if (tlastConv) { + return std::nullopt; + } + } + else { + assert( + t >= std::numeric_limits::min() && + t <= std::numeric_limits::max() ); + } + + u64 inFT = (t * sTo100ns) + eDiffIn100ns; + + FILETIME ft; + ft.dwLowDateTime = inFT & ((1ULL << 32) - 1); + ft.dwHighDateTime = inFT >> 32; + + SYSTEMTIME st; + if (!FileTimeToSystemTime(&ft, &st)) { + return std::nullopt; + } + + return st; +} + +std::optional formatSystemTime(LPCSTR lc, const SYSTEMTIME* pss, DTOpts opts) { + // We try to minimize heap allocations by preferring stack-based buffers. + // If the data exceeds the stack buffer size, we fall back to the heap. + // Functions like GetDateFormatEx and WideCharToMultiByte are somewhat + // awkward: they can either calculate the required buffer size or write + // into a supplied buffer, but not both at the same time. Our approach is + // to start with a stack buffer and, if it proves too small, query the API + // for the required size and retry using a suitably sized buffer. + // These constants are guesses at reasonably sized stack-based buffers. + constexpr size_t dateBufLen = 48; + constexpr size_t timeBufLen = 24; + constexpr WCHAR dtSep[] = L" "; + constexpr size_t dtSepStrLen = sizeof(dtSep)/sizeof(dtSep[0])-1; + constexpr size_t dtStrLen = dateBufLen + dtSepStrLen + timeBufLen; + + WCHAR wideLocale[LOCALE_NAME_MAX_LENGTH]; + for (size_t i=0; i date; + LPWSTR pCursor = date; + + DWORD dateFlags = ((opts & DTOpts::DateFmtMask) == DTOpts::LongDate) ? DATE_LONGDATE : 0; + + int gdfLen = 0; + if ((opts & DTOpts::D) == DTOpts::D) { + gdfLen = GetDateFormatEx(wideLocale, dateFlags, pss, NULL, pCursor, static_cast(date.size()), NULL); + if (gdfLen == 0) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Our stack buffer was too small. Measure, alloc and convert. + gdfLen = GetDateFormatEx(wideLocale, dateFlags, pss, NULL, NULL, 0, NULL); + if (gdfLen == 0) { + return std::nullopt; + } + date.grow(gdfLen-1 + ((opts & DTOpts::T)==DTOpts::T ? dtSepStrLen+timeBufLen : 0)+1, {&pCursor}); + gdfLen = GetDateFormatEx(wideLocale, dateFlags, pss, NULL, pCursor, static_cast(date.size()), NULL); + if (gdfLen == 0) { + return std::nullopt; + } + } + else { + return std::nullopt; + } + } + pCursor += gdfLen-1; + } + + if ((opts & DTOpts::T) == DTOpts::T) { + if ((opts & DTOpts::D) == DTOpts::D) { + date.grow(gdfLen-1 + dtSepStrLen + timeBufLen + 1, {&pCursor}); + memcpy(pCursor, dtSep, sizeof(dtSep)); + pCursor += dtSepStrLen; + } + + size_t time_sz = date.size()-(pCursor-date); + + int gtfLen = GetTimeFormatEx(wideLocale, 0, pss, NULL, pCursor, static_cast(time_sz)); + if (gtfLen == 0) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Our stack buffer was too small. Measure, alloc and convert. + gtfLen = GetTimeFormatEx(wideLocale, 0, pss, NULL, NULL, 0); + if (gtfLen == 0) { + return std::nullopt; + } + + date.grow(gdfLen-1 + dtSepStrLen + gtfLen-1 + 1, {&pCursor}); + + time_sz = date.size() - (pCursor - date); + + gtfLen = GetTimeFormatEx( + wideLocale, 0, pss, NULL, pCursor, static_cast(gtfLen-1 + 1)); + if (gtfLen == 0) { + return std::nullopt; + } + } + else { + return std::nullopt; + } + } + } + + std::string out; + out.resize(dtStrLen); + + int res = WideCharToMultiByte(CP_UTF8, 0, date, -1, &out[0], dtStrLen, NULL, NULL); + if (res == 0) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Our buffer was too small. Measure, alloc and convert. + res = WideCharToMultiByte(CP_UTF8, 0, date, -1, NULL, 0, NULL, NULL); + if (res == 0) { + return std::nullopt; + } + + out.resize(res); + + res = WideCharToMultiByte(CP_UTF8, 0, date, -1, &out[0], res, NULL, NULL); + if (res == 0) { + return std::nullopt; + } + } + else { + return std::nullopt; + } + } + out.resize(res-1); + + return out; +} + +std::optional formatTT(const Locale &lc, wolv::i64 t, DTOpts opts) { + auto st = timeTToSystemTime(t, opts); + if (!st) { + return std::nullopt; + } + + auto dt = formatSystemTime(lc, &st.value(), opts); + if (!dt) { + return std::nullopt; + } + + return dt.value(); +} + +#else + +std::optional formatTT(const Locale &lc, wolv::i64 t, DTOpts opts) { + constexpr size_t szMin = 64; + constexpr size_t szMax = 1024; + + const char *datetime_fs = "%c"; + const char *date_fs = "%x"; + const char *time_fs = "%X"; + + const char *fs = datetime_fs; + switch (opts & DTOpts::DTMask) { + case DTOpts::DandT: + fs = datetime_fs; + break; + + case DTOpts::D: + fs = date_fs; + break; + + case DTOpts::T: + fs = time_fs; + break; + } + + struct tm tm; + gmtime_r(&t, &tm); + + std::string str; + for (size_t bsz=szMin; bsz<=szMax; bsz*=2) { + str.resize(bsz); + size_t sz; + if (sz = strftime_l(&str[0], bsz, fs, &tm, lc)) { + str.resize(sz); + return str; + } + } + + return std::nullopt; +} + +#endif // #if defined(OS_WINDOWS) + +} // namespace wolv::util