{"id":609,"date":"2012-09-29T16:20:31","date_gmt":"2012-09-29T15:20:31","guid":{"rendered":"http:\/\/www.grandmaster.nu\/blog\/?page_id=609"},"modified":"2012-09-29T16:20:31","modified_gmt":"2012-09-29T15:20:31","slug":"game-engine-part-7","status":"publish","type":"page","link":"https:\/\/www.grandmaster.nu\/blog\/?page_id=609","title":{"rendered":"Game Engine part 7"},"content":{"rendered":"<p>In this part I&#8217;ll introduce another engine subsystem &#8211; Input, and we&#8217;ll look at how an external API can be neatly wrapped in our own engine. I&#8217;m also going to make use of some C++11 features, most of which can be approximated by using <a href=\"http:\/\/www.boost.org\">Boost<\/a>, but some stuff is just too neat to pass up on. On a related note &#8211; most of the new standard language features is not supported by any compiler yet (userdefined literals, variadic templates, initializer lists, and so forth), but the new STL implementation seems pretty complete. This means more algorithms for containers are available, more containers, regular expressions and perhaps most important of all, standardized threading support. Let me illustrate this with something that would be pretty difficult in C++03: <\/p>\n<pre lang=\"c++\">#include <array>\r\n#include <future>\r\n#include <vector>\r\n\r\nint main() {\r\n\tstd::array<int, 5> data = { 1, 2, 3, 4, 5 };\r\n\tstd::vector<std::future<int>> pending;\r\n\r\n\tfor (auto& element: data)\r\n\t\tpending.emplace_back(\r\n\t\t\tstd::async([](int& value) {\r\n\t\t\t\treturn value * value;\r\n\t\t\t}, element));\r\n\r\n\tfor (auto& ft: pending)\r\n\t\tstd::cout << \"Element: \" << ft.get() << std::endl;\r\n\r\n\treturn 0;\r\n}<\/pre>\n<p>Scheduling parallel executiong of an in-place defined function applied to every element of an array and fetching all results in the correct order without even touching actual threads, mutexes and\/or locks, effectively under 10 lines of code. Pretty sweet stuff :)<\/p>\n<p>But let's get back on track with the game engine, right -- in part 6 you've created a window and you've probably noticed that shutting down doesn't happen quite as nicely as you'd hoped. Let us start by remembering that the engine runs a loop and keeps going until it has been given a termination signal. So we need to make something that sends a termination signal, say, when the escape button is pressed on the keyboard. Also, any user probably expects his program to shut down when its window is closed, so that needs to trigger the termination signal as well.<\/p>\n<p>We hope to end up with something like this: <\/p>\n<pre lang=\"c++\">void operator()(const KeyPressed& kp) {\r\n\tif (kp.mKey == KEY_ESCAPE) {\r\n\t\tEventChannel chan;\r\n\t\tchan.broadcast(TerminationEvent());\r\n\t}\r\n}<\/pre>\n<p>So we need a class that broadcasts keys that are pressed. GLFW provides C-style callback functions for handling keypresses and releases, so let's wrap it in our own class and translate events from GLFW type to C++ structs: <\/p>\n<pre lang=\"c++\">class Keyboard {\r\npublic:\r\n\tstatic void glfwKeyboardCallback(int key, int state) {\r\n\t\tstatic EventChannel chan;\r\n\r\n\t\tif (state == GLFW_PRESS)\r\n\t\t\tchan.broadcast(KeyPressed(key));\r\n\t}\r\n\r\n\tstruct KeyPressed {\r\n\t\tint mKey;\r\n\r\n\t\tKeyPressed(int key): mKey(key) {}\r\n\t};\r\n};<\/pre>\n<p>Of course, when pressing key is being broadcast, releasing them should be as well. And while we're at it, let's keep an overview of every key on the keyboard and its current state.<\/p>\n<pre lang=\"c++\">class Keyboard {\r\npublic:\r\n\tbool mKeyState[GLFW_KEY_LAST]; \/\/true means the key is pressed\r\n\t\r\n\tKeyboard() {\r\n\t\tfor (auto& key: mKeyState)\r\n\t\t\tkey = false;\r\n\r\n\t\tEventChannel chan;\r\n\t\tchan.add<KeyPressed>(this);\r\n\t\tchan.add<KeyReleased>(this);\r\n\t}\r\n\r\n\tstatic void glfwKeyboardCallback(int key, int state) {\r\n\t\tstatic EventChannel chan;\r\n\r\n\t\tswitch (state) {\r\n\t\tcase GLFW_PRESS:\r\n\t\t\tchan.broadcast(KeyPressed(key));\r\n\t\t\tbreak;\r\n\t\tcase GLFW_RELEASE:\r\n\t\t\tchan.broadcast(KeyReleased(key));\r\n\t\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tstd::cout << \"Unhandled glfw state: \" << state;\r\n\t\t}\r\n\t}\r\n\r\n\tstruct KeyPressed {\r\n\t\tint mKey;\r\n\r\n\t\tKeyPressed(int key): mKey(key) {}\r\n\t};\r\n\r\n\tstruct KeyReleased {\r\n\t\tint mKey;\r\n\r\n\t\tKeyReleased(int key): mKey(key) {}\r\n\t};\r\n\r\n\tvoid operator()(const KeyPressed&#038; kp) {\r\n\t\tassert(kp.mKey < GLFW_LAST);\r\n\t\tmKeyState[kp.mKey] = true;\r\n\t}\r\n\r\n\tvoid operator()(const KeyReleased&#038; kr) {\r\n\t\tassert(kp.mKey < GLFW_LAST);\r\n\t\tmKeyState[kp.mKey] = false;\r\n\t}\r\n};<\/pre>\n<p>Okay, looking good. A little snag however -- GLFW requires a fully initialized window before the callbacks can be attached. This calls for some kind of manager-type class, which suggests to me that it should be a System class, so let's get started on an Input System: <\/p>\n<pre lang=\"c++\">class Input: public System {\r\npublic:\r\n\tInput():\r\n\t\tSystem(\"Input\", Task::SINGLETHREADED_REPEATING)\r\n\t{\r\n\t\t\/\/add a callback when the window creation has finished\r\n\t\tmChan.add<WindowCreated>(this); \r\n\t}\r\n\r\n\t~Input() {}\r\n\r\n\tbool Input::init() {\r\n\t\treturn System::init(); \r\n\t}\r\n\r\n\tvoid Input::update() {\r\n\t\t\/\/poll-based input devices (joysticks\/gamepads) should be updated here\r\n\t}\r\n\r\n\tvoid Input::shutdown() {\r\n\t\tglfwSetKeyCallback(nullptr);\r\n\t}\r\n\r\n\tvoid operator()(const WindowCreated& ) {\r\n\t\tglfwSetKeyCallback(&mKeyboard::glfwKeyboardCallback);\r\n\t}\r\n};\r\n<\/pre>\n<p>There you have it. Thanks to our earlier components implementing this has become very clean and concise. You'll note that the update function can be used to query poll-based input devices, making the class mixed callback- and poll-based. Very convenient. It's pretty straightforward to take the GLFW documentation and implement a similar scheme for a mouse and joystick devices. While I'm at it - take a good look at the documentation of any API you use! For example, by default GLFW processes keyboard and mouse events at each call to glfwSwapBuffers (which displays the current frame). If you don't call glfwSwapBuffers, not only will the window stay blank, but your program will also not respond to key and mouse events, unless you're calling glfwPollEvents in a different loop. Also, special measures must be taken if you want to override system keys (alt+tab etc.)... this is typical stuff that can only be found in the documentation; try to pay attention to various special and\/or edge cases.<\/p>\n<p>This should be more than enough to get you started, next time I'll get started on modern openGL usage (core shader-based pipeline). See you then!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this part I&#8217;ll introduce another engine subsystem &#8211; Input, and we&#8217;ll look at how an external API can be neatly wrapped in our own engine. I&#8217;m also going to make use of some C++11 features, most of which can be approximated by using Boost, but some stuff is just too neat to pass up &hellip; <a href=\"https:\/\/www.grandmaster.nu\/blog\/?page_id=609\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Game Engine part 7&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":25,"menu_order":0,"comment_status":"open","ping_status":"open","template":"","meta":{"ngg_post_thumbnail":0,"footnotes":""},"class_list":["post-609","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/609","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=609"}],"version-history":[{"count":11,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/609\/revisions"}],"predecessor-version":[{"id":620,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/609\/revisions\/620"}],"up":[{"embeddable":true,"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=\/wp\/v2\/pages\/25"}],"wp:attachment":[{"href":"https:\/\/www.grandmaster.nu\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=609"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}