Undo/Redo Hook
Overview
This example demonstrates how to react to undo and redo operations using RegisterOnUndoRedo. This hook enables addons to synchronize their state with the editor's undo/redo system, ensuring addon UI and data remain consistent with editor operations.
Files
package.json
{
"name": "Undo Sync Addon",
"author": "Polyphase Examples",
"description": "Syncs addon state with undo/redo operations.",
"version": "1.0.0",
"tags": ["editor", "example"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "undosyncaddon",
"apiVersion": 2
}
}
Source/UndoSyncAddon.cpp
#include "Plugins/PolyphasePluginAPI.h"
#include "Plugins/PolyphaseEngineAPI.h"
#if EDITOR
#include "Plugins/EditorUIHooks.h"
#include "imgui.h"
#endif
static PolyphaseEngineAPI* sEngineAPI = nullptr;
#if EDITOR
static int sTotalOperations = 0;
/**
* @brief Called when an undo or redo operation occurs.
* @param userData User-provided data (unused in this example).
*
* This callback is invoked for both undo (Ctrl+Z) and redo (Ctrl+Y)
* operations. Use this to refresh addon state that depends on scene
* or asset state.
*
* @note The callback does not indicate whether it was undo or redo.
* If you need to distinguish, maintain your own history stack.
*/
static void OnUndoRedo(void* userData)
{
sTotalOperations++;
sEngineAPI->LogDebug("Undo/Redo operation (#%d)", sTotalOperations);
// Example: Refresh cached data that may have changed
sEngineAPI->LogDebug("Refreshing addon cache...");
// Example: Update UI to reflect new state
sEngineAPI->LogDebug("Updating addon UI...");
// Example: Revalidate addon-specific data
sEngineAPI->LogDebug("Revalidating addon state...");
}
/**
* @brief Draw callback for an undo stats menu.
* @param userData User-provided data (unused in this example).
*/
static void DrawUndoStatsMenu(void* userData)
{
char label[64];
snprintf(label, sizeof(label), "Operations: %d", sTotalOperations);
ImGui::MenuItem(label, nullptr, false, false);
ImGui::Separator();
if (ImGui::MenuItem("Reset Counter"))
{
sTotalOperations = 0;
}
}
#endif
/**
* @brief Called when the plugin is loaded by the engine.
* @param api Pointer to the engine API.
* @return 0 on success, non-zero on failure.
*/
static int OnLoad(PolyphaseEngineAPI* api)
{
sEngineAPI = api;
api->LogDebug("Undo Sync Addon loaded!");
return 0;
}
/**
* @brief Called when the plugin is about to be unloaded.
*/
static void OnUnload()
{
if (sEngineAPI) sEngineAPI->LogDebug("Undo Sync Addon unloading.");
sEngineAPI = nullptr;
}
#if EDITOR
/**
* @brief Register editor UI hooks.
* @param hooks Pointer to the EditorUIHooks function table.
* @param hookId Unique identifier for this plugin's hooks.
*
* All hooks registered here are automatically cleaned up via
* RemoveAllHooks(hookId) when the plugin unloads.
*/
static void RegisterEditorUI(EditorUIHooks* hooks, uint64_t hookId)
{
hooks->RegisterOnUndoRedo(hookId, OnUndoRedo, nullptr);
sEngineAPI->LogDebug("Undo/redo hook registered");
}
#endif
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Undo Sync Addon";
desc->pluginVersion = "1.0.0";
desc->OnLoad = OnLoad;
desc->OnUnload = OnUnload;
desc->Tick = nullptr;
desc->TickEditor = nullptr;
desc->RegisterTypes = nullptr;
desc->RegisterScriptFuncs = nullptr;
#if EDITOR
desc->RegisterEditorUI = RegisterEditorUI;
#else
desc->RegisterEditorUI = nullptr;
#endif
desc->OnEditorPreInit = nullptr;
desc->OnEditorReady = nullptr;
return 0;
}
API Reference
RegisterOnUndoRedo
void (*RegisterOnUndoRedo)(HookId hookId, EventCallback cb, void* userData);
Registers a callback invoked when an undo or redo operation occurs.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- cb - Function to call when undo/redo happens
- userData - Optional user data passed to the callback
Triggered when: - User performs undo (Ctrl+Z) - User performs redo (Ctrl+Y) - Undo/redo is triggered programmatically
EventCallback
typedef void (*EventCallback)(void* userData);
Callback signature for parameterless events.
Parameters:
- userData - User-provided data from registration
Important Notes
- No Direction Info - The callback does not indicate whether it was undo or redo
- Frequent Calls - May be called frequently during rapid undo/redo sequences
- State Refresh - Always refresh addon state that depends on scene/asset data
- Performance - Keep callbacks fast to avoid UI lag during undo/redo
Best Practices
- Cache Invalidation - Use this hook to invalidate caches that depend on editor state
- Lazy Refresh - Defer expensive updates until the data is actually needed
- Selective Updates - Only refresh what actually changed, if possible
- UI Responsiveness - Keep the callback fast to maintain editor responsiveness
- Custom History - If you need to track undo vs redo, maintain your own history stack
- Automatic Cleanup - Hooks are cleaned up automatically; manual unregistration is not required
- Batch Updates - If multiple operations occur rapidly, consider debouncing updates