feat(switch): 添加 Nintendo Switch 平台支持

- 添加 Switch 平台专用的日志输出和初始化处理
- 修改窗口模块以支持 OpenGL ES 3.2 渲染
- 更新构建系统以支持 Switch 平台编译
- 添加 Switch 平台文件系统操作支持
- 移除 Vulkan 相关依赖,改用 OpenGL ES
- 更新插件加载器以适配 Switch 平台限制
This commit is contained in:
ChestnutYueyue 2026-03-01 03:48:51 +08:00
parent fb11f2a71e
commit 6717015e28
11 changed files with 4474 additions and 27686 deletions

View File

@ -20,9 +20,9 @@ using ResizeCb = std::function<void(int32 w, int32 h)>;
using CloseCb = std::function<void()>; using CloseCb = std::function<void()>;
/** /**
* @brief - Vulkan * @brief - OpenGL ES 3.2
* *
* SDL2 Vulkan * SDL2 OpenGL ES 3.2
* 使 Module * 使 Module
*/ */
class WindowModule : public Module { class WindowModule : public Module {
@ -122,18 +122,26 @@ public:
bool shouldClose() const { return shouldClose_; } bool shouldClose() const { return shouldClose_; }
/** /**
* @brief Vulkan * @brief OpenGL ES
* @return
*/
std::vector<const char *> getVulkanExtensions() const;
/**
* @brief Vulkan
* @param instance Vulkan
* @param surface
* @return * @return
*/ */
bool createVulkanSurface(void *instance, void **surface) const; bool createGLContext();
/**
* @brief OpenGL ES
*/
void destroyGLContext();
/**
* @brief
*/
void swapBuffers();
/**
* @brief OpenGL ES
* @return SDL_GLContext
*/
void* getGLContext() const;
private: private:
void handleWindowEvent(const SDL_WindowEvent &evt); void handleWindowEvent(const SDL_WindowEvent &evt);
@ -147,6 +155,7 @@ private:
void onModuleConfig(const AppConfig &config); void onModuleConfig(const AppConfig &config);
SDL_Window *window_ = nullptr; SDL_Window *window_ = nullptr;
SDL_GLContext glContext_ = nullptr;
bool shouldClose_ = false; bool shouldClose_ = false;
CloseCb onClose_; CloseCb onClose_;

View File

@ -6,6 +6,11 @@
#include <type_traits> #include <type_traits>
#include <types/base/types.h> #include <types/base/types.h>
// Switch 平台支持
#ifdef __SWITCH__
#include <switch.h>
#endif
#ifndef TEST_BUILD #ifndef TEST_BUILD
// SDL2 日志头文件(测试构建时不使用) // SDL2 日志头文件(测试构建时不使用)
#include <SDL.h> #include <SDL.h>

View File

@ -9,6 +9,11 @@
#include <direct.h> #include <direct.h>
#include <windows.h> #include <windows.h>
#define mkdir_impl(path, mode) _mkdir(path) #define mkdir_impl(path, mode) _mkdir(path)
#elif defined(__SWITCH__)
#include <dirent.h>
#include <unistd.h>
// Switch 使用 ::mkdir 避免与类成员函数冲突
#define mkdir_impl(path, mode) ::mkdir(path, mode)
#else #else
#include <dirent.h> #include <dirent.h>
#include <unistd.h> #include <unistd.h>

View File

@ -1,246 +1,278 @@
#include <platform/window_module.h>
#include <config/app_config.h> #include <config/app_config.h>
#include <event/events.h> #include <event/events.h>
#include <memory> #include <memory>
#include <platform/window_module.h>
#include <utils/logger.h> #include <utils/logger.h>
// SDL Vulkan support // OpenGL ES 3.2 with GLAD
#include <SDL_vulkan.h> #include <glad/glad.h>
// Vulkan
#include <vulkan/vulkan.h>
namespace extra2d { namespace extra2d {
WindowModule::WindowModule() = default; WindowModule::WindowModule() = default;
WindowModule::~WindowModule() { WindowModule::~WindowModule() { shutdown(); }
WindowModule::WindowModule(WindowModule &&other) noexcept
: window_(other.window_), glContext_(other.glContext_),
shouldClose_(other.shouldClose_), onClose_(std::move(other.onClose_)),
onResize_(std::move(other.onResize_)),
configListener_(std::move(other.configListener_)) {
other.window_ = nullptr;
other.glContext_ = nullptr;
}
WindowModule &WindowModule::operator=(WindowModule &&other) noexcept {
if (this != &other) {
shutdown(); shutdown();
} window_ = other.window_;
glContext_ = other.glContext_;
WindowModule::WindowModule(WindowModule&& other) noexcept shouldClose_ = other.shouldClose_;
: window_(other.window_) onClose_ = std::move(other.onClose_);
, shouldClose_(other.shouldClose_) onResize_ = std::move(other.onResize_);
, onClose_(std::move(other.onClose_)) configListener_ = std::move(other.configListener_);
, onResize_(std::move(other.onResize_))
, configListener_(std::move(other.configListener_)) {
other.window_ = nullptr; other.window_ = nullptr;
} other.glContext_ = nullptr;
}
WindowModule& WindowModule::operator=(WindowModule&& other) noexcept { return *this;
if (this != &other) {
shutdown();
window_ = other.window_;
shouldClose_ = other.shouldClose_;
onClose_ = std::move(other.onClose_);
onResize_ = std::move(other.onResize_);
configListener_ = std::move(other.configListener_);
other.window_ = nullptr;
}
return *this;
} }
bool WindowModule::init() { bool WindowModule::init() {
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError()); E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
return false; return false;
} }
// 监听模块配置事件 // 监听模块配置事件
configListener_ = std::make_unique<events::OnModuleConfig<AppConfig>::Listener>(); configListener_ =
configListener_->bind([this](const AppConfig& config) { std::make_unique<events::OnModuleConfig<AppConfig>::Listener>();
this->onModuleConfig(config); configListener_->bind(
}); [this](const AppConfig &config) { this->onModuleConfig(config); });
return true; return true;
} }
void WindowModule::shutdown() { void WindowModule::shutdown() {
if (window_) { destroyGLContext();
SDL_DestroyWindow(window_); if (window_) {
window_ = nullptr; SDL_DestroyWindow(window_);
} window_ = nullptr;
}
} }
bool WindowModule::create(const WindowCfg& cfg) { bool WindowModule::create(const WindowCfg &cfg) {
if (window_) if (window_)
return true;
uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN;
if (cfg.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (cfg.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
window_ = SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, cfg.width, cfg.height, flags);
if (!window_) {
E2D_LOG_ERROR("Failed to create window: {}", SDL_GetError());
return false;
}
// 发送窗口显示事件
events::OnShow::emit();
E2D_LOG_INFO("Window created: {}x{}", cfg.width, cfg.height);
return true; return true;
uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;
if (cfg.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (cfg.fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
window_ =
SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, cfg.width, cfg.height, flags);
if (!window_) {
E2D_LOG_ERROR("Failed to create window: {}", SDL_GetError());
return false;
}
// 发送窗口显示事件
events::OnShow::emit();
E2D_LOG_INFO("Window created: {}x{}", cfg.width, cfg.height);
return true;
} }
bool WindowModule::pollEvents() { bool WindowModule::pollEvents() {
SDL_Event evt; SDL_Event evt;
while (SDL_PollEvent(&evt)) { while (SDL_PollEvent(&evt)) {
switch (evt.type) { switch (evt.type) {
case SDL_QUIT: case SDL_QUIT:
shouldClose_ = true; shouldClose_ = true;
// 发送窗口关闭事件 // 发送窗口关闭事件
events::OnClose::emit(); events::OnClose::emit();
if (onClose_) if (onClose_)
onClose_(); onClose_();
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
handleWindowEvent(evt.window); handleWindowEvent(evt.window);
break; break;
default: default:
// 输入事件由 InputModule 处理 // 输入事件由 InputModule 处理
break; break;
}
} }
return !shouldClose_; }
#ifdef __SWITCH__
// Switch 平台:每帧更新控制台,确保日志实时显示
consoleUpdate(NULL);
#endif
return !shouldClose_;
} }
void WindowModule::handleWindowEvent(const SDL_WindowEvent& evt) { void WindowModule::handleWindowEvent(const SDL_WindowEvent &evt) {
switch (evt.event) { switch (evt.event) {
case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_RESIZED:
// 发送窗口大小改变事件 // 发送窗口大小改变事件
events::OnResize::emit(evt.data1, evt.data2); events::OnResize::emit(evt.data1, evt.data2);
if (onResize_) { if (onResize_) {
onResize_(evt.data1, evt.data2); onResize_(evt.data1, evt.data2);
}
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
// 发送窗口获得焦点事件
events::OnFocus::emit();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
// 发送窗口失去焦点事件
events::OnBlur::emit();
break;
case SDL_WINDOWEVENT_SHOWN:
// 发送窗口显示事件
events::OnShow::emit();
break;
case SDL_WINDOWEVENT_HIDDEN:
// 发送窗口隐藏事件
events::OnHide::emit();
break;
case SDL_WINDOWEVENT_CLOSE:
shouldClose_ = true;
// 发送窗口关闭事件
events::OnClose::emit();
if (onClose_)
onClose_();
break;
} }
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
// 发送窗口获得焦点事件
events::OnFocus::emit();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
// 发送窗口失去焦点事件
events::OnBlur::emit();
break;
case SDL_WINDOWEVENT_SHOWN:
// 发送窗口显示事件
events::OnShow::emit();
break;
case SDL_WINDOWEVENT_HIDDEN:
// 发送窗口隐藏事件
events::OnHide::emit();
break;
case SDL_WINDOWEVENT_CLOSE:
shouldClose_ = true;
// 发送窗口关闭事件
events::OnClose::emit();
if (onClose_)
onClose_();
break;
}
} }
void WindowModule::onModuleConfig(const AppConfig &config) { void WindowModule::onModuleConfig(const AppConfig &config) {
WindowCfg cfg; WindowCfg cfg;
cfg.title = config.title; cfg.title = config.title;
cfg.width = config.width; cfg.width = config.width;
cfg.height = config.height; cfg.height = config.height;
cfg.fullscreen = config.fullscreen; cfg.fullscreen = config.fullscreen;
cfg.resizable = config.resizable; cfg.resizable = config.resizable;
if (create(cfg)) { if (create(cfg)) {
setVisible(true); setVisible(true);
} }
} }
Size WindowModule::getSize() const { Size WindowModule::getSize() const {
if (!window_) if (!window_)
return Size(0, 0); return Size(0, 0);
int w, h; int w, h;
SDL_GetWindowSize(window_, &w, &h); SDL_GetWindowSize(window_, &w, &h);
return Size(w, h); return Size(w, h);
} }
Vec2 WindowModule::getPosition() const { Vec2 WindowModule::getPosition() const {
if (!window_) if (!window_)
return Vec2(0, 0); return Vec2(0, 0);
int x, y; int x, y;
SDL_GetWindowPosition(window_, &x, &y); SDL_GetWindowPosition(window_, &x, &y);
return Vec2(static_cast<float>(x), static_cast<float>(y)); return Vec2(static_cast<float>(x), static_cast<float>(y));
} }
void WindowModule::setSize(int32 w, int32 h) { void WindowModule::setSize(int32 w, int32 h) {
if (window_) { if (window_) {
SDL_SetWindowSize(window_, w, h); SDL_SetWindowSize(window_, w, h);
} }
} }
void WindowModule::setTitle(const std::string& title) { void WindowModule::setTitle(const std::string &title) {
if (window_) { if (window_) {
SDL_SetWindowTitle(window_, title.c_str()); SDL_SetWindowTitle(window_, title.c_str());
} }
} }
void WindowModule::setFullscreen(bool fullscreen) { void WindowModule::setFullscreen(bool fullscreen) {
if (window_) { if (window_) {
SDL_SetWindowFullscreen(window_, SDL_SetWindowFullscreen(window_,
fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
} }
} }
bool WindowModule::isFullscreen() const { bool WindowModule::isFullscreen() const {
if (!window_) if (!window_)
return false; return false;
uint32 flags = SDL_GetWindowFlags(window_); uint32 flags = SDL_GetWindowFlags(window_);
return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
} }
void WindowModule::setVisible(bool visible) { void WindowModule::setVisible(bool visible) {
if (window_) { if (window_) {
if (visible) { if (visible) {
SDL_ShowWindow(window_); SDL_ShowWindow(window_);
// 发送窗口显示事件 // 发送窗口显示事件
events::OnShow::emit(); events::OnShow::emit();
} else { } else {
SDL_HideWindow(window_); SDL_HideWindow(window_);
// 发送窗口隐藏事件 // 发送窗口隐藏事件
events::OnHide::emit(); events::OnHide::emit();
}
} }
}
} }
bool WindowModule::isVisible() const { bool WindowModule::isVisible() const {
if (!window_) if (!window_)
return false; return false;
uint32 flags = SDL_GetWindowFlags(window_); uint32 flags = SDL_GetWindowFlags(window_);
return (flags & SDL_WINDOW_SHOWN) != 0; return (flags & SDL_WINDOW_SHOWN) != 0;
} }
std::vector<const char*> WindowModule::getVulkanExtensions() const { bool WindowModule::createGLContext() {
std::vector<const char*> extensions; if (!window_) {
if (!window_) { return false;
return extensions; }
}
uint32 count = 0; // 创建 OpenGL ES 3.2 上下文
SDL_Vulkan_GetInstanceExtensions(window_, &count, nullptr); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
extensions.resize(count); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_Vulkan_GetInstanceExtensions(window_, &count, extensions.data()); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
return extensions; glContext_ = SDL_GL_CreateContext(window_);
if (!glContext_) {
E2D_LOG_ERROR("Failed to create OpenGL ES context: {}", SDL_GetError());
return false;
}
// 初始化 GLAD
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
E2D_LOG_ERROR("Failed to initialize GLAD");
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
return false;
}
E2D_LOG_INFO("OpenGL ES context created: {}.{}", GLVersion.major,
GLVersion.minor);
return true;
} }
bool WindowModule::createVulkanSurface(void* instance, void** surface) const { void WindowModule::destroyGLContext() {
if (!window_ || !instance || !surface) { if (glContext_) {
return false; SDL_GL_DeleteContext(glContext_);
} glContext_ = nullptr;
return SDL_Vulkan_CreateSurface(window_, static_cast<VkInstance>(instance), reinterpret_cast<VkSurfaceKHR*>(surface)); }
} }
void WindowModule::swapBuffers() {
if (window_) {
SDL_GL_SwapWindow(window_);
}
}
void *WindowModule::getGLContext() const { return glContext_; }
} // namespace extra2d } // namespace extra2d

View File

@ -1,12 +1,13 @@
#include <plugin/plugin_loader.h>
#include <event/event_bus.h> #include <event/event_bus.h>
#include <algorithm> #include <plugin/plugin_loader.h>
#include <queue> #include <queue>
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#elif defined(__SWITCH__)
// Nintendo Switch 平台使用 libnx不支持传统动态库加载
#else #else
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
namespace extra2d { namespace extra2d {
@ -14,335 +15,352 @@ namespace extra2d {
PluginLoader::PluginLoader() = default; PluginLoader::PluginLoader() = default;
PluginLoader::~PluginLoader() { PluginLoader::~PluginLoader() {
if (inited_) { if (inited_) {
shutdownAll(); shutdownAll();
} }
// 卸载所有动态库插件 // 卸载所有动态库插件
for (auto& pair : plugins_) { for (auto &pair : plugins_) {
if (pair.second.isDynamic && pair.second.handle) { if (pair.second.isDynamic && pair.second.handle) {
if (pair.second.plugin) { if (pair.second.plugin) {
pair.second.plugin->unload(); pair.second.plugin->unload();
if (pair.second.owned) { if (pair.second.owned) {
delete pair.second.plugin; delete pair.second.plugin;
}
}
unloadDynamicLibrary(pair.second.handle);
} }
}
unloadDynamicLibrary(pair.second.handle);
} }
}
} }
PluginLoader::PluginLoader(PluginLoader&&) noexcept = default; PluginLoader::PluginLoader(PluginLoader &&) noexcept = default;
PluginLoader& PluginLoader::operator=(PluginLoader&&) noexcept = default; PluginLoader &PluginLoader::operator=(PluginLoader &&) noexcept = default;
bool PluginLoader::loadFromLibrary(const char* path) { bool PluginLoader::loadFromLibrary(const char *path) {
if (!path) { if (!path) {
return false; return false;
}
// 加载动态库
void *handle = loadDynamicLibrary(path);
if (!handle) {
return false;
}
// 获取创建函数
using CreateFunc = IPlugin *(*)();
CreateFunc createFunc =
reinterpret_cast<CreateFunc>(getSymbol(handle, "extra2d_create_plugin"));
if (!createFunc) {
unloadDynamicLibrary(handle);
return false;
}
// 创建插件实例
IPlugin *plugin = createFunc();
if (!plugin) {
unloadDynamicLibrary(handle);
return false;
}
// 检查是否已存在
const char *name = plugin->getInfo().name.c_str();
if (hasPlugin(name)) {
delete plugin;
unloadDynamicLibrary(handle);
return false;
}
// 添加到插件列表
PluginEntry entry;
entry.plugin = plugin;
entry.handle = handle;
entry.isDynamic = true;
entry.owned = true;
plugins_[name] = entry;
sortedPlugins_.clear();
// 插件加载事件(暂不发送,避免依赖 events.h
// event::broadcast<events::OnPluginLoaded>(name,
// plugin->getInfo().version.c_str());
// 如果已经初始化,立即加载插件
if (inited_) {
if (!plugin->load()) {
unloadPlugin(name);
return false;
} }
}
// 加载动态库 return true;
void* handle = loadDynamicLibrary(path);
if (!handle) {
return false;
}
// 获取创建函数
using CreateFunc = IPlugin* (*)();
CreateFunc createFunc = reinterpret_cast<CreateFunc>(
getSymbol(handle, "extra2d_create_plugin")
);
if (!createFunc) {
unloadDynamicLibrary(handle);
return false;
}
// 创建插件实例
IPlugin* plugin = createFunc();
if (!plugin) {
unloadDynamicLibrary(handle);
return false;
}
// 检查是否已存在
const char* name = plugin->getInfo().name.c_str();
if (hasPlugin(name)) {
delete plugin;
unloadDynamicLibrary(handle);
return false;
}
// 添加到插件列表
PluginEntry entry;
entry.plugin = plugin;
entry.handle = handle;
entry.isDynamic = true;
entry.owned = true;
plugins_[name] = entry;
sortedPlugins_.clear();
// 插件加载事件(暂不发送,避免依赖 events.h
// event::broadcast<events::OnPluginLoaded>(name, plugin->getInfo().version.c_str());
// 如果已经初始化,立即加载插件
if (inited_) {
if (!plugin->load()) {
unloadPlugin(name);
return false;
}
}
return true;
} }
void PluginLoader::registerPlugin(IPlugin* plugin) { void PluginLoader::registerPlugin(IPlugin *plugin) {
if (!plugin) { if (!plugin) {
return; return;
} }
const char* name = plugin->getInfo().name.c_str(); const char *name = plugin->getInfo().name.c_str();
if (!name) { if (!name) {
return; return;
} }
// 如果已存在同名插件,先卸载旧的 // 如果已存在同名插件,先卸载旧的
if (hasPlugin(name)) { if (hasPlugin(name)) {
unloadPlugin(name); unloadPlugin(name);
} }
PluginEntry entry; PluginEntry entry;
entry.plugin = plugin; entry.plugin = plugin;
entry.handle = nullptr; entry.handle = nullptr;
entry.isDynamic = false; entry.isDynamic = false;
entry.owned = false; entry.owned = false;
plugins_[name] = entry; plugins_[name] = entry;
sortedPlugins_.clear(); sortedPlugins_.clear();
// 插件加载事件(暂不发送,避免依赖 events.h // 插件加载事件(暂不发送,避免依赖 events.h
// event::broadcast<events::OnPluginLoaded>(name, plugin->getInfo().version.c_str()); // event::broadcast<events::OnPluginLoaded>(name,
// plugin->getInfo().version.c_str());
// 如果已经初始化,立即加载插件 // 如果已经初始化,立即加载插件
if (inited_) { if (inited_) {
plugin->load(); plugin->load();
} }
} }
void PluginLoader::unloadPlugin(const char* name) { void PluginLoader::unloadPlugin(const char *name) {
if (!name) { if (!name) {
return; return;
} }
auto it = plugins_.find(name); auto it = plugins_.find(name);
if (it == plugins_.end()) { if (it == plugins_.end()) {
return; return;
} }
PluginEntry& entry = it->second; PluginEntry &entry = it->second;
// 如果已初始化,先卸载 // 如果已初始化,先卸载
if (inited_ && entry.plugin) { if (inited_ && entry.plugin) {
entry.plugin->unload(); entry.plugin->unload();
} }
// 如果是动态加载且由加载器管理,删除实例 // 如果是动态加载且由加载器管理,删除实例
if (entry.isDynamic && entry.owned && entry.plugin) { if (entry.isDynamic && entry.owned && entry.plugin) {
delete entry.plugin; delete entry.plugin;
} }
// 卸载动态库 // 卸载动态库
if (entry.handle) { if (entry.handle) {
unloadDynamicLibrary(entry.handle); unloadDynamicLibrary(entry.handle);
} }
plugins_.erase(it); plugins_.erase(it);
sortedPlugins_.clear(); sortedPlugins_.clear();
} }
IPlugin* PluginLoader::getPlugin(const char* name) const { IPlugin *PluginLoader::getPlugin(const char *name) const {
if (!name) { if (!name) {
return nullptr; return nullptr;
} }
auto it = plugins_.find(name); auto it = plugins_.find(name);
return it != plugins_.end() ? it->second.plugin : nullptr; return it != plugins_.end() ? it->second.plugin : nullptr;
} }
bool PluginLoader::hasPlugin(const char* name) const { bool PluginLoader::hasPlugin(const char *name) const {
if (!name) { if (!name) {
return false; return false;
} }
return plugins_.find(name) != plugins_.end(); return plugins_.find(name) != plugins_.end();
} }
std::vector<IPlugin*> PluginLoader::getAllPlugins() const { std::vector<IPlugin *> PluginLoader::getAllPlugins() const {
std::vector<IPlugin*> result; std::vector<IPlugin *> result;
for (const auto& pair : plugins_) { for (const auto &pair : plugins_) {
if (pair.second.plugin) { if (pair.second.plugin) {
result.push_back(pair.second.plugin); result.push_back(pair.second.plugin);
}
} }
return result; }
return result;
} }
bool PluginLoader::initAll() { bool PluginLoader::initAll() {
if (inited_) { if (inited_) {
return true;
}
// 检查所有插件的依赖是否满足
for (const auto& pair : plugins_) {
if (pair.second.plugin && !checkDependencies(pair.second.plugin->getDependencies())) {
return false;
}
}
// 按依赖顺序排序
sortPluginsByDependencies();
// 加载所有插件
for (auto* plugin : sortedPlugins_) {
if (plugin && !plugin->load()) {
// 加载失败,卸载已加载的插件
shutdownAll();
return false;
}
}
inited_ = true;
return true; return true;
}
// 检查所有插件的依赖是否满足
for (const auto &pair : plugins_) {
if (pair.second.plugin &&
!checkDependencies(pair.second.plugin->getDependencies())) {
return false;
}
}
// 按依赖顺序排序
sortPluginsByDependencies();
// 加载所有插件
for (auto *plugin : sortedPlugins_) {
if (plugin && !plugin->load()) {
// 加载失败,卸载已加载的插件
shutdownAll();
return false;
}
}
inited_ = true;
return true;
} }
void PluginLoader::shutdownAll() { void PluginLoader::shutdownAll() {
if (!inited_) { if (!inited_) {
return; return;
} }
// 按依赖逆序卸载 // 按依赖逆序卸载
for (auto it = sortedPlugins_.rbegin(); it != sortedPlugins_.rend(); ++it) { for (auto it = sortedPlugins_.rbegin(); it != sortedPlugins_.rend(); ++it) {
if (*it) { if (*it) {
(*it)->unload(); (*it)->unload();
}
} }
}
inited_ = false; inited_ = false;
} }
size_t PluginLoader::getPluginCount() const { size_t PluginLoader::getPluginCount() const { return plugins_.size(); }
return plugins_.size();
void PluginLoader::addSearchPath(const char *path) {
if (path) {
searchPaths_.push_back(path);
}
} }
void PluginLoader::addSearchPath(const char* path) { size_t PluginLoader::loadPluginsFromDirectory(const char *directory) {
if (path) { // TODO: 实现目录扫描加载
searchPaths_.push_back(path); // 需要平台相关的文件系统操作
return 0;
}
bool PluginLoader::resolveDependencies(IPlugin *plugin) {
if (!plugin) {
return false;
}
return checkDependencies(plugin->getDependencies());
}
bool PluginLoader::checkDependencies(
const std::vector<std::string> &dependencies) {
for (const auto &dep : dependencies) {
if (!hasPlugin(dep.c_str())) {
return false;
} }
} }
return true;
size_t PluginLoader::loadPluginsFromDirectory(const char* directory) {
// TODO: 实现目录扫描加载
// 需要平台相关的文件系统操作
return 0;
}
bool PluginLoader::resolveDependencies(IPlugin* plugin) {
if (!plugin) {
return false;
}
return checkDependencies(plugin->getDependencies());
}
bool PluginLoader::checkDependencies(const std::vector<std::string>& dependencies) {
for (const auto& dep : dependencies) {
if (!hasPlugin(dep.c_str())) {
return false;
}
}
return true;
} }
void PluginLoader::sortPluginsByDependencies() { void PluginLoader::sortPluginsByDependencies() {
if (!sortedPlugins_.empty()) { if (!sortedPlugins_.empty()) {
return; return;
}
// 拓扑排序处理依赖关系
std::unordered_map<std::string, int> inDegree;
std::unordered_map<std::string, std::vector<std::string>> graph;
// 初始化
for (const auto &pair : plugins_) {
inDegree[pair.first] = 0;
}
// 构建图
for (const auto &pair : plugins_) {
if (!pair.second.plugin)
continue;
for (const auto &dep : pair.second.plugin->getDependencies()) {
graph[dep].push_back(pair.first);
inDegree[pair.first]++;
} }
}
// 拓扑排序处理依赖关系 // 拓扑排序
std::unordered_map<std::string, int> inDegree; std::queue<std::string> q;
std::unordered_map<std::string, std::vector<std::string>> graph; for (const auto &pair : inDegree) {
if (pair.second == 0) {
// 初始化 q.push(pair.first);
for (const auto& pair : plugins_) {
inDegree[pair.first] = 0;
} }
}
// 构建图 std::vector<std::string> sortedNames;
for (const auto& pair : plugins_) { while (!q.empty()) {
if (!pair.second.plugin) continue; std::string name = q.front();
q.pop();
sortedNames.push_back(name);
for (const auto& dep : pair.second.plugin->getDependencies()) { for (const auto &next : graph[name]) {
graph[dep].push_back(pair.first); inDegree[next]--;
inDegree[pair.first]++; if (inDegree[next] == 0) {
} q.push(next);
}
} }
}
// 拓扑排序 // 构建排序后的插件列表
std::queue<std::string> q; sortedPlugins_.clear();
for (const auto& pair : inDegree) { for (const auto &name : sortedNames) {
if (pair.second == 0) { auto it = plugins_.find(name);
q.push(pair.first); if (it != plugins_.end() && it->second.plugin) {
} sortedPlugins_.push_back(it->second.plugin);
}
std::vector<std::string> sortedNames;
while (!q.empty()) {
std::string name = q.front();
q.pop();
sortedNames.push_back(name);
for (const auto& next : graph[name]) {
inDegree[next]--;
if (inDegree[next] == 0) {
q.push(next);
}
}
}
// 构建排序后的插件列表
sortedPlugins_.clear();
for (const auto& name : sortedNames) {
auto it = plugins_.find(name);
if (it != plugins_.end() && it->second.plugin) {
sortedPlugins_.push_back(it->second.plugin);
}
} }
}
} }
void* PluginLoader::loadDynamicLibrary(const char* path) { void *PluginLoader::loadDynamicLibrary(const char *path) {
#ifdef _WIN32 #ifdef _WIN32
return LoadLibraryA(path); return LoadLibraryA(path);
#elif defined(__SWITCH__)
// Nintendo Switch 平台不支持动态库加载
(void)path;
return nullptr;
#else #else
return dlopen(path, RTLD_LAZY); return dlopen(path, RTLD_LAZY);
#endif #endif
} }
void PluginLoader::unloadDynamicLibrary(void* handle) { void PluginLoader::unloadDynamicLibrary(void *handle) {
if (!handle) return; if (!handle)
return;
#ifdef _WIN32 #ifdef _WIN32
FreeLibrary(static_cast<HMODULE>(handle)); FreeLibrary(static_cast<HMODULE>(handle));
#elif defined(__SWITCH__)
// Nintendo Switch 平台不支持动态库加载
(void)handle;
#else #else
dlclose(handle); dlclose(handle);
#endif #endif
} }
void* PluginLoader::getSymbol(void* handle, const char* name) { void *PluginLoader::getSymbol(void *handle, const char *name) {
if (!handle || !name) return nullptr; if (!handle || !name)
return nullptr;
#ifdef _WIN32 #ifdef _WIN32
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name)); return reinterpret_cast<void *>(
GetProcAddress(static_cast<HMODULE>(handle), name));
#elif defined(__SWITCH__)
// Nintendo Switch 平台不支持动态库加载
(void)handle;
(void)name;
return nullptr;
#else #else
return dlsym(handle, name); return dlsym(handle, name);
#endif #endif
} }

View File

@ -9,6 +9,25 @@ bool Logger::consoleOutput_ = true;
bool Logger::fileOutput_ = false; bool Logger::fileOutput_ = false;
std::string Logger::logFile_; std::string Logger::logFile_;
// Switch 平台:自定义 SDL 日志输出函数
#ifdef __SWITCH__
/**
* @brief Switch
* @param userdata
* @param category
* @param priority
* @param message
*/
static void SwitchLogOutput(void *userdata, int category,
SDL_LogPriority priority, const char *message) {
(void)userdata;
(void)category;
(void)priority;
// 输出到 Switch 控制台
printf("%s\n", message);
}
#endif
/** /**
* @brief * @brief
* @param level * @param level
@ -41,11 +60,17 @@ void Logger::init() {
return; return;
} }
// 设置 SDL 日志级别为详细模式(允许所有级别的日志) #ifdef __SWITCH__
// Switch 平台:初始化控制台并设置 SDL 日志重定向
consoleInit(NULL);
SDL_LogSetOutputFunction(SwitchLogOutput, nullptr);
#else
// 其他平台:设置 SDL 日志级别为详细模式
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE); SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
#endif
initialized_ = true; initialized_ = true;
log(LogLevel::Info, "Logger initialized with SDL2"); log(LogLevel::Info, "Logger initialized");
} }
/** /**
@ -54,6 +79,9 @@ void Logger::init() {
void Logger::shutdown() { void Logger::shutdown() {
if (initialized_) { if (initialized_) {
log(LogLevel::Info, "Logger shutting down"); log(LogLevel::Info, "Logger shutting down");
#ifdef __SWITCH__
consoleExit(NULL);
#endif
} }
initialized_ = false; initialized_ = false;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -78,7 +78,7 @@ end
-- ============================================== -- ==============================================
if target_plat == "mingw" then if target_plat == "mingw" then
add_requires("glm", "libsdl2", "libsdl2_mixer", "vulkansdk") add_requires("glm", "libsdl2", "libsdl2_mixer")
end end
-- ============================================== -- ==============================================

View File

@ -13,24 +13,27 @@ function define_extra2d_engine()
target("extra2d") target("extra2d")
set_kind("static") set_kind("static")
add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp|platform/window.cpp|platform/input.cpp") add_files("src/**.cpp")
add_files("third_party/glad/src/glad.c")
add_includedirs("include", {public = true}) add_includedirs("include", {public = true})
add_includedirs("third_party", {public = true}) add_includedirs("third_party", {public = true})
add_includedirs("third_party/glad/include", {public = true})
local plat = get_current_plat() local plat = get_current_plat()
if plat == "mingw" then if plat == "mingw" then
add_defines("_UNICODE", "UNICODE") add_defines("_UNICODE", "UNICODE")
-- 使用 xmake 官方包 -- 使用 xmake 官方包
add_packages("glm", "libsdl2", "libsdl2_mixer", "vulkansdk", {public = true}) add_packages("glm", "libsdl2", "libsdl2_mixer", {public = true})
add_syslinks("winmm", "imm32", "version", "setupapi", {public = true}) add_syslinks("winmm", "imm32", "version", "setupapi", {public = true})
elseif plat == "switch" then elseif plat == "switch" then
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true}) add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
add_linkdirs(devkitPro .. "/portlibs/switch/lib") add_linkdirs(devkitPro .. "/portlibs/switch/lib")
-- Switch 平台可能需要不同的图形库 -- Switch 平台使用 OpenGL ES + EGL + Mesa 驱动
-- 注意:链接顺序很重要,被依赖的库要放在后面
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
"modplug", "mpg123", "FLAC", "drm_nouveau", "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
{public = true}) {public = true})
end end

View File

@ -34,7 +34,7 @@ function define_switch_toolchain()
add_includedirs(path.join(devkitPro, "libnx/include")) add_includedirs(path.join(devkitPro, "libnx/include"))
add_linkdirs(path.join(devkitPro, "libnx/lib")) add_linkdirs(path.join(devkitPro, "libnx/lib"))
-- portlibs 路径(EGL + 桌面 OpenGL + SDL2 -- portlibs 路径(OpenGL ES + EGL + SDL2
add_includedirs(path.join(devkitPro, "portlibs/switch/include")) add_includedirs(path.join(devkitPro, "portlibs/switch/include"))
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2")) add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))