feat: 添加任务系统并实现异步资源加载功能
添加任务系统(TaskSystem)支持多线程任务处理和主线程回调 扩展Actor类添加更新监听器功能 新增GameWorld和GameTown场景类 添加多种混合模式(BlendMode)支持 实现异步资源加载界面和流程
This commit is contained in:
@@ -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<void(Actor&, float)>;
|
||||
using EventCallback = std::function<bool(const Event&)>;
|
||||
|
||||
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<UpdateListener> 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;
|
||||
};
|
||||
|
||||
142
Frostbite2D/include/frostbite2D/core/task_system.h
Normal file
142
Frostbite2D/include/frostbite2D/core/task_system.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
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 <typename F>
|
||||
void post(F&& task) {
|
||||
enqueueWorkerTask(Function<void()>(std::forward<F>(task)));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto submit(F&& task) -> std::future<std::invoke_result_t<F>> {
|
||||
using Result = std::invoke_result_t<F>;
|
||||
|
||||
auto packagedTask =
|
||||
std::make_shared<std::packaged_task<Result()>>(std::forward<F>(task));
|
||||
std::future<Result> future = packagedTask->get_future();
|
||||
|
||||
enqueueWorkerTask([packagedTask]() mutable { (*packagedTask)(); });
|
||||
return future;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void postToMainThread(F&& callback) {
|
||||
enqueueMainThreadTask(Function<void()>(std::forward<F>(callback)));
|
||||
}
|
||||
|
||||
template <typename Task, typename OnComplete>
|
||||
void submitThen(Task&& task, OnComplete&& onComplete) {
|
||||
using Result = std::invoke_result_t<Task>;
|
||||
using Storage = std::decay_t<Result>;
|
||||
|
||||
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
|
||||
auto completeFn =
|
||||
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
|
||||
|
||||
enqueueWorkerTask([this, taskFn, completeFn]() mutable {
|
||||
try {
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
std::invoke(*taskFn);
|
||||
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
|
||||
} else {
|
||||
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
|
||||
enqueueMainThreadTask([completeFn, result]() mutable {
|
||||
std::invoke(*completeFn, std::move(*result));
|
||||
});
|
||||
}
|
||||
} catch (...) {
|
||||
logUnhandledTaskException(std::current_exception());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Task, typename OnComplete, typename OnError>
|
||||
void submitThen(Task&& task, OnComplete&& onComplete, OnError&& onError) {
|
||||
using Result = std::invoke_result_t<Task>;
|
||||
using Storage = std::decay_t<Result>;
|
||||
|
||||
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
|
||||
auto completeFn =
|
||||
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
|
||||
auto errorFn =
|
||||
std::make_shared<std::decay_t<OnError>>(std::forward<OnError>(onError));
|
||||
|
||||
enqueueWorkerTask([this, taskFn, completeFn, errorFn]() mutable {
|
||||
try {
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
std::invoke(*taskFn);
|
||||
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
|
||||
} else {
|
||||
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
|
||||
enqueueMainThreadTask([completeFn, result]() mutable {
|
||||
std::invoke(*completeFn, std::move(*result));
|
||||
});
|
||||
}
|
||||
} catch (...) {
|
||||
auto error = std::make_shared<std::exception_ptr>(std::current_exception());
|
||||
enqueueMainThreadTask([errorFn, error]() mutable { std::invoke(*errorFn, *error); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void drainMainThreadTasks(uint32 maxTasks = 0);
|
||||
|
||||
private:
|
||||
TaskSystem() = default;
|
||||
~TaskSystem();
|
||||
|
||||
void enqueueWorkerTask(Function<void()> task);
|
||||
void enqueueMainThreadTask(Function<void()> task);
|
||||
size_t resolveWorkerCount(uint32 requestedCount) const;
|
||||
static void logUnhandledTaskException(std::exception_ptr error);
|
||||
|
||||
TaskSystemConfig config_;
|
||||
std::vector<std::thread> workers_;
|
||||
std::queue<Function<void()>> workerTasks_;
|
||||
std::queue<Function<void()>> mainThreadTasks_;
|
||||
|
||||
mutable std::mutex workerMutex_;
|
||||
mutable std::mutex mainThreadMutex_;
|
||||
std::condition_variable workerCondition_;
|
||||
|
||||
std::atomic<bool> initialized_ = false;
|
||||
std::atomic<bool> acceptingTasks_ = false;
|
||||
std::thread::id mainThreadId_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
void drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture);
|
||||
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
|
||||
Ptr<Texture> 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_;
|
||||
|
||||
@@ -11,6 +11,8 @@ enum class BlendMode {
|
||||
None,
|
||||
Normal,
|
||||
Additive,
|
||||
Screen,
|
||||
Premultiplied,
|
||||
Subtractive,
|
||||
Multiply
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <frostbite2D/audio/audio_system.h>
|
||||
#include <frostbite2D/base/object_registry.h>
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/core/task_system.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/event/key_event.h>
|
||||
#include <frostbite2D/event/mouse_event.h>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
207
Frostbite2D/src/frostbite2D/core/task_system.cpp
Normal file
207
Frostbite2D/src/frostbite2D/core/task_system.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <frostbite2D/core/task_system.h>
|
||||
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
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<uint32>(resolveWorkerCount(0));
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> workerLock(workerMutex_);
|
||||
std::queue<Function<void()>> emptyWorkerTasks;
|
||||
std::swap(workerTasks_, emptyWorkerTasks);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> mainThreadLock(mainThreadMutex_);
|
||||
std::queue<Function<void()>> 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<void()> task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<std::mutex> workerLock(workerMutex_);
|
||||
std::queue<Function<void()>> emptyWorkerTasks;
|
||||
std::swap(workerTasks_, emptyWorkerTasks);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> mainThreadLock(mainThreadMutex_);
|
||||
std::queue<Function<void()>> 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<void()> task;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<void()> task) {
|
||||
if (!acceptingTasks_) {
|
||||
throw std::runtime_error("TaskSystem is not accepting new worker tasks");
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(workerMutex_);
|
||||
workerTasks_.push(std::move(task));
|
||||
}
|
||||
|
||||
workerCondition_.notify_one();
|
||||
}
|
||||
|
||||
void TaskSystem::enqueueMainThreadTask(Function<void()> task) {
|
||||
if (!acceptingTasks_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> 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
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "SDL_log.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <frostbite2D/graphics/batch.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
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<BlendMode>(currentBatch_.key.blendMode));
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
|
||||
// 绑定着色器和纹理
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Game/assets/ImagePacks2/Loading0.jpg
Normal file
BIN
Game/assets/ImagePacks2/Loading0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 910 KiB |
BIN
Game/assets/ImagePacks2/Loading1.png
Normal file
BIN
Game/assets/ImagePacks2/Loading1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
BIN
Game/assets/ImagePacks2/Loading2.png
Normal file
BIN
Game/assets/ImagePacks2/Loading2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 711 B |
12
Game/include/Actor/GameTown.h
Normal file
12
Game/include/Actor/GameTown.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
class GameTown : public Actor {
|
||||
|
||||
|
||||
public:
|
||||
GameTown();
|
||||
~GameTown();
|
||||
};
|
||||
} // namespace frostbite2D
|
||||
18
Game/include/Actor/GameWorld.h
Normal file
18
Game/include/Actor/GameWorld.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <map>
|
||||
#include "GameTown.h"
|
||||
|
||||
namespace frostbite2D {
|
||||
class GameWorld : public Scene {
|
||||
private:
|
||||
/**城镇Map */
|
||||
std::map<int, RefPtr<GameTown>> m_TownMap;
|
||||
|
||||
|
||||
public:
|
||||
GameWorld();
|
||||
~GameWorld();
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -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 <SDL2/SDL.h>
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/core/window.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/graphics/renderer.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
#include <frostbite2D/resource/binary_reader.h>
|
||||
#include <frostbite2D/resource/pvf_archive.h>
|
||||
#include <frostbite2D/resource/script_parser.h>
|
||||
|
||||
#include <frostbite2D/audio/audio_system.h>
|
||||
#include <frostbite2D/audio/music.h>
|
||||
#include <frostbite2D/audio/sound.h>
|
||||
#include <frostbite2D/resource/npk_archive.h>
|
||||
|
||||
#include <frostbite2D/resource/audio_database.h>
|
||||
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||
|
||||
#include <frostbite2D/animation/animation.h>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <frostbite2D/2d/text_sprite.h>
|
||||
#include <frostbite2D/audio/audio_system.h>
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/core/task_system.h>
|
||||
#include <frostbite2D/graphics/font_manager.h>
|
||||
|
||||
#include <frostbite2D/script/squirrel_vm.h>
|
||||
#include <frostbite2D/resource/asset.h>
|
||||
#include <frostbite2D/resource/npk_archive.h>
|
||||
#include <frostbite2D/resource/pvf_archive.h>
|
||||
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
|
||||
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<Scene>();
|
||||
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<Scene>();
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user