Project Lifecycle Hooks
Overview
This example demonstrates how to react to project lifecycle events using RegisterOnProjectOpen, RegisterOnProjectClose, and RegisterOnProjectSave. These hooks enable addons to maintain project-specific state, validate project data, and perform cleanup when switching projects.
Files
package.json
{
"name": "Project Watcher Addon",
"author": "Polyphase Examples",
"description": "Monitors project lifecycle events.",
"version": "1.0.0",
"tags": ["editor", "example"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "projectwatcheraddon",
"apiVersion": 2
}
}
Source/ProjectWatcherAddon.cpp
#include "Plugins/PolyphasePluginAPI.h"
#include "Plugins/PolyphaseEngineAPI.h"
#if EDITOR
#include "Plugins/EditorUIHooks.h"
#endif
static PolyphaseEngineAPI* sEngineAPI = nullptr;
#if EDITOR
static char sCurrentProjectPath[512] = {};
/**
* @brief Called when a project is opened.
* @param projectPath Absolute path to the project file.
* @param userData User-provided data.
*/
static void OnProjectOpen(const char* projectPath, void* userData)
{
sEngineAPI->LogDebug("Project opened: %s", projectPath);
if (projectPath)
{
strncpy(sCurrentProjectPath, projectPath, sizeof(sCurrentProjectPath) - 1);
sCurrentProjectPath[sizeof(sCurrentProjectPath) - 1] = '\0';
}
// Example: Load project-specific addon settings
sEngineAPI->LogDebug("Loading project-specific addon settings...");
// Example: Initialize project-level caches
sEngineAPI->LogDebug("Initializing project caches...");
}
/**
* @brief Called when the current project is closed.
* @param projectPath Absolute path to the project file being closed.
* @param userData User-provided data.
*/
static void OnProjectClose(const char* projectPath, void* userData)
{
sEngineAPI->LogDebug("Project closing: %s", projectPath);
// Example: Save addon data before closing
sEngineAPI->LogDebug("Saving addon data for project...");
// Example: Clear project-specific caches
sEngineAPI->LogDebug("Clearing project caches...");
sCurrentProjectPath[0] = '\0';
}
/**
* @brief Called when the project is saved.
* @param projectPath Absolute path to the project file being saved.
* @param userData User-provided data.
*/
static void OnProjectSave(const char* projectPath, void* userData)
{
sEngineAPI->LogDebug("Project saved: %s", projectPath);
// Example: Persist addon configuration alongside the project
sEngineAPI->LogDebug("Persisting addon settings...");
}
#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("Project Watcher Addon loaded!");
return 0;
}
/**
* @brief Called when the plugin is about to be unloaded.
*/
static void OnUnload()
{
if (sEngineAPI) sEngineAPI->LogDebug("Project Watcher 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->RegisterOnProjectOpen(hookId, OnProjectOpen, nullptr);
hooks->RegisterOnProjectClose(hookId, OnProjectClose, nullptr);
hooks->RegisterOnProjectSave(hookId, OnProjectSave, nullptr);
sEngineAPI->LogDebug("Project lifecycle hooks registered");
}
#endif
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Project Watcher 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
RegisterOnProjectOpen
void (*RegisterOnProjectOpen)(HookId hookId, StringEventCallback cb, void* userData);
Registers a callback invoked when a project is opened.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- cb - Function to call when a project opens
- userData - Optional user data passed to the callback
RegisterOnProjectClose
void (*RegisterOnProjectClose)(HookId hookId, StringEventCallback cb, void* userData);
Registers a callback invoked when a project is closed.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- cb - Function to call when a project closes
- userData - Optional user data passed to the callback
RegisterOnProjectSave
void (*RegisterOnProjectSave)(HookId hookId, StringEventCallback cb, void* userData);
Registers a callback invoked when a project is saved.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- cb - Function to call when a project is saved
- userData - Optional user data passed to the callback
StringEventCallback
typedef void (*StringEventCallback)(const char* str, void* userData);
Callback signature for events that provide a string parameter.
Parameters:
- str - String data (project file path in these cases)
- userData - User-provided data from registration
Best Practices
- State Management - Use
OnProjectOpento initialize andOnProjectCloseto clean up project-specific state - Path Storage - Store the project path if you need to reference it elsewhere in your addon
- Save Validation - Use
OnProjectSaveto validate or persist addon data alongside the project - Cleanup Order - Always clean up in
OnProjectClosebefore a new project opens - Null Checks - Always validate the path parameter before using it
- Automatic Cleanup - Hooks are cleaned up automatically; manual unregistration is not required
- No Blocking - Keep callbacks fast to avoid delaying project operations