Auto-save the active render layer's sublayer file
Render layer overrides only reached disk on an explicit File > Save -- SaveDirtyRenderLayers() was called from SaveUsdFile/SaveUsdFileAs and nowhere else. Everything a user authored after switching to a render layer (property values, shader parameters, material assignments) sat in a dirty in-memory SdfLayer marked with an orange asterisk, and a crash took the whole layer's overrides with it. Application::Update() now polls the active render layer's IsDirty() and saves it 0.5s after the user goes idle, gated on IsAnyItemActive() and the left mouse button so a slider or gizmo drag produces one write on release rather than one per frame. Polling rather than hooking CommandHistory::Push/Undo/Redo is deliberate: many of the writes this is meant to catch never build a command and author straight to the ambient edit target -- PropertyPanel's live WriteTranslate/Rotate/Scale and its generic attribute widgets, TransformManipulator's gizmo drags, and MaterialEditorPanel::RenderInputWidget's shader input writes. A command hook would have silently missed exactly the property and shader-parameter edits at issue. IsDirty() catches every path, commanded or not. Scope is the active layer only. Muted render layers still rely on SaveDirtyRenderLayers() at save time, and the root layer is never auto-written -- so the persisted active-layer choice (custom layer data on the root layer) still only survives a reopen once the stage itself is saved. Auto-writing the user's main scene file was ruled out. - SaveRenderLayerIfDirty() refreshes LayerManager after a successful save, or the cached isDirty snapshot leaves a stale asterisk in StageEditorPanel and the Scene Hierarchy sublayer list. A failed save latches the layer id so a read-only file can't spam the log twice a second. - Switching layers flushes the outgoing one, and Shutdown() flushes before the panels are destroyed, so edits inside the debounce window aren't stranded. - RefreshManagers() clears the auto-save state, whose layer ids belong to the outgoing stage. - New "Auto-save active layer" checkbox in the Render Layer panel, on by default, persisted as AutoSaveRenderLayer in preferences.ini. See docs/adr/0002-render-layer.md D9-D11 for the rationale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -313,3 +313,76 @@ round-trip、跨圖層切換的 Undo/Redo 行為),寫法比照
|
|||||||
`0001-viewport-color-correction.md` 文末的「Supersession Note」
|
`0001-viewport-color-correction.md` 文末的「Supersession Note」
|
||||||
——以追加的方式記錄後續進展或設計變動,而非直接覆寫、抹除本次
|
——以追加的方式記錄後續進展或設計變動,而非直接覆寫、抹除本次
|
||||||
決策當下的紀錄。
|
決策當下的紀錄。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 後續紀錄 — 現用 Render Layer 的自動存檔(2026-08-05)
|
||||||
|
|
||||||
|
- **Component:** `src/ui/Application`、`src/ui/RenderLayerPanel`
|
||||||
|
|
||||||
|
### Context
|
||||||
|
|
||||||
|
D8 讓 render layer 的內容只有在使用者明確存檔(File > Save /
|
||||||
|
Save As)時才會寫入磁碟——`Application::SaveDirtyRenderLayers()`
|
||||||
|
的呼叫點就只有 `SaveUsdFile()` 與 `SaveUsdFileAs()` 兩處。在那之前,
|
||||||
|
使用者切換到某個 render layer 後所做的所有編輯(屬性數值、shader
|
||||||
|
參數、材質指派)都只存在於記憶體中的 dirty `SdfLayer` 裡,
|
||||||
|
Render Layer 面板僅以橘色 `*` 標示;一旦當掉,該圖層的全部覆寫
|
||||||
|
就會遺失。使用者要求:切換到現用 render layer 之後,只要編輯屬性、
|
||||||
|
shader 參數或材質指派,該圖層的 sublayer 就應該自動存檔。
|
||||||
|
|
||||||
|
### D9 — 以「去彈跳的 dirty 輪詢」實作,而非掛在 `CommandHistory` 上
|
||||||
|
|
||||||
|
**決策:** 在 `Application::Update()` 每一幀輪詢現用 render layer 的
|
||||||
|
`SdfLayer::IsDirty()`;當(a)沒有任何 ImGui item 處於 active、
|
||||||
|
且(b)滑鼠左鍵未按下、且(c)已維持 0.5 秒,才呼叫
|
||||||
|
`SdfLayer::Save()`。實作為
|
||||||
|
`Application::TickRenderLayerAutoSave()` /
|
||||||
|
`FlushActiveRenderLayerSave()` / `SaveRenderLayerIfDirty()`。
|
||||||
|
|
||||||
|
**理由:** 規劃階段清查全部的寫入路徑後發現,使用者點名的編輯裡有
|
||||||
|
相當大一部分**根本不經過 `CommandHistory`**,而是直接對 ambient
|
||||||
|
edit target authoring:`PropertyPanel::WriteTranslate/WriteRotate/
|
||||||
|
WriteScale`、`PropertyPanel` 的一般屬性 widget 與 variant 選擇、
|
||||||
|
`TransformManipulator::ApplyTranslate/Rotate/Scale`(操作桿拖曳期間
|
||||||
|
的即時寫入)、以及 `MaterialEditorPanel::RenderInputWidget` 的
|
||||||
|
shader input 即時寫入。若把自動存檔掛在
|
||||||
|
`CommandHistory::Push/Undo/Redo`,這些路徑會被**靜默漏掉**,正好
|
||||||
|
涵蓋使用者最在意的「編輯屬性與 shader 參數」情境。輪詢
|
||||||
|
`IsDirty()` 則不管走不走 command 都能捕捉到。
|
||||||
|
|
||||||
|
(b) 的滑鼠條件是必要的:viewport 操作桿與 node editor 的拖曳是自行
|
||||||
|
hit-test 的,不是 ImGui item,`IsAnyItemActive()` 對它們回傳 false;
|
||||||
|
少了這個條件,拖曳期間會每幀寫檔一次。
|
||||||
|
|
||||||
|
### D10 — 只自動存現用的那一個 render layer
|
||||||
|
|
||||||
|
**決策:** 自動存檔只寫**現用**的 render layer;root layer、一般內容
|
||||||
|
sublayer、以及其餘被 mute 的 render layer 都維持原狀,仍需 Ctrl+S。
|
||||||
|
D8 完全不變、且仍然必要。
|
||||||
|
|
||||||
|
**已知取捨:** `PersistActiveLayerId()` 是把「目前哪個圖層生效」寫進
|
||||||
|
**root layer** 的 custom layer data,而 root layer 不在自動存檔範圍內
|
||||||
|
——因此「重新開檔後回到同一個現用圖層」這件事,仍然只有在使用者
|
||||||
|
明確存檔過之後才成立。刻意不自動寫 root layer:那等於在使用者沒有
|
||||||
|
要求的情況下改寫他的主場景檔案。
|
||||||
|
|
||||||
|
另有兩個附帶行為:切換圖層(或切回 Default)時會先把前一個圖層
|
||||||
|
flush 掉,避免在 0.5 秒去彈跳視窗內切走而漏存;`Shutdown()`
|
||||||
|
在面板被解構前也會 flush 一次。Undo/Redo 同樣會弄髒圖層、因而觸發
|
||||||
|
自動存檔,這是預期行為。
|
||||||
|
|
||||||
|
### D11 — 開關放在 Render Layer 面板,預設開啟
|
||||||
|
|
||||||
|
**決策:** `RenderLayerPanel` 提供「Auto-save active layer」
|
||||||
|
checkbox(預設勾選),狀態以 `AutoSaveRenderLayer` 鍵持久化到
|
||||||
|
`preferences.ini`。面板自己持有該旗標,`Application` 每幀讀回——
|
||||||
|
與 `MaterialEditorPanel` 的 column widths 採同一種
|
||||||
|
setter/getter 模式,不需要額外的 callback。
|
||||||
|
|
||||||
|
**理由:** 自動寫檔是行為變更,預設開啟才能滿足使用者的要求,但仍
|
||||||
|
保留關閉的退路。
|
||||||
|
|
||||||
|
**注意:** 沿用既有慣例,`preferences.ini` 只在 `Shutdown()` 時寫出,
|
||||||
|
所以這個開關的狀態在當掉時不會被保存——這是既有偏好設定共通的
|
||||||
|
行為,本次未一併變更。
|
||||||
|
|||||||
@@ -237,6 +237,11 @@ void Application::Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Application::Shutdown() {
|
void Application::Shutdown() {
|
||||||
|
// Catch edits made inside the last debounce window before the managers go
|
||||||
|
// away. Must run before m_renderLayerPanel/m_renderLayerManager are reset.
|
||||||
|
if (m_prefs.autoSaveRenderLayer)
|
||||||
|
FlushActiveRenderLayerSave();
|
||||||
|
|
||||||
if (m_viewportPanel && !m_viewportSettingsPath.empty())
|
if (m_viewportPanel && !m_viewportSettingsPath.empty())
|
||||||
m_viewportPanel->SaveSettings(m_viewportSettingsPath);
|
m_viewportPanel->SaveSettings(m_viewportSettingsPath);
|
||||||
if (!m_prefsPath.empty())
|
if (!m_prefsPath.empty())
|
||||||
@@ -273,6 +278,11 @@ void Application::Shutdown() {
|
|||||||
|
|
||||||
void Application::RefreshManagers() {
|
void Application::RefreshManagers() {
|
||||||
m_commandHistory.Clear();
|
m_commandHistory.Clear();
|
||||||
|
// Auto-save state is keyed by layer identifier and must not survive a
|
||||||
|
// stage swap — the ids below belong to the outgoing stage.
|
||||||
|
m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
m_renderLayerAutoSaveLastActiveId.clear();
|
||||||
|
m_renderLayerAutoSaveFailedId.clear();
|
||||||
if (m_stageManager->HasStage()) {
|
if (m_stageManager->HasStage()) {
|
||||||
auto stage = m_stageManager->GetStage();
|
auto stage = m_stageManager->GetStage();
|
||||||
m_layerManager->SetStage(stage);
|
m_layerManager->SetStage(stage);
|
||||||
@@ -312,6 +322,10 @@ void Application::Update() {
|
|||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
m_timelinePanel->Update(io.DeltaTime);
|
m_timelinePanel->Update(io.DeltaTime);
|
||||||
|
|
||||||
|
if (m_renderLayerPanel)
|
||||||
|
m_prefs.autoSaveRenderLayer = m_renderLayerPanel->IsAutoSaveEnabled();
|
||||||
|
TickRenderLayerAutoSave(io.DeltaTime);
|
||||||
|
|
||||||
// Process undo/redo hotkeys (Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z).
|
// Process undo/redo hotkeys (Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z).
|
||||||
// Only fire when no ImGui text-input widget has keyboard focus.
|
// Only fire when no ImGui text-input widget has keyboard focus.
|
||||||
if (!io.WantTextInput) {
|
if (!io.WantTextInput) {
|
||||||
@@ -472,6 +486,8 @@ void Application::LoadPreferences()
|
|||||||
m_prefs.materialPreviewWidth = [&]{ try { return std::stof(val); } catch(...){ return 320.0f; } }();
|
m_prefs.materialPreviewWidth = [&]{ try { return std::stof(val); } catch(...){ return 320.0f; } }();
|
||||||
else if (key == "KeepGraphNodeViewSettingsInUsd")
|
else if (key == "KeepGraphNodeViewSettingsInUsd")
|
||||||
m_prefs.keepGraphNodeViewSettingsInUsd = (val == "1");
|
m_prefs.keepGraphNodeViewSettingsInUsd = (val == "1");
|
||||||
|
else if (key == "AutoSaveRenderLayer")
|
||||||
|
m_prefs.autoSaveRenderLayer = (val == "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_materialEditorPanel) {
|
if (m_materialEditorPanel) {
|
||||||
@@ -479,6 +495,8 @@ void Application::LoadPreferences()
|
|||||||
m_prefs.materialPreviewWidth);
|
m_prefs.materialPreviewWidth);
|
||||||
m_materialEditorPanel->SetKeepGraphNodeViewSettingsInUsd(m_prefs.keepGraphNodeViewSettingsInUsd);
|
m_materialEditorPanel->SetKeepGraphNodeViewSettingsInUsd(m_prefs.keepGraphNodeViewSettingsInUsd);
|
||||||
}
|
}
|
||||||
|
if (m_renderLayerPanel)
|
||||||
|
m_renderLayerPanel->SetAutoSaveEnabled(m_prefs.autoSaveRenderLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::ValidateOcioPreferences()
|
void Application::ValidateOcioPreferences()
|
||||||
@@ -558,6 +576,8 @@ void Application::SavePreferences()
|
|||||||
if (m_materialEditorPanel)
|
if (m_materialEditorPanel)
|
||||||
m_materialEditorPanel->GetColumnWidths(m_prefs.materialBrowserWidth,
|
m_materialEditorPanel->GetColumnWidths(m_prefs.materialBrowserWidth,
|
||||||
m_prefs.materialPreviewWidth);
|
m_prefs.materialPreviewWidth);
|
||||||
|
if (m_renderLayerPanel)
|
||||||
|
m_prefs.autoSaveRenderLayer = m_renderLayerPanel->IsAutoSaveEnabled();
|
||||||
|
|
||||||
std::ofstream f(m_prefsPath);
|
std::ofstream f(m_prefsPath);
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
@@ -571,6 +591,7 @@ void Application::SavePreferences()
|
|||||||
f << "MaterialBrowserWidth=" << m_prefs.materialBrowserWidth << "\n";
|
f << "MaterialBrowserWidth=" << m_prefs.materialBrowserWidth << "\n";
|
||||||
f << "MaterialPreviewWidth=" << m_prefs.materialPreviewWidth << "\n";
|
f << "MaterialPreviewWidth=" << m_prefs.materialPreviewWidth << "\n";
|
||||||
f << "KeepGraphNodeViewSettingsInUsd=" << (m_prefs.keepGraphNodeViewSettingsInUsd ? 1 : 0) << "\n";
|
f << "KeepGraphNodeViewSettingsInUsd=" << (m_prefs.keepGraphNodeViewSettingsInUsd ? 1 : 0) << "\n";
|
||||||
|
f << "AutoSaveRenderLayer=" << (m_prefs.autoSaveRenderLayer ? 1 : 0) << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::ApplyPrefsToAllViewports()
|
void Application::ApplyPrefsToAllViewports()
|
||||||
@@ -1016,6 +1037,71 @@ void Application::SaveDirtyRenderLayers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::SaveRenderLayerIfDirty(const std::string& layerId) {
|
||||||
|
if (!m_renderLayerManager || layerId.empty()) return false;
|
||||||
|
if (layerId == m_renderLayerAutoSaveFailedId) return false;
|
||||||
|
|
||||||
|
for (const auto& info : m_renderLayerManager->GetRenderLayers()) {
|
||||||
|
if (info.isDefault || info.layerIdentifier != layerId) continue;
|
||||||
|
if (!info.layer || !info.layer->IsDirty()) return false;
|
||||||
|
|
||||||
|
if (!info.layer->Save()) {
|
||||||
|
m_renderLayerAutoSaveFailedId = layerId;
|
||||||
|
LOG_ERROR("Auto-save failed for render layer: " + layerId +
|
||||||
|
" - auto-save suspended for this layer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// LayerManager caches isDirty in a snapshot rebuilt only on Refresh(),
|
||||||
|
// so without this the dirty asterisk in StageEditorPanel and the Scene
|
||||||
|
// Hierarchy sublayer list would stay stale after the file is written.
|
||||||
|
if (m_layerManager) m_layerManager->Refresh();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::FlushActiveRenderLayerSave() {
|
||||||
|
if (!m_renderLayerManager) return false;
|
||||||
|
// Empty when Default is active — nothing of ours to save.
|
||||||
|
return SaveRenderLayerIfDirty(m_renderLayerManager->GetActiveRenderLayerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::TickRenderLayerAutoSave(float deltaTime) {
|
||||||
|
// How long the user must be idle before the active render layer is written.
|
||||||
|
// Long enough that a slider or gizmo drag produces one file write on
|
||||||
|
// release rather than one per frame.
|
||||||
|
constexpr float kAutoSaveDelay = 0.5f;
|
||||||
|
|
||||||
|
if (!m_prefs.autoSaveRenderLayer || !m_renderLayerManager) {
|
||||||
|
m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switching layers (or back to Default) within the debounce window would
|
||||||
|
// otherwise strand the previous layer's edits unsaved — flush it first.
|
||||||
|
std::string activeId = m_renderLayerManager->GetActiveRenderLayerId();
|
||||||
|
if (activeId != m_renderLayerAutoSaveLastActiveId) {
|
||||||
|
SaveRenderLayerIfDirty(m_renderLayerAutoSaveLastActiveId);
|
||||||
|
m_renderLayerAutoSaveLastActiveId = activeId;
|
||||||
|
m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAnyItemActive covers ImGui widgets being dragged/typed into; the mouse
|
||||||
|
// test additionally covers viewport gizmo and node-editor drags, which are
|
||||||
|
// custom hit-tested and are not ImGui items.
|
||||||
|
if (ImGui::IsAnyItemActive() || ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||||
|
m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_renderLayerAutoSaveTimer += deltaTime;
|
||||||
|
if (m_renderLayerAutoSaveTimer < kAutoSaveDelay) return;
|
||||||
|
|
||||||
|
m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
FlushActiveRenderLayerSave();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::CloseUsdFile() {
|
void Application::CloseUsdFile() {
|
||||||
m_stageManager->CloseStage();
|
m_stageManager->CloseStage();
|
||||||
// Re-create a fresh default stage so the app is always in an editable state.
|
// Re-create a fresh default stage so the app is always in an editable state.
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ struct AppPreferences {
|
|||||||
/// customData so they survive reopening the material; when false, that
|
/// customData so they survive reopening the material; when false, that
|
||||||
/// state is session-only.
|
/// state is session-only.
|
||||||
bool keepGraphNodeViewSettingsInUsd = false;
|
bool keepGraphNodeViewSettingsInUsd = false;
|
||||||
|
/// Auto-save the active render layer's SdfLayer shortly after each edit.
|
||||||
|
bool autoSaveRenderLayer = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
@@ -73,6 +75,20 @@ private:
|
|||||||
/// rest would otherwise be silently dropped on save.
|
/// rest would otherwise be silently dropped on save.
|
||||||
void SaveDirtyRenderLayers();
|
void SaveDirtyRenderLayers();
|
||||||
|
|
||||||
|
/// Per-frame debounce driving the active render layer's auto-save.
|
||||||
|
/// Polls SdfLayer::IsDirty() rather than hooking CommandHistory because
|
||||||
|
/// many write paths (PropertyPanel's live transform/attribute widgets,
|
||||||
|
/// TransformManipulator's gizmo drags, MaterialEditorPanel's shader input
|
||||||
|
/// widgets) author straight to the ambient edit target without going
|
||||||
|
/// through a command — a command hook would silently miss them.
|
||||||
|
void TickRenderLayerAutoSave(float deltaTime);
|
||||||
|
/// Saves the active render layer's SdfLayer if it's dirty. Returns true
|
||||||
|
/// only when a save actually happened.
|
||||||
|
bool FlushActiveRenderLayerSave();
|
||||||
|
/// Saves one render layer by identifier if it's dirty; no-op for an empty
|
||||||
|
/// id (Default active) or a layer whose previous save failed.
|
||||||
|
bool SaveRenderLayerIfDirty(const std::string& layerId);
|
||||||
|
|
||||||
// Playblast
|
// Playblast
|
||||||
void CapturePlayblastFrame();
|
void CapturePlayblastFrame();
|
||||||
|
|
||||||
@@ -120,6 +136,13 @@ private:
|
|||||||
AppPreferences m_prefs;
|
AppPreferences m_prefs;
|
||||||
bool m_showPreferences = false;
|
bool m_showPreferences = false;
|
||||||
|
|
||||||
|
// Render-layer auto-save state (see TickRenderLayerAutoSave).
|
||||||
|
float m_renderLayerAutoSaveTimer = 0.0f;
|
||||||
|
std::string m_renderLayerAutoSaveLastActiveId;
|
||||||
|
// Identifier of a render layer whose Save() failed — retried only after the
|
||||||
|
// active layer changes, so a read-only file can't spam the log every tick.
|
||||||
|
std::string m_renderLayerAutoSaveFailedId;
|
||||||
|
|
||||||
// OCIO edit buffers for Preferences dialog
|
// OCIO edit buffers for Preferences dialog
|
||||||
char m_prefOcioDisplayBuf[128] = {};
|
char m_prefOcioDisplayBuf[128] = {};
|
||||||
char m_prefOcioViewBuf[128] = {};
|
char m_prefOcioViewBuf[128] = {};
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ void RenderLayerPanel::Render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Checkbox("Auto-save active layer", &m_autoSave);
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip(
|
||||||
|
"Writes the active render layer's .usda file shortly after each edit.\n"
|
||||||
|
"The stage itself (root layer and other sublayers) still needs Ctrl+S.");
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
RenderLayerList();
|
RenderLayerList();
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ public:
|
|||||||
void SetStage(pxr::UsdStageRefPtr stage);
|
void SetStage(pxr::UsdStageRefPtr stage);
|
||||||
void Render();
|
void Render();
|
||||||
|
|
||||||
|
/// Whether Application should auto-save the active render layer's SdfLayer
|
||||||
|
/// shortly after each edit. Owned here because the checkbox lives in this
|
||||||
|
/// panel; Application reads it back every frame and when persisting
|
||||||
|
/// preferences (same setter/getter shape as MaterialEditorPanel's column
|
||||||
|
/// widths).
|
||||||
|
void SetAutoSaveEnabled(bool enabled) { m_autoSave = enabled; }
|
||||||
|
bool IsAutoSaveEnabled() const { return m_autoSave; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RenderLayerList();
|
void RenderLayerList();
|
||||||
void RenderMembershipPanel();
|
void RenderMembershipPanel();
|
||||||
@@ -44,6 +52,8 @@ private:
|
|||||||
|
|
||||||
char m_newLayerNameBuf[128] = "RenderLayer";
|
char m_newLayerNameBuf[128] = "RenderLayer";
|
||||||
|
|
||||||
|
bool m_autoSave = true;
|
||||||
|
|
||||||
bool m_openRenameModal = false;
|
bool m_openRenameModal = false;
|
||||||
std::string m_renameTargetId;
|
std::string m_renameTargetId;
|
||||||
char m_renameBuf[128] = {};
|
char m_renameBuf[128] = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user