Skip to content
Merged
4 changes: 4 additions & 0 deletions tools/inspector/include/inspector_layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <vector>
#include "gui/gui_layer.h"

class CutReplayWindow;
class GuiScaleController;
class InspectorSession;
class RepairReplayWindow;
Expand All @@ -28,4 +29,7 @@ class InspectorLayout : public IGuiLayer {
// File > Repair Replay: floating, independent of a loaded replay (it repairs a
// possibly-unopenable .vtx from its ".recovery" sidecar). At most one at a time.
std::shared_ptr<RepairReplayWindow> repair_window_;

// File > Cut Replay: floating, operates on the loaded replay. At most one.
std::shared_ptr<CutReplayWindow> cut_window_;
};
47 changes: 47 additions & 0 deletions tools/inspector/include/services/replay_cut_service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <cstdint>
#include <string>

#include "vtx/common/vtx_types.h"

namespace VtxServices {

// Resolved cut request. Interior chunks are always copied verbatim; when the
// requested range starts or ends inside a chunk (trims_head/trims_tail) that
// edge chunk is re-serialized with only the kept frames, and its seek-table
// entry (size, checksum, frame range) is regenerated.
struct ReplayCutPlan {
bool valid = false;
std::string error; // set when !valid

int first_chunk = 0; // index into footer.chunk_index
int last_chunk = 0;
int first_frame = 0; // exact kept range, original frame numbering
int last_frame = 0;
bool trims_head = false; // first chunk starts before first_frame
bool trims_tail = false; // last chunk ends after last_frame
uint64_t chunk_bytes = 0; // on-disk bytes of the involved source chunks
};

// Cuts a new .vtx out of an existing flatbuffers replay: header block copied
// verbatim, whole chunks copied verbatim, partial edge chunks rewritten, and
// the footer rebuilt (frame numbering rebased to 0, time table sliced,
// duration recomputed). Timeline events are not carried over.
class ReplayCutService {
public:
// Exact cut: keeps [start_frame, end_frame] inclusive, trimming inside
// the edge chunks when the bounds do not fall on chunk boundaries.
static ReplayCutPlan PlanCutFrames(const VTX::FileFooter& footer, int start_frame, int end_frame);

// Whole-chunk cut: keeps chunks [first_chunk, last_chunk] untouched.
static ReplayCutPlan PlanCutChunks(const VTX::FileFooter& footer, int first_chunk, int last_chunk);

// Writes the cut to dest_path. Returns false and fills `error` on failure.
// dest_path must differ from source_path. When the plan trims an edge
// chunk, the source is reopened with the SDK reader to re-serialize it.
static bool ExecuteCut(const std::string& source_path, const VTX::FileFooter& footer, const ReplayCutPlan& plan,
const std::string& dest_path, std::string& error);
};

} // namespace VtxServices
68 changes: 68 additions & 0 deletions tools/inspector/include/windows/cut_replay_window.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <future>
#include <memory>
#include <string>

#include "gui/gui_layer.h"
#include "services/replay_cut_service.h"

class InspectorSession;

// Floating window (File > Cut Replay...) that writes a new .vtx containing a
// sub-range of the loaded replay. The range can be given as elapsed time,
// frames, or absolute UTC; it is snapped to whole chunks (chunks are copied
// verbatim, only the footer is rebuilt). The copy runs on a worker thread.
//
// Implements IGuiLayer directly (rather than the shared ImGuiWindow base) so this
// header never pulls in gui/gui_window.h -- whose `class ImGuiWindow` would clash
// with Dear ImGui's `struct ImGuiWindow` in translation units that include
// imgui_internal.h (e.g. inspector_layout.cpp).
class CutReplayWindow : public IGuiLayer {
public:
explicit CutReplayWindow(std::shared_ptr<InspectorSession> session);
~CutReplayWindow() override = default;

void OnUpdate() override {}
void OnRender() override;

void SetOpen(bool open) { is_open_ = open; }
bool IsOpen() const { return is_open_; }

private:
enum class Phase { Idle, Running, Done };

struct Outcome {
bool ok = false;
std::string error;
std::string dest_path;
};

void DrawContent();
void ResetRangeToFullReplay();
// Resolves the current inputs to an inclusive frame range; false when the
// inputs cannot be parsed (error_out explains why).
bool ResolveRequestedFrames(int& start_frame, int& end_frame, std::string& error_out) const;
void StartCut(const VtxServices::ReplayCutPlan& plan, const std::string& dest_path);
static Outcome RunCut(std::string source_path, VTX::FileFooter footer, VtxServices::ReplayCutPlan plan,
std::string dest_path);

std::shared_ptr<InspectorSession> session_;

int range_mode_ = 0; // 0 = time, 1 = frame, 2 = UTC (exact cuts); 3 = chunk (whole chunks)
double time_start_seconds_ = 0.0;
double time_end_seconds_ = 0.0;
int frame_start_ = 0;
int frame_end_ = 0;
char utc_start_[64] = {};
char utc_end_[64] = {};
int chunk_start_ = 0;
int chunk_end_ = 0;
bool range_initialized_ = false;

Phase phase_ = Phase::Idle;
std::future<Outcome> future_;
Outcome outcome_;
int spinner_tick_ = 0;
bool is_open_ = true;
};
18 changes: 18 additions & 0 deletions tools/inspector/src/inspector_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "gui/gui_types.h"
#include "inspector_session.h"
#include "windows/analysis_window_factory.h"
#include "windows/cut_replay_window.h"
#include "windows/repair_replay_window.h"

namespace {
Expand Down Expand Up @@ -260,6 +261,15 @@ void InspectorLayout::OnRender() {
}
}

// Cut a sub-range of the loaded replay into a new .vtx.
if (ImGui::MenuItem("Cut Replay...", nullptr, false, session_->HasLoadedReplay())) {
if (!cut_window_) {
cut_window_ = std::make_shared<CutReplayWindow>(session_);
} else {
cut_window_->SetOpen(true);
}
}

ImGui::Separator();

if (ImGui::MenuItem("Exit")) {
Expand Down Expand Up @@ -332,4 +342,12 @@ void InspectorLayout::OnRender() {
repair_window_.reset();
}
}

// Cut-replay window (floating, single instance), same lifecycle as repair.
if (cut_window_) {
cut_window_->OnRender();
if (!cut_window_->IsOpen()) {
cut_window_.reset();
}
}
}
Loading
Loading