Repository navigation
Editor Plugin Development
This page documents how to create plugins for SparkEditor, including the plugin interface, lifecycle, registration, and extension points.
Source: SparkEditor/Source/Core/IEditorPlugin.h, EditorPluginManager.h, EditorPanel.h
SparkEditor supports two kinds of plugins:
| Type | Registration | Use case |
|---|---|---|
| Built-in C++ |
RegisterPlugin<T>() at compile time |
Engine-shipped panels and tools |
| Stable C ABI |
LoadPlugin(path) at runtime |
Third-party lifecycle/tick extensions |
Built-ins implement IEditorPlugin. Native shared libraries use
Spark/PluginABI.h; they never exchange C++ objects, STL types, exceptions,
allocations, or vtables with the editor.
Every built-in plugin implements IEditorPlugin:
class IEditorPlugin {
public:
virtual ~IEditorPlugin() = default;
// Required — identification
virtual const char* GetName() const = 0;
virtual const char* GetVersion() const = 0;
// Required — lifecycle
virtual bool Initialize(EditorApplication* app) = 0;
virtual void Shutdown() = 0;
virtual void Update(float deltaTime) = 0;
virtual void OnGUI() = 0;
// Optional — event hooks (default no-op)
virtual void OnSceneLoad(const std::string& scenePath) {}
virtual void OnSceneSave(const std::string& scenePath) {}
virtual void OnEntitySelected(uint32_t entityID) {}
virtual void OnMenuBar() {}
};-
Initialize(app)— Called once when the plugin is loaded. Useappto access editor subsystems. Returnfalseto abort loading. -
Update(deltaTime)— Called every frame. Perform non-UI logic here. -
OnGUI()— Called every frame during ImGui rendering. Draw your UI here. -
Shutdown()— Called when the plugin is unloaded or the editor exits.
The plugin manager broadcasts editor events to all loaded plugins:
| Hook | When fired |
|---|---|
OnSceneLoad(path) |
After a scene file is loaded |
OnSceneSave(path) |
After a scene file is saved |
OnEntitySelected(id) |
When the user selects an entity in the hierarchy |
OnMenuBar() |
During main menu bar rendering (add custom menu items) |
// MyToolPlugin.h
#pragma once
#include "Core/IEditorPlugin.h"
class MyToolPlugin : public IEditorPlugin {
public:
const char* GetName() const override { return "My Tool"; }
const char* GetVersion() const override { return "1.0.0"; }
bool Initialize(EditorApplication* app) override {
m_app = app;
return true;
}
void Shutdown() override {}
void Update(float deltaTime) override {
// Non-UI logic
}
void OnGUI() override {
if (ImGui::Begin("My Tool Window")) {
ImGui::Text("Hello from my plugin!");
}
ImGui::End();
}
private:
EditorApplication* m_app = nullptr;
};In EditorApplication initialization:
m_pluginManager.RegisterPlugin<MyToolPlugin>();Use the SDK helper spark_add_plugin(...) and implement the stable C entry
point. The generated sibling .sparkplugin.json records the ABI version read
directly from Spark/PluginABI.h, binary name, identity, and SHA-256. The editor
verifies that metadata before mapping the native image.
#include <Spark/PluginABI.h>
namespace {
SparkPluginResult Create(const SparkPluginHostAPI*, SparkPluginInstance* out) {
if (!out) return SPARK_PLUGIN_ERROR_INVALID_ARGUMENT;
*out = 1;
return SPARK_PLUGIN_OK;
}
void Destroy(SparkPluginInstance) {}
SparkPluginResult Tick(SparkPluginInstance, double) { return SPARK_PLUGIN_OK; }
const SparkPluginAPI api = {
sizeof(SparkPluginAPI), SPARK_PLUGIN_ABI_MAJOR, SPARK_PLUGIN_ABI_MINOR, 0,
SPARK_PLUGIN_CAP_EDITOR_EXTENSION | SPARK_PLUGIN_CAP_TICK,
&Create, &Destroy, nullptr, nullptr, &Tick,
nullptr, nullptr, nullptr, nullptr, {}
};
const SparkPluginDescriptor descriptor = {
sizeof(SparkPluginDescriptor), SPARK_PLUGIN_ABI_MAGIC,
SPARK_PLUGIN_ABI_MAJOR, SPARK_PLUGIN_ABI_MINOR, 1, 0,
"org.example.my-tool", "My Tool", "Example", "1.0.0", &api, {}
};
}
SPARK_DECLARE_PLUGIN_ENTRY_POINT() {
if (host_abi_major != SPARK_PLUGIN_ABI_MAJOR ||
host_abi_minor < SPARK_PLUGIN_ABI_MINOR) return nullptr;
return &descriptor;
}find_package(SparkEngine REQUIRED)
spark_add_plugin(MyTool
ID "org.example.my-tool"
VERSION "1.0.0"
TYPE "editor-extension"
SOURCES MyTool.cpp)Load at runtime:
m_pluginManager.LoadPlugin("plugins/MyDLLPlugin.dll");For a production editor launch, opt in to a project-owned plugin directory:
SparkEditor --project path/to/MyGame.sparkproject --editor-plugin-dir Plugins/Editor--editor-plugin-dir is repeatable. Relative paths are resolved from the project root;
absolute paths are accepted only when they remain inside that root. Discovery is
non-recursive and only loads regular native libraries that have regular sibling
.sparkplugin.json metadata. Symlinks and traversal outside the project are rejected.
The --project file must open successfully; the editor never derives plugin ownership
from a missing, malformed, or merely requested project path. If a plugin refuses its
unload fence during a failed directory load, the manager retains fail-closed ownership
and reports the retained count instead of claiming a complete rollback.
Unload by descriptor name or stable ID:
m_pluginManager.UnloadPlugin("My Tool");The current stable ABI supports lifecycle, task/resource host services, per-frame ticks, and transactional hot reload. ImGui/panel hooks remain a built-in C++ surface until an append-only C UI service table is standardized.
class EditorPluginManager {
public:
// Registration
template<typename T> bool RegisterPlugin();
bool LoadPlugin(const std::string& path);
bool UnloadPlugin(const std::string& name);
// Query
IEditorPlugin* GetPlugin(const std::string& name) const;
const SparkPluginDescriptor* GetDynamicPluginDescriptor(const std::string& nameOrId) const;
size_t GetPluginCount() const;
std::vector<std::string> GetPluginNames() const;
// Lifecycle (called by EditorApplication)
bool InitializeAll(EditorApplication* app);
void ShutdownAll();
void UpdateAll(float deltaTime);
void RenderAll();
// Event broadcasting
void NotifySceneLoad(const std::string& scenePath);
void NotifySceneSave(const std::string& scenePath);
void NotifyEntitySelected(uint32_t entityID);
void RenderMenuBarItems();
// Panel registration (plugins can add panels)
void RegisterPanel(std::unique_ptr<EditorPanel> panel);
};Plugins can register new editor panels that integrate with the panel system:
#include "Core/EditorPanel.h"
class MyPanel : public EditorPanel {
public:
MyPanel() : EditorPanel("My Panel", "my_panel_id") {}
bool Initialize() override { return true; }
void Update(float dt) override {}
void Render() override {
if (!IsVisible()) return;
if (BeginPanel()) {
ImGui::Text("Panel content here");
}
EndPanel();
}
void Shutdown() override {}
};bool MyPlugin::Initialize(EditorApplication* app) {
auto panel = std::make_unique<MyPanel>();
app->GetPluginManager().RegisterPanel(std::move(panel));
return true;
}Plugins can extend the asset pipeline with custom format importers:
#include "AssetPipeline/AdvancedAssetPipeline.h"
class MyFormatProcessor : public AssetProcessor {
public:
std::string GetName() const override { return "My Format"; }
std::vector<std::string> GetSupportedExtensions() const override {
return {".myformat"};
}
AssetType GetAssetType() const override { return AssetType::Mesh; }
bool Process(AssetMetadata& metadata,
const AssetImportSettings& settings,
std::function<void(float)> progress) override {
// Parse .myformat file and populate metadata
return true;
}
bool GenerateThumbnail(const AssetMetadata& metadata, int size) override {
return false; // No thumbnail support
}
bool Validate(const AssetMetadata& metadata) override {
return true;
}
};
// Register
pipeline.RegisterProcessor(std::make_unique<MyFormatProcessor>());Use the OnMenuBar() hook to add items to the editor's main menu:
void MyPlugin::OnMenuBar() {
if (ImGui::BeginMenu("My Plugin")) {
if (ImGui::MenuItem("Open Tool Window"))
m_showWindow = true;
if (ImGui::MenuItem("Run Analysis"))
RunAnalysis();
ImGui::EndMenu();
}
}- Keep plugins self-contained. Don't modify engine internals — use the provided APIs.
-
Handle initialization failure gracefully. Return
falsefromInitialize()if dependencies are missing. -
Clean up in
Shutdown(). Release all resources, unregister callbacks. - Use ImGui for UI. All editor UI uses Dear ImGui — use it for consistency.
-
Version your plugins. The
GetVersion()method helps track compatibility. -
Don't block in
Update(). Long operations should be async or spread across frames.
See SparkEditor for the full editor architecture.
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers