diff --git a/SDK/include/NDK/Application.hpp b/SDK/include/NDK/Application.hpp index 6a375776c..5eca90961 100644 --- a/SDK/include/NDK/Application.hpp +++ b/SDK/include/NDK/Application.hpp @@ -8,18 +8,34 @@ #define NDK_APPLICATION_HPP #include +#include #include #include -#include #include +#include #include +#ifndef NDK_SERVER +#include +#include +#include +#include +#include +#include +#endif + namespace Ndk { class NDK_API Application { public: + #ifndef NDK_SERVER + struct ConsoleOverlay; + struct FPSCounterOverlay; + #endif + inline Application(); + inline Application(int argc, const char* argv[]); Application(const Application&) = delete; Application(Application&&) = delete; inline ~Application(); @@ -29,8 +45,21 @@ namespace Ndk #endif template World& AddWorld(Args&&... args); + #ifndef NDK_SERVER + inline void EnableConsole(bool enable); + inline void EnableFPSCounter(bool enable); + + inline ConsoleOverlay& GetConsoleOverlay(std::size_t windowIndex = 0U); + inline FPSCounterOverlay& GetFPSCounterOverlay(std::size_t windowIndex = 0U); + #endif + inline float GetUpdateTime() const; + #ifndef NDK_SERVER + inline bool IsConsoleEnabled() const; + inline bool IsFPSCounterEnabled() const; + #endif + bool Run(); #ifndef NDK_SERVER @@ -44,13 +73,56 @@ namespace Ndk inline static Application* Instance(); + #ifndef NDK_SERVER + struct ConsoleOverlay + { + std::unique_ptr console; + Nz::LuaInstance lua; + + NazaraSlot(Nz::EventHandler, OnEvent, eventSlot); + NazaraSlot(Nz::EventHandler, OnKeyPressed, keyPressedSlot); + NazaraSlot(Nz::EventHandler, OnResized, resizedSlot); + NazaraSlot(Nz::Log, OnLogWrite, logSlot); + }; + + struct FPSCounterOverlay + { + Nz::TextSpriteRef sprite; + EntityOwner entity; + float elapsedTime = 0.f; + unsigned int frameCount = 0; + }; + #endif + private: #ifndef NDK_SERVER - std::vector> m_windows; + enum OverlayFlags + { + OverlayFlags_Console = 0x1, + OverlayFlags_FPSCounter = 0x2 + }; + + struct WindowInfo + { + inline WindowInfo(std::unique_ptr&& window); + + Nz::RenderTarget* renderTarget; + std::unique_ptr window; + std::unique_ptr console; + std::unique_ptr fpsCounter; + std::unique_ptr overlayWorld; + }; + + void SetupConsole(WindowInfo& info); + void SetupFPSCounter(WindowInfo& info); + void SetupOverlay(WindowInfo& info); + + std::vector m_windows; #endif std::list m_worlds; Nz::Clock m_updateClock; #ifndef NDK_SERVER + Nz::UInt32 m_overlayFlags; bool m_exitOnClosedWindows; #endif bool m_shouldQuit; diff --git a/SDK/include/NDK/Application.inl b/SDK/include/NDK/Application.inl index 0d21d1b94..19013c778 100644 --- a/SDK/include/NDK/Application.inl +++ b/SDK/include/NDK/Application.inl @@ -2,6 +2,7 @@ // This file is part of the "Nazara Development Kit" // For conditions of distribution and use, see copyright notice in Prerequesites.hpp +#include #include #include #include @@ -9,13 +10,15 @@ namespace Ndk { /*! - * \brief Constructs an Application object by default + * \brief Constructs an Application object without passing command-line arguments * - * \remark Produces a NazaraAssert if there's more than one application instance currently running + * This calls Sdk::Initialize() + * + * \remark Only one Application instance can exist at a time */ - inline Application::Application() : #ifndef NDK_SERVER + m_overlayFlags(0U), m_exitOnClosedWindows(true), #endif m_shouldQuit(false), @@ -31,9 +34,26 @@ namespace Ndk } /*! - * \brief Destructs the object + * \brief Constructs an Application object with command-line arguments + * + * Pass the argc and argv arguments from the main function. + * + * Command-line arguments can be retrieved by application methods + * + * This calls Sdk::Initialize() + * + * \remark Only one Application instance can exist at a time */ + inline Application::Application(int argc, const char* argv[]) : + Application() + { + } + /*! + * \brief Destructs the application object + * + * This destroy all worlds and windows and then calls Sdk::Uninitialize + */ inline Application::~Application() { m_worlds.clear(); @@ -61,8 +81,28 @@ namespace Ndk { static_assert(std::is_base_of::value, "Type must inherit Window"); - m_windows.emplace_back(new T(std::forward(args)...)); - return static_cast(*m_windows.back().get()); + m_windows.emplace_back(std::make_unique(std::forward(args)...)); + WindowInfo& info = m_windows.back(); + + T& window = static_cast(*info.window.get()); //< Warning: ugly + + if (std::is_base_of()) + { + info.renderTarget = &window; + + if (m_overlayFlags) + { + SetupOverlay(info); + + if (m_overlayFlags & OverlayFlags_Console) + SetupConsole(info); + + if (m_overlayFlags & OverlayFlags_FPSCounter) + SetupFPSCounter(info); + } + } + + return window; } #endif @@ -80,22 +120,159 @@ namespace Ndk return m_worlds.back(); } + /*! + * \brief Enable/disable debug console + * + * \param enable Should the console overlay be enabled + */ + inline void Application::EnableConsole(bool enable) + { + if (enable != ((m_overlayFlags & OverlayFlags_Console) != 0)) + { + if (enable) + { + if (m_overlayFlags == 0) + { + for (WindowInfo& info : m_windows) + SetupOverlay(info); + } + + for (WindowInfo& info : m_windows) + SetupConsole(info); + + m_overlayFlags |= OverlayFlags_Console; + + } + else + { + for (WindowInfo& info : m_windows) + info.console.reset(); + + m_overlayFlags &= ~OverlayFlags_Console; + if (m_overlayFlags == 0) + { + for (WindowInfo& info : m_windows) + info.overlayWorld.reset(); + } + } + } + } + + /*! + * \brief Enable/disable debug FPS counter + * + * \param enable Should the FPS counter be displayed + */ + inline void Application::EnableFPSCounter(bool enable) + { + if (enable != ((m_overlayFlags & OverlayFlags_FPSCounter) != 0)) + { + if (enable) + { + if (m_overlayFlags == 0) + { + for (WindowInfo& info : m_windows) + SetupOverlay(info); + } + + for (WindowInfo& info : m_windows) + SetupFPSCounter(info); + + m_overlayFlags |= OverlayFlags_FPSCounter; + + } + else + { + for (WindowInfo& info : m_windows) + info.fpsCounter.reset(); + + m_overlayFlags &= ~OverlayFlags_FPSCounter; + if (m_overlayFlags == 0) + { + for (WindowInfo& info : m_windows) + info.overlayWorld.reset(); + } + } + } + } + + /*! + * \brief Gets the console overlay for a specific window + * + * \param windowIndex Index of the window to get + * + * \remark The console overlay must be enabled + * + * \return A reference to the console overlay of the window + * + * \see IsConsoleOverlayEnabled + */ + inline Application::ConsoleOverlay& Application::GetConsoleOverlay(std::size_t windowIndex) + { + NazaraAssert(m_overlayFlags & OverlayFlags_Console, "Console overlay is not enabled"); + NazaraAssert(windowIndex <= m_windows.size(), "Window index is out of range"); + + return *m_windows[windowIndex].console; + } + + /*! + * \brief Gets the console overlay for a specific window + * + * \param windowIndex Index of the window to get + * + * \remark The console overlay must be enabled + * + * \return A reference to the console overlay of the window + * + * \see IsFPSCounterEnabled + */ + inline Application::FPSCounterOverlay& Application::GetFPSCounterOverlay(std::size_t windowIndex) + { + NazaraAssert(m_overlayFlags & OverlayFlags_FPSCounter, "FPS counter overlay is not enabled"); + NazaraAssert(windowIndex <= m_windows.size(), "Window index is out of range"); + + return *m_windows[windowIndex].fpsCounter; + } + /*! * \brief Gets the update time of the application * \return Update rate */ - inline float Application::GetUpdateTime() const { return m_updateTime; } + /*! + * \brief Checks if the console overlay is enabled + * + * \remark This has nothing to do with the visibility state of the console + * + * \return True if the console overlay is enabled + * + * \see GetConsoleOverlay + */ + inline bool Application::IsConsoleEnabled() const + { + return (m_overlayFlags & OverlayFlags_Console) != 0; + } + + /*! + * \brief Checks if the FPS counter overlay is enabled + * \return True if the FPS counter overlay is enabled + * + * \see GetFPSCounterOverlay + */ + inline bool Application::IsFPSCounterEnabled() const + { + return (m_overlayFlags & OverlayFlags_FPSCounter) != 0; + } + /*! * \brief Makes the application exit when there's no more open window * * \param exitOnClosedWindows Should exit be called when no more window is open */ - #ifndef NDK_SERVER inline void Application::MakeExitOnLastWindowClosed(bool exitOnClosedWindows) { @@ -121,4 +298,10 @@ namespace Ndk { return s_application; } + + inline Application::WindowInfo::WindowInfo(std::unique_ptr&& window) : + window(std::move(window)), + renderTarget(nullptr) + { + } } diff --git a/SDK/src/NDK/Application.cpp b/SDK/src/NDK/Application.cpp index 63e834bd0..c4492eb0f 100644 --- a/SDK/src/NDK/Application.cpp +++ b/SDK/src/NDK/Application.cpp @@ -4,6 +4,15 @@ #include +#ifndef NDK_SERVER +#include +#include +#include +#include +#include +#include +#endif + namespace Ndk { /*! @@ -15,7 +24,6 @@ namespace Ndk /*! * \brief Runs the application by updating worlds, taking care about windows, ... */ - bool Application::Run() { #ifndef NDK_SERVER @@ -24,7 +32,7 @@ namespace Ndk auto it = m_windows.begin(); while (it != m_windows.end()) { - Nz::Window& window = **it; + Nz::Window& window = *it->window; window.ProcessEvents(); @@ -54,8 +62,128 @@ namespace Ndk for (World& world : m_worlds) world.Update(m_updateTime); + #ifndef NDK_SERVER + for (WindowInfo& info : m_windows) + { + if (info.fpsCounter) + { + FPSCounterOverlay& fpsCounter = *info.fpsCounter; + + fpsCounter.frameCount++; + + fpsCounter.elapsedTime += m_updateTime; + if (fpsCounter.elapsedTime >= 1.f) + { + fpsCounter.sprite->Update(Nz::SimpleTextDrawer::Draw("FPS: " + Nz::String::Number(fpsCounter.frameCount), 36)); + fpsCounter.frameCount = 0; + fpsCounter.elapsedTime = 0.f; + } + } + + info.overlayWorld->Update(m_updateTime); + } + #endif + return true; } + void Application::SetupConsole(WindowInfo& info) + { + std::unique_ptr overlay = std::make_unique(); + + overlay->console = std::make_unique(*info.overlayWorld, Nz::Vector2f(Nz::Vector2ui(info.window->GetWidth(), info.window->GetHeight() / 4)), overlay->lua); + + Console& consoleRef = *overlay->console; + // Redirect logs toward the console + overlay->logSlot.Connect(Nz::Log::OnLogWrite, [&consoleRef] (const Nz::String& str) + { + consoleRef.AddLine(str); + }); + + LuaAPI::RegisterClasses(overlay->lua); + + // Override "print" function to add a line in the console + overlay->lua.PushFunction([&consoleRef] (Nz::LuaInstance& instance) + { + Nz::StringStream stream; + + unsigned int argCount = instance.GetStackTop(); + instance.GetGlobal("tostring"); + for (unsigned int i = 1; i <= argCount; ++i) + { + instance.PushValue(-1); // tostring function + instance.PushValue(i); // argument + instance.Call(1, 1); + + std::size_t length; + const char* str = instance.CheckString(-1, &length); + if (i > 1) + stream << '\t'; + + stream << Nz::String(str, length); + instance.Pop(1); + } + + consoleRef.AddLine(stream); + return 0; + }); + overlay->lua.SetGlobal("print"); + + // Define a few base variables to allow our interface to interact with the application + overlay->lua.PushGlobal("Application", Ndk::Application::Instance()); + overlay->lua.PushGlobal("Console", consoleRef.CreateHandle()); + + // Setup a few event callback to handle the console + Nz::EventHandler& eventHandler = info.window->GetEventHandler(); + + overlay->eventSlot.Connect(eventHandler.OnEvent, [&consoleRef] (const Nz::EventHandler*, const Nz::WindowEvent& event) + { + if (consoleRef.IsVisible()) + consoleRef.SendEvent(event); + }); + + overlay->keyPressedSlot.Connect(eventHandler.OnKeyPressed, [&consoleRef] (const Nz::EventHandler*, const Nz::WindowEvent::KeyEvent& event) + { + if (event.code == Nz::Keyboard::F9) + consoleRef.Show(!consoleRef.IsVisible()); + }); + + overlay->resizedSlot.Connect(eventHandler.OnResized, [&consoleRef] (const Nz::EventHandler*, const Nz::WindowEvent::SizeEvent& event) + { + consoleRef.SetSize({float(event.width), event.height / 4.f}); + }); + + info.console = std::move(overlay); + } + + void Application::SetupFPSCounter(WindowInfo& info) + { + std::unique_ptr fpsCounter = std::make_unique(); + fpsCounter->sprite = Nz::TextSprite::New(); + + fpsCounter->entity = info.overlayWorld->CreateEntity(); + fpsCounter->entity->AddComponent(); + fpsCounter->entity->AddComponent().Attach(fpsCounter->sprite); + + info.fpsCounter = std::move(fpsCounter); + } + + void Application::SetupOverlay(WindowInfo& info) + { + info.overlayWorld = std::make_unique(false); //< No default system + + RenderSystem& renderSystem = info.overlayWorld->AddSystem(); + renderSystem.ChangeRenderTechnique(); + renderSystem.SetDefaultBackground(nullptr); + renderSystem.SetGlobalUp(Nz::Vector3f::Down()); + + EntityHandle viewer = info.overlayWorld->CreateEntity(); + CameraComponent& camComponent = viewer->AddComponent(); + viewer->AddComponent(); + + camComponent.SetProjectionType(Nz::ProjectionType_Orthogonal); + camComponent.SetTarget(info.renderTarget); + } + Application* Application::s_application = nullptr; } \ No newline at end of file diff --git a/SDK/src/NDK/LuaBinding_SDK.cpp b/SDK/src/NDK/LuaBinding_SDK.cpp index bf66cbf00..35c59ae68 100644 --- a/SDK/src/NDK/LuaBinding_SDK.cpp +++ b/SDK/src/NDK/LuaBinding_SDK.cpp @@ -17,7 +17,14 @@ namespace Ndk #ifndef NDK_SERVER //application.SetMethod("AddWindow", &Application::AddWindow); + + application.BindMethod("EnableConsole", &Application::EnableConsole); + application.BindMethod("EnableFPSCounter", &Application::EnableFPSCounter); + + application.BindMethod("IsConsoleEnabled", &Application::IsConsoleEnabled); + application.BindMethod("IsFPSCounterEnabled", &Application::IsFPSCounterEnabled); #endif + application.BindMethod("AddWorld", [] (Nz::LuaInstance& instance, Application* application) -> int { instance.Push(application->AddWorld().CreateHandle()); diff --git a/examples/FirstScene/main.cpp b/examples/FirstScene/main.cpp index 0a471acee..b1e7e2a64 100644 --- a/examples/FirstScene/main.cpp +++ b/examples/FirstScene/main.cpp @@ -256,81 +256,19 @@ int main() // On lie la caméra à la fenêtre cameraComp.SetTarget(&window); - // Et on créé deux horloges pour gérer le temps - Nz::Clock secondClock, updateClock; + // Et on créé une horloge pour gérer le temps + Nz::Clock updateClock; Nz::UInt64 updateAccumulator = 0; - // Ainsi qu'un compteur de FPS improvisé - unsigned int fps = 0; - // Quelques variables de plus pour notre caméra bool smoothMovement = true; Nz::Vector3f targetPos = cameraNode.GetPosition(); - // Pour ajouter une console à notre application, nous avons besoin d'un monde 2D pour gérer ces rendus - Ndk::WorldHandle world2D = application.AddWorld().CreateHandle(); - world2D->GetSystem().SetDefaultBackground(nullptr); - world2D->GetSystem().SetGlobalUp(Nz::Vector3f::Down()); - - // Nous ajoutons une caméra comme précédement - Ndk::EntityHandle viewEntity = world2D->CreateEntity(); - viewEntity->AddComponent(); - - // À la différence que celui-ci effectuera une projection orthogonale - Ndk::CameraComponent& viewer = viewEntity->AddComponent(); - viewer.SetTarget(&window); - viewer.SetProjectionType(Nz::ProjectionType_Orthogonal); - - // Nous créons un environnement Lua pour gérer nos scripts - Nz::LuaInstance lua; - - // Faisons en sorte d'enregistrer les classes du moteur dans cet environnement - Ndk::LuaAPI::RegisterClasses(lua); - - // Ensuite nous créons la console en elle-même - Ndk::Console console(*world2D, Nz::Vector2f(window.GetWidth(), window.GetHeight() / 4), lua); - - // Nous redirigeons les logs vers cette console - Nz::Log::OnLogWriteType::ConnectionGuard logGuard = Nz::Log::OnLogWrite.Connect([&console] (const Nz::String& str) - { - console.AddLine(str); - }); - - // Nous réécrivons la fonction "print" du Lua pour la rediriger vers la console - lua.PushFunction([&console] (Nz::LuaInstance& instance) - { - Nz::StringStream stream; - - unsigned int argCount = instance.GetStackTop(); - instance.GetGlobal("tostring"); - for (unsigned int i = 1; i <= argCount; ++i) - { - instance.PushValue(-1); // ToString - instance.PushValue(i); // Arg - instance.Call(1, 1); - - std::size_t length; - const char* str = instance.CheckString(-1, &length); - if (i > 1) - stream << '\t'; - - stream << Nz::String(str, length); - instance.Pop(1); - } - - console.AddLine(stream); - return 0; - }); - lua.SetGlobal("print"); - - // Définissons quelques variables de base - lua.PushGlobal("Application", Ndk::Application::Instance()); - lua.PushGlobal("Console", console.CreateHandle()); - lua.PushGlobal("Spaceship", spaceship->CreateHandle()); - lua.PushGlobal("World", world->CreateHandle()); - window.EnableEventPolling(true); // Déprécié + application.EnableConsole(true); + application.EnableFPSCounter(true); + // Début de la boucle de rendu du programme (s'occupant par exemple de mettre à jour le monde) while (application.Run()) { @@ -342,8 +280,12 @@ int main() { case Nz::WindowEventType_MouseMoved: // La souris a bougé { - if (console.IsVisible()) - break; + if (application.IsConsoleEnabled()) + { + Ndk::Application::ConsoleOverlay& consoleOverlay = application.GetConsoleOverlay(); + if (consoleOverlay.console->IsVisible()) + break; + } // Gestion de la caméra free-fly (Rotation) float sensitivity = 0.3f; // Sensibilité de la souris @@ -368,9 +310,6 @@ int main() break; case Nz::WindowEventType_KeyPressed: // Une touche a été pressée ! - if (console.IsVisible()) - console.SendEvent(event); - if (event.key.code == Nz::Keyboard::Key::Escape) window.Close(); else if (event.key.code == Nz::Keyboard::F1) @@ -383,19 +322,6 @@ int main() else smoothMovement = true; } - else if (event.key.code == Nz::Keyboard::F9) - console.Show(!console.IsVisible()); - break; - - case Nz::WindowEventType_TextEntered: - { - if (console.IsVisible()) - console.SendCharacter(event.text.character); - break; - } - - case Nz::WindowEventType_Resized: - console.SetSize({float(event.size.width), event.size.height / 4.f}); break; default: @@ -419,7 +345,16 @@ int main() // Vitesse de déplacement de la caméra float cameraSpeed = 3.f * elapsedTime; // Trois mètres par seconde - if (!console.IsVisible()) + bool move = true; + + if (application.IsConsoleEnabled()) + { + Ndk::Application::ConsoleOverlay& consoleOverlay = application.GetConsoleOverlay(); + if (consoleOverlay.console->IsVisible()) + move = false; + } + + if (move) { // Si la touche espace est enfoncée, notre vitesse de déplacement est multipliée par deux if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::Space)) @@ -460,28 +395,6 @@ int main() // Après avoir dessiné sur la fenêtre, il faut s'assurer qu'elle affiche cela // Cet appel ne fait rien d'autre qu'échanger les buffers de rendu (Double Buffering) window.Display(); - - // On incrémente le compteur de FPS improvisé - fps++; - - if (secondClock.GetMilliseconds() >= 1000) // Toutes les secondes - { - // Et on insère ces données dans le titre de la fenêtre - window.SetTitle(windowTitle + " - " + Nz::String::Number(fps) + " FPS"); - - /* - Note: En C++11 il est possible d'insérer de l'Unicode de façon standard, quel que soit l'encodage du fichier, - via quelque chose de similaire à u8"Cha\u00CEne de caract\u00E8res". - Cependant, si le code source est encodé en UTF-8 (Comme c'est le cas dans ce fichier), - cela fonctionnera aussi comme ceci : "Chaîne de caractères". - */ - - // Et on réinitialise le compteur de FPS - fps = 0; - - // Et on relance l'horloge pour refaire ça dans une seconde - secondClock.Restart(); - } } return EXIT_SUCCESS;