diff options
| author | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
|---|---|---|
| committer | Per Larsson <[email protected]> | 2021-12-14 12:34:47 +0100 |
| commit | b6c6568e1618f10d2160d836b65e35586e3c740f (patch) | |
| tree | f6a929cf918850bbba87d0ee67cd3482b2d50e24 | |
| parent | Fixed bug in z$ service returning partial cache records and enable small obje... (diff) | |
| parent | Partial revert b363c5b (diff) | |
| download | zen-b6c6568e1618f10d2160d836b65e35586e3c740f.tar.xz zen-b6c6568e1618f10d2160d836b65e35586e3c740f.zip | |
Merged main.
99 files changed, 8216 insertions, 922 deletions
@@ -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(>raceStatistics.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(>raceStatistics.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 : */
@@ -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(); @@ -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; } } |