feat(示例): 添加自定义模块示例和文档
新增HelloModule示例展示如何创建自定义模块,包含: 1. 模块配置类实现 2. 模块初始化器实现 3. 自动注册机制 4. JSON配置支持 5. 场景中使用模块的示例 同时更新模块系统文档,详细说明自定义模块开发流程
This commit is contained in:
parent
867013f6eb
commit
b55d279611
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
@ -7,7 +8,6 @@
|
||||||
#include <extra2d/graphics/texture.h>
|
#include <extra2d/graphics/texture.h>
|
||||||
#include <glm/mat4x4.hpp>
|
#include <glm/mat4x4.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
|
@ -52,10 +52,11 @@ public:
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
// 批量绘制接口 - 用于自动批处理
|
// 批量绘制接口 - 用于自动批处理
|
||||||
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
|
void drawBatch(const Texture &texture,
|
||||||
|
const std::vector<SpriteData> &sprites);
|
||||||
|
|
||||||
// 立即绘制(不缓存)
|
// 立即绘制(不缓存)
|
||||||
void drawImmediate(const Texture& texture, const SpriteData& data);
|
void drawImmediate(const Texture &texture, const SpriteData &data);
|
||||||
|
|
||||||
// 统计
|
// 统计
|
||||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||||
|
|
@ -63,7 +64,7 @@ public:
|
||||||
uint32_t getBatchCount() const { return batchCount_; }
|
uint32_t getBatchCount() const { return batchCount_; }
|
||||||
|
|
||||||
// 检查是否需要刷新
|
// 检查是否需要刷新
|
||||||
bool needsFlush(const Texture& texture, bool isSDF) const;
|
bool needsFlush(const Texture &texture, bool isSDF) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint vao_;
|
GLuint vao_;
|
||||||
|
|
@ -78,7 +79,7 @@ private:
|
||||||
const Texture *currentTexture_;
|
const Texture *currentTexture_;
|
||||||
bool currentIsSDF_;
|
bool currentIsSDF_;
|
||||||
glm::mat4 viewProjection_;
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
// 缓存上一帧的 viewProjection,避免重复设置
|
// 缓存上一帧的 viewProjection,避免重复设置
|
||||||
glm::mat4 cachedViewProjection_;
|
glm::mat4 cachedViewProjection_;
|
||||||
bool viewProjectionDirty_ = true;
|
bool viewProjectionDirty_ = true;
|
||||||
|
|
@ -89,9 +90,9 @@ private:
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
void setupShader();
|
void setupShader();
|
||||||
|
|
||||||
// 添加顶点到缓冲区
|
// 添加顶点到缓冲区
|
||||||
void addVertices(const SpriteData& data);
|
void addVertices(const SpriteData &data);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/config/config_module.h>
|
#include <extra2d/config/config_module.h>
|
||||||
#include <extra2d/config/module_registry.h>
|
#include <extra2d/config/module_registry.h>
|
||||||
#include <extra2d/graphics/render_module.h>
|
|
||||||
#include <extra2d/graphics/render_config.h>
|
#include <extra2d/graphics/render_config.h>
|
||||||
|
#include <extra2d/graphics/render_module.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
#include <extra2d/platform/iinput.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/platform/input_module.h>
|
#include <extra2d/platform/input_module.h>
|
||||||
#include <extra2d/platform/platform_init_module.h>
|
#include <extra2d/platform/platform_init_module.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
|
#include <extra2d/services/camera_service.h>
|
||||||
|
#include <extra2d/services/event_service.h>
|
||||||
#include <extra2d/services/scene_service.h>
|
#include <extra2d/services/scene_service.h>
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
#include <extra2d/services/event_service.h>
|
|
||||||
#include <extra2d/services/camera_service.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
@ -20,384 +21,391 @@ namespace extra2d {
|
||||||
|
|
||||||
static double getTimeSeconds() {
|
static double getTimeSeconds() {
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return static_cast<double>(ts.tv_sec) +
|
return static_cast<double>(ts.tv_sec) +
|
||||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||||
#else
|
#else
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
auto now = steady_clock::now();
|
auto now = steady_clock::now();
|
||||||
auto duration = now.time_since_epoch();
|
auto duration = now.time_since_epoch();
|
||||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Application& Application::get() {
|
Application &Application::get() {
|
||||||
static Application instance;
|
static Application instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
|
||||||
|
|
||||||
Application::~Application() {
|
|
||||||
shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::init() {
|
bool Application::init() {
|
||||||
AppConfig cfg;
|
AppConfig cfg;
|
||||||
return init(cfg);
|
return init(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::init(const AppConfig& config) {
|
bool Application::init(const AppConfig &config) {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
register_config_module();
|
||||||
|
register_platform_module();
|
||||||
|
register_window_module();
|
||||||
|
register_input_module();
|
||||||
|
register_render_module();
|
||||||
|
|
||||||
|
auto *configInit =
|
||||||
|
ModuleRegistry::instance().getInitializer(get_config_module_id());
|
||||||
|
if (configInit) {
|
||||||
|
auto *cfgInit = dynamic_cast<ConfigModuleInitializer *>(configInit);
|
||||||
|
if (cfgInit) {
|
||||||
|
cfgInit->setAppConfig(config);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
register_config_module();
|
return initModules();
|
||||||
register_platform_module();
|
|
||||||
register_window_module();
|
|
||||||
register_input_module();
|
|
||||||
register_render_module();
|
|
||||||
|
|
||||||
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
|
|
||||||
if (configInit) {
|
|
||||||
auto* cfgInit = dynamic_cast<ConfigModuleInitializer*>(configInit);
|
|
||||||
if (cfgInit) {
|
|
||||||
cfgInit->setAppConfig(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initModules();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::init(const std::string& configPath) {
|
bool Application::init(const std::string &configPath) {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
register_config_module();
|
||||||
|
register_platform_module();
|
||||||
|
register_window_module();
|
||||||
|
register_input_module();
|
||||||
|
register_render_module();
|
||||||
|
|
||||||
|
auto *configInit =
|
||||||
|
ModuleRegistry::instance().getInitializer(get_config_module_id());
|
||||||
|
if (configInit) {
|
||||||
|
auto *cfgInit = dynamic_cast<ConfigModuleInitializer *>(configInit);
|
||||||
|
if (cfgInit) {
|
||||||
|
cfgInit->setConfigPath(configPath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
register_config_module();
|
return initModules();
|
||||||
register_platform_module();
|
|
||||||
register_window_module();
|
|
||||||
register_input_module();
|
|
||||||
register_render_module();
|
|
||||||
|
|
||||||
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
|
|
||||||
if (configInit) {
|
|
||||||
auto* cfgInit = dynamic_cast<ConfigModuleInitializer*>(configInit);
|
|
||||||
if (cfgInit) {
|
|
||||||
cfgInit->setConfigPath(configPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initModules();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::registerCoreServices() {
|
void Application::registerCoreServices() {
|
||||||
auto& locator = ServiceLocator::instance();
|
auto &locator = ServiceLocator::instance();
|
||||||
|
|
||||||
if (!locator.hasService<ISceneService>()) {
|
if (!locator.hasService<ISceneService>()) {
|
||||||
locator.registerService<ISceneService>(makeShared<SceneService>());
|
locator.registerService<ISceneService>(makeShared<SceneService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!locator.hasService<ITimerService>()) {
|
if (!locator.hasService<ITimerService>()) {
|
||||||
locator.registerService<ITimerService>(makeShared<TimerService>());
|
locator.registerService<ITimerService>(makeShared<TimerService>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!locator.hasService<ICameraService>()) {
|
if (!locator.hasService<ICameraService>()) {
|
||||||
auto cameraService = makeShared<CameraService>();
|
auto cameraService = makeShared<CameraService>();
|
||||||
if (window_) {
|
if (window_) {
|
||||||
cameraService->setViewport(0, static_cast<float>(window_->width()),
|
cameraService->setViewport(0, static_cast<float>(window_->width()),
|
||||||
static_cast<float>(window_->height()), 0);
|
static_cast<float>(window_->height()), 0);
|
||||||
ViewportConfig vpConfig;
|
ViewportConfig vpConfig;
|
||||||
vpConfig.logicWidth = static_cast<float>(window_->width());
|
vpConfig.logicWidth = static_cast<float>(window_->width());
|
||||||
vpConfig.logicHeight = static_cast<float>(window_->height());
|
vpConfig.logicHeight = static_cast<float>(window_->height());
|
||||||
vpConfig.mode = ViewportMode::AspectRatio;
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
cameraService->setViewportConfig(vpConfig);
|
cameraService->setViewportConfig(vpConfig);
|
||||||
cameraService->updateViewport(window_->width(), window_->height());
|
cameraService->updateViewport(window_->width(), window_->height());
|
||||||
}
|
|
||||||
locator.registerService<ICameraService>(cameraService);
|
|
||||||
}
|
}
|
||||||
|
locator.registerService<ICameraService>(cameraService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::initModules() {
|
bool Application::initModules() {
|
||||||
auto& locator = ServiceLocator::instance();
|
auto &locator = ServiceLocator::instance();
|
||||||
|
|
||||||
if (!locator.hasService<IEventService>()) {
|
|
||||||
locator.registerService<IEventService>(makeShared<EventService>());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
|
||||||
|
|
||||||
for (ModuleId moduleId : initOrder) {
|
|
||||||
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
|
||||||
if (!initializer) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
if (!locator.hasService<IEventService>()) {
|
||||||
if (!moduleConfig) {
|
locator.registerService<IEventService>(makeShared<EventService>());
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto info = moduleConfig->getModuleInfo();
|
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||||
if (!info.enabled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.name == "Render") {
|
for (ModuleId moduleId : initOrder) {
|
||||||
continue;
|
auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||||
}
|
if (!initializer) {
|
||||||
|
continue;
|
||||||
if (!initializer->initialize(moduleConfig)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* windowInit = ModuleRegistry::instance().getInitializer(get_window_module_id());
|
auto *moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
||||||
if (!windowInit || !windowInit->isInitialized()) {
|
if (!moduleConfig) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = moduleConfig->getModuleInfo();
|
||||||
|
if (!info.enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.name == "Render") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initializer->initialize(moduleConfig)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *windowInit =
|
||||||
|
ModuleRegistry::instance().getInitializer(get_window_module_id());
|
||||||
|
if (!windowInit || !windowInit->isInitialized()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *windowModule = dynamic_cast<WindowModuleInitializer *>(windowInit);
|
||||||
|
if (!windowModule) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = windowModule->getWindow();
|
||||||
|
if (!window_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *renderInit =
|
||||||
|
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
|
if (renderInit) {
|
||||||
|
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||||
|
if (renderModule) {
|
||||||
|
renderModule->setWindow(window_);
|
||||||
|
|
||||||
|
auto *renderConfig =
|
||||||
|
ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||||
|
if (renderConfig && !renderInit->initialize(renderConfig)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto* windowModule = dynamic_cast<WindowModuleInitializer*>(windowInit);
|
registerCoreServices();
|
||||||
if (!windowModule) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
window_ = windowModule->getWindow();
|
if (!ServiceLocator::instance().initializeAll()) {
|
||||||
if (!window_) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||||
if (renderInit) {
|
if (cameraService && window_) {
|
||||||
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
window_->onResize([cameraService](int width, int height) {
|
||||||
if (renderModule) {
|
cameraService->updateViewport(width, height);
|
||||||
renderModule->setWindow(window_);
|
cameraService->applyViewportAdapter();
|
||||||
|
|
||||||
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
auto sceneService =
|
||||||
if (renderConfig && !renderInit->initialize(renderConfig)) {
|
ServiceLocator::instance().getService<ISceneService>();
|
||||||
return false;
|
if (sceneService) {
|
||||||
}
|
auto currentScene = sceneService->getCurrentScene();
|
||||||
|
if (currentScene) {
|
||||||
|
currentScene->setViewportSize(static_cast<float>(width),
|
||||||
|
static_cast<float>(height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
registerCoreServices();
|
initialized_ = true;
|
||||||
|
running_ = true;
|
||||||
|
|
||||||
if (!ServiceLocator::instance().initializeAll()) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
|
||||||
if (cameraService && window_) {
|
|
||||||
window_->onResize([this, cameraService](int width, int height) {
|
|
||||||
cameraService->updateViewport(width, height);
|
|
||||||
cameraService->applyViewportAdapter();
|
|
||||||
|
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
|
||||||
if (sceneService) {
|
|
||||||
auto currentScene = sceneService->getCurrentScene();
|
|
||||||
if (currentScene) {
|
|
||||||
currentScene->setViewportSize(static_cast<float>(width),
|
|
||||||
static_cast<float>(height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
running_ = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::shutdown() {
|
void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
VRAMMgr::get().printStats();
|
VRAMMgr::get().printStats();
|
||||||
|
|
||||||
ServiceLocator::instance().clear();
|
ServiceLocator::instance().clear();
|
||||||
|
|
||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
|
|
||||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||||
|
|
||||||
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
||||||
ModuleId moduleId = *it;
|
ModuleId moduleId = *it;
|
||||||
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||||
if (initializer && initializer->isInitialized()) {
|
if (initializer && initializer->isInitialized()) {
|
||||||
initializer->shutdown();
|
initializer->shutdown();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::~Application() {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::run() {
|
void Application::run() {
|
||||||
if (!initialized_) {
|
if (!initialized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
|
||||||
while (running_ && !window_->shouldClose()) {
|
while (running_ && !window_->shouldClose()) {
|
||||||
mainLoop();
|
mainLoop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::quit() {
|
void Application::quit() {
|
||||||
shouldQuit_ = true;
|
shouldQuit_ = true;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::pause() {
|
void Application::pause() {
|
||||||
if (!paused_) {
|
if (!paused_) {
|
||||||
paused_ = true;
|
paused_ = true;
|
||||||
ServiceLocator::instance().pauseAll();
|
ServiceLocator::instance().pauseAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::resume() {
|
void Application::resume() {
|
||||||
if (paused_) {
|
if (paused_) {
|
||||||
paused_ = false;
|
paused_ = false;
|
||||||
ServiceLocator::instance().resumeAll();
|
ServiceLocator::instance().resumeAll();
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::mainLoop() {
|
void Application::mainLoop() {
|
||||||
double currentTime = getTimeSeconds();
|
double currentTime = getTimeSeconds();
|
||||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||||
lastFrameTime_ = currentTime;
|
lastFrameTime_ = currentTime;
|
||||||
|
|
||||||
totalTime_ += deltaTime_;
|
totalTime_ += deltaTime_;
|
||||||
|
|
||||||
frameCount_++;
|
frameCount_++;
|
||||||
fpsTimer_ += deltaTime_;
|
fpsTimer_ += deltaTime_;
|
||||||
if (fpsTimer_ >= 1.0f) {
|
if (fpsTimer_ >= 1.0f) {
|
||||||
currentFps_ = frameCount_;
|
currentFps_ = frameCount_;
|
||||||
frameCount_ = 0;
|
frameCount_ = 0;
|
||||||
fpsTimer_ -= 1.0f;
|
fpsTimer_ -= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_->poll();
|
||||||
|
|
||||||
|
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||||
|
if (eventService) {
|
||||||
|
eventService->processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paused_) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
const auto &appConfig = ConfigManager::instance().appConfig();
|
||||||
|
|
||||||
|
auto *renderConfig =
|
||||||
|
ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||||
|
auto *renderModuleConfig =
|
||||||
|
dynamic_cast<const RenderModuleConfig *>(renderConfig);
|
||||||
|
|
||||||
|
if (renderModuleConfig && !renderModuleConfig->vsync &&
|
||||||
|
renderModuleConfig->targetFPS > 0) {
|
||||||
|
double frameEndTime = getTimeSeconds();
|
||||||
|
double frameTime = frameEndTime - currentTime;
|
||||||
|
double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
|
||||||
|
if (frameTime < target) {
|
||||||
|
auto sleepSeconds = target - frameTime;
|
||||||
|
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window_->poll();
|
ConfigManager::instance().update(deltaTime_);
|
||||||
|
|
||||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
|
||||||
if (eventService) {
|
|
||||||
eventService->processQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!paused_) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
render();
|
|
||||||
|
|
||||||
const auto& appConfig = ConfigManager::instance().appConfig();
|
|
||||||
|
|
||||||
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
|
||||||
auto* renderModuleConfig = dynamic_cast<const RenderModuleConfig*>(renderConfig);
|
|
||||||
|
|
||||||
if (renderModuleConfig && !renderModuleConfig->vsync && renderModuleConfig->targetFPS > 0) {
|
|
||||||
double frameEndTime = getTimeSeconds();
|
|
||||||
double frameTime = frameEndTime - currentTime;
|
|
||||||
double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
|
|
||||||
if (frameTime < target) {
|
|
||||||
auto sleepSeconds = target - frameTime;
|
|
||||||
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager::instance().update(deltaTime_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::update() {
|
void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); }
|
||||||
ServiceLocator::instance().updateAll(deltaTime_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::render() {
|
void Application::render() {
|
||||||
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
auto *renderInit =
|
||||||
RenderBackend* renderer = nullptr;
|
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
if (renderInit) {
|
RenderBackend *renderer = nullptr;
|
||||||
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
if (renderInit) {
|
||||||
if (renderModule) {
|
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||||
renderer = renderModule->getRenderer();
|
if (renderModule) {
|
||||||
}
|
renderer = renderModule->getRenderer();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!renderer) {
|
if (!renderer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||||
if (cameraService) {
|
if (cameraService) {
|
||||||
const auto& vp = cameraService->getViewportResult().viewport;
|
const auto &vp = cameraService->getViewportResult().viewport;
|
||||||
renderer->setViewport(
|
renderer->setViewport(
|
||||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||||
|
|
||||||
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
|
||||||
} else {
|
|
||||||
renderer->setViewport(0, 0, window_->width(), window_->height());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
||||||
if (sceneService) {
|
} else {
|
||||||
sceneService->render(*renderer);
|
renderer->setViewport(0, 0, window_->width(), window_->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
window_->swap();
|
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||||
|
if (sceneService) {
|
||||||
|
sceneService->render(*renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
window_->swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
IInput& Application::input() {
|
IInput &Application::input() { return *window_->input(); }
|
||||||
return *window_->input();
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderBackend& Application::renderer() {
|
RenderBackend &Application::renderer() {
|
||||||
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
auto *renderInit =
|
||||||
if (renderInit) {
|
ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
if (renderInit) {
|
||||||
if (renderModule && renderModule->getRenderer()) {
|
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
|
||||||
return *renderModule->getRenderer();
|
if (renderModule && renderModule->getRenderer()) {
|
||||||
}
|
return *renderModule->getRenderer();
|
||||||
}
|
}
|
||||||
static RenderBackend* dummy = nullptr;
|
}
|
||||||
if (!dummy) {
|
static RenderBackend *dummy = nullptr;
|
||||||
dummy = RenderBackend::create(BackendType::OpenGL).release();
|
if (!dummy) {
|
||||||
}
|
dummy = RenderBackend::create(BackendType::OpenGL).release();
|
||||||
return *dummy;
|
}
|
||||||
|
return *dummy;
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ISceneService> Application::scenes() {
|
SharedPtr<ISceneService> Application::scenes() {
|
||||||
return ServiceLocator::instance().getService<ISceneService>();
|
return ServiceLocator::instance().getService<ISceneService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ITimerService> Application::timers() {
|
SharedPtr<ITimerService> Application::timers() {
|
||||||
return ServiceLocator::instance().getService<ITimerService>();
|
return ServiceLocator::instance().getService<ITimerService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<IEventService> Application::events() {
|
SharedPtr<IEventService> Application::events() {
|
||||||
return ServiceLocator::instance().getService<IEventService>();
|
return ServiceLocator::instance().getService<IEventService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ICameraService> Application::camera() {
|
SharedPtr<ICameraService> Application::camera() {
|
||||||
return ServiceLocator::instance().getService<ICameraService>();
|
return ServiceLocator::instance().getService<ICameraService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::enterScene(Ptr<Scene> scene) {
|
void Application::enterScene(Ptr<Scene> scene) {
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||||
if (sceneService && scene) {
|
if (sceneService && scene) {
|
||||||
scene->setViewportSize(static_cast<float>(window_->width()),
|
scene->setViewportSize(static_cast<float>(window_->width()),
|
||||||
static_cast<float>(window_->height()));
|
static_cast<float>(window_->height()));
|
||||||
sceneService->enterScene(scene);
|
sceneService->enterScene(scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigManager& Application::config() {
|
ConfigManager &Application::config() { return ConfigManager::instance(); }
|
||||||
return ConfigManager::instance();
|
|
||||||
|
const AppConfig &Application::getConfig() const {
|
||||||
|
return ConfigManager::instance().appConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppConfig& Application::getConfig() const {
|
} // namespace extra2d
|
||||||
return ConfigManager::instance().appConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -966,9 +966,222 @@ window_->onResize([this, cameraService](int width, int height) {
|
||||||
## 示例
|
## 示例
|
||||||
|
|
||||||
完整示例请参考:
|
完整示例请参考:
|
||||||
|
- [examples/hello_module/](../../examples/hello_module/) - **Hello World 自定义模块示例**
|
||||||
- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例(场景图、输入事件、视口适配)
|
- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例(场景图、输入事件、视口适配)
|
||||||
- [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现
|
- [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现
|
||||||
- [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现
|
- [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现
|
||||||
- [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现
|
- [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现
|
||||||
- [Extra2D/src/scene/node.cpp](../../Extra2D/src/scene/node.cpp) - Node 实现
|
- [Extra2D/src/scene/node.cpp](../../Extra2D/src/scene/node.cpp) - Node 实现
|
||||||
- [Extra2D/src/scene/shape_node.cpp](../../Extra2D/src/scene/shape_node.cpp) - ShapeNode 实现
|
- [Extra2D/src/scene/shape_node.cpp](../../Extra2D/src/scene/shape_node.cpp) - ShapeNode 实现
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hello World 自定义模块示例
|
||||||
|
|
||||||
|
### 示例概述
|
||||||
|
|
||||||
|
`examples/hello_module/` 目录包含一个完整的自定义模块示例,展示如何:
|
||||||
|
|
||||||
|
1. 定义模块配置数据结构
|
||||||
|
2. 实现 `IModuleConfig` 接口
|
||||||
|
3. 实现 `IModuleInitializer` 接口
|
||||||
|
4. 使用自动注册机制
|
||||||
|
5. 支持 JSON 配置
|
||||||
|
|
||||||
|
### 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
examples/hello_module/
|
||||||
|
├── hello_module.h # 模块头文件(配置类 + 初始化器类)
|
||||||
|
├── hello_module.cpp # 模块实现
|
||||||
|
├── main.cpp # 示例入口
|
||||||
|
└── config.json # 配置文件示例
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心代码解析
|
||||||
|
|
||||||
|
#### 1. 配置数据结构
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct HelloModuleConfigData {
|
||||||
|
std::string greeting = "Hello, Extra2D!";
|
||||||
|
int repeatCount = 1;
|
||||||
|
bool enableLogging = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 配置类实现
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class HelloModuleConfig : public IModuleConfig {
|
||||||
|
public:
|
||||||
|
HelloModuleConfigData config;
|
||||||
|
|
||||||
|
ModuleInfo getModuleInfo() const override {
|
||||||
|
ModuleInfo info;
|
||||||
|
info.name = "HelloModule";
|
||||||
|
info.version = "1.0.0";
|
||||||
|
info.priority = ModulePriority::User; // 用户自定义模块
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getConfigSectionName() const override {
|
||||||
|
return "hello"; // 对应 config.json 中的 "hello" 节
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate() const override {
|
||||||
|
return !config.greeting.empty() && config.repeatCount > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 初始化器实现
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class HelloModuleInitializer : public IModuleInitializer {
|
||||||
|
public:
|
||||||
|
bool initialize(const IModuleConfig* config) override {
|
||||||
|
const HelloModuleConfig* cfg = dynamic_cast<const HelloModuleConfig*>(config);
|
||||||
|
if (!cfg || !cfg->validate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_ = cfg->config;
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
|
// 执行模块初始化逻辑
|
||||||
|
E2D_LOG_INFO("HelloModule initialized: {}", config_.greeting);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() override {
|
||||||
|
E2D_LOG_INFO("HelloModule shutdown");
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 自动注册机制
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace {
|
||||||
|
struct HelloModuleAutoRegister {
|
||||||
|
HelloModuleAutoRegister() {
|
||||||
|
register_hello_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static HelloModuleAutoRegister s_autoRegister; // 程序启动时自动执行
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hello": {
|
||||||
|
"greeting": "Hello from custom module!",
|
||||||
|
"repeatCount": 3,
|
||||||
|
"enableLogging": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xmake run demo_hello_module
|
||||||
|
```
|
||||||
|
|
||||||
|
### 预期输出
|
||||||
|
|
||||||
|
```
|
||||||
|
[INFO] HelloModule initialized
|
||||||
|
[INFO] Greeting: Hello, Extra2D!
|
||||||
|
[INFO] Repeat Count: 1
|
||||||
|
[INFO] Logging Enabled: true
|
||||||
|
[INFO] [HelloModule] Hello, Extra2D!
|
||||||
|
[INFO] HelloScene entered
|
||||||
|
[INFO] [HelloModule] Hello, Extra2D! # 场景 onEnter() 调用
|
||||||
|
[INFO] Scene calling HelloModule from onUpdate...
|
||||||
|
[INFO] [HelloModule] Hello, Extra2D! # 场景每5秒调用
|
||||||
|
[INFO] HelloModule shutdown - Goodbye!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在场景中使用模块
|
||||||
|
|
||||||
|
模块初始化后,可以在场景中通过 `ModuleRegistry` 获取模块实例并调用其功能:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class HelloScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 获取模块初始化器
|
||||||
|
ModuleId helloId = get_hello_module_id();
|
||||||
|
auto* initializer = ModuleRegistry::instance().getInitializer(helloId);
|
||||||
|
|
||||||
|
if (initializer) {
|
||||||
|
// 转换为具体类型
|
||||||
|
auto* helloInit = dynamic_cast<HelloModuleInitializer*>(initializer);
|
||||||
|
if (helloInit) {
|
||||||
|
// 调用模块功能
|
||||||
|
helloInit->sayHello();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
time_ += dt;
|
||||||
|
|
||||||
|
// 每5秒调用一次模块功能
|
||||||
|
if (time_ >= 5.0f) {
|
||||||
|
ModuleId helloId = get_hello_module_id();
|
||||||
|
auto* initializer = ModuleRegistry::instance().getInitializer(helloId);
|
||||||
|
if (initializer) {
|
||||||
|
auto* helloInit = dynamic_cast<HelloModuleInitializer*>(initializer);
|
||||||
|
if (helloInit) {
|
||||||
|
helloInit->sayHello();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float time_ = 0.0f;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模块使用流程总结
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 1. 程序启动 │
|
||||||
|
│ └── 静态变量 HelloModuleAutoRegister 自动注册模块 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 2. Application::init() │
|
||||||
|
│ └── 遍历 ModuleRegistry 中所有已注册模块 │
|
||||||
|
│ └── 按优先级顺序调用 initialize() │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 3. 场景/其他代码使用模块 │
|
||||||
|
│ └── ModuleRegistry::getInitializer(moduleId) │
|
||||||
|
│ └── dynamic_cast 转换为具体类型 │
|
||||||
|
│ └── 调用模块方法 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 4. Application::shutdown() │
|
||||||
|
│ └── 按逆序调用所有模块的 shutdown() │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "HelloModule Example",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"window": {
|
||||||
|
"title": "Hello Module Example",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600,
|
||||||
|
"mode": "windowed",
|
||||||
|
"vsync": true
|
||||||
|
},
|
||||||
|
"hello": {
|
||||||
|
"greeting": "Hello from custom module!",
|
||||||
|
"repeatCount": 3,
|
||||||
|
"enableLogging": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
#include "hello_module.h"
|
||||||
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static ModuleId s_helloModuleId = INVALID_MODULE_ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取Hello模块标识符
|
||||||
|
*/
|
||||||
|
ModuleId get_hello_module_id() {
|
||||||
|
return s_helloModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON加载配置
|
||||||
|
*/
|
||||||
|
bool HelloModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
|
|
||||||
|
if (j.contains("greeting")) {
|
||||||
|
config.greeting = j["greeting"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (j.contains("repeatCount")) {
|
||||||
|
config.repeatCount = j["repeatCount"].get<int>();
|
||||||
|
}
|
||||||
|
if (j.contains("enableLogging")) {
|
||||||
|
config.enableLogging = j["enableLogging"].get<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存配置到JSON
|
||||||
|
*/
|
||||||
|
bool HelloModuleConfig::saveToJson(void* jsonData) const {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["greeting"] = config.greeting;
|
||||||
|
j["repeatCount"] = config.repeatCount;
|
||||||
|
j["enableLogging"] = config.enableLogging;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
|
HelloModuleInitializer::HelloModuleInitializer()
|
||||||
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
|
, initialized_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
HelloModuleInitializer::~HelloModuleInitializer() {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块依赖列表
|
||||||
|
*/
|
||||||
|
std::vector<ModuleId> HelloModuleInitializer::getDependencies() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化模块
|
||||||
|
*/
|
||||||
|
bool HelloModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
|
if (initialized_) {
|
||||||
|
E2D_LOG_WARN("HelloModule already initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
E2D_LOG_ERROR("HelloModule config is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HelloModuleConfig* helloConfig = dynamic_cast<const HelloModuleConfig*>(config);
|
||||||
|
if (!helloConfig) {
|
||||||
|
E2D_LOG_ERROR("Invalid HelloModule config type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!helloConfig->validate()) {
|
||||||
|
E2D_LOG_ERROR("HelloModule config validation failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_ = helloConfig->config;
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("HelloModule initialized");
|
||||||
|
E2D_LOG_INFO(" Greeting: {}", config_.greeting);
|
||||||
|
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
|
||||||
|
E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging);
|
||||||
|
|
||||||
|
sayHello();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭模块
|
||||||
|
*/
|
||||||
|
void HelloModuleInitializer::shutdown() {
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
|
if (config_.enableLogging) {
|
||||||
|
E2D_LOG_INFO("HelloModule shutdown - Goodbye!");
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行问候操作
|
||||||
|
*/
|
||||||
|
void HelloModuleInitializer::sayHello() const {
|
||||||
|
if (!config_.enableLogging) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < config_.repeatCount; ++i) {
|
||||||
|
E2D_LOG_INFO("[HelloModule] {}", config_.greeting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册Hello模块
|
||||||
|
*/
|
||||||
|
void register_hello_module() {
|
||||||
|
if (s_helloModuleId != INVALID_MODULE_ID) {
|
||||||
|
E2D_LOG_WARN("HelloModule already registered");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_helloModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<HelloModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<HelloModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_helloModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("HelloModule registered with id: {}", s_helloModuleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
* @brief 自动注册器
|
||||||
|
* 在程序启动时自动注册模块
|
||||||
|
*/
|
||||||
|
struct HelloModuleAutoRegister {
|
||||||
|
HelloModuleAutoRegister() {
|
||||||
|
register_hello_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static HelloModuleAutoRegister s_autoRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/config/module_config.h>
|
||||||
|
#include <extra2d/config/module_initializer.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hello模块配置数据结构
|
||||||
|
*/
|
||||||
|
struct HelloModuleConfigData {
|
||||||
|
std::string greeting = "Hello, Extra2D!";
|
||||||
|
int repeatCount = 1;
|
||||||
|
bool enableLogging = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hello模块配置类
|
||||||
|
*
|
||||||
|
* 这是一个简单的自定义模块示例,展示如何:
|
||||||
|
* 1. 定义模块配置数据结构
|
||||||
|
* 2. 实现IModuleConfig接口
|
||||||
|
* 3. 支持JSON配置加载/保存
|
||||||
|
*/
|
||||||
|
class HelloModuleConfig : public IModuleConfig {
|
||||||
|
public:
|
||||||
|
HelloModuleConfigData config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块信息
|
||||||
|
*/
|
||||||
|
ModuleInfo getModuleInfo() const override {
|
||||||
|
ModuleInfo info;
|
||||||
|
info.id = 0;
|
||||||
|
info.name = "HelloModule";
|
||||||
|
info.version = "1.0.0";
|
||||||
|
info.priority = ModulePriority::User;
|
||||||
|
info.enabled = true;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取配置节名称
|
||||||
|
*/
|
||||||
|
std::string getConfigSectionName() const override {
|
||||||
|
return "hello";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 验证配置有效性
|
||||||
|
*/
|
||||||
|
bool validate() const override {
|
||||||
|
return !config.greeting.empty() && config.repeatCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置为默认配置
|
||||||
|
*/
|
||||||
|
void resetToDefaults() override {
|
||||||
|
config = HelloModuleConfigData{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用平台约束
|
||||||
|
*/
|
||||||
|
void applyPlatformConstraints(PlatformType platform) override {
|
||||||
|
(void)platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON加载配置
|
||||||
|
*/
|
||||||
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存配置到JSON
|
||||||
|
*/
|
||||||
|
bool saveToJson(void* jsonData) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hello模块初始化器
|
||||||
|
*
|
||||||
|
* 负责模块的生命周期管理
|
||||||
|
*/
|
||||||
|
class HelloModuleInitializer : public IModuleInitializer {
|
||||||
|
public:
|
||||||
|
HelloModuleInitializer();
|
||||||
|
~HelloModuleInitializer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块标识符
|
||||||
|
*/
|
||||||
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块优先级
|
||||||
|
*/
|
||||||
|
ModulePriority getPriority() const override { return ModulePriority::User; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块依赖列表
|
||||||
|
*/
|
||||||
|
std::vector<ModuleId> getDependencies() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化模块
|
||||||
|
*/
|
||||||
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭模块
|
||||||
|
*/
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置模块标识符
|
||||||
|
*/
|
||||||
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行问候操作
|
||||||
|
*/
|
||||||
|
void sayHello() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
|
bool initialized_ = false;
|
||||||
|
HelloModuleConfigData config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取Hello模块标识符
|
||||||
|
*/
|
||||||
|
ModuleId get_hello_module_id();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册Hello模块
|
||||||
|
*/
|
||||||
|
void register_hello_module();
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
#include "hello_module.h"
|
||||||
|
#include <extra2d/app/application.h>
|
||||||
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <extra2d/services/scene_service.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
using namespace extra2d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 自定义场景类
|
||||||
|
*
|
||||||
|
* 展示如何在场景中使用自定义模块
|
||||||
|
*/
|
||||||
|
class HelloScene : public Scene {
|
||||||
|
public:
|
||||||
|
static Ptr<HelloScene> create() { return makeShared<HelloScene>(); }
|
||||||
|
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
E2D_LOG_INFO("HelloScene entered");
|
||||||
|
|
||||||
|
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
|
||||||
|
|
||||||
|
ModuleId helloId = get_hello_module_id();
|
||||||
|
auto *initializer = ModuleRegistry::instance().getInitializer(helloId);
|
||||||
|
if (initializer) {
|
||||||
|
auto *helloInit = dynamic_cast<HelloModuleInitializer *>(initializer);
|
||||||
|
if (helloInit) {
|
||||||
|
E2D_LOG_INFO("Scene calling HelloModule from onEnter...");
|
||||||
|
helloInit->sayHello();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
time_ += dt;
|
||||||
|
|
||||||
|
if (time_ >= 5.0f) {
|
||||||
|
ModuleId helloId = get_hello_module_id();
|
||||||
|
auto *initializer = ModuleRegistry::instance().getInitializer(helloId);
|
||||||
|
if (initializer) {
|
||||||
|
auto *helloInit = dynamic_cast<HelloModuleInitializer *>(initializer);
|
||||||
|
if (helloInit) {
|
||||||
|
E2D_LOG_INFO("Scene calling HelloModule from onUpdate...");
|
||||||
|
helloInit->sayHello();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float time_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用程序入口
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("=== Hello Module Example ===");
|
||||||
|
E2D_LOG_INFO("This example demonstrates how to create a custom module");
|
||||||
|
E2D_LOG_INFO("");
|
||||||
|
|
||||||
|
Application &app = Application::get();
|
||||||
|
|
||||||
|
AppConfig appConfig;
|
||||||
|
appConfig.appName = "HelloModule Example";
|
||||||
|
appConfig.appVersion = "1.0.0";
|
||||||
|
|
||||||
|
if (!app.init(appConfig)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize application");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("");
|
||||||
|
E2D_LOG_INFO("Application initialized successfully");
|
||||||
|
E2D_LOG_INFO("HelloModule should have been auto-registered and initialized");
|
||||||
|
E2D_LOG_INFO("");
|
||||||
|
|
||||||
|
auto scene = HelloScene::create();
|
||||||
|
app.enterScene(scene);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Starting main loop...");
|
||||||
|
E2D_LOG_INFO("Press ESC or close window to exit");
|
||||||
|
E2D_LOG_INFO("");
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Application shutting down...");
|
||||||
|
|
||||||
|
app.shutdown();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Application shutdown complete");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
26
xmake.lua
26
xmake.lua
|
|
@ -164,3 +164,29 @@ target("demo_basic")
|
||||||
-- 构建后安装Shader文件
|
-- 构建后安装Shader文件
|
||||||
after_build(install_shaders)
|
after_build(install_shaders)
|
||||||
target_end()
|
target_end()
|
||||||
|
|
||||||
|
-- Hello Module 示例 - 展示如何创建自定义模块
|
||||||
|
target("demo_hello_module")
|
||||||
|
set_kind("binary")
|
||||||
|
set_default(false)
|
||||||
|
|
||||||
|
add_deps("extra2d")
|
||||||
|
add_files("examples/hello_module/*.cpp")
|
||||||
|
add_includedirs("examples/hello_module")
|
||||||
|
|
||||||
|
-- 平台配置
|
||||||
|
local plat = get_config("plat") or os.host()
|
||||||
|
if plat == "mingw" or plat == "windows" then
|
||||||
|
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||||
|
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||||
|
elseif plat == "linux" then
|
||||||
|
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||||
|
add_syslinks("GL", "dl", "pthread")
|
||||||
|
elseif plat == "macosx" then
|
||||||
|
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||||
|
add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 构建后安装Shader文件
|
||||||
|
after_build(install_shaders)
|
||||||
|
target_end()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue