aboutsummaryrefslogtreecommitdiff
path: root/src/zen/progressbar.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-04-20 07:27:35 +0200
committerGitHub Enterprise <[email protected]>2026-04-20 07:27:35 +0200
commitc7c59cdc5a70bfd6e5f66f3b032ea3f8f6b4d12a (patch)
tree8ce2472f9fbdd29a899be25adc864baf98ff8184 /src/zen/progressbar.cpp
parentMerge pull request #976 from ue-foundation/zs/config-build-storage (diff)
downloadarchived-zen-c7c59cdc5a70bfd6e5f66f3b032ea3f8f6b4d12a.tar.xz
archived-zen-c7c59cdc5a70bfd6e5f66f3b032ea3f8f6b4d12a.zip
builds cmd refactor (#975)
- Bugfix: `builds download` partial-block fetch decisions now account for build storage host latency - Bugfix: Transfer rate displays in `builds` commands now smooth correctly - Split `buildstorageoperations.cpp` (8.5k lines) into per-operation TUs: buildinspect, buildprimecache, buildstorageresolve, buildupdatefolder, builduploadfolder, buildvalidatebuildpart; stats moved to buildstoragestats.h. - FilteredRate extracted to zenutil. - BuildsCommand shared state consolidated into a BuildsConfiguration struct; subcommands inherit from BuildsSubCmdBase holding a `const BuildsConfiguration&` instead of a `BuildsCommand&`. - `ProgressBar` renamed to `ConsoleProgressBar`; mode enum (`ConsoleProgressMode`) lifted to namespace scope; `PushLogOperation`/`PopLogOperation`/`ForceLinebreak` promoted to virtuals on `ProgressBase`. - Free-function wrappers (`UploadFolder`, `DownloadFolder`, `ValidateBuildPart`) added around the existing operation classes so callers stop reimplementing setup + stats logging.
Diffstat (limited to 'src/zen/progressbar.cpp')
-rw-r--r--src/zen/progressbar.cpp567
1 files changed, 0 insertions, 567 deletions
diff --git a/src/zen/progressbar.cpp b/src/zen/progressbar.cpp
deleted file mode 100644
index 780b08707..000000000
--- a/src/zen/progressbar.cpp
+++ /dev/null
@@ -1,567 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-// Zen command line client utility
-//
-
-#include "progressbar.h"
-
-#include <zencore/fmtutils.h>
-#include <zencore/logging.h>
-#include <zencore/windows.h>
-#include <zenutil/consoletui.h>
-#include <zenutil/progress.h>
-
-#if !ZEN_PLATFORM_WINDOWS
-# include <csignal>
-#endif
-
-ZEN_THIRD_PARTY_INCLUDES_START
-#include <gsl/gsl-lite.hpp>
-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()
-{
- static HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
- return hStdOut;
-}
-#endif
-
-static void
-OutputToConsoleRaw(const char* String, size_t Length)
-{
-#if ZEN_PLATFORM_WINDOWS
- HANDLE hStdOut = GetConsoleHandle();
- if (TuiIsStdoutTty())
- {
- WriteConsoleA(hStdOut, String, (DWORD)Length, 0, 0);
- }
- else
- {
- ::WriteFile(hStdOut, (LPCVOID)String, (DWORD)Length, 0, 0);
- }
-#else
- fwrite(String, 1, Length, stdout);
-#endif
-}
-
-static void
-OutputToConsoleRaw(const std::string& String)
-{
- OutputToConsoleRaw(String.c_str(), String.length());
-}
-
-static void
-OutputToConsoleRaw(const StringBuilderBase& SB)
-{
- OutputToConsoleRaw(SB.c_str(), SB.Size());
-}
-
-uint32_t
-GetUpdateDelayMS(ProgressBar::Mode InMode)
-{
- switch (InMode)
- {
- case ProgressBar::Mode::Plain:
- return 5000;
- case ProgressBar::Mode::Pretty:
- return 200;
- case ProgressBar::Mode::Log:
- return 2000;
- default:
- ZEN_ASSERT(false);
- return 0;
- }
-}
-
-void
-ProgressBar::SetLogOperationName(Mode InMode, std::string_view Name)
-{
- ZEN_ASSERT(Name.find('\"') == std::string_view::npos);
- if (InMode == Mode::Log)
- {
- std::string String = fmt::format("@progress \"{}\"\n", Name);
- OutputToConsoleRaw(String);
- }
-}
-
-void
-ProgressBar::SetLogOperationProgress(Mode InMode, uint32_t StepIndex, uint32_t StepCount)
-{
- if (InMode == Mode::Log)
- {
- const size_t PercentDone = StepCount > 0u ? gsl::narrow<uint8_t>((100 * StepIndex) / StepCount) : 0u;
-
- std::string String = fmt::format("@progress {}%\n", PercentDone);
- OutputToConsoleRaw(String);
- }
-}
-
-void
-ProgressBar::PushLogOperation(Mode InMode, std::string_view Name)
-{
- if (InMode == Mode::Log)
- {
- std::string String = fmt::format("@progress push \"{}\"\n", Name);
- OutputToConsoleRaw(String);
- }
-}
-
-void
-ProgressBar::PopLogOperation(Mode InMode)
-{
- if (InMode == Mode::Log)
- {
- const std::string String("@progress pop\n");
- OutputToConsoleRaw(String);
- }
-}
-
-ProgressBar::ProgressBar(Mode InMode, std::string_view InSubTask)
-: m_Mode((!TuiIsStdoutTty() && InMode == Mode::Pretty) ? Mode::Plain : InMode)
-, m_LastUpdateMS((uint64_t)-1)
-, m_PausedMS(0)
-, m_SubTask(InSubTask)
-{
- ZEN_ASSERT(InSubTask.find('\"') == std::string_view::npos);
- if (!m_SubTask.empty())
- {
- PushLogOperation(InMode, m_SubTask);
- }
-
- if (m_Mode == Mode::Pretty)
- {
- SetupScrollRegion();
- }
-}
-
-ProgressBar::~ProgressBar()
-{
- try
- {
- TeardownScrollRegion();
- ForceLinebreak();
- if (!m_SubTask.empty())
- {
- PopLogOperation(m_Mode);
- }
- }
- catch (const std::exception& Ex)
- {
- ZEN_ERROR("ProgressBar::~ProgressBar() failed with {}", Ex.what());
- }
-}
-
-void
-ProgressBar::SetupScrollRegion()
-{
- // Only one scroll region owner at a time; nested bars fall back to the inline \r path.
- if (g_ActiveScrollRegionOwner.load(std::memory_order_acquire) != nullptr)
- {
- return;
- }
-
- 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);
- ZEN_ASSERT(NewState.Task.find('\"') == std::string::npos);
- if (DoLinebreak == false && m_State == NewState)
- {
- return;
- }
-
- uint64_t ElapsedTimeMS = NewState.OptionalElapsedTime == (uint64_t)-1 ? m_SW.GetElapsedTimeMs() : NewState.OptionalElapsedTime;
- if (m_LastUpdateMS != (uint64_t)-1)
- {
- if (!DoLinebreak && (NewState.Status == m_State.Status) && (NewState.Task == m_State.Task) &&
- ((m_LastUpdateMS + 200) > ElapsedTimeMS))
- {
- return;
- }
- if (m_State.Status == State::EStatus::Paused)
- {
- uint64_t ElapsedSinceLast = ElapsedTimeMS - m_LastUpdateMS;
- m_PausedMS += ElapsedSinceLast;
- }
- }
-
- m_LastUpdateMS = ElapsedTimeMS;
-
- std::string Task = NewState.Task;
- switch (NewState.Status)
- {
- case State::EStatus::Aborted:
- Task = "Aborting";
- break;
- case State::EStatus::Paused:
- Task = "Paused";
- break;
- default:
- break;
- }
- if (NewState.Task.length() > Task.length())
- {
- Task += std::string(NewState.Task.length() - Task.length(), ' ');
- }
-
- const size_t PercentDone =
- NewState.TotalCount > 0u ? gsl::narrow<uint8_t>((100 * (NewState.TotalCount - NewState.RemainingCount)) / NewState.TotalCount) : 0u;
-
- uint64_t Completed = NewState.TotalCount - NewState.RemainingCount;
- uint64_t ETAElapsedMS = ElapsedTimeMS - m_PausedMS;
- uint64_t ETAMS = ((m_State.TotalCount == NewState.TotalCount) && (NewState.Status == State::EStatus::Running)) && (PercentDone > 5)
- ? (ETAElapsedMS * NewState.RemainingCount) / Completed
- : 0;
- const std::string ETAString = (ETAMS > 0) ? fmt::format(" ETA {}", NiceTimeSpanMs(ETAMS)) : "";
-
- if (m_Mode == Mode::Plain)
- {
- const std::string Details = (!NewState.Details.empty()) ? fmt::format(": {}", NewState.Details) : "";
- const std::string Output = fmt::format("{} {}% {}{}{}\n", Task, PercentDone, NiceTimeSpanMs(ElapsedTimeMS), ETAString, Details);
- OutputToConsoleRaw(Output);
- m_State = NewState;
- }
- else if (m_Mode == Mode::Pretty)
- {
- size_t ProgressBarSize = 20;
-
- size_t ProgressBarCount = (ProgressBarSize * PercentDone) / 100;
-
- uint32_t ConsoleColumns = TuiConsoleColumns(1024);
-
- const std::string PercentString = fmt::format("{:#3}%", PercentDone);
-
- const std::string ProgressBarString =
- fmt::format(": |{}{}|", std::string(ProgressBarCount, '#'), std::string(ProgressBarSize - ProgressBarCount, ' '));
-
- const std::string ElapsedString = fmt::format(": {}", NiceTimeSpanMs(ElapsedTimeMS));
-
- const std::string DetailsString = (!NewState.Details.empty()) ? fmt::format(". {}", NewState.Details) : "";
-
- ExtendableStringBuilder<256> OutputBuilder;
-
- OutputBuilder << Task << " " << PercentString;
- if (OutputBuilder.Size() + 1 < ConsoleColumns)
- {
- size_t RemainingSpace = ConsoleColumns - (OutputBuilder.Size() + 1);
- bool ElapsedFits = RemainingSpace >= ElapsedString.length();
- RemainingSpace -= ElapsedString.length();
- bool ETAFits = ElapsedFits && RemainingSpace >= ETAString.length();
- RemainingSpace -= ETAString.length();
- bool DetailsFits = ETAFits && RemainingSpace >= DetailsString.length();
- RemainingSpace -= DetailsString.length();
- bool ProgressBarFits = DetailsFits && RemainingSpace >= ProgressBarString.length();
- RemainingSpace -= ProgressBarString.length();
-
- if (ProgressBarFits)
- {
- OutputBuilder << ProgressBarString;
- }
- if (ElapsedFits)
- {
- OutputBuilder << ElapsedString;
- }
- if (ETAFits)
- {
- OutputBuilder << ETAString;
- }
- if (DetailsFits)
- {
- OutputBuilder << DetailsString;
- }
- }
-
- if (m_ScrollRegionActive)
- {
- // Render on the pinned bottom status line
- RenderStatusLine(OutputBuilder.ToView());
- }
- else
- {
- // Fallback: inline \r-based overwrite (terminal too small for scroll region)
- 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;
-
- if (Output.length() + 1 + EraseLength >= ConsoleColumns)
- {
- if (m_LastOutputLength > 0)
- {
- LineToPrint << "\n";
- }
- LineToPrint << Output;
- DoLinebreak = true;
- }
- else
- {
- LineToPrint << "\r" << Output << std::string(EraseLength, ' ');
- }
-
- if (DoLinebreak)
- {
- LineToPrint << "\n";
- }
-
- OutputToConsoleRaw(LineToPrint);
- m_LastOutputLength = DoLinebreak ? 0 : (Output.length() + 1); // +1 for \r prefix
- }
-
- m_State = NewState;
- }
- else if (m_Mode == Mode::Log)
- {
- if (m_State.Task != NewState.Task ||
- m_State.Details != NewState.Details) // TODO: Should we output just because details change? Will this spam the log collector?
- {
- std::string Details = (!NewState.Details.empty()) ? fmt::format(": {}", NewState.Details) : "";
- for (std::string::value_type& Char : Details)
- {
- if (Char == '"')
- {
- Char = '\'';
- }
- }
- const std::string Message =
- fmt::format("@progress \"{} {}{}{}\"\n", NewState.Task, NiceTimeSpanMs(ElapsedTimeMS), ETAString, Details);
- OutputToConsoleRaw(Message);
- }
-
- const size_t OldPercentDone =
- m_State.TotalCount > 0u ? gsl::narrow<uint8_t>((100 * (m_State.TotalCount - m_State.RemainingCount)) / m_State.TotalCount) : 0u;
-
- if (OldPercentDone != PercentDone)
- {
- const std::string Progress = fmt::format("@progress {}%\n", PercentDone);
- OutputToConsoleRaw(Progress);
- }
- m_State = NewState;
- }
-}
-
-void
-ProgressBar::ForceLinebreak()
-{
- if (m_LastOutputLength > 0)
- {
- State NewState = m_State;
- UpdateState(NewState, /*DoLinebreak*/ true);
- }
-}
-
-void
-ProgressBar::Finish()
-{
- TeardownScrollRegion();
-
- if (m_LastOutputLength > 0 || m_State.RemainingCount > 0)
- {
- State NewState = m_State;
- NewState.RemainingCount = 0;
- NewState.Details = "";
- UpdateState(NewState, /*DoLinebreak*/ true);
- }
- m_State = State{};
- m_LastOutputLength = 0;
- m_SW.Reset();
-}
-
-bool
-ProgressBar::IsSameTask(std::string_view Task) const
-{
- return Task == m_State.Task;
-}
-
-bool
-ProgressBar::HasActiveTask() const
-{
- return !m_State.Task.empty();
-}
-
-class ConsoleOpLogOutput : public ProgressBase
-{
-public:
- ConsoleOpLogOutput(zen::ProgressBar::Mode InMode) : m_Mode(InMode) {}
-
- virtual void SetLogOperationName(std::string_view Name) override { zen::ProgressBar::SetLogOperationName(m_Mode, Name); }
- virtual void SetLogOperationProgress(uint32_t StepIndex, uint32_t StepCount) override
- {
- zen::ProgressBar::SetLogOperationProgress(m_Mode, StepIndex, StepCount);
- }
- virtual uint32_t GetProgressUpdateDelayMS() const override { return GetUpdateDelayMS(m_Mode); }
-
- virtual std::unique_ptr<ProgressBase::ProgressBar> CreateProgressBar(std::string_view InSubTask) override
- {
- return std::make_unique<zen::ProgressBar>(m_Mode, InSubTask);
- }
-
-private:
- zen::ProgressBar::Mode m_Mode;
-};
-
-ProgressBase*
-CreateConsoleProgress(ProgressBar::Mode InMode)
-{
- return new ConsoleOpLogOutput(InMode);
-}
-
-} // namespace zen