From 8abf58e3d59c3b2f71879dcc56c357a0dd4c9335 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Fri, 27 Feb 2026 22:59:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0SDL2=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=94=AF=E6=8C=81=E5=8F=8A=E6=A0=B8=E5=BF=83=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现SDL2平台初始化、窗口管理、输入处理和文件系统服务 重构服务管理机制,支持暂停/恢复状态 添加事件总线系统,实现引擎事件分发 优化应用主循环和更新机制 完善跨平台文件操作接口 --- include/app/application.h | 74 +++++- include/core/event/event_bus.h | 177 ++++++++++++ include/core/event/event_bus_macros.h | 112 ++++++++ include/core/event/events.h | 194 ++++++++++++++ include/core/scheduler.h | 6 +- include/core/service.h | 22 +- include/extra2d.h | 6 + include/platform/file.h | 156 +++++++++++ include/platform/input.h | 369 ++++++++++++++++++++++++++ include/platform/sdl2.h | 59 ++++ include/platform/window.h | 161 +++++++++++ src/app/application.cpp | 66 ++++- src/core/event/event_bus.cpp | 96 +++++++ src/core/service.cpp | 39 ++- src/platform/file.cpp | 214 +++++++++++++++ src/platform/input.cpp | 365 +++++++++++++++++++++++++ src/platform/sdl2.cpp | 66 +++++ src/platform/window.cpp | 177 ++++++++++++ xmake/engine.lua | 4 +- 19 files changed, 2339 insertions(+), 24 deletions(-) create mode 100644 include/core/event/event_bus.h create mode 100644 include/core/event/event_bus_macros.h create mode 100644 include/core/event/events.h create mode 100644 include/platform/file.h create mode 100644 include/platform/input.h create mode 100644 include/platform/sdl2.h create mode 100644 include/platform/window.h create mode 100644 src/core/event/event_bus.cpp create mode 100644 src/platform/file.cpp create mode 100644 src/platform/input.cpp create mode 100644 src/platform/sdl2.cpp create mode 100644 src/platform/window.cpp diff --git a/include/app/application.h b/include/app/application.h index 028ebaf..7f369ac 100644 --- a/include/app/application.h +++ b/include/app/application.h @@ -5,17 +5,28 @@ namespace extra2d { -enum class PlatformType { Auto = 0, PC, Switch }; +/** + * @brief 平台类型枚举 + */ +enum class PlatformType { + Auto = 0, + PC, + Switch +}; +/** + * @brief 应用程序配置 + */ struct AppConfig { std::string title = "Extra2D Application"; - int width = 800; - int height = 600; + int32 width = 1280; + int32 height = 720; bool fullscreen = false; bool resizable = true; bool vsync = true; - int fpsLimit = 0; - int msaaSamples = 0; + int32 fpsLimit = 0; + int32 glMajor = 3; + int32 glMinor = 3; PlatformType platform = PlatformType::Auto; bool enableCursors = true; bool enableDpiScale = false; @@ -28,25 +39,72 @@ struct AppConfig { */ class Application { public: + /** + * @brief 获取单例实例 + */ static Application& instance(); Application(const Application&) = delete; Application& operator=(const Application&) = delete; + /** + * @brief 初始化应用程序 + */ bool init(const AppConfig& config); + + /** + * @brief 关闭应用程序 + */ void shutdown(); + + /** + * @brief 运行主循环 + */ void run(); + + /** + * @brief 请求退出 + */ void quit(); + /** + * @brief 暂停应用 + */ void pause(); + + /** + * @brief 恢复应用 + */ void resume(); + + /** + * @brief 是否暂停 + */ bool isPaused() const { return paused_; } + + /** + * @brief 是否运行中 + */ bool isRunning() const { return running_; } + /** + * @brief 获取帧时间 + */ float deltaTime() const { return deltaTime_; } + + /** + * @brief 获取总运行时间 + */ float totalTime() const { return totalTime_; } - int fps() const { return currentFps_; } + + /** + * @brief 获取当前 FPS + */ + int32 fps() const { return currentFps_; } + /** + * @brief 获取配置 + */ const AppConfig& getConfig() const { return config_; } private: @@ -66,9 +124,9 @@ private: float deltaTime_ = 0.0f; float totalTime_ = 0.0f; double lastFrameTime_ = 0.0; - int frameCount_ = 0; + int32 frameCount_ = 0; float fpsTimer_ = 0.0f; - int currentFps_ = 0; + int32 currentFps_ = 0; }; #define APP extra2d::Application::instance() diff --git a/include/core/event/event_bus.h b/include/core/event/event_bus.h new file mode 100644 index 0000000..ee6e076 --- /dev/null +++ b/include/core/event/event_bus.h @@ -0,0 +1,177 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d::event { + +/** + * @brief 监听器链表节点 + */ +struct ListenerEntry { + ListenerEntry* next = nullptr; + ListenerEntry* prev = nullptr; + class ListenerBase* listener = nullptr; +}; + +/** + * @brief 监听器基类 + */ +class ListenerBase { +protected: + ListenerEntry* entry_ = nullptr; + friend class ListenerContainer; +}; + +/** + * @brief 监听器容器,管理同一事件的所有监听器 + */ +class ListenerContainer { +public: + ListenerContainer() = default; + ~ListenerContainer(); + + template + bool broadcast(Args&&... args); + +protected: + ListenerEntry* listenerList_ = nullptr; + ListenerEntry* listenersToAdd_ = nullptr; + std::vector listenersToRemove_; + int broadcasting_ = 0; + + void addListener(ListenerBase* listener); + void removeListener(ListenerBase* listener); + void processPendingListeners(); + bool hasPendingListeners() const; + + template + friend class Listener; +}; + +/** + * @brief 事件处理器数据库,每个事件类型一个容器 + */ +template +class ListenerDB { +public: + static ListenerContainer* container() { + static ListenerContainer* ctn = new ListenerContainer(); + return ctn; + } +}; + +/** + * @brief 事件特征定义 + */ +template +struct EventTrait { + using Bus = BusT; + using ArgTuple = std::tuple; + static constexpr size_t ARG_COUNT = sizeof...(Args); +}; + +/** + * @brief 元组提取器 + */ +template +struct TupleExtractor { + using FuncType = void(); + using StdFuncType = std::function; +}; + +template +struct TupleExtractor> { + using FuncType = void(Args...); + using StdFuncType = std::function; +}; + +/** + * @brief 监听器模板类 + * + * 构造时自动注册,析构时自动注销 + */ +template +class Listener : public ListenerBase { +public: + using ArgTuple = typename EHandler::ArgTuple; + using StdFuncType = typename TupleExtractor::StdFuncType; + + Listener(); + ~Listener(); + + template + void bind(Fn&& func) { + callback_ = std::forward(func); + } + + void enable() { enabled_ = true; } + void disable() { enabled_ = false; } + bool isEnabled() const { return enabled_; } + void reset() { callback_ = nullptr; } + + template + void invoke(Args&&... args) { + if (callback_ && enabled_) { + callback_(std::forward(args)...); + } + } + + const char* busName() const { return EHandler::BUS_NAME; } + const char* eventName() const { return EHandler::NAME; } + +private: + bool enabled_ = true; + StdFuncType callback_; +}; + +template +Listener::Listener() { + entry_ = new ListenerEntry(); + entry_->listener = this; + ListenerDB::container()->addListener(this); +} + +template +Listener::~Listener() { + ListenerDB::container()->removeListener(this); +} + +#define EVENT_LIST_LOOP_BEGIN(curr, list) \ + for (ListenerEntry* curr = list; curr != nullptr; curr = curr->next) { + +#define EVENT_LIST_LOOP_END(curr, list) } + +template +bool ListenerContainer::broadcast(Args&&... args) { + broadcasting_++; + + EVENT_LIST_LOOP_BEGIN(curr, listenerList_) + if (curr->listener) { + static_cast*>(curr->listener)->invoke(std::forward(args)...); + } + EVENT_LIST_LOOP_END(curr, listenerList_) + + broadcasting_--; + + if (!broadcasting_ && hasPendingListeners()) { + processPendingListeners(); + } + + return true; +} + +/** + * @brief 广播事件 + */ +template +void broadcast(Args&&... args) { + static_assert(sizeof...(Args) == EHandler::ARG_COUNT, "Parameter count incorrect"); + auto* container = ListenerDB::container(); + container->template broadcast(std::forward(args)...); +} + +} // namespace extra2d::event diff --git a/include/core/event/event_bus_macros.h b/include/core/event/event_bus_macros.h new file mode 100644 index 0000000..a067be3 --- /dev/null +++ b/include/core/event/event_bus_macros.h @@ -0,0 +1,112 @@ +#pragma once + +#include + +#define E2D_EVENT_BUS_NAME_(n) n##_Bus + +/** + * @brief 声明事件总线 + */ +#define DECLARE_EVENT_BUS(BusName) \ + struct BusName##_Bus { \ + static constexpr const char* NAME = #BusName; \ + }; + +/** + * @brief 声明无参数事件 + */ +#define DECLARE_EVENT_0(EventName, BusName) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit() { ::extra2d::event::broadcast(); } \ + }; + +/** + * @brief 声明单参数事件 + */ +#define DECLARE_EVENT_1(EventName, BusName, Arg0) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0) { ::extra2d::event::broadcast(arg0); } \ + }; + +/** + * @brief 声明双参数事件 + */ +#define DECLARE_EVENT_2(EventName, BusName, Arg0, Arg1) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1) { ::extra2d::event::broadcast(arg0, arg1); } \ + }; + +/** + * @brief 声明三参数事件 + */ +#define DECLARE_EVENT_3(EventName, BusName, Arg0, Arg1, Arg2) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2) { ::extra2d::event::broadcast(arg0, arg1, arg2); } \ + }; + +/** + * @brief 声明四参数事件 + */ +#define DECLARE_EVENT_4(EventName, BusName, Arg0, Arg1, Arg2, Arg3) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3) { ::extra2d::event::broadcast(arg0, arg1, arg2, arg3); } \ + }; + +/** + * @brief 声明五参数事件 + */ +#define DECLARE_EVENT_5(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { ::extra2d::event::broadcast(arg0, arg1, arg2, arg3, arg4); } \ + }; + +/** + * @brief 声明六参数事件 + */ +#define DECLARE_EVENT_6(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { ::extra2d::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5); } \ + }; + +/** + * @brief 声明七参数事件 + */ +#define DECLARE_EVENT_7(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { ::extra2d::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6); } \ + }; + +/** + * @brief 声明八参数事件 + */ +#define DECLARE_EVENT_8(EventName, BusName, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) \ + struct EventName : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener; \ + static constexpr const char* NAME = #EventName; \ + static constexpr const char* BUS_NAME = E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(Arg0 arg0, Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6, Arg7 arg7) { ::extra2d::event::broadcast(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } \ + }; diff --git a/include/core/event/events.h b/include/core/event/events.h new file mode 100644 index 0000000..8b432fa --- /dev/null +++ b/include/core/event/events.h @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include + +namespace extra2d::events { + +/** + * @brief 引擎事件总线 + */ +DECLARE_EVENT_BUS(Engine) + +// ============================================================================ +// 应用生命周期事件 +// ============================================================================ + +/** + * @brief 应用初始化完成事件 + */ +DECLARE_EVENT_0(OnInit, Engine) + +/** + * @brief 应用关闭事件 + */ +DECLARE_EVENT_0(OnShutdown, Engine) + +/** + * @brief 应用暂停事件 + */ +DECLARE_EVENT_0(OnPause, Engine) + +/** + * @brief 应用恢复事件 + */ +DECLARE_EVENT_0(OnResume, Engine) + +/** + * @brief 帧更新事件 + * @param dt 帧间隔时间 + */ +DECLARE_EVENT_1(OnUpdate, Engine, float) + +/** + * @brief 帧更新后事件 + * @param dt 帧间隔时间 + */ +DECLARE_EVENT_1(OnLateUpdate, Engine, float) + +/** + * @brief 固定更新事件 + * @param dt 固定时间步长 + */ +DECLARE_EVENT_1(OnFixedUpdate, Engine, float) + +// ============================================================================ +// 窗口事件 +// ============================================================================ + +/** + * @brief 窗口大小改变事件 + * @param width 新宽度 + * @param height 新高度 + */ +DECLARE_EVENT_2(OnResize, Engine, int32, int32) + +/** + * @brief 窗口关闭事件 + */ +DECLARE_EVENT_0(OnClose, Engine) + +/** + * @brief 窗口获得焦点事件 + */ +DECLARE_EVENT_0(OnFocus, Engine) + +/** + * @brief 窗口失去焦点事件 + */ +DECLARE_EVENT_0(OnBlur, Engine) + +/** + * @brief 窗口显示事件 + */ +DECLARE_EVENT_0(OnShow, Engine) + +/** + * @brief 窗口隐藏事件 + */ +DECLARE_EVENT_0(OnHide, Engine) + +// ============================================================================ +// 输入事件 +// ============================================================================ + +/** + * @brief 键盘按下事件 + * @param key 按键码 + */ +DECLARE_EVENT_1(OnKeyDown, Engine, int32) + +/** + * @brief 键盘释放事件 + * @param key 按键码 + */ +DECLARE_EVENT_1(OnKeyUp, Engine, int32) + +/** + * @brief 鼠标按下事件 + * @param button 鼠标按键 + * @param x X 坐标 + * @param y Y 坐标 + */ +DECLARE_EVENT_3(OnMouseDown, Engine, int32, int32, int32) + +/** + * @brief 鼠标释放事件 + * @param button 鼠标按键 + * @param x X 坐标 + * @param y Y 坐标 + */ +DECLARE_EVENT_3(OnMouseUp, Engine, int32, int32, int32) + +/** + * @brief 鼠标移动事件 + * @param x X 坐标 + * @param y Y 坐标 + */ +DECLARE_EVENT_2(OnMouseMove, Engine, int32, int32) + +/** + * @brief 鼠标滚轮事件 + * @param delta 滚轮增量 + */ +DECLARE_EVENT_1(OnMouseWheel, Engine, int32) + +/** + * @brief 触摸开始事件 + * @param touch 触摸点信息 + */ +DECLARE_EVENT_1(OnTouchBegan, Engine, const TouchPoint&) + +/** + * @brief 触摸移动事件 + * @param touch 触摸点信息 + */ +DECLARE_EVENT_1(OnTouchMoved, Engine, const TouchPoint&) + +/** + * @brief 触摸结束事件 + * @param touch 触摸点信息 + */ +DECLARE_EVENT_1(OnTouchEnded, Engine, const TouchPoint&) + +/** + * @brief 触摸取消事件 + * @param touch 触摸点信息 + */ +DECLARE_EVENT_1(OnTouchCancelled, Engine, const TouchPoint&) + +// ============================================================================ +// 场景事件 +// ============================================================================ + +/** + * @brief 场景加载完成事件 + */ +DECLARE_EVENT_0(OnSceneLoad, Engine) + +/** + * @brief 场景卸载事件 + */ +DECLARE_EVENT_0(OnSceneUnload, Engine) + +// ============================================================================ +// 系统事件 +// ============================================================================ + +/** + * @brief 低内存警告事件 + */ +DECLARE_EVENT_0(OnLowMemory, Engine) + +/** + * @brief 进入后台事件 + */ +DECLARE_EVENT_0(OnEnterBackground, Engine) + +/** + * @brief 进入前台事件 + */ +DECLARE_EVENT_0(OnEnterForeground, Engine) + +} // namespace extra2d::events diff --git a/include/core/scheduler.h b/include/core/scheduler.h index 3c96946..31d6650 100644 --- a/include/core/scheduler.h +++ b/include/core/scheduler.h @@ -4,10 +4,12 @@ #include #include #include -#include #include #include +#include +#include #include +#include namespace extra2d { @@ -105,7 +107,7 @@ private: bool operator<(const UpdateEntry& o) const { return pri > o.pri; } }; - tbb::concurrent_vector updates_; + std::vector updates_; tbb::concurrent_hash_map updateIndex_; tbb::concurrent_hash_map> timers_; tbb::concurrent_priority_queue updateQueue_; diff --git a/include/core/service.h b/include/core/service.h index 491ac4f..870f547 100644 --- a/include/core/service.h +++ b/include/core/service.h @@ -10,6 +10,17 @@ namespace extra2d { +/** + * @brief 服务状态枚举 + */ +enum class SvcState : uint8 { + None, + Inited, + Running, + Paused, + Shutdown +}; + /** * @brief 服务基类 * @@ -21,6 +32,8 @@ public: virtual bool init() { return true; } virtual void shutdown() {} + virtual void pause() {} + virtual void resume() {} virtual void update(float dt) {} virtual void lateUpdate(float dt) {} @@ -29,13 +42,14 @@ public: virtual const char* name() const = 0; virtual int pri() const { return Pri::Default; } - bool isInited() const { return inited_; } + SvcState state() const { return state_; } + bool isInited() const { return state_ >= SvcState::Inited; } bool isEnabled() const { return enabled_; } void setEnabled(bool v) { enabled_ = v; } protected: IService() = default; - bool inited_ = false; + SvcState state_ = SvcState::None; bool enabled_ = true; friend class SvcMgr; @@ -50,7 +64,7 @@ class SvcMgr { public: static SvcMgr& inst(); - void reg(Ptr svc); + void reg(IService* svc); void unreg(const char* name); Ptr get(const char* name); @@ -60,6 +74,8 @@ public: } bool initAll(); + void pauseAll(); + void resumeAll(); void shutdownAll(); void updateAll(float dt); diff --git a/include/extra2d.h b/include/extra2d.h index 2aee61f..47615d8 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -22,6 +22,12 @@ #include #include +// Platform +#include +#include +#include +#include + // Utils #include #include diff --git a/include/platform/file.h b/include/platform/file.h new file mode 100644 index 0000000..dd4cc15 --- /dev/null +++ b/include/platform/file.h @@ -0,0 +1,156 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 文件信息 + */ +struct FileInfo { + std::string path; + std::string name; + bool isDir = false; + int64 size = 0; +}; + +/** + * @brief 文件读取结果 + */ +struct FileData { + bool ok = false; + std::vector data; + std::string error; + + operator bool() const { return ok; } + const uint8* ptr() const { return data.data(); } + size_t size() const { return data.size(); } +}; + +/** + * @brief 文件服务 + * + * 提供跨平台文件系统操作 + */ +class FileSvc : public IService { +public: + /** + * @brief 获取单例实例 + */ + static FileSvc& inst(); + + const char* name() const override { return "FileSvc"; } + int pri() const override { return Pri::System; } + + bool init() override; + void shutdown() override; + + /** + * @brief 检查文件是否存在 + */ + bool exists(const std::string& path) const; + + /** + * @brief 检查是否为目录 + */ + bool isDir(const std::string& path) const; + + /** + * @brief 读取整个文件到内存 + */ + FileData read(const std::string& path) const; + + /** + * @brief 读取文件为字符串 + */ + std::string readString(const std::string& path) const; + + /** + * @brief 写入数据到文件 + */ + bool write(const std::string& path, const void* data, size_t size) const; + + /** + * @brief 写入字符串到文件 + */ + bool writeString(const std::string& path, const std::string& content) const; + + /** + * @brief 追加数据到文件 + */ + bool append(const std::string& path, const void* data, size_t size) const; + + /** + * @brief 删除文件 + */ + bool remove(const std::string& path) const; + + /** + * @brief 创建目录 + */ + bool mkdir(const std::string& path) const; + + /** + * @brief 列出目录内容 + */ + std::vector listDir(const std::string& path) const; + + /** + * @brief 获取文件大小 + */ + int64 fileSize(const std::string& path) const; + + /** + * @brief 获取文件扩展名 + */ + std::string ext(const std::string& path) const; + + /** + * @brief 获取文件名(不含路径) + */ + std::string fileName(const std::string& path) const; + + /** + * @brief 获取文件所在目录 + */ + std::string dirName(const std::string& path) const; + + /** + * @brief 连接路径 + */ + std::string join(const std::string& a, const std::string& b) const; + + /** + * @brief 获取可写目录(用于存档等) + */ + std::string writableDir() const; + + /** + * @brief 设置资源根目录 + */ + void setAssetRoot(const std::string& root) { assetRoot_ = root; } + + /** + * @brief 获取资源根目录 + */ + const std::string& assetRoot() const { return assetRoot_; } + + /** + * @brief 获取资源完整路径 + */ + std::string assetPath(const std::string& relPath) const; + +private: + FileSvc() = default; + + std::string assetRoot_; + std::string writableDir_; +}; + +#define FILE_SVC extra2d::FileSvc::inst() + +} // namespace extra2d diff --git a/include/platform/input.h b/include/platform/input.h new file mode 100644 index 0000000..880bda8 --- /dev/null +++ b/include/platform/input.h @@ -0,0 +1,369 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 键键码别名 + */ +using Key = SDL_Scancode; + +/** + * @brief 按键常量 + */ +namespace Keys { +constexpr Key Unknown = SDL_SCANCODE_UNKNOWN; +constexpr Key A = SDL_SCANCODE_A; +constexpr Key B = SDL_SCANCODE_B; +constexpr Key C = SDL_SCANCODE_C; +constexpr Key D = SDL_SCANCODE_D; +constexpr Key E = SDL_SCANCODE_E; +constexpr Key F = SDL_SCANCODE_F; +constexpr Key G = SDL_SCANCODE_G; +constexpr Key H = SDL_SCANCODE_H; +constexpr Key I = SDL_SCANCODE_I; +constexpr Key J = SDL_SCANCODE_J; +constexpr Key K = SDL_SCANCODE_K; +constexpr Key L = SDL_SCANCODE_L; +constexpr Key M = SDL_SCANCODE_M; +constexpr Key N = SDL_SCANCODE_N; +constexpr Key O = SDL_SCANCODE_O; +constexpr Key P = SDL_SCANCODE_P; +constexpr Key Q = SDL_SCANCODE_Q; +constexpr Key R = SDL_SCANCODE_R; +constexpr Key S = SDL_SCANCODE_S; +constexpr Key T = SDL_SCANCODE_T; +constexpr Key U = SDL_SCANCODE_U; +constexpr Key V = SDL_SCANCODE_V; +constexpr Key W = SDL_SCANCODE_W; +constexpr Key X = SDL_SCANCODE_X; +constexpr Key Y = SDL_SCANCODE_Y; +constexpr Key Z = SDL_SCANCODE_Z; +constexpr Key Num0 = SDL_SCANCODE_0; +constexpr Key Num1 = SDL_SCANCODE_1; +constexpr Key Num2 = SDL_SCANCODE_2; +constexpr Key Num3 = SDL_SCANCODE_3; +constexpr Key Num4 = SDL_SCANCODE_4; +constexpr Key Num5 = SDL_SCANCODE_5; +constexpr Key Num6 = SDL_SCANCODE_6; +constexpr Key Num7 = SDL_SCANCODE_7; +constexpr Key Num8 = SDL_SCANCODE_8; +constexpr Key Num9 = SDL_SCANCODE_9; +constexpr Key F1 = SDL_SCANCODE_F1; +constexpr Key F2 = SDL_SCANCODE_F2; +constexpr Key F3 = SDL_SCANCODE_F3; +constexpr Key F4 = SDL_SCANCODE_F4; +constexpr Key F5 = SDL_SCANCODE_F5; +constexpr Key F6 = SDL_SCANCODE_F6; +constexpr Key F7 = SDL_SCANCODE_F7; +constexpr Key F8 = SDL_SCANCODE_F8; +constexpr Key F9 = SDL_SCANCODE_F9; +constexpr Key F10 = SDL_SCANCODE_F10; +constexpr Key F11 = SDL_SCANCODE_F11; +constexpr Key F12 = SDL_SCANCODE_F12; +constexpr Key Space = SDL_SCANCODE_SPACE; +constexpr Key Enter = SDL_SCANCODE_RETURN; +constexpr Key Escape = SDL_SCANCODE_ESCAPE; +constexpr Key Tab = SDL_SCANCODE_TAB; +constexpr Key Backspace = SDL_SCANCODE_BACKSPACE; +constexpr Key Insert = SDL_SCANCODE_INSERT; +constexpr Key Delete = SDL_SCANCODE_DELETE; +constexpr Key Home = SDL_SCANCODE_HOME; +constexpr Key End = SDL_SCANCODE_END; +constexpr Key PageUp = SDL_SCANCODE_PAGEUP; +constexpr Key PageDown = SDL_SCANCODE_PAGEDOWN; +constexpr Key Left = SDL_SCANCODE_LEFT; +constexpr Key Right = SDL_SCANCODE_RIGHT; +constexpr Key Up = SDL_SCANCODE_UP; +constexpr Key Down = SDL_SCANCODE_DOWN; +constexpr Key LeftShift = SDL_SCANCODE_LSHIFT; +constexpr Key RightShift = SDL_SCANCODE_RSHIFT; +constexpr Key LeftCtrl = SDL_SCANCODE_LCTRL; +constexpr Key RightCtrl = SDL_SCANCODE_RCTRL; +constexpr Key LeftAlt = SDL_SCANCODE_LALT; +constexpr Key RightAlt = SDL_SCANCODE_RALT; +} // namespace Keys + +/** + * @brief 鼠标按键 + */ +enum class MouseBtn : uint8 { + Left = 0, + Middle = 1, + Right = 2, + X1 = 3, + X2 = 4, + Count = 5 +}; + +/** + * @brief 游戏手柄按键 + */ +enum class GamepadBtn : uint8 { + A = 0, + B = 1, + X = 2, + Y = 3, + Back = 4, + Guide = 5, + Start = 6, + LeftStick = 7, + RightStick = 8, + LeftShoulder = 9, + RightShoulder = 10, + DPadUp = 11, + DPadDown = 12, + DPadLeft = 13, + DPadRight = 14, + Count = 15 +}; + +/** + * @brief 游戏手柄轴 + */ +enum class GamepadAxis : uint8 { + LeftX = 0, + LeftY = 1, + RightX = 2, + RightY = 3, + TriggerLeft = 4, + TriggerRight = 5, + Count = 6 +}; + +/** + * @brief 触摸状态 + */ +enum class TouchState : uint8 { None = 0, Began, Moved, Ended, Cancelled }; + +/** + * @brief 触摸点信息 + */ +struct TouchPoint { + int64 id = 0; + float x = 0.0f; + float y = 0.0f; + float prevX = 0.0f; + float prevY = 0.0f; + float deltaX = 0.0f; + float deltaY = 0.0f; + TouchState state = TouchState::None; + float pressure = 1.0f; +}; + +/** + * @brief 按键回调类型 + */ +using KeyCb = Fn; +using MouseBtnCb = Fn; +using TouchCb = Fn; + +/** + * @brief 输入服务 + * + * 管理键盘、鼠标、触摸、游戏手柄输入 + */ +class InputSvc : public IService { +public: + /** + * @brief 获取单例实例 + */ + static InputSvc &inst(); + + const char *name() const override { return "InputSvc"; } + int pri() const override { return Pri::Input; } + + bool init() override; + void shutdown() override; + void update(float dt) override; + + // ========== 键盘 ========== + + /** + * @brief 检查按键是否按下 + */ + bool isKeyDown(Key key) const; + + /** + * @brief 检查按键是否刚按下 + */ + bool isKeyPressed(Key key) const; + + /** + * @brief 检查按键是否刚释放 + */ + bool isKeyReleased(Key key) const; + + // ========== 鼠标 ========== + + /** + * @brief 获取鼠标位置 + */ + void getMousePos(int32 &x, int32 &y) const; + + /** + * @brief 获取鼠标位置(浮点) + */ + void getMousePos(float &x, float &y) const; + + /** + * @brief 检查鼠标按键是否按下 + */ + bool isMouseBtnDown(MouseBtn btn) const; + + /** + * @brief 检查鼠标按键是否刚按下 + */ + bool isMouseBtnPressed(MouseBtn btn) const; + + /** + * @brief 检查鼠标按键是否刚释放 + */ + bool isMouseBtnReleased(MouseBtn btn) const; + + /** + * @brief 获取鼠标滚轮 + */ + int32 getMouseWheel() const; + + // ========== 触摸 ========== + + /** + * @brief 获取触摸点数量 + */ + int32 touchCount() const; + + /** + * @brief 获取触摸点 + * @param idx 触摸点索引 + * @return 触摸点信息,无效索引返回 nullptr + */ + const TouchPoint *getTouch(int32 idx) const; + + /** + * @brief 根据 ID 获取触摸点 + */ + const TouchPoint *getTouchById(int64 id) const; + + /** + * @brief 检查是否有触摸 + */ + bool hasTouch() const { return touchCount() > 0; } + + /** + * @brief 获取所有活跃触摸点 + */ + const std::vector &getTouches() const { return activeTouches_; } + + // ========== 游戏手柄 ========== + + /** + * @brief 连接的游戏手柄数量 + */ + int32 gamepadCount() const; + + /** + * @brief 检查手柄按键是否按下 + */ + bool isGamepadBtnDown(int32 idx, GamepadBtn btn) const; + + /** + * @brief 检查手柄按键是否刚按下 + */ + bool isGamepadBtnPressed(int32 idx, GamepadBtn btn) const; + + /** + * @brief 获取手柄轴值 (-1.0 到 1.0) + */ + float getGamepadAxis(int32 idx, GamepadAxis axis) const; + + // ========== 回调设置 ========== + + /** + * @brief 设置按键按下回调 + */ + void setOnKeyDown(KeyCb cb) { onKeyDown_ = std::move(cb); } + + /** + * @brief 设置按键释放回调 + */ + void setOnKeyUp(KeyCb cb) { onKeyUp_ = std::move(cb); } + + /** + * @brief 设置鼠标按下回调 + */ + void setOnMouseDown(MouseBtnCb cb) { onMouseDown_ = std::move(cb); } + + /** + * @brief 设置鼠标释放回调 + */ + void setOnMouseUp(MouseBtnCb cb) { onMouseUp_ = std::move(cb); } + + /** + * @brief 设置触摸开始回调 + */ + void setOnTouchBegan(TouchCb cb) { onTouchBegan_ = std::move(cb); } + + /** + * @brief 设置触摸移动回调 + */ + void setOnTouchMoved(TouchCb cb) { onTouchMoved_ = std::move(cb); } + + /** + * @brief 设置触摸结束回调 + */ + void setOnTouchEnded(TouchCb cb) { onTouchEnded_ = std::move(cb); } + +private: + InputSvc() = default; + + static constexpr int32 KEY_COUNT = SDL_NUM_SCANCODES; + static constexpr int32 MAX_GAMEPADS = 4; + static constexpr int32 MAX_TOUCHES = 10; + + std::array keyState_{}; + std::array keyPrev_{}; + + int32 mouseX_ = 0; + int32 mouseY_ = 0; + int32 mouseWheel_ = 0; + std::array(MouseBtn::Count)> mouseState_{}; + std::array(MouseBtn::Count)> mousePrev_{}; + + std::vector activeTouches_; + std::vector endedTouches_; + + SDL_GameController *gamepads_[MAX_GAMEPADS] = {}; + std::array(GamepadBtn::Count)> + padState_[MAX_GAMEPADS]; + std::array(GamepadBtn::Count)> + padPrev_[MAX_GAMEPADS]; + + KeyCb onKeyDown_; + KeyCb onKeyUp_; + MouseBtnCb onMouseDown_; + MouseBtnCb onMouseUp_; + TouchCb onTouchBegan_; + TouchCb onTouchMoved_; + TouchCb onTouchEnded_; + + void processEvent(const SDL_Event &evt); + void openGamepad(int32 idx); + void closeGamepad(int32 idx); + + void processTouchDown(const SDL_TouchFingerEvent &evt); + void processTouchUp(const SDL_TouchFingerEvent &evt); + void processTouchMotion(const SDL_TouchFingerEvent &evt); + + friend class WindowSvc; +}; + +#define INPUT_SVC extra2d::InputSvc::inst() + +} // namespace extra2d diff --git a/include/platform/sdl2.h b/include/platform/sdl2.h new file mode 100644 index 0000000..4d18a54 --- /dev/null +++ b/include/platform/sdl2.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief SDL2 初始化和生命周期管理 + * + * 提供 SDL2 子系统的统一初始化和清理接口 + */ +class Sdl2 { +public: + /** + * @brief 初始化 SDL2 核心子系统 + * @return true 初始化成功 + */ + static bool initCore(); + + /** + * @brief 初始化视频子系统 + * @return true 初始化成功 + */ + static bool initVideo(); + + /** + * @brief 初始化音频子系统 + * @return true 初始化成功 + */ + static bool initAudio(); + + /** + * @brief 初始化游戏控制器子系统 + * @return true 初始化成功 + */ + static bool initGamepad(); + + /** + * @brief 初始化所有子系统 + * @return true 初始化成功 + */ + static bool initAll(); + + /** + * @brief 关闭 SDL2 + */ + static void shutdown(); + + /** + * @brief 检查是否已初始化 + */ + static bool isInited() { return inited_; } + +private: + static bool inited_; +}; + +} // namespace extra2d diff --git a/include/platform/window.h b/include/platform/window.h new file mode 100644 index 0000000..0d79011 --- /dev/null +++ b/include/platform/window.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 窗口配置 + */ +struct WindowCfg { + std::string title = "Extra2D"; + int32 width = 1280; + int32 height = 720; + bool fullscreen = false; + bool resizable = true; + bool vsync = true; + int32 glMajor = 3; + int32 glMinor = 3; +}; + +/** + * @brief 窗口事件回调 + */ +using ResizeCb = Fn; +using CloseCb = Fn; + +/** + * @brief 窗口服务 + * + * 管理 SDL2 窗口和 OpenGL 上下文 + */ +class WindowSvc : public IService { +public: + /** + * @brief 获取单例实例 + */ + static WindowSvc &inst(); + + const char *name() const override { return "WindowSvc"; } + int pri() const override { return Pri::System; } + + bool init() override; + void shutdown() override; + + /** + * @brief 使用配置创建窗口 + */ + bool create(const WindowCfg &cfg); + + /** + * @brief 处理窗口事件 + * @return true 继续运行,false 应退出 + */ + bool pollEvents(); + + /** + * @brief 交换缓冲区 + */ + void swapBuffers(); + + /** + * @brief 获取 SDL 窗口句柄 + */ + SDL_Window *handle() const { return window_; } + + /** + * @brief 获取 OpenGL 上下文 + */ + SDL_GLContext glContext() const { return glCtx_; } + + /** + * @brief 获取窗口尺寸 + */ + Size getSize() const; + + /** + * @brief 获取窗口位置 + */ + Vec2 getPosition() const; + + /** + * @brief 设置窗口尺寸 + */ + void setSize(int32 w, int32 h); + + /** + * @brief 设置窗口标题 + */ + void setTitle(const std::string &title); + + /** + * @brief 设置全屏模式 + */ + void setFullscreen(bool fullscreen); + + /** + * @brief 检查是否全屏 + */ + bool isFullscreen() const; + + /** + * @brief 设置垂直同步 + */ + void setVsync(bool vsync); + + /** + * @brief 检查是否垂直同步 + */ + bool isVsync() const; + + /** + * @brief 显示/隐藏窗口 + */ + void setVisible(bool visible); + + /** + * @brief 检查窗口是否可见 + */ + bool isVisible() const; + + /** + * @brief 设置窗口关闭回调 + */ + void setOnClose(CloseCb cb) { onClose_ = std::move(cb); } + + /** + * @brief 设置窗口大小改变回调 + */ + void setOnResize(ResizeCb cb) { onResize_ = std::move(cb); } + + /** + * @brief 请求关闭窗口 + */ + void requestClose() { shouldClose_ = true; } + + /** + * @brief 检查是否应该关闭 + */ + bool shouldClose() const { return shouldClose_; } + +private: + WindowSvc() = default; + + SDL_Window *window_ = nullptr; + SDL_GLContext glCtx_ = nullptr; + bool shouldClose_ = false; + bool vsync_ = true; + + CloseCb onClose_; + ResizeCb onResize_; +}; + +#define WINDOW extra2d::WindowSvc::inst() + +} // namespace extra2d diff --git a/src/app/application.cpp b/src/app/application.cpp index 86d5feb..ad0e8ba 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -1,5 +1,11 @@ #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -11,6 +17,9 @@ namespace extra2d { +/** + * @brief 获取当前时间(秒) + */ static double getTimeSeconds() { #ifdef __SWITCH__ struct timespec ts; @@ -67,24 +76,65 @@ bool Application::init(const AppConfig& config) { #endif } + if (!Sdl2::initAll()) { + E2D_LOG_ERROR("Failed to initialize SDL2"); + return false; + } + + SVC_MGR.reg(&WINDOW); + SVC_MGR.reg(&INPUT_SVC); + SVC_MGR.reg(&FILE_SVC); + + if (!SVC_MGR.initAll()) { + E2D_LOG_ERROR("Failed to initialize services"); + return false; + } + + WindowCfg winCfg; + winCfg.title = config_.title; + winCfg.width = config_.width; + winCfg.height = config_.height; + winCfg.fullscreen = config_.fullscreen; + winCfg.resizable = config_.resizable; + winCfg.vsync = config_.vsync; + winCfg.glMajor = config_.glMajor; + winCfg.glMinor = config_.glMinor; + + if (!WINDOW.create(winCfg)) { + E2D_LOG_ERROR("Failed to create window"); + return false; + } + if (!DIRECTOR.init()) { E2D_LOG_ERROR("Failed to initialize Director"); return false; } + WINDOW.setOnClose([this]() { + quit(); + }); + initialized_ = true; running_ = true; + events::OnInit::emit(); + E2D_LOG_INFO("Application initialized successfully"); + E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", + config_.width, config_.height, config_.fullscreen, config_.vsync); return true; } void Application::shutdown() { if (!initialized_) return; + events::OnShutdown::emit(); + E2D_LOG_INFO("Shutting down application..."); DIRECTOR.shutdown(); + SVC_MGR.shutdownAll(); + Sdl2::shutdown(); PlatformType platform = config_.platform; if (platform == PlatformType::Auto) { @@ -116,7 +166,7 @@ void Application::run() { lastFrameTime_ = getTimeSeconds(); - while (running_) { + while (running_ && !WINDOW.shouldClose()) { mainLoop(); } } @@ -124,12 +174,15 @@ void Application::run() { void Application::quit() { shouldQuit_ = true; running_ = false; + WINDOW.requestClose(); } void Application::pause() { if (!paused_) { paused_ = true; + SVC_MGR.pauseAll(); DIRECTOR.pause(); + events::OnPause::emit(); E2D_LOG_INFO("Application paused"); } } @@ -137,13 +190,20 @@ void Application::pause() { void Application::resume() { if (paused_) { paused_ = false; + SVC_MGR.resumeAll(); DIRECTOR.resume(); lastFrameTime_ = getTimeSeconds(); + events::OnResume::emit(); E2D_LOG_INFO("Application resumed"); } } void Application::mainLoop() { + if (!WINDOW.pollEvents()) { + running_ = false; + return; + } + double currentTime = getTimeSeconds(); deltaTime_ = static_cast(currentTime - lastFrameTime_); lastFrameTime_ = currentTime; @@ -162,6 +222,8 @@ void Application::mainLoop() { update(); } + WINDOW.swapBuffers(); + if (!config_.vsync && config_.fpsLimit > 0) { double frameEndTime = getTimeSeconds(); double frameTime = frameEndTime - currentTime; @@ -173,6 +235,8 @@ void Application::mainLoop() { } void Application::update() { + SVC_MGR.updateAll(deltaTime_); + events::OnUpdate::emit(deltaTime_); DIRECTOR.mainLoop(deltaTime_); } diff --git a/src/core/event/event_bus.cpp b/src/core/event/event_bus.cpp new file mode 100644 index 0000000..925d852 --- /dev/null +++ b/src/core/event/event_bus.cpp @@ -0,0 +1,96 @@ +#include + +namespace extra2d::event { + +ListenerContainer::~ListenerContainer() { + ListenerEntry* curr = listenerList_; + while (curr) { + ListenerEntry* next = curr->next; + delete curr; + curr = next; + } + + curr = listenersToAdd_; + while (curr) { + ListenerEntry* next = curr->next; + delete curr; + curr = next; + } + + for (auto* entry : listenersToRemove_) { + delete entry; + } +} + +void ListenerContainer::addListener(ListenerBase* listener) { + if (!listener || !listener->entry_) return; + + if (broadcasting_ > 0) { + listener->entry_->next = listenersToAdd_; + listenersToAdd_ = listener->entry_; + } else { + listener->entry_->next = listenerList_; + listener->entry_->prev = nullptr; + if (listenerList_) { + listenerList_->prev = listener->entry_; + } + listenerList_ = listener->entry_; + } +} + +void ListenerContainer::removeListener(ListenerBase* listener) { + if (!listener || !listener->entry_) return; + + if (broadcasting_ > 0) { + listenersToRemove_.push_back(listener->entry_); + listener->entry_->listener = nullptr; + } else { + if (listener->entry_->prev) { + listener->entry_->prev->next = listener->entry_->next; + } else { + listenerList_ = listener->entry_->next; + } + + if (listener->entry_->next) { + listener->entry_->next->prev = listener->entry_->prev; + } + + delete listener->entry_; + listener->entry_ = nullptr; + } +} + +void ListenerContainer::processPendingListeners() { + while (listenersToAdd_) { + ListenerEntry* entry = listenersToAdd_; + listenersToAdd_ = entry->next; + + entry->next = listenerList_; + entry->prev = nullptr; + if (listenerList_) { + listenerList_->prev = entry; + } + listenerList_ = entry; + } + + for (auto* entry : listenersToRemove_) { + if (entry->prev) { + entry->prev->next = entry->next; + } else { + listenerList_ = entry->next; + } + + if (entry->next) { + entry->next->prev = entry->prev; + } + + delete entry; + } + listenersToRemove_.clear(); +} + +bool ListenerContainer::hasPendingListeners() const { + return listenersToAdd_ != nullptr || !listenersToRemove_.empty(); +} + +} // namespace extra2d::event diff --git a/src/core/service.cpp b/src/core/service.cpp index 36ba0bd..b58b052 100644 --- a/src/core/service.cpp +++ b/src/core/service.cpp @@ -8,11 +8,11 @@ SvcMgr& SvcMgr::inst() { return instance; } -void SvcMgr::reg(Ptr svc) { +void SvcMgr::reg(IService* svc) { if (!svc) return; SvcMap::accessor acc; svcMap_.insert(acc, svc->name()); - acc->second = svc; + acc->second = Ptr(svc); sortSvcs(); } @@ -31,28 +31,49 @@ Ptr SvcMgr::get(const char* name) { bool SvcMgr::initAll() { for (auto& svc : sortedSvcs_) { - if (svc && !svc->isInited()) { + if (svc && svc->state_ == SvcState::None) { if (!svc->init()) { return false; } - svc->inited_ = true; + svc->state_ = SvcState::Inited; } } return true; } +void SvcMgr::pauseAll() { + for (auto& svc : sortedSvcs_) { + if (svc && svc->state_ == SvcState::Running) { + svc->pause(); + svc->state_ = SvcState::Paused; + } + } +} + +void SvcMgr::resumeAll() { + for (auto& svc : sortedSvcs_) { + if (svc && svc->state_ == SvcState::Paused) { + svc->resume(); + svc->state_ = SvcState::Running; + } + } +} + void SvcMgr::shutdownAll() { for (auto it = sortedSvcs_.rbegin(); it != sortedSvcs_.rend(); ++it) { - if (*it && (*it)->isInited()) { + if (*it && (*it)->state_ >= SvcState::Inited) { (*it)->shutdown(); - (*it)->inited_ = false; + (*it)->state_ = SvcState::Shutdown; } } } void SvcMgr::updateAll(float dt) { for (auto& svc : sortedSvcs_) { - if (svc && svc->isInited() && svc->isEnabled()) { + if (svc && svc->state_ >= SvcState::Inited && svc->isEnabled()) { + if (svc->state_ == SvcState::Inited) { + svc->state_ = SvcState::Running; + } svc->update(dt); } } @@ -60,7 +81,7 @@ void SvcMgr::updateAll(float dt) { void SvcMgr::lateUpdateAll(float dt) { for (auto& svc : sortedSvcs_) { - if (svc && svc->isInited() && svc->isEnabled()) { + if (svc && svc->state_ >= SvcState::Running && svc->isEnabled()) { svc->lateUpdate(dt); } } @@ -68,7 +89,7 @@ void SvcMgr::lateUpdateAll(float dt) { void SvcMgr::fixedUpdateAll(float dt) { for (auto& svc : sortedSvcs_) { - if (svc && svc->isInited() && svc->isEnabled()) { + if (svc && svc->state_ >= SvcState::Running && svc->isEnabled()) { svc->fixedUpdate(dt); } } diff --git a/src/platform/file.cpp b/src/platform/file.cpp new file mode 100644 index 0000000..9b10c4c --- /dev/null +++ b/src/platform/file.cpp @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#define mkdir_impl(path, mode) _mkdir(path) +#else +#include +#include +#define mkdir_impl(path, mode) mkdir(path, mode) +#endif + +namespace extra2d { + +FileSvc& FileSvc::inst() { + static FileSvc instance; + return instance; +} + +bool FileSvc::init() { + if (isInited()) return true; + + writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D"); + if (writableDir_.empty()) { + writableDir_ = "./"; + } + + state_ = SvcState::Inited; + return true; +} + +void FileSvc::shutdown() { + state_ = SvcState::Shutdown; +} + +bool FileSvc::exists(const std::string& path) const { + struct stat st; + return stat(path.c_str(), &st) == 0; +} + +bool FileSvc::isDir(const std::string& path) const { + struct stat st; + if (stat(path.c_str(), &st) != 0) return false; + return (st.st_mode & S_IFDIR) != 0; +} + +FileData FileSvc::read(const std::string& path) const { + FileData result; + + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + result.error = "Cannot open file: " + path; + return result; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + result.data.resize(static_cast(size)); + if (!file.read(reinterpret_cast(result.data.data()), size)) { + result.error = "Failed to read file: " + path; + return result; + } + + result.ok = true; + return result; +} + +std::string FileSvc::readString(const std::string& path) const { + std::ifstream file(path); + if (!file.is_open()) return ""; + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +bool FileSvc::write(const std::string& path, const void* data, size_t size) const { + std::ofstream file(path, std::ios::binary); + if (!file.is_open()) return false; + + file.write(static_cast(data), static_cast(size)); + return file.good(); +} + +bool FileSvc::writeString(const std::string& path, const std::string& content) const { + return write(path, content.data(), content.size()); +} + +bool FileSvc::append(const std::string& path, const void* data, size_t size) const { + std::ofstream file(path, std::ios::binary | std::ios::app); + if (!file.is_open()) return false; + + file.write(static_cast(data), static_cast(size)); + return file.good(); +} + +bool FileSvc::remove(const std::string& path) const { + return std::remove(path.c_str()) == 0; +} + +bool FileSvc::mkdir(const std::string& path) const { +#ifdef _WIN32 + return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; +#else + return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; +#endif +} + +std::vector FileSvc::listDir(const std::string& path) const { + std::vector result; + +#ifdef _WIN32 + WIN32_FIND_DATAA findData; + std::string searchPath = path + "\\*"; + HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); + + if (hFind == INVALID_HANDLE_VALUE) return result; + + do { + std::string name = findData.cFileName; + if (name == "." || name == "..") continue; + + FileInfo info; + info.name = name; + info.path = join(path, name); + info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (!info.isDir) { + info.size = static_cast(findData.nFileSizeLow) | + (static_cast(findData.nFileSizeHigh) << 32); + } + result.push_back(info); + } while (FindNextFileA(hFind, &findData)); + + FindClose(hFind); +#else + DIR* dir = opendir(path.c_str()); + if (!dir) return result; + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + std::string name = entry->d_name; + if (name == "." || name == "..") continue; + + FileInfo info; + info.name = name; + info.path = join(path, name); + info.isDir = isDir(info.path); + if (!info.isDir) { + info.size = fileSize(info.path); + } + result.push_back(info); + } + + closedir(dir); +#endif + + return result; +} + +int64 FileSvc::fileSize(const std::string& path) const { + struct stat st; + if (stat(path.c_str(), &st) != 0) return -1; + return static_cast(st.st_size); +} + +std::string FileSvc::ext(const std::string& path) const { + size_t pos = path.find_last_of('.'); + if (pos == std::string::npos || pos == 0) return ""; + + size_t lastSep = path.find_last_of("/\\"); + if (lastSep != std::string::npos && pos < lastSep) return ""; + + return path.substr(pos + 1); +} + +std::string FileSvc::fileName(const std::string& path) const { + size_t pos = path.find_last_of("/\\"); + if (pos == std::string::npos) return path; + return path.substr(pos + 1); +} + +std::string FileSvc::dirName(const std::string& path) const { + size_t pos = path.find_last_of("/\\"); + if (pos == std::string::npos) return "."; + if (pos == 0) return "/"; + return path.substr(0, pos); +} + +std::string FileSvc::join(const std::string& a, const std::string& b) const { + if (a.empty()) return b; + if (b.empty()) return a; + + char last = a.back(); + if (last == '/' || last == '\\') { + return a + b; + } + return a + "/" + b; +} + +std::string FileSvc::writableDir() const { + return writableDir_; +} + +std::string FileSvc::assetPath(const std::string& relPath) const { + if (assetRoot_.empty()) return relPath; + return join(assetRoot_, relPath); +} + +} // namespace extra2d diff --git a/src/platform/input.cpp b/src/platform/input.cpp new file mode 100644 index 0000000..1520930 --- /dev/null +++ b/src/platform/input.cpp @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include + +namespace extra2d { + +InputSvc& InputSvc::inst() { + static InputSvc instance; + return instance; +} + +bool InputSvc::init() { + if (isInited()) return true; + + std::memset(keyState_.data(), 0, KEY_COUNT); + std::memset(keyPrev_.data(), 0, KEY_COUNT); + std::memset(mouseState_.data(), 0, static_cast(MouseBtn::Count)); + std::memset(mousePrev_.data(), 0, static_cast(MouseBtn::Count)); + + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + gamepads_[i] = nullptr; + std::memset(padState_[i].data(), 0, static_cast(GamepadBtn::Count)); + std::memset(padPrev_[i].data(), 0, static_cast(GamepadBtn::Count)); + } + + activeTouches_.clear(); + activeTouches_.reserve(MAX_TOUCHES); + endedTouches_.clear(); + + int32 numJoysticks = SDL_NumJoysticks(); + for (int32 i = 0; i < numJoysticks && i < MAX_GAMEPADS; ++i) { + if (SDL_IsGameController(i)) { + openGamepad(i); + } + } + + state_ = SvcState::Inited; + return true; +} + +void InputSvc::shutdown() { + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + closeGamepad(i); + } + activeTouches_.clear(); + endedTouches_.clear(); + state_ = SvcState::Shutdown; +} + +void InputSvc::update(float dt) { + std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT); + std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast(MouseBtn::Count)); + + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + if (gamepads_[i]) { + std::memcpy(padPrev_[i].data(), padState_[i].data(), static_cast(GamepadBtn::Count)); + } + } + + mouseWheel_ = 0; + + const uint8* state = SDL_GetKeyboardState(nullptr); + std::memcpy(keyState_.data(), state, KEY_COUNT); + + uint32 btnState = SDL_GetMouseState(&mouseX_, &mouseY_); + mouseState_[static_cast(MouseBtn::Left)] = (btnState & SDL_BUTTON_LMASK) ? 1 : 0; + mouseState_[static_cast(MouseBtn::Middle)] = (btnState & SDL_BUTTON_MMASK) ? 1 : 0; + mouseState_[static_cast(MouseBtn::Right)] = (btnState & SDL_BUTTON_RMASK) ? 1 : 0; + mouseState_[static_cast(MouseBtn::X1)] = (btnState & SDL_BUTTON_X1MASK) ? 1 : 0; + mouseState_[static_cast(MouseBtn::X2)] = (btnState & SDL_BUTTON_X2MASK) ? 1 : 0; + + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + if (gamepads_[i]) { + SDL_GameController* gc = gamepads_[i]; + padState_[i][static_cast(GamepadBtn::A)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_A); + padState_[i][static_cast(GamepadBtn::B)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_B); + padState_[i][static_cast(GamepadBtn::X)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_X); + padState_[i][static_cast(GamepadBtn::Y)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_Y); + padState_[i][static_cast(GamepadBtn::Back)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_BACK); + padState_[i][static_cast(GamepadBtn::Guide)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_GUIDE); + padState_[i][static_cast(GamepadBtn::Start)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_START); + padState_[i][static_cast(GamepadBtn::LeftStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSTICK); + padState_[i][static_cast(GamepadBtn::RightStick)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSTICK); + padState_[i][static_cast(GamepadBtn::LeftShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + padState_[i][static_cast(GamepadBtn::RightShoulder)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + padState_[i][static_cast(GamepadBtn::DPadUp)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_UP); + padState_[i][static_cast(GamepadBtn::DPadDown)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_DOWN); + padState_[i][static_cast(GamepadBtn::DPadLeft)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_LEFT); + padState_[i][static_cast(GamepadBtn::DPadRight)] = SDL_GameControllerGetButton(gc, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + } + } + + endedTouches_.clear(); + + for (auto& touch : activeTouches_) { + if (touch.state == TouchState::Ended || touch.state == TouchState::Cancelled) { + endedTouches_.push_back(touch); + } + } + + activeTouches_.erase( + std::remove_if(activeTouches_.begin(), activeTouches_.end(), + [](const TouchPoint& t) { + return t.state == TouchState::Ended || t.state == TouchState::Cancelled; + }), + activeTouches_.end()); + + for (auto& touch : activeTouches_) { + touch.prevX = touch.x; + touch.prevY = touch.y; + touch.deltaX = 0.0f; + touch.deltaY = 0.0f; + if (touch.state == TouchState::Began) { + touch.state = TouchState::Moved; + } + } +} + +void InputSvc::processEvent(const SDL_Event& evt) { + switch (evt.type) { + case SDL_KEYDOWN: + if (evt.key.repeat == 0 && onKeyDown_) { + onKeyDown_(static_cast(evt.key.keysym.scancode)); + } + break; + case SDL_KEYUP: + if (onKeyUp_) { + onKeyUp_(static_cast(evt.key.keysym.scancode)); + } + break; + case SDL_MOUSEBUTTONDOWN: + if (onMouseDown_) { + onMouseDown_(static_cast(evt.button.button - 1), evt.button.x, evt.button.y); + } + break; + case SDL_MOUSEBUTTONUP: + if (onMouseUp_) { + onMouseUp_(static_cast(evt.button.button - 1), evt.button.x, evt.button.y); + } + break; + case SDL_MOUSEWHEEL: + mouseWheel_ = evt.wheel.y; + break; + case SDL_CONTROLLERDEVICEADDED: + openGamepad(evt.cdevice.which); + break; + case SDL_CONTROLLERDEVICEREMOVED: + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + if (gamepads_[i] && SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepads_[i])) == evt.cdevice.which) { + closeGamepad(i); + break; + } + } + break; + case SDL_FINGERDOWN: + processTouchDown(evt.tfinger); + break; + case SDL_FINGERUP: + processTouchUp(evt.tfinger); + break; + case SDL_FINGERMOTION: + processTouchMotion(evt.tfinger); + break; + } +} + +void InputSvc::processTouchDown(const SDL_TouchFingerEvent& evt) { + if (static_cast(activeTouches_.size()) >= MAX_TOUCHES) return; + + int32 winW = 0, winH = 0; + SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH); + + TouchPoint touch; + touch.id = static_cast(evt.fingerId); + touch.x = evt.x * static_cast(winW); + touch.y = evt.y * static_cast(winH); + touch.prevX = touch.x; + touch.prevY = touch.y; + touch.deltaX = 0.0f; + touch.deltaY = 0.0f; + touch.state = TouchState::Began; + touch.pressure = evt.pressure; + + activeTouches_.push_back(touch); + + if (onTouchBegan_) { + onTouchBegan_(touch); + } +} + +void InputSvc::processTouchUp(const SDL_TouchFingerEvent& evt) { + int64 fingerId = static_cast(evt.fingerId); + + for (auto& touch : activeTouches_) { + if (touch.id == fingerId) { + int32 winW = 0, winH = 0; + SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH); + + touch.x = evt.x * static_cast(winW); + touch.y = evt.y * static_cast(winH); + touch.deltaX = touch.x - touch.prevX; + touch.deltaY = touch.y - touch.prevY; + touch.state = TouchState::Ended; + touch.pressure = evt.pressure; + + if (onTouchEnded_) { + onTouchEnded_(touch); + } + break; + } + } +} + +void InputSvc::processTouchMotion(const SDL_TouchFingerEvent& evt) { + int64 fingerId = static_cast(evt.fingerId); + + for (auto& touch : activeTouches_) { + if (touch.id == fingerId) { + int32 winW = 0, winH = 0; + SDL_GetWindowSize(SDL_GetWindowFromID(evt.windowID), &winW, &winH); + + touch.x = evt.x * static_cast(winW); + touch.y = evt.y * static_cast(winH); + touch.deltaX = touch.x - touch.prevX; + touch.deltaY = touch.y - touch.prevY; + touch.state = TouchState::Moved; + touch.pressure = evt.pressure; + + if (onTouchMoved_) { + onTouchMoved_(touch); + } + break; + } + } +} + +void InputSvc::openGamepad(int32 idx) { + if (idx < 0 || idx >= MAX_GAMEPADS) return; + if (gamepads_[idx]) return; + + SDL_GameController* gc = SDL_GameControllerOpen(idx); + if (gc) { + gamepads_[idx] = gc; + std::memset(padState_[idx].data(), 0, static_cast(GamepadBtn::Count)); + std::memset(padPrev_[idx].data(), 0, static_cast(GamepadBtn::Count)); + } +} + +void InputSvc::closeGamepad(int32 idx) { + if (idx < 0 || idx >= MAX_GAMEPADS) return; + if (gamepads_[idx]) { + SDL_GameControllerClose(gamepads_[idx]); + gamepads_[idx] = nullptr; + } +} + +bool InputSvc::isKeyDown(Key key) const { + if (key < 0 || key >= KEY_COUNT) return false; + return keyState_[key] != 0; +} + +bool InputSvc::isKeyPressed(Key key) const { + if (key < 0 || key >= KEY_COUNT) return false; + return keyState_[key] != 0 && keyPrev_[key] == 0; +} + +bool InputSvc::isKeyReleased(Key key) const { + if (key < 0 || key >= KEY_COUNT) return false; + return keyState_[key] == 0 && keyPrev_[key] != 0; +} + +void InputSvc::getMousePos(int32& x, int32& y) const { + x = mouseX_; + y = mouseY_; +} + +void InputSvc::getMousePos(float& x, float& y) const { + x = static_cast(mouseX_); + y = static_cast(mouseY_); +} + +bool InputSvc::isMouseBtnDown(MouseBtn btn) const { + size_t idx = static_cast(btn); + if (idx >= static_cast(MouseBtn::Count)) return false; + return mouseState_[idx] != 0; +} + +bool InputSvc::isMouseBtnPressed(MouseBtn btn) const { + size_t idx = static_cast(btn); + if (idx >= static_cast(MouseBtn::Count)) return false; + return mouseState_[idx] != 0 && mousePrev_[idx] == 0; +} + +bool InputSvc::isMouseBtnReleased(MouseBtn btn) const { + size_t idx = static_cast(btn); + if (idx >= static_cast(MouseBtn::Count)) return false; + return mouseState_[idx] == 0 && mousePrev_[idx] != 0; +} + +int32 InputSvc::getMouseWheel() const { + return mouseWheel_; +} + +int32 InputSvc::touchCount() const { + return static_cast(activeTouches_.size()); +} + +const TouchPoint* InputSvc::getTouch(int32 idx) const { + if (idx < 0 || idx >= static_cast(activeTouches_.size())) return nullptr; + return &activeTouches_[idx]; +} + +const TouchPoint* InputSvc::getTouchById(int64 id) const { + for (const auto& touch : activeTouches_) { + if (touch.id == id) return &touch; + } + return nullptr; +} + +int32 InputSvc::gamepadCount() const { + int32 count = 0; + for (int32 i = 0; i < MAX_GAMEPADS; ++i) { + if (gamepads_[i]) ++count; + } + return count; +} + +bool InputSvc::isGamepadBtnDown(int32 idx, GamepadBtn btn) const { + if (idx < 0 || idx >= MAX_GAMEPADS) return false; + if (!gamepads_[idx]) return false; + size_t b = static_cast(btn); + if (b >= static_cast(GamepadBtn::Count)) return false; + return padState_[idx][b] != 0; +} + +bool InputSvc::isGamepadBtnPressed(int32 idx, GamepadBtn btn) const { + if (idx < 0 || idx >= MAX_GAMEPADS) return false; + if (!gamepads_[idx]) return false; + size_t b = static_cast(btn); + if (b >= static_cast(GamepadBtn::Count)) return false; + return padState_[idx][b] != 0 && padPrev_[idx][b] == 0; +} + +float InputSvc::getGamepadAxis(int32 idx, GamepadAxis axis) const { + if (idx < 0 || idx >= MAX_GAMEPADS) return 0.0f; + if (!gamepads_[idx]) return 0.0f; + + SDL_GameControllerAxis sdlAxis = SDL_CONTROLLER_AXIS_INVALID; + switch (axis) { + case GamepadAxis::LeftX: sdlAxis = SDL_CONTROLLER_AXIS_LEFTX; break; + case GamepadAxis::LeftY: sdlAxis = SDL_CONTROLLER_AXIS_LEFTY; break; + case GamepadAxis::RightX: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTX; break; + case GamepadAxis::RightY: sdlAxis = SDL_CONTROLLER_AXIS_RIGHTY; break; + case GamepadAxis::TriggerLeft: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERLEFT; break; + case GamepadAxis::TriggerRight: sdlAxis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT; break; + default: return 0.0f; + } + + int16 value = SDL_GameControllerGetAxis(gamepads_[idx], sdlAxis); + return static_cast(value) / 32767.0f; +} + +} // namespace extra2d diff --git a/src/platform/sdl2.cpp b/src/platform/sdl2.cpp new file mode 100644 index 0000000..ae9a903 --- /dev/null +++ b/src/platform/sdl2.cpp @@ -0,0 +1,66 @@ +#include + +namespace extra2d { + +bool Sdl2::inited_ = false; + +bool Sdl2::initCore() { + if (inited_) return true; + + if (SDL_Init(SDL_INIT_EVENTS) != 0) { + return false; + } + + inited_ = true; + return true; +} + +bool Sdl2::initVideo() { + if (!initCore()) return false; + + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + return false; + } + + return true; +} + +bool Sdl2::initAudio() { + if (!initCore()) return false; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { + return false; + } + + return true; +} + +bool Sdl2::initGamepad() { + if (!initCore()) return false; + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { + return false; + } + + return true; +} + +bool Sdl2::initAll() { + if (inited_) return true; + + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + return false; + } + + inited_ = true; + return true; +} + +void Sdl2::shutdown() { + if (inited_) { + SDL_Quit(); + inited_ = false; + } +} + +} // namespace extra2d diff --git a/src/platform/window.cpp b/src/platform/window.cpp new file mode 100644 index 0000000..80a70e9 --- /dev/null +++ b/src/platform/window.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include + +namespace extra2d { + +WindowSvc& WindowSvc::inst() { + static WindowSvc instance; + return instance; +} + +bool WindowSvc::init() { + if (isInited()) return true; + + if (!Sdl2::initVideo()) { + return false; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + state_ = SvcState::Inited; + return true; +} + +void WindowSvc::shutdown() { + if (glCtx_) { + SDL_GL_DeleteContext(glCtx_); + glCtx_ = nullptr; + } + if (window_) { + SDL_DestroyWindow(window_); + window_ = nullptr; + } + state_ = SvcState::Shutdown; +} + +bool WindowSvc::create(const WindowCfg& cfg) { + if (window_) return true; + + uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; + if (cfg.resizable) { + flags |= SDL_WINDOW_RESIZABLE; + } + if (cfg.fullscreen) { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, cfg.glMajor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, cfg.glMinor); + + window_ = SDL_CreateWindow( + cfg.title.c_str(), + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + cfg.width, + cfg.height, + flags + ); + + if (!window_) { + return false; + } + + glCtx_ = SDL_GL_CreateContext(window_); + if (!glCtx_) { + SDL_DestroyWindow(window_); + window_ = nullptr; + return false; + } + + setVsync(cfg.vsync); + vsync_ = cfg.vsync; + + return true; +} + +bool WindowSvc::pollEvents() { + SDL_Event evt; + while (SDL_PollEvent(&evt)) { + switch (evt.type) { + case SDL_QUIT: + shouldClose_ = true; + if (onClose_) onClose_(); + break; + case SDL_WINDOWEVENT: + if (evt.window.event == SDL_WINDOWEVENT_RESIZED) { + if (onResize_) { + onResize_(evt.window.data1, evt.window.data2); + } + } + break; + default: + if (INPUT_SVC.isInited()) { + INPUT_SVC.processEvent(evt); + } + break; + } + } + return !shouldClose_; +} + +void WindowSvc::swapBuffers() { + if (window_ && glCtx_) { + SDL_GL_SwapWindow(window_); + } +} + +Size WindowSvc::getSize() const { + if (!window_) return Size(0, 0); + int w, h; + SDL_GetWindowSize(window_, &w, &h); + return Size(w, h); +} + +Vec2 WindowSvc::getPosition() const { + if (!window_) return Vec2(0, 0); + int x, y; + SDL_GetWindowPosition(window_, &x, &y); + return Vec2(static_cast(x), static_cast(y)); +} + +void WindowSvc::setSize(int32 w, int32 h) { + if (window_) { + SDL_SetWindowSize(window_, w, h); + } +} + +void WindowSvc::setTitle(const std::string& title) { + if (window_) { + SDL_SetWindowTitle(window_, title.c_str()); + } +} + +void WindowSvc::setFullscreen(bool fullscreen) { + if (window_) { + SDL_SetWindowFullscreen(window_, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + } +} + +bool WindowSvc::isFullscreen() const { + if (!window_) return false; + uint32 flags = SDL_GetWindowFlags(window_); + return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; +} + +void WindowSvc::setVsync(bool vsync) { + SDL_GL_SetSwapInterval(vsync ? 1 : 0); + vsync_ = vsync; +} + +bool WindowSvc::isVsync() const { + return vsync_; +} + +void WindowSvc::setVisible(bool visible) { + if (window_) { + if (visible) { + SDL_ShowWindow(window_); + } else { + SDL_HideWindow(window_); + } + } +} + +bool WindowSvc::isVisible() const { + if (!window_) return false; + uint32 flags = SDL_GetWindowFlags(window_); + return (flags & SDL_WINDOW_SHOWN) != 0; +} + +} // namespace extra2d diff --git a/xmake/engine.lua b/xmake/engine.lua index 4febecf..1743216 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -24,7 +24,9 @@ function define_tbb_target() local plat = get_current_plat() if plat == "mingw" then - add_defines("_UNICODE", "UNICODE") + -- TBB uses ANSI Windows APIs, don't define UNICODE + -- MinGW doesn't define LOAD_LIBRARY_SAFE_CURRENT_DIRS + add_defines("LOAD_LIBRARY_SAFE_CURRENT_DIRS=0x00002000") elseif plat == "switch" then add_defines("__TBB_USE_THREAD_SANITIZER=0") add_defines("TBB_USE_ASSERT=0")