aboutsummaryrefslogtreecommitdiff
path: root/zencore
diff options
context:
space:
mode:
Diffstat (limited to 'zencore')
-rw-r--r--zencore/blake3.cpp6
-rw-r--r--zencore/compactbinary.cpp6
-rw-r--r--zencore/compactbinarybuilder.cpp5
-rw-r--r--zencore/compactbinarypackage.cpp7
-rw-r--r--zencore/compactbinaryvalidation.cpp5
-rw-r--r--zencore/compositebuffer.cpp9
-rw-r--r--zencore/compress.cpp5
-rw-r--r--zencore/except.cpp41
-rw-r--r--zencore/filesystem.cpp10
-rw-r--r--zencore/include/zencore/except.h53
-rw-r--r--zencore/include/zencore/logging.h7
-rw-r--r--zencore/include/zencore/mpscqueue.h109
-rw-r--r--zencore/include/zencore/session.h3
-rw-r--r--zencore/include/zencore/snapshot_manifest.h57
-rw-r--r--zencore/include/zencore/string.h39
-rw-r--r--zencore/include/zencore/testing.h9
-rw-r--r--zencore/include/zencore/testutils.h31
-rw-r--r--zencore/include/zencore/thread.h33
-rw-r--r--zencore/include/zencore/uid.h2
-rw-r--r--zencore/include/zencore/zencore.h94
-rw-r--r--zencore/intmath.cpp6
-rw-r--r--zencore/iobuffer.cpp9
-rw-r--r--zencore/iohash.cpp2
-rw-r--r--zencore/md5.cpp6
-rw-r--r--zencore/memory.cpp7
-rw-r--r--zencore/mpscqueue.cpp25
-rw-r--r--zencore/refcount.cpp7
-rw-r--r--zencore/session.cpp15
-rw-r--r--zencore/sha1.cpp6
-rw-r--r--zencore/sharedbuffer.cpp7
-rw-r--r--zencore/snapshot_manifest.cpp283
-rw-r--r--zencore/stats.cpp9
-rw-r--r--zencore/stream.cpp7
-rw-r--r--zencore/string.cpp51
-rw-r--r--zencore/testutils.cpp33
-rw-r--r--zencore/thread.cpp112
-rw-r--r--zencore/timer.cpp8
-rw-r--r--zencore/uid.cpp23
-rw-r--r--zencore/xxhash.cpp2
-rw-r--r--zencore/zencore.cpp35
-rw-r--r--zencore/zencore.vcxproj7
-rw-r--r--zencore/zencore.vcxproj.filters7
42 files changed, 713 insertions, 485 deletions
diff --git a/zencore/blake3.cpp b/zencore/blake3.cpp
index 090eb6897..663f21b6d 100644
--- a/zencore/blake3.cpp
+++ b/zencore/blake3.cpp
@@ -4,12 +4,12 @@
#include <zencore/compositebuffer.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#include <zencore/zencore.h>
#include "../3rdparty/BLAKE3/c/blake3.h"
#pragma comment(lib, "blake3.lib")
-#include <doctest/doctest.h>
#include <string.h>
//////////////////////////////////////////////////////////////////////////
@@ -123,6 +123,8 @@ BLAKE3Stream::GetHash()
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
doctest::String
toString(const BLAKE3& value)
{
@@ -169,4 +171,6 @@ TEST_CASE("BLAKE3")
}
}
+#endif
+
} // namespace zen
diff --git a/zencore/compactbinary.cpp b/zencore/compactbinary.cpp
index b508d8fe8..f4908aa9a 100644
--- a/zencore/compactbinary.cpp
+++ b/zencore/compactbinary.cpp
@@ -2,12 +2,12 @@
#include "zencore/compactbinary.h"
+#include <zencore/compactbinaryvalidation.h>
#include <zencore/compress.h>
#include <zencore/endian.h>
#include <zencore/stream.h>
-#include "zencore/compactbinaryvalidation.h"
+#include <zencore/testing.h>
-#include <doctest/doctest.h>
#include <string_view>
namespace zen {
@@ -1146,6 +1146,7 @@ SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object)
//////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
void
uson_forcelink()
{
@@ -1297,5 +1298,6 @@ TEST_CASE("uson.null")
CHECK(Field.IsNull() == false);
}
}
+#endif
} // namespace zen
diff --git a/zencore/compactbinarybuilder.cpp b/zencore/compactbinarybuilder.cpp
index 08f37a23d..fa5b6a69b 100644
--- a/zencore/compactbinarybuilder.cpp
+++ b/zencore/compactbinarybuilder.cpp
@@ -7,12 +7,11 @@
#include <zencore/endian.h>
#include <zencore/stream.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#define _USE_MATH_DEFINES
#include <math.h>
-#include <doctest/doctest.h>
-
namespace zen {
template<typename T>
@@ -700,6 +699,7 @@ operator<<(CbWriter& Writer, const TimeSpan Value)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
void
usonbuilder_forcelink()
{
@@ -1534,5 +1534,6 @@ TEST_CASE("usonbuilder.stream")
CHECK(ValidateCompactBinary(Object.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
}
}
+#endif
} // namespace zen
diff --git a/zencore/compactbinarypackage.cpp b/zencore/compactbinarypackage.cpp
index 9a7e7c098..fbdcd24e9 100644
--- a/zencore/compactbinarypackage.cpp
+++ b/zencore/compactbinarypackage.cpp
@@ -5,8 +5,7 @@
#include <zencore/compactbinaryvalidation.h>
#include <zencore/endian.h>
#include <zencore/stream.h>
-
-#include <doctest/doctest.h>
+#include <zencore/testing.h>
namespace zen {
@@ -747,6 +746,8 @@ namespace legacy {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
+
void
usonpackage_forcelink()
{
@@ -1253,4 +1254,6 @@ TEST_CASE("usonpackage.serialization")
}
}
+#endif
+
} // namespace zen
diff --git a/zencore/compactbinaryvalidation.cpp b/zencore/compactbinaryvalidation.cpp
index dafd1bcc8..3d72148f9 100644
--- a/zencore/compactbinaryvalidation.cpp
+++ b/zencore/compactbinaryvalidation.cpp
@@ -6,11 +6,10 @@
#include <zencore/endian.h>
#include <zencore/memory.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#include <algorithm>
-#include <doctest/doctest.h>
-
namespace zen {
namespace CbValidationPrivate {
@@ -649,6 +648,7 @@ ValidateCompactBinaryPackage(MemoryView View, CbValidateMode Mode)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
void
usonvalidation_forcelink()
{
@@ -658,5 +658,6 @@ TEST_CASE("usonvalidation")
{
SUBCASE("Basic") {}
}
+#endif
} // namespace zen
diff --git a/zencore/compositebuffer.cpp b/zencore/compositebuffer.cpp
index 9349c014f..3190ca5ea 100644
--- a/zencore/compositebuffer.cpp
+++ b/zencore/compositebuffer.cpp
@@ -3,8 +3,7 @@
#include <zencore/compositebuffer.h>
#include <zencore/sharedbuffer.h>
-
-#include <doctest/doctest.h>
+#include <zencore/testing.h>
namespace zen {
@@ -91,8 +90,8 @@ CompositeBuffer
CompositeBuffer::Mid(uint64_t Offset, uint64_t Size) const
{
const uint64_t BufferSize = GetSize();
- Offset = zen::Min(Offset, BufferSize);
- Size = zen::Min(Size, BufferSize - Offset);
+ Offset = Min(Offset, BufferSize);
+ Size = Min(Size, BufferSize - Offset);
CompositeBuffer Buffer;
IterateRange(Offset, Size, [&Buffer](MemoryView View, const SharedBuffer& ViewOuter) {
Buffer.m_Segments.push_back(SharedBuffer::MakeView(View, ViewOuter));
@@ -168,6 +167,7 @@ CompositeBuffer::IterateRange(uint64_t Offset,
}
}
+#if ZEN_WITH_TESTS
TEST_CASE("CompositeBuffer Null")
{
CompositeBuffer Buffer;
@@ -337,5 +337,6 @@ void
compositebuffer_forcelink()
{
}
+#endif
} // namespace zen
diff --git a/zencore/compress.cpp b/zencore/compress.cpp
index 12a7b9ef8..8ca799e39 100644
--- a/zencore/compress.cpp
+++ b/zencore/compress.cpp
@@ -6,13 +6,13 @@
#include <zencore/compositebuffer.h>
#include <zencore/crc32.h>
#include <zencore/endian.h>
+#include <zencore/testing.h>
#include "../3rdparty/Oodle/include/oodle2.h"
#if ZEN_PLATFORM_WINDOWS
# pragma comment(lib, "oo2core_win64.lib")
#endif
-#include <doctest/doctest.h>
#include <lz4.h>
#include <functional>
#include <limits>
@@ -823,6 +823,8 @@ CompressedBuffer::TryGetCompressParameters(OodleCompressor& OutCompressor, Oodle
\/ \/ \/
*/
+#if ZEN_WITH_TESTS
+
TEST_CASE("CompressedBuffer")
{
uint8_t Zeroes[1024]{};
@@ -908,5 +910,6 @@ void
compress_forcelink()
{
}
+#endif
} // namespace zen
diff --git a/zencore/except.cpp b/zencore/except.cpp
index 84e52ab9f..0167c406f 100644
--- a/zencore/except.cpp
+++ b/zencore/except.cpp
@@ -7,6 +7,41 @@ namespace zen {
#if ZEN_PLATFORM_WINDOWS
+class WindowsException : public std::exception
+{
+public:
+ WindowsException(std::string_view Message)
+ {
+ m_hResult = HRESULT_FROM_WIN32(GetLastError());
+ m_Message = Message;
+ }
+
+ WindowsException(HRESULT hRes, std::string_view Message)
+ {
+ m_hResult = hRes;
+ m_Message = Message;
+ }
+
+ WindowsException(HRESULT hRes, const char* Message, const char* Detail)
+ {
+ m_hResult = hRes;
+
+ ExtendableStringBuilder<128> msg;
+ msg.Append(Message);
+ msg.Append(" (detail: '");
+ msg.Append(Detail);
+ msg.Append("')");
+
+ m_Message = msg.c_str();
+ }
+
+ virtual const char* what() const override { return m_Message.c_str(); }
+
+private:
+ std::string m_Message;
+ HRESULT m_hResult;
+};
+
void
ThrowSystemException([[maybe_unused]] HRESULT hRes, [[maybe_unused]] std::string_view Message)
{
@@ -28,6 +63,12 @@ ThrowLastError(std::string_view Message)
throw std::system_error(std::error_code(zen::GetLastError(), std::system_category()), std::string(Message));
}
+void
+ThrowSystemError(uint32_t ErrorCode, std::string_view Message)
+{
+ throw std::system_error(std::error_code(ErrorCode, std::system_category()), std::string(Message));
+}
+
std::string
GetLastErrorAsString()
{
diff --git a/zencore/filesystem.cpp b/zencore/filesystem.cpp
index afbddcdbd..8ddcbac52 100644
--- a/zencore/filesystem.cpp
+++ b/zencore/filesystem.cpp
@@ -458,14 +458,14 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
HRESULT hRes = Outfile.Create(Path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS);
if (hRes == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND))
{
- zen::CreateDirectories(Path.parent_path());
+ CreateDirectories(Path.parent_path());
hRes = Outfile.Create(Path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS);
}
if (FAILED(hRes))
{
- zen::ThrowSystemException(hRes, "File open failed for '{}'"_format(Path).c_str());
+ ThrowSystemException(hRes, "File open failed for '{}'"_format(Path).c_str());
}
#else
@@ -491,13 +491,13 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
while (WriteSize)
{
- const uint64_t ChunkSize = zen::Min<uint64_t>(WriteSize, uint64_t(2) * 1024 * 1024 * 1024);
+ const uint64_t ChunkSize = Min<uint64_t>(WriteSize, uint64_t(2) * 1024 * 1024 * 1024);
#if ZEN_PLATFORM_WINDOWS
hRes = Outfile.Write(DataPtr, gsl::narrow_cast<uint32_t>(WriteSize));
if (FAILED(hRes))
{
- zen::ThrowSystemException(hRes, "File write failed for '{}'"_format(Path).c_str());
+ ThrowSystemException(hRes, "File write failed for '{}'"_format(Path).c_str());
}
#else
if (write(Fd, DataPtr, WriteSize) != WriteSize)
@@ -630,7 +630,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
if (FAILED(hRes))
{
- zen::ThrowSystemException(hRes, "Failed to open handle to volume root");
+ ThrowSystemException(hRes, "Failed to open handle to volume root");
}
while (Continue)
diff --git a/zencore/include/zencore/except.h b/zencore/include/zencore/except.h
index f0e04a795..1dc6209d6 100644
--- a/zencore/include/zencore/except.h
+++ b/zencore/include/zencore/except.h
@@ -15,63 +15,20 @@
namespace zen {
#if ZEN_PLATFORM_WINDOWS
-class WindowsException : public std::exception
-{
-public:
- WindowsException(std::string_view Message)
- {
- m_hResult = HRESULT_FROM_WIN32(GetLastError());
- m_Message = Message;
- }
-
- WindowsException(HRESULT hRes, std::string_view Message)
- {
- m_hResult = hRes;
- m_Message = Message;
- }
-
- WindowsException(HRESULT hRes, const char* Message, const char* Detail)
- {
- m_hResult = hRes;
-
- ExtendableStringBuilder<128> msg;
- msg.Append(Message);
- msg.Append(" (detail: '");
- msg.Append(Detail);
- msg.Append("')");
-
- m_Message = msg.c_str();
- }
-
- virtual const char* what() const override { return m_Message.c_str(); }
-
-private:
- std::string m_Message;
- HRESULT m_hResult;
-};
-
-ZENCORE_API void ThrowSystemException(HRESULT hRes, std::string_view Message);
+ZENCORE_API void ThrowSystemException [[noreturn]] (HRESULT hRes, std::string_view Message);
#endif // ZEN_PLATFORM_WINDOWS
-ZENCORE_API void ThrowLastError(std::string_view Message);
+ZENCORE_API void ThrowLastError [[noreturn]] (std::string_view Message);
#if __cpp_lib_source_location
-ZENCORE_API void ThrowLastError(std::string_view Message, const std::source_location& Location);
+ZENCORE_API void ThrowLastError [[noreturn]] (std::string_view Message, const std::source_location& Location);
#endif
+ZENCORE_API void ThrowSystemError [[noreturn]] (uint32_t ErrorCode, std::string_view Message);
+
ZENCORE_API std::string GetLastErrorAsString();
ZENCORE_API std::string GetErrorAsString(uint32_t ErrorCode);
-inline void
-ThrowSystemException(const char* Message)
-{
-#if ZEN_PLATFORM_WINDOWS
- throw WindowsException(Message);
-#else
- ThrowLastError(Message);
-#endif
-}
-
inline int32_t
GetLastError()
{
diff --git a/zencore/include/zencore/logging.h b/zencore/include/zencore/logging.h
index 412a39415..221f5f358 100644
--- a/zencore/include/zencore/logging.h
+++ b/zencore/include/zencore/logging.h
@@ -80,3 +80,10 @@ using zen::Log;
using namespace std::literals; \
Log().critical(fmtstr##sv, ##__VA_ARGS__);\
} while (false)
+
+#define ZEN_CONSOLE(fmtstr, ...) \
+ do \
+ { \
+ using namespace std::literals; \
+ ConsoleLog().info(fmtstr##sv, __VA_ARGS__); \
+ } while (false)
diff --git a/zencore/include/zencore/mpscqueue.h b/zencore/include/zencore/mpscqueue.h
new file mode 100644
index 000000000..bb558bb5a
--- /dev/null
+++ b/zencore/include/zencore/mpscqueue.h
@@ -0,0 +1,109 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <atomic>
+#include <new>
+#include <optional>
+
+#ifdef __cpp_lib_hardware_interference_size
+using std::hardware_constructive_interference_size;
+using std::hardware_destructive_interference_size;
+#else
+// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...
+constexpr std::size_t hardware_constructive_interference_size = 64;
+constexpr std::size_t hardware_destructive_interference_size = 64;
+#endif
+
+namespace zen {
+
+/** An untyped array of data with compile-time alignment and size derived from another type. */
+template<typename ElementType>
+struct TypeCompatibleStorage
+{
+ ElementType* Data() { return (ElementType*)this; }
+ const ElementType* Data() const { return (const ElementType*)this; }
+
+ char alignas(ElementType) DataMember;
+};
+
+/** Fast multi-producer/single-consumer unbounded concurrent queue.
+
+ Based on http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue
+ */
+
+template<typename T>
+class MpscQueue final
+{
+public:
+ using ElementType = T;
+
+ MpscQueue()
+ {
+ Node* Sentinel = new Node;
+ Head.store(Sentinel, std::memory_order_relaxed);
+ Tail = Sentinel;
+ }
+
+ ~MpscQueue()
+ {
+ Node* Next = Tail->Next.load(std::memory_order_relaxed);
+
+ // sentinel's value is already destroyed
+ delete Tail;
+
+ while (Next != nullptr)
+ {
+ Tail = Next;
+ Next = Tail->Next.load(std::memory_order_relaxed);
+
+ std::destroy_at((ElementType*)&Tail->Value);
+ delete Tail;
+ }
+ }
+
+ template<typename... ArgTypes>
+ void Enqueue(ArgTypes&&... Args)
+ {
+ Node* New = new Node;
+ new (&New->Value) ElementType(std::forward<ArgTypes>(Args)...);
+
+ Node* Prev = Head.exchange(New, std::memory_order_acq_rel);
+ Prev->Next.store(New, std::memory_order_release);
+ }
+
+ std::optional<ElementType> Dequeue()
+ {
+ Node* Next = Tail->Next.load(std::memory_order_acquire);
+
+ if (Next == nullptr)
+ {
+ return {};
+ }
+
+ ElementType* ValuePtr = (ElementType*)&Next->Value;
+ std::optional<ElementType> Res{std::move(*ValuePtr)};
+ std::destroy_at(ValuePtr);
+
+ delete Tail; // current sentinel
+
+ Tail = Next; // new sentinel
+ return Res;
+ }
+
+private:
+ struct Node
+ {
+ std::atomic<Node*> Next{nullptr};
+ TypeCompatibleStorage<ElementType> Value;
+ };
+
+private:
+ std::atomic<Node*> Head; // accessed only by producers
+ alignas(hardware_constructive_interference_size)
+ Node* Tail; // accessed only by consumer, hence should be on a different cache line than `Head`
+};
+
+void mpscqueue_forcelink();
+
+} // namespace zen
diff --git a/zencore/include/zencore/session.h b/zencore/include/zencore/session.h
index 2da41b2c8..dd90197bf 100644
--- a/zencore/include/zencore/session.h
+++ b/zencore/include/zencore/session.h
@@ -8,6 +8,7 @@ namespace zen {
struct Oid;
-ZENCORE_API Oid GetSessionId();
+ZENCORE_API [[nodiscard]] Oid GetSessionId();
+ZENCORE_API [[nodiscard]] std::string_view GetSessionIdString();
} // namespace zen
diff --git a/zencore/include/zencore/snapshot_manifest.h b/zencore/include/zencore/snapshot_manifest.h
deleted file mode 100644
index 95e64773a..000000000
--- a/zencore/include/zencore/snapshot_manifest.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include <zencore/iohash.h>
-#include <zencore/zencore.h>
-
-#include <filesystem>
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace zen {
-
-struct LeafNode
-{
- uint64_t FileSize = 0;
- uint64_t FileModifiedTime = 0;
- zen::IoHash ChunkHash = zen::IoHash::Zero;
- std::wstring Name;
-};
-
-struct TreeNode
-{
- std::vector<TreeNode> Children;
- std::vector<LeafNode> Leaves;
- std::wstring Name;
- zen::BLAKE3 ChunkHash = zen::BLAKE3::Zero;
-
- ZENCORE_API void VisitModifyFiles(std::function<void(LeafNode& node)> func);
- ZENCORE_API void VisitFiles(std::function<void(const LeafNode& node)> func);
- ZENCORE_API void Finalize();
-};
-
-struct SnapshotManifest
-{
- std::string Id;
- TreeNode Root;
- zen::BLAKE3 ChunkHash = zen::BLAKE3::Zero;
-
- ZENCORE_API void finalize();
-};
-
-class InStream;
-class OutStream;
-
-ZENCORE_API void ReadManifest(SnapshotManifest& Manifest, InStream& FromStream);
-ZENCORE_API void WriteManifest(const SnapshotManifest& Manifest, OutStream& ToStream);
-ZENCORE_API void PrintManifest(const SnapshotManifest& Manifest, OutStream& ToStream);
-
-// Translate a user-provided manifest specification into a file path.
-// Supports hashtag syntax to implicitly refer to user documents zenfs folder
-ZENCORE_API std::filesystem::path ManifestSpecToPath(const char* ManifestSpec);
-
-void snapshotmanifest_forcelink();
-
-} // namespace zen
diff --git a/zencore/include/zencore/string.h b/zencore/include/zencore/string.h
index 2b5f20f86..bb9b1c896 100644
--- a/zencore/include/zencore/string.h
+++ b/zencore/include/zencore/string.h
@@ -622,6 +622,45 @@ ToLower(const std::string_view& InString)
//////////////////////////////////////////////////////////////////////////
+template<typename Fn>
+uint32_t
+ForEachStrTok(const std::string_view& Str, char Delim, Fn&& Func)
+{
+ auto It = Str.begin();
+ auto End = Str.end();
+ uint32_t Count = 0;
+
+ while (It != End)
+ {
+ if (*It == Delim)
+ {
+ It++;
+ continue;
+ }
+
+ std::string_view Remaining{It, End};
+ size_t Idx = Remaining.find(Delim, 0);
+
+ if (Idx == std::string_view::npos)
+ {
+ Idx = Remaining.size();
+ }
+
+ Count++;
+ std::string_view Token{It, It + Idx};
+ if (!Func(Token))
+ {
+ break;
+ }
+
+ It = It + Idx;
+ }
+
+ return Count;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
void string_forcelink(); // internal
} // namespace zen
diff --git a/zencore/include/zencore/testing.h b/zencore/include/zencore/testing.h
new file mode 100644
index 000000000..80aebc26e
--- /dev/null
+++ b/zencore/include/zencore/testing.h
@@ -0,0 +1,9 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/zencore.h>
+
+#if ZEN_WITH_TESTS
+# include <doctest/doctest.h>
+#endif
diff --git a/zencore/include/zencore/testutils.h b/zencore/include/zencore/testutils.h
new file mode 100644
index 000000000..72d985d5c
--- /dev/null
+++ b/zencore/include/zencore/testutils.h
@@ -0,0 +1,31 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <filesystem>
+
+namespace zen {
+
+std::filesystem::path CreateTemporaryDirectory();
+
+class ScopedTemporaryDirectory
+{
+public:
+ ScopedTemporaryDirectory();
+ ~ScopedTemporaryDirectory();
+
+ std::filesystem::path& Path() { return m_RootPath; }
+
+private:
+ std::filesystem::path m_RootPath;
+};
+
+struct ScopedCurrentDirectoryChange
+{
+ std::filesystem::path OldPath{std::filesystem::current_path()};
+
+ ScopedCurrentDirectoryChange() { std::filesystem::current_path(CreateTemporaryDirectory()); }
+ ~ScopedCurrentDirectoryChange() { std::filesystem::current_path(OldPath); }
+};
+
+} // namespace zen
diff --git a/zencore/include/zencore/thread.h b/zencore/include/zencore/thread.h
index b18da6031..7889682cd 100644
--- a/zencore/include/zencore/thread.h
+++ b/zencore/include/zencore/thread.h
@@ -4,9 +4,9 @@
#include "zencore.h"
-#if !ZEN_PLATFORM_WINDOWS
-# include <shared_mutex>
-#endif
+#include <shared_mutex>
+
+#include <vector>
namespace zen {
@@ -64,11 +64,7 @@ public:
};
private:
-#if ZEN_PLATFORM_WINDOWS
- void* m_Srw = nullptr;
-#else
std::shared_mutex m_Mutex;
-#endif
};
/** Basic abstraction of a simple event synchronization mechanism (aka 'binary semaphore')
@@ -93,6 +89,7 @@ public:
ZENCORE_API void Set();
ZENCORE_API void Reset();
ZENCORE_API bool Wait(int TimeoutMs = -1);
+ ZENCORE_API void Close();
protected:
explicit Event(void* EventHandle) : m_EventHandle(EventHandle) {}
@@ -150,6 +147,28 @@ private:
int m_Pid = 0;
};
+/** Process monitor - monitors a list of running processes via polling
+
+ Intended to be used to monitor a set of "sponsor" processes, where
+ we need to determine when none of them remain alive
+
+ */
+
+class ProcessMonitor
+{
+public:
+ ProcessMonitor();
+ ~ProcessMonitor();
+
+ ZENCORE_API bool IsRunning();
+ ZENCORE_API void AddPid(int Pid);
+ ZENCORE_API bool IsActive() const;
+
+private:
+ mutable RwLock m_Lock;
+ std::vector<void*> m_ProcessHandles;
+};
+
ZENCORE_API bool IsProcessRunning(int pid);
ZENCORE_API int GetCurrentProcessId();
diff --git a/zencore/include/zencore/uid.h b/zencore/include/zencore/uid.h
index 2730b1415..f4e9ab65a 100644
--- a/zencore/include/zencore/uid.h
+++ b/zencore/include/zencore/uid.h
@@ -60,6 +60,8 @@ struct Oid
const Oid& Generate();
[[nodiscard]] static Oid FromHexString(const std::string_view String);
StringBuilderBase& ToString(StringBuilderBase& OutString) const;
+ void ToString(char OutString[StringLength]);
+ [[nodiscard]] static Oid FromMemory(const void* Ptr);
auto operator<=>(const Oid& rhs) const = default;
[[nodiscard]] inline operator bool() const { return *this == Zero; }
diff --git a/zencore/include/zencore/zencore.h b/zencore/include/zencore/zencore.h
index 54df7e85e..f6093cb96 100644
--- a/zencore/include/zencore/zencore.h
+++ b/zencore/include/zencore/zencore.h
@@ -3,9 +3,13 @@
#pragma once
#include <cinttypes>
-#include <exception>
+#include <stdexcept>
#include <string>
+#ifndef ZEN_WITH_TESTS
+# define ZEN_WITH_TESTS 1
+#endif
+
//////////////////////////////////////////////////////////////////////////
// Platform
//
@@ -56,6 +60,10 @@
# endif
#endif
+#if ZEN_COMPILER_MSC
+# pragma warning(disable : 4324) // warning C4324: '<type>': structure was padded due to alignment specifier
+#endif
+
//////////////////////////////////////////////////////////////////////////
// Architecture
//
@@ -90,37 +98,69 @@
// Assert
//
+#if ZEN_PLATFORM_WINDOWS
+// Tells the compiler to put the decorated function in a certain section (aka. segment) of the executable.
+# define ZEN_CODE_SECTION(Name) __declspec(code_seg(Name))
+# define ZEN_FORCENOINLINE __declspec(noinline) /* Force code to NOT be inline */
+#else
+# define ZEN_CODE_SECTION(Name)
+# define ZEN_FORCENOINLINE
+#endif
+
+#if ZEN_ARCH_ARM64
+// On ARM we can't do this because the executable will require jumps larger
+// than the branch instruction can handle. Clang will only generate
+// the trampolines in the .text segment of the binary. If the uedbg segment
+// is present it will generate code that it cannot link.
+# define ZEN_DEBUG_SECTION
+#else
+// We'll put all assert implementation code into a separate section in the linked
+// executable. This code should never execute so using a separate section keeps
+// it well off the hot path and hopefully out of the instruction cache. It also
+// facilitates reasoning about the makeup of a compiled/linked binary.
+# define ZEN_DEBUG_SECTION ZEN_CODE_SECTION(".zcold")
+#endif // DO_CHECK || DO_GUARD_SLOW
+
namespace zen {
-class AssertException : public std::exception
+class AssertException : public std::logic_error
{
public:
- AssertException(const char* Msg);
- ~AssertException();
-
- [[nodiscard]] virtual char const* what() const noexcept override { return m_Msg.c_str(); }
-
-private:
- std::string m_Msg;
+ AssertException(const char* Msg) : std::logic_error(Msg) {}
};
} // namespace zen
-#define ZEN_ASSERT(x, ...) \
- do \
- { \
- if (x) \
- break; \
- throw ::zen::AssertException{#x}; \
+template<typename RetType = void, class InnerType, typename... ArgTypes>
+RetType ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
+DispatchAssert(InnerType&& Inner, ArgTypes const&... Args)
+{
+ return Inner(Args...);
+}
+
+#define ZEN_ASSERT(x, ...) \
+ do \
+ { \
+ if (x) [[unlikely]] \
+ break; \
+ struct Impl \
+ { \
+ static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ExecThrow [[noreturn]] () { throw ::zen::AssertException{#x}; } \
+ }; \
+ Impl::ExecThrow(); \
} while (false)
#ifndef NDEBUG
-# define ZEN_ASSERT_SLOW(x, ...) \
- do \
- { \
- if (x) \
- break; \
- throw ::zen::AssertException{#x}; \
+# define ZEN_ASSERT_SLOW(x, ...) \
+ do \
+ { \
+ if (x) [[unlikely]] \
+ break; \
+ struct Impl \
+ { \
+ static void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION ExecThrow [[noreturn]] () { throw ::zen::AssertException{#x}; } \
+ }; \
+ Impl::ExecThrow(); \
} while (false)
#else
# define ZEN_ASSERT_SLOW(x, ...)
@@ -148,14 +188,24 @@ char (&ZenArrayCountHelper(const T (&)[N]))[N + 1];
#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
+#define ZENCORE_API // Placeholder to allow DLL configs in the future (maybe)
+
+namespace zen {
ZENCORE_API bool IsPointerToStack(const void* ptr); // Query if pointer is within the stack of the currently executing thread
ZENCORE_API bool IsApplicationExitRequested();
ZENCORE_API void RequestApplicationExit(int ExitCode);
+ZENCORE_API bool IsDebuggerPresent();
+ZENCORE_API bool IsInteractiveSession();
ZENCORE_API void zencore_forcelinktests();
+} // namespace zen
+
+#ifndef ZEN_USE_MIMALLOC
+# define ZEN_USE_MIMALLOC 1
+#endif
+
//////////////////////////////////////////////////////////////////////////
#if ZEN_COMPILER_MSC
diff --git a/zencore/intmath.cpp b/zencore/intmath.cpp
index 98c345c79..5a686dc8e 100644
--- a/zencore/intmath.cpp
+++ b/zencore/intmath.cpp
@@ -3,7 +3,7 @@
#include <zencore/endian.h>
#include <zencore/intmath.h>
-#include <doctest/doctest.h>
+#include <zencore/testing.h>
namespace zen {
@@ -12,6 +12,8 @@ namespace zen {
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
intmath_forcelink()
{
@@ -58,4 +60,6 @@ TEST_CASE("intmath")
CHECK(ByteSwap(uint64_t(0x214d'6172'7469'6e21ull)) == 0x216e'6974'7261'4d21ull);
}
+#endif
+
} // namespace zen
diff --git a/zencore/iobuffer.cpp b/zencore/iobuffer.cpp
index beb969bc7..5d3458dba 100644
--- a/zencore/iobuffer.cpp
+++ b/zencore/iobuffer.cpp
@@ -2,14 +2,15 @@
#include <zencore/iobuffer.h>
-#include <doctest/doctest.h>
-#include <memory.h>
#include <zencore/except.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/memory.h>
+#include <zencore/testing.h>
#include <zencore/thread.h>
+
+#include <memory.h>
#include <system_error>
#if ZEN_PLATFORM_WINDOWS
@@ -456,6 +457,8 @@ IoBufferBuilder::MakeFromTemporaryFile(const path_char_t* FileName)
//////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
+
void
iobuffer_forcelink()
{
@@ -468,4 +471,6 @@ TEST_CASE("IoBuffer")
zen::IoBuffer buffer3(buffer2, 0, buffer2.Size());
}
+#endif
+
} // namespace zen
diff --git a/zencore/iohash.cpp b/zencore/iohash.cpp
index ad8d89ff0..77076c133 100644
--- a/zencore/iohash.cpp
+++ b/zencore/iohash.cpp
@@ -5,8 +5,8 @@
#include <zencore/blake3.h>
#include <zencore/compositebuffer.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
-#include <doctest/doctest.h>
#include <gsl/gsl-lite.hpp>
namespace zen {
diff --git a/zencore/md5.cpp b/zencore/md5.cpp
index 228c0feff..237f6cfdd 100644
--- a/zencore/md5.cpp
+++ b/zencore/md5.cpp
@@ -2,9 +2,9 @@
#include <zencore/md5.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#include <zencore/zencore.h>
-#include <doctest/doctest.h>
#include <string.h>
// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN
@@ -425,6 +425,8 @@ MD5::ToHexString(StringBuilderBase& outBuilder) const
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
md5_forcelink()
{
@@ -443,4 +445,6 @@ TEST_CASE("MD5")
{
}
+#endif
+
} // namespace zen
diff --git a/zencore/memory.cpp b/zencore/memory.cpp
index 26c8321e5..613b6ba67 100644
--- a/zencore/memory.cpp
+++ b/zencore/memory.cpp
@@ -2,6 +2,7 @@
#include <zencore/intmath.h>
#include <zencore/memory.h>
+#include <zencore/testing.h>
#ifdef ZEN_PLATFORM_WINDOWS
# include <malloc.h>
@@ -9,8 +10,6 @@
# include <cstdlib>
#endif
-#include <doctest/doctest.h>
-
namespace zen {
//////////////////////////////////////////////////////////////////////////
@@ -147,6 +146,8 @@ ChunkingLinearAllocator::Alloc(size_t Size, size_t Alignment)
// Unit tests
//
+#if ZEN_WITH_TESTS
+
TEST_CASE("ChunkingLinearAllocator")
{
ChunkingLinearAllocator Allocator(4096);
@@ -194,4 +195,6 @@ memory_forcelink()
{
}
+#endif
+
} // namespace zen
diff --git a/zencore/mpscqueue.cpp b/zencore/mpscqueue.cpp
new file mode 100644
index 000000000..e1841ef63
--- /dev/null
+++ b/zencore/mpscqueue.cpp
@@ -0,0 +1,25 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/mpscqueue.h>
+
+#include <zencore/testing.h>
+#include <string>
+
+namespace zen {
+
+#if ZEN_WITH_TESTS && 0
+TEST_CASE("mpsc")
+{
+ MpscQueue<std::string> Queue;
+ Queue.Enqueue("hello");
+ std::optional<std::string> Value = Queue.Dequeue();
+ CHECK_EQ(Value, "hello");
+}
+#endif
+
+void
+mpscqueue_forcelink()
+{
+}
+
+} // namespace zen \ No newline at end of file
diff --git a/zencore/refcount.cpp b/zencore/refcount.cpp
index 943635552..33b530b90 100644
--- a/zencore/refcount.cpp
+++ b/zencore/refcount.cpp
@@ -2,7 +2,8 @@
#include <zencore/refcount.h>
-#include <doctest/doctest.h>
+#include <zencore/testing.h>
+
#include <functional>
namespace zen {
@@ -12,6 +13,8 @@ namespace zen {
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
struct TestRefClass : public RefCounted
{
~TestRefClass()
@@ -93,4 +96,6 @@ TEST_CASE("RefPtr on Stack allocated object")
CHECK(IsDestroyed == true);
}
+#endif
+
} // namespace zen
diff --git a/zencore/session.cpp b/zencore/session.cpp
index d57d3685b..ce4bfae1b 100644
--- a/zencore/session.cpp
+++ b/zencore/session.cpp
@@ -9,14 +9,27 @@
namespace zen {
static Oid GlobalSessionId;
+static char GlobalSessionString[Oid::StringLength];
static std::once_flag SessionInitFlag;
Oid
GetSessionId()
{
- std::call_once(SessionInitFlag, [&] { GlobalSessionId.Generate(); });
+ std::call_once(SessionInitFlag, [&] {
+ GlobalSessionId.Generate();
+ GlobalSessionId.ToString(GlobalSessionString);
+ });
return GlobalSessionId;
}
+std::string_view
+GetSessionIdString()
+{
+ // Ensure we actually have a generated session identifier
+ std::ignore = GetSessionId();
+
+ return std::string_view(GlobalSessionString, Oid::StringLength);
+}
+
} // namespace zen \ No newline at end of file
diff --git a/zencore/sha1.cpp b/zencore/sha1.cpp
index 3cc2f5cdf..8b4e7897f 100644
--- a/zencore/sha1.cpp
+++ b/zencore/sha1.cpp
@@ -6,9 +6,9 @@
#include <zencore/sha1.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#include <zencore/zencore.h>
-#include <doctest/doctest.h>
#include <string.h>
// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN
@@ -357,6 +357,8 @@ SHA1::ToHexString(StringBuilderBase& outBuilder) const
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
sha1_forcelink()
{
@@ -436,4 +438,6 @@ TEST_CASE("SHA1")
}
}
+#endif
+
} // namespace zen
diff --git a/zencore/sharedbuffer.cpp b/zencore/sharedbuffer.cpp
index 2761d0b4d..200e06972 100644
--- a/zencore/sharedbuffer.cpp
+++ b/zencore/sharedbuffer.cpp
@@ -2,7 +2,8 @@
#include <zencore/sharedbuffer.h>
-#include <doctest/doctest.h>
+#include <zencore/testing.h>
+
#include <memory.h>
#include <gsl/gsl-lite.hpp>
@@ -129,6 +130,8 @@ SharedBuffer::Clone(MemoryView View)
//////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
+
void
sharedbuffer_forcelink()
{
@@ -138,4 +141,6 @@ TEST_CASE("SharedBuffer")
{
}
+#endif
+
} // namespace zen
diff --git a/zencore/snapshot_manifest.cpp b/zencore/snapshot_manifest.cpp
deleted file mode 100644
index 87625fb7f..000000000
--- a/zencore/snapshot_manifest.cpp
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#include <doctest/doctest.h>
-#include <zencore/snapshot_manifest.h>
-#include <zencore/stream.h>
-#include <zencore/streamutil.h>
-#include <zencore/string.h>
-#include <ostream>
-
-#include <filesystem>
-
-#include <atlbase.h>
-
-// Used for getting My Documents for default snapshot dir
-#include <ShlObj.h>
-#if ZEN_PLATFORM_WINDOWS
-# pragma comment(lib, "shell32.lib")
-#endif
-
-namespace zen {
-
-constexpr const char* magicString = "-=- ZEN_SNAP -=-";
-
-struct SerializedManifestHeader
-{
- char Magic[16];
-
- void init() { memcpy(Magic, magicString, sizeof Magic); }
- bool verify() const { return memcmp(Magic, magicString, sizeof Magic) == 0; }
-};
-
-TextWriter&
-operator<<(TextWriter& Writer, const LeafNode& Leaf)
-{
- Writer << "modTime: " << Leaf.FileModifiedTime << ", size: " << Leaf.FileSize << ", hash: " << Leaf.ChunkHash << ", name: " << Leaf.Name
- << "\n";
-
- return Writer;
-}
-
-BinaryWriter&
-operator<<(BinaryWriter& Writer, const LeafNode& Leaf)
-{
- Writer << Leaf.FileModifiedTime << Leaf.FileSize << Leaf.ChunkHash << Leaf.Name;
-
- return Writer;
-}
-
-BinaryReader&
-operator>>(BinaryReader& Reader, LeafNode& Leaf)
-{
- Reader >> Leaf.FileModifiedTime >> Leaf.FileSize >> Leaf.ChunkHash >> Leaf.Name;
-
- return Reader;
-}
-
-void
-TreeNode::Finalize()
-{
- zen::BLAKE3Stream Blake3Stream;
-
- for (auto& Node : Children)
- {
- Node.Finalize();
- Blake3Stream.Append(Node.ChunkHash.Hash, sizeof Node.ChunkHash);
- Blake3Stream.Append(Node.Name.data(), Node.Name.size() + 1);
- }
-
- for (auto& leaf : Leaves)
- {
- Blake3Stream.Append(leaf.ChunkHash.Hash, sizeof leaf.ChunkHash);
- Blake3Stream.Append(leaf.Name.data(), leaf.Name.size() + 1);
- }
-
- this->ChunkHash = Blake3Stream.GetHash();
-}
-
-void
-TreeNode::VisitFiles(std::function<void(const LeafNode& node)> func)
-{
- for (auto& Node : Children)
- Node.VisitFiles(func);
-
- for (auto& Leaf : Leaves)
- func(Leaf);
-}
-
-void
-TreeNode::VisitModifyFiles(std::function<void(LeafNode& node)> func)
-{
- for (auto& Node : Children)
- Node.VisitModifyFiles(func);
-
- for (auto& Leaf : Leaves)
- func(Leaf);
-}
-
-IndentTextWriter&
-operator<<(IndentTextWriter& Writer, const TreeNode& Node)
-{
- Writer << "hash: " << Node.ChunkHash << ", name: " << Node.Name << "\n";
-
- if (!Node.Leaves.empty())
- {
- Writer << "files: "
- << "\n";
-
- IndentTextWriter::Scope _(Writer);
-
- for (const LeafNode& Leaf : Node.Leaves)
- Writer << Leaf;
- }
-
- if (!Node.Children.empty())
- {
- Writer << "children: "
- << "\n";
-
- IndentTextWriter::Scope _(Writer);
-
- for (const TreeNode& Child : Node.Children)
- {
- Writer << Child;
- }
- }
-
- return Writer;
-}
-
-BinaryWriter&
-operator<<(BinaryWriter& Writer, const TreeNode& Node)
-{
- Writer << Node.ChunkHash << Node.Name;
- Writer << uint32_t(Node.Children.size());
-
- for (const TreeNode& child : Node.Children)
- Writer << child;
-
- Writer << uint32_t(Node.Leaves.size());
-
- for (const LeafNode& Leaf : Node.Leaves)
- Writer << Leaf;
-
- return Writer;
-}
-
-BinaryReader&
-operator>>(BinaryReader& Reader, TreeNode& Node)
-{
- Reader >> Node.ChunkHash >> Node.Name;
-
- uint32_t ChildCount = 0;
- Reader >> ChildCount;
- Node.Children.resize(ChildCount);
-
- for (TreeNode& Child : Node.Children)
- Reader >> Child;
-
- uint32_t LeafCount = 0;
- Reader >> LeafCount;
- Node.Leaves.resize(LeafCount);
-
- for (LeafNode& Leaf : Node.Leaves)
- Reader >> Leaf;
-
- return Reader;
-}
-
-void
-SnapshotManifest::finalize()
-{
- Root.Finalize();
-
- zen::BLAKE3Stream Blake3Stream;
-
- Blake3Stream.Append(Root.ChunkHash.Hash, sizeof Root.ChunkHash);
- Blake3Stream.Append(Root.Name.data(), Root.Name.size() + 1);
-
- this->ChunkHash = Blake3Stream.GetHash();
-}
-
-void
-WriteManifest(const SnapshotManifest& Manifest, OutStream& ToStream)
-{
- BinaryWriter Out(ToStream);
- SerializedManifestHeader Header;
- Header.init();
- Out.Write(&Header, sizeof Header);
-
- Out << Manifest.ChunkHash << Manifest.Id << Manifest.Root;
-}
-
-void
-ReadManifest(SnapshotManifest& Manifest, InStream& FromStream)
-{
- BinaryReader Reader(FromStream);
- SerializedManifestHeader Header;
- Reader.Read(&Header, sizeof Header);
-
- Reader >> Manifest.ChunkHash >> Manifest.Id >> Manifest.Root;
-}
-
-void
-PrintManifest(const SnapshotManifest& Manifest, OutStream& ToStream)
-{
- IndentTextWriter Writer(ToStream);
-
- Writer << "hash: " << Manifest.ChunkHash << "\n";
- Writer << "id: " << Manifest.Id << "\n";
- Writer << "root: "
- << "\n";
- IndentTextWriter::Scope _(Writer);
- Writer << Manifest.Root;
-}
-
-std::filesystem::path
-ManifestSpecToPath(const char* ManifestSpec)
-{
- ExtendableWideStringBuilder<128> ManifestTargetFile;
-
- if (ManifestSpec[0] == '#')
- {
- // Pick sensible default
-
- WCHAR MyDocumentsDir[MAX_PATH];
- HRESULT hRes = SHGetFolderPathW(NULL,
- CSIDL_PERSONAL /* My Documents */,
- NULL,
- SHGFP_TYPE_CURRENT,
- /* out */ MyDocumentsDir);
-
- if (SUCCEEDED(hRes))
- {
- wcscat_s(MyDocumentsDir, L"\\zenfs\\Snapshots\\");
-
- ManifestTargetFile.Append(MyDocumentsDir);
- ManifestTargetFile.AppendAscii(ManifestSpec + 1);
- }
- }
- else
- {
- ManifestTargetFile.AppendAscii(ManifestSpec);
- }
-
- std::filesystem::path ManifestPath{ManifestTargetFile.c_str()};
-
- if (ManifestPath.extension() != L".zenfs")
- {
- ManifestPath.append(L".zenfs");
- }
-
- return ManifestPath;
-}
-
-//////////////////////////////////////////////////////////////////////////
-//
-// Testing related code follows...
-//
-
-void
-snapshotmanifest_forcelink()
-{
-}
-
-TEST_CASE("Snapshot manifest")
-{
- SnapshotManifest Manifest;
-
- Manifest.Id = "test_manifest";
- Manifest.ChunkHash = zen::BLAKE3::HashMemory("abcd", 4);
-
- MemoryOutStream Outstream;
- WriteManifest(Manifest, Outstream);
-
- MemoryInStream Instream(Outstream.Data(), Outstream.Size());
- SnapshotManifest Manifest2;
- ReadManifest(/* out */ Manifest2, Instream);
-
- CHECK(Manifest.Id == Manifest2.Id);
- CHECK(Manifest.ChunkHash == Manifest2.ChunkHash);
-}
-
-} // namespace zen
diff --git a/zencore/stats.cpp b/zencore/stats.cpp
index f8cdc8fbb..c5187940e 100644
--- a/zencore/stats.cpp
+++ b/zencore/stats.cpp
@@ -1,10 +1,13 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "zencore/stats.h"
-#include <doctest/doctest.h>
#include <cmath>
#include "zencore/timer.h"
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+#endif
+
//
// Derived from https://github.com/dln/medida/blob/master/src/medida/stats/ewma.cc
//
@@ -47,6 +50,8 @@ EWMA::Rate() const
//////////////////////////////////////////////////////////////////////////
+#if ZEN_WITH_TESTS
+
TEST_CASE("Stats")
{
SUBCASE("Simple")
@@ -70,4 +75,6 @@ stats_forcelink()
{
}
+#endif
+
} // namespace zen
diff --git a/zencore/stream.cpp b/zencore/stream.cpp
index 8687d5501..ead0b014b 100644
--- a/zencore/stream.cpp
+++ b/zencore/stream.cpp
@@ -1,9 +1,10 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <doctest/doctest.h>
#include <stdarg.h>
#include <zencore/memory.h>
#include <zencore/stream.h>
+#include <zencore/testing.h>
+
#include <algorithm>
#include <stdexcept>
@@ -279,6 +280,8 @@ IndentTextWriter::Write(const void* data, size_t byteCount)
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
stream_forcelink()
{
@@ -336,4 +339,6 @@ TEST_CASE("BinaryWriter and BinaryWriter")
CHECK(i64 == 42);
}
+#endif
+
} // namespace zen
diff --git a/zencore/string.cpp b/zencore/string.cpp
index 8ea10d2a3..6dcdc9542 100644
--- a/zencore/string.cpp
+++ b/zencore/string.cpp
@@ -1,11 +1,12 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <doctest/doctest.h>
+#include <zencore/memory.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
-#include <zencore/memory.h>
-#include <zencore/string.h>
#include <exception>
#include <ostream>
#include <stdexcept>
@@ -452,6 +453,8 @@ template class StringBuilderImpl<wchar_t>;
// Unit tests
//
+#if ZEN_WITH_TESTS
+
TEST_CASE("niceNum")
{
char Buffer[16];
@@ -912,4 +915,46 @@ TEST_CASE("filepath")
CHECK(FilepathFindExtension(".txt") == std::string_view(".txt"));
}
+TEST_CASE("string")
+{
+ using namespace std::literals;
+
+ SUBCASE("ForEachStrTok")
+ {
+ const auto Tokens = "here,is,my,different,tokens"sv;
+ int32_t ExpectedTokenCount = 5;
+ int32_t TokenCount = 0;
+ StringBuilder<512> Sb;
+
+ TokenCount = ForEachStrTok(Tokens, ',', [&Sb](const std::string_view& Token) {
+ if (Sb.Size())
+ {
+ Sb << ",";
+ }
+ Sb << Token;
+ return true;
+ });
+
+ CHECK(TokenCount == ExpectedTokenCount);
+ CHECK(Sb.ToString() == Tokens);
+
+ ExpectedTokenCount = 1;
+ const auto Str = "mosdef"sv;
+
+ Sb.Reset();
+ TokenCount = ForEachStrTok(Str, ' ', [&Sb](const std::string_view& Token) {
+ Sb << Token;
+ return true;
+ });
+ CHECK(Sb.ToString() == Str);
+ CHECK(TokenCount == ExpectedTokenCount);
+
+ ExpectedTokenCount = 0;
+ TokenCount = ForEachStrTok(""sv, ',', [](const std::string_view&) { return true; });
+ CHECK(TokenCount == ExpectedTokenCount);
+ }
+}
+
+#endif
+
} // namespace zen
diff --git a/zencore/testutils.cpp b/zencore/testutils.cpp
new file mode 100644
index 000000000..116491950
--- /dev/null
+++ b/zencore/testutils.cpp
@@ -0,0 +1,33 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zencore/testutils.h"
+#include <zencore/session.h>
+#include "zencore/string.h"
+
+namespace zen {
+
+static std::atomic<int> Sequence{0};
+
+std::filesystem::path
+CreateTemporaryDirectory()
+{
+ std::error_code Ec;
+
+ std::filesystem::path DirPath = std::filesystem::temp_directory_path() / GetSessionIdString() / IntNum(++Sequence).c_str();
+ std::filesystem::remove_all(DirPath, Ec);
+ std::filesystem::create_directories(DirPath);
+
+ return DirPath;
+}
+
+ScopedTemporaryDirectory::ScopedTemporaryDirectory() : m_RootPath(CreateTemporaryDirectory())
+{
+}
+
+ScopedTemporaryDirectory::~ScopedTemporaryDirectory()
+{
+ std::error_code Ec;
+ std::filesystem::remove_all(m_RootPath, Ec);
+}
+
+} // namespace zen \ No newline at end of file
diff --git a/zencore/thread.cpp b/zencore/thread.cpp
index 620ea3bff..ded180337 100644
--- a/zencore/thread.cpp
+++ b/zencore/thread.cpp
@@ -17,41 +17,25 @@ namespace zen {
void
RwLock::AcquireShared()
{
-#if ZEN_PLATFORM_WINDOWS
- AcquireSRWLockShared((PSRWLOCK)&m_Srw);
-#else
m_Mutex.lock_shared();
-#endif
}
void
RwLock::ReleaseShared()
{
-#if ZEN_PLATFORM_WINDOWS
- ReleaseSRWLockShared((PSRWLOCK)&m_Srw);
-#else
m_Mutex.unlock_shared();
-#endif
}
void
RwLock::AcquireExclusive()
{
-#if ZEN_PLATFORM_WINDOWS
- AcquireSRWLockExclusive((PSRWLOCK)&m_Srw);
-#else
m_Mutex.lock();
-#endif
}
void
RwLock::ReleaseExclusive()
{
-#if ZEN_PLATFORM_WINDOWS
- ReleaseSRWLockExclusive((PSRWLOCK)&m_Srw);
-#else
m_Mutex.unlock();
-#endif
}
//////////////////////////////////////////////////////////////////////////
@@ -78,6 +62,13 @@ Event::Reset()
ResetEvent(m_EventHandle);
}
+void
+Event::Close()
+{
+ CloseHandle(m_EventHandle);
+ m_EventHandle = nullptr;
+}
+
bool
Event::Wait(int TimeoutMs)
{
@@ -174,6 +165,7 @@ ProcessHandle::Initialize(void* ProcessHandle)
ZEN_ASSERT(m_ProcessHandle == nullptr);
// TODO: perform some debug verification here to verify it's a valid handle?
m_ProcessHandle = ProcessHandle;
+ m_Pid = GetProcessId(m_ProcessHandle);
}
ProcessHandle::~ProcessHandle()
@@ -255,7 +247,8 @@ ProcessHandle::Wait(int TimeoutMs)
case WAIT_FAILED:
// What might go wrong here, and what is meaningful to act on?
- throw WindowsException("Process::Wait failed");
+ using namespace std::literals;
+ ThrowLastError("Process::Wait failed"sv);
}
return false;
@@ -263,19 +256,100 @@ ProcessHandle::Wait(int TimeoutMs)
//////////////////////////////////////////////////////////////////////////
+ProcessMonitor::ProcessMonitor()
+{
+}
+
+ProcessMonitor::~ProcessMonitor()
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+
+ for (HANDLE& Proc : m_ProcessHandles)
+ {
+ CloseHandle(Proc);
+ Proc = 0;
+ }
+}
+
+bool
+ProcessMonitor::IsRunning()
+{
+ RwLock::ExclusiveLockScope _(m_Lock);
+
+ bool FoundOne = false;
+
+ for (HANDLE& Proc : m_ProcessHandles)
+ {
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(Proc, &ExitCode);
+
+ if (ExitCode != STILL_ACTIVE)
+ {
+ CloseHandle(Proc);
+ Proc = 0;
+ }
+ else
+ {
+ // Still alive
+ FoundOne = true;
+ }
+ }
+
+ std::erase_if(m_ProcessHandles, [](HANDLE Handle) { return Handle == 0; });
+
+ return FoundOne;
+}
+
+void
+ProcessMonitor::AddPid(int Pid)
+{
+ HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+
+ if (ProcessHandle)
+ {
+ RwLock::ExclusiveLockScope _(m_Lock);
+ m_ProcessHandles.push_back(ProcessHandle);
+ }
+}
+
+bool
+ProcessMonitor::IsActive() const
+{
+ RwLock::SharedLockScope _(m_Lock);
+ return m_ProcessHandles.empty() == false;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
bool
IsProcessRunning(int pid)
{
+ // This function is arguably not super useful, a pid can be re-used
+ // by the OS so holding on to a pid and polling it over some time
+ // period will not necessarily tell you what you probably want to know.
+
+#if ZEN_PLATFORM_WINDOWS
HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
- if (hProc == NULL)
+ if (!hProc)
{
- return false;
+ DWORD Error = zen::GetLastError();
+
+ if (Error == ERROR_INVALID_PARAMETER)
+ {
+ return false;
+ }
+
+ using namespace fmt::literals;
+ ThrowSystemError(Error, "failed to open process with pid {}"_format(pid));
}
CloseHandle(hProc);
return true;
+#else
+ ZEN_NOT_IMPLEMENTED();
+#endif
}
int
diff --git a/zencore/timer.cpp b/zencore/timer.cpp
index 08b5e06d2..1e73a7532 100644
--- a/zencore/timer.cpp
+++ b/zencore/timer.cpp
@@ -1,8 +1,10 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include <doctest/doctest.h>
#include <zencore/thread.h>
#include <zencore/timer.h>
+
+#include <zencore/testing.h>
+
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
#elif ZEN_PLATFORM_LINUX
@@ -62,6 +64,8 @@ GetHifreqTimerFrequencySafe()
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
timer_forcelink()
{
@@ -79,4 +83,6 @@ TEST_CASE("Timer")
CHECK_NE(s0, s1);
}
+#endif
+
} // namespace zen
diff --git a/zencore/uid.cpp b/zencore/uid.cpp
index acf9f9790..d4b708288 100644
--- a/zencore/uid.cpp
+++ b/zencore/uid.cpp
@@ -4,6 +4,7 @@
#include <zencore/endian.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#include <atomic>
#include <bit>
@@ -12,8 +13,6 @@
#include <set>
#include <unordered_map>
-#include <doctest/doctest.h>
-
namespace zen {
//////////////////////////////////////////////////////////////////////////
@@ -83,10 +82,24 @@ Oid::FromHexString(const std::string_view String)
}
}
+Oid
+Oid::FromMemory(const void* Ptr)
+{
+ Oid Id;
+ memcpy(Id.OidBits, Ptr, sizeof Id);
+ return Id;
+}
+
+void
+Oid::ToString(char OutString[StringLength])
+{
+ ToHexBytes(reinterpret_cast<const uint8_t*>(OidBits), sizeof(Oid::OidBits), OutString);
+}
+
StringBuilderBase&
Oid::ToString(StringBuilderBase& OutString) const
{
- char str[25];
+ char str[StringLength + 1];
ToHexBytes(reinterpret_cast<const uint8_t*>(OidBits), sizeof(Oid::OidBits), str);
str[2 * sizeof(Oid)] = '\0';
@@ -95,6 +108,8 @@ Oid::ToString(StringBuilderBase& OutString) const
return OutString;
}
+#if ZEN_WITH_TESTS
+
TEST_CASE("Oid")
{
SUBCASE("Basic")
@@ -129,4 +144,6 @@ uid_forcelink()
{
}
+#endif
+
} // namespace zen
diff --git a/zencore/xxhash.cpp b/zencore/xxhash.cpp
index a20ee10bd..450131d19 100644
--- a/zencore/xxhash.cpp
+++ b/zencore/xxhash.cpp
@@ -3,8 +3,8 @@
#include <zencore/xxhash.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
-#include <doctest/doctest.h>
#include <gsl/gsl-lite.hpp>
namespace zen {
diff --git a/zencore/zencore.cpp b/zencore/zencore.cpp
index f9b19ba9d..3eb43c558 100644
--- a/zencore/zencore.cpp
+++ b/zencore/zencore.cpp
@@ -20,9 +20,9 @@
#include <zencore/intmath.h>
#include <zencore/iobuffer.h>
#include <zencore/memory.h>
+#include <zencore/mpscqueue.h>
#include <zencore/refcount.h>
#include <zencore/sha1.h>
-#include <zencore/snapshot_manifest.h>
#include <zencore/stats.h>
#include <zencore/stream.h>
#include <zencore/string.h>
@@ -30,6 +30,10 @@
#include <zencore/timer.h>
#include <zencore/uid.h>
+namespace zen {
+
+//////////////////////////////////////////////////////////////////////////
+
bool
IsPointerToStack(const void* ptr)
{
@@ -55,12 +59,31 @@ IsPointerToStack(const void* ptr)
#endif
}
-zen::AssertException::AssertException(const char* Msg) : m_Msg(Msg)
+bool
+IsDebuggerPresent()
{
+#if ZEN_PLATFORM_WINDOWS
+ return ::IsDebuggerPresent();
+#else
+ return false;
+#endif
}
-zen::AssertException::~AssertException()
+bool
+IsInteractiveSession()
{
+#if ZEN_PLATFORM_WINDOWS
+ DWORD dwSessionId = 0;
+ if (ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId))
+ {
+ return (dwSessionId != 0);
+ }
+
+ return false;
+#else
+ // TODO: figure out what makes sense here
+ return true;
+#endif
}
//////////////////////////////////////////////////////////////////////////
@@ -81,6 +104,7 @@ RequestApplicationExit(int ExitCode)
s_ApplicationExitRequested = true;
}
+#if ZEN_WITH_TESTS
void
zencore_forcelinktests()
{
@@ -91,9 +115,9 @@ zencore_forcelinktests()
zen::intmath_forcelink();
zen::iobuffer_forcelink();
zen::memory_forcelink();
+ zen::mpscqueue_forcelink();
zen::refcount_forcelink();
zen::sha1_forcelink();
- zen::snapshotmanifest_forcelink();
zen::stats_forcelink();
zen::stream_forcelink();
zen::string_forcelink();
@@ -104,3 +128,6 @@ zencore_forcelinktests()
zen::usonbuilder_forcelink();
zen::usonpackage_forcelink();
}
+#endif
+
+} // namespace zen
diff --git a/zencore/zencore.vcxproj b/zencore/zencore.vcxproj
index 150c42cd6..2322f7173 100644
--- a/zencore/zencore.vcxproj
+++ b/zencore/zencore.vcxproj
@@ -128,6 +128,7 @@
<ClInclude Include="include\zencore\md5.h" />
<ClInclude Include="include\zencore\memory.h" />
<ClInclude Include="include\zencore\meta.h" />
+ <ClInclude Include="include\zencore\mpscqueue.h" />
<ClInclude Include="include\zencore\postwindows.h" />
<ClInclude Include="include\zencore\prewindows.h" />
<ClInclude Include="include\zencore\refcount.h" />
@@ -136,12 +137,13 @@
<ClInclude Include="include\zencore\sha1.h" />
<ClInclude Include="include\zencore\iobuffer.h" />
<ClInclude Include="include\zencore\sharedbuffer.h" />
- <ClInclude Include="include\zencore\snapshot_manifest.h" />
<ClInclude Include="include\zencore\stats.h" />
<ClInclude Include="include\zencore\stream.h" />
<ClInclude Include="include\zencore\streamutil.h" />
<ClInclude Include="include\zencore\string.h" />
<ClInclude Include="include\zencore\targetver.h" />
+ <ClInclude Include="include\zencore\testing.h" />
+ <ClInclude Include="include\zencore\testutils.h" />
<ClInclude Include="include\zencore\thread.h" />
<ClInclude Include="include\zencore\timer.h" />
<ClInclude Include="include\zencore\uid.h" />
@@ -166,6 +168,7 @@
<ClCompile Include="logging.cpp" />
<ClCompile Include="md5.cpp" />
<ClCompile Include="memory.cpp" />
+ <ClCompile Include="mpscqueue.cpp" />
<ClCompile Include="refcount.cpp" />
<ClCompile Include="session.cpp" />
<ClCompile Include="sha1.cpp">
@@ -176,11 +179,11 @@
</ClCompile>
<ClCompile Include="iobuffer.cpp" />
<ClCompile Include="sharedbuffer.cpp" />
- <ClCompile Include="snapshot_manifest.cpp" />
<ClCompile Include="stats.cpp" />
<ClCompile Include="stream.cpp" />
<ClCompile Include="streamutil.cpp" />
<ClCompile Include="string.cpp" />
+ <ClCompile Include="testutils.cpp" />
<ClCompile Include="thread.cpp" />
<ClCompile Include="timer.cpp" />
<ClCompile Include="uid.cpp" />
diff --git a/zencore/zencore.vcxproj.filters b/zencore/zencore.vcxproj.filters
index ea0f8a912..d2e7a3159 100644
--- a/zencore/zencore.vcxproj.filters
+++ b/zencore/zencore.vcxproj.filters
@@ -4,7 +4,6 @@
<ClInclude Include="include\zencore\intmath.h" />
<ClInclude Include="include\zencore\scopeguard.h" />
<ClInclude Include="include\zencore\sha1.h" />
- <ClInclude Include="include\zencore\snapshot_manifest.h" />
<ClInclude Include="include\zencore\targetver.h" />
<ClInclude Include="include\zencore\zencore.h" />
<ClInclude Include="include\zencore\compactbinary.h" />
@@ -42,9 +41,11 @@
<ClInclude Include="include\zencore\postwindows.h" />
<ClInclude Include="include\zencore\logging.h" />
<ClInclude Include="include\zencore\session.h" />
+ <ClInclude Include="include\zencore\testutils.h" />
+ <ClInclude Include="include\zencore\testing.h" />
+ <ClInclude Include="include\zencore\mpscqueue.h" />
</ItemGroup>
<ItemGroup>
- <ClCompile Include="snapshot_manifest.cpp" />
<ClCompile Include="sha1.cpp" />
<ClCompile Include="zencore.cpp" />
<ClCompile Include="compactbinary.cpp" />
@@ -74,6 +75,8 @@
<ClCompile Include="logging.cpp" />
<ClCompile Include="intmath.cpp" />
<ClCompile Include="session.cpp" />
+ <ClCompile Include="testutils.cpp" />
+ <ClCompile Include="mpscqueue.cpp" />
</ItemGroup>
<ItemGroup>
<Filter Include="CAS">