diff --git a/Frostbite2D/include/frostbite2D/2d/actor.h b/Frostbite2D/include/frostbite2D/2d/actor.h index b6ac4d8..0015eb4 100644 --- a/Frostbite2D/include/frostbite2D/2d/actor.h +++ b/Frostbite2D/include/frostbite2D/2d/actor.h @@ -47,7 +47,13 @@ public: const UUID& GetUUID() const { return uuid_; } std::string GetUUIDString() const { return uuid_.toString(); } + /** + * @brief 获取锚点在父坐标系中的位置 + */ const Vec2& GetPosition() const { return position_; } + /** + * @brief 设置锚点在父坐标系中的位置 + */ void SetPosition(const Vec2& pos); void SetPosition(float x, float y); @@ -65,10 +71,26 @@ public: void SetSize(const Vec2& size); void SetSize(float width, float height); + /** + * @brief 获取归一化锚点,(0, 0) 为左上角,(0.5, 0.5) 为中心 + */ const Vec2& GetAnchor() const { return anchor_; } + /** + * @brief 设置归一化锚点,不会自动补偿 position + */ void SetAnchor(const Vec2& anchor); void SetAnchor(float x, float y); + /** + * @brief 获取未旋转矩形左上角在父坐标系中的位置 + */ + Vec2 GetTopLeftPosition() const; + /** + * @brief 设置未旋转矩形左上角在父坐标系中的位置 + */ + void SetTopLeftPosition(const Vec2& pos); + void SetTopLeftPosition(float x, float y); + bool IsVisible() const { return visible_; } void SetVisible(bool visible) { visible_ = visible; } @@ -93,8 +115,19 @@ public: void EnableEventIntercept() { eventInterceptEnabled_ = true; } void DisableEventIntercept() { eventInterceptEnabled_ = false; } + enum class UpdatePhase { + BeforeChildren, + AfterChildren + }; + + using UpdateCallback = Function; using EventCallback = std::function; + uint32 AddUpdateListener(UpdateCallback callback, + UpdatePhase phase = UpdatePhase::BeforeChildren); + bool RemoveUpdateListener(uint32 listenerId); + void ClearUpdateListeners(); + uint32 AddEventListener(EventType type, EventCallback callback); bool RemoveEventListener(uint32 listenerId); void ClearEventListeners(); @@ -152,6 +185,17 @@ private: int32 eventPriority_ = 0; bool eventInterceptEnabled_ = false; + struct UpdateListener { + uint32 id; + UpdatePhase phase; + UpdateCallback callback; + bool active = true; + }; + std::vector updateListeners_; + uint32 nextUpdateListenerId_ = 1; + bool iteratingUpdateListeners_ = false; + bool updateListenersDirty_ = false; + struct EventListener { uint32 id; EventType type; @@ -163,6 +207,8 @@ private: void markTransformDirty(); void updateLocalTransform() const; void updateWorldTransform() const; + void executeUpdateListeners(UpdatePhase phase, float deltaTime); + void compactUpdateListeners(); friend class Scene; }; diff --git a/Frostbite2D/include/frostbite2D/core/task_system.h b/Frostbite2D/include/frostbite2D/core/task_system.h new file mode 100644 index 0000000..00c346c --- /dev/null +++ b/Frostbite2D/include/frostbite2D/core/task_system.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace frostbite2D { + +struct TaskSystemConfig { + uint32 workerCount = 0; + uint32 maxMainThreadCallbacksPerFrame = 0; + uint32 threadStackSize = 0; +}; + +class TaskSystem { +public: + static TaskSystem& get(); + + TaskSystem(const TaskSystem&) = delete; + TaskSystem& operator=(const TaskSystem&) = delete; + + bool init(const TaskSystemConfig& config = {}); + void shutdown(); + + bool isInitialized() const { return initialized_; } + bool isMainThread() const; + size_t workerCount() const; + uint32 maxMainThreadCallbacksPerFrame() const; + + template + void post(F&& task) { + enqueueWorkerTask(Function(std::forward(task))); + } + + template + auto submit(F&& task) -> std::future> { + using Result = std::invoke_result_t; + + auto packagedTask = + std::make_shared>(std::forward(task)); + std::future future = packagedTask->get_future(); + + enqueueWorkerTask([packagedTask]() mutable { (*packagedTask)(); }); + return future; + } + + template + void postToMainThread(F&& callback) { + enqueueMainThreadTask(Function(std::forward(callback))); + } + + template + void submitThen(Task&& task, OnComplete&& onComplete) { + using Result = std::invoke_result_t; + using Storage = std::decay_t; + + auto taskFn = std::make_shared>(std::forward(task)); + auto completeFn = + std::make_shared>(std::forward(onComplete)); + + enqueueWorkerTask([this, taskFn, completeFn]() mutable { + try { + if constexpr (std::is_void_v) { + std::invoke(*taskFn); + enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); }); + } else { + auto result = std::make_shared(std::invoke(*taskFn)); + enqueueMainThreadTask([completeFn, result]() mutable { + std::invoke(*completeFn, std::move(*result)); + }); + } + } catch (...) { + logUnhandledTaskException(std::current_exception()); + } + }); + } + + template + void submitThen(Task&& task, OnComplete&& onComplete, OnError&& onError) { + using Result = std::invoke_result_t; + using Storage = std::decay_t; + + auto taskFn = std::make_shared>(std::forward(task)); + auto completeFn = + std::make_shared>(std::forward(onComplete)); + auto errorFn = + std::make_shared>(std::forward(onError)); + + enqueueWorkerTask([this, taskFn, completeFn, errorFn]() mutable { + try { + if constexpr (std::is_void_v) { + std::invoke(*taskFn); + enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); }); + } else { + auto result = std::make_shared(std::invoke(*taskFn)); + enqueueMainThreadTask([completeFn, result]() mutable { + std::invoke(*completeFn, std::move(*result)); + }); + } + } catch (...) { + auto error = std::make_shared(std::current_exception()); + enqueueMainThreadTask([errorFn, error]() mutable { std::invoke(*errorFn, *error); }); + } + }); + } + + void drainMainThreadTasks(uint32 maxTasks = 0); + +private: + TaskSystem() = default; + ~TaskSystem(); + + void enqueueWorkerTask(Function task); + void enqueueMainThreadTask(Function task); + size_t resolveWorkerCount(uint32 requestedCount) const; + static void logUnhandledTaskException(std::exception_ptr error); + + TaskSystemConfig config_; + std::vector workers_; + std::queue> workerTasks_; + std::queue> mainThreadTasks_; + + mutable std::mutex workerMutex_; + mutable std::mutex mainThreadMutex_; + std::condition_variable workerCondition_; + + std::atomic initialized_ = false; + std::atomic acceptingTasks_ = false; + std::thread::id mainThreadId_; +}; + +} // namespace frostbite2D diff --git a/Frostbite2D/include/frostbite2D/graphics/renderer.h b/Frostbite2D/include/frostbite2D/graphics/renderer.h index a7f4751..75f40ba 100644 --- a/Frostbite2D/include/frostbite2D/graphics/renderer.h +++ b/Frostbite2D/include/frostbite2D/graphics/renderer.h @@ -37,6 +37,7 @@ public: void drawSprite(const Vec2& pos, const Size& size, Ptr texture); void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize, Ptr texture, const Color& color = Color(1, 1, 1, 1)); + void setupBlendMode(BlendMode mode); ShaderManager& getShaderManager() { return shaderManager_; } Batch& getBatch() { return batch_; } @@ -45,7 +46,6 @@ private: Renderer(); // ~Renderer() 在 shutdown() 中手动调用销毁 - void setupBlendMode(BlendMode mode); void updateUniforms(); ShaderManager shaderManager_; @@ -64,4 +64,4 @@ private: Renderer& operator=(const Renderer&) = delete; }; -} \ No newline at end of file +} diff --git a/Frostbite2D/include/frostbite2D/graphics/types.h b/Frostbite2D/include/frostbite2D/graphics/types.h index 8226651..322db79 100644 --- a/Frostbite2D/include/frostbite2D/graphics/types.h +++ b/Frostbite2D/include/frostbite2D/graphics/types.h @@ -11,6 +11,8 @@ enum class BlendMode { None, Normal, Additive, + Screen, + Premultiplied, Subtractive, Multiply }; @@ -77,4 +79,4 @@ struct Quad { } }; -} \ No newline at end of file +} diff --git a/Frostbite2D/src/frostbite2D/2d/actor.cpp b/Frostbite2D/src/frostbite2D/2d/actor.cpp index 4376dc7..952c296 100644 --- a/Frostbite2D/src/frostbite2D/2d/actor.cpp +++ b/Frostbite2D/src/frostbite2D/2d/actor.cpp @@ -78,6 +78,21 @@ void Actor::SetAnchor(float x, float y) { markTransformDirty(); } +Vec2 Actor::GetTopLeftPosition() const { + return Vec2(position_.x - anchor_.x * size_.x, + position_.y - anchor_.y * size_.y); +} + +void Actor::SetTopLeftPosition(const Vec2& pos) { + position_ = Vec2(pos.x + anchor_.x * size_.x, + pos.y + anchor_.y * size_.y); + markTransformDirty(); +} + +void Actor::SetTopLeftPosition(float x, float y) { + SetTopLeftPosition(Vec2(x, y)); +} + const Transform2D& Actor::GetLocalTransform() const { if (transformDirty_) { updateLocalTransform(); @@ -126,7 +141,9 @@ Actor::~Actor() { } void Actor::Update(float deltaTime) { + executeUpdateListeners(UpdatePhase::BeforeChildren, deltaTime); UpdateChildren(deltaTime); + executeUpdateListeners(UpdatePhase::AfterChildren, deltaTime); } void Actor::Render() { @@ -219,6 +236,46 @@ void Actor::RenderChildren() { } } +uint32 Actor::AddUpdateListener(UpdateCallback callback, UpdatePhase phase) { + if (!callback) { + return 0; + } + + UpdateListener listener; + listener.id = nextUpdateListenerId_++; + listener.phase = phase; + listener.callback = std::move(callback); + updateListeners_.push_back(std::move(listener)); + return updateListeners_.back().id; +} + +bool Actor::RemoveUpdateListener(uint32 listenerId) { + for (auto& listener : updateListeners_) { + if (listener.id == listenerId && listener.active) { + listener.active = false; + if (iteratingUpdateListeners_) { + updateListenersDirty_ = true; + } else { + compactUpdateListeners(); + } + return true; + } + } + return false; +} + +void Actor::ClearUpdateListeners() { + if (iteratingUpdateListeners_) { + for (auto& listener : updateListeners_) { + listener.active = false; + } + updateListenersDirty_ = true; + return; + } + + updateListeners_.clear(); +} + uint32 Actor::AddEventListener(EventType type, EventCallback callback) { EventListener listener; listener.id = nextListenerId_++; @@ -242,6 +299,38 @@ void Actor::ClearEventListeners() { eventListeners_.clear(); } +void Actor::executeUpdateListeners(UpdatePhase phase, float deltaTime) { + if (updateListeners_.empty()) { + return; + } + + iteratingUpdateListeners_ = true; + size_t listenerCount = updateListeners_.size(); + + for (size_t i = 0; i < listenerCount; ++i) { + auto& listener = updateListeners_[i]; + if (!listener.active || listener.phase != phase || !listener.callback) { + continue; + } + listener.callback(*this, deltaTime); + } + + iteratingUpdateListeners_ = false; + if (updateListenersDirty_) { + compactUpdateListeners(); + } +} + +void Actor::compactUpdateListeners() { + updateListeners_.erase( + std::remove_if(updateListeners_.begin(), updateListeners_.end(), + [](const UpdateListener& listener) { + return !listener.active; + }), + updateListeners_.end()); + updateListenersDirty_ = false; +} + bool Actor::OnEvent(const Event& event) { if (!eventReceiveEnabled_) { return false; @@ -385,4 +474,3 @@ bool Actor::BubbleEvent(const Event& event) { } } - diff --git a/Frostbite2D/src/frostbite2D/core/application.cpp b/Frostbite2D/src/frostbite2D/core/application.cpp index dc09c45..7028466 100644 --- a/Frostbite2D/src/frostbite2D/core/application.cpp +++ b/Frostbite2D/src/frostbite2D/core/application.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,8 @@ void Application::shutdown() { running_ = false; shouldQuit_ = true; + TaskSystem::get().shutdown(); + // 单例销毁(反向初始化顺序) // 场景管理 SceneManager::get().ClearAll(); @@ -177,6 +180,11 @@ bool Application::initCoreModules() { camera_->setFlipY(true); // 启用 Y 轴翻转,(0,0) 在左上角 renderer_->setCamera(camera_); + if (!TaskSystem::get().init()) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize task system"); + return false; + } + return true; } @@ -248,6 +256,8 @@ void Application::mainLoop() { } } + TaskSystem::get().drainMainThreadTasks(); + if (!paused_) { update(); } diff --git a/Frostbite2D/src/frostbite2D/core/task_system.cpp b/Frostbite2D/src/frostbite2D/core/task_system.cpp new file mode 100644 index 0000000..27d5513 --- /dev/null +++ b/Frostbite2D/src/frostbite2D/core/task_system.cpp @@ -0,0 +1,207 @@ +#include + +#include +#include +#include +#include + +namespace frostbite2D { + +TaskSystem& TaskSystem::get() { + static TaskSystem instance; + return instance; +} + +bool TaskSystem::init(const TaskSystemConfig& config) { + if (initialized_) { + return true; + } + + try { + config_ = config; + if (config_.workerCount == 0) { + config_.workerCount = static_cast(resolveWorkerCount(0)); + } + + { + std::lock_guard workerLock(workerMutex_); + std::queue> emptyWorkerTasks; + std::swap(workerTasks_, emptyWorkerTasks); + } + + { + std::lock_guard mainThreadLock(mainThreadMutex_); + std::queue> emptyMainThreadTasks; + std::swap(mainThreadTasks_, emptyMainThreadTasks); + } + + mainThreadId_ = std::this_thread::get_id(); + acceptingTasks_ = true; + + workers_.reserve(config_.workerCount); + for (uint32 i = 0; i < config_.workerCount; ++i) { + workers_.emplace_back([this]() { + while (true) { + Function task; + + { + std::unique_lock lock(workerMutex_); + workerCondition_.wait(lock, [this]() { + return !acceptingTasks_.load() || !workerTasks_.empty(); + }); + + if (!acceptingTasks_.load() && workerTasks_.empty()) { + return; + } + + task = std::move(workerTasks_.front()); + workerTasks_.pop(); + } + + task(); + } + }); + } + + initialized_ = true; + SDL_Log("TaskSystem initialized with %u worker threads", config_.workerCount); + return true; + } catch (const std::exception& ex) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize TaskSystem: %s", ex.what()); + shutdown(); + return false; + } +} + +void TaskSystem::shutdown() { + if (!initialized_) { + return; + } + + acceptingTasks_ = false; + workerCondition_.notify_all(); + + for (std::thread& worker : workers_) { + if (worker.joinable()) { + worker.join(); + } + } + workers_.clear(); + + { + std::lock_guard workerLock(workerMutex_); + std::queue> emptyWorkerTasks; + std::swap(workerTasks_, emptyWorkerTasks); + } + + { + std::lock_guard mainThreadLock(mainThreadMutex_); + std::queue> emptyMainThreadTasks; + std::swap(mainThreadTasks_, emptyMainThreadTasks); + } + + initialized_ = false; + mainThreadId_ = std::thread::id(); + SDL_Log("TaskSystem shutdown"); +} + +bool TaskSystem::isMainThread() const { + return initialized_ && std::this_thread::get_id() == mainThreadId_; +} + +size_t TaskSystem::workerCount() const { + return workers_.size(); +} + +uint32 TaskSystem::maxMainThreadCallbacksPerFrame() const { + return config_.maxMainThreadCallbacksPerFrame; +} + +void TaskSystem::drainMainThreadTasks(uint32 maxTasks) { + if (!initialized_) { + return; + } + + uint32 taskBudget = maxTasks != 0 ? maxTasks : config_.maxMainThreadCallbacksPerFrame; + uint32 processed = 0; + + while (true) { + Function task; + + { + std::lock_guard lock(mainThreadMutex_); + if (mainThreadTasks_.empty()) { + break; + } + + if (taskBudget != 0 && processed >= taskBudget) { + break; + } + + task = std::move(mainThreadTasks_.front()); + mainThreadTasks_.pop(); + } + + task(); + ++processed; + } +} + +TaskSystem::~TaskSystem() { + shutdown(); +} + +void TaskSystem::enqueueWorkerTask(Function task) { + if (!acceptingTasks_) { + throw std::runtime_error("TaskSystem is not accepting new worker tasks"); + } + + { + std::lock_guard lock(workerMutex_); + workerTasks_.push(std::move(task)); + } + + workerCondition_.notify_one(); +} + +void TaskSystem::enqueueMainThreadTask(Function task) { + if (!acceptingTasks_) { + return; + } + + std::lock_guard lock(mainThreadMutex_); + mainThreadTasks_.push(std::move(task)); +} + +size_t TaskSystem::resolveWorkerCount(uint32 requestedCount) const { + if (requestedCount > 0) { + return requestedCount; + } + +#ifdef __SWITCH__ + return 1; +#else + unsigned int hardwareCount = std::thread::hardware_concurrency(); + if (hardwareCount <= 1) { + return 1; + } + + return std::max(1u, std::min(hardwareCount - 1, 4u)); +#endif +} + +void TaskSystem::logUnhandledTaskException(std::exception_ptr error) { + if (!error) { + return; + } + + try { + std::rethrow_exception(error); + } catch (const std::exception& ex) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TaskSystem task failed: %s", ex.what()); + } catch (...) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TaskSystem task failed with unknown exception"); + } +} + +} // namespace frostbite2D diff --git a/Frostbite2D/src/frostbite2D/graphics/batch.cpp b/Frostbite2D/src/frostbite2D/graphics/batch.cpp index 2a2212d..acbf1c8 100644 --- a/Frostbite2D/src/frostbite2D/graphics/batch.cpp +++ b/Frostbite2D/src/frostbite2D/graphics/batch.cpp @@ -1,6 +1,7 @@ #include "SDL_log.h" #include #include +#include #include namespace frostbite2D { @@ -147,6 +148,10 @@ void Batch::flushCurrentBatch() { return; } + // Apply the batch's blend state right before drawing so per-sprite + // `SetBlendMode()` changes actually affect the GL pipeline. + Renderer::get().setupBlendMode(static_cast(currentBatch_.key.blendMode)); + glBindVertexArray(vao_); // 绑定着色器和纹理 diff --git a/Frostbite2D/src/frostbite2D/graphics/renderer.cpp b/Frostbite2D/src/frostbite2D/graphics/renderer.cpp index bbbcf13..9d22d95 100644 --- a/Frostbite2D/src/frostbite2D/graphics/renderer.cpp +++ b/Frostbite2D/src/frostbite2D/graphics/renderer.cpp @@ -103,23 +103,43 @@ void Renderer::setupBlendMode(BlendMode mode) { switch (mode) { case BlendMode::None: glDisable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); break; case BlendMode::Normal: glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, + GL_ONE, GL_ONE_MINUS_SRC_ALPHA); break; case BlendMode::Additive: glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, + GL_ONE, GL_ONE); + break; + case BlendMode::Screen: + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR, + GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + break; + case BlendMode::Premultiplied: + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, + GL_ONE, GL_ONE_MINUS_SRC_ALPHA); break; case BlendMode::Subtractive: glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, + GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); break; case BlendMode::Multiply: glEnable(GL_BLEND); - glBlendFunc(GL_DST_COLOR, GL_ZERO); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA, + GL_ONE, GL_ONE_MINUS_SRC_ALPHA); break; } } @@ -195,4 +215,4 @@ void Renderer::drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texS batch_.submitQuad(quad, transform, texture, shader, BlendMode::Normal); } -} \ No newline at end of file +} diff --git a/Game/assets/ImagePacks2/Loading0.jpg b/Game/assets/ImagePacks2/Loading0.jpg new file mode 100644 index 0000000..1dc7dff Binary files /dev/null and b/Game/assets/ImagePacks2/Loading0.jpg differ diff --git a/Game/assets/ImagePacks2/Loading1.png b/Game/assets/ImagePacks2/Loading1.png new file mode 100644 index 0000000..62b5a24 Binary files /dev/null and b/Game/assets/ImagePacks2/Loading1.png differ diff --git a/Game/assets/ImagePacks2/Loading2.png b/Game/assets/ImagePacks2/Loading2.png new file mode 100644 index 0000000..a9b520b Binary files /dev/null and b/Game/assets/ImagePacks2/Loading2.png differ diff --git a/Game/include/Actor/GameTown.h b/Game/include/Actor/GameTown.h new file mode 100644 index 0000000..83755bf --- /dev/null +++ b/Game/include/Actor/GameTown.h @@ -0,0 +1,12 @@ +#pragma once +#include + +namespace frostbite2D { +class GameTown : public Actor { + + +public: + GameTown(); + ~GameTown(); +}; +} // namespace frostbite2D diff --git a/Game/include/Actor/GameWorld.h b/Game/include/Actor/GameWorld.h new file mode 100644 index 0000000..a72d89d --- /dev/null +++ b/Game/include/Actor/GameWorld.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include +#include "GameTown.h" + +namespace frostbite2D { +class GameWorld : public Scene { +private: + /**城镇Map */ + std::map> m_TownMap; + + +public: + GameWorld(); + ~GameWorld(); +}; + +} // namespace frostbite2D diff --git a/Game/src/main.cpp b/Game/src/main.cpp index 3a37193..652c2ac 100644 --- a/Game/src/main.cpp +++ b/Game/src/main.cpp @@ -1,85 +1,119 @@ -#include "SDL_log.h" -#include "frostbite2D/types/type_math.h" +#include "SDL_log.h" +#include "frostbite2D/2d/sprite.h" +#include "frostbite2D/base/RefPtr.h" #include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include +#include +#include +#include +#include #include +#include +#include +#include #include - -#include +#include +#include +#include +#include +#include +#include using namespace frostbite2D; int main(int argc, char **argv) { + (void)argc; + (void)argv; + AppConfig config = AppConfig::createDefault(); config.appName = "Frostbite2D Test App"; config.appVersion = "1.0.0"; config.windowConfig.width = 1280; config.windowConfig.height = 720; - config.windowConfig.title = "Frostbite2D - Renderer Test"; + config.windowConfig.title = "Frostbite2D - Async Init Demo"; - Application& app = Application::get(); - - // 初始化应用 + Application &app = Application::get(); if (!app.init(config)) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize application!"); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to initialize application!"); return -1; } app.run([]() { - // 初始化PVF - auto &pvf = PvfArchive::get(); - if (pvf.open("assets/Script.pvf")) { - pvf.init(); - SDL_Log("PVF initialized successfully"); - } - - // 初始化NPK - auto &npk = NpkArchive::get(); - npk.setImagePackDirectory("assets/ImagePacks2"); - npk.setDefaultImg("sprite/interface/base.img", 0); - npk.init(); - - // 初始化SNPK - auto &archive = SoundPackArchive::get(); - archive.setSoundPackDirectory("assets/SoundPacks"); - archive.init(); - - // 初始化字体 auto &fontManager = FontManager::get(); fontManager.init(); fontManager.registerFont("default", "assets/Fonts/VonwaonBitmap-12px.ttf", 12); - // 初始化音频系统 auto &audioSystem = AudioSystem::get(); audioSystem.init(); audioSystem.setMasterVolume(1.0f); audioSystem.setSoundVolume(0.8f); audioSystem.setMusicVolume(0.6f); + + SDL_Log("游戏启动"); + + auto LoadingScene = MakePtr(); + SceneManager::get().PushScene(LoadingScene); + + auto Background = Sprite::createFromFile("assets/ImagePacks2/Loading0.jpg"); + Background->SetSize(1280, 720); + LoadingScene->AddChild(Background); + + auto BackgroundBar = + Sprite::createFromFile("assets/ImagePacks2/Loading1.png"); + BackgroundBar->SetPosition(0, 686); + LoadingScene->AddChild(BackgroundBar); + + auto LoadCircleSp = + Sprite::createFromFile("assets/ImagePacks2/Loading2.png"); + LoadCircleSp->SetAnchor(Vec2(0.5f, 0.5f)); + LoadCircleSp->SetPosition(1280 / 2.0f, 686 - 60); + LoadCircleSp->SetBlendMode(BlendMode::Additive); + LoadCircleSp->AddUpdateListener([](Actor &self, float dt) { + auto rotation = self.GetRotation(); + self.SetRotation(rotation + 180.0f * dt); + }); + LoadingScene->AddChild(LoadCircleSp); + + TaskSystem::get().submitThen( + []() -> std::string { + SDL_Log("Async init task on main thread: %s", + TaskSystem::get().isMainThread() ? "true" : "false"); + + auto &pvf = PvfArchive::get(); + if (!pvf.open("assets/Script.pvf")) { + throw std::runtime_error("Failed to open assets/Script.pvf"); + } + pvf.init(); + + auto &npk = NpkArchive::get(); + npk.setImagePackDirectory("assets/ImagePacks2"); + npk.setDefaultImg("sprite/interface/base.img", 0); + npk.init(); + + auto &archive = SoundPackArchive::get(); + archive.setSoundPackDirectory("assets/SoundPacks"); + archive.init(); + + return "后台资源加载成功"; + }, + [](std::string message) mutable { + SDL_Log("后台资源加载成功"); + + auto TestScene = MakePtr(); + SceneManager::get().PushScene(TestScene); + }, + [](std::exception_ptr error) { + try { + if (error) { + std::rethrow_exception(error); + } + } catch (const std::exception &ex) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Async init failed: %s", + ex.what()); + } + }); }); SDL_Log("Application exited normally");