diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/progressbar.cpp | 227 | ||||
| -rw-r--r-- | src/zen/progressbar.h | 9 | ||||
| -rw-r--r-- | src/zencore/compactbinaryjson.cpp | 34 | ||||
| -rw-r--r-- | src/zencore/filesystem.cpp | 68 | ||||
| -rw-r--r-- | src/zencore/include/zencore/filesystem.h | 4 | ||||
| -rw-r--r-- | src/zencore/include/zencore/fmtutils.h | 22 | ||||
| -rw-r--r-- | src/zencore/include/zencore/process.h | 3 | ||||
| -rw-r--r-- | src/zencore/include/zencore/string.h | 37 | ||||
| -rw-r--r-- | src/zencore/include/zencore/thread.h | 2 | ||||
| -rw-r--r-- | src/zencore/process.cpp | 19 | ||||
| -rw-r--r-- | src/zencore/string.cpp | 28 | ||||
| -rw-r--r-- | src/zencore/thread.cpp | 4 | ||||
| -rw-r--r-- | src/zencore/trace.cpp | 3 |
13 files changed, 414 insertions, 46 deletions
diff --git a/src/zen/progressbar.cpp b/src/zen/progressbar.cpp index b56fc39e8..5d8746f0e 100644 --- a/src/zen/progressbar.cpp +++ b/src/zen/progressbar.cpp @@ -11,6 +11,10 @@ #include <zenremotestore/operationlogoutput.h> #include <zenutil/consoletui.h> +#if !ZEN_PLATFORM_WINDOWS +# include <csignal> +#endif + ZEN_THIRD_PARTY_INCLUDES_START #include <gsl/gsl-lite.hpp> ZEN_THIRD_PARTY_INCLUDES_END @@ -19,6 +23,87 @@ ZEN_THIRD_PARTY_INCLUDES_END namespace zen { +// Global tracking for scroll region cleanup on abnormal termination (Ctrl+C etc.) +// Only one ProgressBar can own a scroll region at a time. +static std::atomic<ProgressBar*> g_ActiveScrollRegionOwner{nullptr}; +static std::atomic<uint32_t> g_ActiveScrollRegionRows{0}; + +static void +ResetScrollRegionRaw() +{ + // Signal-safe: emit raw escape sequences to restore terminal state. + // These are async-signal-safe on POSIX (write()) and safe in console + // ctrl handlers on Windows (WriteConsole is allowed). + uint32_t Rows = g_ActiveScrollRegionRows.load(std::memory_order_acquire); + if (Rows >= 3) + { + // Move to status line, erase it, reset scroll region, move cursor to end of content + TuiMoveCursor(Rows, 1); + TuiEraseLine(); + TuiResetScrollRegion(); + TuiMoveCursor(Rows - 1, 1); + } + else + { + TuiResetScrollRegion(); + } + TuiShowCursor(true); + TuiFlush(); +} + +#if ZEN_PLATFORM_WINDOWS +static BOOL WINAPI +ScrollRegionCtrlHandler(DWORD CtrlType) +{ + if (CtrlType == CTRL_C_EVENT || CtrlType == CTRL_BREAK_EVENT) + { + ResetScrollRegionRaw(); + } + // Return FALSE so the default handler (process termination) still runs + return FALSE; +} +#else +static struct sigaction s_PrevSigIntAction; +static struct sigaction s_PrevSigTermAction; + +static void +ScrollRegionSignalHandler(int Signal) +{ + ResetScrollRegionRaw(); + + // Re-raise with the previous handler + struct sigaction* PrevAction = (Signal == SIGINT) ? &s_PrevSigIntAction : &s_PrevSigTermAction; + sigaction(Signal, PrevAction, nullptr); + raise(Signal); +} +#endif + +static void +InstallScrollRegionCleanupHandler() +{ +#if ZEN_PLATFORM_WINDOWS + SetConsoleCtrlHandler(ScrollRegionCtrlHandler, TRUE); +#else + struct sigaction Action = {}; + Action.sa_handler = ScrollRegionSignalHandler; + Action.sa_flags = SA_RESETHAND; // one-shot + sigemptyset(&Action.sa_mask); + sigaction(SIGINT, &Action, &s_PrevSigIntAction); + sigaction(SIGTERM, &Action, &s_PrevSigTermAction); +#endif +} + +static void +RemoveScrollRegionCleanupHandler() +{ +#if ZEN_PLATFORM_WINDOWS + SetConsoleCtrlHandler(ScrollRegionCtrlHandler, FALSE); +#else + sigaction(SIGINT, &s_PrevSigIntAction, nullptr); + sigaction(SIGTERM, &s_PrevSigTermAction, nullptr); +#endif +} + #if ZEN_PLATFORM_WINDOWS static HANDLE GetConsoleHandle() @@ -66,6 +151,7 @@ GetUpdateDelayMS(ProgressBar::Mode InMode) case ProgressBar::Mode::Plain: return 5000; case ProgressBar::Mode::Pretty: + case ProgressBar::Mode::PrettyScroll: return 200; case ProgressBar::Mode::Log: return 2000; @@ -119,7 +205,7 @@ ProgressBar::PopLogOperation(Mode InMode) } ProgressBar::ProgressBar(Mode InMode, std::string_view InSubTask) -: m_Mode((!TuiIsStdoutTty() && InMode == Mode::Pretty) ? Mode::Plain : InMode) +: m_Mode((!TuiIsStdoutTty() && (InMode == Mode::Pretty || InMode == Mode::PrettyScroll)) ? Mode::Plain : InMode) , m_LastUpdateMS((uint64_t)-1) , m_PausedMS(0) , m_SubTask(InSubTask) @@ -129,12 +215,18 @@ ProgressBar::ProgressBar(Mode InMode, std::string_view InSubTask) { PushLogOperation(InMode, m_SubTask); } + + if (m_Mode == Mode::PrettyScroll) + { + SetupScrollRegion(); + } } ProgressBar::~ProgressBar() { try { + TeardownScrollRegion(); ForceLinebreak(); if (!m_SubTask.empty()) { @@ -148,6 +240,81 @@ ProgressBar::~ProgressBar() } void +ProgressBar::SetupScrollRegion() +{ + uint32_t Rows = TuiConsoleRows(0); + if (Rows < 3) + { + return; + } + + TuiEnableOutput(); + + // Ensure cursor is not on the last row before we install the region. + // Print a newline to push content up if needed, then set the region. + OutputToConsoleRaw("\n"); + TuiSetScrollRegion(1, Rows - 1); + + // Move cursor into the scroll region so normal output stays there + TuiMoveCursor(Rows - 1, 1); + + m_ScrollRegionActive = true; + m_ScrollRegionRows = Rows; + + g_ActiveScrollRegionRows.store(Rows, std::memory_order_release); + g_ActiveScrollRegionOwner.store(this, std::memory_order_release); + InstallScrollRegionCleanupHandler(); +} + +void +ProgressBar::TeardownScrollRegion() +{ + if (!m_ScrollRegionActive) + { + return; + } + m_ScrollRegionActive = false; + + RemoveScrollRegionCleanupHandler(); + g_ActiveScrollRegionOwner.store(nullptr, std::memory_order_release); + g_ActiveScrollRegionRows.store(0, std::memory_order_release); + + // Emit all teardown escape sequences as a single atomic write + ExtendableStringBuilder<128> Buf; + Buf << fmt::format("\x1b[{};1H", m_ScrollRegionRows) // move to status line + << "\x1b[2K" // erase it + << "\x1b[r" // reset scroll region + << fmt::format("\x1b[{};1H", m_ScrollRegionRows - 1); // move to end of content + OutputToConsoleRaw(Buf); + TuiFlush(); +} + +void +ProgressBar::RenderStatusLine(std::string_view Line) +{ + // Handle terminal resizes by re-querying row count + uint32_t CurrentRows = TuiConsoleRows(0); + if (CurrentRows >= 3 && CurrentRows != m_ScrollRegionRows) + { + // Terminal was resized — reinstall scroll region + TuiSetScrollRegion(1, CurrentRows - 1); + m_ScrollRegionRows = CurrentRows; + } + + // Build the entire escape sequence as a single string so the console write + // is atomic and log output from other threads cannot interleave. + ExtendableStringBuilder<512> Buf; + Buf << "\x1b" + "7" // ESC 7 — save cursor + << fmt::format("\x1b[{};1H", m_ScrollRegionRows) // move to bottom row + << "\x1b[2K" // erase entire line + << Line // progress bar content + << "\x1b" + "8"; // ESC 8 — restore cursor + OutputToConsoleRaw(Buf); +} + +void ProgressBar::UpdateState(const State& NewState, bool DoLinebreak) { ZEN_ASSERT(NewState.TotalCount >= NewState.RemainingCount); @@ -233,7 +400,7 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak) OutputToConsoleRaw(Output); m_State = NewState; } - else if (m_Mode == Mode::Pretty) + else if (m_Mode == Mode::Pretty || m_Mode == Mode::PrettyScroll) { size_t ProgressBarSize = 20; @@ -265,11 +432,11 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak) ExtendableStringBuilder<256> OutputBuilder; - OutputBuilder.Append('\r'); AppendPaddedTask(OutputBuilder); OutputBuilder.Append(' '); OutputBuilder.Append(PercentString.ToView()); - if (OutputBuilder.Size() + 1 < ConsoleColumns) + + if (OutputBuilder.Size() + 1 < ConsoleColumns) { size_t RemainingSpace = ConsoleColumns - (OutputBuilder.Size() + 1); bool ElapsedFits = RemainingSpace >= ElapsedString.Size(); @@ -299,35 +466,43 @@ ProgressBar::UpdateState(const State& NewState, bool DoLinebreak) } } - std::string_view Output = OutputBuilder.ToView(); - std::string::size_type EraseLength = m_LastOutputLength > Output.length() ? (m_LastOutputLength - Output.length()) : 0; + if (m_ScrollRegionActive) + { + // Render on the pinned bottom status line + RenderStatusLine(OutputBuilder.ToView()); + } + else + { + // Fallback: inline \r-based overwrite (original behavior) + std::string_view Output = OutputBuilder.ToView(); + std::string::size_type EraseLength = + m_LastOutputLength > (Output.length() + 1) ? (m_LastOutputLength - Output.length() - 1) : 0; + ExtendableStringBuilder<256> LineToPrint; - ExtendableStringBuilder<256> LineToPrint; + if (Output.length() + 1 + EraseLength >= ConsoleColumns) + { + if (m_LastOutputLength > 0) + { + LineToPrint << "\n"; + } + LineToPrint << Output; + DoLinebreak = true; + } + else + { + LineToPrint << "\r" << Output << std::string(EraseLength, ' '); + } - if (Output.length() + EraseLength >= ConsoleColumns) - { - if (m_LastOutputLength > 0) + if (DoLinebreak) { LineToPrint.Append('\n'); } - LineToPrint.Append(Output.substr(1)); - DoLinebreak = true; - } - else - { - LineToPrint.Append(Output); - LineToPrint.AppendFill(' ', EraseLength); - } - if (DoLinebreak) - { - LineToPrint.Append('\n'); + OutputToConsoleRaw(LineToPrint); + m_LastOutputLength = DoLinebreak ? 0 : (Output.length() + 1); // +1 for \r prefix } - OutputToConsoleRaw(LineToPrint); - - m_LastOutputLength = DoLinebreak ? 0 : Output.length(); - m_State = NewState; + m_State = NewState; } else if (m_Mode == Mode::Log) { @@ -381,6 +556,8 @@ ProgressBar::ForceLinebreak() void ProgressBar::Finish() { + TeardownScrollRegion(); + if (m_LastOutputLength > 0 || m_State.RemainingCount > 0) { State NewState = m_State; diff --git a/src/zen/progressbar.h b/src/zen/progressbar.h index b54c009e1..94df2ca3a 100644 --- a/src/zen/progressbar.h +++ b/src/zen/progressbar.h @@ -48,6 +48,7 @@ public: { Plain, Pretty, + PrettyScroll, Log, Quiet }; @@ -67,13 +68,19 @@ public: bool HasActiveTask() const; private: + void SetupScrollRegion(); + void TeardownScrollRegion(); + void RenderStatusLine(std::string_view Line); + const Mode m_Mode; Stopwatch m_SW; uint64_t m_LastUpdateMS; uint64_t m_PausedMS; State m_State; const std::string m_SubTask; - size_t m_LastOutputLength = 0; + size_t m_LastOutputLength = 0; + bool m_ScrollRegionActive = false; + uint32_t m_ScrollRegionRows = 0; }; uint32_t GetUpdateDelayMS(ProgressBar::Mode InMode); diff --git a/src/zencore/compactbinaryjson.cpp b/src/zencore/compactbinaryjson.cpp index 736023ae7..f57d2fa9a 100644 --- a/src/zencore/compactbinaryjson.cpp +++ b/src/zencore/compactbinaryjson.cpp @@ -11,6 +11,8 @@ #include <zencore/testing.h> #include <fmt/format.h> +#include <cmath> +#include <limits> #include <vector> ZEN_THIRD_PARTY_INCLUDES_START @@ -570,13 +572,37 @@ private: break; case Json::Type::NUMBER: { - if (FieldName.empty()) - { - Writer.AddFloat(Json.number_value()); + // If the JSON number has no fractional part and fits in an int64, + // store it as an integer so that AsInt32/AsInt64 work without + // requiring callers to go through AsFloat. + double Value = Json.number_value(); + double IntPart; + bool IsIntegral = (std::modf(Value, &IntPart) == 0.0) && + Value >= static_cast<double>(std::numeric_limits<int64_t>::min()) && + Value <= static_cast<double>(std::numeric_limits<int64_t>::max()); + + if (IsIntegral) + { + int64_t IntValue = static_cast<int64_t>(Value); + if (FieldName.empty()) + { + Writer.AddInteger(IntValue); + } + else + { + Writer.AddInteger(FieldName, IntValue); + } } else { - Writer.AddFloat(FieldName, Json.number_value()); + if (FieldName.empty()) + { + Writer.AddFloat(Value); + } + else + { + Writer.AddFloat(FieldName, Value); + } } } break; diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index ae5103f93..1e5c88b88 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -2977,6 +2977,35 @@ GetEnvVariable(std::string_view VariableName) return ""; } +std::string +ExpandEnvironmentVariables(std::string_view Input) +{ + std::string Result; + Result.reserve(Input.size()); + + for (size_t i = 0; i < Input.size(); ++i) + { + if (Input[i] == '%') + { + size_t End = Input.find('%', i + 1); + if (End != std::string_view::npos && End > i + 1) + { + std::string_view VarName = Input.substr(i + 1, End - i - 1); + std::string Value = GetEnvVariable(VarName); + if (!Value.empty()) + { + Result += Value; + i = End; + continue; + } + } + } + Result += Input[i]; + } + + return Result; +} + std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles) { @@ -4122,6 +4151,45 @@ TEST_CASE("filesystem.MakeSafeAbsolutePath") # endif // ZEN_PLATFORM_WINDOWS } +TEST_CASE("ExpandEnvironmentVariables") +{ + // No variables — pass-through + CHECK_EQ(ExpandEnvironmentVariables("plain/path"), "plain/path"); + CHECK_EQ(ExpandEnvironmentVariables(""), ""); + + // Single percent sign is not a variable reference + CHECK_EQ(ExpandEnvironmentVariables("50%"), "50%"); + + // Empty variable name (%%) is not expanded + CHECK_EQ(ExpandEnvironmentVariables("%%"), "%%"); + + // Known variable +# if ZEN_PLATFORM_WINDOWS + // PATH is always set on Windows + std::string PathValue = GetEnvVariable("PATH"); + CHECK(!PathValue.empty()); + CHECK_EQ(ExpandEnvironmentVariables("%PATH%"), PathValue); + CHECK_EQ(ExpandEnvironmentVariables("prefix/%PATH%/suffix"), "prefix/" + PathValue + "/suffix"); +# else + std::string HomeValue = GetEnvVariable("HOME"); + CHECK(!HomeValue.empty()); + CHECK_EQ(ExpandEnvironmentVariables("%HOME%"), HomeValue); + CHECK_EQ(ExpandEnvironmentVariables("prefix/%HOME%/suffix"), "prefix/" + HomeValue + "/suffix"); +# endif + + // Unknown variable is left unexpanded + CHECK_EQ(ExpandEnvironmentVariables("%ZEN_UNLIKELY_SET_VAR_12345%"), "%ZEN_UNLIKELY_SET_VAR_12345%"); + + // Multiple variables +# if ZEN_PLATFORM_WINDOWS + std::string OSValue = GetEnvVariable("OS"); + if (!OSValue.empty()) + { + CHECK_EQ(ExpandEnvironmentVariables("%PATH%/%OS%"), PathValue + "/" + OSValue); + } +# endif +} + TEST_SUITE_END(); #endif diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h index 6dc159a83..73769cdb4 100644 --- a/src/zencore/include/zencore/filesystem.h +++ b/src/zencore/include/zencore/filesystem.h @@ -400,6 +400,10 @@ void GetDirectoryContent(const std::filesystem::path& RootDir, std::string GetEnvVariable(std::string_view VariableName); +// Expands %VAR% environment variable references in a string. +// Unknown or empty variables are left unexpanded. +std::string ExpandEnvironmentVariables(std::string_view Input); + std::filesystem::path SearchPathForExecutable(std::string_view ExecutableName); std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles); diff --git a/src/zencore/include/zencore/fmtutils.h b/src/zencore/include/zencore/fmtutils.h index 4ec05f901..2a829d2d5 100644 --- a/src/zencore/include/zencore/fmtutils.h +++ b/src/zencore/include/zencore/fmtutils.h @@ -38,27 +38,25 @@ struct fmt::formatter<T> : fmt::formatter<std::string_view> } }; -// Custom formatting for some zencore types +// Generic formatter for any type that is explicitly convertible to std::string_view. +// This covers NiceNum, NiceBytes, ThousandsNum, StringBuilder, and similar types +// without needing per-type fmt::formatter specializations. template<typename T> -requires DerivedFrom<T, zen::StringBuilderBase> -struct fmt::formatter<T> : fmt::formatter<std::string_view> +concept HasStringViewConversion = std::is_class_v<T> && requires(const T& v) { - template<typename FormatContext> - auto format(const zen::StringBuilderBase& a, FormatContext& ctx) const { - return fmt::formatter<std::string_view>::format(a.ToView(), ctx); - } -}; + std::string_view(v) + } -> std::same_as<std::string_view>; +} && !HasFreeToString<T> && !std::is_same_v<T, std::string> && !std::is_same_v<T, std::string_view>; -template<typename T> -requires DerivedFrom<T, zen::NiceBase> +template<HasStringViewConversion T> struct fmt::formatter<T> : fmt::formatter<std::string_view> { template<typename FormatContext> - auto format(const zen::NiceBase& a, FormatContext& ctx) const + auto format(const T& Value, FormatContext& ctx) const { - return fmt::formatter<std::string_view>::format(std::string_view(a), ctx); + return fmt::formatter<std::string_view>::format(std::string_view(Value), ctx); } }; diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h index 8cbed781d..19804795b 100644 --- a/src/zencore/include/zencore/process.h +++ b/src/zencore/include/zencore/process.h @@ -183,6 +183,9 @@ struct CreateProcOptions // Flag_NoConsole the child still gets a console (and a conhost.exe) but no visible // window. Use this when the child needs a console for stdio but should not show a window. Flag_NoWindow = 1 << 5, + // Launch the child at below-normal scheduling priority. + // On Windows: BELOW_NORMAL_PRIORITY_CLASS. On POSIX: nice(5). + Flag_BelowNormalPriority = 1 << 6, }; const std::filesystem::path* WorkingDirectory = nullptr; diff --git a/src/zencore/include/zencore/string.h b/src/zencore/include/zencore/string.h index b1b5d0b39..b4926070c 100644 --- a/src/zencore/include/zencore/string.h +++ b/src/zencore/include/zencore/string.h @@ -633,6 +633,17 @@ ParseHexBytes(std::string_view InputString, uint8_t* OutPtr) return ParseHexBytes(InputString.data(), InputString.size(), OutPtr); } +/** Parse hex string into a byte buffer, validating that the hex string is exactly ExpectedByteCount * 2 characters. */ +inline bool +ParseHexBytes(std::string_view InputString, uint8_t* OutPtr, size_t ExpectedByteCount) +{ + if (InputString.size() != ExpectedByteCount * 2) + { + return false; + } + return ParseHexBytes(InputString.data(), InputString.size(), OutPtr); +} + inline void ToHexBytes(const uint8_t* InputData, size_t ByteCount, char* OutString) { @@ -746,6 +757,32 @@ struct NiceNum : public NiceBase inline NiceNum(uint64_t Num) { NiceNumToBuffer(Num, m_Buffer); } }; +size_t ThousandsToBuffer(uint64_t Num, std::span<char> Buffer); + +/// Integer formatted with comma thousands separators (e.g. "1,234,567") +struct ThousandsNum +{ + inline ThousandsNum(UnsignedIntegral auto Number) { ThousandsToBuffer(uint64_t(Number), m_Buffer); } + inline ThousandsNum(SignedIntegral auto Number) + { + if (Number < 0) + { + m_Buffer[0] = '-'; + ThousandsToBuffer(uint64_t(-Number), std::span<char>(m_Buffer + 1, sizeof(m_Buffer) - 1)); + } + else + { + ThousandsToBuffer(uint64_t(Number), m_Buffer); + } + } + + inline const char* c_str() const { return m_Buffer; } + inline operator std::string_view() const { return std::string_view(m_Buffer); } + +private: + char m_Buffer[28]; // max uint64: "18,446,744,073,709,551,615" (26) + NUL + sign +}; + struct NiceBytes : public NiceBase { inline NiceBytes(uint64_t Num) { NiceBytesToBuffer(Num, m_Buffer); } diff --git a/src/zencore/include/zencore/thread.h b/src/zencore/include/zencore/thread.h index 56ce5904b..0f7733df5 100644 --- a/src/zencore/include/zencore/thread.h +++ b/src/zencore/include/zencore/thread.h @@ -14,7 +14,7 @@ namespace zen { -void SetCurrentThreadName(std::string_view ThreadName); +void SetCurrentThreadName(std::string_view ThreadName, int32_t SortHint = 0); /** * Reader-writer lock diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp index ee821944a..aa41c82ff 100644 --- a/src/zencore/process.cpp +++ b/src/zencore/process.cpp @@ -28,6 +28,7 @@ ZEN_THIRD_PARTY_INCLUDES_START # include <pthread.h> # include <signal.h> # include <sys/file.h> +# include <sys/resource.h> # include <sys/sem.h> # include <sys/stat.h> # include <sys/syscall.h> @@ -833,6 +834,10 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma { CreationFlags |= CREATE_NEW_PROCESS_GROUP; } + if (Options.Flags & CreateProcOptions::Flag_BelowNormalPriority) + { + CreationFlags |= BELOW_NORMAL_PRIORITY_CLASS; + } if (AssignToJob) { CreationFlags |= CREATE_SUSPENDED; @@ -1043,6 +1048,10 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C { CreateProcFlags |= CREATE_NO_WINDOW; } + if (Options.Flags & CreateProcOptions::Flag_BelowNormalPriority) + { + CreateProcFlags |= BELOW_NORMAL_PRIORITY_CLASS; + } if (AssignToJob) { CreateProcFlags |= CREATE_SUSPENDED; @@ -1201,6 +1210,11 @@ CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine _exit(127); } + if (Options.Flags & CreateProcOptions::Flag_BelowNormalPriority) + { + setpriority(PRIO_PROCESS, ChildPid, 5); + } + return ChildPid; #else // macOS std::vector<char*> ArgV; @@ -1280,6 +1294,11 @@ CreateProc(const std::filesystem::path& Executable, std::string_view CommandLine ThrowSystemError(Err, "Failed to posix_spawn a new child process"); } + if (Options.Flags & CreateProcOptions::Flag_BelowNormalPriority) + { + setpriority(PRIO_PROCESS, ChildPid, 5); + } + return int(ChildPid); #endif } diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 0bd5a81b8..44f78aa75 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -381,6 +381,34 @@ NiceNumGeneral(uint64_t Num, std::span<char> Buffer, NicenumFormat Format) } size_t +ThousandsToBuffer(uint64_t Num, std::span<char> Buffer) +{ + // Format into a temporary buffer without separators + char Tmp[24]; + int Len = snprintf(Tmp, sizeof(Tmp), "%llu", (unsigned long long)Num); + + // Insert comma separators + int SepCount = (Len - 1) / 3; + int TotalLen = Len + SepCount; + ZEN_ASSERT(TotalLen < (int)Buffer.size()); + + int Src = Len - 1; + int Dst = TotalLen; + Buffer[Dst--] = '\0'; + + for (int i = 0; Src >= 0; i++) + { + if (i > 0 && i % 3 == 0) + { + Buffer[Dst--] = ','; + } + Buffer[Dst--] = Tmp[Src--]; + } + + return TotalLen; +} + +size_t NiceNumToBuffer(uint64_t Num, std::span<char> Buffer) { return NiceNumGeneral(Num, Buffer, kNicenum1024); diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 067e66c0d..fd72afaa7 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -99,7 +99,7 @@ SetNameInternal(DWORD thread_id, const char* name) #endif void -SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) +SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName, [[maybe_unused]] int32_t SortHint) { constexpr std::string_view::size_type MaxThreadNameLength = 255; std::string_view LimitedThreadName = ThreadName.substr(0, MaxThreadNameLength); @@ -108,7 +108,7 @@ SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) const int ThreadId = GetCurrentThreadId(); #if ZEN_WITH_TRACE - trace::ThreadRegister(ThreadNameZ.c_str(), /* system id */ ThreadId, /* sort id */ 0); + trace::ThreadRegister(ThreadNameZ.c_str(), /* system id */ ThreadId, /* sort id */ SortHint); #endif // ZEN_WITH_TRACE #if ZEN_PLATFORM_WINDOWS diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index 7c195e69f..d7084bbd1 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -6,6 +6,7 @@ # include <zencore/zencore.h> # include <zencore/commandline.h> # include <zencore/string.h> +# include <zencore/thread.h> # include <zencore/logging.h> # define TRACE_IMPLEMENT 1 @@ -121,7 +122,7 @@ TraceInit(std::string_view ProgramName) const char* CommandLineString = ""; # endif - trace::ThreadRegister("main", /* system id */ 0, /* sort id */ 0); + trace::ThreadRegister("main", /* system id */ GetCurrentThreadId(), /* sort id */ -1); trace::DescribeSession(ProgramName, # if ZEN_BUILD_DEBUG trace::Build::Debug, |