aboutsummaryrefslogtreecommitdiff
path: root/zencore
diff options
context:
space:
mode:
authorPer Larsson <[email protected]>2021-12-14 12:34:47 +0100
committerPer Larsson <[email protected]>2021-12-14 12:34:47 +0100
commitb6c6568e1618f10d2160d836b65e35586e3c740f (patch)
treef6a929cf918850bbba87d0ee67cd3482b2d50e24 /zencore
parentFixed bug in z$ service returning partial cache records and enable small obje... (diff)
parentPartial revert b363c5b (diff)
downloadzen-b6c6568e1618f10d2160d836b65e35586e3c740f.tar.xz
zen-b6c6568e1618f10d2160d836b65e35586e3c740f.zip
Merged main.
Diffstat (limited to 'zencore')
-rw-r--r--zencore/blake3.cpp4
-rw-r--r--zencore/compactbinary.cpp186
-rw-r--r--zencore/compactbinarybuilder.cpp5
-rw-r--r--zencore/filesystem.cpp262
-rw-r--r--zencore/include/zencore/blockingqueue.h1
-rw-r--r--zencore/include/zencore/compactbinary.h2
-rw-r--r--zencore/include/zencore/filesystem.h73
-rw-r--r--zencore/include/zencore/intmath.h6
-rw-r--r--zencore/include/zencore/iobuffer.h4
-rw-r--r--zencore/include/zencore/logging.h10
-rw-r--r--zencore/include/zencore/refcount.h2
-rw-r--r--zencore/include/zencore/string.h38
-rw-r--r--zencore/include/zencore/thread.h58
-rw-r--r--zencore/include/zencore/trace.h51
-rw-r--r--zencore/include/zencore/uid.h2
-rw-r--r--zencore/include/zencore/varint.h2
-rw-r--r--zencore/include/zencore/windows.h7
-rw-r--r--zencore/include/zencore/zencore.h79
-rw-r--r--zencore/iobuffer.cpp31
-rw-r--r--zencore/memory.cpp1
-rw-r--r--zencore/stats.cpp2
-rw-r--r--zencore/string.cpp41
-rw-r--r--zencore/thread.cpp803
-rw-r--r--zencore/trace.cpp33
-rw-r--r--zencore/xmake.lua35
25 files changed, 1515 insertions, 223 deletions
diff --git a/zencore/blake3.cpp b/zencore/blake3.cpp
index 8f8952271..b2019d4e2 100644
--- a/zencore/blake3.cpp
+++ b/zencore/blake3.cpp
@@ -8,7 +8,9 @@
#include <zencore/zencore.h>
#include "../thirdparty/BLAKE3/c/blake3.h"
-#pragma comment(lib, "blake3.lib")
+#if ZEN_PLATFORM_WINDOWS
+# pragma comment(lib, "blake3.lib")
+#endif
#include <string.h>
diff --git a/zencore/compactbinary.cpp b/zencore/compactbinary.cpp
index c6bf38b04..494b45581 100644
--- a/zencore/compactbinary.cpp
+++ b/zencore/compactbinary.cpp
@@ -18,6 +18,8 @@
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
+#else
+# include <time.h>
#endif
#if ZEN_WITH_TESTS
@@ -46,15 +48,31 @@ IsLeapYear(int Year)
return false;
}
+static constexpr uint64_t
+GetPlatformToDateTimeBiasInSeconds()
+{
+#if ZEN_PLATFORM_WINDOWS
+ const uint64_t PlatformEpochYear = 1601;
+#else
+ const uint64_t PlatformEpochYear = 1970;
+#endif
+ const uint64_t DateTimeEpochYear = 1;
+ return uint64_t(double(PlatformEpochYear - DateTimeEpochYear) * 365.2425) * 86400;
+}
+
DateTime
DateTime::Now()
{
+ static const uint64_t EpochBias = GetPlatformToDateTimeBiasInSeconds();
+ static const uint64_t SecsTo100nsTicks = int64_t(10e9 / 100);
+
#if ZEN_PLATFORM_WINDOWS
FILETIME SysTime;
GetSystemTimeAsFileTime(&SysTime);
- return DateTime{504911232000000000ull + (uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime};
+ return DateTime{(EpochBias * SecsTo100nsTicks) + (uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime};
#else
-# error Needs implementation
+ int64_t SecondsSinceUnixEpoch = time(nullptr);
+ return DateTime{(EpochBias + SecondsSinceUnixEpoch) * SecsTo100nsTicks};
#endif
}
@@ -82,88 +100,6 @@ DateTime::Set(int Year, int Month, int Day, int Hour, int Minute, int Second, in
Second * TimeSpan::TicksPerSecond + MilliSecond * TimeSpan::TicksPerMillisecond;
}
-void
-TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano)
-{
- int64_t TotalTicks = 0;
-
- TotalTicks += Days * TicksPerDay;
- TotalTicks += Hours * TicksPerHour;
- TotalTicks += Minutes * TicksPerMinute;
- TotalTicks += Seconds * TicksPerSecond;
- TotalTicks += FractionNano / NanosecondsPerTick;
-
- Ticks = TotalTicks;
-}
-
-std::string
-TimeSpan::ToString(const char* Format) const
-{
- using namespace fmt::literals;
-
- StringBuilder<128> Result;
-
- Result.Append((Ticks < 0) ? '-' : '+');
-
- while (*Format != '\0')
- {
- if ((*Format == '%') && (*++Format != '\0'))
- {
- switch (*Format)
- {
- case 'd':
- Result.Append("{}"_format(GetDays()));
- break;
- case 'D':
- Result.Append("{:08}"_format(GetDays()));
- break;
- case 'h':
- Result.Append("{:02}"_format(GetHours()));
- break;
- case 'm':
- Result.Append("{:02}"_format(GetMinutes()));
- break;
- case 's':
- Result.Append("{:02}"_format(GetSeconds()));
- break;
- case 'f':
- Result.Append("{:03}"_format(GetFractionMilli()));
- break;
- case 'u':
- Result.Append("{:06}"_format(GetFractionMicro()));
- break;
- case 't':
- Result.Append("{:07}"_format(GetFractionTicks()));
- break;
- case 'n':
- Result.Append("{:09}"_format(GetFractionNano()));
- break;
- default:
- Result.Append(*Format);
- }
- }
- else
- {
- Result.Append(*Format);
- }
-
- ++Format;
- }
-
- return Result.ToString();
-}
-
-std::string
-TimeSpan::ToString() const
-{
- if (GetDays() == 0)
- {
- return ToString("%h:%m:%s.%f");
- }
-
- return ToString("%d.%h:%m:%s.%f");
-}
-
int
DateTime::GetYear() const
{
@@ -329,6 +265,88 @@ DateTime::ToIso8601() const
return ToString("%Y-%m-%dT%H:%M:%S.%sZ");
}
+void
+TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano)
+{
+ int64_t TotalTicks = 0;
+
+ TotalTicks += Days * TicksPerDay;
+ TotalTicks += Hours * TicksPerHour;
+ TotalTicks += Minutes * TicksPerMinute;
+ TotalTicks += Seconds * TicksPerSecond;
+ TotalTicks += FractionNano / NanosecondsPerTick;
+
+ Ticks = TotalTicks;
+}
+
+std::string
+TimeSpan::ToString(const char* Format) const
+{
+ using namespace fmt::literals;
+
+ StringBuilder<128> Result;
+
+ Result.Append((int64_t(Ticks) < 0) ? '-' : '+');
+
+ while (*Format != '\0')
+ {
+ if ((*Format == '%') && (*++Format != '\0'))
+ {
+ switch (*Format)
+ {
+ case 'd':
+ Result.Append("{}"_format(GetDays()));
+ break;
+ case 'D':
+ Result.Append("{:08}"_format(GetDays()));
+ break;
+ case 'h':
+ Result.Append("{:02}"_format(GetHours()));
+ break;
+ case 'm':
+ Result.Append("{:02}"_format(GetMinutes()));
+ break;
+ case 's':
+ Result.Append("{:02}"_format(GetSeconds()));
+ break;
+ case 'f':
+ Result.Append("{:03}"_format(GetFractionMilli()));
+ break;
+ case 'u':
+ Result.Append("{:06}"_format(GetFractionMicro()));
+ break;
+ case 't':
+ Result.Append("{:07}"_format(GetFractionTicks()));
+ break;
+ case 'n':
+ Result.Append("{:09}"_format(GetFractionNano()));
+ break;
+ default:
+ Result.Append(*Format);
+ }
+ }
+ else
+ {
+ Result.Append(*Format);
+ }
+
+ ++Format;
+ }
+
+ return Result.ToString();
+}
+
+std::string
+TimeSpan::ToString() const
+{
+ if (GetDays() == 0)
+ {
+ return ToString("%h:%m:%s.%f");
+ }
+
+ return ToString("%d.%h:%m:%s.%f");
+}
+
StringBuilderBase&
Guid::ToString(StringBuilderBase& Sb) const
{
diff --git a/zencore/compactbinarybuilder.cpp b/zencore/compactbinarybuilder.cpp
index fa5b6a69b..5111504e1 100644
--- a/zencore/compactbinarybuilder.cpp
+++ b/zencore/compactbinarybuilder.cpp
@@ -985,7 +985,12 @@ TEST_CASE("usonbuilder.string")
SUBCASE("Non-ASCII String")
{
+# if ZEN_SIZEOF_WCHAR_T == 2
wchar_t Value[2] = {0xd83d, 0xde00};
+# else
+ wchar_t Value[1] = {0x1f600};
+# endif
+
Writer.AddString("\xf0\x9f\x98\x80"sv);
Writer.AddString(std::wstring_view(Value, ZEN_ARRAY_COUNT(Value)));
CbFieldIterator Fields = Writer.Save();
diff --git a/zencore/filesystem.cpp b/zencore/filesystem.cpp
index 53e6f1f38..f850f1a80 100644
--- a/zencore/filesystem.cpp
+++ b/zencore/filesystem.cpp
@@ -174,7 +174,8 @@ CleanDirectory(const wchar_t* DirPath)
bool
CreateDirectories(const std::filesystem::path& Dir)
{
- return std::filesystem::create_directories(Dir);
+ std::error_code ErrorCode;
+ return std::filesystem::create_directories(Dir, ErrorCode);
}
bool
@@ -244,6 +245,7 @@ SupportsBlockRefCounting(std::filesystem::path Path)
return true;
#else
+ ZEN_UNUSED(Path);
return false;
#endif // ZEN_PLATFORM_WINDOWS
}
@@ -406,16 +408,47 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
const bool AllOk = (TRUE == SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition));
return AllOk;
-#else
+#elif ZEN_PLATFORM_LINUX
+# if 0
+ struct ScopedFd
+ {
+ ~ScopedFd() { close(Fd); }
+ int Fd;
+ };
+
+ // The 'from' file
+ int FromFd = open(FromPath.c_str(), O_RDONLY|O_CLOEXEC);
+ if (FromFd < 0)
+ {
+ return false;
+ }
+ ScopedFd $From = { FromFd };
+
+ // The 'to' file
+ int ToFd = open(ToPath.c_str(), O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0666);
+ if (ToFd < 0)
+ {
+ return false;
+ }
+ ScopedFd $To = { FromFd };
+
+ ioctl(ToFd, FICLONE, FromFd);
+
+ return false;
+# endif // 0
+ ZEN_UNUSED(FromPath, ToPath);
ZEN_ERROR("CloneFile() is not implemented on this platform");
return false;
+#elif ZEN_PLATFORM_MAC
+ /* clonefile() syscall if APFS */
+# error not implemented
+ return false;
#endif // ZEN_PLATFORM_WINDOWS
}
bool
CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options)
{
-#if ZEN_PLATFORM_WINDOWS
bool Success = false;
if (Options.EnableClone)
@@ -433,6 +466,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
return false;
}
+#if ZEN_PLATFORM_WINDOWS
BOOL CancelFlag = FALSE;
Success = !!::CopyFileExW(FromPath.c_str(),
ToPath.c_str(),
@@ -440,17 +474,58 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
/* lpData */ nullptr,
&CancelFlag,
/* dwCopyFlags */ 0);
+#else
+ using namespace fmt::literals;
- if (!Success)
+ struct ScopedFd
+ {
+ ~ScopedFd() { close(Fd); }
+ int Fd;
+ };
+
+ // From file
+ int FromFd = open(FromPath.c_str(), O_RDONLY | O_CLOEXEC);
+ if (FromFd < 0)
{
- throw std::system_error(std::error_code(::GetLastError(), std::system_category()), "file copy failed");
+ ThrowLastError("failed to open file {}"_format(FromPath));
}
+ ScopedFd $From = {FromFd};
- return Success;
-#else
- ZEN_ERROR("CopyFile() is not implemented on this platform");
- return false;
+ // To file
+ int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
+ if (ToFd < 0)
+ {
+ ThrowLastError("failed to create file {}"_format(ToPath));
+ }
+ ScopedFd $To = {ToFd};
+
+ // Copy impl
+ static const size_t BufferSize = 64 << 10;
+ void* Buffer = malloc(BufferSize);
+ while (true)
+ {
+ int BytesRead = read(FromFd, Buffer, BufferSize);
+ if (BytesRead <= 0)
+ {
+ Success = (BytesRead == 0);
+ break;
+ }
+
+ if (write(ToFd, Buffer, BytesRead) != BufferSize)
+ {
+ Success = false;
+ break;
+ }
+ }
+ free(Buffer);
#endif // ZEN_PLATFORM_WINDOWS
+
+ if (!Success)
+ {
+ ThrowLastError("file copy failed"sv);
+ }
+
+ return true;
}
void
@@ -474,11 +549,12 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
}
#else
- int Fd = open(Path.c_str(), O_WRONLY);
+ int OpenFlags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
+ int Fd = open(Path.c_str(), OpenFlags, 0666);
if (Fd < 0)
{
zen::CreateDirectories(Path.parent_path());
- Fd = open(Path.c_str(), O_WRONLY);
+ Fd = open(Path.c_str(), OpenFlags, 0666);
}
if (Fd < 0)
@@ -505,7 +581,7 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
ThrowSystemException(hRes, "File write failed for '{}'"_format(Path).c_str());
}
#else
- if (write(Fd, DataPtr, WriteSize) != WriteSize)
+ if (write(Fd, DataPtr, WriteSize) != int64_t(WriteSize))
{
ThrowLastError("File write failed for '{}'"_format(Path));
}
@@ -569,10 +645,12 @@ ReadFile(std::filesystem::path Path)
FileSizeBytes = FileSize.EndOfFile.QuadPart;
Handle = FromFile.Detach();
#else
- int Fd = open(Path.c_str(), O_RDONLY);
+ int Fd = open(Path.c_str(), O_RDONLY | O_CLOEXEC);
if (Fd < 0)
{
- return FileContents{.ErrorCode = std::error_code(zen::GetLastError(), std::system_category())};
+ FileContents Ret;
+ Ret.ErrorCode = std::error_code(zen::GetLastError(), std::system_category());
+ return Ret;
}
static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
@@ -616,16 +694,57 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
ProcessFunc(ReadBuffer.data(), dwBytesRead);
}
+#else
+ int Fd = open(Path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (Fd < 0)
+ {
+ return false;
+ }
+
+ bool Success = true;
+
+ void* Buffer = malloc(ChunkSize);
+ while (true)
+ {
+ int BytesRead = read(Fd, Buffer, ChunkSize);
+ if (BytesRead < 0)
+ {
+ Success = false;
+ break;
+ }
+
+ if (BytesRead == 0)
+ {
+ break;
+ }
+
+ ProcessFunc(Buffer, BytesRead);
+ }
+
+ free(Buffer);
+ close(Fd);
+
+ if (!Success)
+ {
+ ThrowLastError("file scan failed");
+ }
+#endif // ZEN_PLATFORM_WINDOWS
return true;
+}
+
+void
+PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out)
+{
+#if ZEN_PLATFORM_WINDOWS
+ WideToUtf8(Path.native().c_str(), Out);
#else
- ZEN_ERROR("ScanFile() is not implemented on this platform");
- return false;
-#endif // ZEN_PLATFORM_WINDOWS
+ Out << Path.c_str();
+#endif
}
std::string
-ToUtf8(const std::filesystem::path& Path)
+PathToUtf8(const std::filesystem::path& Path)
{
#if ZEN_PLATFORM_WINDOWS
return WideToUtf8(Path.native().c_str());
@@ -750,7 +869,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
ThrowLastError("Failed to open directory for traversal: {}"_format(RootDir.c_str()));
}
- for (struct dirent* Entry; Entry = readdir(Dir);)
+ for (struct dirent* Entry; (Entry = readdir(Dir));)
{
const char* FileName = Entry->d_name;
@@ -806,14 +925,15 @@ PathFromHandle(void* NativeHandle)
return FullPath;
#elif ZEN_PLATFORM_LINUX
- char Buffer[256];
- sprintf(Buffer, "/proc/%d/fd/%d", getpid(), int(uintptr_t(NativeHandle)));
- ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1);
+ char Link[256];
+ char Path[64];
+ sprintf(Path, "/proc/self/fd/%d", int(uintptr_t(NativeHandle)));
+ ssize_t BytesRead = readlink(Path, Link, sizeof(Link) - 1);
if (BytesRead <= 0)
return std::filesystem::path();
- Buffer[BytesRead] = '\0';
- return Buffer;
+ Link[BytesRead] = '\0';
+ return Link;
#else
# error Unimplemented platform
#endif // ZEN_PLATFORM_WINDOWS
@@ -828,14 +948,13 @@ GetRunningExecutablePath()
return {std::wstring_view(ExePath, PathLength)};
#elif ZEN_PLATFORM_LINUX
- char Buffer[256];
- sprintf(Buffer, "/proc/%d/exe", getpid());
- ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1);
+ char Link[256];
+ ssize_t BytesRead = readlink("/proc/self/exe", Link, sizeof(Link) - 1);
if (BytesRead < 0)
return {};
- Buffer[BytesRead] = '\0';
- return Buffer;
+ Link[BytesRead] = '\0';
+ return Link;
#else
# error Unimplemented platform
#endif // ZEN_PLATFORM_WINDOWS
@@ -859,7 +978,7 @@ TEST_CASE("filesystem")
// GetExePath -- this is not a great test as it's so dependent on where the this code gets linked in
path BinPath = GetRunningExecutablePath();
- const bool ExpectedExe = ToUtf8(BinPath.stem().native()).ends_with("-test"sv) || BinPath.stem() == "zenserver";
+ const bool ExpectedExe = PathToUtf8(BinPath.stem().native()).ends_with("-test"sv) || BinPath.stem() == "zenserver";
CHECK(ExpectedExe);
CHECK(is_regular_file(BinPath));
@@ -869,7 +988,7 @@ TEST_CASE("filesystem")
Handle = CreateFileW(BinPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
CHECK(Handle != INVALID_HANDLE_VALUE);
# else
- int Fd = open(BinPath.c_str(), O_RDONLY);
+ int Fd = open(BinPath.c_str(), O_RDONLY | O_CLOEXEC);
CHECK(Fd >= 0);
Handle = (void*)uintptr_t(Fd);
# endif
@@ -900,6 +1019,87 @@ TEST_CASE("filesystem")
FileSystemTraversal().TraverseFileSystem(BinPath.parent_path().parent_path(), Visitor);
CHECK(Visitor.bFoundExpected);
+
+ // Scan/read file
+ FileContents BinRead = ReadFile(BinPath);
+ std::vector<uint8_t> BinScan;
+ ScanFile(BinPath, 16 << 10, [&](const void* Data, size_t Size) {
+ const auto* Ptr = (uint8_t*)Data;
+ BinScan.insert(BinScan.end(), Ptr, Ptr + Size);
+ });
+ CHECK_EQ(BinRead.Data.size(), 1);
+ CHECK_EQ(BinScan.size(), BinRead.Data[0].GetSize());
+}
+
+TEST_CASE("WriteFile")
+{
+ std::filesystem::path TempFile = GetRunningExecutablePath().parent_path();
+ TempFile /= "write_file_test";
+
+ uint64_t Magics[] = {
+ 0x0'a9e'a9e'a9e'a9e'a9e,
+ 0x0'493'493'493'493'493,
+ };
+
+ struct
+ {
+ const void* Data;
+ size_t Size;
+ } MagicTests[] = {
+ {
+ Magics,
+ sizeof(Magics),
+ },
+ {
+ Magics + 1,
+ sizeof(Magics[0]),
+ },
+ };
+ for (auto& MagicTest : MagicTests)
+ {
+ WriteFile(TempFile, IoBuffer(IoBuffer::Wrap, MagicTest.Data, MagicTest.Size));
+
+ FileContents MagicsReadback = ReadFile(TempFile);
+ CHECK_EQ(MagicsReadback.Data.size(), 1);
+ CHECK_EQ(MagicsReadback.Data[0].GetSize(), MagicTest.Size);
+ CHECK_EQ(memcmp(MagicTest.Data, MagicsReadback.Data[0].Data(), MagicTest.Size), 0);
+ }
+
+ std::filesystem::remove(TempFile);
+}
+
+TEST_CASE("PathBuilder")
+{
+# if ZEN_PLATFORM_WINDOWS
+ const char* foo_bar = "/foo\\bar";
+# else
+ const char* foo_bar = "/foo/bar";
+# endif
+
+ ExtendablePathBuilder<32> Path;
+ for (const char* Prefix : {"/foo", "/foo/"})
+ {
+ Path.Reset();
+ Path.Append(Prefix);
+ Path /= "bar";
+ CHECK(Path.ToPath() == foo_bar);
+ }
+
+ using fspath = std::filesystem::path;
+
+ Path.Reset();
+ Path.Append(fspath("/foo/"));
+ Path /= (fspath("bar"));
+ CHECK(Path.ToPath() == foo_bar);
+
+# if ZEN_PLATFORM_WINDOWS
+ Path.Reset();
+ Path.Append(fspath(L"/\u0119oo/"));
+ Path /= L"bar";
+ printf("%ls\n", Path.ToPath().c_str());
+ CHECK(Path.ToView() == L"/\u0119oo/bar");
+ CHECK(Path.ToPath() == L"\\\u0119oo\\bar");
+# endif
}
#endif
diff --git a/zencore/include/zencore/blockingqueue.h b/zencore/include/zencore/blockingqueue.h
index 277095689..f92df5a54 100644
--- a/zencore/include/zencore/blockingqueue.h
+++ b/zencore/include/zencore/blockingqueue.h
@@ -3,6 +3,7 @@
#pragma once
#include <atomic>
+#include <condition_variable>
#include <deque>
#include <mutex>
diff --git a/zencore/include/zencore/compactbinary.h b/zencore/include/zencore/compactbinary.h
index 06331c510..66fa3065d 100644
--- a/zencore/include/zencore/compactbinary.h
+++ b/zencore/include/zencore/compactbinary.h
@@ -63,7 +63,7 @@ public:
private:
void Set(int Year, int Month, int Day, int Hours, int Minutes, int Seconds, int MilliSecond);
- uint64_t Ticks;
+ uint64_t Ticks; // 1 tick == 0.1us == 100ns, epoch == Jan 1st 0001
};
class TimeSpan
diff --git a/zencore/include/zencore/filesystem.h b/zencore/include/zencore/filesystem.h
index 155291e67..16c16fc07 100644
--- a/zencore/include/zencore/filesystem.h
+++ b/zencore/include/zencore/filesystem.h
@@ -5,6 +5,7 @@
#include "zencore.h"
#include <zencore/iobuffer.h>
+#include <zencore/string.h>
#include <filesystem>
#include <functional>
@@ -55,7 +56,74 @@ struct CopyFileOptions
ZENCORE_API bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
ZENCORE_API bool SupportsBlockRefCounting(std::filesystem::path Path);
-ZENCORE_API std::string ToUtf8(const std::filesystem::path& Path);
+ZENCORE_API void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out);
+ZENCORE_API std::string PathToUtf8(const std::filesystem::path& Path);
+
+extern template class StringBuilderImpl<std::filesystem::path::value_type>;
+
+/**
+ * Helper class for building paths. Backed by a string builder.
+ *
+ */
+class PathBuilderBase : public StringBuilderImpl<std::filesystem::path::value_type>
+{
+private:
+ using Super = StringBuilderImpl<std::filesystem::path::value_type>;
+
+protected:
+ using CharType = std::filesystem::path::value_type;
+ using ViewType = std::basic_string_view<CharType>;
+
+public:
+ void Append(const std::filesystem::path& Rhs) { Super::Append(Rhs.c_str()); }
+ void operator/=(const std::filesystem::path& Rhs) { this->operator/=(Rhs.c_str()); };
+ void operator/=(const CharType* Rhs)
+ {
+ AppendSeparator();
+ Super::Append(Rhs);
+ }
+ operator ViewType() const { return ToView(); }
+ std::basic_string_view<CharType> ToView() const { return std::basic_string_view<CharType>(Data(), Size()); }
+ std::filesystem::path ToPath() const { return std::filesystem::path(ToView()); }
+
+ std::string ToUtf8() const
+ {
+#if ZEN_PLATFORM_WINDOWS
+ return WideToUtf8(ToView());
+#else
+ return std::string(ToView());
+#endif
+ }
+
+ void AppendSeparator()
+ {
+ if (ToView().ends_with(std::filesystem::path::preferred_separator)
+#if ZEN_PLATFORM_WINDOWS
+ || ToView().ends_with('/')
+#endif
+ )
+ return;
+
+ Super::Append(std::filesystem::path::preferred_separator);
+ }
+};
+
+template<size_t N>
+class PathBuilder : public PathBuilderBase
+{
+public:
+ PathBuilder() { Init(m_Buffer, N); }
+
+private:
+ PathBuilderBase::CharType m_Buffer[N];
+};
+
+template<size_t N>
+class ExtendablePathBuilder : public PathBuilder<N>
+{
+public:
+ ExtendablePathBuilder() { this->m_IsExtendable = true; }
+};
struct DiskSpace
{
@@ -85,7 +153,8 @@ class FileSystemTraversal
public:
struct TreeVisitor
{
- using path_view = std::basic_string_view<std::filesystem::path::value_type>;
+ using path_view = std::basic_string_view<std::filesystem::path::value_type>;
+ using path_string = std::filesystem::path::string_type;
virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) = 0;
diff --git a/zencore/include/zencore/intmath.h b/zencore/include/zencore/intmath.h
index 7619e1950..0d0ceff16 100644
--- a/zencore/include/zencore/intmath.h
+++ b/zencore/include/zencore/intmath.h
@@ -107,7 +107,7 @@ FloorLog2(uint32_t Value)
static inline uint32_t
CountLeadingZeros(uint32_t Value)
{
- unsigned long Log2;
+ unsigned long Log2 = 0;
_BitScanReverse64(&Log2, (uint64_t(Value) << 1) | 1);
return 32 - Log2;
}
@@ -115,7 +115,7 @@ CountLeadingZeros(uint32_t Value)
static inline uint64_t
FloorLog2_64(uint64_t Value)
{
- unsigned long Log2;
+ unsigned long Log2 = 0;
long Mask = -long(_BitScanReverse64(&Log2, Value) != 0);
return Log2 & Mask;
}
@@ -123,7 +123,7 @@ FloorLog2_64(uint64_t Value)
static inline uint64_t
CountLeadingZeros64(uint64_t Value)
{
- unsigned long Log2;
+ unsigned long Log2 = 0;
long Mask = -long(_BitScanReverse64(&Log2, Value) != 0);
return ((63 - Log2) & Mask) | (64 & ~Mask);
}
diff --git a/zencore/include/zencore/iobuffer.h b/zencore/include/zencore/iobuffer.h
index 012e9a9df..816179a0a 100644
--- a/zencore/include/zencore/iobuffer.h
+++ b/zencore/include/zencore/iobuffer.h
@@ -383,10 +383,8 @@ private:
class IoBufferBuilder
{
- using path_char_t = std::filesystem::path::value_type;
-
public:
- ZENCORE_API static IoBuffer MakeFromFile(const path_char_t* FileName, uint64_t Offset = 0, uint64_t Size = ~0ull);
+ ZENCORE_API static IoBuffer MakeFromFile(const std::filesystem::path& FileName, uint64_t Offset = 0, uint64_t Size = ~0ull);
ZENCORE_API static IoBuffer MakeFromTemporaryFile(const std::filesystem::path& FileName);
ZENCORE_API static IoBuffer MakeFromFileHandle(void* FileHandle, uint64_t Offset = 0, uint64_t Size = ~0ull);
ZENCORE_API static IoBuffer ReadFromFileMaybe(IoBuffer& InBuffer);
diff --git a/zencore/include/zencore/logging.h b/zencore/include/zencore/logging.h
index 0b080cb9d..468e5d6e2 100644
--- a/zencore/include/zencore/logging.h
+++ b/zencore/include/zencore/logging.h
@@ -82,9 +82,9 @@ using zen::Log;
Log().critical(fmtstr##sv, ##__VA_ARGS__); \
} while (false)
-#define ZEN_CONSOLE(fmtstr, ...) \
- do \
- { \
- using namespace std::literals; \
- ConsoleLog().info(fmtstr##sv, __VA_ARGS__); \
+#define ZEN_CONSOLE(fmtstr, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ ConsoleLog().info(fmtstr##sv, ##__VA_ARGS__); \
} while (false)
diff --git a/zencore/include/zencore/refcount.h b/zencore/include/zencore/refcount.h
index 7167ab3b5..92ebebcfe 100644
--- a/zencore/include/zencore/refcount.h
+++ b/zencore/include/zencore/refcount.h
@@ -159,7 +159,7 @@ public:
private:
T* m_Ref = nullptr;
- template<class T>
+ template<class U>
friend class Ref;
};
diff --git a/zencore/include/zencore/string.h b/zencore/include/zencore/string.h
index 031ea2c1d..848310aa3 100644
--- a/zencore/include/zencore/string.h
+++ b/zencore/include/zencore/string.h
@@ -219,6 +219,19 @@ public:
return AppendRange(String.data(), String.data() + String.size());
}
+ inline StringBuilderImpl& AppendBool(bool v)
+ {
+ // This is a method instead of a << operator overload as the latter can
+ // easily get called with non-bool types like pointers. It is a very
+ // subtle behaviour that can cause bugs.
+ using namespace std::literals;
+ if (v)
+ {
+ return AppendAscii("true"sv);
+ }
+ return AppendAscii("false"sv);
+ }
+
inline void RemoveSuffix(uint32_t Count)
{
ZEN_ASSERT(Count <= Size());
@@ -291,15 +304,6 @@ public:
inline StringBuilderImpl& operator<<(const char* str) { return AppendAscii(str); }
inline StringBuilderImpl& operator<<(const std::string_view str) { return AppendAscii(str); }
inline StringBuilderImpl& operator<<(const std::u8string_view str) { return AppendAscii(str); }
- inline StringBuilderImpl& operator<<(bool v)
- {
- using namespace std::literals;
- if (v)
- {
- return AppendAscii("true"sv);
- }
- return AppendAscii("false"sv);
- }
protected:
inline void Init(C* Base, size_t Capacity)
@@ -424,7 +428,7 @@ public:
inline std::wstring_view ToView() const { return std::wstring_view{Data(), Size()}; }
inline std::wstring ToString() const { return std::wstring{Data(), Size()}; }
- inline StringBuilderImpl& operator<<(const std::u16string_view str) { return Append((const wchar_t*)str.data(), str.size()); }
+ inline StringBuilderImpl& operator<<(const std::wstring_view str) { return Append((const wchar_t*)str.data(), str.size()); }
inline StringBuilderImpl& operator<<(const wchar_t* str) { return Append(str); }
using StringBuilderImpl:: operator<<;
};
@@ -460,7 +464,6 @@ std::wstring Utf8ToWide(const std::string_view& wstr);
void WideToUtf8(const wchar_t* wstr, StringBuilderBase& out);
std::string WideToUtf8(const wchar_t* wstr);
-void WideToUtf8(const std::u16string_view& wstr, StringBuilderBase& out);
void WideToUtf8(const std::wstring_view& wstr, StringBuilderBase& out);
std::string WideToUtf8(const std::wstring_view Wstr);
@@ -697,6 +700,19 @@ ForEachStrTok(const std::string_view& Str, char Delim, Fn&& Func)
//////////////////////////////////////////////////////////////////////////
+inline int32_t
+StrCaseCompare(const char* Lhs, const char* Rhs, int64_t Length = -1)
+{
+ // A helper for cross-platform case-insensitive string comparison.
+#if ZEN_PLATFORM_WINDOWS
+ return (Length < 0) ? _stricmp(Lhs, Rhs) : _strnicmp(Lhs, Rhs, size_t(Length));
+#else
+ return (Length < 0) ? strcasecmp(Lhs, Rhs) : strncasecmp(Lhs, Rhs, size_t(Length));
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+
/**
* ASCII character bitset useful for fast and readable parsing
*
diff --git a/zencore/include/zencore/thread.h b/zencore/include/zencore/thread.h
index 9fc4c87a2..16d0e9dee 100644
--- a/zencore/include/zencore/thread.h
+++ b/zencore/include/zencore/thread.h
@@ -6,6 +6,7 @@
#include <shared_mutex>
+#include <filesystem>
#include <string_view>
#include <vector>
@@ -102,11 +103,30 @@ protected:
/** Basic abstraction of an IPC mechanism (aka 'binary semaphore')
*/
-class NamedEvent : public Event
+class NamedEvent
{
public:
+ NamedEvent() = default;
ZENCORE_API explicit NamedEvent(std::string_view EventName);
- ZENCORE_API explicit NamedEvent(std::u8string_view EventName);
+ ZENCORE_API ~NamedEvent();
+ ZENCORE_API void Close();
+ ZENCORE_API void Set();
+ ZENCORE_API bool Wait(int TimeoutMs = -1);
+
+ NamedEvent(NamedEvent&& Rhs) noexcept : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; }
+
+ inline NamedEvent& operator=(NamedEvent&& Rhs) noexcept
+ {
+ std::swap(m_EventHandle, Rhs.m_EventHandle);
+ return *this;
+ }
+
+protected:
+ void* m_EventHandle = nullptr;
+
+private:
+ NamedEvent(const NamedEvent& Rhs) = delete;
+ NamedEvent& operator=(const NamedEvent& Rhs) = delete;
};
/** Basic abstraction of a named (system wide) mutex primitive
@@ -143,13 +163,38 @@ public:
ZENCORE_API bool Wait(int TimeoutMs = -1);
ZENCORE_API void Terminate(int ExitCode);
ZENCORE_API void Reset();
- inline [[nodiscard]] int Pid() const { return m_Pid; }
+ [[nodiscard]] inline int Pid() const { return m_Pid; }
private:
void* m_ProcessHandle = nullptr;
int m_Pid = 0;
};
+/** Basic process creation
+ */
+struct CreateProcOptions
+{
+ enum
+ {
+ Flag_NewConsole = 1 << 0,
+ Flag_Elevated = 1 << 1,
+ Flag_Unelevated = 1 << 2,
+ };
+
+ const std::filesystem::path* WorkingDirectory = nullptr;
+ uint32_t Flags = 0;
+};
+
+#if ZEN_PLATFORM_WINDOWS
+using CreateProcResult = void*; // handle to the process
+#else
+using CreateProcResult = int32_t; // pid
+#endif
+
+ZENCORE_API CreateProcResult CreateProc(const std::filesystem::path& Executable,
+ std::string_view CommandLine, // should also include arg[0] (executable name)
+ const CreateProcOptions& Options = {});
+
/** Process monitor - monitors a list of running processes via polling
Intended to be used to monitor a set of "sponsor" processes, where
@@ -168,12 +213,15 @@ public:
ZENCORE_API bool IsActive() const;
private:
- mutable RwLock m_Lock;
- std::vector<void*> m_ProcessHandles;
+ using HandleType = void*;
+
+ mutable RwLock m_Lock;
+ std::vector<HandleType> m_ProcessHandles;
};
ZENCORE_API bool IsProcessRunning(int pid);
ZENCORE_API int GetCurrentProcessId();
+ZENCORE_API int GetCurrentThreadId();
ZENCORE_API void Sleep(int ms);
diff --git a/zencore/include/zencore/trace.h b/zencore/include/zencore/trace.h
new file mode 100644
index 000000000..a2e69a145
--- /dev/null
+++ b/zencore/include/zencore/trace.h
@@ -0,0 +1,51 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/zencore.h>
+
+#if ZEN_WITH_TRACE
+
+# define __UNREAL__ 0
+# define IS_MONOLITHIC 1
+# define PLATFORM_WINDOWS ZEN_PLATFORM_WINDOWS
+# define PLATFORM_UNIX ZEN_PLATFORM_LINUX
+# define PLATFORM_APPLE ZEN_PLATFORM_MAC
+# define PLATFORM_ANDROID 0
+# define PLATFORM_HOLOLENS 0
+# define UE_BUILD_TEST 0
+# define UE_BUILD_SHIPPING 0
+
+ZEN_THIRD_PARTY_INCLUDES_START
+# if !defined(TRACE_IMPLEMENT)
+# define TRACE_IMPLEMENT 0
+# endif
+# include <trace.h>
+# undef TRACE_IMPLEMENT
+
+ZEN_THIRD_PARTY_INCLUDES_END
+# undef __UNREAL__
+# undef IS_MONOLITHIC
+# undef PLATFORM_WINDOWS
+# undef PLATFORM_UNIX
+# undef PLATFORM_APPLE
+# undef PLATFORM_ANDROID
+# undef PLATFORM_HOLOLENS
+# undef UE_BUILD_TEST
+# undef UE_BUILD_SHIPPING
+
+# define ZEN_TRACE_CPU(x) TRACE_CPU_SCOPE(x)
+
+enum class TraceType
+{
+ File,
+ Network,
+};
+
+void TraceInit(const char* HostOrPath, TraceType Type);
+
+#else
+
+# define ZEN_TRACE_CPU(x)
+
+#endif // ZEN_WITH_TRACE
diff --git a/zencore/include/zencore/uid.h b/zencore/include/zencore/uid.h
index f4e9ab65a..d25aa8059 100644
--- a/zencore/include/zencore/uid.h
+++ b/zencore/include/zencore/uid.h
@@ -74,7 +74,7 @@ struct Oid
size_t operator()(const Oid& id) const
{
const size_t seed = id.OidBits[0];
- return (seed << 6) + (seed >> 2) + 0x9e3779b9 + uint64_t(id.OidBits[1]) | (uint64_t(id.OidBits[2]) << 32);
+ return ((seed << 6) + (seed >> 2) + 0x9e3779b9 + uint64_t(id.OidBits[1])) | (uint64_t(id.OidBits[2]) << 32);
}
};
diff --git a/zencore/include/zencore/varint.h b/zencore/include/zencore/varint.h
index 0c40dd66b..d7f2ebfb7 100644
--- a/zencore/include/zencore/varint.h
+++ b/zencore/include/zencore/varint.h
@@ -2,6 +2,8 @@
#include "intmath.h"
+#include <algorithm>
+
namespace zen {
// Variable-Length Integer Encoding
diff --git a/zencore/include/zencore/windows.h b/zencore/include/zencore/windows.h
index 68138566b..117c5eff1 100644
--- a/zencore/include/zencore/windows.h
+++ b/zencore/include/zencore/windows.h
@@ -10,7 +10,12 @@ struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax erro
#ifndef NOMINMAX
# define NOMINMAX // We don't want your min/max macros
#endif
-#define WIN32_LEAN_AND_MEAN
+#ifndef NOGDI
+# define NOGDI // We don't want your GetObject define
+#endif
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
#include <windows.h>
#undef GetObject
diff --git a/zencore/include/zencore/zencore.h b/zencore/include/zencore/zencore.h
index 6b9a0f658..3352af5d2 100644
--- a/zencore/include/zencore/zencore.h
+++ b/zencore/include/zencore/zencore.h
@@ -16,7 +16,7 @@
#define ZEN_PLATFORM_WINDOWS 0
#define ZEN_PLATFORM_LINUX 0
-#define ZEN_PLATFORM_MACOS 0
+#define ZEN_PLATFORM_MAC 0
#ifdef _WIN32
# undef ZEN_PLATFORM_WINDOWS
@@ -25,8 +25,20 @@
# undef ZEN_PLATFORM_LINUX
# define ZEN_PLATFORM_LINUX 1
#elif defined(__APPLE__)
-# undef ZEN_PLATFORM_MACOS
-# define ZEN_PLATFORM_MACOS 1
+# undef ZEN_PLATFORM_MAC
+# define ZEN_PLATFORM_MAC 1
+#endif
+
+#if ZEN_PLATFORM_WINDOWS
+# if !defined(NOMINMAX)
+# define NOMINMAX // stops Windows.h from defining 'min/max' macros
+# endif
+# if !defined(NOGDI)
+# define NOGDI
+# endif
+# if !defined(WIN32_LEAN_AND_MEAN)
+# define WIN32_LEAN_AND_MEAN // cut-down what Windows.h defines
+# endif
#endif
//////////////////////////////////////////////////////////////////////////
@@ -68,20 +80,45 @@
#ifndef ZEN_THIRD_PARTY_INCLUDES_START
# if ZEN_COMPILER_MSC
-# define ZEN_THIRD_PARTY_INCLUDES_START __pragma(warning(push)) __pragma(warning(disable : 4668 4127))
-# else
-# define ZEN_THIRD_PARTY_INCLUDES_START
+# define ZEN_THIRD_PARTY_INCLUDES_START \
+ __pragma(warning(push)) __pragma(warning(disable : 4668)) /* use of undefined preprocessor macro */ \
+ __pragma(warning(disable : 4267)) /* '=': conversion from 'size_t' to 'US' */ \
+ __pragma(warning(disable : 4127))
+# elif ZEN_COMPILER_CLANG
+# define ZEN_THIRD_PARTY_INCLUDES_START \
+ _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wundef\"") \
+ _Pragma("clang diagnostic ignored \"-Wunused-parameter\"") _Pragma("clang diagnostic ignored \"-Wunused-variable\"")
+# elif ZEN_COMPILER_GCC
+# define ZEN_THIRD_PARTY_INCLUDES_START \
+ _Pragma("GCC diagnostic push") /* NB. ignoring -Wundef doesn't work with GCC */ \
+ _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") _Pragma("GCC diagnostic ignored \"-Wunused-variable\"")
# endif
#endif
#ifndef ZEN_THIRD_PARTY_INCLUDES_END
# if ZEN_COMPILER_MSC
# define ZEN_THIRD_PARTY_INCLUDES_END __pragma(warning(pop))
-# else
-# define ZEN_THIRD_PARTY_INCLUDES_END
+# elif ZEN_COMPILER_CLANG
+# define ZEN_THIRD_PARTY_INCLUDES_END _Pragma("clang diagnostic pop")
+# elif ZEN_COMPILER_GCC
+# define ZEN_THIRD_PARTY_INCLUDES_END _Pragma("GCC diagnostic pop")
# endif
#endif
+#if ZEN_COMPILER_MSC
+# define ZEN_DEBUG_BREAK() \
+ do \
+ { \
+ __debugbreak(); \
+ } while (0)
+#else
+# define ZEN_DEBUG_BREAK() \
+ do \
+ { \
+ __builtin_trap(); \
+ } while (0)
+#endif
+
//////////////////////////////////////////////////////////////////////////
// Architecture
//
@@ -112,6 +149,13 @@
#define ZEN_PLATFORM_SUPPORTS_UNALIGNED_LOADS 1
+#if defined(__SIZEOF_WCHAR_T__) && __SIZEOF_WCHAR_T__ == 4
+# define ZEN_SIZEOF_WCHAR_T 4
+#else
+static_assert(sizeof(wchar_t) == 2, "wchar_t is expected to be two bytes in size");
+# define ZEN_SIZEOF_WCHAR_T 2
+#endif
+
//////////////////////////////////////////////////////////////////////////
// Assert
//
@@ -206,6 +250,12 @@ char (&ZenArrayCountHelper(const T (&)[N]))[N + 1];
# define ZEN_NOINLINE __attribute__((noinline))
#endif
+#if ZEN_PLATFORM_WINDOWS
+# define ZEN_EXE_SUFFIX_LITERAL ".exe"
+#else
+# define ZEN_EXE_SUFFIX_LITERAL
+#endif
+
#define ZEN_UNUSED(...) ((void)__VA_ARGS__)
#define ZEN_NOT_IMPLEMENTED(...) ZEN_ASSERT(false, __VA_ARGS__)
#define ZENCORE_API // Placeholder to allow DLL configs in the future (maybe)
@@ -233,7 +283,12 @@ ZENCORE_API void zencore_forcelinktests();
#if ZEN_COMPILER_MSC
# define ZEN_DISABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", off))
# define ZEN_ENABLE_OPTIMIZATION_ACTUAL __pragma(optimize("", on))
-#else
+#elif ZEN_COMPILER_GCC
+# define ZEN_DISABLE_OPTIMIZATION_ACTUAL _Pragma("GCC push_options") _Pragma("GCC optimize (\"O0\")")
+# define ZEN_ENABLE_OPTIMIZATION_ACTUAL _Pragma("GCC pop_options")
+#elif ZEN_COMPILER_CLANG
+# define ZEN_DISABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize off")
+# define ZEN_ENABLE_OPTIMIZATION_ACTUAL _Pragma("clang optimize on")
#endif
// Set up optimization control macros, now that we have both the build settings and the platform macros
@@ -249,4 +304,10 @@ ZENCORE_API void zencore_forcelinktests();
//////////////////////////////////////////////////////////////////////////
+#ifndef ZEN_WITH_TRACE
+# define ZEN_WITH_TRACE 0
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+
using ThreadId_t = uint32_t;
diff --git a/zencore/iobuffer.cpp b/zencore/iobuffer.cpp
index 979772d5e..30865068a 100644
--- a/zencore/iobuffer.cpp
+++ b/zencore/iobuffer.cpp
@@ -246,6 +246,7 @@ IoBufferExtendedCore::Materialize() const
if (m_Flags.load(std::memory_order_relaxed) & kIsMaterialized)
return;
+ void* NewMmapHandle;
uint32_t NewFlags = kIsMaterialized;
const uint64_t MapOffset = m_FileOffset & ~0xffffull;
@@ -253,12 +254,12 @@ IoBufferExtendedCore::Materialize() const
const uint64_t MapSize = m_DataBytes + MappedOffsetDisplacement;
#if ZEN_PLATFORM_WINDOWS
- void* NewMmapHandle = CreateFileMapping(m_FileHandle,
- /* lpFileMappingAttributes */ nullptr,
- /* flProtect */ PAGE_READONLY,
- /* dwMaximumSizeLow */ 0,
- /* dwMaximumSizeHigh */ 0,
- /* lpName */ nullptr);
+ NewMmapHandle = CreateFileMapping(m_FileHandle,
+ /* lpFileMappingAttributes */ nullptr,
+ /* flProtect */ PAGE_READONLY,
+ /* dwMaximumSizeLow */ 0,
+ /* dwMaximumSizeHigh */ 0,
+ /* lpName */ nullptr);
if (NewMmapHandle == nullptr)
{
@@ -398,18 +399,22 @@ IoBufferBuilder::ReadFromFileMaybe(IoBuffer& InBuffer)
DWORD dwNumberOfBytesRead = 0;
BOOL Success = ::ReadFile(FileRef.FileHandle, OutBuffer.MutableData(), DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl);
+#else
+ int Fd = int(intptr_t(FileRef.FileHandle));
+ int Result = pread(Fd, OutBuffer.MutableData(), size_t(FileRef.FileChunkSize), off_t(FileRef.FileChunkOffset));
+ bool Success = (Result < 0);
+
+ uint32_t dwNumberOfBytesRead = uint32_t(Result);
+#endif
if (!Success)
{
ThrowLastError("ReadFile failed in IoBufferBuilder::ReadFromFileMaybe");
}
- ZEN_ASSERT(dwNumberOfBytesRead == NumberOfBytesToRead);
+ ZEN_ASSERT(dwNumberOfBytesRead == FileRef.FileChunkSize);
return OutBuffer;
-#else
-# error Needs implementation
-#endif
}
else
{
@@ -424,14 +429,14 @@ IoBufferBuilder::MakeFromFileHandle(void* FileHandle, uint64_t Offset, uint64_t
}
IoBuffer
-IoBufferBuilder::MakeFromFile(const path_char_t* FileName, uint64_t Offset, uint64_t Size)
+IoBufferBuilder::MakeFromFile(const std::filesystem::path& FileName, uint64_t Offset, uint64_t Size)
{
uint64_t FileSize;
#if ZEN_PLATFORM_WINDOWS
CAtlFile DataFile;
- HRESULT hRes = DataFile.Create(FileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
+ HRESULT hRes = DataFile.Create(FileName.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
if (FAILED(hRes))
{
@@ -440,7 +445,7 @@ IoBufferBuilder::MakeFromFile(const path_char_t* FileName, uint64_t Offset, uint
DataFile.GetSize((ULONGLONG&)FileSize);
#else
- int Fd = open(FileName, O_RDONLY);
+ int Fd = open(FileName.c_str(), O_RDONLY);
if (Fd < 0)
{
return {};
diff --git a/zencore/memory.cpp b/zencore/memory.cpp
index c94829276..4b51ef9a2 100644
--- a/zencore/memory.cpp
+++ b/zencore/memory.cpp
@@ -3,6 +3,7 @@
#include <zencore/intmath.h>
#include <zencore/memory.h>
#include <zencore/testing.h>
+#include <zencore/zencore.h>
#ifdef ZEN_PLATFORM_WINDOWS
# include <malloc.h>
diff --git a/zencore/stats.cpp b/zencore/stats.cpp
index 0c0647999..1bb6f6de0 100644
--- a/zencore/stats.cpp
+++ b/zencore/stats.cpp
@@ -227,7 +227,7 @@ UniformSample::Snapshot() const
uint64_t ValuesSize = Size();
std::vector<double> Values(ValuesSize);
- for (int i = 0; i < ValuesSize; ++i)
+ for (int i = 0, n = int(ValuesSize); i < n; ++i)
{
Values[i] = double(m_Values[i]);
}
diff --git a/zencore/string.cpp b/zencore/string.cpp
index b7670617f..ad6ee78fc 100644
--- a/zencore/string.cpp
+++ b/zencore/string.cpp
@@ -30,6 +30,17 @@ utf16to8_impl(u16bit_iterator StartIt, u16bit_iterator EndIt, ::zen::StringBuild
}
}
+template<typename u32bit_iterator>
+void
+utf32to8_impl(u32bit_iterator StartIt, u32bit_iterator EndIt, ::zen::StringBuilderBase& OutString)
+{
+ for (; StartIt != EndIt; ++StartIt)
+ {
+ wchar_t cp = *StartIt;
+ OutString.AppendCodepoint(cp);
+ }
+}
+
//////////////////////////////////////////////////////////////////////////
namespace zen {
@@ -76,7 +87,7 @@ FilepathFindExtension(const std::string_view& Path, const char* ExtensionToMatch
// Look for extension introducer ('.')
- for (size_t i = PathLen - 1; i >= 0; --i)
+ for (int64_t i = PathLen - 1; i >= 0; --i)
{
if (Path[i] == '.')
return Path.data() + i;
@@ -164,26 +175,24 @@ Utf8ToWide(const std::u8string_view& Str8, WideStringBuilderBase& OutString)
void
WideToUtf8(const wchar_t* Wstr, StringBuilderBase& OutString)
{
- WideToUtf8(std::u16string_view{(char16_t*)Wstr}, OutString);
+ WideToUtf8(std::wstring_view{Wstr}, OutString);
}
void
WideToUtf8(const std::wstring_view& Wstr, StringBuilderBase& OutString)
{
- WideToUtf8(std::u16string_view{(char16_t*)Wstr.data(), Wstr.size()}, OutString);
-}
-
-void
-WideToUtf8(const std::u16string_view& Wstr, StringBuilderBase& OutString)
-{
+#if ZEN_SIZEOF_WCHAR_T == 2
utf16to8_impl(begin(Wstr), end(Wstr), OutString);
+#else
+ utf32to8_impl(begin(Wstr), end(Wstr), OutString);
+#endif
}
std::string
WideToUtf8(const wchar_t* Wstr)
{
ExtendableStringBuilder<128> String;
- WideToUtf8(std::u16string_view{(char16_t*)Wstr}, String);
+ WideToUtf8(std::wstring_view{Wstr}, String);
return String.c_str();
}
@@ -192,7 +201,7 @@ std::string
WideToUtf8(const std::wstring_view Wstr)
{
ExtendableStringBuilder<128> String;
- WideToUtf8(std::u16string_view{(char16_t*)Wstr.data(), Wstr.size()}, String);
+ WideToUtf8(std::wstring_view{Wstr.data(), Wstr.size()}, String);
return String.c_str();
}
@@ -942,6 +951,18 @@ TEST_CASE("string")
CHECK_EQ(ToLower("TE%St"sv), "te%st"sv);
}
+ SUBCASE("StrCaseCompare")
+ {
+ CHECK(StrCaseCompare("foo", "FoO") == 0);
+ CHECK(StrCaseCompare("Bar", "bAs") < 0);
+ CHECK(StrCaseCompare("bAr", "Bas") < 0);
+ CHECK(StrCaseCompare("BBr", "Bar") > 0);
+ CHECK(StrCaseCompare("Bbr", "BAr") > 0);
+ CHECK(StrCaseCompare("foo", "FoO", 3) == 0);
+ CHECK(StrCaseCompare("Bar", "bAs", 3) < 0);
+ CHECK(StrCaseCompare("BBr", "Bar", 2) > 0);
+ }
+
SUBCASE("ForEachStrTok")
{
const auto Tokens = "here,is,my,different,tokens"sv;
diff --git a/zencore/thread.cpp b/zencore/thread.cpp
index da711fe89..6d17e6968 100644
--- a/zencore/thread.cpp
+++ b/zencore/thread.cpp
@@ -3,14 +3,32 @@
#include <zencore/thread.h>
#include <zencore/except.h>
+#include <zencore/filesystem.h>
+#include <zencore/scopeguard.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#if ZEN_PLATFORM_WINDOWS
+# include <shellapi.h>
+# include <Shlobj.h>
# include <zencore/windows.h>
#elif ZEN_PLATFORM_LINUX
+# include <chrono>
+# include <condition_variable>
+# include <mutex>
+
+# include <fcntl.h>
+# include <mqueue.h>
+# include <pthread.h>
+# include <signal.h>
+# include <sys/file.h>
+# include <sys/wait.h>
+# include <time.h>
# include <unistd.h>
#endif
+#include <thread>
+
ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -69,6 +87,8 @@ SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName)
std::string ThreadNameZ{ThreadName};
SetNameInternal(GetCurrentThreadId(), ThreadNameZ.c_str());
#else
+ std::string ThreadNameZ{ThreadName};
+ pthread_setname_np(pthread_self(), ThreadNameZ.c_str());
#endif
} // namespace zen
@@ -98,40 +118,80 @@ RwLock::ReleaseExclusive()
//////////////////////////////////////////////////////////////////////////
-#if ZEN_PLATFORM_WINDOWS
+#if !ZEN_PLATFORM_WINDOWS
+struct EventInner
+{
+ std::mutex Mutex;
+ std::condition_variable CondVar;
+ bool volatile bSet = false;
+};
+#endif // !ZEN_PLATFORM_WINDOWS
Event::Event()
{
- m_EventHandle = CreateEvent(nullptr, true, false, nullptr);
+ bool bManualReset = true;
+ bool bInitialState = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr);
+#else
+ ZEN_UNUSED(bManualReset);
+ auto* Inner = new EventInner();
+ Inner->bSet = bInitialState;
+ m_EventHandle = Inner;
+#endif
}
Event::~Event()
{
- CloseHandle(m_EventHandle);
+ Close();
}
void
Event::Set()
{
+#if ZEN_PLATFORM_WINDOWS
SetEvent(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ {
+ std::unique_lock Lock(Inner->Mutex);
+ Inner->bSet = true;
+ }
+ Inner->CondVar.notify_all();
+#endif
}
void
Event::Reset()
{
+#if ZEN_PLATFORM_WINDOWS
ResetEvent(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ {
+ std::unique_lock Lock(Inner->Mutex);
+ Inner->bSet = false;
+ }
+#endif
}
void
Event::Close()
{
+#if ZEN_PLATFORM_WINDOWS
CloseHandle(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ delete Inner;
+#endif
m_EventHandle = nullptr;
}
bool
Event::Wait(int TimeoutMs)
{
+#if ZEN_PLATFORM_WINDOWS
using namespace std::literals;
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
@@ -144,12 +204,48 @@ Event::Wait(int TimeoutMs)
}
return (Result == WAIT_OBJECT_0);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+
+ if (TimeoutMs >= 0)
+ {
+ std::unique_lock Lock(Inner->Mutex);
+
+ if (Inner->bSet)
+ {
+ return true;
+ }
+
+ return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; });
+ }
+
+ std::unique_lock Lock(Inner->Mutex);
+
+ if (!Inner->bSet)
+ {
+ Inner->CondVar.wait(Lock, [&] { return Inner->bSet; });
+ }
+
+ return true;
+#endif
}
//////////////////////////////////////////////////////////////////////////
-NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr)
+#if ZEN_PLATFORM_LINUX
+static bool
+IsMessageQueueEmpty(int Fd)
{
+ // Check if there is already a message in the queue.
+ mq_attr Attributes = {O_NONBLOCK, 1, 1, 0};
+ mq_getattr(Fd, &Attributes);
+ return (Attributes.mq_curmsgs == 0);
+}
+#endif // ZEN_PLATFORM_LINUX
+
+NamedEvent::NamedEvent(std::string_view EventName)
+{
+#if ZEN_PLATFORM_WINDOWS
using namespace std::literals;
ExtendableStringBuilder<64> Name;
@@ -157,30 +253,151 @@ NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr)
Name << EventName;
m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str());
+#elif ZEN_PLATFORM_LINUX
+ ExtendableStringBuilder<64> Name;
+ Name << "/";
+ Name << EventName;
+
+ mq_attr Attributes = {
+ 0, // flags
+ 1, // max message count
+ 1, // max message size
+ 0, // current messages
+ };
+
+ int Inner = mq_open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644, &Attributes);
+ if (Inner < 0)
+ {
+ ThrowLastError("Failed to get message queue from mq_open()");
+ }
+
+ int LockResult = flock(Inner, LOCK_EX | LOCK_NB);
+ if (LockResult == 0)
+ {
+ // This is really thread safe as the message queue could be set between
+ // getting the exclusive lock and checking the queue. But for the our
+ // simple synchronising of process, this should be okay.
+ while (!IsMessageQueueEmpty(Inner))
+ {
+ char Sink;
+ mq_receive(Inner, &Sink, sizeof(Sink), nullptr);
+ }
+ }
+
+ m_EventHandle = (void*)intptr_t(Inner);
+#else
+# error Implement NamedEvent for this platform
+#endif
}
-NamedEvent::NamedEvent(std::string_view EventName) : Event(nullptr)
+NamedEvent::~NamedEvent()
{
- using namespace std::literals;
+ Close();
+}
- ExtendableStringBuilder<64> Name;
- Name << "Local\\"sv;
- Name << EventName;
+void
+NamedEvent::Close()
+{
+ if (m_EventHandle == nullptr)
+ {
+ return;
+ }
- m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str());
+#if ZEN_PLATFORM_WINDOWS
+ CloseHandle(m_EventHandle);
+#elif ZEN_PLATFORM_LINUX
+ int Inner = int(intptr_t(m_EventHandle));
+
+ if (flock(Inner, LOCK_EX | LOCK_NB) == 0)
+ {
+ flock(Inner, LOCK_UN | LOCK_NB);
+ std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Inner)));
+ mq_unlink(Name.c_str());
+ }
+
+ close(Inner);
+#endif
+
+ m_EventHandle = nullptr;
+}
+
+void
+NamedEvent::Set()
+{
+#if ZEN_PLATFORM_WINDOWS
+ SetEvent(m_EventHandle);
+#elif ZEN_PLATFORM_LINUX
+ int Inner = int(intptr_t(m_EventHandle));
+
+ if (!IsMessageQueueEmpty(Inner))
+ {
+ return;
+ }
+
+ char Message = 0x49;
+ if (mq_send(Inner, &Message, sizeof(Message), 0) != 0)
+ {
+ ThrowLastError("Unable to send set message to queue");
+ }
+#endif
}
+bool
+NamedEvent::Wait(int TimeoutMs)
+{
+#if ZEN_PLATFORM_WINDOWS
+ const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
+
+ DWORD Result = WaitForSingleObject(m_EventHandle, Timeout);
+
+ if (Result == WAIT_FAILED)
+ {
+ using namespace std::literals;
+ zen::ThrowLastError("Event wait failed"sv);
+ }
+
+ return (Result == WAIT_OBJECT_0);
+#elif ZEN_PLATFORM_LINUX
+ int Inner = int(intptr_t(m_EventHandle));
+
+ if (!IsMessageQueueEmpty(Inner))
+ {
+ return true;
+ }
+
+ struct timeval TimeoutValue = {
+ .tv_sec = 0,
+ .tv_usec = TimeoutMs << 10,
+ };
+ struct timeval* TimeoutPtr = (TimeoutMs < 0) ? nullptr : &TimeoutValue;
+
+ fd_set FdSet;
+ FD_ZERO(&FdSet);
+ FD_SET(Inner, &FdSet);
+ return select(Inner + 1, &FdSet, nullptr, nullptr, TimeoutPtr) > 0;
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+
NamedMutex::~NamedMutex()
{
+#if ZEN_PLATFORM_WINDOWS
if (m_MutexHandle)
{
CloseHandle(m_MutexHandle);
}
+#else
+ int Inner = int(intptr_t(m_MutexHandle));
+ flock(Inner, LOCK_UN);
+ close(Inner);
+#endif
}
bool
NamedMutex::Create(std::string_view MutexName)
{
+#if ZEN_PLATFORM_WINDOWS
ZEN_ASSERT(m_MutexHandle == nullptr);
using namespace std::literals;
@@ -192,11 +409,32 @@ NamedMutex::Create(std::string_view MutexName)
m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str());
return !!m_MutexHandle;
+#else
+ ExtendableStringBuilder<64> Name;
+ Name << "/tmp/" << MutexName;
+
+ int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0644);
+ if (Inner < 0)
+ {
+ return false;
+ }
+
+ if (flock(Inner, LOCK_EX) != 0)
+ {
+ close(Inner);
+ Inner = 0;
+ return false;
+ }
+
+ m_MutexHandle = (void*)(intptr_t(Inner));
+ return true;
+#endif // ZEN_PLATFORM_WINDOWS
}
bool
NamedMutex::Exists(std::string_view MutexName)
{
+#if ZEN_PLATFORM_WINDOWS
using namespace std::literals;
ExtendableStringBuilder<64> Name;
@@ -213,24 +451,49 @@ NamedMutex::Exists(std::string_view MutexName)
CloseHandle(MutexHandle);
return true;
-}
+#else
+ ExtendableStringBuilder<64> Name;
+ Name << "/tmp/" << MutexName;
-#endif // ZEN_PLATFORM_WINDOWS
+ bool bExists = false;
+ int Fd = open(Name.c_str(), O_RDWR, 0644);
+ if (Fd >= 0)
+ {
+ if (flock(Fd, LOCK_EX | LOCK_NB) == 0)
+ {
+ flock(Fd, LOCK_UN | LOCK_NB);
+ }
+ else
+ {
+ bExists = true;
+ }
+ close(Fd);
+ }
-#if ZEN_PLATFORM_WINDOWS
+ return bExists;
+#endif // ZEN_PLATFORM_WINDOWS
+}
//////////////////////////////////////////////////////////////////////////
ProcessHandle::ProcessHandle() = default;
+#if ZEN_PLATFORM_WINDOWS
void
ProcessHandle::Initialize(void* ProcessHandle)
{
ZEN_ASSERT(m_ProcessHandle == nullptr);
+
+ if (ProcessHandle == INVALID_HANDLE_VALUE)
+ {
+ ProcessHandle = nullptr;
+ }
+
// TODO: perform some debug verification here to verify it's a valid handle?
m_ProcessHandle = ProcessHandle;
m_Pid = GetProcessId(m_ProcessHandle);
}
+#endif // ZEN_PLATFORM_WINDOWS
ProcessHandle::~ProcessHandle()
{
@@ -241,12 +504,21 @@ void
ProcessHandle::Initialize(int Pid)
{
ZEN_ASSERT(m_ProcessHandle == nullptr);
- m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
- using namespace fmt::literals;
+#if ZEN_PLATFORM_WINDOWS
+ m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+#elif ZEN_PLATFORM_LINUX
+ if (Pid > 0)
+ {
+ m_ProcessHandle = (void*)(intptr_t(Pid));
+ }
+#else
+# error Check process control on this platform
+#endif
if (!m_ProcessHandle)
{
+ using namespace fmt::literals;
ThrowLastError("ProcessHandle::Initialize(pid: {}) failed"_format(Pid));
}
@@ -256,29 +528,51 @@ ProcessHandle::Initialize(int Pid)
bool
ProcessHandle::IsRunning() const
{
+ bool bActive = false;
+
+#if ZEN_PLATFORM_WINDOWS
DWORD ExitCode = 0;
GetExitCodeProcess(m_ProcessHandle, &ExitCode);
+ bActive = (ExitCode == STILL_ACTIVE);
+#elif ZEN_PLATFORM_LINUX
+ StringBuilder<64> ProcPath;
+ ProcPath << "/proc/" << m_Pid;
+ bActive = (access(ProcPath.c_str(), F_OK) != 0);
+#else
+# error Check process control on this platform
+#endif
- return ExitCode == STILL_ACTIVE;
+ return bActive;
}
bool
ProcessHandle::IsValid() const
{
- return (m_ProcessHandle != nullptr) && (m_ProcessHandle != INVALID_HANDLE_VALUE);
+ return (m_ProcessHandle != nullptr);
}
void
ProcessHandle::Terminate(int ExitCode)
{
- if (IsRunning())
+ if (!IsRunning())
{
- TerminateProcess(m_ProcessHandle, ExitCode);
+ return;
}
+ bool bSuccess = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ TerminateProcess(m_ProcessHandle, ExitCode);
DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE);
+ bSuccess = (WaitResult != WAIT_OBJECT_0);
+#elif ZEN_PLATFORM_LINUX
+ ZEN_UNUSED(ExitCode);
+ bSuccess = (kill(m_Pid, SIGKILL) == 0);
+#else
+# error Check kill() on this platform
+#endif
- if (WaitResult != WAIT_OBJECT_0)
+ if (!bSuccess)
{
// What might go wrong here, and what is meaningful to act on?
}
@@ -289,14 +583,20 @@ ProcessHandle::Reset()
{
if (IsValid())
{
+#if ZEN_PLATFORM_WINDOWS
CloseHandle(m_ProcessHandle);
+#endif
m_ProcessHandle = nullptr;
+ m_Pid = 0;
}
}
bool
ProcessHandle::Wait(int TimeoutMs)
{
+ using namespace std::literals;
+
+#if ZEN_PLATFORM_WINDOWS
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout);
@@ -310,19 +610,301 @@ ProcessHandle::Wait(int TimeoutMs)
return false;
case WAIT_FAILED:
- // What might go wrong here, and what is meaningful to act on?
- using namespace std::literals;
- ThrowLastError("Process::Wait failed"sv);
+ break;
}
+#elif ZEN_PLATFORM_LINUX
+ const int SleepMs = 20;
+ timespec SleepTime = {0, SleepMs * 1000 * 1000};
+ for (int i = 0;; i += SleepMs)
+ {
+ int WaitState = 0;
+ waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED);
- return false;
-}
+ if (kill(m_Pid, 0) < 0)
+ {
+ if (zen::GetLastError() == ESRCH)
+ {
+ return true;
+ }
+ break;
+ }
-#endif // ZEN_PLATFORM_WINDOWS
+ if (TimeoutMs >= 0 && i >= TimeoutMs)
+ {
+ return false;
+ }
+
+ nanosleep(&SleepTime, nullptr);
+ }
+#else
+# error Check kill() on this platform
+#endif
+
+ // What might go wrong here, and what is meaningful to act on?
+ ThrowLastError("Process::Wait failed"sv);
+}
//////////////////////////////////////////////////////////////////////////
+#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS
+static void
+BuildArgV(std::vector<char*>& Out, char* CommandLine)
+{
+ char* Cursor = CommandLine;
+ while (true)
+ {
+ // Skip leading whitespace
+ for (; *Cursor == ' '; ++Cursor)
+ ;
+
+ // Check for nullp terminator
+ if (*Cursor == '\0')
+ {
+ break;
+ }
+
+ Out.push_back(Cursor);
+
+ // Extract word
+ int QuoteCount = 0;
+ do
+ {
+ QuoteCount += (*Cursor == '\"');
+ if (*Cursor == ' ' && !(QuoteCount & 1))
+ {
+ break;
+ }
+ ++Cursor;
+ } while (*Cursor != '\0');
+
+ if (*Cursor == '\0')
+ {
+ break;
+ }
+
+ *Cursor = '\0';
+ ++Cursor;
+ }
+}
+#endif // !WINDOWS || TESTS
+
+#if ZEN_PLATFORM_WINDOWS
+static CreateProcResult
+CreateProcNormal(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ PROCESS_INFORMATION ProcessInfo{};
+ STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)};
+
+ const bool InheritHandles = false;
+ void* Environment = nullptr;
+ LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
+ LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
+
+ DWORD CreationFlags = 0;
+ if (Options.Flags & CreateProcOptions::Flag_NewConsole)
+ {
+ CreationFlags |= CREATE_NEW_CONSOLE;
+ }
+
+ const wchar_t* WorkingDir = nullptr;
+ if (Options.WorkingDirectory != nullptr)
+ {
+ WorkingDir = Options.WorkingDirectory->c_str();
+ }
+
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ BOOL Success = CreateProcessW(Executable.c_str(),
+ CommandLineZ.Data(),
+ ProcessAttributes,
+ ThreadAttributes,
+ InheritHandles,
+ CreationFlags,
+ Environment,
+ WorkingDir,
+ &StartupInfo,
+ &ProcessInfo);
+
+ if (!Success)
+ {
+ return nullptr;
+ }
+
+ CloseHandle(ProcessInfo.hThread);
+ return ProcessInfo.hProcess;
+}
+
+static CreateProcResult
+CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ /* Launches a binary with the shell as its parent. The shell (such as
+ Explorer) should be an unelevated process. */
+
+ // No sense in using this route if we are not elevated in the first place
+ if (IsUserAnAdmin() == FALSE)
+ {
+ return CreateProcNormal(Executable, CommandLine, Options);
+ }
+
+ // Get the users' shell process and open it for process creation
+ HWND ShellWnd = GetShellWindow();
+ if (ShellWnd == nullptr)
+ {
+ return nullptr;
+ }
+
+ DWORD ShellPid;
+ GetWindowThreadProcessId(ShellWnd, &ShellPid);
+
+ HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid);
+ if (Process == nullptr)
+ {
+ return nullptr;
+ }
+ auto $0 = MakeGuard([&] { CloseHandle(Process); });
+
+ // Creating a process as a child of another process is done by setting a
+ // thread-attribute list on the startup info passed to CreateProcess()
+ SIZE_T AttrListSize;
+ InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize);
+
+ auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize);
+ auto $1 = MakeGuard([&] { free(AttrList); });
+
+ if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize))
+ {
+ return nullptr;
+ }
+
+ BOOL bOk =
+ UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr);
+ if (!bOk)
+ {
+ return nullptr;
+ }
+
+ // By this point we know we are an elevated process. It is not allowed to
+ // create a process as a child of another unelevated process that share our
+ // elevated console window if we have one. So we'll need to create a new one.
+ uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT;
+ if (GetConsoleWindow() != nullptr)
+ {
+ CreateProcFlags |= CREATE_NEW_CONSOLE;
+ }
+ else
+ {
+ CreateProcFlags |= DETACHED_PROCESS;
+ }
+
+ // Everything is set up now so we can proceed and launch the process
+ STARTUPINFOEXW StartupInfo = {
+ .StartupInfo = {.cb = sizeof(STARTUPINFOEXW)},
+ .lpAttributeList = AttrList,
+ };
+ PROCESS_INFORMATION ProcessInfo = {};
+
+ if (Options.Flags & CreateProcOptions::Flag_NewConsole)
+ {
+ CreateProcFlags |= CREATE_NEW_CONSOLE;
+ }
+
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ bOk = CreateProcessW(Executable.c_str(),
+ CommandLineZ.Data(),
+ nullptr,
+ nullptr,
+ FALSE,
+ CreateProcFlags,
+ nullptr,
+ nullptr,
+ &StartupInfo.StartupInfo,
+ &ProcessInfo);
+ if (bOk == FALSE)
+ {
+ return nullptr;
+ }
+
+ CloseHandle(ProcessInfo.hThread);
+ return ProcessInfo.hProcess;
+}
+
+static CreateProcResult
+CreateProcElevated(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
+ ExtendableWideStringBuilder<256> CommandLineZ;
+ CommandLineZ << CommandLine;
+
+ SHELLEXECUTEINFO ShellExecuteInfo;
+ ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo));
+ ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo);
+ ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
+ ShellExecuteInfo.lpFile = Executable.c_str();
+ ShellExecuteInfo.lpVerb = TEXT("runas");
+ ShellExecuteInfo.nShow = SW_SHOW;
+ ShellExecuteInfo.lpParameters = CommandLineZ.c_str();
+
+ if (Options.WorkingDirectory != nullptr)
+ {
+ ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str();
+ }
+
+ if (::ShellExecuteEx(&ShellExecuteInfo))
+ {
+ return ShellExecuteInfo.hProcess;
+ }
+
+ return nullptr;
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+CreateProcResult
+CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options)
+{
#if ZEN_PLATFORM_WINDOWS
+ if (Options.Flags & CreateProcOptions::Flag_Unelevated)
+ {
+ return CreateProcUnelevated(Executable, CommandLine, Options);
+ }
+
+ if (Options.Flags & CreateProcOptions::Flag_Elevated)
+ {
+ return CreateProcElevated(Executable, CommandLine, Options);
+ }
+
+ return CreateProcNormal(Executable, CommandLine, Options);
+#else
+ std::vector<char*> ArgV;
+ std::string CommandLineZ(CommandLine);
+ BuildArgV(ArgV, CommandLineZ.data());
+ ArgV.push_back(nullptr);
+
+ int ChildPid = fork();
+ if (ChildPid < 0)
+ {
+ ThrowLastError("Failed to fork a new child process");
+ }
+ else if (ChildPid == 0)
+ {
+ if (Options.WorkingDirectory != nullptr)
+ {
+ int Result = chdir(Options.WorkingDirectory->c_str());
+ ZEN_UNUSED(Result);
+ }
+
+ if (execv(Executable.c_str(), ArgV.data()) < 0)
+ {
+ ThrowLastError("Failed to exec() a new process image");
+ }
+ }
+
+ return ChildPid;
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
ProcessMonitor::ProcessMonitor()
{
@@ -332,9 +914,11 @@ ProcessMonitor::~ProcessMonitor()
{
RwLock::ExclusiveLockScope _(m_Lock);
- for (HANDLE& Proc : m_ProcessHandles)
+ for (HandleType& Proc : m_ProcessHandles)
{
+#if ZEN_PLATFORM_WINDOWS
CloseHandle(Proc);
+#endif
Proc = 0;
}
}
@@ -346,24 +930,34 @@ ProcessMonitor::IsRunning()
bool FoundOne = false;
- for (HANDLE& Proc : m_ProcessHandles)
+ for (HandleType& Proc : m_ProcessHandles)
{
+ bool ProcIsActive;
+
+#if ZEN_PLATFORM_WINDOWS
DWORD ExitCode = 0;
GetExitCodeProcess(Proc, &ExitCode);
- if (ExitCode != STILL_ACTIVE)
+ ProcIsActive = (ExitCode == STILL_ACTIVE);
+ if (!ProcIsActive)
{
CloseHandle(Proc);
- Proc = 0;
}
- else
+#else
+ int Pid = int(intptr_t(Proc));
+ ProcIsActive = IsProcessRunning(Pid);
+#endif
+
+ if (!ProcIsActive)
{
- // Still alive
- FoundOne = true;
+ Proc = 0;
}
+
+ // Still alive
+ FoundOne |= ProcIsActive;
}
- std::erase_if(m_ProcessHandles, [](HANDLE Handle) { return Handle == 0; });
+ std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; });
return FoundOne;
}
@@ -371,7 +965,13 @@ ProcessMonitor::IsRunning()
void
ProcessMonitor::AddPid(int Pid)
{
- HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+ HandleType ProcessHandle;
+
+#if ZEN_PLATFORM_WINDOWS
+ ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+#else
+ ProcessHandle = HandleType(intptr_t(Pid));
+#endif
if (ProcessHandle)
{
@@ -387,8 +987,6 @@ ProcessMonitor::IsActive() const
return m_ProcessHandles.empty() == false;
}
-#endif // ZEN_PLATFORM_WINDOWS
-
//////////////////////////////////////////////////////////////////////////
bool
@@ -418,7 +1016,9 @@ IsProcessRunning(int pid)
return true;
#else
- ZEN_NOT_IMPLEMENTED();
+ char Buffer[64];
+ sprintf(Buffer, "/proc/%d", pid);
+ return access(Buffer, F_OK) == 0;
#endif
}
@@ -428,7 +1028,17 @@ GetCurrentProcessId()
#if ZEN_PLATFORM_WINDOWS
return ::GetCurrentProcessId();
#else
- return getpid();
+ return int(getpid());
+#endif
+}
+
+int
+GetCurrentThreadId()
+{
+#if ZEN_PLATFORM_WINDOWS
+ return ::GetCurrentThreadId();
+#else
+ return int(gettid());
#endif
}
@@ -447,9 +1057,124 @@ Sleep(int ms)
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
thread_forcelink()
{
}
+TEST_CASE("Thread")
+{
+ int Pid = GetCurrentProcessId();
+ CHECK(Pid > 0);
+ CHECK(IsProcessRunning(Pid));
+}
+
+TEST_CASE("BuildArgV")
+{
+ const char* Words[] = {"one", "two", "three", "four", "five"};
+ struct
+ {
+ int WordCount;
+ const char* Input;
+ } Cases[] = {
+ {0, ""},
+ {0, " "},
+ {1, "one"},
+ {1, " one"},
+ {1, "one "},
+ {2, "one two"},
+ {2, " one two"},
+ {2, "one two "},
+ {2, " one two"},
+ {2, "one two "},
+ {2, "one two "},
+ {3, "one two three"},
+ {3, "\"one\" two \"three\""},
+ {5, "one two three four five"},
+ };
+
+ for (const auto& Case : Cases)
+ {
+ std::vector<char*> OutArgs;
+ StringBuilder<64> Mutable;
+ Mutable << Case.Input;
+ BuildArgV(OutArgs, Mutable.Data());
+
+ CHECK_EQ(OutArgs.size(), Case.WordCount);
+
+ for (int i = 0, n = int(OutArgs.size()); i < n; ++i)
+ {
+ const char* Truth = Words[i];
+ size_t TruthLen = strlen(Truth);
+
+ const char* Candidate = OutArgs[i];
+ bool bQuoted = (Candidate[0] == '\"');
+ Candidate += bQuoted;
+
+ CHECK(strncmp(Truth, Candidate, TruthLen) == 0);
+
+ if (bQuoted)
+ {
+ CHECK_EQ(Candidate[TruthLen], '\"');
+ }
+ }
+ }
+}
+
+TEST_CASE("NamedEvent")
+{
+ std::string Name = "zencore_test_event";
+ NamedEvent TestEvent(Name);
+
+ // Timeout test
+ for (uint32_t i = 0; i < 8; ++i)
+ {
+ bool bEventSet = TestEvent.Wait(100);
+ CHECK(!bEventSet);
+ }
+
+ // Thread check
+ std::thread Waiter = std::thread([Name]() {
+ NamedEvent ReadyEvent(Name + "_ready");
+ ReadyEvent.Set();
+
+ NamedEvent TestEvent(Name);
+ TestEvent.Wait(1000);
+ });
+
+ NamedEvent ReadyEvent(Name + "_ready");
+ ReadyEvent.Wait();
+
+ zen::Sleep(500);
+ TestEvent.Set();
+
+ Waiter.join();
+
+ // Manual reset property
+ for (uint32_t i = 0; i < 8; ++i)
+ {
+ bool bEventSet = TestEvent.Wait(100);
+ CHECK(bEventSet);
+ }
+}
+
+TEST_CASE("NamedMutex")
+{
+ static const char* Name = "zen_test_mutex";
+
+ CHECK(!NamedMutex::Exists(Name));
+
+ {
+ NamedMutex TestMutex;
+ CHECK(TestMutex.Create(Name));
+ CHECK(NamedMutex::Exists(Name));
+ }
+
+ CHECK(!NamedMutex::Exists(Name));
+}
+
+#endif // ZEN_WITH_TESTS
+
} // namespace zen
diff --git a/zencore/trace.cpp b/zencore/trace.cpp
new file mode 100644
index 000000000..f6a303960
--- /dev/null
+++ b/zencore/trace.cpp
@@ -0,0 +1,33 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if ZEN_WITH_TRACE
+
+# include <zencore/zencore.h>
+
+# define TRACE_IMPLEMENT 1
+# include <zencore/trace.h>
+//#undef TRACE_IMPLEMENT
+
+void
+TraceInit(const char* HostOrPath, TraceType Type)
+{
+ switch (Type)
+ {
+ case TraceType::Network:
+ trace::SendTo(HostOrPath);
+ break;
+
+ case TraceType::File:
+ trace::WriteTo(HostOrPath);
+ break;
+ }
+
+ trace::FInitializeDesc Desc = {
+ .bUseImportantCache = false,
+ };
+ trace::Initialize(Desc);
+
+ trace::ToggleChannel("cpu", true);
+}
+
+#endif // ZEN_WITH_TRACE
diff --git a/zencore/xmake.lua b/zencore/xmake.lua
index d26a9f922..738fe5796 100644
--- a/zencore/xmake.lua
+++ b/zencore/xmake.lua
@@ -2,8 +2,18 @@ target('zencore')
set_kind("static")
add_files("**.cpp")
add_includedirs("include", {public=true})
- add_includedirs("..\\thirdparty\\utfcpp\\source")
- add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Win64", "$(projectdir)/thirdparty/Oodle/lib/Win64")
+ add_includedirs("$(projectdir)/thirdparty/utfcpp/source")
+ if is_os("windows") then
+ add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Win64")
+ add_linkdirs("$(projectdir)/thirdparty/Oodle/lib/Win64")
+ elseif is_os("linux") then
+ add_linkdirs("$(projectdir)/thirdparty/BLAKE3/lib/Linux_x64")
+ add_linkdirs("$(projectdir)/thirdparty/Oodle/lib/Linux_x64")
+ add_links("blake3")
+ add_links("oo2corelinux64")
+ add_syslinks("pthread")
+ end
+ add_options("zentrace")
add_packages(
"vcpkg::spdlog",
"vcpkg::fmt",
@@ -14,5 +24,26 @@ target('zencore')
"vcpkg::cpr",
"vcpkg::curl", -- required by cpr
"vcpkg::zlib", -- required by curl
+ "vcpkg::openssl", -- required by curl
"vcpkg::xxhash",
"vcpkg::gsl-lite")
+
+ if is_plat("linux") then
+ -- The 'vcpkg::openssl' package is two libraries; ssl and crypto, with
+ -- ssl being dependent on symbols in crypto. When GCC-like linkers read
+ -- object files from their command line, those object files only resolve
+ -- symbols of objects previously encountered. Thus crypto must appear
+ -- after ssl so it can fill out ssl's unresolved symbol table. Xmake's
+ -- vcpkg support is basic and works by parsing .list files. Openssl's
+ -- archives are listed alphabetically causing crypto to be _before_ ssl
+ -- and resulting in link errors. The links are restated here to force
+ -- xmake to use the correct order, and "syslinks" is used to force the
+ -- arguments to the end of the line (otherwise they can appear before
+ -- curl and cause more errors).
+ add_syslinks("crypto")
+ add_syslinks("dl")
+ end
+
+ if is_plat("linux") then
+ add_syslinks("rt")
+ end