aboutsummaryrefslogtreecommitdiff
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
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.
-rw-r--r--README.md60
-rw-r--r--thirdparty/trace/trace.h5207
-rw-r--r--xmake.lua120
-rw-r--r--zen/chunk/chunk.cpp56
-rw-r--r--zen/cmds/dedup.cpp49
-rw-r--r--zen/cmds/dedup.h2
-rw-r--r--zen/cmds/deploy.cpp87
-rw-r--r--zen/cmds/hash.cpp41
-rw-r--r--zen/cmds/hash.h2
-rw-r--r--zen/cmds/run.cpp13
-rw-r--r--zen/internalfile.cpp126
-rw-r--r--zen/internalfile.h25
-rw-r--r--zen/zen.cpp10
-rw-r--r--zen/zen.h13
-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
-rw-r--r--zenhttp/httpasio.cpp37
-rw-r--r--zenhttp/httpclient.cpp4
-rw-r--r--zenhttp/httpnull.cpp30
-rw-r--r--zenhttp/httpserver.cpp2
-rw-r--r--zenhttp/httpshared.cpp2
-rw-r--r--zenhttp/httpsys.cpp6
-rw-r--r--zenhttp/include/zenhttp/httpclient.h2
-rw-r--r--zenhttp/include/zenhttp/httpserver.h2
-rw-r--r--zenhttp/include/zenhttp/httpshared.h5
-rw-r--r--zenhttp/workthreadpool.h1
-rw-r--r--zenhttp/xmake.lua5
-rw-r--r--zenserver-test/projectclient.cpp22
-rw-r--r--zenserver-test/xmake.lua1
-rw-r--r--zenserver-test/zenserver-test.cpp133
-rw-r--r--zenserver/admin/admin.cpp2
-rw-r--r--zenserver/cache/cachetracking.cpp40
-rw-r--r--zenserver/cache/cachetracking.h5
-rw-r--r--zenserver/cache/structuredcache.cpp39
-rw-r--r--zenserver/cache/structuredcachestore.cpp50
-rw-r--r--zenserver/cache/structuredcachestore.h2
-rw-r--r--zenserver/casstore.cpp3
-rw-r--r--zenserver/compute/apply.cpp67
-rw-r--r--zenserver/compute/apply.h22
-rw-r--r--zenserver/config.cpp46
-rw-r--r--zenserver/config.h10
-rw-r--r--zenserver/diag/logging.cpp8
-rw-r--r--zenserver/experimental/frontend.cpp2
-rw-r--r--zenserver/experimental/usnjournal.cpp19
-rw-r--r--zenserver/experimental/usnjournal.h12
-rw-r--r--zenserver/projectstore.cpp59
-rw-r--r--zenserver/projectstore.h1
-rw-r--r--zenserver/testing/launch.cpp65
-rw-r--r--zenserver/testing/launch.h16
-rw-r--r--zenserver/upstream/jupiter.cpp75
-rw-r--r--zenserver/upstream/upstreamapply.cpp67
-rw-r--r--zenserver/upstream/upstreamapply.h24
-rw-r--r--zenserver/upstream/upstreamcache.cpp78
-rw-r--r--zenserver/upstream/upstreamcache.h4
-rw-r--r--zenserver/upstream/zen.h1
-rw-r--r--zenserver/windows/service.cpp16
-rw-r--r--zenserver/xmake.lua4
-rw-r--r--zenserver/zenserver.cpp116
-rw-r--r--zenstore/basicfile.cpp86
-rw-r--r--zenstore/cas.cpp (renamed from zenstore/CAS.cpp)0
-rw-r--r--zenstore/caslog.cpp6
-rw-r--r--zenstore/cidstore.cpp2
-rw-r--r--zenstore/compactcas.cpp2
-rw-r--r--zenstore/compactcas.h5
-rw-r--r--zenstore/filecas.cpp129
-rw-r--r--zenstore/filecas.h6
-rw-r--r--zenstore/gc.cpp2
-rw-r--r--zenstore/include/zenstore/basicfile.h1
-rw-r--r--zenstore/include/zenstore/cas.h (renamed from zenstore/include/zenstore/CAS.h)6
-rw-r--r--zenstore/include/zenstore/caslog.h5
-rw-r--r--zenstore/include/zenstore/cidstore.h2
-rw-r--r--zenstore/zenstore.cpp2
-rw-r--r--zenstore/zenstore.vcxproj2
-rw-r--r--zenstore/zenstore.vcxproj.filters2
-rw-r--r--zenutil/include/zenutil/zenserverprocess.h4
-rw-r--r--zenutil/zenserverprocess.cpp222
99 files changed, 8216 insertions, 922 deletions
diff --git a/README.md b/README.md
index 055ecca3a..0981e8af8 100644
--- a/README.md
+++ b/README.md
@@ -55,11 +55,67 @@ You can then build from the command line:
It's also possible to generate project files for Visual Studio via
-`d:\zen> xmake project -k vsxmake`
+`d:\zen> xmake project -k vsxmake -a x64`
## Building on Linux
-... coming soon
+The following instructions have been collated using Ubuntu 20.04.
+
+At the time of writing only GCC v11 supports the C++20 features used by Zen. As
+this version is not available in the default package repositories we need to
+get GCC from one of Ubuntu's toolchain repositories;
+
+```
+sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
+sudo apt install -y gcc-11
+gcc-11 --version
+```
+
+Next we need the `xmake` build system. For this we will download and install
+`xmake` as a `.deb` package. Check the project's release page for more up to
+date `.deb` files;
+
+```
+wget https://github.com/xmake-io/xmake/releases/download/v2.5.7/xmake-v2.5.7.amd64.deb
+sudo dpkg -i xmake-v2.5.7.amd64.deb
+xmake --version
+```
+
+For some of Zen's third party dependencies are provided by Microsoft's `vcpkg`
+C++ library manager. After cloning the project there is a initialisation step;
+
+```
+git clone https://github.com/microsoft/vcpkg.git ~/zen/vcpkg
+~/zen/vcpkg/bootstrap-vcpkg.sh
+```
+
+`xmake` uses an environment variable to find `vcpkg`. Alternatively this can be
+done by including `VCPKG_ROOT=...` on the command line when invoking `xmake`;
+
+```
+export VCPKG_ROOT=~/zen/vcpkg
+```
+
+Clone the Zen project and tell `xmake` to use the correct GCC version;
+
+```
+git clone https://github.com/EpicGames/zen.git ~/zen/main
+cd ~/zen/main
+xmake config --plat=linux --cxx=g++-11 --cc=gcc-11 [--mode=debug]
+```
+
+The `--mode=debug` is optionally required to change the build variant. This
+cannot be done when executing the build and the setting of configuration options
+can not be composed across multiple `xmake config` calls.
+
+Now we are ready to build Zen. The `-y` skips `xmake` from prompting about
+updating `vcpkg` packages;
+
+```
+xmake build -y
+```
+
+The `xmake` flags `-vD` can be useful to diagnose `xmake` issues.
## Building on Mac
diff --git a/thirdparty/trace/trace.h b/thirdparty/trace/trace.h
new file mode 100644
index 000000000..931a6eb21
--- /dev/null
+++ b/thirdparty/trace/trace.h
@@ -0,0 +1,5207 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+#pragma once
+/* {{{1 standalone_prologue.h */
+
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#if !defined(TRACE_UE_COMPAT_LAYER)
+# define TRACE_UE_COMPAT_LAYER (!__UNREAL__)
+#endif
+
+#if TRACE_UE_COMPAT_LAYER
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+
+#ifdef _WIN32
+# define PLATFORM_WINDOWS 1
+#elif defined(__linux__)
+# define PLATFORM_LINUX 1
+#elif defined(__APPLE__)
+# define PLATFORM_MAC 1
+#endif
+
+#if defined(__amd64__) || defined(_M_X64)
+# define PLATFORM_CPU_X86_FAMILY 1
+# define PLATFORM_64BITS 1
+#elif defined(__arm64__) || defined(_M_ARM64)
+# define PLATFORM_CPU_ARM_FAMILY 1
+# define PLATFORM_64BITS 1
+#else
+# error Unknown architecture
+#endif
+
+#if PLATFORM_WINDOWS
+# if !defined(WIN32_LEAN_AND_MEAN)
+# define WIN32_LEAN_AND_MEAN
+# endif
+# if !defined(NOGDI)
+# define NOGDI
+# endif
+# if !defined(NOMINMAX)
+# define NOMINMAX
+# endif
+# include <Windows.h>
+#endif
+
+// types
+using uint8 = uint8_t;
+using uint16 = uint16_t;
+using uint32 = uint32_t;
+using uint64 = uint64_t;
+
+using int8 = int8_t;
+using int16 = int16_t;
+using int32 = int32_t;
+using int64 = int64_t;
+
+using UPTRINT = uintptr_t;
+using PTRINT = intptr_t;
+
+using SIZE_T = size_t;
+
+#if PLATFORM_WINDOWS
+# undef TEXT
+#endif
+#define TEXT(x) x
+#define TCHAR ANSICHAR
+using ANSICHAR = char;
+using WIDECHAR = wchar_t;
+
+// keywords
+#if defined(_MSC_VER)
+# define FORCENOINLINE __declspec(noinline)
+# define FORCEINLINE __forceinline
+#else
+# define FORCENOINLINE inline __attribute__((noinline))
+# define FORCEINLINE inline __attribute__((always_inline))
+#endif
+
+#if defined(_MSC_VER)
+# define LIKELY(x) x
+# define UNLIKELY(x) x
+#else
+# define LIKELY(x) __builtin_expect(!!(x), 1)
+# define UNLIKELY(x) __builtin_expect(!!(x), 0)
+#endif
+
+#define UE_ARRAY_COUNT(x) (sizeof(x) / sizeof(x[0]))
+
+// so/dll
+#if defined(TRACE_DLL_EXPORT)
+# if PLATFORM_WINDOWS && defined(TRACE_DLL_EXPORT)
+# if TRACE_IMPLEMENT
+# define TRACELOG_API __declspec(dllexport)
+# else
+# define TRACELOG_API __declspec(dllimport)
+# endif
+# else
+# define TRACELOG_API __attribute__ ((visibility ("default")))
+# endif
+#else
+# define TRACELOG_API
+#endif
+
+// misc defines
+#define TRACE_ENABLED 1
+#define UE_TRACE_ENABLED TRACE_ENABLED
+#define TRACE_PRIVATE_CONTROL_ENABLED 0
+#define TRACE_PRIVATE_EXTERNAL_LZ4 1
+#define PLATFORM_CACHE_LINE_SIZE 64
+#define THIRD_PARTY_INCLUDES_START
+#define THIRD_PARTY_INCLUDES_END
+
+// api
+template <typename T>
+inline auto Forward(T t)
+{
+ return std::forward<T>(t);
+}
+
+#endif // TRACE_UE_COMPAT_LAYER
+
+#include <cstring>
+#include "lz4.h"
+
+#if PLATFORM_WINDOWS
+# pragma warning(push)
+# pragma warning(disable : 4200) // zero-sized arrays
+# pragma warning(disable : 4201) // anonymous structs
+# pragma warning(disable : 4127) // conditional expr. is constant
+#endif
+/* {{{1 Config.h */
+
+#if !defined(UE_TRACE_ENABLED)
+# if !UE_BUILD_SHIPPING && !IS_PROGRAM
+# if PLATFORM_WINDOWS || PLATFORM_UNIX || PLATFORM_APPLE || PLATFORM_ANDROID || PLATFORM_HOLOLENS
+# define UE_TRACE_ENABLED 1
+# endif
+# endif
+#endif
+#if !defined(UE_TRACE_ENABLED)
+# define UE_TRACE_ENABLED 0
+#endif
+#if UE_TRACE_ENABLED
+# define TRACE_PRIVATE_PROTOCOL_5
+#endif
+/* {{{1 Trace.h */
+
+#if UE_TRACE_ENABLED
+#include <type_traits>
+namespace UE {
+namespace Trace {
+class FChannel;
+} // namespace Trace
+} // namespace UE
+#define TRACE_PRIVATE_STATISTICS (!UE_BUILD_SHIPPING)
+#define TRACE_PRIVATE_CHANNEL_DEFAULT_ARGS false, "None"
+#define TRACE_PRIVATE_CHANNEL_DECLARE(LinkageType, ChannelName) \
+ static UE::Trace::FChannel ChannelName##Object; \
+ LinkageType UE::Trace::FChannel& ChannelName = ChannelName##Object;
+#define TRACE_PRIVATE_CHANNEL_IMPL(ChannelName, ...) \
+ struct F##ChannelName##Registrator \
+ { \
+ F##ChannelName##Registrator() \
+ { \
+ ChannelName##Object.Setup(#ChannelName, { __VA_ARGS__ } ); \
+ } \
+ }; \
+ static F##ChannelName##Registrator ChannelName##Reg = F##ChannelName##Registrator();
+#define TRACE_PRIVATE_CHANNEL(ChannelName, ...) \
+ TRACE_PRIVATE_CHANNEL_DECLARE(static, ChannelName) \
+ TRACE_PRIVATE_CHANNEL_IMPL(ChannelName, ##__VA_ARGS__)
+#define TRACE_PRIVATE_CHANNEL_DEFINE(ChannelName, ...) \
+ TRACE_PRIVATE_CHANNEL_DECLARE(, ChannelName) \
+ TRACE_PRIVATE_CHANNEL_IMPL(ChannelName, ##__VA_ARGS__)
+#define TRACE_PRIVATE_CHANNEL_EXTERN(ChannelName, ...) \
+ __VA_ARGS__ extern UE::Trace::FChannel& ChannelName;
+#define TRACE_PRIVATE_CHANNELEXPR_IS_ENABLED(ChannelsExpr) \
+ bool(ChannelsExpr)
+#define TRACE_PRIVATE_EVENT_DEFINE(LoggerName, EventName) \
+ UE::Trace::Private::FEventNode LoggerName##EventName##Event;
+#define TRACE_PRIVATE_EVENT_BEGIN(LoggerName, EventName, ...) \
+ TRACE_PRIVATE_EVENT_BEGIN_IMPL(static, LoggerName, EventName, ##__VA_ARGS__)
+#define TRACE_PRIVATE_EVENT_BEGIN_EXTERN(LoggerName, EventName, ...) \
+ TRACE_PRIVATE_EVENT_BEGIN_IMPL(extern, LoggerName, EventName, ##__VA_ARGS__)
+#define TRACE_PRIVATE_EVENT_BEGIN_IMPL(LinkageType, LoggerName, EventName, ...) \
+ LinkageType TRACE_PRIVATE_EVENT_DEFINE(LoggerName, EventName) \
+ struct F##LoggerName##EventName##Fields \
+ { \
+ enum \
+ { \
+ Important = UE::Trace::Private::FEventInfo::Flag_Important, \
+ NoSync = UE::Trace::Private::FEventInfo::Flag_NoSync, \
+ PartialEventFlags = (0, ##__VA_ARGS__), \
+ }; \
+ enum : bool { bIsImportant = ((0, ##__VA_ARGS__) & Important) != 0, }; \
+ static constexpr uint32 GetSize() { return EventProps_Meta::Size; } \
+ static uint32 GetUid() { static uint32 Uid = 0; return (Uid = Uid ? Uid : Initialize()); } \
+ static uint32 FORCENOINLINE Initialize() \
+ { \
+ static const uint32 Uid_ThreadSafeInit = [] () \
+ { \
+ using namespace UE::Trace; \
+ static F##LoggerName##EventName##Fields Fields; \
+ static UE::Trace::Private::FEventInfo Info = \
+ { \
+ FLiteralName(#LoggerName), \
+ FLiteralName(#EventName), \
+ (FFieldDesc*)(&Fields), \
+ uint16(sizeof(Fields) / sizeof(FFieldDesc)), \
+ uint16(EventFlags), \
+ }; \
+ return LoggerName##EventName##Event.Initialize(&Info); \
+ }(); \
+ return Uid_ThreadSafeInit; \
+ } \
+ typedef UE::Trace::TField<0 /*Index*/, 0 /*Offset*/,
+#define TRACE_PRIVATE_EVENT_FIELD(FieldType, FieldName) \
+ FieldType> FieldName##_Meta; \
+ FieldName##_Meta const FieldName##_Field = UE::Trace::FLiteralName(#FieldName); \
+ template <typename... Ts> auto FieldName(Ts... ts) const { \
+ LogScopeType::FFieldSet<FieldName##_Meta, FieldType>::Impl((LogScopeType*)this, Forward<Ts>(ts)...); \
+ return true; \
+ } \
+ typedef UE::Trace::TField< \
+ FieldName##_Meta::Index + 1, \
+ FieldName##_Meta::Offset + FieldName##_Meta::Size,
+#define TRACE_PRIVATE_EVENT_END() \
+ UE::Trace::EventProps> EventProps_Meta; \
+ EventProps_Meta const EventProps_Private = {}; \
+ typedef std::conditional<bIsImportant, UE::Trace::Private::FImportantLogScope, UE::Trace::Private::FLogScope>::type LogScopeType; \
+ explicit operator bool () const { return true; } \
+ enum { EventFlags = PartialEventFlags|(EventProps_Meta::NumAuxFields ? UE::Trace::Private::FEventInfo::Flag_MaybeHasAux : 0), }; \
+ static_assert( \
+ !bIsImportant || (uint32(EventFlags) & uint32(UE::Trace::Private::FEventInfo::Flag_NoSync)), \
+ "Trace events flagged as Important events must be marked NoSync" \
+ ); \
+ };
+#define TRACE_PRIVATE_LOG_PRELUDE(EnterFunc, LoggerName, EventName, ChannelsExpr, ...) \
+ if (TRACE_PRIVATE_CHANNELEXPR_IS_ENABLED(ChannelsExpr)) \
+ if (auto LogScope = F##LoggerName##EventName##Fields::LogScopeType::EnterFunc<F##LoggerName##EventName##Fields>(__VA_ARGS__)) \
+ if (const auto& __restrict EventName = *(F##LoggerName##EventName##Fields*)(&LogScope)) \
+ ((void)EventName),
+#define TRACE_PRIVATE_LOG_EPILOG() \
+ LogScope += LogScope
+#define TRACE_PRIVATE_LOG(LoggerName, EventName, ChannelsExpr, ...) \
+ TRACE_PRIVATE_LOG_PRELUDE(Enter, LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__) \
+ TRACE_PRIVATE_LOG_EPILOG()
+#define TRACE_PRIVATE_LOG_SCOPED(LoggerName, EventName, ChannelsExpr, ...) \
+ UE::Trace::Private::FScopedLogScope PREPROCESSOR_JOIN(TheScope, __LINE__); \
+ TRACE_PRIVATE_LOG_PRELUDE(ScopedEnter, LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__) \
+ PREPROCESSOR_JOIN(TheScope, __LINE__).SetActive(), \
+ TRACE_PRIVATE_LOG_EPILOG()
+#define TRACE_PRIVATE_LOG_SCOPED_T(LoggerName, EventName, ChannelsExpr, ...) \
+ UE::Trace::Private::FScopedStampedLogScope PREPROCESSOR_JOIN(TheScope, __LINE__); \
+ TRACE_PRIVATE_LOG_PRELUDE(ScopedStampedEnter, LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__) \
+ PREPROCESSOR_JOIN(TheScope, __LINE__).SetActive(), \
+ TRACE_PRIVATE_LOG_EPILOG()
+#else
+#define TRACE_PRIVATE_CHANNEL(ChannelName, ...)
+#define TRACE_PRIVATE_CHANNEL_EXTERN(ChannelName, ...)
+#define TRACE_PRIVATE_CHANNEL_DEFINE(ChannelName, ...)
+#define TRACE_PRIVATE_CHANNELEXPR_IS_ENABLED(ChannelsExpr) \
+ false
+#define TRACE_PRIVATE_EVENT_DEFINE(LoggerName, EventName)
+#define TRACE_PRIVATE_EVENT_BEGIN(LoggerName, EventName, ...) \
+ TRACE_PRIVATE_EVENT_BEGIN_IMPL(LoggerName, EventName)
+#define TRACE_PRIVATE_EVENT_BEGIN_EXTERN(LoggerName, EventName, ...) \
+ TRACE_PRIVATE_EVENT_BEGIN_IMPL(LoggerName, EventName)
+#define TRACE_PRIVATE_EVENT_BEGIN_IMPL(LoggerName, EventName) \
+ struct F##LoggerName##EventName##Dummy \
+ { \
+ struct FTraceDisabled \
+ { \
+ const FTraceDisabled& operator () (...) const { return *this; } \
+ }; \
+ const F##LoggerName##EventName##Dummy& operator << (const FTraceDisabled&) const \
+ { \
+ return *this; \
+ } \
+ explicit operator bool () const { return false; }
+#define TRACE_PRIVATE_EVENT_FIELD(FieldType, FieldName) \
+ const FTraceDisabled& FieldName;
+#define TRACE_PRIVATE_EVENT_END() \
+ };
+#define TRACE_PRIVATE_LOG(LoggerName, EventName, ...) \
+ if (const auto& EventName = *(F##LoggerName##EventName##Dummy*)1) \
+ EventName
+#define TRACE_PRIVATE_LOG_SCOPED(LoggerName, EventName, ...) \
+ if (const auto& EventName = *(F##LoggerName##EventName##Dummy*)1) \
+ EventName
+#define TRACE_PRIVATE_LOG_SCOPED_T(LoggerName, EventName, ...) \
+ if (const auto& EventName = *(F##LoggerName##EventName##Dummy*)1) \
+ EventName
+#endif // UE_TRACE_ENABLED
+/* {{{1 Trace.h */
+
+#if UE_TRACE_ENABLED
+# define UE_TRACE_IMPL(...)
+# define UE_TRACE_API TRACELOG_API
+#else
+# define UE_TRACE_IMPL(...) { return __VA_ARGS__; }
+# define UE_TRACE_API inline
+#endif
+namespace UE {
+namespace Trace {
+enum AnsiString {};
+enum WideString {};
+struct FInitializeDesc
+{
+ uint32 TailSizeBytes = 4 << 20;
+ bool bUseWorkerThread = true;
+ bool bUseImportantCache = true;
+};
+typedef void* AllocFunc(SIZE_T, uint32);
+typedef void FreeFunc(void*, SIZE_T);
+struct FStatistics
+{
+ uint64 BytesSent;
+ uint64 BytesTraced;
+ uint64 MemoryUsed;
+ uint32 CacheUsed; // Important-marked events are
+ uint32 CacheWaste; // stored in the cache.
+};
+UE_TRACE_API void SetMemoryHooks(AllocFunc Alloc, FreeFunc Free) UE_TRACE_IMPL();
+UE_TRACE_API void Initialize(const FInitializeDesc& Desc) UE_TRACE_IMPL();
+UE_TRACE_API void Shutdown() UE_TRACE_IMPL();
+UE_TRACE_API void Update() UE_TRACE_IMPL();
+UE_TRACE_API void GetStatistics(FStatistics& Out) UE_TRACE_IMPL();
+UE_TRACE_API bool SendTo(const TCHAR* Host, uint32 Port=0) UE_TRACE_IMPL(false);
+UE_TRACE_API bool WriteTo(const TCHAR* Path) UE_TRACE_IMPL(false);
+UE_TRACE_API bool IsTracing() UE_TRACE_IMPL(false);
+UE_TRACE_API bool Stop() UE_TRACE_IMPL(false);
+UE_TRACE_API bool IsChannel(const TCHAR* ChanneName) UE_TRACE_IMPL(false);
+UE_TRACE_API bool ToggleChannel(const TCHAR* ChannelName, bool bEnabled) UE_TRACE_IMPL(false);
+UE_TRACE_API void ThreadRegister(const TCHAR* Name, uint32 SystemId, int32 SortHint) UE_TRACE_IMPL();
+UE_TRACE_API void ThreadGroupBegin(const TCHAR* Name) UE_TRACE_IMPL();
+UE_TRACE_API void ThreadGroupEnd() UE_TRACE_IMPL();
+} // namespace Trace
+} // namespace UE
+#define UE_TRACE_EVENT_DEFINE(LoggerName, EventName) TRACE_PRIVATE_EVENT_DEFINE(LoggerName, EventName)
+#define UE_TRACE_EVENT_BEGIN(LoggerName, EventName, ...) TRACE_PRIVATE_EVENT_BEGIN(LoggerName, EventName, ##__VA_ARGS__)
+#define UE_TRACE_EVENT_BEGIN_EXTERN(LoggerName, EventName, ...) TRACE_PRIVATE_EVENT_BEGIN_EXTERN(LoggerName, EventName, ##__VA_ARGS__)
+#define UE_TRACE_EVENT_FIELD(FieldType, FieldName) TRACE_PRIVATE_EVENT_FIELD(FieldType, FieldName)
+#define UE_TRACE_EVENT_END() TRACE_PRIVATE_EVENT_END()
+#define UE_TRACE_LOG(LoggerName, EventName, ChannelsExpr, ...) TRACE_PRIVATE_LOG(LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__)
+#define UE_TRACE_LOG_SCOPED(LoggerName, EventName, ChannelsExpr, ...) TRACE_PRIVATE_LOG_SCOPED(LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__)
+#define UE_TRACE_LOG_SCOPED_T(LoggerName, EventName, ChannelsExpr, ...) TRACE_PRIVATE_LOG_SCOPED_T(LoggerName, EventName, ChannelsExpr, ##__VA_ARGS__)
+#define UE_TRACE_CHANNEL(ChannelName, ...) TRACE_PRIVATE_CHANNEL(ChannelName, ##__VA_ARGS__)
+#define UE_TRACE_CHANNEL_EXTERN(ChannelName, ...) TRACE_PRIVATE_CHANNEL_EXTERN(ChannelName, ##__VA_ARGS__)
+#define UE_TRACE_CHANNEL_DEFINE(ChannelName, ...) TRACE_PRIVATE_CHANNEL_DEFINE(ChannelName, ##__VA_ARGS__)
+#define UE_TRACE_CHANNELEXPR_IS_ENABLED(ChannelsExpr) TRACE_PRIVATE_CHANNELEXPR_IS_ENABLED(ChannelsExpr)
+/* {{{1 Channel.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+/*
+ A named channel which can be used to filter trace events. Channels can be
+ combined using the '|' operator which allows expressions like
+ ```
+ UE_TRACE_LOG(FooWriter, FooEvent, FooChannel|BarChannel);
+ ```
+ Note that this works as an AND operator, similar to how a bitmask is constructed.
+ Channels are by default enabled until this method is called. This is to allow
+ events to be emitted during static initialization. In fact all events during
+ this phase are always emitted. In this method we disable all channels except
+ those specified on the command line using -tracechannels argument.
+*/
+class FChannel
+{
+public:
+ struct Iter
+ {
+ ~Iter();
+ const FChannel* GetNext();
+ void* Inner[3];
+ };
+ struct InitArgs
+ {
+ const ANSICHAR* Desc; // User facing description string
+ bool bReadOnly; // If set, channel cannot be changed during a run, only set through command line.
+ };
+ TRACELOG_API void Setup(const ANSICHAR* InChannelName, const InitArgs& Args);
+ TRACELOG_API static void Initialize();
+ static Iter ReadNew();
+ void Announce() const;
+ static bool Toggle(const ANSICHAR* ChannelName, bool bEnabled);
+ static void ToggleAll(bool bEnabled);
+ static FChannel* FindChannel(const ANSICHAR* ChannelName);
+ bool Toggle(bool bEnabled);
+ bool IsEnabled() const;
+ explicit operator bool () const;
+ bool operator | (const FChannel& Rhs) const;
+private:
+ FChannel* Next;
+ struct
+ {
+ const ANSICHAR* Ptr;
+ uint32 Len;
+ uint32 Hash;
+ } Name;
+ volatile int32 Enabled;
+ InitArgs Args;
+};
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Channel.inl */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+extern TRACELOG_API FChannel& TraceLogChannel;
+inline bool FChannel::IsEnabled() const
+{
+ return Enabled >= 0;
+}
+inline FChannel::operator bool () const
+{
+ return IsEnabled();
+}
+inline bool FChannel::operator | (const FChannel& Rhs) const
+{
+ return IsEnabled() && Rhs.IsEnabled();
+}
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Atomic.h */
+
+#include <atomic>
+#if PLATFORM_CPU_X86_FAMILY
+# include <immintrin.h>
+#endif
+namespace UE {
+namespace Trace {
+namespace Private {
+template <typename Type> Type AtomicLoadRelaxed(Type volatile* Source);
+template <typename Type> Type AtomicLoadAcquire(Type volatile* Source);
+template <typename Type> void AtomicStoreRelaxed(Type volatile* Target, Type Value);
+template <typename Type> void AtomicStoreRelease(Type volatile* Target, Type Value);
+template <typename Type> Type AtomicExchangeAcquire(Type volatile* Target, Type Value);
+template <typename Type> Type AtomicExchangeRelease(Type volatile* Target, Type Value);
+template <typename Type> bool AtomicCompareExchangeRelaxed(Type volatile* Target, Type New, Type Expected);
+template <typename Type> bool AtomicCompareExchangeAcquire(Type volatile* Target, Type New, Type Expected);
+template <typename Type> bool AtomicCompareExchangeRelease(Type volatile* Target, Type New, Type Expected);
+template <typename Type> Type AtomicAddRelaxed(Type volatile* Target, Type Value);
+template <typename Type> Type AtomicAddRelease(Type volatile* Target, Type Value);
+template <typename Type> Type AtomicAddAcquire(Type volatile* Target, Type Value);
+void PlatformYield();
+inline void PlatformYield()
+{
+#if PLATFORM_CPU_X86_FAMILY
+ _mm_pause();
+#elif PLATFORM_CPU_ARM_FAMILY
+# if defined(_MSC_VER) && !defined(__clang__) // MSVC
+ __yield();
+# else
+ __builtin_arm_yield();
+# endif
+#else
+ #error Unsupported architecture!
+#endif
+}
+template <typename Type>
+inline Type AtomicLoadRelaxed(Type volatile* Source)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Source;
+ return T->load(std::memory_order_relaxed);
+}
+template <typename Type>
+inline Type AtomicLoadAcquire(Type volatile* Source)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Source;
+ return T->load(std::memory_order_acquire);
+}
+template <typename Type>
+inline void AtomicStoreRelaxed(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ T->store(Value, std::memory_order_relaxed);
+}
+template <typename Type>
+inline void AtomicStoreRelease(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ T->store(Value, std::memory_order_release);
+}
+template <typename Type>
+inline Type AtomicExchangeAcquire(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->exchange(Value, std::memory_order_acquire);
+}
+template <typename Type>
+inline Type AtomicExchangeRelease(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->exchange(Value, std::memory_order_release);
+}
+template <typename Type>
+inline bool AtomicCompareExchangeRelaxed(Type volatile* Target, Type New, Type Expected)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->compare_exchange_weak(Expected, New, std::memory_order_relaxed);
+}
+template <typename Type>
+inline bool AtomicCompareExchangeAcquire(Type volatile* Target, Type New, Type Expected)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->compare_exchange_weak(Expected, New, std::memory_order_acquire);
+}
+template <typename Type>
+inline bool AtomicCompareExchangeRelease(Type volatile* Target, Type New, Type Expected)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->compare_exchange_weak(Expected, New, std::memory_order_release);
+}
+template <typename Type>
+inline Type AtomicAddRelaxed(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->fetch_add(Value, std::memory_order_relaxed);
+}
+template <typename Type>
+inline Type AtomicAddAcquire(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->fetch_add(Value, std::memory_order_acquire);
+}
+template <typename Type>
+inline Type AtomicAddRelease(Type volatile* Target, Type Value)
+{
+ std::atomic<Type>* T = (std::atomic<Type>*) Target;
+ return T->fetch_add(Value, std::memory_order_release);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol0.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_0)
+inline
+#endif
+namespace Protocol0
+{
+enum EProtocol : uint8 { Id = 0 };
+enum : uint8
+{
+ /* Category */
+ Field_CategoryMask = 0300,
+ Field_Integer = 0000,
+ Field_Float = 0100,
+ Field_Array = 0200,
+ /* Size */
+ Field_Pow2SizeMask = 0003,
+ Field_8 = 0000,
+ Field_16 = 0001,
+ Field_32 = 0002,
+ Field_64 = 0003,
+#if PLATFORM_64BITS
+ Field_Ptr = Field_64,
+#else
+ Field_Ptr = Field_32,
+#endif
+ /* Specials */
+ Field_SpecialMask = 0030,
+ Field_Pod = 0000,
+ Field_String = 0010,
+ /*Field_Unused_2 = 0020,
+ ...
+ Field_Unused_7 = 0070,*/
+};
+enum class EFieldType : uint8
+{
+ Bool = Field_Pod | Field_Integer | Field_8,
+ Int8 = Field_Pod | Field_Integer | Field_8,
+ Int16 = Field_Pod | Field_Integer | Field_16,
+ Int32 = Field_Pod | Field_Integer | Field_32,
+ Int64 = Field_Pod | Field_Integer | Field_64,
+ Pointer = Field_Pod | Field_Integer | Field_Ptr,
+ Float32 = Field_Pod | Field_Float | Field_32,
+ Float64 = Field_Pod | Field_Float | Field_64,
+ AnsiString = Field_String | Field_Integer|Field_Array | Field_8,
+ WideString = Field_String | Field_Integer|Field_Array | Field_16,
+ Array = Field_Array,
+};
+struct FNewEventEvent
+{
+ uint16 EventUid;
+ uint8 FieldCount;
+ uint8 Flags;
+ uint8 LoggerNameSize;
+ uint8 EventNameSize;
+ struct
+ {
+ uint16 Offset;
+ uint16 Size;
+ uint8 TypeInfo;
+ uint8 NameSize;
+ } Fields[];
+ /*uint8 NameData[]*/
+};
+enum class EKnownEventUids : uint16
+{
+ NewEvent,
+ User,
+ Max = (1 << 14) - 1, // ...leaves two MSB bits for other uses.
+ UidMask = Max,
+ Invalid = Max,
+ Flag_Important = 1 << 14,
+ Flag_Unused = 1 << 15,
+};
+struct FEventHeader
+{
+ uint16 Uid;
+ uint16 Size;
+ uint8 EventData[];
+};
+} // namespace Protocol0
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol1.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_1)
+inline
+#endif
+namespace Protocol1
+{
+enum EProtocol : uint8 { Id = 1 };
+using Protocol0::EFieldType;
+using Protocol0::FNewEventEvent;
+enum class EEventFlags : uint8
+{
+ Important = 1 << 0,
+ MaybeHasAux = 1 << 1,
+ NoSync = 1 << 2,
+};
+enum class EKnownEventUids : uint16
+{
+ NewEvent,
+ User,
+ Max = (1 << 15) - 1,
+ UidMask = Max,
+ Invalid = Max,
+};
+struct FEventHeader
+{
+ uint16 Uid;
+ uint16 Size;
+ uint16 Serial;
+ uint8 EventData[];
+};
+struct FAuxHeader
+{
+ enum : uint32
+ {
+ AuxDataBit = 0x80,
+ FieldMask = 0x7f,
+ SizeLimit = 1 << 24,
+ };
+ union
+ {
+ uint8 FieldIndex; // 7 bits max (MSB is used to indicate aux data)
+ uint32 Size; // encoded as (Size & 0x00ffffff) << 8
+ };
+ uint8 Data[];
+};
+} // namespace Protocol1
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol2.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_2)
+inline
+#endif
+namespace Protocol2
+{
+enum EProtocol : uint8 { Id = 2 };
+using Protocol1::EFieldType;
+using Protocol1::FNewEventEvent;
+using Protocol1::EEventFlags;
+using Protocol1::EKnownEventUids;
+using Protocol1::FAuxHeader;
+struct FEventHeader
+{
+ uint16 Uid;
+ uint16 Size;
+};
+#pragma pack(push, 1)
+struct FEventHeaderSync
+ : public FEventHeader
+{
+ uint16 SerialLow; // 24-bit...
+ uint8 SerialHigh; // ...serial no.
+ uint8 EventData[];
+};
+#pragma pack(pop)
+static_assert(sizeof(FEventHeaderSync) == 7, "Packing assumption doesn't hold");
+} // namespace Protocol2
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol3.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_3)
+inline
+#endif
+namespace Protocol3
+{
+enum EProtocol : uint8 { Id = 3 };
+using Protocol2::EFieldType;
+using Protocol2::FNewEventEvent;
+using Protocol2::EEventFlags;
+using Protocol2::EKnownEventUids;
+using Protocol2::FAuxHeader;
+using Protocol2::FEventHeader;
+using Protocol2::FEventHeaderSync;
+} // namespace Protocol3
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol4.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_4)
+inline
+#endif
+namespace Protocol4
+{
+enum EProtocol : uint8 { Id = 4 };
+using Protocol3::EFieldType;
+using Protocol3::FNewEventEvent;
+using Protocol3::EEventFlags;
+using Protocol3::FAuxHeader;
+using Protocol3::FEventHeader;
+using Protocol3::FEventHeaderSync;
+struct EKnownEventUids
+{
+ static const uint16 Flag_TwoByteUid = 1 << 0;
+ static const uint16 _UidShift = 1;
+ enum : uint16
+ {
+ NewEvent = 0,
+ EnterScope,
+ EnterScope_T,
+ LeaveScope,
+ LeaveScope_T,
+ _WellKnownNum,
+ };
+ static const uint16 User = _WellKnownNum;
+ static const uint16 Max = (1 << (16 - _UidShift)) - 1;
+ static const uint16 Invalid = Max;
+};
+} // namespace Protocol4
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol5.h */
+
+namespace UE {
+namespace Trace {
+#if defined(TRACE_PRIVATE_PROTOCOL_5)
+inline
+#endif
+namespace Protocol5
+{
+enum EProtocol : uint8 { Id = 5 };
+using Protocol4::EFieldType;
+using Protocol4::FNewEventEvent;
+using Protocol4::EEventFlags;
+struct EKnownEventUids
+{
+ static const uint16 Flag_TwoByteUid = 1 << 0;
+ static const uint16 _UidShift = 1;
+ enum : uint16
+ {
+ NewEvent = 0,
+ AuxData,
+ _AuxData_Unused,
+ AuxDataTerminal,
+ EnterScope,
+ LeaveScope,
+ _Unused6,
+ _Unused7,
+ EnterScope_T,
+ _EnterScope_T_Unused0, // reserved for variable
+ _EnterScope_T_Unused1, // length timestamps
+ _EnterScope_T_Unused2,
+ LeaveScope_T,
+ _LeaveScope_T_Unused0,
+ _LeaveScope_T_Unused1,
+ _LeaveScope_T_Unused2,
+ _WellKnownNum,
+ };
+ static const uint16 User = _WellKnownNum;
+ static const uint16 Max = (1 << (16 - _UidShift)) - 1;
+ static const uint16 Invalid = Max;
+};
+struct FEventHeader
+{
+ uint16 Uid;
+ uint8 Data[];
+};
+static_assert(sizeof(FEventHeader) == 2, "Struct layout assumption doesn't match expectation");
+struct FImportantEventHeader
+{
+ uint16 Uid;
+ uint16 Size;
+ uint8 Data[];
+};
+static_assert(sizeof(FImportantEventHeader) == 4, "Struct layout assumption doesn't match expectation");
+#pragma pack(push, 1)
+struct FEventHeaderSync
+{
+ uint16 Uid;
+ uint16 SerialLow; // 24-bit
+ uint8 SerialHigh; // serial no.
+ uint8 Data[];
+};
+#pragma pack(pop)
+static_assert(sizeof(FEventHeaderSync) == 5, "Packing assumption doesn't hold");
+struct FAuxHeader
+{
+ enum : uint32
+ {
+ FieldShift = 8,
+ FieldBits = 5,
+ FieldMask = (1 << FieldBits) - 1,
+ SizeShift = FieldShift + FieldBits,
+ SizeLimit = 1 << (32 - SizeShift),
+ };
+ union
+ {
+ struct
+ {
+ uint8 Uid;
+ uint8 FieldIndex_Size;
+ uint16 Size;
+ };
+ uint32 Pack;
+ };
+ uint8 Data[];
+};
+static_assert(sizeof(FAuxHeader) == 4, "Struct layout assumption doesn't match expectation");
+} // namespace Protocol5
+} // namespace Trace
+} // namespace UE
+/* {{{1 Protocol.h */
+
+#if defined(_MSC_VER)
+ #pragma warning(push)
+ #pragma warning(disable : 4200) // non-standard zero-sized array
+#endif
+#if defined(_MSC_VER)
+ #pragma warning(pop)
+#endif
+/* {{{1 Writer.inl */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+struct FWriteBuffer
+{
+ uint8 Overflow[6];
+ uint16 Size;
+ uint64 PrevTimestamp;
+ FWriteBuffer* __restrict NextThread;
+ FWriteBuffer* __restrict NextBuffer;
+ uint8* __restrict Cursor;
+ uint8* __restrict volatile Committed;
+ uint8* __restrict Reaped;
+ int32 volatile EtxOffset;
+ int16 Partial;
+ uint16 ThreadId;
+};
+TRACELOG_API uint64 TimeGetTimestamp();
+TRACELOG_API FWriteBuffer* Writer_NextBuffer(int32);
+TRACELOG_API FWriteBuffer* Writer_GetBuffer();
+#if IS_MONOLITHIC
+extern thread_local FWriteBuffer* GTlsWriteBuffer;
+inline FWriteBuffer* Writer_GetBuffer()
+{
+ return GTlsWriteBuffer;
+}
+#endif // IS_MONOLITHIC
+inline uint64 Writer_GetTimestamp(FWriteBuffer* Buffer)
+{
+ uint64 Ret = TimeGetTimestamp() - Buffer->PrevTimestamp;
+ Buffer->PrevTimestamp += Ret;
+ return Ret;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Field.h */
+
+#if UE_TRACE_ENABLED
+/* Statically sized fields (e.g. UE_TRACE_EVENT_FIELD(float[4], Colours)) are
+ * not supported as yet. No call for them. The following define is used to track
+ * where and partially how to implement them */
+#define STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT 0
+namespace UE {
+namespace Trace {
+namespace Private
+{
+UE_TRACE_API void Field_WriteAuxData(uint32, const uint8*, int32);
+UE_TRACE_API void Field_WriteStringAnsi(uint32, const ANSICHAR*, int32);
+UE_TRACE_API void Field_WriteStringAnsi(uint32, const WIDECHAR*, int32);
+UE_TRACE_API void Field_WriteStringWide(uint32, const WIDECHAR*, int32);
+} // namespace Private
+template <typename Type> struct TFieldType;
+template <> struct TFieldType<bool> { enum { Tid = int(EFieldType::Bool), Size = sizeof(bool) }; };
+template <> struct TFieldType<int8> { enum { Tid = int(EFieldType::Int8), Size = sizeof(int8) }; };
+template <> struct TFieldType<int16> { enum { Tid = int(EFieldType::Int16), Size = sizeof(int16) }; };
+template <> struct TFieldType<int32> { enum { Tid = int(EFieldType::Int32), Size = sizeof(int32) }; };
+template <> struct TFieldType<int64> { enum { Tid = int(EFieldType::Int64), Size = sizeof(int64) }; };
+template <> struct TFieldType<uint8> { enum { Tid = int(EFieldType::Int8), Size = sizeof(uint8) }; };
+template <> struct TFieldType<uint16> { enum { Tid = int(EFieldType::Int16), Size = sizeof(uint16) }; };
+template <> struct TFieldType<uint32> { enum { Tid = int(EFieldType::Int32), Size = sizeof(uint32) }; };
+template <> struct TFieldType<uint64> { enum { Tid = int(EFieldType::Int64), Size = sizeof(uint64) }; };
+template <> struct TFieldType<float> { enum { Tid = int(EFieldType::Float32),Size = sizeof(float) }; };
+template <> struct TFieldType<double> { enum { Tid = int(EFieldType::Float64),Size = sizeof(double) }; };
+template <class T> struct TFieldType<T*> { enum { Tid = int(EFieldType::Pointer),Size = sizeof(void*) }; };
+template <typename T>
+struct TFieldType<T[]>
+{
+ enum
+ {
+ Tid = int(TFieldType<T>::Tid)|int(EFieldType::Array),
+ Size = 0,
+ };
+};
+#if STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <typename T, int N>
+struct TFieldType<T[N]>
+{
+ enum
+ {
+ Tid = int(TFieldType<T>::Tid)|int(EFieldType::Array),
+ Size = sizeof(T[N]),
+ };
+};
+#endif // STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <> struct TFieldType<AnsiString> { enum { Tid = int(EFieldType::AnsiString), Size = 0, }; };
+template <> struct TFieldType<WideString> { enum { Tid = int(EFieldType::WideString), Size = 0, }; };
+struct FLiteralName
+{
+ template <uint32 Size>
+ explicit FLiteralName(const ANSICHAR (&Name)[Size])
+ : Ptr(Name)
+ , Length(Size - 1)
+ {
+ static_assert(Size < 256, "Field name is too large");
+ }
+ const ANSICHAR* Ptr;
+ uint8 Length;
+};
+struct FFieldDesc
+{
+ FFieldDesc(const FLiteralName& Name, uint8 Type, uint16 Offset, uint16 Size)
+ : Name(Name.Ptr)
+ , ValueOffset(Offset)
+ , ValueSize(Size)
+ , NameSize(Name.Length)
+ , TypeInfo(Type)
+ {
+ }
+ const ANSICHAR* Name;
+ uint16 ValueOffset;
+ uint16 ValueSize;
+ uint8 NameSize;
+ uint8 TypeInfo;
+};
+template <int InIndex, int InOffset, typename Type> struct TField;
+enum class EIndexPack
+{
+ NumFieldsMax = 1 << FAuxHeader::FieldBits,
+ NumFieldsShift = 8,
+ NumFieldsMask = (1 << NumFieldsShift) - 1,
+ AuxFieldCounter = 1 << NumFieldsShift,
+};
+#define TRACE_PRIVATE_FIELD(InIndex, InOffset, Type) \
+ enum \
+ { \
+ Index = InIndex, \
+ Offset = InOffset, \
+ Tid = TFieldType<Type>::Tid, \
+ Size = TFieldType<Type>::Size, \
+ }; \
+ static_assert((Index & int(EIndexPack::NumFieldsMask)) < int(EIndexPack::NumFieldsMax), "Trace events may only have up to EIndexPack::NumFieldsMax fields"); \
+ private: \
+ FFieldDesc FieldDesc; \
+ public: \
+ TField(const FLiteralName& Name) \
+ : FieldDesc(Name, Tid, Offset, Size) \
+ { \
+ }
+template <int InIndex, int InOffset, typename Type>
+struct TField<InIndex, InOffset, Type[]>
+{
+ TRACE_PRIVATE_FIELD(InIndex + int(EIndexPack::AuxFieldCounter), InOffset, Type[]);
+};
+#if STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <int InIndex, int InOffset, typename Type, int Count>
+struct TField<InIndex, InOffset, Type[Count]>
+{
+ TRACE_PRIVATE_FIELD(InIndex, InOffset, Type[Count]);
+};
+#endif // STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <int InIndex, int InOffset>
+struct TField<InIndex, InOffset, AnsiString>
+{
+ TRACE_PRIVATE_FIELD(InIndex + int(EIndexPack::AuxFieldCounter), InOffset, AnsiString);
+};
+template <int InIndex, int InOffset>
+struct TField<InIndex, InOffset, WideString>
+{
+ TRACE_PRIVATE_FIELD(InIndex + int(EIndexPack::AuxFieldCounter), InOffset, WideString);
+};
+template <int InIndex, int InOffset, typename Type>
+struct TField
+{
+ TRACE_PRIVATE_FIELD(InIndex, InOffset, Type);
+};
+#undef TRACE_PRIVATE_FIELD
+enum EventProps {};
+template <int InNumFields, int InSize>
+struct TField<InNumFields, InSize, EventProps>
+{
+ enum : uint16
+ {
+ NumFields = InNumFields & int(EIndexPack::NumFieldsMask),
+ Size = InSize,
+ NumAuxFields = (InNumFields >> int(EIndexPack::NumFieldsShift)) & int(EIndexPack::NumFieldsMask),
+ };
+};
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 EventNode.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+struct FEventInfo
+{
+ enum
+ {
+ Flag_None = 0,
+ Flag_Important = 1 << 0,
+ Flag_MaybeHasAux = 1 << 1,
+ Flag_NoSync = 1 << 2,
+ };
+ FLiteralName LoggerName;
+ FLiteralName EventName;
+ const FFieldDesc* Fields;
+ uint16 FieldCount;
+ uint16 Flags;
+};
+class FEventNode
+{
+public:
+ struct FIter
+ {
+ const FEventNode* GetNext();
+ void* Inner;
+ };
+ static FIter ReadNew();
+ static void OnConnect();
+ TRACELOG_API uint32 Initialize(const FEventInfo* InInfo);
+ void Describe() const;
+ uint32 GetUid() const { return Uid; }
+private:
+ FEventNode* Next;
+ const FEventInfo* Info;
+ uint32 Uid;
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 ImportantLogScope.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+class FImportantLogScope
+{
+public:
+ template <typename EventType>
+ static FImportantLogScope Enter();
+ template <typename EventType>
+ static FImportantLogScope Enter(uint32 ArrayDataSize);
+ void operator += (const FImportantLogScope&) const;
+ const FImportantLogScope& operator << (bool) const { return *this; }
+ constexpr explicit operator bool () const { return true; }
+ template <typename FieldMeta, typename Type>
+ struct FFieldSet;
+private:
+ static FImportantLogScope EnterImpl(uint32 Uid, uint32 Size);
+ uint8* Ptr;
+ int32 BufferOffset;
+ int32 AuxCursor;
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 SharedBuffer.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+struct FSharedBuffer
+{
+ enum : uint32 { CursorShift = 10 };
+ enum : uint32 { RefBit = 1 << 0 };
+ enum : uint32 { RefInit = (1 << CursorShift) - 1 };
+ enum : uint32 { MaxSize = 1 << (32 - CursorShift - 1) };
+ int32 volatile Cursor; // also packs in a ref count.
+ uint32 Size;
+ uint32 Final;
+ uint32 _Unused;
+ FSharedBuffer* Next;
+};
+struct FNextSharedBuffer
+{
+ FSharedBuffer* Buffer;
+ int32 RegionStart;
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 ImportantLogScope.inl */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+extern TRACELOG_API FSharedBuffer* volatile GSharedBuffer;
+TRACELOG_API FNextSharedBuffer Writer_NextSharedBuffer(FSharedBuffer*, int32, int32);
+template <class T>
+FORCENOINLINE FImportantLogScope FImportantLogScope::Enter(uint32 ArrayDataSize)
+{
+ static_assert(uint32(T::EventFlags) & uint32(FEventInfo::Flag_MaybeHasAux), "Only important trace events with array-type fields need a size parameter to UE_TRACE_LOG()");
+ ArrayDataSize += sizeof(FAuxHeader) * T::EventProps_Meta::NumAuxFields;
+ ArrayDataSize += 1; // for AuxDataTerminal
+ uint32 Size = T::GetSize();
+ uint32 Uid = T::GetUid() >> EKnownEventUids::_UidShift;
+ FImportantLogScope Ret = EnterImpl(Uid, Size + ArrayDataSize);
+ Ret.AuxCursor += Size;
+ Ret.Ptr[Ret.AuxCursor] = uint8(EKnownEventUids::AuxDataTerminal);
+ return Ret;
+}
+template <class T>
+inline FImportantLogScope FImportantLogScope::Enter()
+{
+ static_assert(!(uint32(T::EventFlags) & uint32(FEventInfo::Flag_MaybeHasAux)), "Important trace events with array-type fields must be traced with UE_TRACE_LOG(Logger, Event, Channel, ArrayDataSize)");
+ uint32 Size = T::GetSize();
+ uint32 Uid = T::GetUid() >> EKnownEventUids::_UidShift;
+ return EnterImpl(Uid, Size);
+}
+inline FImportantLogScope FImportantLogScope::EnterImpl(uint32 Uid, uint32 Size)
+{
+ FSharedBuffer* Buffer = AtomicLoadAcquire(&GSharedBuffer);
+ int32 AllocSize = Size;
+ AllocSize += sizeof(FImportantEventHeader);
+ int32 NegSizeAndRef = 0 - ((AllocSize << FSharedBuffer::CursorShift) | FSharedBuffer::RefBit);
+ int32 RegionStart = AtomicAddRelaxed(&(Buffer->Cursor), NegSizeAndRef);
+ if (UNLIKELY(RegionStart + NegSizeAndRef < 0))
+ {
+ FNextSharedBuffer Next = Writer_NextSharedBuffer(Buffer, RegionStart, NegSizeAndRef);
+ Buffer = Next.Buffer;
+ RegionStart = Next.RegionStart;
+ }
+ int32 Bias = (RegionStart >> FSharedBuffer::CursorShift);
+ uint8* Out = (uint8*)Buffer - Bias;
+ auto* Header = (FImportantEventHeader*)Out;
+ Header->Uid = uint16(Uid);
+ Header->Size = uint16(Size);
+ FImportantLogScope Ret;
+ Ret.Ptr = Header->Data;
+ Ret.BufferOffset = int32(PTRINT(Buffer) - PTRINT(Ret.Ptr));
+ Ret.AuxCursor = 0;
+ return Ret;
+}
+inline void FImportantLogScope::operator += (const FImportantLogScope&) const
+{
+ auto* Buffer = (FSharedBuffer*)(Ptr + BufferOffset);
+ AtomicAddRelease(&(Buffer->Cursor), int32(FSharedBuffer::RefBit));
+}
+template <typename FieldMeta, typename Type>
+struct FImportantLogScope::FFieldSet
+{
+ static void Impl(FImportantLogScope* Scope, const Type& Value)
+ {
+ uint8* Dest = (uint8*)(Scope->Ptr) + FieldMeta::Offset;
+ ::memcpy(Dest, &Value, sizeof(Type));
+ }
+};
+template <typename FieldMeta, typename Type>
+struct FImportantLogScope::FFieldSet<FieldMeta, Type[]>
+{
+ static void Impl(FImportantLogScope* Scope, Type const* Data, int32 Num)
+ {
+ uint32 Size = Num * sizeof(Type);
+ auto* Header = (FAuxHeader*)(Scope->Ptr + Scope->AuxCursor);
+ Header->Pack = Size << FAuxHeader::SizeShift;
+ Header->Pack |= (FieldMeta::Index & int32(EIndexPack::NumFieldsMask)) << FAuxHeader::FieldShift;
+ Header->Uid = uint8(EKnownEventUids::AuxData);
+ memcpy(Header + 1, Data, Size);
+ Scope->AuxCursor += sizeof(FAuxHeader) + Size;
+ Scope->Ptr[Scope->AuxCursor] = uint8(EKnownEventUids::AuxDataTerminal);
+ }
+};
+template <typename FieldMeta>
+struct FImportantLogScope::FFieldSet<FieldMeta, AnsiString>
+{
+ static void Impl(FImportantLogScope* Scope, const ANSICHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = int32(strlen(String));
+ }
+ auto* Header = (FAuxHeader*)(Scope->Ptr + Scope->AuxCursor);
+ Header->Pack = Length << FAuxHeader::SizeShift;
+ Header->Pack |= (FieldMeta::Index & int32(EIndexPack::NumFieldsMask)) << FAuxHeader::FieldShift;
+ Header->Uid = uint8(EKnownEventUids::AuxData);
+ memcpy(Header + 1, String, Length);
+ Scope->AuxCursor += sizeof(FAuxHeader) + Length;
+ Scope->Ptr[Scope->AuxCursor] = uint8(EKnownEventUids::AuxDataTerminal);
+ }
+ static void Impl(FImportantLogScope* Scope, const WIDECHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = 0;
+ for (const WIDECHAR* c = String; *c; ++c, ++Length);
+ }
+ auto* Header = (FAuxHeader*)(Scope->Ptr + Scope->AuxCursor);
+ Header->Pack = Length << FAuxHeader::SizeShift;
+ Header->Pack |= (FieldMeta::Index & int32(EIndexPack::NumFieldsMask)) << FAuxHeader::FieldShift;
+ Header->Uid = uint8(EKnownEventUids::AuxData);
+ auto* Out = (int8*)(Header + 1);
+ for (int32 i = 0; i < Length; ++i)
+ {
+ *Out = int8(*String);
+ ++Out;
+ ++String;
+ }
+ Scope->AuxCursor += sizeof(FAuxHeader) + Length;
+ Scope->Ptr[Scope->AuxCursor] = uint8(EKnownEventUids::AuxDataTerminal);
+ }
+};
+template <typename FieldMeta>
+struct FImportantLogScope::FFieldSet<FieldMeta, WideString>
+{
+ static void Impl(FImportantLogScope* Scope, const WIDECHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = 0;
+ for (const WIDECHAR* c = String; *c; ++c, ++Length);
+ }
+ uint32 Size = Length * sizeof(WIDECHAR);
+ auto* Header = (FAuxHeader*)(Scope->Ptr + Scope->AuxCursor);
+ Header->Pack = Size << FAuxHeader::SizeShift;
+ Header->Pack |= (FieldMeta::Index & int32(EIndexPack::NumFieldsMask)) << FAuxHeader::FieldShift;
+ Header->Uid = uint8(EKnownEventUids::AuxData);
+ memcpy(Header + 1, String, Size);
+ Scope->AuxCursor += sizeof(FAuxHeader) + Size;
+ Scope->Ptr[Scope->AuxCursor] = uint8(EKnownEventUids::AuxDataTerminal);
+ }
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 LogScope.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+struct FWriteBuffer;
+template <bool bMaybeHasAux> class TLogScope;
+class FLogScope
+{
+ friend class FEventNode;
+public:
+ template <typename EventType>
+ static auto Enter();
+ template <typename EventType>
+ static auto ScopedEnter();
+ template <typename EventType>
+ static auto ScopedStampedEnter();
+ void* GetPointer() const { return Ptr; }
+ const FLogScope& operator << (bool) const { return *this; }
+ constexpr explicit operator bool () const { return true; }
+ template <typename FieldMeta, typename Type>
+ struct FFieldSet;
+protected:
+ void Commit() const;
+ void Commit(FWriteBuffer* __restrict LatestBuffer) const;
+private:
+ template <uint32 Flags>
+ static auto EnterImpl(uint32 Uid, uint32 Size);
+ template <class T> void EnterPrelude(uint32 Size);
+ void Enter(uint32 Uid, uint32 Size);
+ void EnterNoSync(uint32 Uid, uint32 Size);
+ uint8* Ptr;
+ FWriteBuffer* Buffer;
+};
+template <bool bMaybeHasAux>
+class TLogScope
+ : public FLogScope
+{
+public:
+ void operator += (const FLogScope&) const;
+};
+class FScopedLogScope
+{
+public:
+ ~FScopedLogScope();
+ void SetActive();
+ bool bActive = false;
+};
+class FScopedStampedLogScope
+{
+public:
+ ~FScopedStampedLogScope();
+ void SetActive();
+ bool bActive = false;
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 LogScope.inl */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+extern TRACELOG_API uint32 volatile GLogSerial;
+inline void FLogScope::Commit() const
+{
+ AtomicStoreRelease((uint8**) &(Buffer->Committed), Buffer->Cursor);
+}
+inline void FLogScope::Commit(FWriteBuffer* __restrict LatestBuffer) const
+{
+ if (LatestBuffer != Buffer)
+ {
+ AtomicStoreRelease((uint8**) &(LatestBuffer->Committed), LatestBuffer->Cursor);
+ }
+ Commit();
+}
+template <uint32 Flags>
+inline auto FLogScope::EnterImpl(uint32 Uid, uint32 Size)
+{
+ TLogScope<(Flags & FEventInfo::Flag_MaybeHasAux) != 0> Ret;
+ if ((Flags & FEventInfo::Flag_NoSync) != 0)
+ {
+ Ret.EnterNoSync(Uid, Size);
+ }
+ else
+ {
+ Ret.Enter(Uid, Size);
+ }
+ return Ret;
+}
+template <class HeaderType>
+inline void FLogScope::EnterPrelude(uint32 Size)
+{
+ uint32 AllocSize = sizeof(HeaderType) + Size;
+ Buffer = Writer_GetBuffer();
+ Buffer->Cursor += AllocSize;
+ if (UNLIKELY(Buffer->Cursor > (uint8*)Buffer))
+ {
+ Buffer = Writer_NextBuffer(AllocSize);
+ }
+ Ptr = Buffer->Cursor - Size;
+}
+inline void FLogScope::Enter(uint32 Uid, uint32 Size)
+{
+ EnterPrelude<FEventHeaderSync>(Size);
+ auto* Header = (uint16*)(Ptr - sizeof(FEventHeaderSync::SerialHigh));
+ *(uint32*)(Header - 1) = uint32(AtomicAddRelaxed(&GLogSerial, 1u));
+ Header[-2] = uint16(Uid)|int32(EKnownEventUids::Flag_TwoByteUid);
+}
+inline void FLogScope::EnterNoSync(uint32 Uid, uint32 Size)
+{
+ EnterPrelude<FEventHeader>(Size);
+ auto* Header = (uint16*)(Ptr);
+ Header[-1] = uint16(Uid)|int32(EKnownEventUids::Flag_TwoByteUid);
+}
+template </*bMaybeHasAux*/>
+inline void TLogScope<false>::operator += (const FLogScope&) const
+{
+ Commit();
+}
+template </*bMaybeHasAux*/>
+inline void TLogScope<true>::operator += (const FLogScope&) const
+{
+ FWriteBuffer* LatestBuffer = Writer_GetBuffer();
+ LatestBuffer->Cursor[0] = uint8(EKnownEventUids::AuxDataTerminal << EKnownEventUids::_UidShift);
+ LatestBuffer->Cursor++;
+ Commit(LatestBuffer);
+}
+inline FScopedLogScope::~FScopedLogScope()
+{
+ if (!bActive)
+ {
+ return;
+ }
+ uint8 LeaveUid = uint8(EKnownEventUids::LeaveScope << EKnownEventUids::_UidShift);
+ FWriteBuffer* Buffer = Writer_GetBuffer();
+ if (UNLIKELY(int32((uint8*)Buffer - Buffer->Cursor) < int32(sizeof(LeaveUid))))
+ {
+ Buffer = Writer_NextBuffer(0);
+ }
+ Buffer->Cursor[0] = LeaveUid;
+ Buffer->Cursor += sizeof(LeaveUid);
+ AtomicStoreRelease((uint8**) &(Buffer->Committed), Buffer->Cursor);
+}
+inline void FScopedLogScope::SetActive()
+{
+ bActive = true;
+}
+inline FScopedStampedLogScope::~FScopedStampedLogScope()
+{
+ if (!bActive)
+ {
+ return;
+ }
+ FWriteBuffer* Buffer = Writer_GetBuffer();
+ uint64 Stamp = Writer_GetTimestamp(Buffer);
+ if (UNLIKELY(int32((uint8*)Buffer - Buffer->Cursor) < int32(sizeof(Stamp))))
+ {
+ Buffer = Writer_NextBuffer(0);
+ }
+ Stamp <<= 8;
+ Stamp += uint8(EKnownEventUids::LeaveScope_T) << EKnownEventUids::_UidShift;
+ memcpy((uint64*)(Buffer->Cursor), &Stamp, sizeof(Stamp));
+ Buffer->Cursor += sizeof(Stamp);
+ AtomicStoreRelease((uint8**) &(Buffer->Committed), Buffer->Cursor);
+}
+inline void FScopedStampedLogScope::SetActive()
+{
+ bActive = true;
+}
+template <class EventType>
+FORCENOINLINE auto FLogScope::Enter()
+{
+ uint32 Size = EventType::GetSize();
+ uint32 Uid = EventType::GetUid();
+ return EnterImpl<EventType::EventFlags>(Uid, Size);
+}
+template <class EventType>
+FORCENOINLINE auto FLogScope::ScopedEnter()
+{
+ uint8 EnterUid = uint8(EKnownEventUids::EnterScope << EKnownEventUids::_UidShift);
+ FWriteBuffer* Buffer = Writer_GetBuffer();
+ if (UNLIKELY(int32((uint8*)Buffer - Buffer->Cursor) < int32(sizeof(EnterUid))))
+ {
+ Buffer = Writer_NextBuffer(0);
+ }
+ Buffer->Cursor[0] = EnterUid;
+ Buffer->Cursor += sizeof(EnterUid);
+ AtomicStoreRelease((uint8**) &(Buffer->Committed), Buffer->Cursor);
+ return Enter<EventType>();
+}
+template <class EventType>
+FORCENOINLINE auto FLogScope::ScopedStampedEnter()
+{
+ uint64 Stamp;
+ FWriteBuffer* Buffer = Writer_GetBuffer();
+ if (UNLIKELY(int32((uint8*)Buffer - Buffer->Cursor) < int32(sizeof(Stamp))))
+ {
+ Buffer = Writer_NextBuffer(0);
+ }
+ Stamp = Writer_GetTimestamp(Buffer);
+ Stamp <<= 8;
+ Stamp += uint8(EKnownEventUids::EnterScope_T) << EKnownEventUids::_UidShift;
+ memcpy((uint64*)(Buffer->Cursor), &Stamp, sizeof(Stamp));
+ Buffer->Cursor += sizeof(Stamp);
+ AtomicStoreRelease((uint8**) &(Buffer->Committed), Buffer->Cursor);
+ return Enter<EventType>();
+}
+template <typename FieldMeta, typename Type>
+struct FLogScope::FFieldSet
+{
+ static void Impl(FLogScope* Scope, const Type& Value)
+ {
+ uint8* Dest = (uint8*)(Scope->Ptr) + FieldMeta::Offset;
+ ::memcpy(Dest, &Value, sizeof(Type));
+ }
+};
+template <typename FieldMeta, typename Type>
+struct FLogScope::FFieldSet<FieldMeta, Type[]>
+{
+ static void Impl(FLogScope*, Type const* Data, int32 Num)
+ {
+ static const uint32 Index = FieldMeta::Index & int32(EIndexPack::NumFieldsMask);
+ int32 Size = (Num * sizeof(Type)) & (FAuxHeader::SizeLimit - 1) & ~(sizeof(Type) - 1);
+ Field_WriteAuxData(Index, (const uint8*)Data, Size);
+ }
+};
+#if STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <typename FieldMeta, typename Type, int32 Count>
+struct FLogScope::FFieldSet<FieldMeta, Type[Count]>
+{
+ static void Impl(FLogScope*, Type const* Data, int32 Num=-1) = delete;
+};
+#endif // STATICALLY_SIZED_ARRAY_FIELDS_SUPPORT
+template <typename FieldMeta>
+struct FLogScope::FFieldSet<FieldMeta, AnsiString>
+{
+ static void Impl(FLogScope*, const ANSICHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = int32(strlen(String));
+ }
+ static const uint32 Index = FieldMeta::Index & int32(EIndexPack::NumFieldsMask);
+ Field_WriteStringAnsi(Index, String, Length);
+ }
+ static void Impl(FLogScope*, const WIDECHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = 0;
+ for (const WIDECHAR* c = String; *c; ++c, ++Length);
+ }
+ static const uint32 Index = FieldMeta::Index & int32(EIndexPack::NumFieldsMask);
+ Field_WriteStringAnsi(Index, String, Length);
+ }
+};
+template <typename FieldMeta>
+struct FLogScope::FFieldSet<FieldMeta, WideString>
+{
+ static void Impl(FLogScope*, const WIDECHAR* String, int32 Length=-1)
+ {
+ if (Length < 0)
+ {
+ Length = 0;
+ for (const WIDECHAR* c = String; *c; ++c, ++Length);
+ }
+ static const uint32 Index = FieldMeta::Index & int32(EIndexPack::NumFieldsMask);
+ Field_WriteStringWide(Index, String, Length);
+ }
+};
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Trace.inl */
+
+/* {{{1 Transport.h */
+
+namespace UE {
+namespace Trace {
+enum ETransport : uint8
+{
+ _Unused = 0,
+ Raw = 1,
+ Packet = 2,
+ TidPacket = 3,
+ TidPacketSync = 4,
+ Active = TidPacketSync,
+};
+enum ETransportTid : uint32
+{
+ Events = 0, // used to describe events
+ Internal = 1, // events to make the trace stream function
+ Importants = Internal, // important/cached events
+ Bias, // [Bias,End] = threads. Note bias can't be..
+ /* ... */ // ..changed as it breaks backwards compat :(
+ End = 0x3ffe, // two msbs are user for packet markers
+ Sync = 0x3fff, // see Writer_SendSync()
+};
+namespace Private
+{
+struct FTidPacketBase
+{
+ enum : uint16
+ {
+ EncodedMarker = 0x8000,
+ PartialMarker = 0x4000,
+ ThreadIdMask = PartialMarker - 1,
+ };
+ uint16 PacketSize;
+ uint16 ThreadId;
+};
+template <uint32 DataSize>
+struct TTidPacket
+ : public FTidPacketBase
+{
+ uint8 Data[DataSize];
+};
+template <uint32 DataSize>
+struct TTidPacketEncoded
+ : public FTidPacketBase
+{
+ uint16 DecodedSize;
+ uint8 Data[DataSize];
+};
+using FTidPacket = TTidPacket<0>;
+using FTidPacketEncoded = TTidPacketEncoded<0>;
+static_assert(sizeof(FTidPacket) == 4, "");
+static_assert(sizeof(FTidPacketEncoded) == 6, "");
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+/* {{{1 Platform.h */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR* Name, void (*Entry)());
+void ThreadSleep(uint32 Milliseconds);
+void ThreadJoin(UPTRINT Handle);
+void ThreadDestroy(UPTRINT Handle);
+uint64 TimeGetFrequency();
+TRACELOG_API uint64 TimeGetTimestamp();
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port);
+UPTRINT TcpSocketListen(uint16 Port);
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out);
+bool TcpSocketHasData(UPTRINT Socket);
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size);
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size);
+void IoClose(UPTRINT Handle);
+UPTRINT FileOpen(const ANSICHAR* Path);
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 WriteBufferRedirect.h */
+
+namespace UE {
+namespace Trace {
+namespace Private {
+extern thread_local FWriteBuffer* GTlsWriteBuffer;
+template <int BufferSize>
+class TWriteBufferRedirect
+{
+public:
+ TWriteBufferRedirect();
+ ~TWriteBufferRedirect();
+ void Close();
+ uint8* GetData();
+ uint32 GetSize() const;
+ uint32 GetCapacity() const;
+ void Reset();
+private:
+ FWriteBuffer* PrevBuffer;
+ uint8 Data[BufferSize];
+ FWriteBuffer Buffer;
+};
+template <int BufferSize>
+inline TWriteBufferRedirect<BufferSize>::TWriteBufferRedirect()
+{
+ Reset();
+ PrevBuffer = GTlsWriteBuffer;
+ GTlsWriteBuffer = &Buffer;
+}
+template <int BufferSize>
+inline TWriteBufferRedirect<BufferSize>::~TWriteBufferRedirect()
+{
+ Close();
+}
+template <int BufferSize>
+inline void TWriteBufferRedirect<BufferSize>::Close()
+{
+ if (PrevBuffer == nullptr)
+ {
+ return;
+ }
+ GTlsWriteBuffer = PrevBuffer;
+ PrevBuffer = nullptr;
+}
+template <int BufferSize>
+inline uint8* TWriteBufferRedirect<BufferSize>::GetData()
+{
+ return Buffer.Reaped;
+}
+template <int BufferSize>
+inline uint32 TWriteBufferRedirect<BufferSize>::GetSize() const
+{
+ return uint32(Buffer.Committed - Buffer.Reaped);
+}
+template <int BufferSize>
+inline uint32 TWriteBufferRedirect<BufferSize>::GetCapacity() const
+{
+ return BufferSize;
+}
+template <int BufferSize>
+inline void TWriteBufferRedirect<BufferSize>::Reset()
+{
+ Buffer.Cursor = Data + sizeof(uint32);
+ Buffer.Committed = Buffer.Cursor;
+ Buffer.Reaped = Buffer.Cursor;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#if TRACE_IMPLEMENT
+/* {{{1 BlockPool.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+void* Writer_MemoryAllocate(SIZE_T, uint32);
+void Writer_MemoryFree(void*, uint32);
+struct FPoolPage
+{
+ FPoolPage* NextPage;
+ uint32 AllocSize;
+};
+struct FPoolBlockList
+{
+ FWriteBuffer* Head;
+ FWriteBuffer* Tail;
+};
+#define T_ALIGN alignas(PLATFORM_CACHE_LINE_SIZE)
+static const uint32 GPoolBlockSize = 4 << 10;
+static const uint32 GPoolPageSize = GPoolBlockSize << 4;
+static const uint32 GPoolInitPageSize = GPoolBlockSize << 6;
+T_ALIGN static FWriteBuffer* volatile GPoolFreeList; // = nullptr;
+T_ALIGN static UPTRINT volatile GPoolFutex; // = 0
+T_ALIGN static FPoolPage* volatile GPoolPageList; // = nullptr;
+static uint32 GPoolUsage; // = 0;
+#undef T_ALIGN
+static FPoolBlockList Writer_AddPageToPool(uint32 PageSize)
+{
+ uint8* PageBase = (uint8*)Writer_MemoryAllocate(PageSize, PLATFORM_CACHE_LINE_SIZE);
+ GPoolUsage += PageSize;
+ uint32 BufferSize = GPoolBlockSize;
+ BufferSize -= sizeof(FWriteBuffer);
+ BufferSize -= sizeof(uint32); // to preceed event data with a small header when sending.
+ uint8* FirstBlock = PageBase + GPoolBlockSize - sizeof(FWriteBuffer);
+ uint8* Block = FirstBlock;
+ for (int i = 1, n = PageSize / GPoolBlockSize; ; ++i)
+ {
+ auto* Buffer = (FWriteBuffer*)Block;
+ Buffer->Size = uint16(BufferSize);
+ if (i >= n)
+ {
+ break;
+ }
+ Buffer->NextBuffer = (FWriteBuffer*)(Block + GPoolBlockSize);
+ Block += GPoolBlockSize;
+ }
+ FWriteBuffer* NextBuffer = (FWriteBuffer*)FirstBlock;
+ NextBuffer->Size -= sizeof(FPoolPage);
+ FPoolPage* PageListNode = (FPoolPage*)PageBase;
+ PageListNode->NextPage = GPoolPageList;
+ PageListNode->AllocSize = PageSize;
+ GPoolPageList = PageListNode;
+ return { NextBuffer, (FWriteBuffer*)Block };
+}
+FWriteBuffer* Writer_AllocateBlockFromPool()
+{
+ FWriteBuffer* Ret;
+ while (true)
+ {
+ FWriteBuffer* Owned = AtomicLoadRelaxed(&GPoolFreeList);
+ if (Owned != nullptr)
+ {
+ if (!AtomicCompareExchangeRelaxed(&GPoolFreeList, Owned->NextBuffer, Owned))
+ {
+ PlatformYield();
+ continue;
+ }
+ }
+ if (Owned != nullptr)
+ {
+ Ret = (FWriteBuffer*)Owned;
+ break;
+ }
+ UPTRINT Futex = AtomicLoadRelaxed(&GPoolFutex);
+ if (Futex || !AtomicCompareExchangeAcquire(&GPoolFutex, Futex + 1, Futex))
+ {
+ ThreadSleep(0);
+ continue;
+ }
+ FPoolBlockList BlockList = Writer_AddPageToPool(GPoolPageSize);
+ Ret = BlockList.Head;
+ for (auto* ListNode = BlockList.Tail;; PlatformYield())
+ {
+ ListNode->NextBuffer = AtomicLoadRelaxed(&GPoolFreeList);
+ if (AtomicCompareExchangeRelease(&GPoolFreeList, Ret->NextBuffer, ListNode->NextBuffer))
+ {
+ break;
+ }
+ }
+ for (;; Private::PlatformYield())
+ {
+ if (AtomicCompareExchangeRelease<UPTRINT>(&GPoolFutex, 0, 1))
+ {
+ break;
+ }
+ }
+ break;
+ }
+ return Ret;
+}
+void Writer_FreeBlockListToPool(FWriteBuffer* Head, FWriteBuffer* Tail)
+{
+ for (FWriteBuffer* ListNode = Tail;; PlatformYield())
+ {
+ ListNode->NextBuffer = AtomicLoadRelaxed(&GPoolFreeList);
+ if (AtomicCompareExchangeRelease(&GPoolFreeList, Head, ListNode->NextBuffer))
+ {
+ break;
+ }
+ }
+}
+void Writer_InitializePool()
+{
+ Writer_AddPageToPool(GPoolBlockSize);
+ static_assert(GPoolPageSize >= 0x10000, "Page growth must be >= 64KB");
+ static_assert(GPoolInitPageSize >= 0x10000, "Initial page size must be >= 64KB");
+}
+void Writer_ShutdownPool()
+{
+ for (auto* Page = AtomicLoadRelaxed(&GPoolPageList); Page != nullptr;)
+ {
+ FPoolPage* NextPage = Page->NextPage;
+ uint32 PageSize = (NextPage == nullptr) ? GPoolBlockSize : GPoolPageSize;
+ Writer_MemoryFree(Page, PageSize);
+ Page = NextPage;
+ }
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Channel.cpp */
+
+#include <ctype.h>
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+struct FTraceChannel : public FChannel
+{
+ bool IsEnabled() const { return true; }
+ explicit operator bool() const { return true; }
+};
+static FTraceChannel TraceLogChannelDetail;
+FChannel& TraceLogChannel = TraceLogChannelDetail;
+UE_TRACE_EVENT_BEGIN(Trace, ChannelAnnounce, NoSync|Important)
+ UE_TRACE_EVENT_FIELD(uint32, Id)
+ UE_TRACE_EVENT_FIELD(bool, IsEnabled)
+ UE_TRACE_EVENT_FIELD(bool, ReadOnly)
+ UE_TRACE_EVENT_FIELD(AnsiString, Name)
+UE_TRACE_EVENT_END()
+UE_TRACE_EVENT_BEGIN(Trace, ChannelToggle, NoSync|Important)
+ UE_TRACE_EVENT_FIELD(uint32, Id)
+ UE_TRACE_EVENT_FIELD(bool, IsEnabled)
+UE_TRACE_EVENT_END()
+static FChannel* volatile GHeadChannel; // = nullptr;
+static FChannel* volatile GNewChannelList; // = nullptr;
+static bool GInitialized;
+static uint32 GetChannelHash(const ANSICHAR* Input, int32 Length)
+{
+ if (Length > 0 && (Input[Length - 1] | 0x20) == 's')
+ {
+ --Length;
+ }
+ uint32 Result = 0x811c9dc5;
+ for (; Length; ++Input, --Length)
+ {
+ Result ^= *Input | 0x20; // a cheap ASCII-only case insensitivity.
+ Result *= 0x01000193;
+ }
+ return Result;
+}
+static uint32 GetChannelNameLength(const ANSICHAR* ChannelName)
+{
+ size_t Len = uint32(strlen(ChannelName));
+ if (Len > 7)
+ {
+ if (strcmp(ChannelName + Len - 7, "Channel") == 0)
+ {
+ Len -= 7;
+ }
+ }
+ return uint32(Len);
+}
+FChannel::Iter::~Iter()
+{
+ if (Inner[2] == nullptr)
+ {
+ return;
+ }
+ using namespace Private;
+ for (auto* Node = (FChannel*)Inner[2];; PlatformYield())
+ {
+ Node->Next = AtomicLoadRelaxed(&GHeadChannel);
+ if (AtomicCompareExchangeRelaxed(&GHeadChannel, (FChannel*)Inner[1], Node->Next))
+ {
+ break;
+ }
+ }
+}
+const FChannel* FChannel::Iter::GetNext()
+{
+ auto* Ret = (const FChannel*)Inner[0];
+ if (Ret != nullptr)
+ {
+ Inner[0] = Ret->Next;
+ if (Inner[0] != nullptr)
+ {
+ Inner[2] = Inner[0];
+ }
+ }
+ return Ret;
+}
+FChannel::Iter FChannel::ReadNew()
+{
+ using namespace Private;
+ FChannel* List = AtomicLoadRelaxed(&GNewChannelList);
+ if (List == nullptr)
+ {
+ return {};
+ }
+ while (!AtomicCompareExchangeAcquire(&GNewChannelList, (FChannel*)nullptr, List))
+ {
+ PlatformYield();
+ List = AtomicLoadRelaxed(&GNewChannelList);
+ }
+ return { { List, List, List } };
+}
+void FChannel::Setup(const ANSICHAR* InChannelName, const InitArgs& InArgs)
+{
+ using namespace Private;
+ Name.Ptr = InChannelName;
+ Name.Len = GetChannelNameLength(Name.Ptr);
+ Name.Hash = GetChannelHash(Name.Ptr, Name.Len);
+ Args = InArgs;
+ for (;; PlatformYield())
+ {
+ FChannel* HeadChannel = AtomicLoadRelaxed(&GNewChannelList);
+ Next = HeadChannel;
+ if (AtomicCompareExchangeRelease(&GNewChannelList, this, Next))
+ {
+ break;
+ }
+ }
+ if (GInitialized)
+ {
+ Enabled = -1;
+ }
+}
+void FChannel::Announce() const
+{
+ UE_TRACE_LOG(Trace, ChannelAnnounce, TraceLogChannel, Name.Len * sizeof(ANSICHAR))
+ << ChannelAnnounce.Id(Name.Hash)
+ << ChannelAnnounce.IsEnabled(IsEnabled())
+ << ChannelAnnounce.ReadOnly(Args.bReadOnly)
+ << ChannelAnnounce.Name(Name.Ptr, Name.Len);
+}
+void FChannel::Initialize()
+{
+ ToggleAll(false);
+ GInitialized = true;
+}
+void FChannel::ToggleAll(bool bEnabled)
+{
+ using namespace Private;
+ FChannel* ChannelLists[] =
+ {
+ AtomicLoadAcquire(&GNewChannelList),
+ AtomicLoadAcquire(&GHeadChannel),
+ };
+ for (FChannel* Channel : ChannelLists)
+ {
+ for (; Channel != nullptr; Channel = (FChannel*)(Channel->Next))
+ {
+ Channel->Toggle(bEnabled);
+ }
+ }
+}
+FChannel* FChannel::FindChannel(const ANSICHAR* ChannelName)
+{
+ using namespace Private;
+ const uint32 ChannelNameLen = GetChannelNameLength(ChannelName);
+ const uint32 ChannelNameHash = GetChannelHash(ChannelName, ChannelNameLen);
+ FChannel* ChannelLists[] =
+ {
+ AtomicLoadAcquire(&GNewChannelList),
+ AtomicLoadAcquire(&GHeadChannel),
+ };
+ for (FChannel* Channel : ChannelLists)
+ {
+ for (; Channel != nullptr; Channel = (FChannel*)(Channel->Next))
+ {
+ if (Channel->Name.Hash == ChannelNameHash)
+ {
+ return Channel;
+ }
+ }
+ }
+ return nullptr;
+}
+bool FChannel::Toggle(bool bEnabled)
+{
+ using namespace Private;
+ AtomicAddRelaxed(&Enabled, bEnabled ? 1 : -1);
+ UE_TRACE_LOG(Trace, ChannelToggle, TraceLogChannel)
+ << ChannelToggle.Id(Name.Hash)
+ << ChannelToggle.IsEnabled(IsEnabled());
+ return IsEnabled();
+}
+bool FChannel::Toggle(const ANSICHAR* ChannelName, bool bEnabled)
+{
+ if (FChannel* Channel = FChannel::FindChannel(ChannelName))
+ {
+ return Channel->Toggle(bEnabled);
+ }
+ return false;
+}
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Codec.cpp */
+
+THIRD_PARTY_INCLUDES_START
+#if defined(_MSC_VER)
+# pragma warning(push)
+# pragma warning(disable : 6239)
+#endif
+#if !defined(TRACE_PRIVATE_EXTERNAL_LZ4)
+# define LZ4_NAMESPACE Trace
+# undef LZ4_NAMESPACE
+# define TRACE_PRIVATE_LZ4_NAMESPACE ::Trace::
+#else
+# define TRACE_PRIVATE_LZ4_NAMESPACE
+#endif
+#if defined(_MSC_VER)
+# pragma warning(pop)
+#endif
+THIRD_PARTY_INCLUDES_END
+namespace UE {
+namespace Trace {
+namespace Private {
+int32 Encode(const void* Src, int32 SrcSize, void* Dest, int32 DestSize)
+{
+ return TRACE_PRIVATE_LZ4_NAMESPACE LZ4_compress_fast(
+ (const char*)Src,
+ (char*)Dest,
+ SrcSize,
+ DestSize,
+ 1 // increase by 1 for small speed increase
+ );
+}
+uint32 GetEncodeMaxSize(uint32 InputSize)
+{
+ return LZ4_COMPRESSBOUND(InputSize);
+}
+TRACELOG_API int32 Decode(const void* Src, int32 SrcSize, void* Dest, int32 DestSize)
+{
+ return TRACE_PRIVATE_LZ4_NAMESPACE LZ4_decompress_safe((const char*)Src, (char*)Dest, SrcSize, DestSize);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+/* {{{1 Control.cpp */
+
+#if UE_TRACE_ENABLED
+#include <type_traits>
+namespace UE {
+namespace Trace {
+namespace Private {
+#if !defined(TRACE_PRIVATE_CONTROL_ENABLED) || TRACE_PRIVATE_CONTROL_ENABLED
+bool Writer_SendTo(const ANSICHAR*, uint32=0);
+bool Writer_WriteTo(const ANSICHAR*);
+bool Writer_Stop();
+enum class EControlState : uint8
+{
+ Closed = 0,
+ Listening,
+ Accepted,
+ Failed,
+};
+struct FControlCommands
+{
+ enum { Max = 8 };
+ struct
+ {
+ uint32 Hash;
+ void* Param;
+ void (*Thunk)(void*, uint32, ANSICHAR const* const*);
+ } Commands[Max];
+ uint8 Count;
+};
+static_assert(std::is_trivial<FControlCommands>(), "FControlCommands must be trivial");
+static FControlCommands GControlCommands;
+static UPTRINT GControlListen = 0;
+static UPTRINT GControlSocket = 0;
+static EControlState GControlState; // = EControlState::Closed;
+static uint32 GControlPort = 1985;
+static uint32 Writer_ControlHash(const ANSICHAR* Word)
+{
+ uint32 Hash = 5381;
+ for (; *Word; (Hash = (Hash * 33) ^ *Word), ++Word);
+ return Hash;
+}
+static bool Writer_ControlAddCommand(
+ const ANSICHAR* Name,
+ void* Param,
+ void (*Thunk)(void*, uint32, ANSICHAR const* const*))
+{
+ if (GControlCommands.Count >= FControlCommands::Max)
+ {
+ return false;
+ }
+ uint32 Index = GControlCommands.Count++;
+ GControlCommands.Commands[Index] = { Writer_ControlHash(Name), Param, Thunk };
+ return true;
+}
+static bool Writer_ControlDispatch(uint32 ArgC, ANSICHAR const* const* ArgV)
+{
+ if (ArgC == 0)
+ {
+ return false;
+ }
+ uint32 Hash = Writer_ControlHash(ArgV[0]);
+ --ArgC;
+ ++ArgV;
+ for (int i = 0, n = GControlCommands.Count; i < n; ++i)
+ {
+ const auto& Command = GControlCommands.Commands[i];
+ if (Command.Hash == Hash)
+ {
+ Command.Thunk(Command.Param, ArgC, ArgV);
+ return true;
+ }
+ }
+ return false;
+}
+static bool Writer_ControlListen()
+{
+ GControlListen = TcpSocketListen(GControlPort);
+ if (!GControlListen)
+ {
+ uint32 Seed = uint32(TimeGetTimestamp());
+ for (uint32 i = 0; i < 10 && !GControlListen; Seed *= 13, ++i)
+ {
+ uint32 Port = (Seed & 0x1fff) + 0x8000;
+ GControlListen = TcpSocketListen(Port);
+ if (GControlListen)
+ {
+ GControlPort = Port;
+ break;
+ }
+ }
+ }
+ if (!GControlListen)
+ {
+ GControlState = EControlState::Failed;
+ return false;
+ }
+ GControlState = EControlState::Listening;
+ return true;
+}
+static bool Writer_ControlAccept()
+{
+ UPTRINT Socket;
+ int Return = TcpSocketAccept(GControlListen, Socket);
+ if (Return <= 0)
+ {
+ if (Return == -1)
+ {
+ IoClose(GControlListen);
+ GControlListen = 0;
+ GControlState = EControlState::Failed;
+ }
+ return false;
+ }
+ GControlState = EControlState::Accepted;
+ GControlSocket = Socket;
+ return true;
+}
+static void Writer_ControlRecv()
+{
+ ANSICHAR Buffer[512];
+ ANSICHAR* __restrict Head = Buffer;
+ while (TcpSocketHasData(GControlSocket))
+ {
+ int32 ReadSize = int32(UPTRINT(Buffer + sizeof(Buffer) - Head));
+ int32 Recvd = IoRead(GControlSocket, Head, ReadSize);
+ if (Recvd <= 0)
+ {
+ IoClose(GControlSocket);
+ GControlSocket = 0;
+ GControlState = EControlState::Listening;
+ break;
+ }
+ Head += Recvd;
+ enum EParseState
+ {
+ CrLfSkip,
+ WhitespaceSkip,
+ Word,
+ } ParseState = EParseState::CrLfSkip;
+ uint32 ArgC = 0;
+ const ANSICHAR* ArgV[16];
+ const ANSICHAR* __restrict Spent = Buffer;
+ for (ANSICHAR* __restrict Cursor = Buffer; Cursor < Head; ++Cursor)
+ {
+ switch (ParseState)
+ {
+ case EParseState::CrLfSkip:
+ if (*Cursor == '\n' || *Cursor == '\r')
+ {
+ continue;
+ }
+ ParseState = EParseState::WhitespaceSkip;
+ /* [[fallthrough]] */
+ case EParseState::WhitespaceSkip:
+ if (*Cursor == ' ' || *Cursor == '\0')
+ {
+ continue;
+ }
+ if (ArgC < UE_ARRAY_COUNT(ArgV))
+ {
+ ArgV[ArgC] = Cursor;
+ ++ArgC;
+ }
+ ParseState = EParseState::Word;
+ /* [[fallthrough]] */
+ case EParseState::Word:
+ if (*Cursor == ' ' || *Cursor == '\0')
+ {
+ *Cursor = '\0';
+ ParseState = EParseState::WhitespaceSkip;
+ continue;
+ }
+ if (*Cursor == '\r' || *Cursor == '\n')
+ {
+ *Cursor = '\0';
+ Writer_ControlDispatch(ArgC, ArgV);
+ ArgC = 0;
+ Spent = Cursor + 1;
+ ParseState = EParseState::CrLfSkip;
+ continue;
+ }
+ break;
+ }
+ }
+ int32 UnspentSize = int32(UPTRINT(Head - Spent));
+ if (UnspentSize)
+ {
+ memmove(Buffer, Spent, UnspentSize);
+ }
+ Head = Buffer + UnspentSize;
+ }
+}
+uint32 Writer_GetControlPort()
+{
+ return GControlPort;
+}
+void Writer_UpdateControl()
+{
+ switch (GControlState)
+ {
+ case EControlState::Closed:
+ if (!Writer_ControlListen())
+ {
+ break;
+ }
+ /* [[fallthrough]] */
+ case EControlState::Listening:
+ if (!Writer_ControlAccept())
+ {
+ break;
+ }
+ /* [[fallthrough]] */
+ case EControlState::Accepted:
+ Writer_ControlRecv();
+ break;
+ }
+}
+void Writer_InitializeControl()
+{
+#if PLATFORM_SWITCH
+ GControlState = EControlState::Failed;
+ return;
+#endif
+ Writer_ControlAddCommand("SendTo", nullptr,
+ [] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
+ {
+ if (ArgC > 0)
+ {
+ Writer_SendTo(ArgV[0]);
+ }
+ }
+ );
+ Writer_ControlAddCommand("WriteTo", nullptr,
+ [] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
+ {
+ if (ArgC > 0)
+ {
+ Writer_WriteTo(ArgV[0]);
+ }
+ }
+ );
+ Writer_ControlAddCommand("Stop", nullptr,
+ [] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
+ {
+ Writer_Stop();
+ }
+ );
+ Writer_ControlAddCommand("ToggleChannels", nullptr,
+ [] (void*, uint32 ArgC, ANSICHAR const* const* ArgV)
+ {
+ if (ArgC < 2)
+ {
+ return;
+ }
+ const size_t BufferSize = 512;
+ ANSICHAR Channels[BufferSize] = {};
+ ANSICHAR* Ctx;
+ const bool bState = (ArgV[1][0] != '0');
+ FCStringAnsi::Strcpy(Channels, BufferSize, ArgV[0]);
+ ANSICHAR* Channel = FCStringAnsi::Strtok(Channels, ",", &Ctx);
+ while (Channel)
+ {
+ FChannel::Toggle(Channel, bState);
+ Channel = FCStringAnsi::Strtok(nullptr, ",", &Ctx);
+ }
+ }
+ );
+}
+void Writer_ShutdownControl()
+{
+ if (GControlListen)
+ {
+ IoClose(GControlListen);
+ GControlListen = 0;
+ }
+}
+#else
+void Writer_InitializeControl() {}
+void Writer_ShutdownControl() {}
+void Writer_UpdateControl() {}
+uint32 Writer_GetControlPort() { return ~0u; }
+#endif // TRACE_PRIVATE_CONTROL_ENABLED
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 EventNode.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+void Writer_InternalInitialize();
+FEventNode* volatile GNewEventList; // = nullptr;
+FEventNode* GEventListHead;// = nullptr;
+FEventNode* GEventListTail;// = nullptr;
+const FEventNode* FEventNode::FIter::GetNext()
+{
+ auto* Ret = (FEventNode*)Inner;
+ if (Ret != nullptr)
+ {
+ Inner = Ret->Next;
+ if (Inner == nullptr)
+ {
+ GEventListTail = Ret;
+ }
+ }
+ return Ret;
+}
+FEventNode::FIter FEventNode::ReadNew()
+{
+ FEventNode* EventList = AtomicExchangeAcquire(&GNewEventList, (FEventNode*)nullptr);
+ if (EventList == nullptr)
+ {
+ return {};
+ }
+ if (GEventListHead == nullptr)
+ {
+ GEventListHead = EventList;
+ }
+ else
+ {
+ GEventListTail->Next = EventList;
+ }
+ return { EventList };
+}
+uint32 FEventNode::Initialize(const FEventInfo* InInfo)
+{
+ if (Uid != 0)
+ {
+ return Uid;
+ }
+ Writer_InternalInitialize();
+ static uint32 volatile EventUidCounter; // = 0;
+ uint32 NewUid = AtomicAddRelaxed(&EventUidCounter, 1u) + EKnownEventUids::User;
+ if (NewUid >= uint32(EKnownEventUids::Max))
+ {
+ return Uid = EKnownEventUids::Invalid;
+ }
+ uint32 UidFlags = 0;
+ if (NewUid >= (1 << (8 - EKnownEventUids::_UidShift)))
+ {
+ UidFlags |= EKnownEventUids::Flag_TwoByteUid;
+ }
+ NewUid <<= EKnownEventUids::_UidShift;
+ NewUid |= UidFlags;
+ Info = InInfo;
+ Uid = uint16(NewUid);
+ for (;; PlatformYield())
+ {
+ Next = AtomicLoadRelaxed(&GNewEventList);
+ if (AtomicCompareExchangeRelease(&GNewEventList, this, Next))
+ {
+ break;
+ }
+ }
+ return Uid;
+}
+void FEventNode::Describe() const
+{
+ const FLiteralName& LoggerName = Info->LoggerName;
+ const FLiteralName& EventName = Info->EventName;
+ uint32 NamesSize = LoggerName.Length + EventName.Length;
+ for (uint32 i = 0; i < Info->FieldCount; ++i)
+ {
+ NamesSize += Info->Fields[i].NameSize;
+ }
+ uint32 EventSize = sizeof(FNewEventEvent);
+ EventSize += sizeof(FNewEventEvent::Fields[0]) * Info->FieldCount;
+ EventSize += NamesSize;
+ FLogScope LogScope = FLogScope::EnterImpl<FEventInfo::Flag_NoSync>(0, EventSize + sizeof(uint16));
+ auto* Ptr = (uint16*)(LogScope.GetPointer());
+ Ptr[-1] = EKnownEventUids::NewEvent; // Make event look like an important one. Ideally they are sent
+ Ptr[ 0] = uint16(EventSize); // as important and not Writer_DescribeEvents()'s redirected buf.
+ auto& Event = *(FNewEventEvent*)(Ptr + 1);
+ Event.EventUid = uint16(Uid) >> EKnownEventUids::_UidShift;
+ Event.LoggerNameSize = LoggerName.Length;
+ Event.EventNameSize = EventName.Length;
+ Event.Flags = 0;
+ uint32 Flags = Info->Flags;
+ if (Flags & FEventInfo::Flag_Important) Event.Flags |= uint8(EEventFlags::Important);
+ if (Flags & FEventInfo::Flag_MaybeHasAux) Event.Flags |= uint8(EEventFlags::MaybeHasAux);
+ if (Flags & FEventInfo::Flag_NoSync) Event.Flags |= uint8(EEventFlags::NoSync);
+ Event.FieldCount = uint8(Info->FieldCount);
+ for (uint32 i = 0; i < Info->FieldCount; ++i)
+ {
+ const FFieldDesc& Field = Info->Fields[i];
+ auto& Out = Event.Fields[i];
+ Out.Offset = Field.ValueOffset;
+ Out.Size = Field.ValueSize;
+ Out.TypeInfo = Field.TypeInfo;
+ Out.NameSize = Field.NameSize;
+ }
+ uint8* Cursor = (uint8*)(Event.Fields + Info->FieldCount);
+ auto WriteName = [&Cursor] (const ANSICHAR* Data, uint32 Size)
+ {
+ memcpy(Cursor, Data, Size);
+ Cursor += Size;
+ };
+ WriteName(LoggerName.Ptr, LoggerName.Length);
+ WriteName(EventName.Ptr, EventName.Length);
+ for (uint32 i = 0; i < Info->FieldCount; ++i)
+ {
+ const FFieldDesc& Field = Info->Fields[i];
+ WriteName(Field.Name, Field.NameSize);
+ }
+ LogScope.Commit();
+}
+void FEventNode::OnConnect()
+{
+ if (GEventListHead == nullptr)
+ {
+ return;
+ }
+ GEventListTail->Next = AtomicExchangeAcquire(&GNewEventList, GEventListHead);
+ GEventListHead = GEventListTail = nullptr;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Field.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+template <typename CallbackType>
+static void Field_WriteAuxData(uint32 Index, int32 Size, CallbackType&& Callback)
+{
+ static_assert(
+ sizeof(Private::FWriteBuffer::Overflow) >= sizeof(FAuxHeader) + sizeof(uint8 /*AuxDataTerminal*/),
+ "FWriteBuffer::Overflow is not large enough"
+ );
+ if (Size == 0)
+ {
+ return;
+ }
+ FWriteBuffer* Buffer = Writer_GetBuffer();
+ auto* Header = (FAuxHeader*)(Buffer->Cursor);
+ Header->Pack = Size << FAuxHeader::SizeShift;
+ Header->Pack |= Index << FAuxHeader::FieldShift;
+ Header->Uid = uint8(EKnownEventUids::AuxData) << EKnownEventUids::_UidShift;
+ Buffer->Cursor += sizeof(FAuxHeader);
+ bool bCommit = ((uint8*)Header == Buffer->Committed);
+ while (true)
+ {
+ if (Buffer->Cursor >= (uint8*)Buffer)
+ {
+ if (bCommit)
+ {
+ AtomicStoreRelease(&(uint8* volatile&)(Buffer->Committed), Buffer->Cursor);
+ }
+ Buffer = Writer_NextBuffer(0);
+ Buffer->Partial = 1;
+ bCommit = true;
+ }
+ int32 Remaining = int32((uint8*)Buffer - Buffer->Cursor);
+ int32 SegmentSize = (Remaining < Size) ? Remaining : Size;
+ Callback(Buffer->Cursor, SegmentSize);
+ Buffer->Cursor += SegmentSize;
+ Size -= SegmentSize;
+ if (Size <= 0)
+ {
+ break;
+ }
+ }
+ if (bCommit)
+ {
+ AtomicStoreRelease(&(uint8* volatile&)(Buffer->Committed), Buffer->Cursor);
+ }
+}
+void Field_WriteAuxData(uint32 Index, const uint8* Data, int32 Size)
+{
+ auto MemcpyLambda = [&Data] (uint8* Cursor, int32 NumBytes)
+ {
+ memcpy(Cursor, Data, NumBytes);
+ Data += NumBytes;
+ };
+ return Field_WriteAuxData(Index, Size, MemcpyLambda);
+}
+void Field_WriteStringAnsi(uint32 Index, const WIDECHAR* String, int32 Length)
+{
+ int32 Size = Length;
+ Size &= (FAuxHeader::SizeLimit - 1);
+ auto WriteLambda = [&String] (uint8* Cursor, int32 NumBytes)
+ {
+ for (int32 i = 0; i < NumBytes; ++i)
+ {
+ *Cursor = uint8(*String & 0x7f);
+ Cursor++;
+ String++;
+ }
+ };
+ return Field_WriteAuxData(Index, Size, WriteLambda);
+}
+void Field_WriteStringAnsi(uint32 Index, const ANSICHAR* String, int32 Length)
+{
+ int32 Size = Length * sizeof(String[0]);
+ Size &= (FAuxHeader::SizeLimit - 1); // a very crude "clamp"
+ return Field_WriteAuxData(Index, (const uint8*)String, Size);
+}
+void Field_WriteStringWide(uint32 Index, const WIDECHAR* String, int32 Length)
+{
+ int32 Size = Length * sizeof(String[0]);
+ Size &= (FAuxHeader::SizeLimit - 1); // (see above)
+ return Field_WriteAuxData(Index, (const uint8*)String, Size);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Tail.cpp */
+
+#if UE_TRACE_ENABLED
+#include <string.h>
+#include <type_traits>
+#include <initializer_list>
+namespace UE {
+namespace Trace {
+namespace Private {
+static_assert(ETransport::Active == ETransport::TidPacketSync, "Tail-tracing is transport aware");
+uint32 GetEncodeMaxSize(uint32);
+int32 Encode(const void*, int32, void*, int32);
+void* Writer_MemoryAllocate(SIZE_T, uint32);
+void Writer_MemoryFree(void*, uint32);
+void Writer_SendData(uint32, uint8* __restrict, uint32);
+void Writer_SendDataRaw(const void*, uint32);
+class FPacketRing
+{
+public:
+ struct FRange
+ {
+ const void* Data;
+ uint32 Size;
+ };
+ void Initialize(uint32 InSize);
+ void Shutdown();
+ void Reset();
+ uint32 GetSize() const;
+ bool IsActive() const;
+ FRange GetBackPackets() const;
+ FRange GetFrontPackets() const;
+ template <typename CallbackType>
+ void IterateRanges(CallbackType&& Callback);
+ template <typename PacketType>
+ PacketType* Append(uint32 InSize);
+ void BackUp(uint32 InSize);
+private:
+ FTidPacketBase* AppendImpl(uint32 InSize);
+ uint8* Data;
+ uint32 Size;
+ uint32 Cursor;
+ uint32 Left;
+ uint32 Right;
+};
+static_assert(std::is_trivial<FPacketRing>(), "FPacketRing must be trivial");
+void FPacketRing::Initialize(uint32 InSize)
+{
+ Data = (uint8*)Writer_MemoryAllocate(InSize, 16);
+ Size = InSize;
+ Reset();
+}
+void FPacketRing::Shutdown()
+{
+ Writer_MemoryFree(Data, Size);
+ Data = nullptr;
+}
+void FPacketRing::Reset()
+{
+ Cursor = 0;
+ Left = Right = Size;
+}
+uint32 FPacketRing::GetSize() const
+{
+ return Size;
+}
+bool FPacketRing::IsActive() const
+{
+ return Data != nullptr;
+}
+FPacketRing::FRange FPacketRing::GetBackPackets() const
+{
+ return { Data + Left, Right - Left };
+}
+FPacketRing::FRange FPacketRing::GetFrontPackets() const
+{
+ return { Data, Cursor };
+}
+template <typename CallbackType>
+void FPacketRing::IterateRanges(CallbackType&& Callback)
+{
+ FPacketRing::FRange Ranges[] = { GetBackPackets(), GetFrontPackets() };
+ for (const auto& Range : Ranges)
+ {
+ if (Range.Size == 0)
+ {
+ continue;
+ }
+ Callback(Range);
+ }
+}
+template <typename PacketType>
+PacketType* FPacketRing::Append(uint32 InSize)
+{
+ FTidPacketBase* Ptr = AppendImpl(InSize + sizeof(PacketType));
+ return static_cast<PacketType*>(Ptr);
+}
+void FPacketRing::BackUp(uint32 InSize)
+{
+ Cursor -= InSize;
+}
+FTidPacketBase* FPacketRing::AppendImpl(uint32 InSize)
+{
+ if (UNLIKELY(InSize > Size))
+ {
+ Reset();
+ return nullptr;
+ }
+ uint32 NextCursor = Cursor + InSize;
+ if (UNLIKELY(NextCursor > Size))
+ {
+ Left = 0;
+ Right = Cursor;
+ Cursor = 0;
+ NextCursor = InSize;
+ }
+ while (true)
+ {
+ if (LIKELY(Left >= NextCursor))
+ {
+ break;
+ }
+ if (UNLIKELY(Left >= Right))
+ {
+ break;
+ }
+ const auto* TidPacket = (const FTidPacketBase*)(Data + Left);
+ Left += TidPacket->PacketSize;
+ }
+ auto* TidPacket = (FTidPacketBase*)(Data + Cursor);
+ TidPacket->PacketSize = uint16(InSize);
+ Cursor = NextCursor;
+ return TidPacket;
+}
+static FPacketRing GPacketRing; // = {};
+void Writer_TailAppend(uint32 ThreadId, uint8* __restrict Data, uint32 Size, bool bPartial)
+{
+ if (!GPacketRing.IsActive())
+ {
+ return Writer_SendData(ThreadId, Data, Size);
+ }
+ if (uint32(Size + sizeof(FTidPacketEncoded)) > GPacketRing.GetSize())
+ {
+ GPacketRing.Reset();
+ return Writer_SendData(ThreadId, Data, Size);
+ }
+ ThreadId &= FTidPacketBase::ThreadIdMask;
+ ThreadId |= bPartial ? FTidPacketBase::PartialMarker : 0;
+ if (Size <= 384)
+ {
+ auto* Packet = GPacketRing.Append<FTidPacket>(Size);
+ Packet->ThreadId = uint16(ThreadId);
+ ::memcpy(Packet->Data, Data, Size);
+ Writer_SendDataRaw(Packet, Packet->PacketSize);
+ return;
+ }
+ uint32 EncodeMaxSize = GetEncodeMaxSize(Size);
+ auto* Packet = GPacketRing.Append<FTidPacketEncoded>(EncodeMaxSize);
+ Packet->ThreadId = uint16(ThreadId);
+ Packet->ThreadId |= FTidPacketBase::EncodedMarker;
+ Packet->DecodedSize = uint16(Size);
+ uint32 EncodeSize = Encode(Data, Size, Packet->Data, EncodeMaxSize);
+ uint32 BackUp = EncodeMaxSize - EncodeSize;
+ GPacketRing.BackUp(BackUp);
+ Packet->PacketSize -= uint16(BackUp);
+ Writer_SendDataRaw(Packet, Packet->PacketSize);
+}
+void Writer_TailOnConnect()
+{
+ if (!GPacketRing.IsActive())
+ {
+ return;
+ }
+ GPacketRing.IterateRanges([] (const FPacketRing::FRange& Range)
+ {
+ Writer_SendDataRaw(Range.Data, Range.Size);
+ });
+}
+void Writer_InitializeTail(int32 BufferSize)
+{
+#if defined(STRESS_PACKET_RING)
+ static void StressRingPacket();
+ StressRingPacket();
+#endif
+ if (BufferSize <= 0)
+ {
+ return;
+ }
+ uint32 Rounding = (1 << 10) - 1;
+ BufferSize = (BufferSize + Rounding) & ~Rounding;
+ if (BufferSize < (128 << 10))
+ {
+ BufferSize = 128 << 10;
+ }
+ GPacketRing.Initialize(BufferSize);
+}
+void Writer_ShutdownTail()
+{
+ GPacketRing.Shutdown();
+}
+#if defined(STRESS_PACKET_RING)
+static void StressRingPacket()
+{
+ FPacketRing Ring;
+ Ring.Initialize(300);
+ uint32 Bits = 0x0493'0493;
+ for (int32 i = 0; i < 1024; ++i)
+ {
+ FTidPacket* Packet = Ring.Append<FTidPacket>((Bits & 0x1f) + 6);
+ Packet->ThreadId = i;
+ if ((Bits & 0x15) == 0)
+ {
+ Packet->ThreadId |= FTidPacketBase::PartialMarker;
+ }
+ Ring.IterateRanges([] (const FPacketRing::FRange&)
+ {
+ /* nop */
+ });
+ Bits = (Bits ^ 0xa93a'93a9) * 0x0493;
+ }
+ for (int32 i = 7; i < 448; i += 67)
+ {
+ if (auto* Packet = Ring.Append<FTidPacket>(i))
+ {
+ Packet->ThreadId = 0;
+ }
+ }
+}
+#endif // STRESS_PACKET_RING
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/*
+FPacketRing ring-buffers packets. Internally the buffer is divided up into two
+ranges; [0-Cursor) and [Left-Right) which are initially empty;
+ 0 L
+ C----------------------------------------------------------------------R
+A packet consists of a size and a opaque blob of data. Reading the sizes allows
+one to stride through the packets.
+ L
+ 0[SZ]==============>[SZ]=============>[SZ]=======>C--------------------R
+Eventually the next packet will not fit in the buffer because the next cursor (N)
+is off the buffer's end;
+ L
+ 0[SZ]==============>[SZ]=============>[SZ]=======>[SZ]==========>C-----R
+ [SZ]========>N
+When this happens the 0-Cursor range is transferred to Left-Right and the 0-Cursor
+range is set such that it can contain the new packet being added.
+ L[SZ]==============>[SZ]=============>[SZ]=======>[SZ]==========>R-----|
+ 0[SZ]========>C
+The two ranges now overlap so packets are then removed from Left until there is
+enough space for the new packet.
+ 0[SZ]========>C-----L[SZ]============>[SZ]=======>[SZ]==========>R-----|
+The Left-Right range has the oldest packets. Left will eventually advance to meet
+Right at which point the Left-Right range becomes empty The process above repeats
+as if the buffer was being filled for the first time.
+*/
+/* {{{1 TlsBuffer.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+void Writer_TailAppend(uint32, uint8* __restrict, uint32, bool);
+FWriteBuffer* Writer_AllocateBlockFromPool();
+uint32 Writer_GetThreadId();
+void Writer_FreeBlockListToPool(FWriteBuffer*, FWriteBuffer*);
+extern uint64 GStartCycle;
+extern FStatistics GTraceStatistics;
+UE_TRACE_EVENT_BEGIN($Trace, ThreadTiming, NoSync)
+ UE_TRACE_EVENT_FIELD(uint64, BaseTimestamp)
+UE_TRACE_EVENT_END()
+#define T_ALIGN alignas(PLATFORM_CACHE_LINE_SIZE)
+static FWriteBuffer GNullWriteBuffer = { {}, 0, 0, nullptr, nullptr, (uint8*)&GNullWriteBuffer };
+thread_local FWriteBuffer* GTlsWriteBuffer = &GNullWriteBuffer;
+static FWriteBuffer* __restrict GActiveThreadList; // = nullptr;
+T_ALIGN static FWriteBuffer* volatile GNewThreadList; // = nullptr;
+#undef T_ALIGN
+#if !IS_MONOLITHIC
+TRACELOG_API FWriteBuffer* Writer_GetBuffer()
+{
+ return GTlsWriteBuffer;
+}
+#endif
+static FWriteBuffer* Writer_NextBufferInternal()
+{
+ FWriteBuffer* NextBuffer = Writer_AllocateBlockFromPool();
+ NextBuffer->Cursor = (uint8*)NextBuffer - NextBuffer->Size;
+ NextBuffer->Committed = NextBuffer->Cursor;
+ NextBuffer->Reaped = NextBuffer->Cursor;
+ NextBuffer->EtxOffset = 0 - int32(sizeof(FWriteBuffer));
+ NextBuffer->NextBuffer = nullptr;
+ FWriteBuffer* CurrentBuffer = GTlsWriteBuffer;
+ if (CurrentBuffer == &GNullWriteBuffer)
+ {
+ NextBuffer->ThreadId = uint16(Writer_GetThreadId());
+ NextBuffer->PrevTimestamp = TimeGetTimestamp();
+ NextBuffer->Partial = 0;
+ GTlsWriteBuffer = NextBuffer;
+ UE_TRACE_LOG($Trace, ThreadTiming, TraceLogChannel)
+ << ThreadTiming.BaseTimestamp(NextBuffer->PrevTimestamp - GStartCycle);
+ for (;; PlatformYield())
+ {
+ NextBuffer->NextThread = AtomicLoadRelaxed(&GNewThreadList);
+ if (AtomicCompareExchangeRelease(&GNewThreadList, NextBuffer, NextBuffer->NextThread))
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ CurrentBuffer->NextBuffer = NextBuffer;
+ NextBuffer->ThreadId = CurrentBuffer->ThreadId;
+ NextBuffer->PrevTimestamp = CurrentBuffer->PrevTimestamp;
+ NextBuffer->Partial = 0;
+ GTlsWriteBuffer = NextBuffer;
+ int32 EtxOffset = int32(PTRINT((uint8*)(CurrentBuffer) - CurrentBuffer->Cursor));
+ AtomicStoreRelease(&(CurrentBuffer->EtxOffset), EtxOffset);
+ }
+ return NextBuffer;
+}
+TRACELOG_API FWriteBuffer* Writer_NextBuffer(int32 Size)
+{
+ FWriteBuffer* CurrentBuffer = GTlsWriteBuffer;
+ if (CurrentBuffer != &GNullWriteBuffer)
+ {
+ CurrentBuffer->Cursor -= Size;
+ }
+ FWriteBuffer* NextBuffer = Writer_NextBufferInternal();
+ if (Size >= NextBuffer->Size)
+ {
+ return nullptr;
+ }
+ NextBuffer->Cursor += Size;
+ return NextBuffer;
+}
+static bool Writer_DrainBuffer(uint32 ThreadId, FWriteBuffer* Buffer)
+{
+ uint8* Committed = AtomicLoadRelaxed((uint8**)&Buffer->Committed);
+ if (uint32 SizeToReap = uint32(Committed - Buffer->Reaped))
+ {
+#if TRACE_PRIVATE_STATISTICS
+ GTraceStatistics.BytesTraced += SizeToReap;
+#endif
+ bool bPartial = (Buffer->Partial == 1);
+ bPartial &= UPTRINT(Buffer->Reaped + Buffer->Size) == UPTRINT(Buffer);
+ Writer_TailAppend(ThreadId, Buffer->Reaped, SizeToReap, bPartial);
+ Buffer->Reaped = Committed;
+ }
+ int32 EtxOffset = AtomicLoadAcquire(&Buffer->EtxOffset);
+ return ((uint8*)Buffer - EtxOffset) > Committed;
+}
+void Writer_DrainBuffers()
+{
+ struct FRetireList
+ {
+ FWriteBuffer* __restrict Head = nullptr;
+ FWriteBuffer* __restrict Tail = nullptr;
+ void Insert(FWriteBuffer* __restrict Buffer)
+ {
+ Buffer->NextBuffer = Head;
+ Head = Buffer;
+ Tail = (Tail != nullptr) ? Tail : Head;
+ }
+ };
+ FWriteBuffer* __restrict NewThreadList = AtomicExchangeAcquire(&GNewThreadList, (FWriteBuffer*)nullptr);
+ FWriteBuffer* __restrict NewThreadCursor = NewThreadList;
+ NewThreadList = nullptr;
+ while (NewThreadCursor != nullptr)
+ {
+ FWriteBuffer* __restrict NextThread = NewThreadCursor->NextThread;
+ NewThreadCursor->NextThread = NewThreadList;
+ NewThreadList = NewThreadCursor;
+ NewThreadCursor = NextThread;
+ }
+ FRetireList RetireList;
+ FWriteBuffer* __restrict ActiveThreadList = GActiveThreadList;
+ GActiveThreadList = nullptr;
+ for (FWriteBuffer* __restrict Buffer : { ActiveThreadList, NewThreadList })
+ {
+ for (FWriteBuffer* __restrict NextThread; Buffer != nullptr; Buffer = NextThread)
+ {
+ NextThread = Buffer->NextThread;
+ uint32 ThreadId = Buffer->ThreadId;
+ for (FWriteBuffer* __restrict NextBuffer; Buffer != nullptr; Buffer = NextBuffer)
+ {
+ if (Writer_DrainBuffer(ThreadId, Buffer))
+ {
+ break;
+ }
+ NextBuffer = Buffer->NextBuffer;
+ RetireList.Insert(Buffer);
+ }
+ if (Buffer != nullptr)
+ {
+ Buffer->NextThread = GActiveThreadList;
+ GActiveThreadList = Buffer;
+ }
+ }
+ }
+ if (RetireList.Head != nullptr)
+ {
+ Writer_FreeBlockListToPool(RetireList.Head, RetireList.Tail);
+ }
+}
+void Writer_EndThreadBuffer()
+{
+ if (GTlsWriteBuffer == &GNullWriteBuffer)
+ {
+ return;
+ }
+ int32 EtxOffset = int32(PTRINT((uint8*)GTlsWriteBuffer - GTlsWriteBuffer->Cursor));
+ AtomicStoreRelaxed(&(GTlsWriteBuffer->EtxOffset), EtxOffset);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 Trace.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private
+{
+void Writer_MemorySetHooks(AllocFunc, FreeFunc);
+void Writer_Initialize(const FInitializeDesc&);
+void Writer_Shutdown();
+void Writer_Update();
+bool Writer_SendTo(const ANSICHAR*, uint32);
+bool Writer_WriteTo(const ANSICHAR*);
+bool Writer_IsTracing();
+bool Writer_Stop();
+uint32 Writer_GetThreadId();
+extern FStatistics GTraceStatistics;
+} // namespace Private
+template <int DestSize, typename SRC_TYPE>
+static uint32 ToAnsiCheap(ANSICHAR (&Dest)[DestSize], const SRC_TYPE* Src)
+{
+ const SRC_TYPE* Cursor = Src;
+ for (ANSICHAR& Out : Dest)
+ {
+ Out = ANSICHAR(*Cursor++ & 0x7f);
+ if (Out == '\0')
+ {
+ break;
+ }
+ }
+ Dest[DestSize - 1] = '\0';
+ return uint32(UPTRINT(Cursor - Src));
+};
+void SetMemoryHooks(AllocFunc Alloc, FreeFunc Free)
+{
+ Private::Writer_MemorySetHooks(Alloc, Free);
+}
+void Initialize(const FInitializeDesc& Desc)
+{
+ Private::Writer_Initialize(Desc);
+ FChannel::Initialize();
+}
+void Shutdown()
+{
+ Private::Writer_Shutdown();
+}
+void Update()
+{
+ Private::Writer_Update();
+}
+void GetStatistics(FStatistics& Out)
+{
+ Out = Private::GTraceStatistics;
+}
+bool SendTo(const TCHAR* InHost, uint32 Port)
+{
+ char Host[256];
+ ToAnsiCheap(Host, InHost);
+ return Private::Writer_SendTo(Host, Port);
+}
+bool WriteTo(const TCHAR* InPath)
+{
+ char Path[512];
+ ToAnsiCheap(Path, InPath);
+ return Private::Writer_WriteTo(Path);
+}
+bool IsTracing()
+{
+ return Private::Writer_IsTracing();
+}
+bool Stop()
+{
+ return Private::Writer_Stop();
+}
+bool IsChannel(const TCHAR* ChannelName)
+{
+ ANSICHAR ChannelNameA[64];
+ ToAnsiCheap(ChannelNameA, ChannelName);
+ return FChannel::FindChannel(ChannelNameA) != nullptr;
+}
+bool ToggleChannel(const TCHAR* ChannelName, bool bEnabled)
+{
+ ANSICHAR ChannelNameA[64];
+ ToAnsiCheap(ChannelNameA, ChannelName);
+ return FChannel::Toggle(ChannelNameA, bEnabled);
+}
+UE_TRACE_CHANNEL_EXTERN(TraceLogChannel)
+UE_TRACE_EVENT_BEGIN($Trace, ThreadInfo, NoSync|Important)
+ UE_TRACE_EVENT_FIELD(uint32, ThreadId)
+ UE_TRACE_EVENT_FIELD(uint32, SystemId)
+ UE_TRACE_EVENT_FIELD(int32, SortHint)
+ UE_TRACE_EVENT_FIELD(AnsiString, Name)
+UE_TRACE_EVENT_END()
+UE_TRACE_EVENT_BEGIN($Trace, ThreadGroupBegin, NoSync|Important)
+ UE_TRACE_EVENT_FIELD(AnsiString, Name)
+UE_TRACE_EVENT_END()
+UE_TRACE_EVENT_BEGIN($Trace, ThreadGroupEnd, NoSync|Important)
+UE_TRACE_EVENT_END()
+void ThreadRegister(const TCHAR* Name, uint32 SystemId, int32 SortHint)
+{
+ ANSICHAR NameA[96];
+ uint32 ThreadId = Private::Writer_GetThreadId();
+ uint32 NameLen = ToAnsiCheap(NameA, Name);
+ UE_TRACE_LOG($Trace, ThreadInfo, TraceLogChannel, NameLen * sizeof(ANSICHAR))
+ << ThreadInfo.ThreadId(ThreadId)
+ << ThreadInfo.SystemId(SystemId)
+ << ThreadInfo.SortHint(SortHint)
+ << ThreadInfo.Name(NameA, NameLen);
+}
+void ThreadGroupBegin(const TCHAR* Name)
+{
+ ANSICHAR NameA[96];
+ uint32 NameLen = ToAnsiCheap(NameA, Name);
+ UE_TRACE_LOG($Trace, ThreadGroupBegin, TraceLogChannel, NameLen * sizeof(ANSICHAR))
+ << ThreadGroupBegin.Name(Name, NameLen);
+}
+void ThreadGroupEnd()
+{
+ UE_TRACE_LOG($Trace, ThreadGroupEnd, TraceLogChannel);
+}
+} // namespace Trace
+} // namespace UE
+#else
+TRACELOG_API int TraceLogExportedSymbol = 0;
+#endif // UE_TRACE_ENABLED
+/* {{{1 Writer.cpp */
+
+#if UE_TRACE_ENABLED
+#include <limits.h>
+#include <stdlib.h>
+#if PLATFORM_WINDOWS
+# define TRACE_PRIVATE_STOMP 0 // 1=overflow, 2=underflow
+# if TRACE_PRIVATE_STOMP
+# endif
+#else
+# define TRACE_PRIVATE_STOMP 0
+#endif
+#ifndef TRACE_PRIVATE_BUFFER_SEND
+# define TRACE_PRIVATE_BUFFER_SEND 0
+#endif
+namespace UE {
+namespace Trace {
+namespace Private {
+int32 Encode(const void*, int32, void*, int32);
+void Writer_SendData(uint32, uint8* __restrict, uint32);
+void Writer_InitializeTail(int32);
+void Writer_ShutdownTail();
+void Writer_TailOnConnect();
+void Writer_InitializeSharedBuffers();
+void Writer_ShutdownSharedBuffers();
+void Writer_UpdateSharedBuffers();
+void Writer_InitializeCache();
+void Writer_ShutdownCache();
+void Writer_CacheOnConnect();
+void Writer_InitializePool();
+void Writer_ShutdownPool();
+void Writer_DrainBuffers();
+void Writer_EndThreadBuffer();
+uint32 Writer_GetControlPort();
+void Writer_UpdateControl();
+void Writer_InitializeControl();
+void Writer_ShutdownControl();
+bool Writer_IsTracing();
+UE_TRACE_EVENT_BEGIN($Trace, NewTrace, Important|NoSync)
+ UE_TRACE_EVENT_FIELD(uint64, StartCycle)
+ UE_TRACE_EVENT_FIELD(uint64, CycleFrequency)
+ UE_TRACE_EVENT_FIELD(uint16, Endian)
+ UE_TRACE_EVENT_FIELD(uint8, PointerSize)
+UE_TRACE_EVENT_END()
+static bool GInitialized; // = false;
+FStatistics GTraceStatistics; // = {};
+uint64 GStartCycle; // = 0;
+TRACELOG_API uint32 volatile GLogSerial; // = 0;
+static uint32 GUpdateCounter; // = 0;
+struct FWriteTlsContext
+{
+ ~FWriteTlsContext();
+ uint32 GetThreadId();
+private:
+ uint32 ThreadId = 0;
+};
+FWriteTlsContext::~FWriteTlsContext()
+{
+ if (GInitialized)
+ {
+ Writer_EndThreadBuffer();
+ }
+}
+uint32 FWriteTlsContext::GetThreadId()
+{
+ if (ThreadId)
+ {
+ return ThreadId;
+ }
+ static uint32 volatile Counter;
+ ThreadId = AtomicAddRelaxed(&Counter, 1u) + ETransportTid::Bias;
+ return ThreadId;
+}
+thread_local FWriteTlsContext GTlsContext;
+uint32 Writer_GetThreadId()
+{
+ return GTlsContext.GetThreadId();
+}
+void* (*AllocHook)(SIZE_T, uint32); // = nullptr
+void (*FreeHook)(void*, SIZE_T); // = nullptr
+void Writer_MemorySetHooks(decltype(AllocHook) Alloc, decltype(FreeHook) Free)
+{
+ AllocHook = Alloc;
+ FreeHook = Free;
+}
+void* Writer_MemoryAllocate(SIZE_T Size, uint32 Alignment)
+{
+ TWriteBufferRedirect<1 << 10> TraceData;
+ void* Ret = nullptr;
+#if TRACE_PRIVATE_STOMP
+ static uint8* Base;
+ if (Base == nullptr)
+ {
+ Base = (uint8*)VirtualAlloc(0, 1ull << 40, MEM_RESERVE, PAGE_READWRITE);
+ }
+ static SIZE_T PageSize = 4096;
+ Base += PageSize;
+ uint8* NextBase = Base + ((PageSize - 1 + Size) & ~(PageSize - 1));
+ VirtualAlloc(Base, SIZE_T(NextBase - Base), MEM_COMMIT, PAGE_READWRITE);
+#if TRACE_PRIVATE_STOMP == 1
+ Ret = NextBase - Size;
+#elif TRACE_PRIVATE_STOMP == 2
+ Ret = Base;
+#endif
+ Base = NextBase;
+#else // TRACE_PRIVATE_STOMP
+ if (AllocHook != nullptr)
+ {
+ Ret = AllocHook(Size, Alignment);
+ }
+ else
+ {
+#if defined(_MSC_VER)
+ Ret = _aligned_malloc(Size, Alignment);
+#elif (defined(__ANDROID_API__) && __ANDROID_API__ < 28) || defined(__APPLE__)
+ posix_memalign(&Ret, Alignment, Size);
+#else
+ Ret = aligned_alloc(Alignment, Size);
+#endif
+ }
+#endif // TRACE_PRIVATE_STOMP
+#if TRACE_PRIVATE_STATISTICS
+ AtomicAddRelaxed(&GTraceStatistics.MemoryUsed, uint64(Size));
+#endif
+ return Ret;
+}
+void Writer_MemoryFree(void* Address, uint32 Size)
+{
+#if TRACE_PRIVATE_STOMP
+ if (Address == nullptr)
+ {
+ return;
+ }
+ *(uint8*)Address = 0xfe;
+ MEMORY_BASIC_INFORMATION MemInfo;
+ VirtualQuery(Address, &MemInfo, sizeof(MemInfo));
+ DWORD Unused;
+ VirtualProtect(MemInfo.BaseAddress, MemInfo.RegionSize, PAGE_READONLY, &Unused);
+#else // TRACE_PRIVATE_STOMP
+ TWriteBufferRedirect<1 << 10> TraceData;
+ if (FreeHook != nullptr)
+ {
+ FreeHook(Address, Size);
+ }
+ else
+ {
+#if defined(_MSC_VER)
+ _aligned_free(Address);
+#else
+ free(Address);
+#endif
+ }
+#endif // TRACE_PRIVATE_STOMP
+#if TRACE_PRIVATE_STATISTICS
+ AtomicAddRelaxed(&GTraceStatistics.MemoryUsed, uint64(-int64(Size)));
+#endif
+}
+static UPTRINT GDataHandle; // = 0
+UPTRINT GPendingDataHandle; // = 0
+#if TRACE_PRIVATE_BUFFER_SEND
+static const SIZE_T GSendBufferSize = 1 << 20; // 1Mb
+uint8* GSendBuffer; // = nullptr;
+uint8* GSendBufferCursor; // = nullptr;
+static bool Writer_FlushSendBuffer()
+{
+ if( GSendBufferCursor > GSendBuffer )
+ {
+ if (!IoWrite(GDataHandle, GSendBuffer, GSendBufferCursor - GSendBuffer))
+ {
+ IoClose(GDataHandle);
+ GDataHandle = 0;
+ return false;
+ }
+ GSendBufferCursor = GSendBuffer;
+ }
+ return true;
+}
+#else
+static bool Writer_FlushSendBuffer() { return true; }
+#endif
+static void Writer_SendDataImpl(const void* Data, uint32 Size)
+{
+#if TRACE_PRIVATE_STATISTICS
+ GTraceStatistics.BytesSent += Size;
+#endif
+#if TRACE_PRIVATE_BUFFER_SEND
+ if (GSendBufferCursor + Size > GSendBuffer + GSendBufferSize)
+ {
+ if (!Writer_FlushSendBuffer())
+ {
+ return;
+ }
+ }
+ if (Size > GSendBufferSize)
+ {
+ if (!IoWrite(GDataHandle, Data, Size))
+ {
+ IoClose(GDataHandle);
+ GDataHandle = 0;
+ }
+ }
+ else
+ {
+ memcpy(GSendBufferCursor, Data, Size);
+ GSendBufferCursor += Size;
+ }
+#else
+ if (!IoWrite(GDataHandle, Data, Size))
+ {
+ IoClose(GDataHandle);
+ GDataHandle = 0;
+ }
+#endif
+}
+void Writer_SendDataRaw(const void* Data, uint32 Size)
+{
+ if (!GDataHandle)
+ {
+ return;
+ }
+ Writer_SendDataImpl(Data, Size);
+}
+void Writer_SendData(uint32 ThreadId, uint8* __restrict Data, uint32 Size)
+{
+ static_assert(ETransport::Active == ETransport::TidPacketSync, "Active should be set to what the compiled code uses. It is used to track places that assume transport packet format");
+ if (!GDataHandle)
+ {
+ return;
+ }
+ if (Size <= 384)
+ {
+ Data -= sizeof(FTidPacket);
+ Size += sizeof(FTidPacket);
+ auto* Packet = (FTidPacket*)Data;
+ Packet->ThreadId = uint16(ThreadId & FTidPacketBase::ThreadIdMask);
+ Packet->PacketSize = uint16(Size);
+ Writer_SendDataImpl(Data, Size);
+ return;
+ }
+ TTidPacketEncoded<8192 + 64> Packet;
+ Packet.ThreadId = FTidPacketBase::EncodedMarker;
+ Packet.ThreadId |= uint16(ThreadId & FTidPacketBase::ThreadIdMask);
+ Packet.DecodedSize = uint16(Size);
+ Packet.PacketSize = uint16(Encode(Data, Packet.DecodedSize, Packet.Data, sizeof(Packet.Data)));
+ Packet.PacketSize += sizeof(FTidPacketEncoded);
+ Writer_SendDataImpl(&Packet, Packet.PacketSize);
+}
+static void Writer_DescribeEvents()
+{
+ TWriteBufferRedirect<4096> TraceData;
+ FEventNode::FIter Iter = FEventNode::ReadNew();
+ while (const FEventNode* Event = Iter.GetNext())
+ {
+ Event->Describe();
+ if (TraceData.GetSize() >= (TraceData.GetCapacity() - 512))
+ {
+ Writer_SendData(ETransportTid::Events, TraceData.GetData(), TraceData.GetSize());
+ TraceData.Reset();
+ }
+ }
+ if (TraceData.GetSize())
+ {
+ Writer_SendData(ETransportTid::Events, TraceData.GetData(), TraceData.GetSize());
+ }
+}
+static void Writer_AnnounceChannels()
+{
+ FChannel::Iter Iter = FChannel::ReadNew();
+ while (const FChannel* Channel = Iter.GetNext())
+ {
+ Channel->Announce();
+ }
+}
+static void Writer_DescribeAnnounce()
+{
+ if (!GDataHandle)
+ {
+ return;
+ }
+ Writer_AnnounceChannels();
+ Writer_DescribeEvents();
+}
+static int8 GSyncPacketCountdown; // = 0
+static const int8 GNumSyncPackets = 3;
+static void Writer_SendSync()
+{
+ if (GSyncPacketCountdown <= 0)
+ {
+ return;
+ }
+ FTidPacketBase SyncPacket = { sizeof(SyncPacket), ETransportTid::Sync };
+ Writer_SendDataImpl(&SyncPacket, sizeof(SyncPacket));
+ --GSyncPacketCountdown;
+}
+static bool Writer_UpdateConnection()
+{
+ if (!GPendingDataHandle)
+ {
+ return false;
+ }
+ static const uint32 CloseInertia = 2;
+ if (GPendingDataHandle >= (~0ull - CloseInertia))
+ {
+ --GPendingDataHandle;
+ if (GPendingDataHandle == (~0ull -CloseInertia))
+ {
+ if (GDataHandle)
+ {
+ Writer_FlushSendBuffer();
+ IoClose(GDataHandle);
+ }
+ GDataHandle = 0;
+ GPendingDataHandle = 0;
+ }
+ return true;
+ }
+ if (GDataHandle)
+ {
+ IoClose(GPendingDataHandle);
+ GPendingDataHandle = 0;
+ return false;
+ }
+ GDataHandle = GPendingDataHandle;
+ GPendingDataHandle = 0;
+#if TRACE_PRIVATE_BUFFER_SEND
+ if (!GSendBuffer)
+ {
+ GSendBuffer = static_cast<uint8*>(Writer_MemoryAllocate(GSendBufferSize, 16));
+ }
+ GSendBufferCursor = GSendBuffer;
+#endif
+ struct FHandshake
+ {
+ uint32 Magic = '2' | ('C' << 8) | ('R' << 16) | ('T' << 24);
+ uint16 MetadataSize = uint16(4); // = sizeof(MetadataField0 + ControlPort)
+ uint16 MetadataField0 = uint16(sizeof(ControlPort) | (ControlPortFieldId << 8));
+ uint16 ControlPort = uint16(Writer_GetControlPort());
+ enum
+ {
+ Size = 10,
+ ControlPortFieldId = 0,
+ };
+ };
+ FHandshake Handshake;
+ bool bOk = IoWrite(GDataHandle, &Handshake, FHandshake::Size);
+ const struct {
+ uint8 TransportVersion = ETransport::TidPacketSync;
+ uint8 ProtocolVersion = EProtocol::Id;
+ } TransportHeader;
+ bOk &= IoWrite(GDataHandle, &TransportHeader, sizeof(TransportHeader));
+ if (!bOk)
+ {
+ IoClose(GDataHandle);
+ GDataHandle = 0;
+ return false;
+ }
+ GTraceStatistics.BytesSent = 0;
+ GTraceStatistics.BytesTraced = 0;
+ FEventNode::OnConnect();
+ Writer_DescribeEvents();
+ Writer_CacheOnConnect();
+ Writer_TailOnConnect();
+ Writer_SendSync();
+ GSyncPacketCountdown = GNumSyncPackets;
+ return true;
+}
+static UPTRINT GWorkerThread; // = 0;
+static volatile bool GWorkerThreadQuit; // = false;
+static void Writer_WorkerUpdate()
+{
+ Writer_UpdateControl();
+ Writer_UpdateConnection();
+ Writer_DescribeAnnounce();
+ Writer_UpdateSharedBuffers();
+ Writer_DrainBuffers();
+ Writer_SendSync();
+#if TRACE_PRIVATE_BUFFER_SEND
+ const uint32 FlushSendBufferCadenceMask = 8-1; // Flush every 8 calls
+ if( (++GUpdateCounter & FlushSendBufferCadenceMask) == 0)
+ {
+ Writer_FlushSendBuffer();
+ }
+#endif
+}
+static void Writer_WorkerThread()
+{
+ ThreadRegister(TEXT("Trace"), 0, INT_MAX);
+ while (!GWorkerThreadQuit)
+ {
+ Writer_WorkerUpdate();
+ const uint32 SleepMs = 17;
+ ThreadSleep(SleepMs);
+ }
+}
+static void Writer_WorkerCreate()
+{
+ if (GWorkerThread)
+ {
+ return;
+ }
+ GWorkerThread = ThreadCreate("TraceWorker", Writer_WorkerThread);
+}
+static void Writer_WorkerJoin()
+{
+ if (!GWorkerThread)
+ {
+ return;
+ }
+ GWorkerThreadQuit = true;
+ ThreadJoin(GWorkerThread);
+ ThreadDestroy(GWorkerThread);
+ Writer_WorkerUpdate();
+ GWorkerThread = 0;
+}
+static void Writer_InternalInitializeImpl()
+{
+ if (GInitialized)
+ {
+ return;
+ }
+ GInitialized = true;
+ GStartCycle = TimeGetTimestamp();
+ Writer_InitializeSharedBuffers();
+ Writer_InitializePool();
+ Writer_InitializeControl();
+ UE_TRACE_LOG($Trace, NewTrace, TraceLogChannel)
+ << NewTrace.StartCycle(GStartCycle)
+ << NewTrace.CycleFrequency(TimeGetFrequency())
+ << NewTrace.Endian(uint16(0x524d))
+ << NewTrace.PointerSize(uint8(sizeof(void*)));
+}
+static void Writer_InternalShutdown()
+{
+ if (!GInitialized)
+ {
+ return;
+ }
+ Writer_WorkerJoin();
+ if (GDataHandle)
+ {
+ Writer_FlushSendBuffer();
+ IoClose(GDataHandle);
+ GDataHandle = 0;
+ }
+ Writer_ShutdownControl();
+ Writer_ShutdownPool();
+ Writer_ShutdownSharedBuffers();
+ Writer_ShutdownCache();
+ Writer_ShutdownTail();
+#if TRACE_PRIVATE_BUFFER_SEND
+ if (GSendBuffer)
+ {
+ Writer_MemoryFree(GSendBuffer, GSendBufferSize);
+ GSendBuffer = nullptr;
+ GSendBufferCursor = nullptr;
+ }
+#endif
+ GInitialized = false;
+}
+void Writer_InternalInitialize()
+{
+ using namespace Private;
+ if (!GInitialized)
+ {
+ static struct FInitializer
+ {
+ FInitializer()
+ {
+ Writer_InternalInitializeImpl();
+ }
+ ~FInitializer()
+ {
+ /* We'll not shut anything down here so we can hopefully capture
+ * any subsequent events. However, we will shutdown the worker
+ * thread and leave it for something else to call update() (mem
+ * tracing at time of writing). Windows will have already done
+ * this implicitly in ExitProcess() anyway. */
+ Writer_WorkerJoin();
+ }
+ } Initializer;
+ }
+}
+void Writer_Initialize(const FInitializeDesc& Desc)
+{
+ Writer_InitializeTail(Desc.TailSizeBytes);
+ if (Desc.bUseImportantCache)
+ {
+ Writer_InitializeCache();
+ }
+ if (Desc.bUseWorkerThread)
+ {
+ Writer_WorkerCreate();
+ }
+}
+void Writer_Shutdown()
+{
+ Writer_InternalShutdown();
+}
+void Writer_Update()
+{
+ if (!GWorkerThread)
+ {
+ Writer_WorkerUpdate();
+ }
+}
+bool Writer_SendTo(const ANSICHAR* Host, uint32 Port)
+{
+ if (GPendingDataHandle || GDataHandle)
+ {
+ return false;
+ }
+ Writer_InternalInitialize();
+ Port = Port ? Port : 1981;
+ UPTRINT DataHandle = TcpSocketConnect(Host, uint16(Port));
+ if (!DataHandle)
+ {
+ return false;
+ }
+ GPendingDataHandle = DataHandle;
+ return true;
+}
+bool Writer_WriteTo(const ANSICHAR* Path)
+{
+ if (GPendingDataHandle || GDataHandle)
+ {
+ return false;
+ }
+ Writer_InternalInitialize();
+ UPTRINT DataHandle = FileOpen(Path);
+ if (!DataHandle)
+ {
+ return false;
+ }
+ GPendingDataHandle = DataHandle;
+ return true;
+}
+bool Writer_IsTracing()
+{
+ return (GDataHandle != 0);
+}
+bool Writer_Stop()
+{
+ if (GPendingDataHandle || !GDataHandle)
+ {
+ return false;
+ }
+ GPendingDataHandle = ~UPTRINT(0);
+ return true;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 AndroidTrace.cpp */
+
+#if UE_TRACE_ENABLED && PLATFORM_ANDROID
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR* Name, void (*Entry)())
+{
+ void* (*PthreadThunk)(void*) = [] (void* Param) -> void * {
+ typedef void (*EntryType)(void);
+ pthread_setname_np(pthread_self(), "Trace");
+ (EntryType(Param))();
+ return nullptr;
+ };
+ pthread_t ThreadHandle;
+ if (pthread_create(&ThreadHandle, nullptr, PthreadThunk, reinterpret_cast<void *>(Entry)) != 0)
+ {
+ return 0;
+ }
+ return static_cast<UPTRINT>(ThreadHandle);
+}
+void ThreadSleep(uint32 Milliseconds)
+{
+ usleep(Milliseconds * 1000U);
+}
+void ThreadJoin(UPTRINT Handle)
+{
+ pthread_join(static_cast<pthread_t>(Handle), nullptr);
+}
+void ThreadDestroy(UPTRINT Handle)
+{
+}
+uint64 TimeGetFrequency()
+{
+ return 1000000ull;
+}
+uint64 TimeGetTimestamp()
+{
+ struct timespec TimeSpec;
+ clock_gettime(CLOCK_MONOTONIC, &TimeSpec);
+ return static_cast<uint64>(static_cast<uint64>(TimeSpec.tv_sec) * 1000000ULL + static_cast<uint64>(TimeSpec.tv_nsec) / 1000ULL);
+}
+static bool TcpSocketSetNonBlocking(int Socket, bool bNonBlocking)
+{
+ int Flags = fcntl(Socket, F_GETFL, 0);
+ if (Flags == -1)
+ {
+ return false;
+ }
+ Flags = bNonBlocking ? (Flags|O_NONBLOCK) : (Flags & ~O_NONBLOCK);
+ return fcntl(Socket, F_SETFL, Flags) >= 0;
+}
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port)
+{
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = inet_addr(Host);
+ SockAddr.sin_port = htons(Port);
+ int Result = connect(Socket, (sockaddr*)&SockAddr, sizeof(SockAddr));
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, false))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+UPTRINT TcpSocketListen(uint16 Port)
+{
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = 0;
+ SockAddr.sin_port = htons(Port);
+ int Result = bind(Socket, reinterpret_cast<sockaddr*>(&SockAddr), sizeof(SockAddr));
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ Result = listen(Socket, 1);
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, true))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out)
+{
+ int Inner = Socket - 1;
+ Inner = accept(Inner, nullptr, nullptr);
+ if (Inner < 0)
+ {
+ return (errno == EAGAIN || errno == EWOULDBLOCK) - 1; // 0 if would block else -1
+ }
+ if (!TcpSocketSetNonBlocking(Inner, false))
+ {
+ close(Inner);
+ return 0;
+ }
+ Out = UPTRINT(Inner + 1);
+ return 1;
+}
+bool TcpSocketHasData(UPTRINT Socket)
+{
+ int Inner = Socket - 1;
+ fd_set FdSet;
+ FD_ZERO(&FdSet);
+ FD_SET(Inner, &FdSet);
+ timeval TimeVal = {};
+ return (select(Inner + 1, &FdSet, nullptr, nullptr, &TimeVal) != 0);
+}
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size)
+{
+ int Inner = int(Handle) - 1;
+ return write(Inner, Data, Size) == Size;
+}
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size)
+{
+ int Inner = int(Handle) - 1;
+ return read(Inner, Data, Size);
+}
+void IoClose(UPTRINT Handle)
+{
+ int Inner = int(Handle) - 1;
+ close(Inner);
+}
+UPTRINT FileOpen(const ANSICHAR* Path)
+{
+ int Flags = O_CREAT|O_WRONLY|O_TRUNC;
+ int Mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ int Out = open(Path, Flags, Mode);
+ if (Out < 0)
+ {
+ return 0;
+ }
+ return UPTRINT(Out + 1);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 AppleTrace.cpp */
+
+#if UE_TRACE_ENABLED && PLATFORM_APPLE
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR* Name, void (*Entry)())
+{
+ void* (*PthreadThunk)(void*) = [] (void* Param) -> void * {
+ typedef void (*EntryType)(void);
+ (EntryType(Param))();
+ return nullptr;
+ };
+ pthread_t ThreadHandle;
+ if (pthread_create(&ThreadHandle, nullptr, PthreadThunk, reinterpret_cast<void *>(Entry)) != 0)
+ {
+ return 0;
+ }
+ return reinterpret_cast<UPTRINT>(ThreadHandle);
+}
+void ThreadSleep(uint32 Milliseconds)
+{
+ usleep(Milliseconds * 1000U);
+}
+void ThreadJoin(UPTRINT Handle)
+{
+ pthread_join(reinterpret_cast<pthread_t>(Handle), nullptr);
+}
+void ThreadDestroy(UPTRINT Handle)
+{
+}
+uint64 TimeGetFrequency()
+{
+ mach_timebase_info_data_t Info;
+ mach_timebase_info(&Info);
+ return (uint64(1 * 1000 * 1000 * 1000) * uint64(Info.denom)) / uint64(Info.numer);
+}
+TRACELOG_API uint64 TimeGetTimestamp()
+{
+ return mach_absolute_time();
+}
+static bool TcpSocketSetNonBlocking(int Socket, bool bNonBlocking)
+{
+ int Flags = fcntl(Socket, F_GETFL, 0);
+ if (Flags == -1)
+ {
+ return false;
+ }
+ Flags = bNonBlocking ? (Flags|O_NONBLOCK) : (Flags & ~O_NONBLOCK);
+ return fcntl(Socket, F_SETFL, Flags) >= 0;
+}
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port)
+{
+#if PLATFORM_MAC // We're only accepting named hosts on desktop platforms
+ struct FAddrInfoPtr
+ {
+ ~FAddrInfoPtr() { freeaddrinfo(Value); }
+ addrinfo* operator -> () { return Value; }
+ addrinfo** operator & () { return &Value; }
+ addrinfo* Value;
+ };
+ FAddrInfoPtr Info;
+ addrinfo Hints = {};
+ Hints.ai_family = AF_INET;
+ Hints.ai_socktype = SOCK_STREAM;
+ Hints.ai_protocol = IPPROTO_TCP;
+ if (getaddrinfo(Host, nullptr, &Hints, &Info))
+ {
+ return 0;
+ }
+ if (&Info == nullptr)
+ {
+ return 0;
+ }
+ auto* SockAddr = (sockaddr_in*)Info->ai_addr;
+ SockAddr->sin_port = htons(Port);
+ int SockAddrSize = int(Info->ai_addrlen);
+#else
+ sockaddr_in SockAddrIp;
+ SockAddrIp.sin_family = AF_INET;
+ SockAddrIp.sin_addr.s_addr = inet_addr(Host);
+ SockAddrIp.sin_port = htons(Port);
+ auto* SockAddr = &SockAddrIp;
+ int SockAddrSize = sizeof(SockAddrIp);
+#endif
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ int Result = connect(Socket, (sockaddr*)SockAddr, SockAddrSize);
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, false))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+UPTRINT TcpSocketListen(uint16 Port)
+{
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = 0;
+ SockAddr.sin_port = htons(Port);
+ int Result = bind(Socket, reinterpret_cast<sockaddr*>(&SockAddr), sizeof(SockAddr));
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ Result = listen(Socket, 1);
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, true))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out)
+{
+ int Inner = int(Socket - 1);
+ Inner = accept(Inner, nullptr, nullptr);
+ if (Inner < 0)
+ {
+ return (errno == EAGAIN || errno == EWOULDBLOCK) - 1; // 0 if would block else -1
+ }
+ if (!TcpSocketSetNonBlocking(Inner, false))
+ {
+ close(Inner);
+ return 0;
+ }
+ Out = UPTRINT(Inner + 1);
+ return 1;
+}
+bool TcpSocketHasData(UPTRINT Socket)
+{
+ int Inner = int(Socket - 1);
+ fd_set FdSet;
+ FD_ZERO(&FdSet);
+ FD_SET(Inner, &FdSet);
+ timeval TimeVal = {};
+ int result = select(Inner + 1, &FdSet, nullptr, nullptr, &TimeVal);
+ return ((result != 0) || ((result == -1) && (errno == ETIMEDOUT)));
+}
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size)
+{
+ int Inner = int(Handle - 1);
+ return (write(Inner, Data, Size) == Size);
+}
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size)
+{
+ int Inner = int(Handle - 1);
+ return read(Inner, Data, Size);
+}
+void IoClose(UPTRINT Handle)
+{
+ int Inner = int(Handle - 1);
+ close(Inner);
+}
+UPTRINT FileOpen(const ANSICHAR* Path)
+{
+ int Flags = O_CREAT|O_WRONLY|O_TRUNC|O_SHLOCK;
+ int Mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ int Out = open(Path, Flags, Mode);
+ if (Out < 0)
+ {
+ return 0;
+ }
+ return UPTRINT(Out + 1);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 HoloLensTrace.cpp */
+
+#if UE_TRACE_ENABLED && PLATFORM_HOLOLENS
+# define _WINSOCK_DEPRECATED_NO_WARNINGS
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# pragma comment(lib, "ws2_32.lib")
+#pragma warning(push)
+#pragma warning(disable : 6031) // WSAStartup() return ignore - we're error tolerant
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR* Name, void (*Entry)())
+{
+ DWORD (WINAPI *WinApiThunk)(void*) = [] (void* Param) -> DWORD
+ {
+ typedef void (*EntryType)(void);
+ (EntryType(Param))();
+ return 0;
+ };
+ HANDLE Handle = CreateThread(nullptr, 0, WinApiThunk, (void*)Entry, 0, nullptr);
+ return UPTRINT(Handle);
+}
+void ThreadSleep(uint32 Milliseconds)
+{
+ Sleep(Milliseconds);
+}
+void ThreadJoin(UPTRINT Handle)
+{
+ WaitForSingleObject(HANDLE(Handle), INFINITE);
+}
+void ThreadDestroy(UPTRINT Handle)
+{
+ CloseHandle(HANDLE(Handle));
+}
+uint64 TimeGetFrequency()
+{
+ LARGE_INTEGER Value;
+ QueryPerformanceFrequency(&Value);
+ return Value.QuadPart;
+}
+TRACELOG_API uint64 TimeGetTimestamp()
+{
+ LARGE_INTEGER Value;
+ QueryPerformanceCounter(&Value);
+ return Value.QuadPart;
+}
+static void TcpSocketInitialize()
+{
+ WSADATA WsaData;
+ WSAStartup(MAKEWORD(2, 2), &WsaData);
+}
+static bool TcpSocketSetNonBlocking(SOCKET Socket, bool bNonBlocking)
+{
+ unsigned long NonBlockingMode = !!bNonBlocking;
+ return ioctlsocket(Socket, FIONBIO, &NonBlockingMode) != SOCKET_ERROR;
+}
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port)
+{
+ TcpSocketInitialize();
+ struct FAddrInfoPtr
+ {
+ ~FAddrInfoPtr() { freeaddrinfo(Value); }
+ addrinfo* operator -> () { return Value; }
+ addrinfo** operator & () { return &Value; }
+ addrinfo* Value;
+ };
+ FAddrInfoPtr Info;
+ addrinfo Hints = {};
+ Hints.ai_family = AF_INET;
+ Hints.ai_socktype = SOCK_STREAM;
+ Hints.ai_protocol = IPPROTO_TCP;
+ if (getaddrinfo(Host, nullptr, &Hints, &Info))
+ {
+ return 0;
+ }
+ if (&Info == nullptr)
+ {
+ return 0;
+ }
+ auto* SockAddr = (sockaddr_in*)Info->ai_addr;
+ SockAddr->sin_port = htons(Port);
+ SOCKET Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
+ if (Socket == INVALID_SOCKET)
+ {
+ return 0;
+ }
+ int Result = connect(Socket, Info->ai_addr, int(Info->ai_addrlen));
+ if (Result == SOCKET_ERROR)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, 0))
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket) + 1;
+}
+UPTRINT TcpSocketListen(uint16 Port)
+{
+ TcpSocketInitialize();
+ SOCKET Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
+ if (Socket == INVALID_SOCKET)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = 0;
+ SockAddr.sin_port = htons(Port);
+ int Result = bind(Socket, (SOCKADDR*)&SockAddr, sizeof(SockAddr));
+ if (Result == INVALID_SOCKET)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ Result = listen(Socket, 1);
+ if (Result == INVALID_SOCKET)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, 1))
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket) + 1;
+}
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out)
+{
+ SOCKET Inner = Socket - 1;
+ Inner = accept(Inner, nullptr, nullptr);
+ if (Inner == INVALID_SOCKET)
+ {
+ return (WSAGetLastError() == WSAEWOULDBLOCK) - 1; // 0 if would block else -1
+ }
+ if (!TcpSocketSetNonBlocking(Inner, 0))
+ {
+ closesocket(Inner);
+ return 0;
+ }
+ Out = UPTRINT(Inner) + 1;
+ return 1;
+}
+bool TcpSocketHasData(UPTRINT Socket)
+{
+ SOCKET Inner = Socket - 1;
+ fd_set FdSet = { 1, { Inner }, };
+ TIMEVAL TimeVal = {};
+ return (select(0, &FdSet, nullptr, nullptr, &TimeVal) != 0);
+}
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ DWORD BytesWritten = 0;
+ if (!WriteFile(Inner, (const char*)Data, Size, &BytesWritten, nullptr))
+ {
+ return false;
+ }
+ return (BytesWritten == Size);
+}
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ DWORD BytesRead = 0;
+ if (!ReadFile(Inner, (char*)Data, Size, &BytesRead, nullptr))
+ {
+ return -1;
+ }
+ return BytesRead;
+}
+void IoClose(UPTRINT Handle)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ CloseHandle(Inner);
+}
+UPTRINT FileOpen(const ANSICHAR* Path)
+{
+ DWORD Access = GENERIC_WRITE;
+ DWORD Share = FILE_SHARE_READ;
+ DWORD Disposition = CREATE_ALWAYS;
+ DWORD Flags = FILE_ATTRIBUTE_NORMAL;
+ HANDLE Out = CreateFile2((LPCWSTR)Path, Access, Share, Disposition, nullptr);
+ if (Out == INVALID_HANDLE_VALUE)
+ {
+ return 0;
+ }
+ return UPTRINT(Out) + 1;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#pragma warning(pop)
+#endif // UE_TRACE_ENABLED
+/* {{{1 UnixTrace.cpp */
+
+#if UE_TRACE_ENABLED && PLATFORM_UNIX
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#if defined(_GNU_SOURCE)
+ #include <sys/syscall.h>
+#endif // _GNU_SOURCE
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR* Name, void (*Entry)())
+{
+ void* (*PthreadThunk)(void*) = [] (void* Param) -> void * {
+ typedef void (*EntryType)(void);
+ (EntryType(Param))();
+ return nullptr;
+ };
+ pthread_t ThreadHandle;
+ if (pthread_create(&ThreadHandle, nullptr, PthreadThunk, reinterpret_cast<void *>(Entry)) != 0)
+ {
+ return 0;
+ }
+ return static_cast<UPTRINT>(ThreadHandle);
+}
+void ThreadSleep(uint32 Milliseconds)
+{
+ usleep(Milliseconds * 1000U);
+}
+void ThreadJoin(UPTRINT Handle)
+{
+ pthread_join(static_cast<pthread_t>(Handle), nullptr);
+}
+void ThreadDestroy(UPTRINT Handle)
+{
+}
+uint64 TimeGetFrequency()
+{
+ return 10000000ull;
+}
+TRACELOG_API uint64 TimeGetTimestamp()
+{
+ struct timespec TimeSpec;
+ clock_gettime(CLOCK_MONOTONIC, &TimeSpec);
+ return static_cast<uint64>(static_cast<uint64>(TimeSpec.tv_sec) * 10000000ULL + static_cast<uint64>(TimeSpec.tv_nsec) / 100ULL);
+}
+static bool TcpSocketSetNonBlocking(int Socket, bool bNonBlocking)
+{
+ int Flags = fcntl(Socket, F_GETFL, 0);
+ if (Flags == -1)
+ {
+ return false;
+ }
+ Flags = bNonBlocking ? (Flags|O_NONBLOCK) : (Flags & ~O_NONBLOCK);
+ return fcntl(Socket, F_SETFL, Flags) >= 0;
+}
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port)
+{
+ struct FAddrInfoPtr
+ {
+ ~FAddrInfoPtr() { freeaddrinfo(Value); }
+ addrinfo* operator -> () { return Value; }
+ addrinfo** operator & () { return &Value; }
+ addrinfo* Value;
+ };
+ FAddrInfoPtr Info;
+ addrinfo Hints = {};
+ Hints.ai_family = AF_INET;
+ Hints.ai_socktype = SOCK_STREAM;
+ Hints.ai_protocol = IPPROTO_TCP;
+ if (getaddrinfo(Host, nullptr, &Hints, &Info))
+ {
+ return 0;
+ }
+ if (&Info == nullptr)
+ {
+ return 0;
+ }
+ auto* SockAddr = (sockaddr_in*)Info->ai_addr;
+ SockAddr->sin_port = htons(Port);
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ int Result = connect(Socket, Info->ai_addr, int(Info->ai_addrlen));
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, false))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+UPTRINT TcpSocketListen(uint16 Port)
+{
+ int Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (Socket < 0)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = 0;
+ SockAddr.sin_port = htons(Port);
+ int Result = bind(Socket, reinterpret_cast<sockaddr*>(&SockAddr), sizeof(SockAddr));
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ Result = listen(Socket, 1);
+ if (Result < 0)
+ {
+ close(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, true))
+ {
+ close(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket + 1);
+}
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out)
+{
+ int Inner = Socket - 1;
+ Inner = accept(Inner, nullptr, nullptr);
+ if (Inner < 0)
+ {
+ return (errno == EAGAIN || errno == EWOULDBLOCK) - 1; // 0 if would block else -1
+ }
+ if (!TcpSocketSetNonBlocking(Inner, false))
+ {
+ close(Inner);
+ return 0;
+ }
+ Out = UPTRINT(Inner + 1);
+ return 1;
+}
+bool TcpSocketHasData(UPTRINT Socket)
+{
+ int Inner = Socket - 1;
+ fd_set FdSet;
+ FD_ZERO(&FdSet);
+ FD_SET(Inner, &FdSet);
+ timeval TimeVal = {};
+ return (select(Inner + 1, &FdSet, nullptr, nullptr, &TimeVal) != 0);
+}
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size)
+{
+ int Inner = int(Handle) - 1;
+ return write(Inner, Data, Size) == Size;
+}
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size)
+{
+ int Inner = int(Handle) - 1;
+ return read(Inner, Data, Size);
+}
+void IoClose(UPTRINT Handle)
+{
+ int Inner = int(Handle) - 1;
+ close(Inner);
+}
+UPTRINT FileOpen(const ANSICHAR* Path)
+{
+ int Flags = O_CREAT|O_WRONLY|O_TRUNC;
+ int Mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ int Out = open(Path, Flags, Mode);
+ if (Out < 0)
+ {
+ return 0;
+ }
+ return UPTRINT(Out + 1);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 WindowsTrace.cpp */
+
+#if UE_TRACE_ENABLED && PLATFORM_WINDOWS
+# define _WINSOCK_DEPRECATED_NO_WARNINGS
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# pragma comment(lib, "ws2_32.lib")
+#pragma warning(push)
+#pragma warning(disable : 6031) // WSAStartup() return ignore - we're error tolerant
+namespace UE {
+namespace Trace {
+namespace Private {
+UPTRINT ThreadCreate(const ANSICHAR*, void (*Entry)())
+{
+ DWORD (WINAPI *WinApiThunk)(void*) = [] (void* Param) -> DWORD
+ {
+ typedef void (*EntryType)(void);
+ (EntryType(Param))();
+ return 0;
+ };
+ HANDLE Handle = CreateThread(nullptr, 0, WinApiThunk, (void*)Entry, 0, nullptr);
+ return UPTRINT(Handle);
+}
+void ThreadSleep(uint32 Milliseconds)
+{
+ Sleep(Milliseconds);
+}
+void ThreadJoin(UPTRINT Handle)
+{
+ WaitForSingleObject(HANDLE(Handle), INFINITE);
+}
+void ThreadDestroy(UPTRINT Handle)
+{
+ CloseHandle(HANDLE(Handle));
+}
+uint64 TimeGetFrequency()
+{
+ LARGE_INTEGER Value;
+ QueryPerformanceFrequency(&Value);
+ return Value.QuadPart;
+}
+TRACELOG_API uint64 TimeGetTimestamp()
+{
+ LARGE_INTEGER Value;
+ QueryPerformanceCounter(&Value);
+ return Value.QuadPart;
+}
+static void TcpSocketInitialize()
+{
+ WSADATA WsaData;
+ WSAStartup(MAKEWORD(2, 2), &WsaData);
+}
+static bool TcpSocketSetNonBlocking(SOCKET Socket, bool bNonBlocking)
+{
+ unsigned long NonBlockingMode = !!bNonBlocking;
+ return ioctlsocket(Socket, FIONBIO, &NonBlockingMode) != SOCKET_ERROR;
+}
+UPTRINT TcpSocketConnect(const ANSICHAR* Host, uint16 Port)
+{
+ TcpSocketInitialize();
+ struct FAddrInfoPtr
+ {
+ ~FAddrInfoPtr() { freeaddrinfo(Value); }
+ addrinfo* operator -> () { return Value; }
+ addrinfo** operator & () { return &Value; }
+ addrinfo* Value;
+ };
+ FAddrInfoPtr Info;
+ addrinfo Hints = {};
+ Hints.ai_family = AF_INET;
+ Hints.ai_socktype = SOCK_STREAM;
+ Hints.ai_protocol = IPPROTO_TCP;
+ if (getaddrinfo(Host, nullptr, &Hints, &Info))
+ {
+ return 0;
+ }
+ if (&Info == nullptr)
+ {
+ return 0;
+ }
+ auto* SockAddr = (sockaddr_in*)Info->ai_addr;
+ SockAddr->sin_port = htons(Port);
+ SOCKET Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
+ if (Socket == INVALID_SOCKET)
+ {
+ return 0;
+ }
+ int Result = connect(Socket, Info->ai_addr, int(Info->ai_addrlen));
+ if (Result == SOCKET_ERROR)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, 0))
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket) + 1;
+}
+UPTRINT TcpSocketListen(uint16 Port)
+{
+ TcpSocketInitialize();
+ SOCKET Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
+ if (Socket == INVALID_SOCKET)
+ {
+ return 0;
+ }
+ sockaddr_in SockAddr;
+ SockAddr.sin_family = AF_INET;
+ SockAddr.sin_addr.s_addr = 0;
+ SockAddr.sin_port = htons(Port);
+ int Result = bind(Socket, (SOCKADDR*)&SockAddr, sizeof(SockAddr));
+ if (Result == INVALID_SOCKET)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ Result = listen(Socket, 1);
+ if (Result == INVALID_SOCKET)
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ if (!TcpSocketSetNonBlocking(Socket, 1))
+ {
+ closesocket(Socket);
+ return 0;
+ }
+ return UPTRINT(Socket) + 1;
+}
+int32 TcpSocketAccept(UPTRINT Socket, UPTRINT& Out)
+{
+ SOCKET Inner = Socket - 1;
+ Inner = accept(Inner, nullptr, nullptr);
+ if (Inner == INVALID_SOCKET)
+ {
+ return (WSAGetLastError() == WSAEWOULDBLOCK) - 1; // 0 if would block else -1
+ }
+ if (!TcpSocketSetNonBlocking(Inner, 0))
+ {
+ closesocket(Inner);
+ return 0;
+ }
+ Out = UPTRINT(Inner) + 1;
+ return 1;
+}
+bool TcpSocketHasData(UPTRINT Socket)
+{
+ SOCKET Inner = Socket - 1;
+ fd_set FdSet = { 1, { Inner }, };
+ TIMEVAL TimeVal = {};
+ return (select(0, &FdSet, nullptr, nullptr, &TimeVal) != 0);
+}
+bool IoWrite(UPTRINT Handle, const void* Data, uint32 Size)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ DWORD BytesWritten = 0;
+ if (!WriteFile(Inner, (const char*)Data, Size, &BytesWritten, nullptr))
+ {
+ return false;
+ }
+ return (BytesWritten == Size);
+}
+int32 IoRead(UPTRINT Handle, void* Data, uint32 Size)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ DWORD BytesRead = 0;
+ if (!ReadFile(Inner, (char*)Data, Size, &BytesRead, nullptr))
+ {
+ return -1;
+ }
+ return BytesRead;
+}
+void IoClose(UPTRINT Handle)
+{
+ HANDLE Inner = HANDLE(Handle - 1);
+ CloseHandle(Inner);
+}
+UPTRINT FileOpen(const ANSICHAR* Path)
+{
+ DWORD Access = GENERIC_WRITE;
+ DWORD Share = FILE_SHARE_READ;
+ DWORD Disposition = CREATE_ALWAYS;
+ DWORD Flags = FILE_ATTRIBUTE_NORMAL;
+ HANDLE Out = CreateFileA(Path, Access, Share, nullptr, Disposition, Flags, nullptr);
+ if (Out == INVALID_HANDLE_VALUE)
+ {
+ return 0;
+ }
+ return UPTRINT(Out) + 1;
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#pragma warning(pop)
+#endif // UE_TRACE_ENABLED
+/* {{{1 Cache.cpp */
+
+#if UE_TRACE_ENABLED
+#include <memory.h>
+namespace UE {
+namespace Trace {
+namespace Private {
+uint32 GetEncodeMaxSize(uint32);
+int32 Encode(const void*, int32, void*, int32);
+void* Writer_MemoryAllocate(SIZE_T, uint32);
+void Writer_MemoryFree(void*, uint32);
+void Writer_SendDataRaw(const void*, uint32);
+void Writer_SendData(uint32, uint8* __restrict, uint32);
+struct alignas(16) FCacheBuffer
+{
+ union
+ {
+ FCacheBuffer* Next;
+ FCacheBuffer** TailNext;
+ };
+ uint32 Size;
+ uint32 Remaining;
+ uint32 _Unused[3];
+ uint32 Underflow; // For packet header
+ uint8 Data[];
+};
+static const uint32 GCacheBufferSize = 64 << 10;
+static const uint32 GCacheCollectorSize = 1 << 10;
+static FCacheBuffer* GCacheCollector; // = nullptr;
+static FCacheBuffer* GCacheActiveBuffer; // = nullptr;
+static FCacheBuffer* GCacheHeadBuffer; // = nullptr;
+extern FStatistics GTraceStatistics;
+static FCacheBuffer* Writer_CacheCreateBuffer(uint32 Size)
+{
+ void* Block = Writer_MemoryAllocate(sizeof(FCacheBuffer) + Size, alignof(FCacheBuffer));
+ auto* Buffer = (FCacheBuffer*)Block;
+ Buffer->Size = Size;
+ Buffer->Remaining = Buffer->Size;
+ Buffer->Next = nullptr;
+ return Buffer;
+}
+static void Writer_CacheCommit(const FCacheBuffer* Collector)
+{
+ uint32 InputSize = uint32(Collector->Size - Collector->Remaining);
+ uint32 EncodeMaxSize = GetEncodeMaxSize(InputSize);
+ if (EncodeMaxSize + sizeof(FTidPacketEncoded) > GCacheActiveBuffer->Remaining)
+ {
+#if TRACE_PRIVATE_STATISTICS
+ GTraceStatistics.CacheWaste += GCacheActiveBuffer->Remaining;
+#endif
+ *(GCacheActiveBuffer->TailNext) = GCacheActiveBuffer;
+ GCacheActiveBuffer->TailNext = nullptr;
+ FCacheBuffer* NewBuffer = Writer_CacheCreateBuffer(GCacheBufferSize);
+ NewBuffer->TailNext = &(GCacheActiveBuffer->Next);
+ GCacheActiveBuffer = NewBuffer;
+ }
+ uint32 Used = GCacheActiveBuffer->Size - GCacheActiveBuffer->Remaining;
+ auto* Packet = (FTidPacketEncoded*)(GCacheActiveBuffer->Data + Used);
+ uint32 OutputSize = Encode(Collector->Data, InputSize, Packet->Data, EncodeMaxSize);
+ Packet->PacketSize = uint16(OutputSize + sizeof(FTidPacketEncoded));
+ Packet->ThreadId = FTidPacketBase::EncodedMarker | uint16(ETransportTid::Importants);
+ Packet->DecodedSize = uint16(InputSize);
+ Used = sizeof(FTidPacketEncoded) + OutputSize;
+ GCacheActiveBuffer->Remaining -= Used;
+#if TRACE_PRIVATE_STATISTICS
+ GTraceStatistics.CacheUsed += Used;
+#endif
+}
+void Writer_CacheData(uint8* Data, uint32 Size)
+{
+ Writer_SendData(ETransportTid::Importants, Data, Size);
+ if (GCacheCollector == nullptr)
+ {
+ return;
+ }
+ while (true)
+ {
+ uint32 StepSize = (Size < GCacheCollector->Remaining) ? Size : GCacheCollector->Remaining;
+ uint32 Used = GCacheCollector->Size - GCacheCollector->Remaining;
+ memcpy(GCacheCollector->Data + Used, Data, StepSize);
+ GCacheCollector->Remaining -= StepSize;
+ if (GCacheCollector->Remaining == 0)
+ {
+ Writer_CacheCommit(GCacheCollector);
+ GCacheCollector->Remaining = GCacheCollector->Size;
+ }
+ Size -= StepSize;
+ if (Size == 0)
+ {
+ break;
+ }
+ Data += StepSize;
+ }
+}
+void Writer_CacheOnConnect()
+{
+ if (GCacheCollector == nullptr)
+ {
+ return;
+ }
+ for (FCacheBuffer* Buffer = GCacheHeadBuffer; Buffer != nullptr; Buffer = Buffer->Next)
+ {
+ uint32 Used = Buffer->Size - Buffer->Remaining;
+ Writer_SendDataRaw(Buffer->Data, Used);
+ }
+ if (uint32 Used = GCacheActiveBuffer->Size - GCacheActiveBuffer->Remaining)
+ {
+ Writer_SendDataRaw(GCacheActiveBuffer->Data, Used);
+ }
+ if (uint32 Used = GCacheCollector->Size - GCacheCollector->Remaining)
+ {
+ Writer_SendData(ETransportTid::Importants, GCacheCollector->Data, Used);
+ }
+}
+void Writer_InitializeCache()
+{
+ GCacheCollector = Writer_CacheCreateBuffer(GCacheCollectorSize);
+ GCacheActiveBuffer = Writer_CacheCreateBuffer(GCacheBufferSize);
+ GCacheActiveBuffer->TailNext = &GCacheHeadBuffer;
+ static_assert(ETransport::Active == ETransport::TidPacketSync, "The important cache is transport aware");
+}
+void Writer_ShutdownCache()
+{
+ for (FCacheBuffer* Buffer = GCacheHeadBuffer; Buffer != nullptr;)
+ {
+ FCacheBuffer* Next = Buffer->Next;
+ Writer_MemoryFree(Buffer, GCacheBufferSize);
+ Buffer = Next;
+ }
+ Writer_MemoryFree(GCacheActiveBuffer, GCacheBufferSize);
+ Writer_MemoryFree(GCacheCollector, GCacheCollectorSize);
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+/* {{{1 SharedBuffer.cpp */
+
+#if UE_TRACE_ENABLED
+namespace UE {
+namespace Trace {
+namespace Private {
+void* Writer_MemoryAllocate(SIZE_T, uint32);
+void Writer_MemoryFree(void*, uint32);
+void Writer_CacheData(uint8*, uint32);
+static FSharedBuffer GNullSharedBuffer = { 0, FSharedBuffer::RefInit };
+FSharedBuffer* volatile GSharedBuffer = &GNullSharedBuffer;
+static FSharedBuffer* GTailBuffer; // = nullptr
+static uint32 GTailPreSent; // = 0
+static const uint32 GBlockSize = 1024; // Block size must be a power of two!
+extern FStatistics GTraceStatistics;
+static FSharedBuffer* Writer_CreateSharedBuffer(uint32 SizeHint=0)
+{
+ const uint32 OverheadSize = sizeof(FSharedBuffer) + sizeof(uint32);
+ uint32 BlockSize = GBlockSize;
+ if (SizeHint + OverheadSize > GBlockSize)
+ {
+ BlockSize += SizeHint + OverheadSize - GBlockSize;
+ BlockSize += GBlockSize - 1;
+ BlockSize &= ~(GBlockSize - 1);
+ }
+ void* Block = Writer_MemoryAllocate(BlockSize, alignof(FSharedBuffer));
+ auto* Buffer = (FSharedBuffer*)(UPTRINT(Block) + BlockSize) - 1;
+ Buffer->Size = uint32(UPTRINT(Buffer) - UPTRINT(Block));
+ Buffer->Size -= sizeof(uint32); // to preceed event data with a small header when sending.
+ Buffer->Cursor = (Buffer->Size << FSharedBuffer::CursorShift) | FSharedBuffer::RefInit;
+ Buffer->Next = nullptr;
+ Buffer->Final = 0;
+ return Buffer;
+}
+FNextSharedBuffer Writer_NextSharedBuffer(FSharedBuffer* Buffer, int32 RegionStart, int32 NegSizeAndRef)
+{
+ FSharedBuffer* NextBuffer;
+ while (true)
+ {
+ bool bBufferOwner = (RegionStart >= 0);
+ if (LIKELY(bBufferOwner))
+ {
+ uint32 Size = -NegSizeAndRef >> FSharedBuffer::CursorShift;
+ NextBuffer = Writer_CreateSharedBuffer(Size);
+ Buffer->Next = NextBuffer;
+ Buffer->Final = RegionStart >> FSharedBuffer::CursorShift;
+ AtomicStoreRelease(&GSharedBuffer, NextBuffer);
+ }
+ else
+ {
+ for (;; PlatformYield())
+ {
+ NextBuffer = AtomicLoadAcquire(&GSharedBuffer);
+ if (NextBuffer != Buffer)
+ {
+ break;
+ }
+ }
+ }
+ AtomicAddRelease(&(Buffer->Cursor), int32(FSharedBuffer::RefBit));
+ RegionStart = AtomicAddRelaxed(&(NextBuffer->Cursor), NegSizeAndRef);
+ if (LIKELY(RegionStart + NegSizeAndRef >= 0))
+ {
+ break;
+ }
+ Buffer = NextBuffer;
+ }
+ return { NextBuffer, RegionStart };
+}
+static void Writer_RetireSharedBufferImpl()
+{
+ uint8* Data = (uint8*)GTailBuffer - GTailBuffer->Size + GTailPreSent;
+ if (auto SendSize = UPTRINT(GTailBuffer) - UPTRINT(Data) - GTailBuffer->Final)
+ {
+#if TRACE_PRIVATE_STATISTICS
+ GTraceStatistics.BytesTraced += SendSize;
+#endif
+ Writer_CacheData(Data, uint32(SendSize));
+ }
+ FSharedBuffer* Temp = GTailBuffer->Next;
+ void* Block = (uint8*)GTailBuffer - GTailBuffer->Size - sizeof(uint32);
+ Writer_MemoryFree(Block, GBlockSize);
+ GTailBuffer = Temp;
+ GTailPreSent = 0;
+}
+static void Writer_RetireSharedBuffer()
+{
+ for (;; PlatformYield())
+ {
+ int32 TailCursor = AtomicLoadAcquire(&(GTailBuffer->Cursor));
+ if (LIKELY(((TailCursor + 1) & FSharedBuffer::RefInit) == 0))
+ {
+ break;
+ }
+ }
+ Writer_RetireSharedBufferImpl();
+}
+void Writer_UpdateSharedBuffers()
+{
+ FSharedBuffer* HeadBuffer = AtomicLoadAcquire(&GSharedBuffer);
+ while (true)
+ {
+ if (GTailBuffer != HeadBuffer)
+ {
+ Writer_RetireSharedBuffer();
+ continue;
+ }
+ int32 Cursor = AtomicLoadAcquire(&(HeadBuffer->Cursor));
+ if ((Cursor + 1) & FSharedBuffer::RefInit)
+ {
+ continue;
+ }
+ Cursor = Cursor >> FSharedBuffer::CursorShift;
+ if (Cursor < 0)
+ {
+ Writer_RetireSharedBufferImpl();
+ break;
+ }
+ uint32 PreSentBias = HeadBuffer->Size - GTailPreSent;
+ if (uint32 Sendable = PreSentBias - Cursor)
+ {
+ uint8* Data = (uint8*)(UPTRINT(HeadBuffer) - PreSentBias);
+ Writer_CacheData(Data, Sendable);
+ GTailPreSent += Sendable;
+ }
+ break;
+ }
+}
+void Writer_InitializeSharedBuffers()
+{
+ FSharedBuffer* Buffer = Writer_CreateSharedBuffer();
+ GTailBuffer = Buffer;
+ GTailPreSent = 0;
+ AtomicStoreRelease(&GSharedBuffer, Buffer);
+}
+void Writer_ShutdownSharedBuffers()
+{
+}
+} // namespace Private
+} // namespace Trace
+} // namespace UE
+#endif // UE_TRACE_ENABLED
+#endif // TRACE_IMPLEMENT
+/* {{{1 standalone_epilogue.h */
+
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#if PLATFORM_WINDOWS
+# pragma warning(pop)
+#endif
+
+#if TRACE_UE_COMPAT_LAYER
+
+#if PLATFORM_WINDOWS
+# if defined(UNICODE) || defined(_UNICODE)
+# undef TEXT
+# undef TCHAR
+# define TEXT(x) L##x
+# endif
+#endif
+
+#endif // TRACE_UE_COMPAT_LAYER
+
+#include <string_view>
+
+#define TRACE_EVENT_DEFINE UE_TRACE_EVENT_DEFINE
+#define TRACE_EVENT_BEGIN UE_TRACE_EVENT_BEGIN
+#define TRACE_EVENT_BEGIN_EXTERN UE_TRACE_EVENT_BEGIN_EXTERN
+#define TRACE_EVENT_FIELD UE_TRACE_EVENT_FIELD
+#define TRACE_EVENT_END UE_TRACE_EVENT_END
+#define TRACE_LOG UE_TRACE_LOG
+#define TRACE_LOG_SCOPED UE_TRACE_LOG_SCOPED
+#define TRACE_LOG_SCOPED_T UE_TRACE_LOG_SCOPED_T
+#define TRACE_CHANNEL UE_TRACE_CHANNEL
+#define TRACE_CHANNEL_EXTERN UE_TRACE_CHANNEL_EXTERN
+#define TRACE_CHANNEL_DEFINE UE_TRACE_CHANNEL_DEFINE
+
+namespace trace = UE::Trace;
+
+#define TRACE_PRIVATE_CONCAT_(x, y) x##y
+#define TRACE_PRIVATE_CONCAT(x, y) TRACE_PRIVATE_CONCAT_(x, y)
+#define TRACE_PRIVATE_UNIQUE_VAR(name) TRACE_PRIVATE_CONCAT($trace_##name, __LINE__)
+
+TRACE_CHANNEL_EXTERN(CpuChannel)
+
+namespace UE {
+namespace Trace {
+
+struct TraceCpuScope
+{
+ ~TraceCpuScope();
+ void Enter(int ScopeId);
+ int _ScopeId = 0;
+};
+
+int ScopeNew(const std::string_view& Name);
+
+} // namespace Trace
+} // namespace UE
+
+#define TRACE_CPU_SCOPE(name) \
+ using namespace std::literals; \
+ trace::TraceCpuScope TRACE_PRIVATE_UNIQUE_VAR(cpu_scope); \
+ if (CpuChannel) { \
+ static int TRACE_PRIVATE_UNIQUE_VAR(scope_id); \
+ if (0 == TRACE_PRIVATE_UNIQUE_VAR(scope_id)) \
+ TRACE_PRIVATE_UNIQUE_VAR(scope_id) = trace::ScopeNew(name##sv); \
+ TRACE_PRIVATE_UNIQUE_VAR(cpu_scope).Enter(TRACE_PRIVATE_UNIQUE_VAR(scope_id)); \
+ } \
+ do {} while (0)
+
+#if TRACE_IMPLEMENT
+
+////////////////////////////////////////////////////////////////////////////////
+TRACE_CHANNEL_DEFINE(CpuChannel)
+
+TRACE_EVENT_BEGIN(CpuProfiler, EventSpec, NoSync|Important)
+ TRACE_EVENT_FIELD(uint32, Id)
+ TRACE_EVENT_FIELD(UE::Trace::AnsiString, Name)
+TRACE_EVENT_END()
+
+TRACE_EVENT_BEGIN(CpuProfiler, EventBatch, NoSync)
+ TRACE_EVENT_FIELD(uint8[], Data)
+TRACE_EVENT_END()
+
+namespace UE {
+namespace Trace {
+namespace Private {
+
+static int32_t encode32_7bit(int32_t value, void* __restrict out)
+{
+ // Calculate the number of bytes
+#if 0
+ int32_t msb_test = (value << sizeof(value)) | 0x10;
+#if _MSC_VER
+ unsigned long bit_index;
+ _BitScanReverse(&bit_index, msb_test);
+#else
+ int32_t leading_zeros = __builtin_clz(msb_test);
+ int32_t bit_index = ((sizeof(value) * 8) - 1) - leading_zeros;
+#endif
+ int32_t length = (bit_index + 3) / 7;
+#else
+ int32_t length = 1;
+ length += (value >= (1 << 7));
+ length += (value >= (1 << 14));
+ length += (value >= (1 << 21));
+#endif
+
+ // Add a gap every eigth bit for the continuations
+ int32_t ret = value;
+ ret = (ret & 0x0000'3fff) | ((ret & 0x0fff'c000) << 2);
+ ret = (ret & 0x007f'007f) | ((ret & 0x3f80'3f80) << 1);
+
+ // Set the bits indicating another byte follows
+ int32_t continuations = 0x0080'8080;
+ continuations >>= (sizeof(value) - length) * 8;
+ ret |= continuations;
+
+ ::memcpy(out, &ret, sizeof(value));
+
+ return length;
+}
+
+static int32_t encode64_7bit(int64_t value, void* __restrict out)
+{
+ // Calculate the output length
+#if 0
+ int64_t msb_test = (value << sizeof(value)) | 0x100ull;
+#if _MSC_VER
+ unsigned long bit_index;
+ _BitScanReverse64(&bit_index, msb_test);
+#else
+ int32_t leading_zeros = __builtin_clzll(msb_test);
+ int32_t bit_index = ((sizeof(value) * 8) - 1) - leading_zeros;
+#endif
+ int32_t length = (bit_index - 1) / 7;
+#else
+ uint32_t length = 1;
+ length += (value >= (1ll << 7));
+ length += (value >= (1ll << 14));
+ length += (value >= (1ll << 21));
+ length += (value >= (1ll << 28));
+ length += (value >= (1ll << 35));
+ length += (value >= (1ll << 42));
+ length += (value >= (1ll << 49));
+#endif
+
+ // Add a gap every eigth bit for the continuations
+ int64_t ret = value;
+ ret = (ret & 0x0000'0000'0fff'ffffull) | ((ret & 0x00ff'ffff'f000'0000ull) << 4);
+ ret = (ret & 0x0000'3fff'0000'3fffull) | ((ret & 0x0fff'c000'0fff'c000ull) << 2);
+ ret = (ret & 0x007f'007f'007f'007full) | ((ret & 0x3f80'3f80'3f80'3f80ull) << 1);
+
+ // Set the bits indicating another byte follows
+ int64_t continuations = 0x0080'8080'8080'8080ull;
+ continuations >>= (sizeof(value) - length) * 8;
+ ret |= continuations;
+
+ ::memcpy(out, &ret, sizeof(value));
+
+ return length;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+class ThreadBuffer
+{
+public:
+ static void Enter(uint64_t Timestamp, uint32_t ScopeId) { TlsInstance.EnterImpl(Timestamp, ScopeId); }
+ static void Leave(uint64_t Timestamp) { TlsInstance.LeaveImpl(Timestamp); }
+
+private:
+ ~ThreadBuffer();
+ void Flush(bool Force);
+ void EnterImpl(uint64_t Timestamp, uint32_t ScopeId);
+ void LeaveImpl(uint64_t Timestamp);
+ enum
+ {
+ BufferSize = 256,
+ Overflow = 16,
+ EnterLsb = 1,
+ LeaveLsb = 0,
+ };
+ uint64_t PrevTimestamp = 0;
+ uint8_t* Cursor = Buffer;
+ uint8_t Buffer[BufferSize];
+
+ static thread_local ThreadBuffer TlsInstance;
+};
+
+thread_local ThreadBuffer ThreadBuffer::TlsInstance;
+
+////////////////////////////////////////////////////////////////////////////////
+ThreadBuffer::~ThreadBuffer()
+{
+ Flush(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void ThreadBuffer::Flush(bool Force)
+{
+ if (!Force && (Cursor <= (Buffer + BufferSize - Overflow)))
+ return;
+
+ TRACE_LOG(CpuProfiler, EventBatch, true)
+ << EventBatch.Data(Buffer, uint32(ptrdiff_t(Cursor - Buffer)));
+
+ PrevTimestamp = 0;
+ Cursor = Buffer;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void ThreadBuffer::EnterImpl(uint64_t Timestamp, uint32_t ScopeId)
+{
+ Timestamp -= PrevTimestamp;
+ PrevTimestamp += Timestamp;
+ Cursor += encode64_7bit((Timestamp) << 1 | EnterLsb, Cursor);
+ Cursor += encode32_7bit(ScopeId, Cursor);
+
+ Flush(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void ThreadBuffer::LeaveImpl(uint64_t Timestamp)
+{
+ Timestamp -= PrevTimestamp;
+ PrevTimestamp += Timestamp;
+ Cursor += encode64_7bit((Timestamp << 1) | LeaveLsb, Cursor);
+
+ Flush(false);
+}
+
+} // namespace Private
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+int ScopeNew(const std::string_view& Name)
+{
+ static int volatile NextSpecId = 1;
+ int SpecId = Private::AtomicAddRelaxed(&NextSpecId, 1);
+
+ uint32 NameSize = uint32(Name.size());
+ TRACE_LOG(CpuProfiler, EventSpec, true, NameSize)
+ << EventSpec.Id(uint32(SpecId))
+ << EventSpec.Name(Name.data(), NameSize);
+
+ return SpecId;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+TraceCpuScope::~TraceCpuScope()
+{
+ using namespace Private;
+
+ if (!_ScopeId)
+ return;
+
+ uint64 Timestamp = TimeGetTimestamp();
+ ThreadBuffer::Leave(Timestamp);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+void TraceCpuScope::Enter(int ScopeId)
+{
+ using namespace Private;
+
+ _ScopeId = ScopeId;
+ uint64 Timestamp = TimeGetTimestamp();
+ ThreadBuffer::Enter(Timestamp, ScopeId);
+}
+
+} // namespace Trace
+} // namespace UE
+
+#endif // TRACE_IMPLEMENT
+
+/* vim: set noet foldlevel=1 foldmethod=marker : */
diff --git a/xmake.lua b/xmake.lua
index 1da00b8df..c1aef5f85 100644
--- a/xmake.lua
+++ b/xmake.lua
@@ -1,24 +1,25 @@
add_requires(
- "vcpkg::doctest",
- "vcpkg::spdlog",
- "vcpkg::gsl-lite",
- "vcpkg::asio",
- "vcpkg::cpr",
- "vcpkg::xxhash",
- "vcpkg::robin-map",
- "vcpkg::lz4",
- "vcpkg::fmt",
- "vcpkg::cxxopts",
- "vcpkg::mimalloc",
- "vcpkg::sol2",
- "vcpkg::sentry-native",
- "vcpkg::json11",
- "vcpkg::lua",
- "vcpkg::curl",
- "vcpkg::zlib",
- "vcpkg::zstd",
+ "vcpkg::asio",
+ "vcpkg::cpr",
+ "vcpkg::curl",
+ "vcpkg::cxxopts",
+ "vcpkg::doctest",
+ "vcpkg::fmt",
+ "vcpkg::gsl-lite",
"vcpkg::http-parser",
- "vcpkg::rocksdb")
+ "vcpkg::json11",
+ "vcpkg::lua",
+ "vcpkg::lz4",
+ "vcpkg::mimalloc",
+ "vcpkg::openssl",
+ "vcpkg::robin-map",
+ "vcpkg::sentry-native",
+ "vcpkg::sol2",
+ "vcpkg::spdlog",
+ "vcpkg::xxhash",
+ "vcpkg::zlib",
+ "vcpkg::zstd"
+)
add_rules("mode.debug", "mode.release")
@@ -40,27 +41,88 @@ else
end
if is_os("windows") then
- add_defines("_CRT_SECURE_NO_WARNINGS", "_UNICODE", "UNICODE", "_WIN32_WINNT=0x0A00")
+ add_defines(
+ "_CRT_SECURE_NO_WARNINGS",
+ "_UNICODE",
+ "UNICODE",
+ "_CONSOLE",
+ "NOMINMAX", -- stop Windows SDK defining 'min' and 'max'
+ "NOGDI", -- otherwise Windows.h defines 'GetObject'
+ "WIN32_LEAN_AND_MEAN", -- cut down Windows.h
+ "_WIN32_WINNT=0x0A00"
+ )
-- add_ldflags("/MAP")
end
-add_defines("USE_SENTRY=1")
-add_defines("ZEN_USE_MIMALLOC=1")
+if is_os("linux") then
+ add_cxxflags("-Wno-unused-value")
+ add_cxxflags("-Wno-strict-aliasing")
+ add_cxxflags("-Wno-implicit-fallthrough")
+ add_cxxflags("-Wno-missing-field-initializers")
+end
-option("vfs")
+-- Turn use of undefined cpp macros into errors
+if is_os("windows") then
+ add_cxxflags("/we4668")
+else
+ add_cxxflags("-Wundef")
+end
+
+function add_define_by_config(define, config_name)
+ local value = has_config(config_name) and 1 or 0
+ add_defines(define.."="..value)
+end
+
+option("zensentry")
+ set_default(true)
set_showmenu(true)
- set_description("Enable VFS functionality")
- add_defines("ZEN_WITH_VFS")
+ set_description("Enables Sentry support in Zen")
option_end()
+add_define_by_config("USE_SENTRY", "zensentry")
-option("httpsys")
+option("zenmimalloc")
set_default(true)
set_showmenu(true)
- set_description("Enable http.sys server")
- add_defines("ZEN_WITH_HTTPSYS")
+ set_description("Use MiMalloc as Zen's allocator")
option_end()
+add_define_by_config("ZEN_USE_MIMALLOC", "zenmimalloc")
-add_defines("UNICODE", "_CONSOLE")
+if is_os("windows") then
+ option("vfs")
+ set_showmenu(true)
+ set_description("Enable VFS functionality")
+ option_end()
+ add_define_by_config("ZEN_WITH_VFS", "vfs")
+
+ option("httpsys")
+ set_default(true)
+ set_showmenu(true)
+ set_description("Enable http.sys server")
+ option_end()
+ add_define_by_config("ZEN_WITH_HTTPSYS", "httpsys")
+end
+
+option("compute")
+ set_default(is_os("windows"))
+ set_showmenu(true)
+ set_description("Enable compute services endpoint")
+option_end()
+add_define_by_config("ZEN_WITH_COMPUTE_SERVICES", "compute")
+
+option("zenmesh")
+ set_default(false)
+ set_showmenu(true)
+ set_description("Enables Zen's mesh feature")
+option_end()
+add_define_by_config("ZEN_ENABLE_MESH", "zenmesh")
+
+option("zentrace")
+ set_default(false)
+ set_showmenu(true)
+ set_description("Enable UE's Trace support")
+ add_includedirs("thirdparty/trace")
+option_end()
+add_define_by_config("ZEN_WITH_TRACE", "zentrace")
set_warnings("allextra", "error")
set_languages("cxx20")
diff --git a/zen/chunk/chunk.cpp b/zen/chunk/chunk.cpp
index 9697969c0..b6dc0c465 100644
--- a/zen/chunk/chunk.cpp
+++ b/zen/chunk/chunk.cpp
@@ -22,8 +22,10 @@
#include <lz4.h>
#include <zstd.h>
-#include <ppl.h>
-#include <ppltasks.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <ppl.h>
+# include <ppltasks.h>
+#endif // ZEN_PLATFORM_WINDOWS
#include <cmath>
#include <filesystem>
@@ -32,6 +34,46 @@
//////////////////////////////////////////////////////////////////////////
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+namespace Concurrency {
+
+template<typename IterType, typename LambdaType>
+void
+parallel_for_each(IterType Cursor, IterType End, const LambdaType& Lambda)
+{
+ for (; Cursor < End; ++Cursor)
+ {
+ Lambda(*Cursor);
+ }
+}
+
+template<typename T>
+struct combinable
+{
+ T& local() { return Value; }
+
+ template<typename LambdaType>
+ void combine_each(const LambdaType& Lambda)
+ {
+ Lambda(Value);
+ }
+
+ T Value = {};
+};
+
+struct task_group
+{
+ void run(...) {}
+ void wait() {}
+};
+
+} // namespace Concurrency
+
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+//////////////////////////////////////////////////////////////////////////
+
namespace detail {
static const uint32_t buzhashTable[] = {
0x458be752, 0xc10748cc, 0xfbbcdbb8, 0x6ded5b68, 0xb10a82b5, 0x20d75648, 0xdfc5665f, 0xa8428801, 0x7ebf5191, 0x841135c7, 0x65cc53b3,
@@ -548,7 +590,9 @@ protected:
{
zen::RwLock HashLock;
std::unordered_set<zen::IoHash, zen::IoHash::Hasher> Hashes;
-#pragma warning(suppress : 4324) // Padding due to alignment
+#if ZEN_PLATFORM_WINDOWS
+# pragma warning(suppress : 4324) // Padding due to alignment
+#endif
};
Bucket m_Buckets[256];
@@ -668,7 +712,7 @@ public:
zen::IoBuffer CompressedBuffer = m_CompressionBufferManager->AllocBuffer();
char* CompressBuffer = (char*)CompressedBuffer.Data();
- ZEN_ASSERT(CompressedBuffer.Size() >= CompressBufferSize);
+ ZEN_ASSERT(CompressedBuffer.Size() >= size_t(CompressBufferSize));
const int CompressedSize = LZ4_compress_default((const char*)DataPointer,
CompressBuffer,
@@ -796,8 +840,6 @@ public:
uint64_t CurrentChunkSize = 0;
size_t RemainBytes = DataSize;
- Concurrency::structured_task_group CompressionTasks;
-
zen::IoHashStream IoHashStream;
while (RemainBytes != 0)
@@ -1039,7 +1081,7 @@ ChunkCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
catch (std::exception& ex)
{
zen::ExtendableStringBuilder<256> Path8;
- zen::WideToUtf8(ThisFile.Path.c_str(), Path8);
+ zen::PathToUtf8(ThisFile.Path, Path8);
ZEN_WARN("Caught exception while chunking '{}': {}", Path8, ex.what());
}
});
diff --git a/zen/cmds/dedup.cpp b/zen/cmds/dedup.cpp
index 089212ed9..7f735571c 100644
--- a/zen/cmds/dedup.cpp
+++ b/zen/cmds/dedup.cpp
@@ -10,10 +10,33 @@
#include <zencore/thread.h>
#include <zencore/timer.h>
-#include <ppl.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <ppl.h>
+#endif
+
+#include <list>
namespace zen {
+////////////////////////////////////////////////////////////////////////////////
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+namespace Concurrency {
+
+ template<typename T0, typename T1>
+ inline void parallel_invoke(T0 const& t0, T1 const& t1)
+ {
+ t0();
+ t1();
+ }
+
+} // namespace Concurrency
+
+#endif // ZEN_PLATFORM_LINUX/MAC
+
+////////////////////////////////////////////////////////////////////////////////
+
DedupCommand::DedupCommand()
{
m_Options.add_options()("h,help", "Print help");
@@ -86,15 +109,6 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::filesystem::recursive_directory_iterator DirEnd;
- struct Utf8Helper
- {
- zen::ExtendableStringBuilder<128> Path8;
-
- Utf8Helper(const wchar_t* Path) { zen::WideToUtf8(Path, Path8); };
-
- std::string_view c_str() { return std::string_view(Path8); };
- };
-
ZEN_INFO("Gathering file info from source: '{}'", m_DedupSource);
ZEN_INFO("Gathering file info from target: '{}'", m_DedupTarget);
@@ -165,7 +179,7 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
size_t BucketFileCount = 0;
bool FirstBucket = true;
- for (int i = 0; i < SizeLists.size(); ++i)
+ for (size_t i = 0; i < SizeLists.size(); ++i)
{
const size_t ThisSize = SizeLists[i].Size;
const size_t Pow2 = zen::NextPow2(ThisSize);
@@ -204,7 +218,7 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
ZEN_ASSERT(BucketOffsets.size() == BucketFileCounts.size());
}
- for (int i = 0; i < BucketOffsets.size(); ++i)
+ for (size_t i = 0; i < BucketOffsets.size(); ++i)
{
ZEN_INFO(" Bucket {} : {}, {} candidates", zen::NiceBytes(BucketId[i]), zen::NiceBytes(BucketSizes[i]), BucketFileCounts[i]);
}
@@ -252,8 +266,8 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (const std::filesystem::directory_entry* Dupe = DedupMap[Hash])
{
- std::wstring FileA = Dupe->path().c_str();
- std::wstring FileB = Entry.path().c_str();
+ std::string FileA = PathToUtf8(Dupe->path());
+ std::string FileB = PathToUtf8(Entry.path());
size_t MinLen = std::min(FileA.size(), FileB.size());
auto Its = std::mismatch(FileB.rbegin(), FileB.rbegin() + MinLen, FileA.rbegin());
@@ -263,13 +277,10 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (Its.first[-1] == '\\' || Its.first[-1] == '/')
--Its.first;
- FileB = std::wstring(FileB.begin(), Its.first.base()) + L"...";
+ FileB = std::string(FileB.begin(), Its.first.base()) + "...";
}
- ZEN_INFO("{} {} <-> {}",
- zen::NiceBytes(Entry.file_size()).c_str(),
- Utf8Helper(FileA.c_str()).c_str(),
- Utf8Helper(FileB.c_str()).c_str());
+ ZEN_INFO("{} {} <-> {}", zen::NiceBytes(Entry.file_size()).c_str(), FileA.c_str(), FileB.c_str());
zen::CopyFileOptions Options;
Options.EnableClone = true;
diff --git a/zen/cmds/dedup.h b/zen/cmds/dedup.h
index 7932d10e6..dbda236f6 100644
--- a/zen/cmds/dedup.h
+++ b/zen/cmds/dedup.h
@@ -4,8 +4,6 @@
#include "../zen.h"
-#include <ppl.h>
-
namespace zen {
/** Deduplicate files in a tree using block cloning
diff --git a/zen/cmds/deploy.cpp b/zen/cmds/deploy.cpp
new file mode 100644
index 000000000..10808d063
--- /dev/null
+++ b/zen/cmds/deploy.cpp
@@ -0,0 +1,87 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "deploy.h"
+
+#include <zencore/filesystem.h>
+#include <zencore/logging.h>
+#include <zencore/string.h>
+
+namespace zen {
+
+DeployCommand::DeployCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_options()("no-clone", "Do not perform block clone", cxxopts::value(m_NoClone)->default_value("false"));
+ m_Options.add_options()("clean",
+ "Make clean deploy (i.e remove anything in target first)",
+ cxxopts::value(m_IsClean)->default_value("false"));
+ m_Options.add_option("", "s", "source", "Deploy source", cxxopts::value(m_CopySource), "<build store>");
+ m_Options.add_option("", "t", "target", "Deploy target", cxxopts::value(m_CopyTarget), "<directory>");
+ m_Options.add_option("", "", "positional", "Positional arguments", cxxopts::value(m_Positional), "");
+}
+
+DeployCommand::~DeployCommand() = default;
+
+int
+DeployCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ m_Options.parse_positional({"source", "target", "positional"});
+
+ auto result = m_Options.parse(argc, argv);
+
+ if (result.count("help"))
+ {
+ std::cout << m_Options.help({"", "Group"}) << std::endl;
+
+ return 0;
+ }
+
+ // Validate arguments
+
+ if (m_CopySource.empty())
+ throw std::runtime_error("No source specified");
+
+ if (m_CopyTarget.empty())
+ throw std::runtime_error("No target specified");
+
+ std::filesystem::path ToPath;
+
+ ToPath = m_CopyTarget;
+
+ const bool IsTargetDir = std::filesystem::is_directory(ToPath);
+ bool IsTargetNew = !std::filesystem::exists(ToPath);
+
+ if (!IsTargetNew && !IsTargetDir)
+ {
+ throw std::runtime_error("Invalid target specification (needs to be a directory)");
+ }
+
+ zen::ExtendableStringBuilder<128> Path8;
+ zen::PathToUtf8(ToPath, Path8);
+
+ if (IsTargetNew == false && m_IsClean)
+ {
+ ZEN_INFO("Clean deploy -- deleting directory {}", Path8.c_str());
+
+ std::filesystem::remove_all(ToPath);
+
+ IsTargetNew = true; // Create fresh new directory
+ }
+
+ if (IsTargetNew)
+ {
+ ZEN_INFO("Creating directory {}", Path8.c_str());
+
+ std::filesystem::create_directories(ToPath);
+ }
+
+ ZEN_INFO("Starting deploy operation...");
+
+ // TODO: implement!
+
+ return 0;
+}
+
+} // namespace zen
diff --git a/zen/cmds/hash.cpp b/zen/cmds/hash.cpp
index 7d234c2da..59dea5a5d 100644
--- a/zen/cmds/hash.cpp
+++ b/zen/cmds/hash.cpp
@@ -7,10 +7,49 @@
#include <zencore/string.h>
#include <zencore/timer.h>
-#include <ppl.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <ppl.h>
+#endif
namespace zen {
+////////////////////////////////////////////////////////////////////////////////
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+namespace Concurrency {
+
+ template<typename IterType, typename LambdaType>
+ void parallel_for_each(IterType Cursor, IterType End, const LambdaType& Lambda)
+ {
+ for (; Cursor < End; ++Cursor)
+ {
+ Lambda(*Cursor);
+ }
+ }
+
+ template<typename T>
+ struct combinable
+ {
+ combinable<T>& local() { return *this; }
+
+ void operator+=(T Rhs) { Value += Rhs; }
+
+ template<typename LambdaType>
+ void combine_each(const LambdaType& Lambda)
+ {
+ Lambda(Value);
+ }
+
+ T Value = 0;
+ };
+
+} // namespace Concurrency
+
+#endif // ZEN_PLATFORM_LINUX|MAC
+
+////////////////////////////////////////////////////////////////////////////////
+
HashCommand::HashCommand()
{
m_Options.add_options()("d,dir", "Directory to scan", cxxopts::value<std::string>(m_ScanDirectory))(
diff --git a/zen/cmds/hash.h b/zen/cmds/hash.h
index 3df9063ea..3cc2d1440 100644
--- a/zen/cmds/hash.h
+++ b/zen/cmds/hash.h
@@ -5,8 +5,6 @@
#include "../internalfile.h"
#include "../zen.h"
-#include <ppl.h>
-
namespace zen {
/** Generate hash list file
diff --git a/zen/cmds/run.cpp b/zen/cmds/run.cpp
index 8cbf13566..0fde4648a 100644
--- a/zen/cmds/run.cpp
+++ b/zen/cmds/run.cpp
@@ -45,6 +45,7 @@ RunCommand::~RunCommand() = default;
void
CreateTreeManifest(std::filesystem::path RootPath)
{
+ ZEN_UNUSED(RootPath);
}
int
@@ -84,7 +85,7 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
zen::ScanFile(FullPath, 64 * 1024, [&](const void* Data, size_t Size) { Ios.Append(Data, Size); });
zen::IoHash Hash = Ios.GetHash();
- std::wstring RelativePath = FullPath.lexically_relative(m_RootPath).native();
+ path_string RelativePath = FullPath.lexically_relative(m_RootPath).native();
// ZEN_INFO("File: {:32} => {} ({})", zen::WideToUtf8(RelativePath), Hash, FileSize);
FileEntry& Entry = m_Files[RelativePath];
@@ -94,11 +95,11 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
m_HashToFile[Hash] = FullPath;
}
- virtual bool VisitDirectory(const std::filesystem::path& Parent, const std::wstring_view& DirectoryName) override
+ virtual bool VisitDirectory(const std::filesystem::path& Parent, const path_view& DirectoryName) override
{
std::filesystem::path FullPath = Parent / DirectoryName;
- if (DirectoryName.starts_with(L"."))
+ if (DirectoryName.starts_with('.'))
{
return false;
}
@@ -112,7 +113,7 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
zen::IoHash Hash;
};
- std::map<std::wstring, FileEntry> m_Files;
+ std::map<path_string, FileEntry> m_Files;
std::unordered_map<zen::IoHash, std::filesystem::path, zen::IoHash::Hasher> m_HashToFile;
};
@@ -128,7 +129,7 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
for (const auto& Kv : Visit.m_Files)
{
PrepReq.BeginObject();
- PrepReq << "file" << zen::WideToUtf8(Kv.first) << "size" << Kv.second.Size << "hash" << Kv.second.Hash;
+ PrepReq << "file" << zen::PathToUtf8(Kv.first) << "size" << Kv.second.Size << "hash" << Kv.second.Hash;
PrepReq.EndObject();
}
PrepReq.EndArray();
@@ -152,7 +153,7 @@ RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
if (auto It = Visit.m_HashToFile.find(NeedHash); It != Visit.m_HashToFile.end())
{
- zen::IoBuffer FileData = zen::IoBufferBuilder::MakeFromFile(It->second.c_str());
+ zen::IoBuffer FileData = zen::IoBufferBuilder::MakeFromFile(It->second);
cpr::Response CasResponse =
cpr::Post(cpr::Url("http://localhost:13337/cas"), cpr::Body((const char*)FileData.Data(), FileData.Size()));
diff --git a/zen/internalfile.cpp b/zen/internalfile.cpp
index 3ad5a7d05..a1f07992b 100644
--- a/zen/internalfile.cpp
+++ b/zen/internalfile.cpp
@@ -2,10 +2,18 @@
#include "internalfile.h"
+#include <zencore/except.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
-#include <zencore/windows.h>
+#include <zencore/memory.h>
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+# include <fcntl.h>
+# include <sys/file.h>
+# include <sys/mman.h>
+# include <sys/stat.h>
+#endif
#include <gsl/gsl-lite.hpp>
@@ -123,50 +131,95 @@ FileBufferManager::ReturnBuffer(zen::IoBuffer Buffer)
//////////////////////////////////////////////////////////////////////////
-using namespace fmt::literals;
-
InternalFile::InternalFile()
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+: m_File(nullptr)
+, m_Mmap(nullptr)
+#endif
{
}
InternalFile::~InternalFile()
{
if (m_Memory)
- _aligned_free(m_Memory);
+ zen::Memory::Free(m_Memory);
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ if (m_Mmap)
+ munmap(m_Mmap, GetFileSize());
+ if (m_File)
+ close(int(intptr_t(m_File)));
+#endif
}
size_t
InternalFile::GetFileSize()
{
+#if ZEN_PLATFORM_WINDOWS
ULONGLONG sz;
m_File.GetSize(sz);
-
return size_t(sz);
+#else
+ int Fd = int(intptr_t(m_File));
+ static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
+ struct stat Stat;
+ fstat(Fd, &Stat);
+ return size_t(Stat.st_size);
+#endif
}
void
InternalFile::OpenWrite(std::filesystem::path FileName, bool IsCreate)
{
+ bool Success = false;
+
+#if ZEN_PLATFORM_WINDOWS
const DWORD dwCreationDisposition = IsCreate ? CREATE_ALWAYS : OPEN_EXISTING;
HRESULT hRes = m_File.Create(FileName.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, dwCreationDisposition);
+ Success = SUCCEEDED(hRes);
+#else
+ int OpenFlags = O_RDWR;
+ OpenFlags |= IsCreate ? O_CREAT | O_TRUNC : 0;
+
+ int Fd = open(FileName.c_str(), OpenFlags, 0666);
+ if (Fd >= 0)
+ {
+ Success = true;
+ m_File = (void*)(intptr_t(Fd));
+ }
+#endif // ZEN_PLATFORM_WINDOWS
- if (FAILED(hRes))
+ if (Success)
{
- throw std::system_error(GetLastError(), std::system_category(), "Failed to open file for writing: '{}'"_format(FileName));
+ using namespace fmt::literals;
+ zen::ThrowLastError("Failed to open file for writing: '{}'"_format(FileName));
}
}
void
InternalFile::OpenRead(std::filesystem::path FileName)
{
+ bool Success = false;
+
+#if ZEN_PLATFORM_WINDOWS
const DWORD dwCreationDisposition = OPEN_EXISTING;
HRESULT hRes = m_File.Create(FileName.c_str(), GENERIC_READ, FILE_SHARE_READ, dwCreationDisposition);
+ Success = SUCCEEDED(hRes);
+#else
+ int Fd = open(FileName.c_str(), O_RDONLY);
+ if (Fd >= 0)
+ {
+ Success = true;
+ m_File = (void*)(intptr_t(Fd));
+ }
+#endif
- if (FAILED(hRes))
+ if (Success)
{
- throw std::system_error(GetLastError(), std::system_category(), "Failed to open file for reading: '{}'"_format(FileName));
+ using namespace fmt::literals;
+ zen::ThrowLastError("Failed to open file for reading: '{}'"_format(FileName));
}
}
@@ -175,55 +228,72 @@ InternalFile::MemoryMapFile()
{
auto FileSize = GetFileSize();
- if (FileSize > 100 * 1024 * 1024)
+ if (FileSize <= 100 * 1024 * 1024)
{
- m_Mmap.MapFile(m_File);
+ m_Memory = zen::Memory::Alloc(FileSize, 64);
+ Read(m_Memory, FileSize, 0);
- return m_Mmap.GetData();
+ return m_Memory;
}
- m_Memory = _aligned_malloc(FileSize, 64);
- Read(m_Memory, FileSize, 0);
-
- return m_Memory;
+#if ZEN_PLATFORM_WINDOWS
+ m_Mmap.MapFile(m_File);
+ return m_Mmap.GetData();
+#else
+ int Fd = int(intptr_t(m_File));
+ m_Mmap = mmap(nullptr, FileSize, PROT_READ, MAP_PRIVATE, Fd, 0);
+ return m_Mmap;
+#endif
}
void
InternalFile::Read(void* Data, uint64_t Size, uint64_t Offset)
{
+ bool Success;
+
+#if ZEN_PLATFORM_WINDOWS
OVERLAPPED ovl{};
ovl.Offset = DWORD(Offset & 0xffff'ffffu);
ovl.OffsetHigh = DWORD(Offset >> 32);
HRESULT hRes = m_File.Read(Data, gsl::narrow<DWORD>(Size), &ovl);
+ Success = SUCCEEDED(hRes);
+#else
+ int Fd = int(intptr_t(m_File));
+ int BytesRead = pread(Fd, Data, Size, Offset);
+ Success = (BytesRead > 0);
+#endif
- if (FAILED(hRes))
+ if (Success)
{
- throw std::system_error(GetLastError(),
- std::system_category(),
- "Failed to read from file '{}'"_format(zen::PathFromHandle(m_File)));
+ using namespace fmt::literals;
+ zen::ThrowLastError("Failed to read from file '{}'"_format("")); // zen::PathFromHandle(m_File)));
}
}
void
InternalFile::Write(const void* Data, uint64_t Size, uint64_t Offset)
{
+ bool Success;
+
+#if ZEN_PLATFORM_WINDOWS
OVERLAPPED Ovl{};
Ovl.Offset = DWORD(Offset & 0xffff'ffffu);
Ovl.OffsetHigh = DWORD(Offset >> 32);
HRESULT hRes = m_File.Write(Data, gsl::narrow<DWORD>(Size), &Ovl);
+ Success = SUCCEEDED(hRes);
+#else
+ int Fd = int(intptr_t(m_File));
+ int BytesWritten = pwrite(Fd, Data, Size, Offset);
+ Success = (BytesWritten > 0);
+#endif
- if (FAILED(hRes))
+ if (Success)
{
- throw std::system_error(GetLastError(), std::system_category(), "Failed to write to file '{}'"_format(zen::PathFromHandle(m_File)));
+ using namespace fmt::literals;
+ zen::ThrowLastError("Failed to write to file '{}'"_format(zen::PathFromHandle(m_File)));
}
}
-
-void
-InternalFile::Flush()
-{
- m_File.Flush();
-}
diff --git a/zen/internalfile.h b/zen/internalfile.h
index c6071418e..ab93dff26 100644
--- a/zen/internalfile.h
+++ b/zen/internalfile.h
@@ -2,17 +2,23 @@
#pragma once
-#pragma warning(push)
-#pragma warning(disable : 4267) // warning C4267: '=': conversion from 'size_t' to 'US', possible loss of data
-#include <cxxopts.hpp>
-#pragma warning(pop)
+#include <zencore/zencore.h>
#include <zencore/iobuffer.h>
#include <zencore/refcount.h>
#include <zencore/thread.h>
-#include <zencore/windows.h>
-#include <atlfile.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cxxopts.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#if ZEN_PLATFORM_WINDOWS
+# include <atlfile.h>
+#endif
#include <filesystem>
#include <list>
@@ -45,13 +51,14 @@ public:
void OpenWrite(std::filesystem::path FileName, bool isCreate);
void Write(const void* Data, uint64_t Size, uint64_t Offset);
- void Flush();
- void* Handle() { return m_File; }
-
const void* MemoryMapFile();
size_t GetFileSize();
private:
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ using CAtlFile = void*;
+ using CAtlFileMappingBase = void*;
+#endif
CAtlFile m_File;
CAtlFileMappingBase m_Mmap;
void* m_Memory = nullptr;
diff --git a/zen/zen.cpp b/zen/zen.cpp
index f5088533f..4cbf75c18 100644
--- a/zen/zen.cpp
+++ b/zen/zen.cpp
@@ -37,7 +37,11 @@ class TemplateCommand : public ZenCmdBase
public:
TemplateCommand() { m_Options.add_options()("r,root", "Root directory for CAS pool", cxxopts::value<std::string>(m_RootDirectory)); }
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override { ZEN_UNUSED(GlobalOptions, argc, argv); }
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override
+ {
+ ZEN_UNUSED(GlobalOptions, argc, argv);
+ return 0;
+ }
virtual cxxopts::Options* Options() override { return &m_Options; }
@@ -58,6 +62,7 @@ public:
ZEN_UNUSED(GlobalOptions);
// Set output mode to handle virtual terminal sequences
+# if ZEN_PLATFORM_WINDOWS
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE)
return GetLastError();
@@ -69,6 +74,7 @@ public:
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(hOut, dwMode))
return GetLastError();
+# endif // ZEN_PLATFORM_WINDOWS
return doctest::Context(argc, argv).run();
}
@@ -261,7 +267,7 @@ main(int argc, char** argv)
for (const CommandInfo& CmdInfo : Commands)
{
- if (_stricmp(SubCommand.c_str(), CmdInfo.CmdName) == 0)
+ if (StrCaseCompare(SubCommand.c_str(), CmdInfo.CmdName) == 0)
{
cxxopts::Options* VerbOptions = CmdInfo.Cmd->Options();
diff --git a/zen/zen.h b/zen/zen.h
index 1c8d102d3..b4d41a094 100644
--- a/zen/zen.h
+++ b/zen/zen.h
@@ -2,13 +2,16 @@
#pragma once
-#pragma warning(push)
-#pragma warning(disable : 4267) // warning C4267: '=': conversion from 'size_t' to 'US', possible loss of data
+#include <zencore/refcount.h>
+#include <zencore/zencore.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
#include <cxxopts.hpp>
-#pragma warning(pop)
+ZEN_THIRD_PARTY_INCLUDES_END
-#include <zencore/refcount.h>
-#include <zencore/windows.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
#include <filesystem>
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
diff --git a/zenhttp/httpasio.cpp b/zenhttp/httpasio.cpp
index 08cefc3bc..e1d417d06 100644
--- a/zenhttp/httpasio.cpp
+++ b/zenhttp/httpasio.cpp
@@ -10,7 +10,9 @@
#include <memory_resource>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <conio.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <conio.h>
+#endif
#include <http_parser.h>
#include <asio.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -405,7 +407,7 @@ HttpServerConnection::OnDataReceived(const asio::error_code& Ec, [[maybe_unused]
ZEN_TRACE_VERBOSE("on data received, connection '{}', request '{}', thread '{}', bytes '{}'",
m_ConnectionId,
m_RequestCounter.load(std::memory_order_relaxed),
- GetCurrentThreadId(),
+ zen::GetCurrentThreadId(),
NiceBytes(ByteCount));
while (m_RequestBuffer.size())
@@ -443,7 +445,7 @@ HttpServerConnection::OnResponseDataSent(const asio::error_code& Ec, [[maybe_unu
ZEN_TRACE_VERBOSE("on data sent, connection '{}', request '{}', thread '{}', bytes '{}'",
m_ConnectionId,
m_RequestCounter.load(std::memory_order_relaxed),
- GetCurrentThreadId(),
+ zen::GetCurrentThreadId(),
NiceBytes(ByteCount));
if (!m_RequestData.IsKeepAlive())
@@ -1071,7 +1073,7 @@ HttpAsioServerRequest::WriteResponse(HttpResponseCode ResponseCode)
m_Response.reset(new HttpResponse(HttpContentType::kBinary));
std::array<IoBuffer, 0> Empty;
- m_Response->InitializeForPayload((UINT16)ResponseCode, Empty);
+ m_Response->InitializeForPayload((uint16_t)ResponseCode, Empty);
}
void
@@ -1080,7 +1082,7 @@ HttpAsioServerRequest::WriteResponse(HttpResponseCode ResponseCode, HttpContentT
ZEN_ASSERT(!m_Response);
m_Response.reset(new HttpResponse(ContentType));
- m_Response->InitializeForPayload((UINT16)ResponseCode, Blobs);
+ m_Response->InitializeForPayload((uint16_t)ResponseCode, Blobs);
}
void
@@ -1220,6 +1222,13 @@ HttpAsioServer::Run(bool IsInteractive)
{
const bool TestMode = !IsInteractive;
+ int WaitTimeout = -1;
+ if (!TestMode)
+ {
+ WaitTimeout = 1000;
+ }
+
+#if ZEN_PLATFORM_WINDOWS
if (TestMode == false)
{
zen::logging::ConsoleLog().info("Zen Server running (asio HTTP). Press ESC or Q to quit");
@@ -1227,13 +1236,6 @@ HttpAsioServer::Run(bool IsInteractive)
do
{
- int WaitTimeout = -1;
-
- if (!TestMode)
- {
- WaitTimeout = 1000;
- }
-
if (!TestMode && _kbhit() != 0)
{
char c = (char)_getch();
@@ -1246,6 +1248,17 @@ HttpAsioServer::Run(bool IsInteractive)
m_ShutdownEvent.Wait(WaitTimeout);
} while (!IsApplicationExitRequested());
+#else
+ if (TestMode == false)
+ {
+ zen::logging::ConsoleLog().info("Zen Server running (asio HTTP). Ctrl-C to quit");
+ }
+
+ do
+ {
+ m_ShutdownEvent.Wait(WaitTimeout);
+ } while (!IsApplicationExitRequested());
+#endif
}
void
diff --git a/zenhttp/httpclient.cpp b/zenhttp/httpclient.cpp
index 6e915e613..e6813d407 100644
--- a/zenhttp/httpclient.cpp
+++ b/zenhttp/httpclient.cpp
@@ -22,7 +22,7 @@ using namespace std::literals;
HttpClient::Response
FromCprResponse(cpr::Response& InResponse)
{
- return {.StatusCode = InResponse.status_code};
+ return {.StatusCode = int(InResponse.status_code)};
}
//////////////////////////////////////////////////////////////////////////
@@ -130,7 +130,7 @@ HttpClient::TransactPackage(std::string_view Url, CbPackage Package)
ResponseBuffer.SetContentType(ContentType);
}
- return {.StatusCode = FilterResponse.status_code, .ResponsePayload = ResponseBuffer};
+ return {.StatusCode = int(FilterResponse.status_code), .ResponsePayload = ResponseBuffer};
}
HttpClient::Response
diff --git a/zenhttp/httpnull.cpp b/zenhttp/httpnull.cpp
index e49051ac5..31b13a6ce 100644
--- a/zenhttp/httpnull.cpp
+++ b/zenhttp/httpnull.cpp
@@ -2,9 +2,12 @@
#include "httpnull.h"
-#include <conio.h>
#include <zencore/logging.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <conio.h>
+#endif
+
namespace zen {
HttpNullServer::HttpNullServer()
@@ -32,6 +35,13 @@ HttpNullServer::Run(bool IsInteractiveSession)
{
const bool TestMode = !IsInteractiveSession;
+ int WaitTimeout = -1;
+ if (!TestMode)
+ {
+ WaitTimeout = 1000;
+ }
+
+#if ZEN_PLATFORM_WINDOWS
if (TestMode == false)
{
zen::logging::ConsoleLog().info("Zen Server running (null HTTP). Press ESC or Q to quit");
@@ -39,13 +49,6 @@ HttpNullServer::Run(bool IsInteractiveSession)
do
{
- int WaitTimeout = -1;
-
- if (!TestMode)
- {
- WaitTimeout = 1000;
- }
-
if (!TestMode && _kbhit() != 0)
{
char c = (char)_getch();
@@ -58,6 +61,17 @@ HttpNullServer::Run(bool IsInteractiveSession)
m_ShutdownEvent.Wait(WaitTimeout);
} while (!IsApplicationExitRequested());
+#else
+ if (TestMode == false)
+ {
+ zen::logging::ConsoleLog().info("Zen Server running (null HTTP). Ctrl-C to quit");
+ }
+
+ do
+ {
+ m_ShutdownEvent.Wait(WaitTimeout);
+ } while (!IsApplicationExitRequested());
+#endif
}
void
diff --git a/zenhttp/httpserver.cpp b/zenhttp/httpserver.cpp
index b1bf99bce..5712563c3 100644
--- a/zenhttp/httpserver.cpp
+++ b/zenhttp/httpserver.cpp
@@ -18,8 +18,6 @@
#include <zencore/thread.h>
#include <zenhttp/httpshared.h>
-#include <conio.h>
-#include <new.h>
#include <charconv>
#include <mutex>
#include <span>
diff --git a/zenhttp/httpshared.cpp b/zenhttp/httpshared.cpp
index c703409af..ab1463559 100644
--- a/zenhttp/httpshared.cpp
+++ b/zenhttp/httpshared.cpp
@@ -109,7 +109,7 @@ FormatPackageMessage(const CbPackage& Data)
}
}
- return std::move(ResponseBuffers);
+ return ResponseBuffers;
}
CbPackage
diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp
index 78cf253cc..a17c2661c 100644
--- a/zenhttp/httpsys.cpp
+++ b/zenhttp/httpsys.cpp
@@ -1187,7 +1187,7 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService&
const HTTP_REQUEST* HttpRequestPtr = Tx.HttpRequest();
const int PrefixLength = Service.UriPrefixLength();
- const int AbsPathLength = HttpRequestPtr->CookedUrl.AbsPathLength / sizeof(char16_t);
+ const int AbsPathLength = HttpRequestPtr->CookedUrl.AbsPathLength / sizeof(wchar_t);
HttpContentType AcceptContentType = HttpContentType::kUnknownContentType;
@@ -1197,7 +1197,7 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService&
// with utf8. This is overhead which I'd prefer to avoid but for now we just have
// to live with it
- WideToUtf8({(char16_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow<size_t>(AbsPathLength - PrefixLength)},
+ WideToUtf8({(wchar_t*)HttpRequestPtr->CookedUrl.pAbsPath + PrefixLength, gsl::narrow<size_t>(AbsPathLength - PrefixLength)},
m_UriUtf8);
std::string_view UriSuffix8{m_UriUtf8};
@@ -1234,7 +1234,7 @@ HttpSysServerRequest::HttpSysServerRequest(HttpSysTransaction& Tx, HttpService&
{
--QueryStringLength; // We skip the leading question mark
- WideToUtf8({(char16_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(char16_t)}, m_QueryStringUtf8);
+ WideToUtf8({(wchar_t*)(HttpRequestPtr->CookedUrl.pQueryString) + 1, QueryStringLength / sizeof(wchar_t)}, m_QueryStringUtf8);
}
else
{
diff --git a/zenhttp/include/zenhttp/httpclient.h b/zenhttp/include/zenhttp/httpclient.h
index 9ece86111..8316a9b9f 100644
--- a/zenhttp/include/zenhttp/httpclient.h
+++ b/zenhttp/include/zenhttp/httpclient.h
@@ -8,8 +8,6 @@
#include <zencore/uid.h>
#include <zenhttp/httpcommon.h>
-#include <zencore/windows.h>
-
ZEN_THIRD_PARTY_INCLUDES_START
#include <cpr/cpr.h>
ZEN_THIRD_PARTY_INCLUDES_END
diff --git a/zenhttp/include/zenhttp/httpserver.h b/zenhttp/include/zenhttp/httpserver.h
index b32359d67..902310f04 100644
--- a/zenhttp/include/zenhttp/httpserver.h
+++ b/zenhttp/include/zenhttp/httpserver.h
@@ -48,7 +48,7 @@ public:
if (Key.size() == ParamName.size())
{
- if (0 == _strnicmp(Key.data(), ParamName.data(), Key.size()))
+ if (0 == StrCaseCompare(Key.data(), ParamName.data(), Key.size()))
{
return Kv.second;
}
diff --git a/zenhttp/include/zenhttp/httpshared.h b/zenhttp/include/zenhttp/httpshared.h
index 2e728577d..a6a61485f 100644
--- a/zenhttp/include/zenhttp/httpshared.h
+++ b/zenhttp/include/zenhttp/httpshared.h
@@ -36,7 +36,10 @@ struct CbPackageHeader
static_assert(sizeof(CbPackageHeader) == 16);
-static constinit uint32_t kCbPkgMagic = 0xaa77aacc;
+enum : uint32_t
+{
+ kCbPkgMagic = 0xaa77aacc
+};
struct CbAttachmentEntry
{
diff --git a/zenhttp/workthreadpool.h b/zenhttp/workthreadpool.h
index 6581cc08f..834339d50 100644
--- a/zenhttp/workthreadpool.h
+++ b/zenhttp/workthreadpool.h
@@ -6,7 +6,6 @@
#include <zencore/blockingqueue.h>
#include <zencore/refcount.h>
-#include <zencore/windows.h>
#include <exception>
#include <functional>
diff --git a/zenhttp/xmake.lua b/zenhttp/xmake.lua
index fff4fb526..a94069c17 100644
--- a/zenhttp/xmake.lua
+++ b/zenhttp/xmake.lua
@@ -3,5 +3,8 @@ target('zenhttp')
add_files("**.cpp")
add_includedirs("include", {public=true})
add_deps("zencore")
- add_packages("vcpkg::gsl-lite")
+ add_packages(
+ "vcpkg::gsl-lite",
+ "vcpkg::http-parser"
+ )
add_options("httpsys") \ No newline at end of file
diff --git a/zenserver-test/projectclient.cpp b/zenserver-test/projectclient.cpp
index 2700ae9da..88608bfe0 100644
--- a/zenserver-test/projectclient.cpp
+++ b/zenserver-test/projectclient.cpp
@@ -2,16 +2,20 @@
#include "projectclient.h"
-#include <zencore/compactbinary.h>
-#include <zencore/logging.h>
-#include <zencore/sharedbuffer.h>
-#include <zencore/string.h>
-#include <zencore/zencore.h>
+#if 0
-#include <asio.hpp>
-#include <gsl/gsl-lite.hpp>
+# include <zencore/compactbinary.h>
+# include <zencore/logging.h>
+# include <zencore/sharedbuffer.h>
+# include <zencore/string.h>
+# include <zencore/zencore.h>
-#include <atlbase.h>
+# include <asio.hpp>
+# include <gsl/gsl-lite.hpp>
+
+# if ZEN_PLATFORM_WINDOWS
+# include <atlbase.h>
+# endif
namespace zen {
@@ -158,3 +162,5 @@ LocalProjectClient::MessageTransaction(CbObject Request)
}
} // namespace zen
+
+#endif // 0
diff --git a/zenserver-test/xmake.lua b/zenserver-test/xmake.lua
index 0e30da12e..44f5669b2 100644
--- a/zenserver-test/xmake.lua
+++ b/zenserver-test/xmake.lua
@@ -2,4 +2,5 @@ target("zenserver-test")
set_kind("binary")
add_files("*.cpp")
add_deps("zencore", "zenutil", "zenhttp")
+ add_deps("zenserver", {inherit=false})
add_packages("vcpkg::http-parser", "vcpkg::mimalloc")
diff --git a/zenserver-test/zenserver-test.cpp b/zenserver-test/zenserver-test.cpp
index ad467753c..2266e5305 100644
--- a/zenserver-test/zenserver-test.cpp
+++ b/zenserver-test/zenserver-test.cpp
@@ -41,16 +41,19 @@ ZEN_THIRD_PARTY_INCLUDES_START
#undef GetObject
ZEN_THIRD_PARTY_INCLUDES_END
-#include <ppl.h>
#include <atomic>
#include <filesystem>
#include <map>
#include <random>
#include <span>
+#include <thread>
#include <unordered_map>
-#include <atlbase.h>
-#include <process.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <ppl.h>
+# include <atlbase.h>
+# include <process.h>
+#endif
#include <asio.hpp>
@@ -71,6 +74,25 @@ ZEN_THIRD_PARTY_INCLUDES_END
using namespace fmt::literals;
using namespace std::literals;
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+struct Concurrency
+{
+ template<typename... T>
+ static void parallel_invoke(T&&... t)
+ {
+ constexpr size_t NumTs = sizeof...(t);
+ std::thread Threads[NumTs] = {
+ std::thread(std::forward<T>(t))...,
+ };
+
+ for (std::thread& Thread : Threads)
+ {
+ Thread.join();
+ }
+ }
+};
+#endif
+
/*
___ ___ _________ _________ ________ ________ ___ ___ _______ ________ _________
@@ -143,7 +165,17 @@ public:
private:
void Reset() {}
- void OnError(const std::error_code& Error) { ZEN_ERROR("HTTP client error! '{}'", Error.message()); }
+ void OnError(const asio::error_code& Error)
+ {
+ // Let EOF errors proceed. They're raised when sockets close.
+ if (Error == asio::error::eof)
+ {
+ return;
+ }
+
+ using namespace fmt::literals;
+ zen::ThrowLastError("HTTP client error! '{}'"_format(Error.message()));
+ }
int OnHeader(const char* Data, size_t Bytes)
{
@@ -185,7 +217,7 @@ private:
// Send initial request payload
asio::async_write(m_Socket,
asio::const_buffer(m_RequestBody.data(), m_RequestBody.size()),
- [this](const std::error_code& Error, size_t Bytes) {
+ [this](const asio::error_code& Error, size_t Bytes) {
ZEN_UNUSED(Bytes);
if (Error)
{
@@ -198,7 +230,7 @@ private:
void OnRequestWritten()
{
- asio::async_read(m_Socket, m_ResponseBuffer, asio::transfer_at_least(1), [this](const std::error_code& Error, size_t Bytes) {
+ asio::async_read(m_Socket, m_ResponseBuffer, asio::transfer_at_least(1), [this](const asio::error_code& Error, size_t Bytes) {
if (Error)
{
return OnError(Error);
@@ -229,7 +261,7 @@ private:
asio::async_read(m_Socket,
m_ResponseBuffer,
asio::transfer_at_least(1),
- [this](const std::error_code& Error, size_t Bytes) {
+ [this](const asio::error_code& Error, size_t Bytes) {
if (Error)
{
return OnError(Error);
@@ -312,6 +344,8 @@ HttpConnectionPool::~HttpConnectionPool()
std::unique_ptr<HttpClientConnection>
HttpConnectionPool::GetConnection()
{
+ using namespace fmt::literals;
+
zen::RwLock::ExclusiveLockScope ScopedLock(m_Lock);
if (m_AvailableConnections.empty())
@@ -321,13 +355,13 @@ HttpConnectionPool::GetConnection()
asio::ip::tcp::resolver Resolver{m_Context};
- std::error_code ErrCode;
- auto it = Resolver.resolve(m_HostName, Service, ErrCode);
- auto itEnd = asio::ip::tcp::resolver::iterator();
+ asio::error_code ErrCode;
+ auto it = Resolver.resolve(m_HostName, Service, ErrCode);
+ auto itEnd = asio::ip::tcp::resolver::iterator();
if (ErrCode)
{
- return nullptr;
+ zen::ThrowLastError("Unabled to resolve '{}'"_format(m_HostName));
}
asio::ip::tcp::socket Socket{m_Context};
@@ -335,7 +369,7 @@ HttpConnectionPool::GetConnection()
if (ErrCode)
{
- return nullptr;
+ zen::ThrowLastError("Failed connecting '{}:{}'"_format(m_HostName, m_Port));
}
return std::make_unique<HttpClientConnection>(m_Context, this, std::move(Socket));
@@ -343,7 +377,7 @@ HttpConnectionPool::GetConnection()
std::unique_ptr<HttpClientConnection> Connection{m_AvailableConnections.back()};
m_AvailableConnections.pop_back();
- return std::move(Connection);
+ return Connection;
}
void
@@ -742,7 +776,7 @@ TEST_CASE("default.single")
auto IssueTestRequests = [&] {
const uint64_t BatchNo = BatchCounter.fetch_add(1);
- const DWORD ThreadId = GetCurrentThreadId();
+ const int ThreadId = zen::GetCurrentThreadId();
ZEN_INFO("query batch {} started (thread {})", BatchNo, ThreadId);
cpr::Session cli;
@@ -756,22 +790,8 @@ TEST_CASE("default.single")
ZEN_INFO("query batch {} ended (thread {})", BatchNo, ThreadId);
};
- auto fun10 = [&] {
- Concurrency::parallel_invoke(IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests,
- IssueTestRequests);
- };
-
zen::Stopwatch timer;
- // Concurrency::parallel_invoke(fun10, fun10, fun, fun, fun, fun, fun, fun, fun, fun);
Concurrency::parallel_invoke(IssueTestRequests,
IssueTestRequests,
IssueTestRequests,
@@ -810,7 +830,7 @@ TEST_CASE("multi.basic")
auto IssueTestRequests = [&](int PortNumber) {
const uint64_t BatchNo = BatchCounter.fetch_add(1);
- const DWORD ThreadId = GetCurrentThreadId();
+ const int ThreadId = zen::GetCurrentThreadId();
ZEN_INFO("query batch {} started (thread {}) for port {}", BatchNo, ThreadId, PortNumber);
@@ -962,6 +982,10 @@ TEST_CASE("project.basic")
zen::StringBuilder<64> BaseUri;
BaseUri << "http://localhost:{}/prj/test"_format(PortNumber);
+ std::filesystem::path BinPath = zen::GetRunningExecutablePath();
+ std::filesystem::path RootPath = BinPath.parent_path().parent_path();
+ BinPath = BinPath.lexically_relative(RootPath);
+
SUBCASE("build store init")
{
{
@@ -969,8 +993,7 @@ TEST_CASE("project.basic")
zen::CbObjectWriter Body;
Body << "id"
<< "test";
- Body << "root"
- << "/zooom";
+ Body << "root" << RootPath.c_str();
Body << "project"
<< "/zooom";
Body << "engine"
@@ -990,7 +1013,7 @@ TEST_CASE("project.basic")
zen::CbObjectView ResponseObject = zen::CbFieldView(Response.text.data()).AsObjectView();
CHECK(ResponseObject["id"].AsString() == "test"sv);
- CHECK(ResponseObject["root"].AsString() == "/zooom"sv);
+ CHECK(ResponseObject["root"].AsString() == PathToUtf8(RootPath.c_str()));
}
}
@@ -1032,13 +1055,12 @@ TEST_CASE("project.basic")
"00010000"};
auto FileOid = zen::Oid::FromHexString(ChunkId);
- std::filesystem::path ReliablePath = zen::GetRunningExecutablePath();
-
OpWriter.BeginArray("files");
OpWriter.BeginObject();
OpWriter << "id" << FileOid;
- OpWriter << "clientpath" << ReliablePath.c_str();
- OpWriter << "serverpath" << ReliablePath.c_str();
+ OpWriter << "clientpath"
+ << "/{engine}/client/side/path";
+ OpWriter << "serverpath" << BinPath.c_str();
OpWriter.EndObject();
OpWriter.EndArray();
@@ -1353,14 +1375,13 @@ TEST_CASE("zcache.cbpackage")
ZenServerInstance RemoteInstance(TestEnv);
RemoteInstance.SetTestDir(RemoteDataDir);
RemoteInstance.SpawnServer(RemotePortNumber);
+ RemoteInstance.WaitUntilReady();
ZenServerInstance LocalInstance(TestEnv);
LocalInstance.SetTestDir(LocalDataDir);
LocalInstance.SpawnServer(LocalPortNumber,
"--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}"_format(RemotePortNumber));
-
LocalInstance.WaitUntilReady();
- RemoteInstance.WaitUntilReady();
const std::string_view Bucket = "mosdef"sv;
zen::IoHash Key;
@@ -1417,14 +1438,13 @@ TEST_CASE("zcache.cbpackage")
ZenServerInstance RemoteInstance(TestEnv);
RemoteInstance.SetTestDir(RemoteDataDir);
RemoteInstance.SpawnServer(RemotePortNumber);
+ RemoteInstance.WaitUntilReady();
ZenServerInstance LocalInstance(TestEnv);
LocalInstance.SetTestDir(LocalDataDir);
LocalInstance.SpawnServer(LocalPortNumber,
"--upstream-thread-count=0 --upstream-zen-url=http://localhost:{}"_format(RemotePortNumber));
-
LocalInstance.WaitUntilReady();
- RemoteInstance.WaitUntilReady();
const std::string_view Bucket = "mosdef"sv;
zen::IoHash Key;
@@ -1810,6 +1830,8 @@ TEST_CASE("zcache.policy")
const bool Ok = Package.TryLoad(Body);
CHECK(Ok);
+ CHECK(Ok);
+
CbObject CacheRecord = Package.GetObject();
std::vector<IoHash> AttachmentKeys;
@@ -1830,6 +1852,7 @@ TEST_CASE("zcache.policy")
const bool Ok = Package.TryLoad(Body);
CHECK(Ok);
+ CHECK(Ok);
CHECK(Package.GetAttachments().size() != 0);
}
}
@@ -1951,7 +1974,7 @@ TEST_CASE("zcache.rpc")
for (uint32_t Key = 1; Key <= Num; ++Key)
{
- const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, zen::IoHash::HashBuffer(&Key, sizeof uint32_t));
+ const zen::CacheKey CacheKey = zen::CacheKey::Create(Bucket, zen::IoHash::HashBuffer(&Key, sizeof(uint32_t)));
CbPackage CacheRecord = CreateCacheRecord(CacheKey, PayloadSize);
OutKeys.push_back(CacheKey);
@@ -2018,8 +2041,6 @@ TEST_CASE("zcache.rpc")
for (CbFieldView RecordView : ResponseObject["Result"])
{
- ExtendableStringBuilder<256> Tmp;
- auto JSON = RecordView.AsObjectView().ToJson(Tmp).ToView();
OutResult.Records.push_back(RecordView);
}
@@ -2108,7 +2129,6 @@ TEST_CASE("zcache.rpc")
{
const CacheKey& ExpectedKey = ExistingKeys[KeyIndex++];
CbObjectView RecordObj = RecordView.AsObjectView();
- CbObjectView KeyObj = RecordObj["CacheKey"sv].AsObjectView();
zen::CacheKey Key = LoadKey(RecordObj["CacheKey"sv]);
const IoHash AttachmentHash = RecordObj["Data"sv].AsHash();
const CbAttachment* Attachment = Result.Response.FindAttachment(AttachmentHash);
@@ -2143,10 +2163,9 @@ TEST_CASE("zcache.rpc")
{
const CacheKey& ExpectedKey = Keys[Index++];
- CbObjectView RecordObj = RecordView.AsObjectView();
- CbObjectView KeyObj = RecordObj["CacheKey"sv].AsObjectView();
- const CacheKey Key = CacheKey::Create(KeyObj["Bucket"sv].AsString(), KeyObj["Hash"].AsHash());
- const IoHash AttachmentHash = RecordObj["Data"sv].AsHash();
+ CbObjectView RecordObj = RecordView.AsObjectView();
+ CbObjectView KeyObj = RecordObj["CacheKey"sv].AsObjectView();
+ const CacheKey Key = CacheKey::Create(KeyObj["Bucket"sv].AsString(), KeyObj["Hash"].AsHash());
CHECK(Key == ExpectedKey);
}
@@ -2232,7 +2251,7 @@ struct RemoteExecutionRequest
for (const auto& Kv : m_Visit.m_Files)
{
PrepReq.BeginObject();
- PrepReq << "file" << zen::WideToUtf8(Kv.first) << "size" << Kv.second.Size << "hash" << Kv.second.Hash;
+ PrepReq << "file" << zen::PathToUtf8(Kv.first) << "size" << Kv.second.Size << "hash" << Kv.second.Hash;
PrepReq.EndObject();
}
PrepReq.EndArray();
@@ -2255,7 +2274,7 @@ struct RemoteExecutionRequest
if (auto It = m_Visit.m_HashToFile.find(NeedHash); It != m_Visit.m_HashToFile.end())
{
- zen::IoBuffer FileData = zen::IoBufferBuilder::MakeFromFile(It->second.c_str());
+ zen::IoBuffer FileData = zen::IoBufferBuilder::MakeFromFile(It->second);
cpr::Response CasResponse = cpr::Post(cpr::Url(m_CasUri), cpr::Body((const char*)FileData.Data(), FileData.Size()));
@@ -2293,7 +2312,7 @@ private:
Visitor(const std::filesystem::path& RootPath) : m_RootPath(RootPath) {}
- virtual void VisitFile(const std::filesystem::path& Parent, const std::wstring_view& FileName, uint64_t FileSize) override
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& FileName, uint64_t FileSize) override
{
std::filesystem::path FullPath = Parent / FileName;
@@ -2301,8 +2320,8 @@ private:
zen::ScanFile(FullPath, 64 * 1024, [&](const void* Data, size_t Size) { Ios.Append(Data, Size); });
zen::IoHash Hash = Ios.GetHash();
- std::wstring RelativePath = FullPath.lexically_relative(m_RootPath).native();
- // ZEN_INFO("File: {:32} => {} ({})", zen::WideToUtf8(RelativePath), Hash, FileSize);
+ auto RelativePath = FullPath.lexically_relative(m_RootPath).native();
+ // ZEN_INFO("File: {:32} => {} ({})", zen::PathToUtf8(RelativePath), Hash, FileSize);
FileEntry& Entry = m_Files[RelativePath];
Entry.Hash = Hash;
@@ -2311,11 +2330,11 @@ private:
m_HashToFile[Hash] = FullPath;
}
- virtual bool VisitDirectory(const std::filesystem::path& Parent, const std::wstring_view& DirectoryName) override
+ virtual bool VisitDirectory(const std::filesystem::path& Parent, const path_view& DirectoryName) override
{
std::filesystem::path FullPath = Parent / DirectoryName;
- if (DirectoryName.starts_with(L"."))
+ if (DirectoryName.starts_with('.'))
{
return false;
}
@@ -2329,7 +2348,7 @@ private:
zen::IoHash Hash;
};
- std::map<std::wstring, FileEntry> m_Files;
+ std::map<std::filesystem::path::string_type, FileEntry> m_Files;
std::unordered_map<zen::IoHash, std::filesystem::path, zen::IoHash::Hasher> m_HashToFile;
};
@@ -2344,6 +2363,7 @@ private:
TEST_CASE("exec.basic")
{
+# if ZEN_WITH_COMPUTE_SERVICES
using namespace std::literals;
std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
@@ -2374,6 +2394,7 @@ TEST_CASE("exec.basic")
CHECK(Result["exitcode"sv].AsInt32(-1) == 1);
}
+# endif // ZEN_WITH_COMPUTE_SERVICES
}
TEST_CASE("mesh.basic")
diff --git a/zenserver/admin/admin.cpp b/zenserver/admin/admin.cpp
index 15314b12a..01c35a1d8 100644
--- a/zenserver/admin/admin.cpp
+++ b/zenserver/admin/admin.cpp
@@ -1,7 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#pragma once
-
#include "admin.h"
#include <zencore/compactbinarybuilder.h>
diff --git a/zenserver/cache/cachetracking.cpp b/zenserver/cache/cachetracking.cpp
index d1c99a597..ae132f37f 100644
--- a/zenserver/cache/cachetracking.cpp
+++ b/zenserver/cache/cachetracking.cpp
@@ -2,24 +2,26 @@
#include "cachetracking.h"
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinaryvalue.h>
-#include <zencore/endian.h>
-#include <zencore/filesystem.h>
-#include <zencore/logging.h>
-#include <zencore/scopeguard.h>
-#include <zencore/string.h>
+#if ZEN_USE_CACHE_TRACKER
-#include <zencore/testing.h>
-#include <zencore/testutils.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinaryvalue.h>
+# include <zencore/endian.h>
+# include <zencore/filesystem.h>
+# include <zencore/logging.h>
+# include <zencore/scopeguard.h>
+# include <zencore/string.h>
+
+# include <zencore/testing.h>
+# include <zencore/testutils.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#pragma comment(lib, "Rpcrt4.lib") // RocksDB made me do this
-#include <fmt/format.h>
-#include <rocksdb/db.h>
-#include <tsl/robin_map.h>
-#include <tsl/robin_set.h>
-#include <gsl/gsl-lite.hpp>
+# pragma comment(lib, "Rpcrt4.lib") // RocksDB made me do this
+# include <fmt/format.h>
+# include <rocksdb/db.h>
+# include <tsl/robin_map.h>
+# include <tsl/robin_set.h>
+# include <gsl/gsl-lite.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
@@ -150,7 +152,7 @@ struct ZenCacheTracker::Impl
{
std::filesystem::path StatsDbPath{StateDirectory / ".zdb"};
- std::string RocksdbPath = ToUtf8(StatsDbPath);
+ std::string RocksdbPath = StatsDbPath.string();
ZEN_DEBUG("opening tracker db at '{}'", RocksdbPath);
@@ -291,7 +293,7 @@ ZenCacheTracker::IterateSnapshots(std::function<void(uint64_t TimeStamp, CbObjec
m_Impl->IterateSnapshots(std::move(Callback));
}
-#if ZEN_WITH_TESTS
+# if ZEN_WITH_TESTS
TEST_CASE("z$.tracker")
{
@@ -365,7 +367,7 @@ TEST_CASE("z$.tracker")
CHECK_EQ(SnapshotCount, 11);
}
-#endif
+# endif
void
cachetracker_forcelink()
@@ -373,3 +375,5 @@ cachetracker_forcelink()
}
} // namespace zen
+
+#endif // ZEN_USE_CACHE_TRACKER
diff --git a/zenserver/cache/cachetracking.h b/zenserver/cache/cachetracking.h
index 06109ebb0..fdfe1a4c7 100644
--- a/zenserver/cache/cachetracking.h
+++ b/zenserver/cache/cachetracking.h
@@ -10,6 +10,9 @@
namespace zen {
+#define ZEN_USE_CACHE_TRACKER 0
+#if ZEN_USE_CACHE_TRACKER
+
class CbObject;
/**
@@ -33,4 +36,6 @@ private:
void cachetracker_forcelink();
+#endif // ZEN_USE_CACHE_TRACKER
+
} // namespace zen
diff --git a/zenserver/cache/structuredcache.cpp b/zenserver/cache/structuredcache.cpp
index 22c8c4afb..b322a5d1b 100644
--- a/zenserver/cache/structuredcache.cpp
+++ b/zenserver/cache/structuredcache.cpp
@@ -13,7 +13,7 @@
#include <zencore/stream.h>
#include <zencore/timer.h>
#include <zenhttp/httpserver.h>
-#include <zenstore/CAS.h>
+#include <zenstore/cas.h>
#include <zenutil/cache/cache.h>
//#include "cachekey.h"
@@ -152,7 +152,7 @@ void
HttpStructuredCacheService::HandleCacheBucketRequest(HttpServerRequest& Request, std::string_view Bucket)
{
ZEN_UNUSED(Request, Bucket);
- switch (auto Verb = Request.RequestVerb())
+ switch (Request.RequestVerb())
{
using enum HttpVerb;
@@ -175,13 +175,16 @@ HttpStructuredCacheService::HandleCacheBucketRequest(HttpServerRequest& Request,
return Request.WriteResponse(HttpResponseCode::NotFound);
}
break;
+
+ default:
+ break;
}
}
void
HttpStructuredCacheService::HandleCacheRecordRequest(HttpServerRequest& Request, const CacheRef& Ref, CachePolicy Policy)
{
- switch (auto Verb = Request.RequestVerb())
+ switch (Request.RequestVerb())
{
using enum HttpVerb;
@@ -442,7 +445,7 @@ HttpStructuredCacheService::HandlePutCacheRecord(zen::HttpServerRequest& Request
if (StoreUpstream)
{
ZEN_ASSERT(m_UpstreamCache);
- auto Result = m_UpstreamCache->EnqueueUpstream({.Type = ZenContentType::kBinary, .CacheKey = {Ref.BucketSegment, Ref.HashKey}});
+ m_UpstreamCache->EnqueueUpstream({.Type = ZenContentType::kBinary, .Key = {Ref.BucketSegment, Ref.HashKey}});
}
Request.WriteResponse(HttpResponseCode::Created);
@@ -486,9 +489,8 @@ HttpStructuredCacheService::HandlePutCacheRecord(zen::HttpServerRequest& Request
if (StoreUpstream && !IsPartialRecord)
{
ZEN_ASSERT(m_UpstreamCache);
- auto Result = m_UpstreamCache->EnqueueUpstream({.Type = ZenContentType::kCbObject,
- .CacheKey = {Ref.BucketSegment, Ref.HashKey},
- .PayloadIds = std::move(ValidAttachments)});
+ m_UpstreamCache->EnqueueUpstream(
+ {.Type = ZenContentType::kCbObject, .Key = {Ref.BucketSegment, Ref.HashKey}, .PayloadIds = std::move(ValidAttachments)});
}
Request.WriteResponse(HttpResponseCode::Created);
@@ -568,9 +570,8 @@ HttpStructuredCacheService::HandlePutCacheRecord(zen::HttpServerRequest& Request
if (StoreUpstream && !IsPartialRecord)
{
ZEN_ASSERT(m_UpstreamCache);
- auto Result = m_UpstreamCache->EnqueueUpstream({.Type = ZenContentType::kCbPackage,
- .CacheKey = {Ref.BucketSegment, Ref.HashKey},
- .PayloadIds = std::move(ValidAttachments)});
+ m_UpstreamCache->EnqueueUpstream(
+ {.Type = ZenContentType::kCbPackage, .Key = {Ref.BucketSegment, Ref.HashKey}, .PayloadIds = std::move(ValidAttachments)});
}
Request.WriteResponse(HttpResponseCode::Created);
@@ -584,7 +585,7 @@ HttpStructuredCacheService::HandlePutCacheRecord(zen::HttpServerRequest& Request
void
HttpStructuredCacheService::HandleCachePayloadRequest(HttpServerRequest& Request, const CacheRef& Ref, CachePolicy Policy)
{
- switch (auto Verb = Request.RequestVerb())
+ switch (Request.RequestVerb())
{
using enum HttpVerb;
@@ -768,7 +769,7 @@ HttpStructuredCacheService::ValidateKeyUri(HttpServerRequest& Request, CacheRef&
void
HttpStructuredCacheService::HandleRpcRequest(zen::HttpServerRequest& Request)
{
- switch (auto Verb = Request.RequestVerb())
+ switch (Request.RequestVerb())
{
using enum HttpVerb;
@@ -927,8 +928,8 @@ HttpStructuredCacheService::HandleRpcGetCacheRecords(zen::HttpServerRequest& Req
{
ZEN_DEBUG("Uncompressed payload '{}' from upstream cache record '{}/{}'",
HashView.AsHash(),
- Params.CacheKey.Bucket,
- Params.CacheKey.Hash);
+ Params.Key.Bucket,
+ Params.Key.Hash);
Count.Invalid++;
}
}
@@ -948,8 +949,8 @@ HttpStructuredCacheService::HandleRpcGetCacheRecords(zen::HttpServerRequest& Req
if (CacheValue)
{
ZEN_DEBUG("HIT - '{}/{}' {} '{}' attachments '{}/{}/{}' (new/valid/total) (UPSTREAM)",
- Params.CacheKey.Bucket,
- Params.CacheKey.Hash,
+ Params.Key.Bucket,
+ Params.Key.Hash,
NiceBytes(CacheValue.GetSize()),
ToString(HttpContentType::kCbPackage),
Count.New,
@@ -959,7 +960,7 @@ HttpStructuredCacheService::HandleRpcGetCacheRecords(zen::HttpServerRequest& Req
CacheValue.SetContentType(ZenContentType::kCbObject);
CacheValues[Params.KeyIndex] = CacheValue;
- m_CacheStore.Put(Params.CacheKey.Bucket, Params.CacheKey.Hash, {.Value = CacheValue});
+ m_CacheStore.Put(Params.Key.Bucket, Params.Key.Hash, {.Value = CacheValue});
m_CacheStats.HitCount++;
m_CacheStats.UpstreamHitCount++;
@@ -967,7 +968,7 @@ HttpStructuredCacheService::HandleRpcGetCacheRecords(zen::HttpServerRequest& Req
else
{
const bool IsPartial = Count.Valid != Count.Total;
- ZEN_DEBUG("MISS - '{}/{}' {}", Params.CacheKey.Bucket, Params.CacheKey.Hash, IsPartial ? "(partial)"sv : ""sv);
+ ZEN_DEBUG("MISS - '{}/{}' {}", Params.Key.Bucket, Params.Key.Hash, IsPartial ? "(partial)"sv : ""sv);
m_CacheStats.MissCount++;
}
};
@@ -1135,7 +1136,7 @@ HttpStructuredCacheService::HandleRpcGetCachePayloads(zen::HttpServerRequest& Re
const auto OnCachePayloadGetComplete = [this, &ChunkRequests, &Chunks](CachePayloadGetCompleteParams&& Params) {
if (CompressedBuffer Compressed = CompressedBuffer::FromCompressed(SharedBuffer(Params.Payload)))
{
- auto InsertResult = m_CidStore.AddChunk(Compressed);
+ m_CidStore.AddChunk(Compressed);
ZEN_DEBUG("HIT - '{}/{}/{}' {} ({})",
Params.Request.Key.Bucket,
diff --git a/zenserver/cache/structuredcachestore.cpp b/zenserver/cache/structuredcachestore.cpp
index 44c7b728b..718a8db51 100644
--- a/zenserver/cache/structuredcachestore.cpp
+++ b/zenserver/cache/structuredcachestore.cpp
@@ -2,7 +2,7 @@
#include "structuredcachestore.h"
-#include "cachetracking.h"
+#include <zencore/except.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinarypackage.h>
@@ -23,7 +23,10 @@
#include <zenstore/caslog.h>
#include <zenstore/cidstore.h>
-#include <chrono>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
#include <concepts>
#include <memory_resource>
#include <ranges>
@@ -38,8 +41,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
using namespace fmt::literals;
-using PathBuilder = WideStringBuilder<256>;
-namespace fs = std::filesystem;
+namespace fs = std::filesystem;
static CbObject
LoadCompactBinaryObject(const fs::path& Path)
@@ -487,7 +489,7 @@ private:
uint64_t m_SobsCursor = 0;
std::atomic_uint64_t m_TotalSize{};
- void BuildPath(WideStringBuilderBase& Path, const IoHash& HashKey);
+ void BuildPath(PathBuilderBase& Path, const IoHash& HashKey);
void PutStandaloneCacheValue(const IoHash& HashKey, const ZenCacheValue& Value);
bool GetStandaloneCacheValue(const DiskLocation& Loc, const IoHash& HashKey, ZenCacheValue& OutValue);
void DeleteStandaloneCacheValue(const DiskLocation& Loc, const IoHash& HashKey, const fs::path& Path, std::error_code& Ec);
@@ -619,17 +621,17 @@ ZenCacheDiskLayer::CacheBucket::OpenLog(const fs::path& BucketDir, const bool Is
}
void
-ZenCacheDiskLayer::CacheBucket::BuildPath(WideStringBuilderBase& Path, const IoHash& HashKey)
+ZenCacheDiskLayer::CacheBucket::BuildPath(PathBuilderBase& Path, const IoHash& HashKey)
{
char HexString[sizeof(HashKey.Hash) * 2];
ToHexBytes(HashKey.Hash, sizeof HashKey.Hash, HexString);
- Path.Append(m_BucketDir.c_str());
+ Path.Append(m_BucketDir);
Path.Append(L"/blob/");
Path.AppendAsciiRange(HexString, HexString + 3);
- Path.Append(L"/");
+ Path.AppendSeparator();
Path.AppendAsciiRange(HexString + 3, HexString + 5);
- Path.Append(L"/");
+ Path.AppendSeparator();
Path.AppendAsciiRange(HexString + 5, HexString + sizeof(HexString));
}
@@ -650,12 +652,12 @@ ZenCacheDiskLayer::CacheBucket::GetInlineCacheValue(const DiskLocation& Loc, Zen
bool
ZenCacheDiskLayer::CacheBucket::GetStandaloneCacheValue(const DiskLocation& Loc, const IoHash& HashKey, ZenCacheValue& OutValue)
{
- PathBuilder DataFilePath;
+ PathBuilder<256> DataFilePath;
BuildPath(DataFilePath, HashKey);
RwLock::SharedLockScope ValueLock(LockForHash(HashKey));
- if (IoBuffer Data = IoBufferBuilder::MakeFromFile(DataFilePath.c_str()))
+ if (IoBuffer Data = IoBufferBuilder::MakeFromFile(DataFilePath.ToPath()))
{
OutValue.Value = Data;
OutValue.Value.SetContentType(Loc.GetContentType());
@@ -970,8 +972,8 @@ ZenCacheDiskLayer::CacheBucket::CollectGarbage(GcContext& GcCtx)
// Remove all standalone file(s)
// NOTE: This can probably be made asynchronously
{
- std::error_code Ec;
- PathBuilder Path;
+ std::error_code Ec;
+ PathBuilder<256> Path;
for (const auto& Entry : ExpiredEntries)
{
@@ -988,7 +990,7 @@ ZenCacheDiskLayer::CacheBucket::CollectGarbage(GcContext& GcCtx)
if (Ec)
{
- ZEN_WARN("delete expired z$ standalone file '{}' FAILED, reason '{}'", WideToUtf8(Path.ToString()), Ec.message());
+ ZEN_WARN("delete expired z$ standalone file '{}' FAILED, reason '{}'", Path.ToUtf8(), Ec.message());
Ec.clear();
}
}
@@ -1168,7 +1170,7 @@ ZenCacheDiskLayer::CacheBucket::PutStandaloneCacheValue(const IoHash& HashKey, c
{
RwLock::ExclusiveLockScope ValueLock(LockForHash(HashKey));
- PathBuilder DataFilePath;
+ PathBuilder<256> DataFilePath;
BuildPath(DataFilePath, HashKey);
TemporaryFile DataFile;
@@ -1190,7 +1192,7 @@ ZenCacheDiskLayer::CacheBucket::PutStandaloneCacheValue(const IoHash& HashKey, c
// Move file into place (atomically)
- std::filesystem::path FsPath{DataFilePath.c_str()};
+ std::filesystem::path FsPath{DataFilePath.ToPath()};
DataFile.MoveTemporaryIntoPlace(FsPath, Ec);
@@ -1200,7 +1202,7 @@ ZenCacheDiskLayer::CacheBucket::PutStandaloneCacheValue(const IoHash& HashKey, c
do
{
- std::filesystem::path ParentPath = std::filesystem::path(DataFilePath.c_str()).parent_path();
+ std::filesystem::path ParentPath = FsPath.parent_path();
CreateDirectories(ParentPath);
DataFile.MoveTemporaryIntoPlace(FsPath, Ec);
@@ -1225,7 +1227,7 @@ ZenCacheDiskLayer::CacheBucket::PutStandaloneCacheValue(const IoHash& HashKey, c
if (Ec)
{
- throw std::system_error(Ec, "Failed to finalize file '{}'"_format(WideToUtf8(DataFilePath)));
+ throw std::system_error(Ec, "Failed to finalize file '{}'"_format(DataFilePath.ToUtf8()));
}
}
@@ -1371,11 +1373,11 @@ ZenCacheDiskLayer::DiscoverBuckets()
virtual bool VisitDirectory([[maybe_unused]] const std::filesystem::path& Parent, const path_view& DirectoryName) override
{
- Dirs.push_back(std::wstring(DirectoryName));
+ Dirs.push_back((decltype(Dirs)::value_type)(DirectoryName));
return false;
}
- std::vector<std::wstring> Dirs;
+ std::vector<std::filesystem::path::string_type> Dirs;
} Visit;
Traversal.TraverseFileSystem(m_RootDir, Visit);
@@ -1384,11 +1386,15 @@ ZenCacheDiskLayer::DiscoverBuckets()
RwLock::ExclusiveLockScope _(m_Lock);
- for (const std::wstring& BucketName : Visit.Dirs)
+ for (const auto& BucketName : Visit.Dirs)
{
// New bucket needs to be created
- const std::string BucketName8 = ToUtf8(BucketName);
+#if ZEN_PLATFORM_WINDOWS
+ std::string BucketName8 = WideToUtf8(BucketName);
+#else
+ const auto& BucketName8 = BucketName;
+#endif
if (auto It = m_Buckets.find(BucketName8); It != m_Buckets.end())
{
diff --git a/zenserver/cache/structuredcachestore.h b/zenserver/cache/structuredcachestore.h
index b64b1353e..41b47b409 100644
--- a/zenserver/cache/structuredcachestore.h
+++ b/zenserver/cache/structuredcachestore.h
@@ -23,9 +23,9 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
+class PathBuilderBase;
class CasStore;
class CasGc;
-class WideStringBuilderBase;
class ZenCacheTracker;
/******************************************************************************
diff --git a/zenserver/casstore.cpp b/zenserver/casstore.cpp
index 88525bd36..872a40df8 100644
--- a/zenserver/casstore.cpp
+++ b/zenserver/casstore.cpp
@@ -51,6 +51,9 @@ HttpCasService::HttpCasService(CasStore& Store) : m_CasStore(Store)
return ServerRequest.WriteResponse(HttpResponseCode::OK);
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPut | HttpVerb::kHead);
diff --git a/zenserver/compute/apply.cpp b/zenserver/compute/apply.cpp
index 11ae8b41d..d483fd4f7 100644
--- a/zenserver/compute/apply.cpp
+++ b/zenserver/compute/apply.cpp
@@ -2,35 +2,35 @@
#include "apply.h"
-#include <upstream/jupiter.h>
-#include <upstream/upstreamapply.h>
-#include <zencore/compactbinary.h>
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinarypackage.h>
-#include <zencore/compress.h>
-#include <zencore/except.h>
-#include <zencore/filesystem.h>
-#include <zencore/fmtutils.h>
-#include <zencore/iobuffer.h>
-#include <zencore/iohash.h>
-#include <zencore/scopeguard.h>
-#include <zencore/windows.h>
-#include <zenstore/CAS.h>
-#include <zenstore/cidstore.h>
-
-#if ZEN_PLATFORM_WINDOWS
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include <upstream/jupiter.h>
+# include <upstream/upstreamapply.h>
+# include <zencore/compactbinary.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compress.h>
+# include <zencore/except.h>
+# include <zencore/filesystem.h>
+# include <zencore/fmtutils.h>
+# include <zencore/iobuffer.h>
+# include <zencore/iohash.h>
+# include <zencore/scopeguard.h>
+# include <zenstore/cas.h>
+# include <zenstore/cidstore.h>
+
+# include <zencore/windows.h>
ZEN_THIRD_PARTY_INCLUDES_START
# include <AccCtrl.h>
# include <AclAPI.h>
-# include <sddl.h>
# include <UserEnv.h>
+# include <sddl.h>
# pragma comment(lib, "UserEnv.lib")
# include <atlbase.h>
ZEN_THIRD_PARTY_INCLUDES_END
-#endif
-#include <filesystem>
-#include <span>
+# include <filesystem>
+# include <span>
using namespace std::literals;
@@ -136,6 +136,8 @@ BasicFunctionJob::ExitCode()
return gsl::narrow_cast<int>(Ec);
}
+////////////////////////////////////////////////////////////////////////////////
+
struct SandboxedFunctionJob
{
SandboxedFunctionJob() = default;
@@ -326,6 +328,8 @@ SandboxedFunctionJob::SpawnJob(std::filesystem::path ExePath)
return true;
}
+////////////////////////////////////////////////////////////////////////////////
+
HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore, const std::filesystem::path& BaseDir)
: m_Log(logging::Get("apply"))
, m_CasStore(Store)
@@ -448,6 +452,8 @@ HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore,
SharedBuffer Decompressed = DataView.Decompress();
const uint64_t DecompressedSize = DataView.GetRawSize();
+ ZEN_UNUSED(DataHash);
+
TotalAttachmentBytes += DecompressedSize;
++AttachmentCount;
@@ -478,9 +484,15 @@ HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore,
return HttpReq.WriteResponse(HttpResponseCode::NoContent);
}
break;
+
+ default:
+ break;
}
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost);
@@ -497,6 +509,9 @@ HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore,
case HttpVerb::kPost:
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost);
@@ -622,6 +637,8 @@ HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore,
const IoHash DataHash = Attachment.GetHash();
CompressedBuffer DataView = Attachment.AsCompressedBinary();
+ ZEN_UNUSED(DataHash);
+
const uint64_t CompressedSize = DataView.GetCompressedSize();
TotalAttachmentBytes += CompressedSize;
@@ -652,8 +669,14 @@ HttpFunctionService::HttpFunctionService(CasStore& Store, CidStore& InCidStore,
return HttpReq.WriteResponse(HttpResponseCode::OK, Output);
}
break;
+
+ default:
+ break;
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kPost);
@@ -958,3 +981,5 @@ HttpFunctionService::ExecActionUpstreamResult(const IoHash& WorkerId, const IoHa
}
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/compute/apply.h b/zenserver/compute/apply.h
index 0d6edf119..161e47e06 100644
--- a/zenserver/compute/apply.h
+++ b/zenserver/compute/apply.h
@@ -2,13 +2,21 @@
#pragma once
-#include <zencore/compactbinary.h>
-#include <zencore/iohash.h>
-#include <zencore/logging.h>
-#include <zenhttp/httpserver.h>
+#include <zencore/zencore.h>
-#include <filesystem>
-#include <unordered_map>
+#if !defined(ZEN_WITH_COMPUTE_SERVICES)
+# define ZEN_WITH_COMPUTE_SERVICES ZEN_PLATFORM_WINDOWS
+#endif
+
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include <zencore/compactbinary.h>
+# include <zencore/iohash.h>
+# include <zencore/logging.h>
+# include <zenhttp/httpserver.h>
+
+# include <filesystem>
+# include <unordered_map>
namespace zen {
@@ -54,3 +62,5 @@ private:
};
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/config.cpp b/zenserver/config.cpp
index 5e5676494..b25d05409 100644
--- a/zenserver/config.cpp
+++ b/zenserver/config.cpp
@@ -9,16 +9,18 @@
#include <zencore/string.h>
#include <zenhttp/zenhttp.h>
-#pragma warning(push)
-#pragma warning(disable : 4267) // warning C4267: '=': conversion from 'size_t' to 'US', possible loss of data
-#include <cxxopts.hpp>
-#pragma warning(pop)
-
+ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
#include <zencore/logging.h>
+#include <cxxopts.hpp>
#include <sol/sol.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
-#include <conio.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <conio.h>
+#else
+# include <pwd.h>
+#endif
#if ZEN_PLATFORM_WINDOWS
@@ -86,7 +88,9 @@ PickDefaultStateDirectory()
std::filesystem::path
PickDefaultStateDirectory()
{
- return std::filesystem::path("~/.zen");
+ int UserId = getuid();
+ const passwd* Passwd = getpwuid(UserId);
+ return std::filesystem::path(Passwd->pw_dir) / ".zen";
}
#endif
@@ -183,6 +187,22 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
"");
#endif
+#if ZEN_WITH_TRACE
+ options.add_option("ue-trace",
+ "",
+ "tracehost",
+ "Hostname to send the trace to",
+ cxxopts::value<std::string>(ServerOptions.TraceHost)->default_value(""),
+ "");
+
+ options.add_option("ue-trace",
+ "",
+ "tracefile",
+ "Path to write a trace to",
+ cxxopts::value<std::string>(ServerOptions.TraceFile)->default_value(""),
+ "");
+#endif // ZEN_WITH_TRACE
+
options.add_option("diagnostics",
"",
"crash",
@@ -337,8 +357,13 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
if (result.count("help"))
{
zen::logging::ConsoleLog().info("{}", options.help());
+#if ZEN_PLATFORM_WINDOWS
zen::logging::ConsoleLog().info("Press any key to exit!");
_getch();
+#else
+ // Assume the user's in a terminal on all other platforms and that
+ // they'll use less/more/etc. if need be.
+#endif
exit(0);
}
@@ -371,7 +396,7 @@ ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptio
{
using namespace fmt::literals;
- zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path.native().c_str());
+ zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path);
if (LuaScript)
{
@@ -380,6 +405,7 @@ ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptio
lua.open_libraries(sol::lib::base);
lua.set_function("getenv", [&](const std::string env) -> sol::object {
+#if ZEN_PLATFORM_WINDOWS
std::wstring EnvVarValue;
size_t RequiredSize = 0;
std::wstring EnvWide = zen::Utf8ToWide(env);
@@ -391,6 +417,10 @@ ParseConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptio
EnvVarValue.resize(RequiredSize);
_wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str());
return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str()));
+#else
+ ZEN_UNUSED(env);
+ return sol::make_object(lua, sol::lua_nil);
+#endif
});
try
diff --git a/zenserver/config.h b/zenserver/config.h
index 8a507df39..60e9976e0 100644
--- a/zenserver/config.h
+++ b/zenserver/config.h
@@ -2,8 +2,10 @@
#pragma once
+#include <zencore/zencore.h>
#include <filesystem>
#include <string>
+#include <vector>
#ifndef ZEN_ENABLE_MESH
# define ZEN_ENABLE_MESH 0
@@ -17,6 +19,10 @@
# define ZEN_USE_EXEC 0
#endif
+#ifndef ZEN_WITH_TRACE
+# define ZEN_WITH_TRACE 0
+#endif
+
struct ZenUpstreamJupiterConfig
{
std::string Url;
@@ -101,6 +107,10 @@ struct ZenServerOptions
bool StructuredCacheEnabled = true;
bool ShouldCrash = false; // Option for testing crash handling
bool IsFirstRun = false;
+#if ZEN_WITH_TRACE
+ std::string TraceHost; // Host name or IP address to send trace data to
+ std::string TraceFile; // Path of a file to write a trace
+#endif
#if ZEN_ENABLE_MESH
bool MeshEnabled = false; // Experimental p2p mesh discovery
#endif
diff --git a/zenserver/diag/logging.cpp b/zenserver/diag/logging.cpp
index 728001202..3a5da2de9 100644
--- a/zenserver/diag/logging.cpp
+++ b/zenserver/diag/logging.cpp
@@ -171,6 +171,7 @@ private:
bool
EnableVTMode()
{
+#if ZEN_PLATFORM_WINDOWS
// Set output mode to handle virtual terminal sequences
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE)
@@ -189,6 +190,7 @@ EnableVTMode()
{
return false;
}
+#endif
return true;
}
@@ -233,13 +235,13 @@ InitializeLogging(const ZenServerOptions& GlobalOptions)
auto ConsoleSink = std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
#if 0
- auto FileSink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(zen::WideToUtf8(LogPath.c_str()),
+ auto FileSink = std::make_shared<spdlog::sinks::daily_file_sink_mt>(zen::PathToUtf8(LogPath),
0,
0,
/* truncate */ false,
uint16_t(/* max files */ 14));
#else
- auto FileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(zen::ToUtf8(LogPath),
+ auto FileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(zen::PathToUtf8(LogPath),
/* max size */ 128 * 1024 * 1024,
/* max files */ 16,
/* rotate on open */ true);
@@ -267,7 +269,7 @@ InitializeLogging(const ZenServerOptions& GlobalOptions)
std::filesystem::path HttpLogPath = GlobalOptions.DataDir / "logs/http.log";
- auto HttpSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(zen::WideToUtf8(HttpLogPath.c_str()),
+ auto HttpSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(zen::PathToUtf8(HttpLogPath),
/* max size */ 128 * 1024 * 1024,
/* max files */ 16,
/* rotate on open */ true);
diff --git a/zenserver/experimental/frontend.cpp b/zenserver/experimental/frontend.cpp
index 98d570cfe..4bd3ec90a 100644
--- a/zenserver/experimental/frontend.cpp
+++ b/zenserver/experimental/frontend.cpp
@@ -54,7 +54,7 @@ body {
<pre>
__________ _________ __
\____ / ____ ____ / _____/_/ |_ ____ _______ ____
- / / _/ __ \ / \ \_____ \ \ __\ / _ \ \_ __ \_/ __ \
+ / / _/ __ \ / \ \_____ \ \ __\ / _ \ \_ __ \_/ __ \
/ /_ \ ___/ | | \ / \ | | ( <_> ) | | \/\ ___/
/_______ \ \___ >|___| //_______ / |__| \____/ |__| \___ >
\/ \/ \/ \/ \/
diff --git a/zenserver/experimental/usnjournal.cpp b/zenserver/experimental/usnjournal.cpp
index 9422dd485..580d71e45 100644
--- a/zenserver/experimental/usnjournal.cpp
+++ b/zenserver/experimental/usnjournal.cpp
@@ -1,17 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "usnjournal.h"
-
-#include <zencore/except.h>
-#include <zencore/logging.h>
-#include <zencore/timer.h>
#include <zencore/zencore.h>
+#if ZEN_PLATFORM_WINDOWS
+
+# include "usnjournal.h"
+
+# include <zencore/except.h>
+# include <zencore/logging.h>
+# include <zencore/timer.h>
+
ZEN_THIRD_PARTY_INCLUDES_START
-#include <atlfile.h>
+# include <atlfile.h>
ZEN_THIRD_PARTY_INCLUDES_END
-#include <filesystem>
+# include <filesystem>
namespace zen {
@@ -344,3 +347,5 @@ UsnJournalReader::Initialize(std::filesystem::path VolumePath)
}
} // namespace zen
+
+#endif // ZEN_PLATFORM_WINDOWS
diff --git a/zenserver/experimental/usnjournal.h b/zenserver/experimental/usnjournal.h
index db1f59abc..910eb7d06 100644
--- a/zenserver/experimental/usnjournal.h
+++ b/zenserver/experimental/usnjournal.h
@@ -2,14 +2,16 @@
#pragma once
-#include <zencore/windows.h>
-#include <zencore/zencore.h>
+#if ZEN_PLATFORM_WINDOWS
+
+# include <zencore/windows.h>
+# include <zencore/zencore.h>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <winioctl.h>
+# include <winioctl.h>
ZEN_THIRD_PARTY_INCLUDES_END
-#include <filesystem>
+# include <filesystem>
namespace zen {
@@ -63,3 +65,5 @@ private:
};
} // namespace zen
+
+#endif // ZEN_PLATFORM_WINDOWS
diff --git a/zenserver/projectstore.cpp b/zenserver/projectstore.cpp
index 8f8b6e163..7e0aed328 100644
--- a/zenserver/projectstore.cpp
+++ b/zenserver/projectstore.cpp
@@ -13,13 +13,16 @@
#include <zencore/testing.h>
#include <zencore/testutils.h>
#include <zencore/timer.h>
-#include <zencore/windows.h>
#include <zenstore/basicfile.h>
#include <zenstore/cas.h>
#include <zenstore/caslog.h>
#include "config.h"
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
#define USE_ROCKSDB 0
ZEN_THIRD_PARTY_INCLUDES_START
@@ -122,7 +125,7 @@ struct ProjectStore::OplogStorage : public RefCounted
#if USE_ROCKSDB
{
- std::string RocksdbPath = WideToUtf8((m_OplogStoragePath / "ops.rdb").native().c_str());
+ std::string RocksdbPath = PathToUtf8(m_OplogStoragePath / "ops.rdb");
ZEN_DEBUG("opening rocksdb db at '{}'", RocksdbPath);
@@ -200,9 +203,8 @@ struct ProjectStore::OplogStorage : public RefCounted
CbObject Op(SharedBuffer::MakeView(OpBuffer.Data(), OpBuffer.Size()));
- m_NextOpsOffset =
- Max(m_NextOpsOffset.load(std::memory_order::memory_order_relaxed), RoundUp(OpFileOffset + LogEntry.OpCoreSize, m_OpsAlign));
- m_MaxLsn = Max(m_MaxLsn.load(std::memory_order::memory_order_relaxed), LogEntry.OpLsn);
+ m_NextOpsOffset = Max(m_NextOpsOffset.load(std::memory_order_relaxed), RoundUp(OpFileOffset + LogEntry.OpCoreSize, m_OpsAlign));
+ m_MaxLsn = Max(m_MaxLsn.load(std::memory_order_relaxed), LogEntry.OpLsn);
Handler(Op, LogEntry);
});
@@ -299,8 +301,8 @@ private:
ProjectStore::Oplog::Oplog(std::string_view Id, Project* Project, CidStore& Store, std::filesystem::path BasePath)
: m_OuterProject(Project)
, m_CidStore(Store)
-, m_OplogId(Id)
, m_BasePath(BasePath)
+, m_OplogId(Id)
{
m_Storage = new OplogStorage(this, m_BasePath);
const bool StoreExists = m_Storage->Exists();
@@ -385,7 +387,7 @@ ProjectStore::Oplog::FindChunk(Oid ChunkId)
std::filesystem::path FilePath = m_OuterProject->RootDir / FileIt->second.ServerPath;
- IoBuffer FileChunk = IoBufferBuilder::MakeFromFile(FilePath.native().c_str());
+ IoBuffer FileChunk = IoBufferBuilder::MakeFromFile(FilePath);
FileChunk.SetContentType(ZenContentType::kBinary);
return FileChunk;
@@ -465,16 +467,6 @@ ProjectStore::Oplog::AddFileMapping(Oid FileId, IoHash Hash, std::string_view Se
return false;
}
- if (ServerPath[0] == '/' || ClientPath[0] != '/')
- {
- // This is a special case just to enable tests to use absolute paths. We might want
- // to have configuration to control this
- if (ServerPath[1] != ':')
- {
- return false;
- }
- }
-
FileMapEntry Entry;
Entry.ServerPath = ServerPath;
Entry.ClientPath = ClientPath;
@@ -680,7 +672,7 @@ ProjectStore::Project::Write()
CbObjectWriter Cfg;
Cfg << "id" << Identifier;
- Cfg << "root" << WideToUtf8(RootDir.c_str());
+ Cfg << "root" << PathToUtf8(RootDir);
Cfg << "project" << ProjectRootDir;
Cfg << "engine" << EngineRootDir;
@@ -779,8 +771,6 @@ ProjectStore::Project::OpenOplog(std::string_view OplogId)
void
ProjectStore::Project::DeleteOplog(std::string_view OplogId)
{
- bool Exists = false;
-
{
RwLock::ExclusiveLockScope _(m_ProjectLock);
@@ -788,8 +778,6 @@ ProjectStore::Project::DeleteOplog(std::string_view OplogId)
if (OplogIt != m_Oplogs.end())
{
- Exists = true;
-
m_Oplogs.erase(OplogIt);
}
}
@@ -1647,7 +1635,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects)
ProjectStore::Oplog& Log = *OplogIt;
CbObjectWriter Cb;
- Cb << "id"sv << Log.OplogId() << "project"sv << Prj.Identifier << "tempdir"sv << Log.TempDir();
+ Cb << "id"sv << Log.OplogId() << "project"sv << Prj.Identifier << "tempdir"sv << Log.TempPath().c_str();
Req.ServerRequest().WriteResponse(HttpResponseCode::OK, Cb.Save());
}
@@ -1686,6 +1674,9 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects)
return Req.ServerRequest().WriteResponse(HttpResponseCode::OK);
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kPost | HttpVerb::kGet | HttpVerb::kDelete);
@@ -1783,7 +1774,7 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects)
const ProjectStore::Project& Prj = *ProjectIt;
CbObjectWriter Response;
- Response << "id" << Prj.Identifier << "root" << WideToUtf8(Prj.RootDir.c_str());
+ Response << "id" << Prj.Identifier << "root" << PathToUtf8(Prj.RootDir);
Response.BeginArray("oplogs"sv);
Prj.IterateOplogs([&](const ProjectStore::Oplog& I) { Response << "id"sv << I.OplogId(); });
@@ -1809,6 +1800,9 @@ HttpProjectService::HttpProjectService(CidStore& Store, ProjectStore* Projects)
return Req.ServerRequest().WriteResponse(HttpResponseCode::NoContent);
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost | HttpVerb::kDelete);
@@ -1837,6 +1831,7 @@ HttpProjectService::HandleRequest(HttpServerRequest& Request)
//////////////////////////////////////////////////////////////////////////
+# if ZEN_PLATFORM_WINDOWS
class SecurityAttributes
{
public:
@@ -1869,6 +1864,12 @@ public:
}
}
};
+# else
+struct AnyUserSecurityAttributes
+{
+ int Attributes() { return 0666; }
+};
+# endif // ZEN_PLATFORM_WINDOWS
//////////////////////////////////////////////////////////////////////////
@@ -1939,6 +1940,7 @@ private:
asio::thread_pool m_WorkerThreadPool;
asio::io_context m_IoContext;
+# if ZEN_PLATFORM_WINDOWS
class PipeConnection
{
enum PipeState
@@ -2097,6 +2099,15 @@ private:
uint8_t m_MsgBuffer[16384];
};
+# else
+ class PipeConnection
+ {
+ public:
+ PipeConnection(LocalProjectImpl*) {}
+ void Accept() {}
+ void Disconnect() {}
+ };
+# endif
AnyUserSecurityAttributes m_AnyUserSecurityAttributes;
std::vector<PipeConnection*> m_ServicePipes;
diff --git a/zenserver/projectstore.h b/zenserver/projectstore.h
index 43acdf05f..283dec3b2 100644
--- a/zenserver/projectstore.h
+++ b/zenserver/projectstore.h
@@ -101,7 +101,6 @@ public:
const std::string& OplogId() const { return m_OplogId; }
- const std::wstring& TempDir() const { return m_TempPath.native(); }
const std::filesystem::path& TempPath() const { return m_TempPath; }
spdlog::logger& Log() { return m_OuterProject->Log(); }
diff --git a/zenserver/testing/launch.cpp b/zenserver/testing/launch.cpp
index 55695ac9c..706594b10 100644
--- a/zenserver/testing/launch.cpp
+++ b/zenserver/testing/launch.cpp
@@ -2,26 +2,29 @@
#include "launch.h"
-#include <zencore/compactbinary.h>
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/filesystem.h>
-#include <zencore/fmtutils.h>
-#include <zencore/iobuffer.h>
-#include <zencore/iohash.h>
-#include <zencore/logging.h>
-#include <zencore/windows.h>
-#include <zenstore/CAS.h>
-
-#include <AccCtrl.h>
-#include <AclAPI.h>
-#include <sddl.h>
-
-#include <UserEnv.h>
-#pragma comment(lib, "UserEnv.lib")
-
-#include <atlbase.h>
-#include <filesystem>
-#include <span>
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include <zencore/compactbinary.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/filesystem.h>
+# include <zencore/fmtutils.h>
+# include <zencore/iobuffer.h>
+# include <zencore/iohash.h>
+# include <zencore/logging.h>
+# include <zencore/windows.h>
+# include <zenstore/cas.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <AccCtrl.h>
+# include <AclAPI.h>
+# include <UserEnv.h>
+# include <atlbase.h>
+# include <sddl.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+# pragma comment(lib, "UserEnv.lib")
+
+# include <filesystem>
+# include <span>
using namespace std::literals;
@@ -127,6 +130,8 @@ BasicJob::ExitCode()
return gsl::narrow_cast<int>(Ec);
}
+////////////////////////////////////////////////////////////////////////////////
+
struct SandboxedJob
{
SandboxedJob() = default;
@@ -317,6 +322,8 @@ SandboxedJob::SpawnJob(std::filesystem::path ExePath)
return true;
}
+////////////////////////////////////////////////////////////////////////////////
+
HttpLaunchService::HttpLaunchService(CasStore& Store, const std::filesystem::path& SandboxBaseDir)
: m_Log(logging::Get("exec"))
, m_CasStore(Store)
@@ -336,13 +343,16 @@ HttpLaunchService::HttpLaunchService(CasStore& Store, const std::filesystem::pat
case HttpVerb::kPost:
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost);
// Experimental
-#if 0
+# if 0
m_Router.RegisterRoute(
"jobs/sandbox",
[this](HttpRouterRequest& Req) {
@@ -362,10 +372,13 @@ HttpLaunchService::HttpLaunchService(CasStore& Store, const std::filesystem::pat
Job.SpawnJob("c:\\windows\\system32\\cmd.exe");
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost);
-#endif
+# endif
m_Router.RegisterRoute(
"jobs/prep",
@@ -413,6 +426,9 @@ HttpLaunchService::HttpLaunchService(CasStore& Store, const std::filesystem::pat
return HttpReq.WriteResponse(HttpResponseCode::OK, Response);
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kPost);
@@ -496,6 +512,9 @@ HttpLaunchService::HttpLaunchService(CasStore& Store, const std::filesystem::pat
return HttpReq.WriteResponse(HttpResponseCode::OK, Response.Save());
}
break;
+
+ default:
+ break;
}
},
HttpVerb::kGet | HttpVerb::kPost);
@@ -530,3 +549,5 @@ HttpLaunchService::CreateNewSandbox()
}
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/testing/launch.h b/zenserver/testing/launch.h
index 49f12e2ec..925fa18b0 100644
--- a/zenserver/testing/launch.h
+++ b/zenserver/testing/launch.h
@@ -2,10 +2,18 @@
#pragma once
-#include <zencore/logging.h>
-#include <zenhttp/httpserver.h>
+#include <zencore/zencore.h>
-#include <filesystem>
+#if !defined(ZEN_WITH_COMPUTE_SERVICES)
+# define ZEN_WITH_COMPUTE_SERVICES ZEN_PLATFORM_WINDOWS
+#endif
+
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include <zencore/logging.h>
+# include <zenhttp/httpserver.h>
+
+# include <filesystem>
namespace zen {
@@ -36,3 +44,5 @@ private:
};
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/upstream/jupiter.cpp b/zenserver/upstream/jupiter.cpp
index f9be068ec..177184591 100644
--- a/zenserver/upstream/jupiter.cpp
+++ b/zenserver/upstream/jupiter.cpp
@@ -2,7 +2,6 @@
#include "jupiter.h"
-#include "cache/structuredcachestore.h"
#include "diag/formatters.h"
#include "diag/logging.h"
@@ -58,7 +57,7 @@ namespace detail {
cpr::Session& GetSession() { return Session; }
private:
- friend class CloudCacheClient;
+ friend class zen::CloudCacheClient;
CloudCacheClient& OwnerClient;
CloudCacheAccessToken AccessToken;
@@ -116,11 +115,7 @@ CloudCacheSession::GetDerivedData(std::string_view BucketId, std::string_view Ke
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -165,11 +160,7 @@ CloudCacheSession::GetRef(std::string_view BucketId, const IoHash& Key, ZenConte
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -206,11 +197,7 @@ CloudCacheSession::GetBlob(const IoHash& Key)
const IoBuffer Buffer =
Success && Response.text.size() > 0 ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -246,11 +233,7 @@ CloudCacheSession::GetCompressedBlob(const IoHash& Key)
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -286,11 +269,7 @@ CloudCacheSession::GetObject(const IoHash& Key)
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -327,12 +306,9 @@ CloudCacheSession::PutDerivedData(std::string_view BucketId, std::string_view Ke
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
-
return {.Bytes = Response.uploaded_bytes,
.ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ .Success = (Response.status_code == 200 || Response.status_code == 201)};
}
CloudCacheResult
@@ -388,7 +364,6 @@ CloudCacheSession::PutRef(std::string_view BucketId, const IoHash& Key, IoBuffer
PutRefResult Result;
Result.Success = (Response.status_code == 200 || Response.status_code == 201);
- Result.ErrorCode = !Result.Success ? Response.status_code : 0;
Result.Bytes = Response.uploaded_bytes;
Result.ElapsedSeconds = Response.elapsed;
@@ -453,7 +428,6 @@ CloudCacheSession::FinalizeRef(std::string_view BucketId, const IoHash& Key, con
FinalizeRefResult Result;
Result.Success = (Response.status_code == 200 || Response.status_code == 201);
- Result.ErrorCode = !Result.Success ? Response.status_code : 0;
Result.Bytes = Response.uploaded_bytes;
Result.ElapsedSeconds = Response.elapsed;
@@ -504,12 +478,9 @@ CloudCacheSession::PutBlob(const IoHash& Key, IoBuffer Blob)
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
-
return {.Bytes = Response.uploaded_bytes,
.ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ .Success = (Response.status_code == 200 || Response.status_code == 201)};
}
CloudCacheResult
@@ -542,12 +513,9 @@ CloudCacheSession::PutCompressedBlob(const IoHash& Key, IoBuffer Blob)
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
-
return {.Bytes = Response.uploaded_bytes,
.ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ .Success = (Response.status_code == 200 || Response.status_code == 201)};
}
CloudCacheResult
@@ -580,12 +548,9 @@ CloudCacheSession::PutObject(const IoHash& Key, IoBuffer Object)
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200 || Response.status_code == 201;
-
return {.Bytes = Response.uploaded_bytes,
.ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ .Success = (Response.status_code == 200 || Response.status_code == 201)};
}
CloudCacheResult
@@ -619,9 +584,7 @@ CloudCacheSession::RefExists(std::string_view BucketId, const IoHash& Key)
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200;
-
- return {.ElapsedSeconds = Response.elapsed, .ErrorCode = !Success ? Response.status_code : 0, .Success = Success};
+ return {.ElapsedSeconds = Response.elapsed, .Success = Response.status_code == 200};
}
CloudCacheResult
@@ -690,9 +653,7 @@ CloudCacheSession::PostComputeTasks(std::string_view ChannelId, IoBuffer TasksDa
return {.ErrorCode = 401, .Reason = std::string("Invalid access token")};
}
- const bool Success = Response.status_code == 200;
-
- return {.ElapsedSeconds = Response.elapsed, .ErrorCode = !Success ? Response.status_code : 0, .Success = Success};
+ return {.ElapsedSeconds = Response.elapsed, .Success = Response.status_code == 200};
}
CloudCacheResult
@@ -728,11 +689,7 @@ CloudCacheSession::GetComputeUpdates(std::string_view ChannelId, const uint32_t
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
CloudCacheResult
@@ -768,11 +725,7 @@ CloudCacheSession::GetObjectTree(const IoHash& Key)
const bool Success = Response.status_code == 200;
const IoBuffer Buffer = Success ? IoBufferBuilder::MakeCloneFromMemory(Response.text.data(), Response.text.size()) : IoBuffer();
- return {.Response = Buffer,
- .Bytes = Response.downloaded_bytes,
- .ElapsedSeconds = Response.elapsed,
- .ErrorCode = !Success ? Response.status_code : 0,
- .Success = Success};
+ return {.Response = Buffer, .Bytes = Response.downloaded_bytes, .ElapsedSeconds = Response.elapsed, .Success = Success};
}
std::vector<IoHash>
diff --git a/zenserver/upstream/upstreamapply.cpp b/zenserver/upstream/upstreamapply.cpp
index 05be5f65c..f8a8c3e62 100644
--- a/zenserver/upstream/upstreamapply.cpp
+++ b/zenserver/upstream/upstreamapply.cpp
@@ -1,37 +1,40 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "upstreamapply.h"
-#include "jupiter.h"
-#include "zen.h"
-
-#include <zencore/blockingqueue.h>
-#include <zencore/compactbinary.h>
-#include <zencore/compactbinarybuilder.h>
-#include <zencore/compactbinarypackage.h>
-#include <zencore/compactbinaryvalidation.h>
-#include <zencore/compress.h>
-#include <zencore/fmtutils.h>
-#include <zencore/session.h>
-#include <zencore/stats.h>
-#include <zencore/stream.h>
-#include <zencore/thread.h>
-#include <zencore/timer.h>
-
-#include <zenstore/cas.h>
-#include <zenstore/cidstore.h>
-
-#include "cache/structuredcachestore.h"
-#include "diag/logging.h"
-
-#include <fmt/format.h>
-
-#include <algorithm>
-#include <atomic>
-#include <map>
-#include <set>
-#include <stack>
-#include <thread>
-#include <unordered_map>
+
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include "jupiter.h"
+# include "zen.h"
+
+# include <zencore/blockingqueue.h>
+# include <zencore/compactbinary.h>
+# include <zencore/compactbinarybuilder.h>
+# include <zencore/compactbinarypackage.h>
+# include <zencore/compactbinaryvalidation.h>
+# include <zencore/compress.h>
+# include <zencore/fmtutils.h>
+# include <zencore/session.h>
+# include <zencore/stats.h>
+# include <zencore/stream.h>
+# include <zencore/thread.h>
+# include <zencore/timer.h>
+
+# include <zenstore/cas.h>
+# include <zenstore/cidstore.h>
+
+# include "cache/structuredcachestore.h"
+# include "diag/logging.h"
+
+# include <fmt/format.h>
+
+# include <algorithm>
+# include <atomic>
+# include <map>
+# include <set>
+# include <stack>
+# include <thread>
+# include <unordered_map>
namespace zen {
@@ -1569,3 +1572,5 @@ MakeHordeUpstreamEndpoint(const CloudCacheClientOptions& Options, CasStore& CasS
}
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/upstream/upstreamapply.h b/zenserver/upstream/upstreamapply.h
index 98f193c02..ed73ec7f8 100644
--- a/zenserver/upstream/upstreamapply.h
+++ b/zenserver/upstream/upstreamapply.h
@@ -2,16 +2,20 @@
#pragma once
-#include <zencore/compactbinarypackage.h>
-#include <zencore/iobuffer.h>
-#include <zencore/iohash.h>
-#include <zencore/zencore.h>
+#include "compute/apply.h"
-#include <atomic>
-#include <chrono>
-#include <memory>
-#include <unordered_map>
-#include <unordered_set>
+#if ZEN_WITH_COMPUTE_SERVICES
+
+# include <zencore/compactbinarypackage.h>
+# include <zencore/iobuffer.h>
+# include <zencore/iohash.h>
+# include <zencore/zencore.h>
+
+# include <atomic>
+# include <chrono>
+# include <memory>
+# include <unordered_map>
+# include <unordered_set>
namespace zen {
@@ -170,3 +174,5 @@ std::unique_ptr<UpstreamApplyEndpoint> MakeHordeUpstreamEndpoint(const CloudCach
CidStore& CidStore);
} // namespace zen
+
+#endif // ZEN_WITH_COMPUTE_SERVICES
diff --git a/zenserver/upstream/upstreamcache.cpp b/zenserver/upstream/upstreamcache.cpp
index e2dc09872..6283457e3 100644
--- a/zenserver/upstream/upstreamcache.cpp
+++ b/zenserver/upstream/upstreamcache.cpp
@@ -195,7 +195,7 @@ namespace detail {
}
}
- OnComplete({.CacheKey = CacheKey, .KeyIndex = Index, .Record = Record, .Package = Package});
+ OnComplete({.Key = CacheKey, .KeyIndex = Index, .Record = Record, .Package = Package});
}
return Result;
@@ -271,18 +271,15 @@ namespace detail {
if (CacheRecord.Type == ZenContentType::kBinary)
{
CloudCacheResult Result;
- for (int32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
+ for (uint32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
{
if (m_UseLegacyDdc)
{
- Result = Session.PutDerivedData(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, RecordValue);
+ Result = Session.PutDerivedData(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RecordValue);
}
else
{
- Result = Session.PutRef(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
- RecordValue,
- ZenContentType::kBinary);
+ Result = Session.PutRef(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RecordValue, ZenContentType::kBinary);
}
}
@@ -335,16 +332,15 @@ namespace detail {
PutRefResult RefResult;
for (int32_t Attempt = 0; Attempt < MaxAttempts && !RefResult.Success; Attempt++)
{
- RefResult =
- Session.PutRef(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, RecordValue, ZenContentType::kCbObject);
+ RefResult = Session.PutRef(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RecordValue, ZenContentType::kCbObject);
}
m_HealthOk = RefResult.ErrorCode == 0;
if (!RefResult.Success)
{
- return {.Reason = "upload cache record '{}/{}' FAILED, reason '{}'"_format(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ return {.Reason = "upload cache record '{}/{}' FAILED, reason '{}'"_format(CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
RefResult.Reason),
.Success = false};
}
@@ -359,13 +355,13 @@ namespace detail {
}
const IoHash RefHash = IoHash::HashBuffer(RecordValue);
- FinalizeRefResult FinalizeResult = Session.FinalizeRef(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, RefHash);
+ FinalizeRefResult FinalizeResult = Session.FinalizeRef(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RefHash);
m_HealthOk = FinalizeResult.ErrorCode == 0;
if (!FinalizeResult.Success)
{
- return {.Reason = "finalize cache record '{}/{}' FAILED, reason '{}'"_format(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ return {.Reason = "finalize cache record '{}/{}' FAILED, reason '{}'"_format(CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
FinalizeResult.Reason),
.Success = false};
}
@@ -377,13 +373,13 @@ namespace detail {
return {.Reason = std::move(Reason), .Success = false};
}
- FinalizeResult = Session.FinalizeRef(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, RefHash);
+ FinalizeResult = Session.FinalizeRef(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RefHash);
m_HealthOk = FinalizeResult.ErrorCode == 0;
if (!FinalizeResult.Success)
{
- return {.Reason = "finalize '{}/{}' FAILED, reason '{}'"_format(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ return {.Reason = "finalize '{}/{}' FAILED, reason '{}'"_format(CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
FinalizeResult.Reason),
.Success = false};
}
@@ -396,8 +392,8 @@ namespace detail {
Sb << MissingHash.ToHexString() << ",";
}
- return {.Reason = "finalize '{}/{}' FAILED, still needs payload(s) '{}'"_format(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ return {.Reason = "finalize '{}/{}' FAILED, still needs payload(s) '{}'"_format(CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
Sb.ToString()),
.Success = false};
}
@@ -606,8 +602,7 @@ namespace detail {
for (size_t LocalIndex = 0; CbFieldView Record : BatchResponse.GetObject()["Result"sv])
{
const size_t Index = IndexMap[LocalIndex++];
- OnComplete(
- {.CacheKey = CacheKeys[Index], .KeyIndex = Index, .Record = Record.AsObjectView(), .Package = BatchResponse});
+ OnComplete({.Key = CacheKeys[Index], .KeyIndex = Index, .Record = Record.AsObjectView(), .Package = BatchResponse});
}
return {.Bytes = Result.Bytes, .ElapsedSeconds = Result.ElapsedSeconds, .Success = true};
@@ -620,7 +615,7 @@ namespace detail {
for (size_t Index : KeyIndex)
{
- OnComplete({.CacheKey = CacheKeys[Index], .KeyIndex = Index, .Record = CbObjectView(), .Package = CbPackage()});
+ OnComplete({.Key = CacheKeys[Index], .KeyIndex = Index, .Record = CbObjectView(), .Package = CbPackage()});
}
return {.Error{.ErrorCode = Result.ErrorCode, .Reason = std::move(Result.Reason)}};
@@ -772,12 +767,9 @@ namespace detail {
Package.Save(MemStream);
IoBuffer PackagePayload(IoBuffer::Wrap, MemStream.Data(), MemStream.Size());
- for (int32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
+ for (uint32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
{
- Result = Session.PutCacheRecord(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
- PackagePayload,
- CacheRecord.Type);
+ Result = Session.PutCacheRecord(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, PackagePayload, CacheRecord.Type);
m_HealthOk = Result.ErrorCode == 0;
}
@@ -790,10 +782,10 @@ namespace detail {
for (size_t Idx = 0, Count = Payloads.size(); Idx < Count; Idx++)
{
Result.Success = false;
- for (int32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
+ for (uint32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
{
- Result = Session.PutCachePayload(CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ Result = Session.PutCachePayload(CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
CacheRecord.PayloadIds[Idx],
Payloads[Idx]);
@@ -813,10 +805,9 @@ namespace detail {
}
Result.Success = false;
- for (int32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
+ for (uint32_t Attempt = 0; Attempt < MaxAttempts && !Result.Success; Attempt++)
{
- Result =
- Session.PutCacheRecord(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, RecordValue, CacheRecord.Type);
+ Result = Session.PutCacheRecord(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, RecordValue, CacheRecord.Type);
m_HealthOk = Result.ErrorCode == 0;
}
@@ -1099,7 +1090,7 @@ public:
for (size_t Index : MissingKeys)
{
- OnComplete({.CacheKey = CacheKeys[Index], .KeyIndex = Index, .Record = CbObjectView(), .Package = CbPackage()});
+ OnComplete({.Key = CacheKeys[Index], .KeyIndex = Index, .Record = CbObjectView(), .Package = CbPackage()});
}
}
@@ -1236,11 +1227,9 @@ private:
ZenCacheValue CacheValue;
std::vector<IoBuffer> Payloads;
- if (!m_CacheStore.Get(CacheRecord.CacheKey.Bucket, CacheRecord.CacheKey.Hash, CacheValue))
+ if (!m_CacheStore.Get(CacheRecord.Key.Bucket, CacheRecord.Key.Hash, CacheValue))
{
- ZEN_WARN("process upstream FAILED, '{}/{}', cache record doesn't exist",
- CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash);
+ ZEN_WARN("process upstream FAILED, '{}/{}', cache record doesn't exist", CacheRecord.Key.Bucket, CacheRecord.Key.Hash);
return;
}
@@ -1253,8 +1242,8 @@ private:
else
{
ZEN_WARN("process upstream FAILED, '{}/{}/{}', payload doesn't exist in CAS",
- CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
PayloadId);
return;
}
@@ -1270,8 +1259,8 @@ private:
if (!Result.Success)
{
ZEN_WARN("upload cache record '{}/{}' FAILED, endpoint '{}', reason '{}'",
- CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
+ CacheRecord.Key.Bucket,
+ CacheRecord.Key.Hash,
Endpoint->GetEndpointInfo().Url,
Result.Reason);
}
@@ -1292,10 +1281,7 @@ private:
}
catch (std::exception& Err)
{
- ZEN_ERROR("upload cache record '{}/{}' FAILED, reason '{}'",
- CacheRecord.CacheKey.Bucket,
- CacheRecord.CacheKey.Hash,
- Err.what());
+ ZEN_ERROR("upload cache record '{}/{}' FAILED, reason '{}'", CacheRecord.Key.Bucket, CacheRecord.Key.Hash, Err.what());
}
}
diff --git a/zenserver/upstream/upstreamcache.h b/zenserver/upstream/upstreamcache.h
index 12287198d..c463c4996 100644
--- a/zenserver/upstream/upstreamcache.h
+++ b/zenserver/upstream/upstreamcache.h
@@ -25,7 +25,7 @@ struct ZenStructuredCacheClientOptions;
struct UpstreamCacheRecord
{
ZenContentType Type = ZenContentType::kBinary;
- CacheKey CacheKey;
+ CacheKey Key;
std::vector<IoHash> PayloadIds;
};
@@ -83,7 +83,7 @@ struct UpstreamEndpointStats
struct CacheRecordGetCompleteParams
{
- const CacheKey& CacheKey;
+ const CacheKey& Key;
size_t KeyIndex = ~size_t(0);
const CbObjectView& Record;
const CbPackage& Package;
diff --git a/zenserver/upstream/zen.h b/zenserver/upstream/zen.h
index df975df1f..e97ce755d 100644
--- a/zenserver/upstream/zen.h
+++ b/zenserver/upstream/zen.h
@@ -16,6 +16,7 @@ ZEN_THIRD_PARTY_INCLUDES_START
ZEN_THIRD_PARTY_INCLUDES_END
#include <chrono>
+#include <list>
struct ZenCacheValue;
diff --git a/zenserver/windows/service.cpp b/zenserver/windows/service.cpp
index 23cefb7b5..89bacab0b 100644
--- a/zenserver/windows/service.cpp
+++ b/zenserver/windows/service.cpp
@@ -2,14 +2,18 @@
#include "service.h"
-#include <zencore/except.h>
#include <zencore/zencore.h>
-#include <stdio.h>
-#include <tchar.h>
-#include <zencore/windows.h>
+#if ZEN_PLATFORM_WINDOWS
-#define SVCNAME L"Zen Store"
+# include <zencore/except.h>
+# include <zencore/zencore.h>
+
+# include <stdio.h>
+# include <tchar.h>
+# include <zencore/windows.h>
+
+# define SVCNAME L"Zen Store"
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
@@ -638,3 +642,5 @@ SvcReportEvent(LPTSTR szFunction)
// DeregisterEventSource(hEventSource);
//}
}
+
+#endif // ZEN_PLATFORM_WINDOWS
diff --git a/zenserver/xmake.lua b/zenserver/xmake.lua
index 2fe32112b..9e12df3e3 100644
--- a/zenserver/xmake.lua
+++ b/zenserver/xmake.lua
@@ -18,6 +18,7 @@ target("zenserver")
end
add_options("vfs")
+ add_options("compute")
add_packages(
"vcpkg::sentry-native",
@@ -43,7 +44,6 @@ target("zenserver")
else
commit = "dbg-" .. commit
end
- target:add("defines","BUILD_VERSION=\"" .. commit .. "\"")
- print("build version " .. commit)
+ target:add("defines", "BUILD_VERSION=\"" .. commit .. "\"")
end
end)
diff --git a/zenserver/zenserver.cpp b/zenserver/zenserver.cpp
index e14f93f5b..49bb3004d 100644
--- a/zenserver/zenserver.cpp
+++ b/zenserver/zenserver.cpp
@@ -12,13 +12,17 @@
#include <zencore/string.h>
#include <zencore/thread.h>
#include <zencore/timer.h>
-#include <zencore/windows.h>
+#include <zencore/trace.h>
#include <zenhttp/httpserver.h>
#include <zenstore/basicfile.h>
#include <zenstore/cas.h>
#include <zenstore/cidstore.h>
#include <zenutil/zenserverprocess.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
#if ZEN_USE_MIMALLOC
ZEN_THIRD_PARTY_INCLUDES_START
# include <mimalloc-new-delete.h>
@@ -138,7 +142,7 @@ namespace utils {
if (!ErrorCode)
{
- for (const asio::ip::tcp::endpoint& Ep : Endpoints)
+ for (const asio::ip::tcp::endpoint Ep : Endpoints)
{
OutEndpoints.push_back("http://{}:{}"_format(Ep.address().to_string(), Ep.port()));
}
@@ -225,17 +229,23 @@ public:
m_LocalProjectService = zen::LocalProjectService::New(*m_CasStore, m_ProjectStore);
#endif
- ZEN_INFO("instantiating compute services");
-
#if ZEN_USE_EXEC
std::filesystem::path SandboxDir = m_DataRoot / "exec" / "sandbox";
zen::CreateDirectories(SandboxDir);
m_HttpLaunchService = std::make_unique<zen::HttpLaunchService>(*m_CasStore, SandboxDir);
#endif
+#if ZEN_WITH_COMPUTE_SERVICES
+ ZEN_INFO("instantiating compute services");
+
+ std::filesystem::path SandboxDir = m_DataRoot / "exec" / "sandbox";
+ zen::CreateDirectories(SandboxDir);
+ m_HttpLaunchService = std::make_unique<zen::HttpLaunchService>(*m_CasStore, SandboxDir);
+
std::filesystem::path ApplySandboxDir = m_DataRoot / "exec" / "apply";
zen::CreateDirectories(ApplySandboxDir);
m_HttpFunctionService = std::make_unique<zen::HttpFunctionService>(*m_CasStore, *m_CidStore, ApplySandboxDir);
+#endif // ZEN_WITH_COMPUTE_SERVICES
if (ServerOptions.StructuredCacheEnabled)
{
@@ -273,17 +283,17 @@ public:
m_Http->RegisterService(*m_StructuredCacheService);
}
-#if ZEN_USE_EXEC
+#if ZEN_WITH_COMPUTE_SERVICES
if (m_HttpLaunchService)
{
m_Http->RegisterService(*m_HttpLaunchService);
}
-#endif
if (m_HttpFunctionService)
{
m_Http->RegisterService(*m_HttpFunctionService);
}
+#endif // ZEN_WITH_COMPUTE_SERVICES
m_FrontendService = std::make_unique<HttpFrontendService>(m_ContentRoot);
@@ -343,7 +353,7 @@ public:
if (m_DebugOptionForcedCrash)
{
- __debugbreak();
+ ZEN_DEBUG_BREAK();
}
const bool IsInteractiveMode = zen::IsInteractiveSession() && !m_TestMode;
@@ -404,7 +414,7 @@ public:
for (auto& PidEntry : m_ServerEntry->SponsorPids)
{
- if (uint32_t ThisPid = PidEntry.load(std::memory_order::memory_order_relaxed))
+ if (uint32_t ThisPid = PidEntry.load(std::memory_order_relaxed))
{
if (PidEntry.compare_exchange_strong(ThisPid, 0))
{
@@ -523,13 +533,17 @@ private:
zen::HttpTestingService m_TestingService;
zen::HttpCasService m_CasService{*m_CasStore};
zen::RefPtr<zen::ProjectStore> m_ProjectStore;
+ zen::Ref<zen::LocalProjectService> m_LocalProjectService;
std::unique_ptr<zen::HttpProjectService> m_HttpProjectService;
std::unique_ptr<zen::HttpStructuredCacheService> m_StructuredCacheService;
zen::HttpAdminService m_AdminService{m_GcScheduler};
zen::HttpHealthService m_HealthService;
zen::Mesh m_ZenMesh{m_IoContext};
- std::unique_ptr<zen::HttpFunctionService> m_HttpFunctionService;
- std::unique_ptr<zen::HttpFrontendService> m_FrontendService;
+#if ZEN_WITH_COMPUTE_SERVICES
+ std::unique_ptr<zen::HttpLaunchService> m_HttpLaunchService;
+ std::unique_ptr<zen::HttpFunctionService> m_HttpFunctionService;
+#endif
+ std::unique_ptr<zen::HttpFrontendService> m_FrontendService;
#if ZEN_USE_EXEC
std::unique_ptr<zen::HttpLaunchService> m_HttpLaunchService;
@@ -766,33 +780,34 @@ ZenServer::InitializeStructuredCache(const ZenServerOptions& ServerOptions)
new zen::HttpStructuredCacheService(*m_CacheStore, *m_CidStore, m_StatsService, m_StatusService, std::move(UpstreamCache)));
}
-} // namespace zen
+////////////////////////////////////////////////////////////////////////////////
-class ZenWindowsService : public WindowsService
+class ZenEntryPoint
{
public:
- ZenWindowsService(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions) {}
-
- ZenWindowsService(const ZenWindowsService&) = delete;
- ZenWindowsService& operator=(const ZenWindowsService&) = delete;
-
- virtual int Run() override;
+ ZenEntryPoint(ZenServerOptions& ServerOptions);
+ ZenEntryPoint(const ZenEntryPoint&) = delete;
+ ZenEntryPoint& operator=(const ZenEntryPoint&) = delete;
+ int Run();
private:
ZenServerOptions& m_ServerOptions;
zen::LockFile m_LockFile;
};
-int
-ZenWindowsService::Run()
+ZenEntryPoint::ZenEntryPoint(ZenServerOptions& ServerOptions) : m_ServerOptions(ServerOptions)
{
- using namespace zen;
+}
+int
+ZenEntryPoint::Run()
+{
#if USE_SENTRY
// Initialize sentry.io client
sentry_options_t* SentryOptions = sentry_options_new();
sentry_options_set_dsn(SentryOptions, "https://[email protected]/5919284");
+ sentry_options_set_database_path(SentryOptions, PathToUtf8(m_ServerOptions.DataDir / ".sentry-native").c_str());
sentry_init(SentryOptions);
auto _ = zen::MakeGuard([] { sentry_close(); });
@@ -812,8 +827,8 @@ ZenWindowsService::Run()
auto MakeLockData = [&] {
CbObjectWriter Cbo;
- Cbo << "pid" << _getpid() << "data" << ToUtf8(ServerOptions.DataDir) << "port" << ServerOptions.BasePort << "session_id"
- << GetSessionId() << "ready" << IsReady;
+ Cbo << "pid" << zen::GetCurrentProcessId() << "data" << PathToUtf8(ServerOptions.DataDir) << "port" << ServerOptions.BasePort
+ << "session_id" << GetSessionId() << "ready" << IsReady;
return Cbo.Save();
};
@@ -880,6 +895,10 @@ ZenWindowsService::Run()
ZEN_INFO("shutdown signal received");
Server.RequestExit(0);
}
+ else
+ {
+ ZEN_INFO("shutdown signal wait() failed");
+ }
}});
// If we have a parent process, establish the mechanisms we need
@@ -913,6 +932,36 @@ ZenWindowsService::Run()
return 0;
}
+} // namespace zen
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if ZEN_PLATFORM_WINDOWS
+
+class ZenWindowsService : public WindowsService
+{
+public:
+ ZenWindowsService(ZenServerOptions& ServerOptions) : m_EntryPoint(ServerOptions) {}
+
+ ZenWindowsService(const ZenWindowsService&) = delete;
+ ZenWindowsService& operator=(const ZenWindowsService&) = delete;
+
+ virtual int Run() override;
+
+private:
+ zen::ZenEntryPoint m_EntryPoint;
+};
+
+int
+ZenWindowsService::Run()
+{
+ return m_EntryPoint.Run();
+}
+
+#endif // ZEN_PLATFORM_WINDOWS
+
+////////////////////////////////////////////////////////////////////////////////
+
#if ZEN_WITH_TESTS
int
test_main(int argc, char** argv)
@@ -960,6 +1009,17 @@ main(int argc, char* argv[])
std::filesystem::create_directories(ServerOptions.DataDir);
}
+#if ZEN_WITH_TRACE
+ if (ServerOptions.TraceHost.size())
+ {
+ TraceInit(ServerOptions.TraceHost.c_str(), TraceType::Network);
+ }
+ else if (ServerOptions.TraceFile.size())
+ {
+ TraceInit(ServerOptions.TraceFile.c_str(), TraceType::File);
+ }
+#endif // ZEN_WITH_TRACE
+
#if ZEN_PLATFORM_WINDOWS
if (ServerOptions.InstallService)
{
@@ -974,10 +1034,18 @@ main(int argc, char* argv[])
std::exit(0);
}
-#endif
ZenWindowsService App(ServerOptions);
return App.ServiceMain();
+#else
+ if (ServerOptions.InstallService || ServerOptions.UninstallService)
+ {
+ throw std::runtime_error("Service mode is not supported on this platform");
+ }
+
+ ZenEntryPoint App(ServerOptions);
+ return App.Run();
+#endif // ZEN_PLATFORM_WINDOWS
}
catch (std::exception& Ex)
{
diff --git a/zenstore/basicfile.cpp b/zenstore/basicfile.cpp
index 9ed70a5ec..80d9a2204 100644
--- a/zenstore/basicfile.cpp
+++ b/zenstore/basicfile.cpp
@@ -9,6 +9,14 @@
#include <zencore/testing.h>
#include <zencore/testutils.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#else
+# include <fcntl.h>
+# include <sys/file.h>
+# include <sys/stat.h>
+#endif
+
#include <fmt/format.h>
#include <gsl/gsl-lite.hpp>
@@ -38,6 +46,7 @@ BasicFile::Open(std::filesystem::path FileName, bool IsCreate, std::error_code&
{
Ec.clear();
+#if ZEN_PLATFORM_WINDOWS
const DWORD dwCreationDisposition = IsCreate ? CREATE_ALWAYS : OPEN_EXISTING;
DWORD dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
const DWORD dwShareMode = FILE_SHARE_READ;
@@ -63,6 +72,19 @@ BasicFile::Open(std::filesystem::path FileName, bool IsCreate, std::error_code&
return;
}
+#else
+ int OpenFlags = O_RDWR;
+ OpenFlags |= IsCreate ? O_CREAT | O_TRUNC : 0;
+
+ int Fd = open(FileName.c_str(), OpenFlags, 0666);
+ if (Fd < 0)
+ {
+ Ec = zen::MakeErrorCodeFromLastError();
+ return;
+ }
+
+ void* FileHandle = (void*)(uintptr_t(Fd));
+#endif
m_FileHandle = FileHandle;
}
@@ -72,7 +94,12 @@ BasicFile::Close()
{
if (m_FileHandle)
{
+#if ZEN_PLATFORM_WINDOWS
::CloseHandle(m_FileHandle);
+#else
+ int Fd = int(uintptr_t(m_FileHandle));
+ close(Fd);
+#endif
m_FileHandle = nullptr;
}
}
@@ -86,6 +113,7 @@ BasicFile::Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset)
{
const uint64_t NumberOfBytesToRead = Min(BytesToRead, MaxChunkSize);
+#if ZEN_PLATFORM_WINDOWS
OVERLAPPED Ovl{};
Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu);
@@ -95,6 +123,12 @@ BasicFile::Read(void* Data, uint64_t BytesToRead, uint64_t FileOffset)
BOOL Success = ::ReadFile(m_FileHandle, Data, DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl);
ZEN_ASSERT(dwNumberOfBytesRead == NumberOfBytesToRead);
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(m_FileHandle));
+ int BytesRead = pread(Fd, Data, NumberOfBytesToRead, FileOffset);
+ bool Success = (BytesRead > 0);
+#endif
if (!Success)
{
@@ -161,6 +195,7 @@ BasicFile::Write(const void* Data, uint64_t Size, uint64_t FileOffset, std::erro
{
const uint64_t NumberOfBytesToWrite = Min(Size, MaxChunkSize);
+#if ZEN_PLATFORM_WINDOWS
OVERLAPPED Ovl{};
Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu);
@@ -169,6 +204,12 @@ BasicFile::Write(const void* Data, uint64_t Size, uint64_t FileOffset, std::erro
DWORD dwNumberOfBytesWritten = 0;
BOOL Success = ::WriteFile(m_FileHandle, Data, DWORD(NumberOfBytesToWrite), &dwNumberOfBytesWritten, &Ovl);
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(m_FileHandle));
+ int BytesWritten = pwrite(Fd, Data, NumberOfBytesToWrite, FileOffset);
+ bool Success = (BytesWritten > 0);
+#endif
if (!Success)
{
@@ -210,16 +251,29 @@ BasicFile::WriteAll(IoBuffer Data, std::error_code& Ec)
void
BasicFile::Flush()
{
+#if ZEN_PLATFORM_WINDOWS
FlushFileBuffers(m_FileHandle);
+#else
+ int Fd = int(uintptr_t(m_FileHandle));
+ fsync(Fd);
+#endif
}
uint64_t
BasicFile::FileSize()
{
+#if ZEN_PLATFORM_WINDOWS
ULARGE_INTEGER liFileSize;
liFileSize.LowPart = ::GetFileSize(m_FileHandle, &liFileSize.HighPart);
return uint64_t(liFileSize.QuadPart);
+#else
+ int Fd = int(uintptr_t(m_FileHandle));
+ static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
+ struct stat Stat;
+ fstat(Fd, &Stat);
+ return uint64_t(Stat.st_size);
+#endif
}
//////////////////////////////////////////////////////////////////////////
@@ -234,11 +288,16 @@ TemporaryFile::Close()
{
if (m_FileHandle)
{
+#if ZEN_PLATFORM_WINDOWS
// Mark file for deletion when final handle is closed
FILE_DISPOSITION_INFO Fdi{.DeleteFile = TRUE};
SetFileInformationByHandle(m_FileHandle, FileDispositionInfo, &Fdi, sizeof Fdi);
+#else
+ std::filesystem::path FilePath = zen::PathFromHandle(m_FileHandle);
+ unlink(FilePath.c_str());
+#endif
BasicFile::Close();
}
@@ -275,11 +334,18 @@ LockFile::LockFile()
LockFile::~LockFile()
{
+#if ZEN_PLATFORM_LINUX
+ int Fd = int(intptr_t(m_FileHandle));
+ flock(Fd, LOCK_UN | LOCK_NB);
+#elif ZEN_PLATFORM_MAC
+# error check flock() support
+#endif
}
void
LockFile::Create(std::filesystem::path FileName, CbObject Payload, std::error_code& Ec)
{
+#if ZEN_PLATFORM_WINDOWS
Ec.clear();
const DWORD dwCreationDisposition = CREATE_ALWAYS;
@@ -302,6 +368,26 @@ LockFile::Create(std::filesystem::path FileName, CbObject Payload, std::error_co
return;
}
+#elif ZEN_PLATFORM_LINUX
+ int Fd = open(FileName.c_str(), O_RDWR | O_CREAT, 0666);
+ if (Fd < 0)
+ {
+ Ec = zen::MakeErrorCodeFromLastError();
+ return;
+ }
+
+ int LockRet = flock(Fd, LOCK_EX | LOCK_NB);
+ if (LockRet < 0)
+ {
+ Ec = zen::MakeErrorCodeFromLastError();
+ close(Fd);
+ return;
+ }
+
+ void* FileHandle = (void*)uintptr_t(Fd);
+#else
+# error check flock() support
+#endif
m_FileHandle = FileHandle;
diff --git a/zenstore/CAS.cpp b/zenstore/cas.cpp
index d4023bdc4..d4023bdc4 100644
--- a/zenstore/CAS.cpp
+++ b/zenstore/cas.cpp
diff --git a/zenstore/caslog.cpp b/zenstore/caslog.cpp
index 369bc55ad..dc4891e9f 100644
--- a/zenstore/caslog.cpp
+++ b/zenstore/caslog.cpp
@@ -2,7 +2,7 @@
#include <zenstore/cas.h>
-#include "CompactCas.h"
+#include "compactcas.h"
#include <zencore/except.h>
#include <zencore/filesystem.h>
@@ -133,7 +133,7 @@ CasLogFile::Replay(std::function<void(const void*)>&& Handler)
m_File.Read(ReadBuffer.data(), LogDataSize, LogBaseOffset);
- for (int i = 0; i < LogEntryCount; ++i)
+ for (int i = 0; i < int(LogEntryCount); ++i)
{
Handler(ReadBuffer.data() + (i * m_RecordSize));
}
@@ -149,7 +149,7 @@ CasLogFile::Append(const void* DataPointer, uint64_t DataSize)
uint64_t AppendOffset = m_AppendOffset.fetch_add(DataSize);
std::error_code Ec;
- m_File.Write(DataPointer, gsl::narrow<DWORD>(DataSize), AppendOffset, Ec);
+ m_File.Write(DataPointer, gsl::narrow<uint32_t>(DataSize), AppendOffset, Ec);
if (Ec)
{
diff --git a/zenstore/cidstore.cpp b/zenstore/cidstore.cpp
index c5396cff3..8b53a8304 100644
--- a/zenstore/cidstore.cpp
+++ b/zenstore/cidstore.cpp
@@ -7,7 +7,7 @@
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/string.h>
-#include <zenstore/CAS.h>
+#include <zenstore/cas.h>
#include <zenstore/caslog.h>
#include <filesystem>
diff --git a/zenstore/compactcas.cpp b/zenstore/compactcas.cpp
index d4d29c179..aa60ec37b 100644
--- a/zenstore/compactcas.cpp
+++ b/zenstore/compactcas.cpp
@@ -2,7 +2,7 @@
#include <zenstore/cas.h>
-#include "CompactCas.h"
+#include "compactcas.h"
#include <zencore/compactbinarybuilder.h>
#include <zencore/except.h>
diff --git a/zenstore/compactcas.h b/zenstore/compactcas.h
index f3a933718..974f45a0c 100644
--- a/zenstore/compactcas.h
+++ b/zenstore/compactcas.h
@@ -9,7 +9,6 @@
#include <zencore/string.h>
#include <zencore/thread.h>
#include <zencore/uid.h>
-#include <zencore/windows.h>
#include <zenstore/basicfile.h>
#include <zenstore/cas.h>
#include <zenstore/caslog.h>
@@ -19,6 +18,10 @@ namespace spdlog {
class logger;
}
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
namespace zen {
//////////////////////////////////////////////////////////////////////////
diff --git a/zenstore/filecas.cpp b/zenstore/filecas.cpp
index 2fc968a91..4e4502e6d 100644
--- a/zenstore/filecas.cpp
+++ b/zenstore/filecas.cpp
@@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-#include "FileCas.h"
+#include "filecas.h"
#include <zencore/except.h>
#include <zencore/filesystem.h>
@@ -28,7 +28,9 @@
#include <unordered_map>
ZEN_THIRD_PARTY_INCLUDES_START
-#include <atlfile.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <atlfile.h>
+#endif
ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
@@ -38,7 +40,6 @@ using namespace fmt::literals;
FileCasStrategy::ShardingHelper::ShardingHelper(const std::filesystem::path& RootPath, const IoHash& ChunkHash)
{
ShardedPath.Append(RootPath.c_str());
- ShardedPath.Append(std::filesystem::path::preferred_separator);
ExtendableStringBuilder<64> HashString;
ChunkHash.ToHexString(HashString);
@@ -58,13 +59,14 @@ FileCasStrategy::ShardingHelper::ShardingHelper(const std::filesystem::path& Roo
// would probably be a good idea to measure performance for different
// policies and chunk counts
+ ShardedPath.AppendSeparator();
ShardedPath.AppendAsciiRange(str, str + 3);
- ShardedPath.Append(std::filesystem::path::preferred_separator);
+ ShardedPath.AppendSeparator();
ShardedPath.AppendAsciiRange(str + 3, str + 5);
Shard2len = ShardedPath.Size();
- ShardedPath.Append(std::filesystem::path::preferred_separator);
+ ShardedPath.AppendSeparator();
ShardedPath.AppendAsciiRange(str + 5, str + 40);
}
@@ -115,6 +117,9 @@ FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash)
{
ShardingHelper Name(m_Config.RootDirectory.c_str(), ChunkHash);
+ RwLock::ExclusiveLockScope _(LockForHash(ChunkHash));
+
+#if ZEN_PLATFORM_WINDOWS
const HANDLE ChunkFileHandle = FileRef.FileHandle;
auto DeletePayloadFileOnClose = [&] {
@@ -135,8 +140,6 @@ FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash)
//
// Future improvement: maintain Bloom filter to avoid expensive file system probes?
- RwLock::ExclusiveLockScope _(LockForHash(ChunkHash));
-
{
CAtlFile PayloadFile;
@@ -268,6 +271,49 @@ FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash)
ChunkHash);
DeletePayloadFileOnClose();
+#elif ZEN_PLATFORM_LINUX
+ std::filesystem::path SourcePath = PathFromHandle(FileRef.FileHandle);
+ std::filesystem::path DestPath = Name.ShardedPath.c_str();
+ int Ret = link(SourcePath.c_str(), DestPath.c_str());
+ if (Ret < 0 && zen::GetLastError() == ENOENT)
+ {
+ // Destination directory doesn't exist. Create it any try again.
+ CreateDirectories(DestPath.parent_path().c_str());
+ Ret = link(SourcePath.c_str(), DestPath.c_str());
+ }
+ int LinkError = zen::GetLastError();
+
+ // Unlink the file. If the path to unlink didn't exist someone else
+ // beat us to it and that is hunky-dory.
+ if (unlink(SourcePath.c_str()) < 0)
+ {
+ int UnlinkError = zen::GetLastError();
+ if (UnlinkError != ENOENT)
+ {
+ ZEN_WARN("unlink of CAS payload file failed ('{}')", GetSystemErrorAsString(UnlinkError));
+ }
+ }
+
+ // It is possible that someone beat us to it in linking the file. In that
+ // case a "file exists" error is okay. All others are not.
+ if (Ret < 0)
+ {
+ if (LinkError == EEXIST)
+ {
+ return CasStore::InsertResult{.New = false};
+ }
+
+ ZEN_WARN("link of CAS payload file failed ('{}'), falling back to regular write for insert of {}",
+ GetSystemErrorAsString(LinkError),
+ ChunkHash);
+ }
+ else
+ {
+ return CasStore::InsertResult{.New = true};
+ }
+#else
+# error check link/unlink for this platform
+#endif // ZEN_PLATFORM_*
}
return InsertChunk(Chunk.Data(), Chunk.Size(), ChunkHash);
@@ -284,6 +330,7 @@ FileCasStrategy::InsertChunk(const void* const ChunkData, const size_t ChunkSize
//
// Future improvement: maintain Bloom filter to avoid expensive file system probes?
+#if ZEN_PLATFORM_WINDOWS
CAtlFile PayloadFile;
HRESULT hRes = PayloadFile.Create(Name.ShardedPath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
@@ -298,9 +345,18 @@ FileCasStrategy::InsertChunk(const void* const ChunkData, const size_t ChunkSize
}
PayloadFile.Close();
+#elif ZEN_PLATFORM_LINUX
+ if (access(Name.ShardedPath.c_str(), F_OK) == 0)
+ {
+ return CasStore::InsertResult{.New = false};
+ }
+#else
+# error Check access() for this platform
+#endif
RwLock::ExclusiveLockScope _(LockForHash(ChunkHash));
+#if ZEN_PLATFORM_WINDOWS
// For now, use double-checked locking to see if someone else was first
hRes = PayloadFile.Create(Name.ShardedPath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING);
@@ -336,6 +392,49 @@ FileCasStrategy::InsertChunk(const void* const ChunkData, const size_t ChunkSize
{
ThrowSystemException(hRes, "Failed to open shard file '{}'"_format(WideToUtf8(Name.ShardedPath)));
}
+#else
+ // Attempt to exclusively create the file.
+ auto InternalCreateFile = [&] { return open(Name.ShardedPath.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0666); };
+ int Fd = InternalCreateFile();
+ if (Fd < 0)
+ {
+ switch (zen::GetLastError())
+ {
+ case EEXIST:
+ // Another thread has beat us to it so we're golden.
+ return {.New = false};
+
+ case ENOENT:
+ if (zen::CreateDirectories(std::string_view(Name.ShardedPath.c_str(), Name.Shard2len)))
+ {
+ Fd = InternalCreateFile();
+ if (Fd >= 0)
+ {
+ break;
+ }
+ }
+ ThrowLastError("Failed creating shard directory '{}'"_format(Name.ShardedPath));
+
+ default:
+ ThrowLastError("Unexpected error occurred opening shard file '{}'"_format(Name.ShardedPath));
+ }
+ }
+
+ struct FdWrapper
+ {
+ ~FdWrapper() { Close(); }
+ void Write(const void* Cursor, size_t Size) { (void)!write(Fd, Cursor, Size); }
+ void Close()
+ {
+ if (Fd >= 0)
+ {
+ close(Fd);
+ Fd = -1;
+ }
+ }
+ int Fd;
+ } PayloadFile = {Fd};
+#endif // ZEN_PLATFORM_WINDOWS
size_t ChunkRemain = ChunkSize;
auto ChunkCursor = reinterpret_cast<const uint8_t*>(ChunkData);
@@ -437,13 +536,13 @@ FileCasStrategy::IterateChunks(std::function<void(const IoHash& Hash, BasicFile&
struct Visitor : public FileSystemTraversal::TreeVisitor
{
Visitor(const std::filesystem::path& RootDir) : RootDirectory(RootDir) {}
- virtual void VisitFile(const std::filesystem::path& Parent, const std::wstring_view& File, uint64_t FileSize) override
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize) override
{
ZEN_UNUSED(FileSize);
std::filesystem::path RelPath = std::filesystem::relative(Parent, RootDirectory);
- std::wstring PathString = RelPath.native();
+ std::filesystem::path::string_type PathString = RelPath.native();
if ((PathString.size() == (3 + 2 + 1)) && (File.size() == (40 - 3 - 2)))
{
@@ -453,12 +552,15 @@ FileCasStrategy::IterateChunks(std::function<void(const IoHash& Hash, BasicFile&
}
PathString.append(File);
- StringBuilder<64> Utf8;
- WideToUtf8(PathString, Utf8);
-
// TODO: should validate that we're actually dealing with a valid hex string here
+#if ZEN_PLATFORM_WINDOWS
+ StringBuilder<64> Utf8;
+ WideToUtf8(PathString, Utf8);
IoHash NameHash = IoHash::FromHexString({Utf8.Data(), Utf8.Size()});
+#else
+ IoHash NameHash = IoHash::FromHexString(PathString);
+#endif
BasicFile PayloadFile;
std::error_code Ec;
@@ -471,8 +573,7 @@ FileCasStrategy::IterateChunks(std::function<void(const IoHash& Hash, BasicFile&
}
}
- virtual bool VisitDirectory([[maybe_unused]] const std::filesystem::path& Parent,
- [[maybe_unused]] const std::wstring_view& DirectoryName)
+ virtual bool VisitDirectory([[maybe_unused]] const std::filesystem::path& Parent, [[maybe_unused]] const path_view& DirectoryName)
{
return true;
}
diff --git a/zenstore/filecas.h b/zenstore/filecas.h
index a229ba6f1..ef67ae9eb 100644
--- a/zenstore/filecas.h
+++ b/zenstore/filecas.h
@@ -4,9 +4,9 @@
#include <zencore/zencore.h>
+#include <zencore/filesystem.h>
#include <zencore/iobuffer.h>
#include <zencore/iohash.h>
-#include <zencore/string.h>
#include <zencore/thread.h>
#include <zenstore/cas.h>
#include <zenstore/caslog.h>
@@ -74,8 +74,8 @@ private:
{
ShardingHelper(const std::filesystem::path& RootPath, const IoHash& ChunkHash);
- size_t Shard2len = 0;
- ExtendableWideStringBuilder<128> ShardedPath;
+ size_t Shard2len = 0;
+ ExtendablePathBuilder<128> ShardedPath;
};
};
diff --git a/zenstore/gc.cpp b/zenstore/gc.cpp
index 307adc04e..7c6fa4dab 100644
--- a/zenstore/gc.cpp
+++ b/zenstore/gc.cpp
@@ -306,7 +306,7 @@ CasGc::CollectGarbage(GcContext& GcCtx)
}
// Remove Cid to CAS hash mappings. Scrub?
-
+
if (CidStore* CidStore = m_CidStore)
{
CidStore->RemoveCids(GcCtx.DeletedCas());
diff --git a/zenstore/include/zenstore/basicfile.h b/zenstore/include/zenstore/basicfile.h
index e4414787c..2df016c76 100644
--- a/zenstore/include/zenstore/basicfile.h
+++ b/zenstore/include/zenstore/basicfile.h
@@ -5,7 +5,6 @@
#include "zenstore.h"
#include <zencore/iobuffer.h>
-#include <zencore/windows.h>
#include <filesystem>
#include <functional>
diff --git a/zenstore/include/zenstore/CAS.h b/zenstore/include/zenstore/cas.h
index 2e530c418..9371e4de0 100644
--- a/zenstore/include/zenstore/CAS.h
+++ b/zenstore/include/zenstore/cas.h
@@ -45,9 +45,9 @@ public:
void AddChunksToSet(std::span<const IoHash> HashesToAdd);
void RemoveChunksIf(std::function<bool(const IoHash& CandidateHash)>&& Predicate);
void IterateChunks(std::function<void(const IoHash& ChunkHash)>&& Callback);
- inline [[nodiscard]] bool ContainsChunk(const IoHash& Hash) const { return m_ChunkSet.find(Hash) != m_ChunkSet.end(); }
- inline [[nodiscard]] bool IsEmpty() const { return m_ChunkSet.empty(); }
- inline [[nodiscard]] size_t GetSize() const { return m_ChunkSet.size(); }
+ [[nodiscard]] inline bool ContainsChunk(const IoHash& Hash) const { return m_ChunkSet.find(Hash) != m_ChunkSet.end(); }
+ [[nodiscard]] inline bool IsEmpty() const { return m_ChunkSet.empty(); }
+ [[nodiscard]] inline size_t GetSize() const { return m_ChunkSet.size(); }
inline void FilterChunks(std::span<const IoHash> Candidates, std::invocable<const IoHash&> auto MatchFunc)
{
diff --git a/zenstore/include/zenstore/caslog.h b/zenstore/include/zenstore/caslog.h
index 9b2ef574c..1ecb721f7 100644
--- a/zenstore/include/zenstore/caslog.h
+++ b/zenstore/include/zenstore/caslog.h
@@ -8,10 +8,13 @@
#include <zencore/string.h>
#include <zencore/thread.h>
#include <zencore/uid.h>
-#include <zencore/windows.h>
#include <zenstore/basicfile.h>
#include <zenstore/cas.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
#include <functional>
namespace zen {
diff --git a/zenstore/include/zenstore/cidstore.h b/zenstore/include/zenstore/cidstore.h
index a8cb87f40..04a3d419c 100644
--- a/zenstore/include/zenstore/cidstore.h
+++ b/zenstore/include/zenstore/cidstore.h
@@ -5,7 +5,7 @@
#include "zenstore.h"
#include <zencore/iohash.h>
-#include <zenstore/CAS.h>
+#include <zenstore/cas.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <tsl/robin_map.h>
diff --git a/zenstore/zenstore.cpp b/zenstore/zenstore.cpp
index 337a1c75b..dbb3dbbf7 100644
--- a/zenstore/zenstore.cpp
+++ b/zenstore/zenstore.cpp
@@ -2,8 +2,8 @@
#include "zenstore/zenstore.h"
-#include <zenstore/CAS.h>
#include <zenstore/basicfile.h>
+#include <zenstore/cas.h>
#include <zenstore/gc.h>
#include "compactcas.h"
#include "filecas.h"
diff --git a/zenstore/zenstore.vcxproj b/zenstore/zenstore.vcxproj
index 832ea8159..77a4dff7d 100644
--- a/zenstore/zenstore.vcxproj
+++ b/zenstore/zenstore.vcxproj
@@ -28,7 +28,7 @@
<ClInclude Include="include\zenstore\cidstore.h" />
<ClInclude Include="include\zenstore\gc.h" />
<ClInclude Include="include\zenstore\scrub.h" />
- <ClInclude Include="include\zenstore\CAS.h" />
+ <ClInclude Include="include\zenstore\cas.h" />
<ClInclude Include="include\zenstore\caslog.h" />
<ClInclude Include="include\zenstore\zenstore.h" />
</ItemGroup>
diff --git a/zenstore/zenstore.vcxproj.filters b/zenstore/zenstore.vcxproj.filters
index 8a52c69f6..904de0748 100644
--- a/zenstore/zenstore.vcxproj.filters
+++ b/zenstore/zenstore.vcxproj.filters
@@ -14,7 +14,7 @@
<ItemGroup>
<ClInclude Include="compactcas.h" />
<ClInclude Include="filecas.h" />
- <ClInclude Include="include\zenstore\CAS.h" />
+ <ClInclude Include="include\zenstore\cas.h" />
<ClInclude Include="include\zenstore\caslog.h" />
<ClInclude Include="include\zenstore\gc.h" />
<ClInclude Include="include\zenstore\scrub.h" />
diff --git a/zenutil/include/zenutil/zenserverprocess.h b/zenutil/include/zenutil/zenserverprocess.h
index 8a4f9604d..55b9a50cd 100644
--- a/zenutil/include/zenutil/zenserverprocess.h
+++ b/zenutil/include/zenutil/zenserverprocess.h
@@ -67,8 +67,8 @@ struct ZenServerInstance
private:
ZenServerEnvironment& m_Env;
ProcessHandle m_Process;
- Event m_ReadyEvent;
- Event m_ShutdownEvent;
+ NamedEvent m_ReadyEvent;
+ NamedEvent m_ShutdownEvent;
bool m_Terminate = false;
std::filesystem::path m_TestDir;
bool m_MeshEnabled = false;
diff --git a/zenutil/zenserverprocess.cpp b/zenutil/zenserverprocess.cpp
index bc8526a20..93886a6b7 100644
--- a/zenutil/zenserverprocess.cpp
+++ b/zenutil/zenserverprocess.cpp
@@ -8,18 +8,24 @@
#include <zencore/logging.h>
#include <zencore/session.h>
#include <zencore/string.h>
+#include <zencore/thread.h>
-#include <atlbase.h>
-#include <shellapi.h>
+#include <atomic>
#include <source_location>
-#include <zencore/windows.h>
+#if ZEN_PLATFORM_WINDOWS
+# include <atlbase.h>
+# include <zencore/windows.h>
+#else
+# include <sys/mman.h>
+#endif
//////////////////////////////////////////////////////////////////////////
namespace zen {
namespace zenutil {
+#if ZEN_PLATFORM_WINDOWS
class SecurityAttributes
{
public:
@@ -53,6 +59,7 @@ namespace zenutil {
}
}
};
+#endif // ZEN_PLATFORM_WINDOWS
} // namespace zenutil
@@ -72,21 +79,35 @@ ZenServerState::~ZenServerState()
m_OurEntry = nullptr;
}
+#if ZEN_PLATFORM_WINDOWS
if (m_Data)
{
UnmapViewOfFile(m_Data);
- m_Data = nullptr;
}
if (m_hMapFile)
{
CloseHandle(m_hMapFile);
}
+#else
+ if (m_Data != nullptr)
+ {
+ munmap(m_Data, m_MaxEntryCount * sizeof(ZenServerEntry));
+ }
+
+ int Fd = int(intptr_t(m_hMapFile));
+ close(Fd);
+#endif
+
+ m_Data = nullptr;
}
void
ZenServerState::Initialize()
{
+ size_t MapSize = m_MaxEntryCount * sizeof(ZenServerEntry);
+
+#if ZEN_PLATFORM_WINDOWS
// TODO: there's a small chance of a race here, this logic could be tightened up with a mutex to
// ensure only a single process at a time creates the mapping
// TODO: the fallback to Local instead of Global has a flaw where if you start a non-elevated instance
@@ -94,25 +115,23 @@ ZenServerState::Initialize()
// mapping and the second instance with a global mapping. This kind of elevated/non-elevated
// shouldn't be common, but handling for it should be improved in the future.
- if (HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\ZenMap"))
- {
- m_hMapFile = hMap;
- }
- else if (HANDLE hLocalMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\ZenMap"))
+ HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\ZenMap");
+ if (hMap == NULL)
{
- m_hMapFile = hLocalMap;
+ hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\ZenMap");
}
- else
+
+ if (hMap == NULL)
{
// Security attributes to enable any user to access state
zenutil::AnyUserSecurityAttributes Attrs;
- hMap = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file
- Attrs.Attributes(), // allow anyone to access
- PAGE_READWRITE, // read/write access
- 0, // maximum object size (high-order DWORD)
- m_MaxEntryCount * sizeof(ZenServerEntry), // maximum object size (low-order DWORD)
- L"Global\\ZenMap"); // name of mapping object
+ hMap = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file
+ Attrs.Attributes(), // allow anyone to access
+ PAGE_READWRITE, // read/write access
+ 0, // maximum object size (high-order DWORD)
+ DWORD(MapSize), // maximum object size (low-order DWORD)
+ L"Global\\ZenMap"); // name of mapping object
if (hMap == NULL)
{
@@ -128,21 +147,37 @@ ZenServerState::Initialize()
{
ThrowLastError("Could not open or create file mapping object for Zen server state");
}
-
- m_hMapFile = hMap;
}
- void* pBuf = MapViewOfFile(m_hMapFile, // handle to map object
+ void* pBuf = MapViewOfFile(hMap, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0, // offset high
0, // offset low
- m_MaxEntryCount * sizeof(ZenServerEntry));
+ DWORD(MapSize));
if (pBuf == NULL)
{
ThrowLastError("Could not map view of Zen server state");
}
+#else
+ int Fd = shm_open("UnrealEngineZen", O_RDWR | O_CREAT, 0666);
+ if (Fd < 0)
+ {
+ ThrowLastError("Could not open a shared memory object");
+ }
+ void* hMap = (void*)intptr_t(Fd);
+ int Result = ftruncate(Fd, MapSize);
+ ZEN_UNUSED(Result);
+
+ void* pBuf = mmap(nullptr, MapSize, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0);
+ if (pBuf == MAP_FAILED)
+ {
+ ThrowLastError("Could not map view of Zen server state");
+ }
+#endif
+
+ m_hMapFile = hMap;
m_Data = reinterpret_cast<ZenServerEntry*>(pBuf);
m_IsReadOnly = false;
}
@@ -150,31 +185,47 @@ ZenServerState::Initialize()
bool
ZenServerState::InitializeReadOnly()
{
- if (HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\ZenMap"))
- {
- m_hMapFile = hMap;
- }
- else if (HANDLE hLocalMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\ZenMap"))
+ size_t MapSize = m_MaxEntryCount * sizeof(ZenServerEntry);
+
+#if ZEN_PLATFORM_WINDOWS
+ HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Global\\ZenMap");
+ if (hMap == NULL)
{
- m_hMapFile = hLocalMap;
+ hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Local\\ZenMap");
}
- else
+
+ if (hMap == NULL)
{
return false;
}
- void* pBuf = MapViewOfFile(m_hMapFile, // handle to map object
+ void* pBuf = MapViewOfFile(hMap, // handle to map object
FILE_MAP_READ, // read permission
0, // offset high
0, // offset low
- m_MaxEntryCount * sizeof(ZenServerEntry));
+ MapSize);
if (pBuf == NULL)
{
ThrowLastError("Could not map view of Zen server state");
}
+#else
+ int Fd = shm_open("UnrealEngineZen", O_RDONLY, 0666);
+ if (Fd < 0)
+ {
+ return false;
+ }
+ void* hMap = (void*)intptr_t(Fd);
- m_Data = reinterpret_cast<ZenServerEntry*>(pBuf);
+ void* pBuf = mmap(nullptr, MapSize, PROT_READ, MAP_PRIVATE, Fd, 0);
+ if (pBuf == MAP_FAILED)
+ {
+ ThrowLastError("Could not map read-only view of Zen server state");
+ }
+#endif
+
+ m_hMapFile = hMap;
+ m_Data = reinterpret_cast<ZenServerEntry*>(pBuf);
return true;
}
@@ -209,7 +260,7 @@ ZenServerState::Register(int ListenPort)
{
ZenServerEntry& Entry = m_Data[i];
- if (Entry.ListenPort.load(std::memory_order::memory_order_relaxed) == 0)
+ if (Entry.ListenPort.load(std::memory_order_relaxed) == 0)
{
uint16_t Expected = 0;
if (Entry.ListenPort.compare_exchange_strong(Expected, uint16_t(ListenPort)))
@@ -302,7 +353,7 @@ ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd)
{
for (std::atomic<uint32_t>& PidEntry : SponsorPids)
{
- if (PidEntry.load(std::memory_order::memory_order_relaxed) == 0)
+ if (PidEntry.load(std::memory_order_relaxed) == 0)
{
uint32_t Expected = 0;
if (PidEntry.compare_exchange_strong(Expected, PidToAdd))
@@ -311,7 +362,7 @@ ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd)
return true;
}
}
- else if (PidEntry.load(std::memory_order::memory_order_relaxed) == PidToAdd)
+ else if (PidEntry.load(std::memory_order_relaxed) == PidToAdd)
{
// Success, the because pid is already in the list
return true;
@@ -428,10 +479,7 @@ ZenServerInstance::SpawnServer(int BasePort, std::string_view AdditionalServerAr
{
ZEN_ASSERT(!m_Process.IsValid()); // Only spawn once
- const std::filesystem::path BaseDir = m_Env.ProgramBaseDir();
- const std::filesystem::path Executable = BaseDir / "zenserver.exe";
-
- const int MyPid = _getpid();
+ const int MyPid = zen::GetCurrentProcessId();
const int ChildId = ++ChildIdCounter;
ExtendableStringBuilder<32> ChildEventName;
@@ -443,10 +491,8 @@ ZenServerInstance::SpawnServer(int BasePort, std::string_view AdditionalServerAr
ExtendableStringBuilder<32> LogId;
LogId << "Zen" << ChildId;
- ExtendableWideStringBuilder<512> CommandLine;
- CommandLine << "\"";
- CommandLine.Append(Executable.c_str());
- CommandLine << "\"";
+ ExtendableStringBuilder<512> CommandLine;
+ CommandLine << "zenserver" ZEN_EXE_SUFFIX_LITERAL; // see CreateProc() call for actual binary path
const bool IsTest = m_Env.IsTestEnvironment();
@@ -476,7 +522,7 @@ ZenServerInstance::SpawnServer(int BasePort, std::string_view AdditionalServerAr
if (!m_TestDir.empty())
{
CommandLine << " --data-dir ";
- CommandLine << m_TestDir.c_str();
+ PathToUtf8(m_TestDir.c_str(), CommandLine);
}
if (m_MeshEnabled)
@@ -493,87 +539,38 @@ ZenServerInstance::SpawnServer(int BasePort, std::string_view AdditionalServerAr
ZEN_DEBUG("Spawning server '{}'", LogId);
- PROCESS_INFORMATION ProcessInfo{};
- STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)};
-
- DWORD CreationFlags = 0;
-
+ uint32_t CreationFlags = 0;
if (!IsTest)
{
- CreationFlags |= CREATE_NEW_CONSOLE;
+ CreationFlags |= CreateProcOptions::Flag_NewConsole;
}
- HANDLE hProcess = NULL;
-
+ const std::filesystem::path BaseDir = m_Env.ProgramBaseDir();
+ const std::filesystem::path Executable = BaseDir / "zenserver" ZEN_EXE_SUFFIX_LITERAL;
+ CreateProcOptions CreateOptions = {
+ .WorkingDirectory = &CurrentDirectory,
+ .Flags = CreationFlags,
+ };
+ CreateProcResult ChildPid = CreateProc(Executable, CommandLine.ToView(), CreateOptions);
+#if ZEN_PLATFORM_WINDOWS
+ if (!ChildPid && ::GetLastError() == ERROR_ELEVATION_REQUIRED)
{
- const bool InheritHandles = false;
- void* Environment = nullptr;
- LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
- LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
-
- BOOL Success = CreateProcessW(Executable.c_str(),
- (LPWSTR)CommandLine.c_str(),
- ProcessAttributes,
- ThreadAttributes,
- InheritHandles,
- CreationFlags,
- Environment,
- CurrentDirectory.c_str(),
- &StartupInfo,
- &ProcessInfo);
-
- if (Success)
- {
- hProcess = ProcessInfo.hProcess;
- CloseHandle(ProcessInfo.hThread);
- }
- else
- {
- DWORD WinError = ::GetLastError();
-
- if (WinError == ERROR_ELEVATION_REQUIRED)
- {
- // Try launching elevated process
-
- ZEN_DEBUG("Regular spawn failed - spawning elevated server");
-
- 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 = CommandLine.c_str();
-
- if (::ShellExecuteEx(&ShellExecuteInfo))
- {
- WinError = NO_ERROR;
-
- hProcess = ShellExecuteInfo.hProcess;
- }
- }
-
- if (WinError != NO_ERROR)
- {
- std::error_code err(WinError, std::system_category());
-
- ZEN_ERROR("Server spawn failed: {}", err.message());
+ ZEN_DEBUG("Regular spawn failed - spawning elevated server");
+ CreateOptions.Flags |= CreateProcOptions::Flag_Elevated;
+ ChildPid = CreateProc(Executable, CommandLine.ToView(), CreateOptions);
+ }
+#endif
- throw std::system_error(err, "failed to create server process");
- }
- }
+ if (!ChildPid)
+ {
+ ThrowLastError("Server spawn failed");
}
ZEN_DEBUG("Server '{}' spawned OK", LogId);
if (IsTest)
{
- m_Process.Initialize(hProcess);
- }
- else
- {
- CloseHandle(hProcess);
+ m_Process.Initialize(ChildPid);
}
m_ReadyEvent = std::move(ChildEvent);
@@ -637,6 +634,7 @@ ZenServerInstance::WaitUntilReady()
{
if (!m_Process.IsRunning() || !m_Process.IsValid())
{
+ ZEN_INFO("Wait abandoned by invalid process (running={})", m_Process.IsRunning());
return;
}
}