ADD: Integrate SceneManager into Application, derive scene for LoadingScreen, closes #17
All checks were successful
Tests / test (push) Successful in 2m34s

This commit is contained in:
Sebastian Böckelmann 2026-04-21 17:33:48 +02:00
parent 8ea01b7850
commit d84995bd29
17 changed files with 277 additions and 62 deletions

View File

@ -313,6 +313,12 @@ if(BUILD_GAME)
src/engine/renderer/loader/async/AssetLoadingProgressEvent.h src/engine/renderer/loader/async/AssetLoadingProgressEvent.h
src/engine/core/scenes/Scene.cpp src/engine/core/scenes/Scene.cpp
src/engine/core/scenes/Scene.h src/engine/core/scenes/Scene.h
src/engine/core/scenes/SceneManager.cpp
src/engine/core/scenes/SceneManager.h
src/engine/core/scenes/SplashScreen.cpp
src/engine/core/scenes/SplashScreen.h
src/engine/core/scenes/SplashScreenLayer.cpp
src/engine/core/scenes/SplashScreenLayer.h
) )
target_compile_options(Dicewars_Siedler PRIVATE target_compile_options(Dicewars_Siedler PRIVATE
-Wall -Wall

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -5,10 +5,10 @@
#include "Application.h" #include "Application.h"
#include "EngineTime.h" #include "EngineTime.h"
#include "../layer/Layer.h"
#include "../platform/glfw/InputManager.h" #include "../platform/glfw/InputManager.h"
#include "inputsOutputs/stateControl/StateRegistry.h" #include "inputsOutputs/stateControl/StateRegistry.h"
#include "../core/scenes/SceneManager.h"
#include "../core/scenes/Scene.h"
Application* Application::instance = nullptr; Application* Application::instance = nullptr;
void Application::updateTime() { void Application::updateTime() {
@ -20,14 +20,10 @@ void Application::updateTime() {
// printf("Frametime: %f\n", EngineTime::deltaTime); // printf("Frametime: %f\n", EngineTime::deltaTime);
} }
void Application::pushLayer(Layer* layer) {
layers.push_back(layer);
layer->onAttach();
}
Application::Application() Application::Application()
{ {
instance = this; instance = this;
sceneManager = std::make_unique<SceneManager>();
WindowProps window_props = WindowProps(); WindowProps window_props = WindowProps();
window_props.Width = 1280; window_props.Width = 1280;
@ -50,11 +46,9 @@ void Application::run() {
{ {
window->OnUpdate(); window->OnUpdate();
updateTime(); updateTime();
for (Layer* layer : layers)
layer->onUpdate();
for (Layer* layer : layers) sceneManager->update();
layer->onRender(); sceneManager->render();
InputManager::update(); InputManager::update();

View File

@ -15,7 +15,8 @@
#include "inputsOutputs/stateControl/states/State.h" #include "inputsOutputs/stateControl/states/State.h"
class Layer;
class SceneManager;
class Application class Application
{ {
@ -31,22 +32,17 @@ public:
std::unique_ptr<Keyboard> keyboard; std::unique_ptr<Keyboard> keyboard;
std::unique_ptr<Mouse> mouse; std::unique_ptr<Mouse> mouse;
std::unique_ptr<StateManager> stateManager; std::unique_ptr<StateManager> stateManager;
std::shared_ptr<GameState> gameState; std::shared_ptr<GameState> gameState;
std::unique_ptr<SceneManager> sceneManager;
private: private:
bool running = true; bool running = true;
std::unique_ptr<Window> window; std::unique_ptr<Window> window;
static Application* instance; static Application* instance;
std::vector<Layer*> layers;
void updateTime(); void updateTime();
float lastFrame; float lastFrame;
protected:
void pushLayer(Layer* layer);
}; };

View File

@ -21,12 +21,16 @@ namespace LayoutEngine {
std::vector<ComputedLayout> children; std::vector<ComputedLayout> children;
}; };
inline float resolveDim(const SizeValue size, float parentSize, float axisLength) { inline float resolveDim(const SizeValue size, float parentSize, float axisLength, float screenWidth, float screenHeight) {
switch (size.unit) { switch (size.unit) {
case SizeUnit::Percent: case SizeUnit::Percent:
return size.value * parentSize; return size.value * parentSize;
case SizeUnit::Pixels: case SizeUnit::Pixels:
return size.value / axisLength; // has to be normalized! return size.value / axisLength; // has to be normalized!
case SizeUnit::Vmin: {
float vmin = std::min(screenWidth, screenHeight);
return (size.value * vmin) / axisLength; // normalisiert
}
default: default:
return 0.0f; return 0.0f;
} }
@ -34,10 +38,10 @@ namespace LayoutEngine {
inline Dimensions computeSelfDimensions(const NodeLayout &node, const Dimensions &parent, float screenWidth, float screenHeight) { inline Dimensions computeSelfDimensions(const NodeLayout &node, const Dimensions &parent, float screenWidth, float screenHeight) {
Dimensions d = {}; Dimensions d = {};
d.x = parent.x + resolveDim(node.layoutStyle.margin.left, parent.width, screenWidth); d.x = parent.x + resolveDim(node.layoutStyle.margin.left, parent.width, screenWidth, screenWidth, screenHeight);
d.y = parent.y + resolveDim(node.layoutStyle.margin.top, parent.height, screenHeight); d.y = parent.y + resolveDim(node.layoutStyle.margin.top, parent.height, screenHeight, screenWidth, screenHeight);
d.width = resolveDim(node.layoutStyle.width, parent.width, screenWidth); d.width = resolveDim(node.layoutStyle.width, parent.width, screenWidth, screenWidth, screenHeight);
d.height = resolveDim(node.layoutStyle.height, parent.height, screenHeight); d.height = resolveDim(node.layoutStyle.height, parent.height, screenHeight, screenWidth, screenHeight);
return d; return d;
} }
@ -81,10 +85,10 @@ namespace LayoutEngine {
result.children.resize(node.children.size()); result.children.resize(node.children.size());
//Padding auflösen //Padding auflösen
float padLeft = resolveDim(node.layoutStyle.padding.left, result.self.width, screenWidth); float padLeft = resolveDim(node.layoutStyle.padding.left, result.self.width, screenWidth, screenWidth, screenHeight);
float padRight = resolveDim(node.layoutStyle.padding.right, result.self.width, screenWidth); float padRight = resolveDim(node.layoutStyle.padding.right, result.self.width, screenWidth, screenWidth, screenHeight);
float padTop = resolveDim(node.layoutStyle.padding.top, result.self.height, screenHeight); float padTop = resolveDim(node.layoutStyle.padding.top, result.self.height, screenHeight, screenWidth, screenHeight);
float padBottom = resolveDim(node.layoutStyle.padding.bottom, result.self.height, screenHeight); float padBottom = resolveDim(node.layoutStyle.padding.bottom, result.self.height, screenHeight, screenWidth, screenHeight);
// Available space for children // Available space for children
Dimensions innerArea = { Dimensions innerArea = {
@ -105,8 +109,8 @@ namespace LayoutEngine {
const float mainSize = isColumn ? childLayout.self.height : childLayout.self.width; const float mainSize = isColumn ? childLayout.self.height : childLayout.self.width;
const float marginMain = isColumn const float marginMain = isColumn
? resolveDim(child.layoutStyle.margin.top, parent.height, screenHeight) + resolveDim(child.layoutStyle.margin.bottom, parent.height, screenHeight) ? resolveDim(child.layoutStyle.margin.top, parent.height, screenHeight, screenWidth, screenHeight) + resolveDim(child.layoutStyle.margin.bottom, parent.height, screenHeight, screenWidth, screenHeight)
: resolveDim(child.layoutStyle.margin.left, parent.width, screenWidth) + resolveDim(child.layoutStyle.margin.right, parent.width, screenWidth); : resolveDim(child.layoutStyle.margin.left, parent.width, screenWidth, screenWidth, screenHeight) + resolveDim(child.layoutStyle.margin.right, parent.width, screenWidth, screenWidth, screenHeight);
totalMainSize += mainSize + marginMain; totalMainSize += mainSize + marginMain;
} }
@ -128,15 +132,15 @@ namespace LayoutEngine {
cursor += gap; cursor += gap;
if (isColumn) { if (isColumn) {
float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight); float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight, screenWidth, screenHeight);
float marginBottom = resolveDim(child.layoutStyle.margin.bottom, innerArea.height, screenHeight); float marginBottom = resolveDim(child.layoutStyle.margin.bottom, innerArea.height, screenHeight, screenWidth, screenHeight);
offsetLayout(childLayout, 0.0f, cursor); offsetLayout(childLayout, 0.0f, cursor);
cursor += childLayout.self.height + marginTop + marginBottom; cursor += childLayout.self.height + marginTop + marginBottom;
if (i + 1 < node.children.size()) if (i + 1 < node.children.size())
cursor += node.layoutStyle.gap; cursor += node.layoutStyle.gap;
} else { } else {
float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth); float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth, screenWidth, screenHeight);
float marginRight = resolveDim(child.layoutStyle.margin.right, innerArea.width, screenWidth); float marginRight = resolveDim(child.layoutStyle.margin.right, innerArea.width, screenWidth, screenWidth, screenHeight);
offsetLayout(childLayout, cursor, 0.0f); offsetLayout(childLayout, cursor, 0.0f);
cursor += childLayout.self.width + marginLeft + marginRight; cursor += childLayout.self.width + marginLeft + marginRight;
if (i + 1 < node.children.size()) if (i + 1 < node.children.size())
@ -149,7 +153,7 @@ namespace LayoutEngine {
auto& childLayout = result.children[i]; auto& childLayout = result.children[i];
if (isColumn) { if (isColumn) {
float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth); float marginLeft = resolveDim(child.layoutStyle.margin.left, innerArea.width, screenWidth, screenWidth, screenHeight);
float remainingCross = innerArea.width - childLayout.self.width; float remainingCross = innerArea.width - childLayout.self.width;
float oldX = childLayout.self.x; float oldX = childLayout.self.x;
switch (node.layoutStyle.alignItems) { switch (node.layoutStyle.alignItems) {
@ -163,7 +167,7 @@ namespace LayoutEngine {
} }
offsetChildLayouts(childLayout, childLayout.self.x - oldX, 0.0f); offsetChildLayouts(childLayout, childLayout.self.x - oldX, 0.0f);
} else { } else {
float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight); float marginTop = resolveDim(child.layoutStyle.margin.top, innerArea.height, screenHeight, screenWidth, screenHeight);
float remainingCross = innerArea.height - childLayout.self.height; float remainingCross = innerArea.height - childLayout.self.height;
float oldY = childLayout.self.y; float oldY = childLayout.self.y;
switch (node.layoutStyle.alignItems) { switch (node.layoutStyle.alignItems) {

View File

@ -7,7 +7,8 @@
enum class SizeUnit { enum class SizeUnit {
Pixels, Pixels,
Percent Percent,
Vmin
}; };
struct SizeValue { struct SizeValue {

View File

@ -17,5 +17,6 @@ void Scene::onRender() {
} }
void Scene::addLayer(std::unique_ptr<Layer> layer) { void Scene::addLayer(std::unique_ptr<Layer> layer) {
layer->onAttach();
layers.push_back(std::move(layer)); layers.push_back(std::move(layer));
} }

View File

@ -8,13 +8,19 @@
#include <vector> #include <vector>
#include "../../layer/Layer.h" #include "../../layer/Layer.h"
#include "../../renderer/loader/async/AssetLoader.h"
class Scene { class Scene {
public: public:
virtual ~Scene() = default; virtual ~Scene() = default;
virtual void onEnter() {} //Layer registrieren virtual void onEnter() {} //Layer registrieren
virtual void onExit() {} //Aufräumen, Daten unloaden virtual void onExit() {
for (const auto& layer : layers) {
layer->onDetach();
}
} //Aufräumen, Daten unloaden
virtual std::vector<AssetRequest> getRequiredAssets() {return {};}
void onUpdate(); void onUpdate();
void onRender(); void onRender();

View File

@ -0,0 +1,47 @@
//
// Created by sebastian on 21.04.26.
//
#include "SceneManager.h"
#include "Scene.h"
#include "spdlog/spdlog.h"
void SceneManager::switchTo(std::unique_ptr<Scene> next) {
nextScene = std::move(next);
std::vector<AssetRequest> requestedAssets = nextScene->getRequiredAssets();
spdlog::debug("Required assets: {}", requestedAssets.size());
for (AssetRequest& request : requestedAssets) {
assetLoader.scheduleAsset(request);
}
assetLoader.start();
}
void SceneManager::update() {
if (nextScene) {
assetLoader.processUploadQueue(2);
if (assetLoader.getProgress().isDone()) {
if (currentScene) {
currentScene->onExit();
}
currentScene = std::move(nextScene);
nextScene = nullptr;
currentScene->onEnter();
assetLoader.stop();
}
}
if (currentScene) {
currentScene->onUpdate();
}
}
void SceneManager::render() {
if (currentScene) {
currentScene->onRender();
}
}
SceneManager::~SceneManager() = default;

View File

@ -0,0 +1,29 @@
//
// Created by sebastian on 21.04.26.
//
#ifndef SCENEMANAGER_H
#define SCENEMANAGER_H
#include <memory>
#include "../../renderer/loader/async/AssetLoader.h""
class Scene;
class SceneManager {
public:
void switchTo(std::unique_ptr<Scene> next);
void update();
void render();
~SceneManager();
private:
std::unique_ptr<Scene> currentScene;
std::unique_ptr<Scene> nextScene;
AssetLoader assetLoader;
bool isTransitioning = false;
};
#endif //SCENEMANAGER_H

View File

@ -0,0 +1,21 @@
//
// Created by sebastian on 21.04.26.
//
#include "SplashScreen.h"
#include "SplashScreenLayer.h"
std::vector<AssetRequest> SplashScreen::getRequiredAssets() {
std::vector<AssetRequest> requests;
requests.push_back(TextureRequest("logo", "assets/logo.png"));
return requests;
}
void SplashScreen::onEnter() {
addLayer(std::make_unique<SplashScreenLayer>());
}
void SplashScreen::onExit() {
Scene::onExit();
}

View File

@ -0,0 +1,20 @@
//
// Created by sebastian on 21.04.26.
//
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include "Scene.h"
class SplashScreen : public Scene {
public:
std::vector<AssetRequest> getRequiredAssets() override;
void onEnter() override;
void onExit() override;
};
#endif //SPLASHSCREEN_H

View File

@ -0,0 +1,49 @@
//
// Created by sebastian on 21.04.26.
//
#include "SplashScreenLayer.h"
#include "../../renderer/loader/AssetManager.h"
#include "../../renderer/GUIRenderer.h"
#include "../gui/uiComponent/UiImage.h"
#include "../gui/uiMain/UiContainer.h"
#include "spdlog/spdlog.h"
SplashScreenLayer::SplashScreenLayer() {
guiRenderer = std::make_unique<GUIRenderer>(Loader::instance());
}
void SplashScreenLayer::onRender() {
UiRenderBundle renderBundle;
if (rootContainer) {
rootContainer->collectRenderData(renderBundle);
}
auto guis = renderBundle.getGUITextures();
guiRenderer->render(guis);
}
void SplashScreenLayer::onUpdate() {
Dimensions rootParent {0.0, 0.0, 1.0, 1.0f};
rootContainer->uiPositioner.compute(rootParent);
}
void SplashScreenLayer::onAttach() {
rootContainer = std::make_unique<UiContainer>();
auto logoLayoutStyle = LayoutStyle();
logoLayoutStyle.width = SizeValue(0.5f, SizeUnit::Vmin);
logoLayoutStyle.height = SizeValue(0.5f, SizeUnit::Vmin);
rootContainer->getLayoutStyle().justifyContent = JustifyContent::Center;
rootContainer->getLayoutStyle().alignItems = AlignItems::Center;
rootContainer->addChild<UiImage>(std::make_unique<UiImage>(AssetManager::getTexture("logo")->getTextureID(), logoLayoutStyle));
}
void SplashScreenLayer::onDetach() {
Layer::onDetach();
}
SplashScreenLayer::~SplashScreenLayer() = default;

View File

@ -0,0 +1,26 @@
//
// Created by sebastian on 21.04.26.
//
#ifndef SPLASHSCREENLAYER_H
#define SPLASHSCREENLAYER_H
#include "../../layer/Layer.h"
#include "../../renderer/GUIRenderer.h"
#include "../../core/gui/uiMain/UiContainer.h"
class SplashScreenLayer: public Layer {
public:
SplashScreenLayer();
virtual void onRender() override;
virtual void onUpdate() override;
virtual void onAttach() override;
virtual void onDetach() override;
~SplashScreenLayer() override;
private:
std::unique_ptr<UiContainer> rootContainer;
std::unique_ptr<GUIRenderer> guiRenderer;
};
#endif //SPLASHSCREENLAYER_H

View File

@ -12,34 +12,28 @@
#include "../OBJLoader.h" #include "../OBJLoader.h"
#include "../TextureLoader.h" #include "../TextureLoader.h"
#include "../../../core/events/EventBus.h" #include "../../../core/events/EventBus.h"
#include "spdlog/spdlog.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
void AssetLoader::scheduleTexture(const std::string &name, const std::string &path) {
{
std::lock_guard lock(pendingMutex);
pendingQueue.push(TextureRequest{name, path});
++total;
}
pendingCV.notify_one();
}
void AssetLoader::scheduleModel(const std::string &name, const std::string &objPath) { void AssetLoader::scheduleAsset(const AssetRequest &assetRequest) {
{ {
std::lock_guard lock(pendingMutex); std::lock_guard lock(pendingMutex);
pendingQueue.push(ModelRequest{name, objPath}); pendingQueue.push(assetRequest);
++total; ++total;
} }
pendingCV.notify_one(); pendingCV.notify_one();
} std::visit([]<typename T>(const T& req) -> void {
using Type = std::decay_t<T>;
void AssetLoader::scheduleAsset(const std::string &name, const std::string &assetPath) { if constexpr (std::is_same_v<Type, TextureRequest>) {
{ spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path);
std::lock_guard lock(pendingMutex); } else if constexpr (std::is_same_v<Type, ModelRequest>) {
pendingQueue.push(StagedModelRequest{name, assetPath}); spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.objPath);
++total; } else if constexpr (std::is_same_v<Type, StagedModelRequest>) {
spdlog::debug("Scheduled Staged Model Request: {} -> {}", req.name, req.assetPath);
} }
pendingCV.notify_one(); }, assetRequest);
} }
void AssetLoader::start() { void AssetLoader::start() {
@ -97,6 +91,7 @@ void AssetLoader::loadingThreadFunc() {
} }
RawTextureData AssetLoader::processTextureRequest(const TextureRequest &request) { RawTextureData AssetLoader::processTextureRequest(const TextureRequest &request) {
spdlog::debug("Processing Texture Request: {} -> {}", request.name, request.path);
RawTextureData textureData = TextureLoader::loadRawTextureData(request.path, request.flipVertically); RawTextureData textureData = TextureLoader::loadRawTextureData(request.path, request.flipVertically);
textureData.name = request.name; textureData.name = request.name;
return textureData; return textureData;

View File

@ -5,6 +5,7 @@
#ifndef ASSETLOADER_H #ifndef ASSETLOADER_H
#define ASSETLOADER_H #define ASSETLOADER_H
#include <condition_variable> #include <condition_variable>
#include <format>
#include <queue> #include <queue>
#include <string> #include <string>
#include <variant> #include <variant>
@ -39,6 +40,27 @@ struct StagedModelRequest {
using AssetRequest = std::variant<TextureRequest, ModelRequest, StagedModelRequest>; using AssetRequest = std::variant<TextureRequest, ModelRequest, StagedModelRequest>;
using IntermediateAsset = std::variant<RawTextureData, RawModelData, RawStagedModelData>; using IntermediateAsset = std::variant<RawTextureData, RawModelData, RawStagedModelData>;
inline std::string assetRequestToString(const AssetRequest& request) {
return std::visit([]<typename T>(const T& req) -> std::string {
using Type = std::decay_t<T>;
if constexpr (std::is_same_v<Type, TextureRequest>) {
return std::format("TextureRequest{{name='{}', path='{}'}}", req.name, req.path);
} else if constexpr (std::is_same_v<Type, ModelRequest>) {
return std::format("ModelRequest{{name='{}', objPath='{}'}}", req.name, req.objPath);
} else if constexpr (std::is_same_v<Type, StagedModelRequest>) {
return std::format("StagedModelRequest{{name='{}', assetPath='{}'}}", req.name, req.assetPath);
}
}, request);
}
template <>
struct std::formatter<AssetRequest> {
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
auto format(const AssetRequest& request, std::format_context& ctx) const {
return std::format_to(ctx.out(), "{}", assetRequestToString(request));
}
};
struct LoadingProgress { struct LoadingProgress {
int total; int total;
int loaded; int loaded;
@ -52,9 +74,7 @@ struct LoadingProgress {
class AssetLoader { class AssetLoader {
public: public:
void scheduleTexture(const std::string& name, const std::string& path); void scheduleAsset(const AssetRequest& assetRequest);
void scheduleModel(const std::string& name, const std::string& objPath);
void scheduleAsset(const std::string& name, const std::string& assetPath);
void start(); void start();
void stop(); void stop();

View File

@ -4,9 +4,9 @@
#include "DicewarsApp.h" #include "DicewarsApp.h"
#include "GameLayer.h" #include "../engine/core/scenes/SceneManager.h"
#include "UILayer.h" #include "../engine/core/scenes/SplashScreen.h"
DicewarsApp::DicewarsApp() { DicewarsApp::DicewarsApp() {
sceneManager->switchTo(std::make_unique<SplashScreen>());
} }