From 2ab07e6d6cd0cab80abc54bb4235966eba80ad90 Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sun, 13 Mar 2022 15:48:03 +0000 Subject: [PATCH] cleaner game loop code --- demo/main.cpp | 186 ++++++++++++++++++++++------------- fggl/CMakeLists.txt | 1 + fggl/app.cpp | 39 ++++++++ include/fggl/app.hpp | 126 ++++++++++++++++++++++++ include/fggl/fggl.hpp | 30 ++++++ include/fggl/util/states.hpp | 76 ++++++++++++++ 6 files changed, 387 insertions(+), 71 deletions(-) create mode 100644 fggl/app.cpp create mode 100644 include/fggl/app.hpp create mode 100644 include/fggl/util/states.hpp diff --git a/demo/main.cpp b/demo/main.cpp index 188813c..2a04b5f 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,7 +1,10 @@ +#include <fggl/ecs3/types.hpp> #include <filesystem> #include <iostream> - +#include <memory> #include <utility> + +#include <fggl/app.hpp> #include <fggl/gfx/window.hpp> #include <fggl/gfx/camera.hpp> #include <fggl/input/camera_input.h> @@ -24,6 +27,8 @@ constexpr bool showNormals = false; +std::shared_ptr<fggl::gfx::ecsOpenGLModule> glModule; + // prototype of resource discovery void discover(const std::filesystem::path& base) { @@ -93,12 +98,19 @@ void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs result->origin( targetPos ); } -class MenuScene : public fggl::scenes::Scene { +class MenuScene : public fggl::scenes::Scene, public fggl::AppState { public: - explicit MenuScene(InputManager inputs) : m_inputs(std::move(inputs)) {} + explicit MenuScene(fggl::App& app) : fggl::AppState(app), m_inputs(nullptr) {} ~MenuScene() override = default; + void activate() override { + auto& locator = fggl::util::ServiceLocator::instance(); + m_inputs = locator.providePtr<fggl::input::Input>(); + + setup(); + } + void setup() override { } @@ -107,6 +119,10 @@ public: } void update() override { + if ( !m_inputs ) { + return; + } + bool leftMouse = m_inputs->mouse.button(fggl::input::MouseButton::LEFT); if (leftMouse) { auto scenes = fggl::util::ServiceLocator::instance().providePtr<fggl::scenes::SceneManager>(); @@ -123,25 +139,39 @@ private: }; -class GameScene : public fggl::scenes::Scene { +class GameScene : public fggl::scenes::Scene, public fggl::AppState { public: - explicit GameScene(fggl::ecs3::World& world, InputManager inputs) : m_world(world), m_inputs(std::move(inputs)) { }; + explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { }; ~GameScene() override = default; + void activate() override { + auto& locator = fggl::util::ServiceLocator::instance(); + m_inputs = locator.providePtr<fggl::input::Input>(); + + auto types = locator.providePtr<fggl::ecs3::TypeRegistry>(); + m_world = std::make_unique<fggl::ecs3::World>(*types); + + m_sceneTime = std::make_unique<fggl::util::Timer>(); + m_sceneTime->frequency( glfwGetTimerFrequency() ); + m_sceneTime->setup( glfwGetTimerValue() ); + + setup(); + } + void setup() override { - auto types = m_world.types(); + auto types = m_world->types(); // create camera using strings { - auto prototype = m_world.create(false); - m_world.add(prototype, types.find(fggl::math::Transform::name)); - m_world.add(prototype, types.find(fggl::gfx::Camera::name)); - m_world.add(prototype, types.find(fggl::input::FreeCamKeys::name)); + auto prototype = m_world->create(false); + m_world->add(prototype, types.find(fggl::math::Transform::name)); + m_world->add(prototype, types.find(fggl::gfx::Camera::name)); + m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name)); - auto camTf = m_world.get<fggl::math::Transform>(prototype); + auto camTf = m_world->get<fggl::math::Transform>(prototype); camTf->origin( glm::vec3(10.0f, 3.0f, 10.0f) ); - auto cameraKeys = m_world.get<fggl::input::FreeCamKeys>(prototype); + auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype); cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W); cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S); cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A); @@ -152,10 +182,10 @@ public: fggl::ecs3::entity_t terrain; { - terrain = m_world.create(false); - m_world.add(terrain, types.find(fggl::math::Transform::name)); + terrain = m_world->create(false); + m_world->add(terrain, types.find(fggl::math::Transform::name)); - auto camTf = m_world.get<fggl::math::Transform>(terrain); + auto camTf = m_world->get<fggl::math::Transform>(terrain); camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) ); //auto terrainData = m_world.get<fggl::data::HeightMap>(terrain); @@ -171,14 +201,14 @@ public: terrainData.heightValues[x * 255 +y] = (float)noise; } } - m_world.set<fggl::data::HeightMap>(terrain, &terrainData); + m_world->set<fggl::data::HeightMap>(terrain, &terrainData); } // create foundation object fggl::ecs3::entity_t foundation; { - foundation = m_world.create(true); - m_world.add(foundation, types.find(fggl::math::Transform::name)); + foundation = m_world->create(true); + m_world->add(foundation, types.find(fggl::math::Transform::name)); // plot rendering fggl::data::Mesh mesh; @@ -188,14 +218,14 @@ public: // add mesh as a component constexpr char shader[] = "phong"; fggl::gfx::StaticMesh staticMesh{mesh, shader}; - m_world.set<fggl::gfx::StaticMesh>(foundation, &staticMesh); + m_world->set<fggl::gfx::StaticMesh>(foundation, &staticMesh); } // create building prototype fggl::ecs3::entity_t bunker; { - bunker = m_world.create(true); - m_world.add(bunker, types.find(fggl::math::Transform::name)); + bunker = m_world->create(true); + m_world->add(bunker, types.find(fggl::math::Transform::name)); // mesh int nSections = 2; @@ -221,12 +251,12 @@ public: mesh.removeDups(); fggl::gfx::StaticMesh staticMesh{mesh, shader}; - m_world.set<fggl::gfx::StaticMesh>(bunker, &staticMesh); + m_world->set<fggl::gfx::StaticMesh>(bunker, &staticMesh); } for (int i=0; i<3; ++i) { glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f); - placeObject(m_world, terrain, foundation, location); + placeObject(*m_world, terrain, foundation, location); } int nCubes = 3; @@ -234,16 +264,20 @@ public: glm::vec3 location; location.x = i * 6.f + 1.0f; location.z = -5.0f + 1.0f; - placeObject(m_world, terrain, bunker, location); + placeObject(*m_world, terrain, bunker, location); } } + void deactivate() override { + cleanup(); + } + void cleanup() override { } void update() override { - process_camera(m_world, m_inputs); + process_camera(*m_world, m_inputs); // JWR - this doesn't really seem like it belongs in the game scene... if ( m_inputs->keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F10)) ) { @@ -254,18 +288,19 @@ public: void render() override { debugInspector(); + fggl::gfx::renderMeshes(glModule, *m_world, m_sceneTime->delta()); } // entity inspector void debugInspector() { - auto types = m_world.types(); + auto types = m_world->types(); ImGui::Begin("Entities"); - auto entityItr = m_world.all(); + auto entityItr = m_world->all(); for (auto& entity : entityItr) { std::string label = "entity-" + std::to_string(entity); if ( ImGui::TreeNode(label.c_str()) ){ - auto entComp = m_world.getComponents(entity); + auto entComp = m_world->getComponents(entity); for ( auto comp : entComp ){ auto meta = types.meta(comp); ImGui::Text("%s (%d) - %lu bytes", meta->name(), comp, meta->size()); @@ -277,7 +312,8 @@ public: }; private: - fggl::ecs3::World& m_world; + std::unique_ptr<fggl::ecs3::World> m_world; + std::unique_ptr<fggl::util::Timer> m_sceneTime; InputManager m_inputs; }; @@ -326,16 +362,23 @@ void gamepadDebug(bool* visible) { ImGui::End(); } -int main(int argc, char* argv[]) { +int main(int argc, const char* argv[]) { + fggl::App app( "fggl-demo" ); + + app.add_state<MenuScene>("menu"); + app.add_state<GameScene>("game"); + auto& locator = fggl::util::ServiceLocator::instance(); // setup ECS Types - fggl::ecs3::TypeRegistry types; - types.make<fggl::math::Transform>(); - fggl::ecs3::ModuleManager modules(types); + auto types = std::make_shared< fggl::ecs3::TypeRegistry >(); + locator.supply<fggl::ecs3::TypeRegistry>( types ); + + types->make<fggl::math::Transform>(); + fggl::ecs3::ModuleManager modules(*types); // setup ECS - fggl::ecs3::World ecs(types); + //fggl::ecs3::World ecs(types); // input management auto inputs = std::make_shared<fggl::input::Input>(); @@ -352,7 +395,7 @@ int main(int argc, char* argv[]) { //discover( storage.resolvePath(fggl::data::Data, "../../packs") ); // Opengl APIs - auto glModule = modules.load<fggl::gfx::ecsOpenGLModule>(window, storage); + glModule = modules.load<fggl::gfx::ecsOpenGLModule>(window, storage); fggl::gfx::loadPipeline(glModule, "unlit", false); fggl::gfx::loadPipeline(glModule, "phong", false); fggl::gfx::loadPipeline(glModule, "normals", false); @@ -369,41 +412,42 @@ int main(int argc, char* argv[]) { // Scene management auto scenes = std::make_shared<fggl::scenes::SceneManager>(); locator.supply<fggl::scenes::SceneManager>(scenes); - scenes->create("main_menu", std::make_shared<MenuScene>(inputs)); - scenes->create("game", std::make_shared<GameScene>(ecs, inputs)); - scenes->activate("main_menu"); - - // Main game/event loop - fggl::util::Timer time{}; - time.frequency( glfwGetTimerFrequency() ); - time.setup( glfwGetTimerValue() ); - - while( !window->closeRequested() ) { - // - // Setup setup - // - time.tick( glfwGetTimerValue() ); - inputs->frame( time.delta() ); - - glfwModule->context.pollEvents(); - debug->frameStart(); - - // - // update step - // - scenes->update(); - - // render the scene - window->activate(); - glModule->ogl.clear(); - - // allow the scene to do stuff, then actually render - scenes->render(); - fggl::gfx::renderMeshes(glModule, ecs, time.delta()); - - debug->draw(); - window->swap(); - } + //scenes->create("main_menu", std::make_shared<MenuScene>(inputs)); + //scenes->create("game", std::make_shared<GameScene>(ecs, inputs)); + //scenes->activate("main_menu"); + + /* + // Main game/event loop + fggl::util::Timer time{}; + time.frequency( glfwGetTimerFrequency() ); + time.setup( glfwGetTimerValue() ); + + while( !window->closeRequested() ) { + // + // Setup setup + // + time.tick( glfwGetTimerValue() ); + inputs->frame( time.delta() ); + + glfwModule->context.pollEvents(); + debug->frameStart(); + + // + // update step + // + scenes->update(); + + // render the scene + window->activate(); + glModule->ogl.clear(); + + // allow the scene to do stuff, then actually render + scenes->render(); + + debug->draw(); + window->swap(); + } + */ - return 0; + return app.run(argc, argv); } diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index a6974e6..2fde6fd 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -9,6 +9,7 @@ endif() target_sources(${PROJECT_NAME} PRIVATE fggl.cpp + app.cpp ecs/ecs.cpp data/model.cpp data/procedural.cpp diff --git a/fggl/app.cpp b/fggl/app.cpp new file mode 100644 index 0000000..a1b7b06 --- /dev/null +++ b/fggl/app.cpp @@ -0,0 +1,39 @@ + +#include <cstdlib> +#include <fggl/app.hpp> +#include <fggl/util/states.hpp> + +namespace fggl { + + App::App(const Identifer& name) : App::App( name, name ) { + } + + App::App(const Identifer& name, const Identifer& folder ) : m_running(true), m_states() { + } + + int App::run(int argc, const char** argv) { + + { + // activate the first state + auto& state = m_states.active(); + state.activate(); + } + + while ( m_running ) { + auto& state = m_states.active(); + + state.update(); + state.render(); + } + + { + // shutdown last state + auto& state = m_states.active(); + state.deactivate(); + } + + return EXIT_SUCCESS; + } + +} //namespace fggl + diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp new file mode 100644 index 0000000..04f2cc3 --- /dev/null +++ b/include/fggl/app.hpp @@ -0,0 +1,126 @@ +/** + * FOSS Galaxy Game Library + * Copyright (c) 2022 Joseph Walton-Rivers + * webpigeon@fossgalaxy.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FGGL_APP_H +#define FGGL_APP_H + +#define assertm(exp, msg) assert(((void)msg, exp)) + +#include <cassert> +#include <string> +#include <memory> +#include <unordered_map> +#include <fggl/util/states.hpp> + +namespace fggl { + + class App; + class AppState; + + using Identifer = std::string; + using AppMachine = util::StateMachine<AppState, Identifer>; + + class AppState { + public: + /** + * Create an Application State. + * + * A state is responsible for managing user interaction with the app. When created, the appstate + * is passed a reference to the application that owns it. The lifetime of the state is bounded + * by the lifetype of this object. + * + * @param owner a non-owned reference to the owner of the state. + */ + explicit AppState(App& owner) : m_owner( owner ) {} + + /** + * Update the underlying model of this state. + * + * States should not assume that one update means one render call, as the game loop may issue + * multiple updates per render or vice-versa depending on requriements. Update is intended for + * dispatching game-system related infomation. + */ + virtual void update() = 0; + + /** + * Perform actions neccerary for rendering the scene. + * + * When this method is invoked it is safe to assume that rendering of some form will take place + * after it returns and the rendering state should be updated to reflect this. The rendering + * environment will be passed in as an argument. + * + * It is not safe to assume the render target will always be the same, as the scene may be + * rendered in mutliple passes (eg, for VR requirements). + */ + virtual void render() = 0; + + virtual void activate() {} + virtual void deactivate() {} + + protected: + App& m_owner; + }; + + class App { + public: + explicit App(const Identifer& name); + App(const Identifer& name, const Identifer& folderName); + + // class is non copy-able + App(const App& app) = delete; + App& operator=(App other) = delete; + + /** + * Perform main game loop functions. + */ + int run(int argc, const char** argv); + + template<typename T> + void add_state(const Identifer& name) { + static_assert( std::is_base_of<AppState,T>::value, "States must be AppStates"); + m_states.put<T>(name, *this); + } + + inline void change_state(const Identifer& name) { + m_states.active().deactivate(); + m_states.change(name); + m_states.active().activate(); + } + + inline AppState& active_state() const { + return m_states.active(); + } + + inline bool running() const { + return m_running; + } + + inline void running(bool state) { + m_running = state; + } + + private: + bool m_running; + AppMachine m_states; + }; + +} + +#endif diff --git a/include/fggl/fggl.hpp b/include/fggl/fggl.hpp index e69de29..2363995 100644 --- a/include/fggl/fggl.hpp +++ b/include/fggl/fggl.hpp @@ -0,0 +1,30 @@ +/** + * FOSS Galaxy Game Library + * Copyright (c) 2022 Joseph Walton-Rivers + * webpigeon@fossgalaxy.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FGGL_H +#define FGGL_H + +#include <fggl/app.hpp> + +namespace fggl { + +} + +#endif diff --git a/include/fggl/util/states.hpp b/include/fggl/util/states.hpp new file mode 100644 index 0000000..66ca6d3 --- /dev/null +++ b/include/fggl/util/states.hpp @@ -0,0 +1,76 @@ +/** + * FOSS Galaxy Game Library + * Copyright (c) 2022 Joseph Walton-Rivers + * webpigeon@fossgalaxy.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FGGL_UTIL_STATES_H +#define FGGL_UTIL_STATES_H + +#include <cassert> +#include <string> +#include <memory> +#include <unordered_map> + +namespace fggl::util { + + template<typename S, typename I> + class StateMachine { + public: + using Identifer = I; + using StateType = S; + + StateMachine() = default; + ~StateMachine() = default; + + // class is non copy-able + StateMachine(const StateMachine& app) = delete; + StateMachine& operator=(StateMachine other) = delete; + + template<typename T, typename... Args> + void put(const Identifer& name, Args&&... args) { + static_assert( std::is_base_of<StateType,T>::value, "States must be AppStates"); + m_states[name] = std::make_unique<T>( std::forward<Args...>(args...) ); + + // no active scene? first is the active one + if ( m_active.empty() ) { + m_active = name; + } + } + + void change(const Identifer& name) { + assertm( m_states.find(name) != m_states.end(), "state does not exist"); + + active().deactivate(); + m_active = name; + active().activate(); + } + + StateType& active() const { + assertm( m_states.find(m_active) != m_states.end(), "active state does not exist!"); + return *(m_states.at( m_active ).get()); + } + + private: + Identifer m_active; + std::unordered_map<Identifer, std::unique_ptr<StateType>> m_states; + + }; + +} + +#endif -- GitLab