aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/zen/progressbar.cpp227
-rw-r--r--src/zen/progressbar.h9
-rw-r--r--src/zencore/compactbinaryjson.cpp34
-rw-r--r--src/zencore/filesystem.cpp68
-rw-r--r--src/zencore/include/zencore/filesystem.h4
-rw-r--r--src/zencore/include/zencore/fmtutils.h22
-rw-r--r--src/zencore/include/zencore/process.h3
-rw-r--r--src/zencore/include/zencore/string.h37
-rw-r--r--src/zencore/include/zencore/thread.h2
-rw-r--r--src/zencore/process.cpp19
-rw-r--r--src/zencore/string.cpp28
-rw-r--r--src/zencore/thread.cpp4
-rw-r--r--src/zencore/trace.cpp3
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,