Browse Source

Revise events, input, and logging

master
C. J. Howard 1 year ago
parent
commit
a37f7c0f5f
128 changed files with 4776 additions and 5664 deletions
  1. +1
    -1
      CMakeLists.txt
  2. +289
    -228
      src/application.cpp
  3. +63
    -121
      src/application.hpp
  4. +2
    -2
      src/color/cct.hpp
  5. +10
    -10
      src/color/color.hpp
  6. +65
    -6
      src/config.hpp.in
  7. +0
    -74
      src/debug/ansi-codes.hpp
  8. +3
    -3
      src/debug/cli.cpp
  9. +44
    -23
      src/debug/cli.hpp
  10. +0
    -56
      src/debug/console-commands.cpp
  11. +51
    -0
      src/debug/console.cpp
  12. +39
    -0
      src/debug/console.hpp
  13. +1
    -6
      src/debug/debug.hpp
  14. +58
    -0
      src/debug/log.cpp
  15. +189
    -0
      src/debug/log.hpp
  16. +67
    -0
      src/debug/log/event.hpp
  17. +22
    -20
      src/debug/log/logger.cpp
  18. +65
    -0
      src/debug/log/logger.hpp
  19. +71
    -0
      src/debug/log/message-severity.hpp
  20. +0
    -230
      src/debug/logger.cpp
  21. +0
    -130
      src/debug/logger.hpp
  22. +1
    -1
      src/debug/performance-sampler.hpp
  23. +104
    -0
      src/event/channel.hpp
  24. +0
    -125
      src/event/event-dispatcher.cpp
  25. +0
    -140
      src/event/event-dispatcher.hpp
  26. +0
    -83
      src/event/event-handler.hpp
  27. +0
    -88
      src/event/event.hpp
  28. +0
    -117
      src/event/input-events.cpp
  29. +0
    -170
      src/event/input-events.hpp
  30. +95
    -0
      src/event/publisher.hpp
  31. +143
    -0
      src/event/queue.hpp
  32. +0
    -262
      src/event/signal.hpp
  33. +12
    -13
      src/event/subscriber.hpp
  34. +48
    -0
      src/event/subscription.cpp
  35. +67
    -0
      src/event/subscription.hpp
  36. +0
    -1
      src/game/ant/morphogenesis.cpp
  37. +43
    -37
      src/game/context.hpp
  38. +54
    -46
      src/game/controls.cpp
  39. +2
    -2
      src/game/fonts.cpp
  40. +30
    -22
      src/game/graphics.cpp
  41. +6
    -22
      src/game/load.cpp
  42. +5
    -1
      src/game/menu.cpp
  43. +13
    -7
      src/game/save.cpp
  44. +184
    -232
      src/game/state/boot.cpp
  45. +5
    -5
      src/game/state/controls-menu.cpp
  46. +14
    -14
      src/game/state/credits.cpp
  47. +2
    -0
      src/game/state/credits.hpp
  48. +5
    -5
      src/game/state/extras-menu.cpp
  49. +12
    -10
      src/game/state/gamepad-config-menu.cpp
  50. +18
    -18
      src/game/state/graphics-menu.cpp
  51. +12
    -10
      src/game/state/keyboard-config-menu.cpp
  52. +13
    -13
      src/game/state/language-menu.cpp
  53. +4
    -42
      src/game/state/main-menu.cpp
  54. +0
    -6
      src/game/state/main-menu.hpp
  55. +16
    -13
      src/game/state/nest-selection.cpp
  56. +10
    -7
      src/game/state/nuptial-flight.cpp
  57. +5
    -5
      src/game/state/options-menu.cpp
  58. +10
    -10
      src/game/state/pause-menu.cpp
  59. +9
    -9
      src/game/state/sound-menu.cpp
  60. +32
    -20
      src/game/state/splash.cpp
  61. +3
    -0
      src/game/state/splash.hpp
  62. +0
    -1
      src/game/system/astronomy.cpp
  63. +0
    -5
      src/game/system/camera.cpp
  64. +1
    -8
      src/game/system/camera.hpp
  65. +0
    -1
      src/game/system/orbit.cpp
  66. +0
    -1
      src/game/system/render.cpp
  67. +0
    -1
      src/game/system/spatial.cpp
  68. +0
    -1
      src/game/system/terrain.cpp
  69. +35
    -36
      src/game/world.cpp
  70. +299
    -0
      src/input/control-map.cpp
  71. +111
    -0
      src/input/control-map.hpp
  72. +0
    -83
      src/input/control-set.hpp
  73. +33
    -88
      src/input/control.cpp
  74. +56
    -94
      src/input/control.hpp
  75. +121
    -0
      src/input/device-manager.cpp
  76. +97
    -0
      src/input/device-manager.hpp
  77. +40
    -0
      src/input/device-type.hpp
  78. +13
    -6
      src/input/device.cpp
  79. +55
    -31
      src/input/device.hpp
  80. +0
    -399
      src/input/event-router.cpp
  81. +0
    -127
      src/input/event-router.hpp
  82. +283
    -0
      src/input/event.hpp
  83. +51
    -0
      src/input/gamepad-axis.hpp
  84. +78
    -0
      src/input/gamepad-button.hpp
  85. +53
    -108
      src/input/gamepad.cpp
  86. +42
    -72
      src/input/gamepad.hpp
  87. +0
    -38
      src/input/input.hpp
  88. +6
    -315
      src/input/keyboard.cpp
  89. +40
    -33
      src/input/keyboard.hpp
  90. +0
    -111
      src/input/listener.cpp
  91. +0
    -97
      src/input/listener.hpp
  92. +31
    -103
      src/input/mapper.cpp
  93. +29
    -64
      src/input/mapper.hpp
  94. +28
    -31
      src/input/mapping-type.hpp
  95. +19
    -105
      src/input/mapping.cpp
  96. +193
    -119
      src/input/mapping.hpp
  97. +90
    -0
      src/input/modifier-key.hpp
  98. +42
    -0
      src/input/mouse-button.hpp
  99. +14
    -9
      src/input/mouse-motion-axis.hpp
  100. +18
    -7
      src/input/mouse-scroll-axis.hpp

+ 1
- 1
CMakeLists.txt View File

@ -76,7 +76,7 @@ else()
target_compile_definitions(${EXECUTABLE_TARGET} PRIVATE NDEBUG)
endif()
# Set C++20 standard
# Set C++ standard
set_target_properties(${EXECUTABLE_TARGET} PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON

+ 289
- 228
src/application.cpp View File

@ -18,21 +18,19 @@
*/
#include "application.hpp"
#include "debug/logger.hpp"
#include "event/event-dispatcher.hpp"
#include "event/window-events.hpp"
#include "config.hpp"
#include "debug/log.hpp"
#include "input/scancode.hpp"
#include "input/sdl-game-controller-tables.hpp"
#include "input/sdl-scancode-table.hpp"
#include "math/map.hpp"
#include "resources/image.hpp"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <cstring>
#include <iomanip>
#include <stdexcept>
#include <utility>
#include <iostream>
#include <iomanip>
application::application(debug::logger& log):
application::application():
closed(false),
fullscreen(true),
v_sync(false),
@ -42,133 +40,148 @@ application::application(debug::logger& log):
window_dimensions({0, 0}),
viewport_dimensions({0, 0}),
mouse_position({0, 0}),
logger(&log),
sdl_window(nullptr),
sdl_gl_context(nullptr)
{
// Get SDL compiled version
// Log SDL compiled version
SDL_version sdl_compiled_version;
SDL_VERSION(&sdl_compiled_version);
std::string sdl_compiled_version_string = std::to_string(sdl_compiled_version.major) + "." + std::to_string(sdl_compiled_version.minor) + "." + std::to_string(sdl_compiled_version.patch);
// Get SDL linked version
debug::log::debug("Compiled against SDL {}.{}.{}", sdl_compiled_version.major, sdl_compiled_version.minor, sdl_compiled_version.patch);
// Log SDL linked version
SDL_version sdl_linked_version;
SDL_GetVersion(&sdl_linked_version);
std::string sdl_linked_version_string = std::to_string(sdl_linked_version.major) + "." + std::to_string(sdl_linked_version.minor) + "." + std::to_string(sdl_linked_version.patch);
// Init SDL
logger->push_task("Initializing SDL");
logger->log("Compiled against SDL " + sdl_compiled_version_string);
logger->log("Linking against SDL " + sdl_linked_version_string);
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
logger->pop_task(EXIT_FAILURE);
throw std::runtime_error("Failed to initialize SDL");
}
else
{
logger->pop_task(EXIT_SUCCESS);
}
debug::log::debug("Linking against SDL {}.{}.{}", sdl_linked_version.major, sdl_linked_version.minor, sdl_linked_version.patch);
// Load default OpenGL library
logger->push_task("Loading OpenGL library");
if (SDL_GL_LoadLibrary(nullptr) != 0)
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
if (SDL_InitSubSystem(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
{
logger->pop_task(EXIT_FAILURE);
debug::log::fatal("Failed to initialize SDL events and video subsystems: {}", SDL_GetError());
throw std::runtime_error("Failed to initialize SDL events and video subsystems");
}
else
{
logger->pop_task(EXIT_SUCCESS);
}
// Set window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
debug::log::trace("Initialized SDL events and video subsystems");
// Get display dimensions
// Detect display dimensions
SDL_DisplayMode sdl_desktop_display_mode;
if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
{
logger->error("Failed to detect desktop display mode: \"" + std::string(SDL_GetError()) + "\"");
debug::log::fatal("Failed to detect desktop display mode: {}", SDL_GetError());
throw std::runtime_error("Failed to detect desktop display mode");
}
else
{
display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
}
display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
// Get display DPI
// Detect display DPI
if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
{
logger->error("Failed to detect display DPI: \"" + std::string(SDL_GetError()) + "\"");
debug::log::fatal("Failed to detect display DPI: {}", SDL_GetError());
throw std::runtime_error("Failed to detect display DPI");
}
else
// Log display properties
debug::log::info("Detected {}x{}@{}Hz display with {} DPI", sdl_desktop_display_mode.w, sdl_desktop_display_mode.h, sdl_desktop_display_mode.refresh_rate, display_dpi);
// Load OpenGL library
debug::log::trace("Loading OpenGL library...");
if (SDL_GL_LoadLibrary(nullptr) != 0)
{
logger->log("Detected " + std::to_string(sdl_desktop_display_mode.w) + "x" + std::to_string(sdl_desktop_display_mode.h) + " display with " + std::to_string(display_dpi) + " DPI");
debug::log::fatal("Failed to load OpenGL library: {}", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL library");
}
debug::log::trace("Loaded OpenGL library");
// Set OpenGL-related window creation hints
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config::opengl_version_major);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config::opengl_version_minor);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, config::opengl_min_red_size);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, config::opengl_min_green_size);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, config::opengl_min_blue_size);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, config::opengl_min_alpha_size);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
// Create a hidden fullscreen window
logger->push_task("Creating " + std::to_string(display_dimensions[0]) + "x" + std::to_string(display_dimensions[1]) + " window");
debug::log::trace("Creating {}x{} window...", display_dimensions[0], display_dimensions[1]);
sdl_window = SDL_CreateWindow
(
"Antkeeper",
config::application_name,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
display_dimensions[0], display_dimensions[1],
SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN
);
if (!sdl_window)
{
logger->pop_task(EXIT_FAILURE);
debug::log::fatal("Failed to create {}x{} window: {}", display_dimensions[0], display_dimensions[1], SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
else
{
logger->pop_task(EXIT_SUCCESS);
}
debug::log::trace("Created {}x{} window", display_dimensions[0], display_dimensions[1]);
// Create OpenGL context
logger->push_task("Creating OpenGL 3.3 context");
debug::log::trace("Creating OpenGL {}.{} context...", config::opengl_version_major, config::opengl_version_minor);
sdl_gl_context = SDL_GL_CreateContext(sdl_window);
if (!sdl_gl_context)
{
logger->pop_task(EXIT_FAILURE);
debug::log::fatal("Failed to create OpenGL context: {}", SDL_GetError());
throw std::runtime_error("Failed to create OpenGL context");
}
else
{
logger->pop_task(EXIT_SUCCESS);
}
// Make OpenGL context current
logger->push_task("Making OpenGL context current");
if (SDL_GL_MakeCurrent(sdl_window, sdl_gl_context) != 0)
// Log version of created OpenGL context
int opengl_context_version_major = -1;
int opengl_context_version_minor = -1;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &opengl_context_version_major);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &opengl_context_version_minor);
debug::log::info("Created OpenGL {}.{} context", opengl_context_version_major, opengl_context_version_minor);
// Compare OpenGL context version with requested version
if (opengl_context_version_major != config::opengl_version_major ||
opengl_context_version_minor != config::opengl_version_minor)
{
logger->pop_task(EXIT_FAILURE);
throw std::runtime_error("Failed to make OpenGL context current");
debug::log::warning("Requested OpenGL {}.{} context but created OpenGL {}.{} context", config::opengl_version_major, config::opengl_version_minor, opengl_context_version_major, opengl_context_version_minor);
}
else
// Log format of OpenGL context default framebuffer
int opengl_context_red_size = -1;
int opengl_context_green_size = -1;
int opengl_context_blue_size = -1;
int opengl_context_alpha_size = -1;
int opengl_context_depth_size = -1;
int opengl_context_stencil_size = -1;
SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &opengl_context_red_size);
SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &opengl_context_green_size);
SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &opengl_context_blue_size);
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
debug::log::info("OpenGL context default framebuffer format: R{}G{}B{}A{}D{}S{}", opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size);
// Compare OpenGL context default framebuffer format with request format
if (opengl_context_red_size < config::opengl_min_red_size ||
opengl_context_green_size < config::opengl_min_green_size ||
opengl_context_blue_size < config::opengl_min_blue_size ||
opengl_context_alpha_size < config::opengl_min_alpha_size ||
opengl_context_depth_size < config::opengl_min_depth_size ||
opengl_context_stencil_size < config::opengl_min_stencil_size)
{
logger->pop_task(EXIT_SUCCESS);
debug::log::warning
(
"OpenGL default framebuffer format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size,
config::opengl_min_red_size, config::opengl_min_green_size, config::opengl_min_blue_size, config::opengl_min_alpha_size, config::opengl_min_depth_size, config::opengl_min_stencil_size
);
}
// Load OpenGL functions via GLAD
logger->push_task("Loading OpenGL functions");
debug::log::trace("Loading OpenGL functions...");
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
logger->pop_task(EXIT_FAILURE);
debug::log::fatal("Failed to load OpenGL functions", SDL_GetError());
throw std::runtime_error("Failed to load OpenGL functions");
}
else
{
logger->pop_task(EXIT_SUCCESS);
}
debug::log::trace("Loaded OpenGL functions");
// Update window size and viewport size
SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
@ -177,28 +190,27 @@ application::application(debug::logger& log):
// Set v-sync mode
set_v_sync(true);
// Init SDL joystick and gamepad subsystems
logger->push_task("Initializing SDL Joystick and Game Controller subsystems");
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to initialize SDL joystick and controller subsytems: {}", SDL_GetError());
}
else
{
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Initialized SDL joystick and controller subsystems");
}
// Setup rasterizer
rasterizer = new gl::rasterizer();
// Setup events
event_dispatcher = new ::event_dispatcher();
// Register keyboard and mouse with input device manager
device_manager.register_device(keyboard);
device_manager.register_device(mouse);
// Setup input
keyboard = new input::keyboard();
keyboard->set_event_dispatcher(event_dispatcher);
mouse = new input::mouse();
mouse->set_event_dispatcher(event_dispatcher);
// Generate keyboard and mouse device connected events
keyboard.connect();
mouse.connect();
// Connect gamepads
process_events();
@ -246,7 +258,9 @@ void application::set_relative_mouse_mode(bool enabled)
SDL_SetRelativeMouseMode(SDL_FALSE);
SDL_WarpMouseInWindow(sdl_window, mouse_position[0], mouse_position[1]);
if (cursor_visible)
{
SDL_ShowCursor(SDL_ENABLE);
}
}
}
@ -287,39 +301,38 @@ void application::set_v_sync(bool v_sync)
{
if (v_sync)
{
logger->push_task("Enabling adaptive v-sync");
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
logger->pop_task(EXIT_FAILURE);
logger->push_task("Enabling synchronized v-sync");
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
logger->pop_task(EXIT_SUCCESS);
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
this->v_sync = v_sync;
logger->pop_task(EXIT_SUCCESS);
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
logger->push_task("Disabling v-sync");
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
logger->pop_task(EXIT_SUCCESS);
debug::log::debug("Disabled v-sync");
}
}
}
@ -348,227 +361,272 @@ void application::hide_window()
void application::add_game_controller_mappings(const void* mappings, std::size_t size)
{
logger->push_task("Adding SDL game controller mappings");
debug::log::trace("Adding SDL game controller mappings...");
int mapping_count = SDL_GameControllerAddMappingsFromRW(SDL_RWFromConstMem(mappings, static_cast<int>(size)), 0);
if (mapping_count == -1)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to add SDL game controller mappings: {}", SDL_GetError());
}
else
{
logger->log("Added " + std::to_string(mapping_count) + " SDL game controller mappings");
logger->pop_task(EXIT_SUCCESS);
debug::log::debug("Added {} SDL game controller mappings", mapping_count);
}
}
void application::process_events()
{
// Active modifier keys
std::uint16_t sdl_key_mod = KMOD_NONE;
std::uint16_t modifier_keys = input::modifier_key::none;
// Mouse motion event accumulators
bool mouse_motion = false;
int mouse_x;
int mouse_y;
int mouse_dx = 0;
int mouse_dy = 0;
// bool mouse_motion = false;
// std::int32_t mouse_x;
// std::int32_t mouse_y;
// std::int32_t mouse_dx = 0;
// std::int32_t mouse_dy = 0;
SDL_Event sdl_event;
while (SDL_PollEvent(&sdl_event))
{
switch (sdl_event.type)
{
[[likely]] case SDL_MOUSEMOTION:
{
// More than one mouse motion event is often generated per frame, and may be a source of lag.
// Mouse events can be accumulated here to prevent excessive function calls and allocations
// mouse_motion = true;
// mouse_x = sdl_event.motion.x;
// mouse_y = sdl_event.motion.y;
// mouse_dx += sdl_event.motion.xrel;
// mouse_dy += sdl_event.motion.yrel;
mouse.move({sdl_event.motion.x, sdl_event.motion.y}, {sdl_event.motion.xrel, sdl_event.motion.yrel});
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
if (sdl_event.key.repeat == 0)
// Get scancode of key
const input::scancode scancode = static_cast<input::scancode>(sdl_event.key.keysym.scancode);
// Rebuild modifier keys bit mask
if (sdl_event.key.keysym.mod != sdl_key_mod)
{
input::scancode scancode = input::scancode::unknown;
if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
{
scancode = input::sdl_scancode_table[sdl_event.key.keysym.scancode];
}
if (sdl_event.type == SDL_KEYDOWN)
keyboard->press(scancode);
else
keyboard->release(scancode);
sdl_key_mod = sdl_event.key.keysym.mod;
modifier_keys = input::modifier_key::none;
if (sdl_key_mod & KMOD_LSHIFT)
modifier_keys |= input::modifier_key::left_shift;
if (sdl_key_mod & KMOD_RSHIFT)
modifier_keys |= input::modifier_key::right_shift;
if (sdl_key_mod & KMOD_LCTRL)
modifier_keys |= input::modifier_key::left_ctrl;
if (sdl_key_mod & KMOD_RCTRL)
modifier_keys |= input::modifier_key::right_ctrl;
if (sdl_key_mod & KMOD_LGUI)
modifier_keys |= input::modifier_key::left_gui;
if (sdl_key_mod & KMOD_RGUI)
modifier_keys |= input::modifier_key::right_gui;
if (sdl_key_mod & KMOD_NUM)
modifier_keys |= input::modifier_key::num_lock;
if (sdl_key_mod & KMOD_CAPS)
modifier_keys |= input::modifier_key::caps_lock;
if (sdl_key_mod & KMOD_SCROLL)
modifier_keys |= input::modifier_key::scroll_lock;
if (sdl_key_mod & KMOD_MODE)
modifier_keys |= input::modifier_key::alt_gr;
}
// Determine if event was generated from a key repeat
const bool repeat = sdl_event.key.repeat > 0;
if (sdl_event.type == SDL_KEYDOWN)
{
keyboard.press(scancode, repeat, modifier_keys);
}
else
{
keyboard.release(scancode, repeat, modifier_keys);
}
break;
}
case SDL_MOUSEMOTION:
case SDL_MOUSEWHEEL:
{
// More than one mouse motion event is often generated per frame, and may be a source of lag.
// Mouse events are accumulated here to prevent excess function calls and allocations
mouse_motion = true;
mouse_x = sdl_event.motion.x;
mouse_y = sdl_event.motion.y;
mouse_dx += sdl_event.motion.xrel;
mouse_dy += sdl_event.motion.yrel;
const float flip = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f;
mouse.scroll({sdl_event.wheel.preciseX * flip, sdl_event.wheel.preciseY * flip});
break;
}
case SDL_MOUSEBUTTONDOWN:
{
mouse->press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
mouse.press(static_cast<input::mouse_button>(sdl_event.button.button));
break;
}
case SDL_MOUSEBUTTONUP:
{
mouse->release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
mouse.release(static_cast<input::mouse_button>(sdl_event.button.button));
break;
}
case SDL_MOUSEWHEEL:
[[likely]] case SDL_CONTROLLERAXISMOTION:
{
int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
mouse->scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
// Map axis position onto `[-1, 1]`.
const float position = math::map
(
static_cast<float>(sdl_event.caxis.value),
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::min()),
static_cast<float>(std::numeric_limits<decltype(sdl_event.caxis.value)>::max()),
-1.0f,
1.0f
);
// Generate gamepad axis moved event
it->second->move(static_cast<input::gamepad_axis>(sdl_event.caxis.axis), position);
}
}
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
it->second->press(button);
it->second->press(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERBUTTONUP:
{
if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
input::gamepad_button button = input::sdl_button_table[sdl_event.cbutton.button];
it->second->release(button);
it->second->release(static_cast<input::gamepad_button>(sdl_event.cbutton.button));
}
}
break;
}
case SDL_CONTROLLERAXISMOTION:
case SDL_WINDOWEVENT:
{
if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
switch (sdl_event.window.event)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
input::gamepad_axis axis = input::sdl_axis_table[sdl_event.caxis.axis];
float value = sdl_event.caxis.value;
static const float min = static_cast<float>(std::numeric_limits<std::int16_t>::min());
static const float max = static_cast<float>(std::numeric_limits<std::int16_t>::max());
value /= (value < 0.0f) ? -min : max;
it->second->move(axis, value);
}
case SDL_WINDOWEVENT_SIZE_CHANGED:
window_resized();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
debug::log::debug("Window focus gained");
window_focus_changed_publisher.publish({true});
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
debug::log::debug("Window focus lost");
window_focus_changed_publisher.publish({false});
break;
case SDL_WINDOWEVENT_MOVED:
debug::log::trace("Window moved to ({}, {})", sdl_event.window.data1, sdl_event.window.data2);
window_moved_publisher.publish({static_cast<int>(sdl_event.window.data1), static_cast<int>(sdl_event.window.data2)});
break;
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
debug::log::info("Window closed");
window_closed_publisher.publish({});
break;
default:
break;
}
break;
}
case SDL_CONTROLLERDEVICEADDED:
[[unlikely]] case SDL_CONTROLLERDEVICEADDED:
{
if (SDL_IsGameController(sdl_event.cdevice.which))
{
SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
if (sdl_controller)
{
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
logger->log("Reconnected gamepad \"" + controller_name + "\"");
it->second->connect(true);
gamepad_connection_signal.emit(*it->second, true);
// Gamepad reconnected
debug::log::info("Reconnected gamepad \"{}\"", controller_name);
it->second->connect();
}
else
{
// Get gamepad GUID
SDL_Joystick* sdl_joystick = SDL_GameControllerGetJoystick(sdl_controller);
SDL_JoystickGUID sdl_guid = SDL_JoystickGetGUID(sdl_joystick);
char guid_buffer[33];
std::memset(guid_buffer, '\0', 33);
SDL_JoystickGetGUIDString(sdl_guid, &guid_buffer[0], 33);
std::string guid(guid_buffer);
logger->log("Connected gamepad \"" + controller_name + "\" with GUID: " + guid + "");
// Copy into UUID struct
::uuid gamepad_uuid;
std::memcpy(gamepad_uuid.data.data(), sdl_guid.data, gamepad_uuid.data.size());
debug::log::info("Connected gamepad \"{}\" with UUID {}", controller_name, gamepad_uuid.string());
// Create new gamepad
input::gamepad* gamepad = new input::gamepad();
gamepad->set_event_dispatcher(event_dispatcher);
gamepad->set_guid(guid);
gamepad->set_uuid(gamepad_uuid);
// Add gamepad to list and map
gamepads.push_back(gamepad);
// Add gamepad to gamepad map
gamepad_map[sdl_event.cdevice.which] = gamepad;
// Send controller connected event
gamepad->connect(false);
gamepad_connection_signal.emit(*it->second, false);
// Register gamepad with device manager
device_manager.register_device(*gamepad);
// Generate gamepad connected event
gamepad->connect();
}
}
else
{
logger->error("Failed to connected gamepad \"" + controller_name + "\"");
debug::log::error("Failed to connected gamepad \"{}\": {}", controller_name, SDL_GetError());
}
}
break;
}
case SDL_CONTROLLERDEVICEREMOVED:
[[unlikely]] case SDL_CONTROLLERDEVICEREMOVED:
{
SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
if (sdl_controller)
{
SDL_GameControllerClose(sdl_controller);
logger->log("Disconnected gamepad");
const char* controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
SDL_GameControllerClose(sdl_controller);
if (auto it = gamepad_map.find(sdl_event.cdevice.which); it != gamepad_map.end())
{
it->second->disconnect();
}
}
break;
}
case SDL_WINDOWEVENT:
{
switch (sdl_event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
window_resized();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
window_focus_signal.emit(true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
window_focus_signal.emit(false);
break;
case SDL_WINDOWEVENT_MOVED:
window_motion_signal.emit(sdl_event.window.data1, sdl_event.window.data2);
break;
case SDL_WINDOWEVENT_CLOSE:
window_close_signal.emit();
break;
default:
break;
debug::log::info("Disconnected gamepad \"{}\"", controller_name);
}
break;
}
case SDL_QUIT:
[[unlikely]] case SDL_QUIT:
{
debug::log::info("Quit requested");
close();
break;
}
@ -576,10 +634,10 @@ void application::process_events()
}
// Process accumulated mouse motion events
if (mouse_motion)
{
mouse->move(mouse_x, mouse_y, mouse_dx, mouse_dy);
}
// if (mouse_motion)
// {
// mouse.move(mouse_x, mouse_y, mouse_dx, mouse_dy);
// }
}
void application::window_resized()
@ -588,14 +646,17 @@ void application::window_resized()
SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
window_resized_event event;
event.w = window_dimensions[0];
event.h = window_dimensions[1];
debug::log::debug("Window resized to {}x{}", window_dimensions[0], window_dimensions[1]);
event_dispatcher->queue(event);
rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
window_size_signal.emit(window_dimensions[0], window_dimensions[1]);
viewport_size_signal.emit(viewport_dimensions[0], viewport_dimensions[1]);
window_resized_publisher.publish
(
{
window_dimensions[0],
window_dimensions[1],
viewport_dimensions[0],
viewport_dimensions[1]
}
);
}

+ 63
- 121
src/application.hpp View File

@ -20,21 +20,21 @@
#ifndef ANTKEEPER_APPLICATION_HPP
#define ANTKEEPER_APPLICATION_HPP
#include <list>
#include <memory>
#include <unordered_map>
#include "event/publisher.hpp"
#include "gl/rasterizer.hpp"
#include "input/device-manager.hpp"
#include "input/event.hpp"
#include "input/gamepad.hpp"
#include "input/keyboard.hpp"
#include "input/mouse.hpp"
#include "input/gamepad.hpp"
#include "utility/fundamental-types.hpp"
#include "debug/logger.hpp"
#include "event/signal.hpp"
#include <list>
#include <memory>
#include <unordered_map>
// Forward declarations
typedef struct SDL_Window SDL_Window;
typedef void* SDL_GLContext;
class event_dispatcher;
/**
*
@ -43,14 +43,12 @@ class application
{
public:
/**
* Creates and initializes an application.
*
* @param logger Debug logger.
* Constructs and initializes an application.
*/
application(debug::logger& log);
application();
/**
* Destroys an application.
* Destructs an application.
*/
~application();
@ -112,62 +110,67 @@ public:
void add_game_controller_mappings(const void* mappings, std::size_t size);
/// Returns the dimensions of the current display.
const int2& get_display_dimensions() const;
[[nodiscard]] const int2& get_display_dimensions() const;
/// Returns the DPI of the display.
float get_display_dpi() const;
[[nodiscard]] float get_display_dpi() const;
/// Returns the dimensions of the window.
const int2& get_window_dimensions() const;
[[nodiscard]] const int2& get_window_dimensions() const;
/// Returns the dimensions of the window's drawable viewport.
const int2& get_viewport_dimensions() const;
[[nodiscard]] const int2& get_viewport_dimensions() const;
/// Returns `true` if the window is in fullscreen mode, `false` otherwise.
bool is_fullscreen() const;
[[nodiscard]] bool is_fullscreen() const;
/// Returns `true` if the v-sync is enabled, `false` otherwise.
bool get_v_sync() const;
[[nodiscard]] bool get_v_sync() const;
/// Returns the rasterizer for the window.
gl::rasterizer* get_rasterizer();
/// Returns the application logger.
debug::logger* get_logger();
/// Returns the virtual keyboard.
input::keyboard* get_keyboard();
/// Returns the virtual mouse.
input::mouse* get_mouse();
/// Returns the list of virtual gamepads.
const std::list<input::gamepad*>& get_gamepads();
/// Returns the application's event dispatcher.
event_dispatcher* get_event_dispatcher();
[[nodiscard]] gl::rasterizer* get_rasterizer();
void process_events();
bool was_closed() const;
/// Returns a connector for the signal emitted when a gamepad is connected or disconnected.
connector<void(input::gamepad&, bool)>& get_gamepad_connection_signal() noexcept;
[[nodiscard]] bool was_closed() const;
/// Returns a connector for the signal emitted when the window is requested to close.
connector<void()>& get_window_close_signal() noexcept;
/// Returns a connector for the signal emitted each time the window gains or loses focus.
connector<void(bool)>& get_window_focus_signal() noexcept;
/// Returns a connector for the signal emitted each time the window is moved.
connector<void(int, int)>& get_window_motion_signal() noexcept;
/// Returns a connector for the signal emitted each time the window is resized.
connector<void(int, int)>& get_window_size_signal() noexcept;
/// Returns a connector for the signal emitted each time the window viewport is resized.
connector<void(int, int)>& get_viewport_size_signal() noexcept;
/**
* Returns the input device manager.
*/
/// @{
[[nodiscard]] inline const input::device_manager& get_device_manager() const noexcept
{
return device_manager;
}
[[nodiscard]] inline input::device_manager& get_device_manager() noexcept
{
return device_manager;
}
/// @}
/// Returns the channel through which window closed events are published.
[[nodiscard]] inline event::channel<input::event::window_closed>& get_window_closed_channel() noexcept
{
return window_closed_publisher.channel();
}
/// Returns the channel through which window focus changed events are published.
[[nodiscard]] inline event::channel<input::event::window_focus_changed>& get_window_focus_changed_channel() noexcept
{
return window_focus_changed_publisher.channel();
}
/// Returns the channel through which window moved events are published.
[[nodiscard]] inline event::channel<input::event::window_moved>& get_window_moved_channel() noexcept
{
return window_moved_publisher.channel();
}
/// Returns the channel through which window resized events are published.
[[nodiscard]] inline event::channel<input::event::window_resized>& get_window_resized_channel() noexcept
{
return window_resized_publisher.channel();
}
private:
void window_resized();
@ -181,35 +184,24 @@ private:
int2 window_dimensions;
int2 viewport_dimensions;
int2 mouse_position;
debug::logger* logger;
SDL_Window* sdl_window;
SDL_GLContext sdl_gl_context;
gl::rasterizer* rasterizer;
// Events
event_dispatcher* event_dispatcher;
// Input devices
input::keyboard* keyboard;
input::mouse* mouse;
std::list<input::gamepad*> gamepads;
input::device_manager device_manager;
input::keyboard keyboard;
input::mouse mouse;
std::unordered_map<int, input::gamepad*> gamepad_map;
signal<void(input::gamepad&, bool)> gamepad_connection_signal;
signal<void()> window_close_signal;
signal<void(bool)> window_focus_signal;
signal<void(int, int)> window_motion_signal;
signal<void(int, int)> window_size_signal;
signal<void(int, int)> viewport_size_signal;
event::publisher<input::event::window_closed> window_closed_publisher;
event::publisher<input::event::window_focus_changed> window_focus_changed_publisher;
event::publisher<input::event::window_moved> window_moved_publisher;
event::publisher<input::event::window_resized> window_resized_publisher;
};
inline debug::logger* application::get_logger()
{
return logger;
}
inline const int2& application::get_display_dimensions() const
{
return display_dimensions;
@ -245,59 +237,9 @@ inline gl::rasterizer* application::get_rasterizer()
return rasterizer;
}
inline input::keyboard* application::get_keyboard()
{
return keyboard;
}
inline input::mouse* application::get_mouse()
{
return mouse;
}
inline const std::list<input::gamepad*>& application::get_gamepads()
{
return gamepads;
}
inline event_dispatcher* application::get_event_dispatcher()
{
return event_dispatcher;
}
inline bool application::was_closed() const
{
return closed;
}
inline connector<void(input::gamepad&, bool)>& application::get_gamepad_connection_signal() noexcept
{
return gamepad_connection_signal.connector();
}
inline connector<void()>& application::get_window_close_signal() noexcept
{
return window_close_signal.connector();
}
inline connector<void(bool)>& application::get_window_focus_signal() noexcept
{
return window_focus_signal.connector();
}
inline connector<void(int, int)>& application::get_window_motion_signal() noexcept
{
return window_motion_signal.connector();
}
inline connector<void(int, int)>& application::get_window_size_signal() noexcept
{
return window_size_signal.connector();
}
inline connector<void(int, int)>& application::get_viewport_size_signal() noexcept
{
return viewport_size_signal.connector();
}
#endif // ANTKEEPER_APPLICATION_HPP

+ 2
- 2
src/color/cct.hpp View File

@ -20,8 +20,8 @@
#ifndef ANTKEEPER_COLOR_CCT_HPP
#define ANTKEEPER_COLOR_CCT_HPP
#include "ucs.hpp"
#include "xyy.hpp"
#include "color/ucs.hpp"
#include "color/xyy.hpp"
#include "math/vector.hpp"
namespace color {

+ 10
- 10
src/color/color.hpp View File

@ -23,15 +23,15 @@
/// Color manipulation.
namespace color {}
#include "aces.hpp"
#include "cat.hpp"
#include "cct.hpp"
#include "illuminant.hpp"
#include "index.hpp"
#include "rgb.hpp"
#include "srgb.hpp"
#include "ucs.hpp"
#include "xyy.hpp"
#include "xyz.hpp"
#include "color/aces.hpp"
#include "color/cat.hpp"
#include "color/cct.hpp"
#include "color/illuminant.hpp"
#include "color/index.hpp"
#include "color/rgb.hpp"
#include "color/srgb.hpp"
#include "color/ucs.hpp"
#include "color/xyy.hpp"
#include "color/xyz.hpp"
#endif // ANTKEEPER_COLOR_HPP

+ 65
- 6
src/config.hpp.in View File

@ -20,15 +20,74 @@
#ifndef ANTKEEPER_CONFIG_HPP
#define ANTKEEPER_CONFIG_HPP
// Disable trace message logging on release builds
#if defined(NDEBUG)
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY 1
#else
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY 0
#endif
#include "math/vector.hpp"
/// Global configuration constants.
namespace config {
constexpr int version_major = @PROJECT_VERSION_MAJOR@;
constexpr int version_minor = @PROJECT_VERSION_MINOR@;
constexpr int version_patch = @PROJECT_VERSION_PATCH@;
constexpr const char* version_string = "@PROJECT_VERSION@";
/// @name Application config
/// @{
/// Application name string.
constexpr const char* application_name = "@PROJECT_NAME@";
/// Application major version number.
constexpr int application_version_major = @PROJECT_VERSION_MAJOR@;
/// Application minor version number.
constexpr int application_version_minor = @PROJECT_VERSION_MINOR@;
/// Application patch version number.
constexpr int application_version_patch = @PROJECT_VERSION_PATCH@;
/// Application version string ("`major.minor.patch`")
constexpr const char* application_version_string = "@PROJECT_VERSION@";
/// @}
/// @name Debug config
/// @{
/// Maximum number of debug logs to archive.
constexpr std::size_t debug_log_archive_capacity = 5;
/// @}
/// @name OpenGL config
/// @{
/// OpenGL major version number, used when creating OpenGL contexts.
constexpr int opengl_version_major = 3;
/// OpenGL minor version number, used when creating OpenGL contexts.
constexpr int opengl_version_minor = 3;
/// Minimum number of bits in the red channel of the color attachment of the OpenGL default framebuffer.
constexpr int opengl_min_red_size = 8;
/// Minimum number of bits in the green channel of the color attachment of the OpenGL default framebuffer.
constexpr int opengl_min_green_size = 8;
/// Minimum number of bits in the blue channel of the color attachment of the OpenGL default framebuffer.
constexpr int opengl_min_blue_size = 8;
/// Minimum number of bits in the alpha channel of the color attachment of the OpenGL default framebuffer.
constexpr int opengl_min_alpha_size = 0;
/// Minimum number of bits in the depth attachment, if any, of the OpenGL default framebuffer.
constexpr int opengl_min_depth_size = 0;
/// Minimum number of bits in the stencil attachment, if any, of the OpenGL default framebuffer.
constexpr int opengl_min_stencil_size = 0;
/// @}
constexpr math::vector<float, 3> global_forward = {0.0f, 0.0f, -1.0f};
constexpr math::vector<float, 3> global_up = {0.0f, 1.0f, 0.0f};
@ -47,10 +106,10 @@ constexpr float menu_mouseover_padding = 0.1f;
constexpr float menu_bg_opacity = 2.0f / 4.0f;
/// RGBA color of active menu items.
constexpr float4 menu_active_color{1.0f, 1.0f, 1.0f, 1.0f};
constexpr math::vector<float, 4> menu_active_color{1.0f, 1.0f, 1.0f, 1.0f};
/// RGBA color of inactive menu items.
constexpr float4 menu_inactive_color{1.0f, 1.0f, 1.0f, 0.5f};
constexpr math::vector<float, 4> menu_inactive_color{1.0f, 1.0f, 1.0f, 0.5f};
/// Duration of the title screen fade in, in seconds.
constexpr float title_fade_in_duration = 1.0f;

+ 0
- 74
src/debug/ansi-codes.hpp View File

@ -1,74 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_ANSI_CODES_HPP
#define ANTKEEPER_DEBUG_ANSI_CODES_HPP
namespace debug {
/// ANSI escape codes.
namespace ansi {
constexpr char* reset = "\u004b[0m";
constexpr char* black = "\u003b[30m";
constexpr char* red = "\u003b[31m";
constexpr char* green = "\u003b[32m";
constexpr char* yellow = "\u003b[33m";
constexpr char* blue = "\u003b[34m";
constexpr char* magenta = "\u003b[35m";
constexpr char* cyan = "\u003b[36m";
constexpr char* white = "\u003b[37m";
constexpr char* bright_black = "\u003b[30;1m";
constexpr char* bright_red = "\u003b[31;1m";
constexpr char* bright_green = "\u003b[32;1m";
constexpr char* bright_yellow = "\u003b[33;1m";
constexpr char* bright_blue = "\u003b[34;1m";
constexpr char* bright_magenta = "\u003b[35;1m";
constexpr char* bright_cyan = "\u003b[36;1m";
constexpr char* bright_white = "\u003b[37;1m";
constexpr char* background_black = "\u003b[40m";
constexpr char* background_red = "\u003b[41m";
constexpr char* background_green = "\u003b[42m";
constexpr char* background_yellow = "\u003b[43m";
constexpr char* background_blue = "\u003b[44m";
constexpr char* background_magenta = "\u003b[45m";
constexpr char* background_cyan = "\u003b[46m";
constexpr char* background_white = "\u003b[47m";
constexpr char* background_bright_black = "\u003b[40;1m";
constexpr char* background_bright_red = "\u003b[41;1m";
constexpr char* background_bright_green = "\u003b[42;1m";
constexpr char* background_bright_yellow = "\u003b[43;1m";
constexpr char* background_bright_blue = "\u003b[44;1m";
constexpr char* background_bright_magenta = "\u003b[45;1m";
constexpr char* background_bright_cyan = "\u003b[46;1m";
constexpr char* background_bright_white = "\u003b[47;1m";
constexpr char* bold = "\u003b[1m";
constexpr char* underline = "\u003b[4m";
constexpr char* reversed = "\u001b[7m";
} // namespace ansi
} // namespace debug
#endif // ANTKEEPER_DEBUG_ANSI_CODES_HPP

+ 3
- 3
src/debug/cli.cpp View File

@ -17,7 +17,7 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cli.hpp"
#include "debug/cli.hpp"
namespace debug {
@ -26,12 +26,12 @@ std::string cli::interpret(const std::string& line) const
std::istringstream stream(line);
std::string command_name;
stream >> command_name;
if (auto it = commands.find(command_name); it != commands.end())
{
return it->second(line.substr(command_name.length() + 1));
}
return std::string();
}

+ 44
- 23
src/debug/cli.hpp View File

@ -21,10 +21,11 @@
#define ANTKEEPER_DEBUG_CLI_HPP
#include <functional>
#include <map>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
namespace debug {
@ -41,12 +42,13 @@ public:
* @return Stringified return value of the command function.
*/
std::string interpret(const std::string& line) const;
/**
* Registers a command with the CLI.
*
* @tparam T Command function return type.
* @tparam Args Command function argument types.
*
* @param name Command name.
* @param function Command function.
*/
@ -56,14 +58,14 @@ public:
template <class T, class... Args>
void register_command(const std::string& name, T (*function)(Args...));
/// @}
/**
* Unregisters a command from the CLI.
*
* @param name Command name.
*/
void unregister_command(const std::string& name);
private:
/// String-wrapped function object
typedef std::function<std::string(const std::string&)> command_type;
@ -71,21 +73,27 @@ private:
/**
* Parses a single argument from a string stream.
*
* @tparam T Argument type.
*
* @param stream String stream from which an argument should be parsed.
*/
template <class T>
static T parse(std::istringstream& stream);
[[nodiscard]] static T parse(std::istringstream& stream);
/**
* Wraps a function in an interpreter function that parses strings as arguments then executes the wrapped function with the parsed arguments.
* Wraps a function in an interpreter function that parses strings as arguments, then executes the wrapped function with the parsed arguments.
*
* @tparam T Function return type.
* @tparam Args Function argument types.
*
* @param function Function to wrap.
*
* @return Wrapped function.
*/
template <class T, class... Args>
static command_type wrap(const std::function<T(Args...)>& function);
std::map<std::string, command_type> commands;
[[nodiscard]] static command_type wrap(std::function<T(Args...)> function);
std::unordered_map<std::string, command_type> commands;
};
template <class T, class... Args>
@ -109,26 +117,39 @@ T cli::parse(std::istringstream& stream)
}
template <class T, class... Args>
typename cli::command_type cli::wrap(const std::function<T(Args...)>& function)
typename cli::command_type cli::wrap(std::function<T(Args...)> function)
{
return std::bind(
return std::bind
(
[function](const std::string& line) -> std::string
{
//std::size_t argument_count = sizeof...(Args);
//constexpr std::size_t argument_count = sizeof...(Args);
// Parse string into tuple of arguments
std::istringstream istream(line);
std::tuple<Args...> arguments{parse<Args>(istream)...};
// Invoke function with arguments and save the result
T result = std::apply(function, arguments);
// Return function result as string
std::ostringstream ostream;
ostream << result;
return ostream.str();
if constexpr(std::is_void_v<T>)
{
// Invoke function with parsed arguments
std::apply(function, arguments);
// Return empty string
return std::string();
}
else
{
// Invoke function with parsed arguments and save the result
T result = std::apply(function, arguments);
// Return invocation result as a string
std::ostringstream ostream;
ostream << result;
return ostream.str();
}
},
std::placeholders::_1);
std::placeholders::_1
);
}
} // namespace debug

+ 0
- 56
src/debug/console-commands.cpp View File

@ -1,56 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "console-commands.hpp"
#include "application.hpp"
#include "animation/timeline.hpp"
#include "debug/cli.hpp"
namespace debug {
namespace cc {
std::string echo(std::string text)
{
return text;
}
std::string exit(game::context* ctx)
{
ctx->app->close();
return std::string();
}
std::string scrot(game::context* ctx)
{
//ctx->app->take_screenshot();
return std::string("screenshot saved");
}
std::string cue(game::context* ctx, float t, std::string command)
{
::timeline* timeline = ctx->timeline;
debug::cli* cli = ctx->cli;
timeline->add_cue({timeline->get_position() + t, std::function<void()>(std::bind(&debug::cli::interpret, cli, command))});
return std::string("command \"" + command + "\" will execute in " + std::to_string(t) + " seconds");
}
} // namespace cc
} // namespace debug

+ 51
- 0
src/debug/console.cpp View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "debug/console.hpp"
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace debug {
namespace console {
void enable_vt100()
{
#if defined(_WIN32)
DWORD mode = 0;
HANDLE std_output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(std_output_handle, &mode);
SetConsoleMode(std_output_handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
#endif
}
void disable_vt100()
{
#if defined(_WIN32)
DWORD mode = 0;
HANDLE std_output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(std_output_handle, &mode);
SetConsoleMode(std_output_handle, mode & (~ENABLE_VIRTUAL_TERMINAL_PROCESSING));
#endif
}
} // namespace console
} // namespace debug

+ 39
- 0
src/debug/console.hpp View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_CONSOLE_HPP
#define ANTKEEPER_DEBUG_CONSOLE_HPP
namespace debug {
/**
* Debug console.
*/
namespace console {
/// Enables VT100 virtual terminal sequences.
void enable_vt100();
/// Disables VT100 virtual terminal sequences.
void disable_vt100();
} // namespace console
} // namespace debug
#endif // ANTKEEPER_DEBUG_CONSOLE_HPP

+ 1
- 6
src/debug/debug.hpp View File

@ -20,12 +20,7 @@
#ifndef ANTKEEPER_DEBUG_HPP
#define ANTKEEPER_DEBUG_HPP
/// Debugging functions and classes
/// Debugging functions and classes.
namespace debug {}
#include "ansi-codes.hpp"
#include "cli.hpp"
#include "logger.hpp"
#include "performance-sampler.hpp"
#endif // ANTKEEPER_DEBUG_HPP

+ 58
- 0
src/debug/log.cpp View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "debug/log.hpp"
namespace debug {
namespace log {
logger& default_logger() noexcept
{
static logger instance;
return instance;
}
void push_task(const std::string& description, std::source_location&& location)
{
default_logger().log(description + " {", message_severity::info, std::forward<std::source_location>(location));
}
void pop_task(int status, const std::string& description, std::source_location&& location)
{
std::string message = "} => ";
if (status == EXIT_SUCCESS)
{
message += "success";
default_logger().log(std::move(message), message_severity::info, std::forward<std::source_location>(location));
}
else
{
message += "failure";
if (!description.empty())
{
message += "(" + description + ")";
}
default_logger().log(std::move(message), message_severity::error, std::forward<std::source_location>(location));
}
}
} // namespace log
} // namespace debug

+ 189
- 0
src/debug/log.hpp View File

@ -0,0 +1,189 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_LOG_HPP
#define ANTKEEPER_DEBUG_LOG_HPP
#include "config.hpp"
#include "debug/log/message-severity.hpp"
#include "debug/log/logger.hpp"
#include <source_location>
#include <string>
#include <format>
// Enable logging of messages of all severities by default.
#if !defined(ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY)
#define ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY (ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE)
#endif
namespace debug {
/**
* Debug message logging.
*/
namespace log {
/**
* Returns the default logger.
*/
[[nodiscard]] logger& default_logger() noexcept;
/**
* Self-formatting message that logs itself to the default logger on construction.
*
* @tparam Severity Message severity. A message will not log itself if @p Severity is less than the user-defined macro `ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY`.
* @tparam Args Types of arguments to be formatted.
*/
template <message_severity Severity, class... Args>
struct message
{
/**
* Formats and logs a message.
*
* Class template argument deduction (CTAD) is utilized to capture source location as a default argument following variadic format arguments.
*
* @param format Message format string.
* @param args Arguments to be formatted.
* @param location Source location from which the message was sent.
*/
message
(
[[maybe_unused]] std::string_view format,
[[maybe_unused]] Args&&... args,
[[maybe_unused]] std::source_location&& location = std::source_location::current()
)
{
if constexpr (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= static_cast<std::underlying_type_t<message_severity>>(Severity))
{
default_logger().log(std::vformat(format, std::make_format_args(std::forward<Args>(args)...)), Severity, std::forward<std::source_location>(location));
}
}
};
// Use class template argument deduction (CTAD) to capture source location as a default argument following variadic format arguments.
template <message_severity Severity, class... Args>
message(std::string_view, Args&&...) -> message<Severity, Args...>;
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE)
/**
* Formats and logs a trace message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using trace = message<message_severity::trace, Args...>;
#else
// Disable trace message logging.
inline void trace([[maybe_unused]] ...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG)
/**
* Formats and logs a debug message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using debug = message<message_severity::debug, Args...>;
#else
// Disable debug message logging.
inline void debug([[maybe_unused]] ...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO)
/**
* Formats and logs an info message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using info = message<message_severity::info, Args...>;
#else
// Disable info message logging.
inline void info([[maybe_unused]] ...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING)
/**
* Formats and logs a warning message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using warning = message<message_severity::warning, Args...>;
#else
// Disable warning message logging.
inline void warning([[maybe_unused]] ...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR)
/**
* Formats and logs an error message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using error = message<message_severity::error, Args...>;
#else
// Disable error message logging.
inline void error([[maybe_unused]] ...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL)
/**
* Formats and logs a fatal error message.
*
* @param Args Types of arguments to be formatted.
*/
template <class... Args>
using fatal = message<message_severity::fatal, Args...>;
#else
// Disable fatal error message logging.
inline void fatal([[maybe_unused]] ...) noexcept {};
#endif
/**
* Pushes a task onto the default logger's task stack and writes its description the log.
*
* @param description Task description.
* @param location Source location from which the message was sent.
*/
void push_task
(
const std::string& description,
std::source_location&& location = std::source_location::current()
);
/**
* Pops a task off the default logger's task stack and writes its status to the log.
*
* @param status Exit status of the task. A value of `0` or `EXIT_SUCCESS` indicates the task exited successfully. A non-zero exit status indicates the task failed.
* @param description Error code description.
*/
void pop_task
(
int status,
const std::string& description = std::string(),
std::source_location&& location = std::source_location::current()
);
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_HPP

+ 67
- 0
src/debug/log/event.hpp View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_LOG_EVENT_HPP
#define ANTKEEPER_DEBUG_LOG_EVENT_HPP
#include "debug/log/message-severity.hpp"
#include <chrono>
#include <source_location>
#include <string>
#include <thread>
namespace debug {
namespace log {
class logger;
/**
* Debug logging events.
*/
namespace event {
/**
* Event generated when a message has been logged.
*/
struct message_logged
{
/// Logger which received the message.
log::logger* logger;
/// Time at which the message was sent.
std::chrono::time_point<std::chrono::system_clock> time;
/// ID of the thread from which the message was sent.
std::thread::id thread_id;
/// Source location from which the message was sent.
std::source_location location;
/// Severity of the message.
message_severity severity;
/// Message contents.
std::string message;
};
} // namespace event
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_EVENT_HPP

src/debug/console-commands.hpp → src/debug/log/logger.cpp View File

@ -17,26 +17,28 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_CONSOLE_COMMANDS_HPP
#define ANTKEEPER_DEBUG_CONSOLE_COMMANDS_HPP
#include "game/context.hpp"
#include <string>
#include "debug/log/logger.hpp"
#include <thread>
#include <utility>
namespace debug {
/// Console commands
namespace cc {
std::string echo(std::string text);
std::string exit(game::context* ctx);
std::string scrot(game::context* ctx);
std::string cue(game::context* ctx, float t, std::string command);
} // namespace cc
namespace log {
void logger::log(std::string&& message, message_severity severity, std::source_location&& location)
{
// Generate message logged event
message_logged_publisher.publish
(
{
this,
std::chrono::system_clock::now(),
std::this_thread::get_id(),
std::move(location),
severity,
std::move(message)
}
);
}
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_CONSOLE_COMMANDS_HPP

+ 65
- 0
src/debug/log/logger.hpp View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_LOG_LOGGER_HPP
#define ANTKEEPER_DEBUG_LOG_LOGGER_HPP
#include "debug/log/message-severity.hpp"
#include "debug/log/event.hpp"
#include "event/publisher.hpp"
#include <source_location>
#include <string>
namespace debug {
namespace log {
/**
* Generates an event each time a message logged.
*/
class logger
{
public:
/**
* Logs a message.
*
* @param message Message contents.
* @param severity Message severity.
* @param location Source location from which the message was sent.
*/
void log
(
std::string&& message,
message_severity severity = message_severity::info,
std::source_location&& location = std::source_location::current()
);
/// Returns the channel through which message logged events are published.
[[nodiscard]] inline ::event::channel<event::message_logged>& get_message_logged_channel() noexcept
{
return message_logged_publisher.channel();
}
private:
::event::publisher<event::message_logged> message_logged_publisher;
};
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_LOGGER_HPP

+ 71
- 0
src/debug/log/message-severity.hpp View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_HPP
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_HPP
#include <cstdint>
/// Trace message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE 0
/// Debug message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG 1
/// Info message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO 2
/// Warning message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING 3
/// Error message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR 4
/// Fatal error message severity level.
#define ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL 5
namespace debug {
namespace log {
/// Log message severity levels.
enum class message_severity: std::uint8_t
{
/// Trace message severity.
trace = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_TRACE,
/// Debug message severity.
debug = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG,
/// Info message severity.
info = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO,
/// Warning message severity.
warning = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING,
/// Error message severity.
error = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR,
/// Fatal error message severity.
fatal = ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL,
};
} // namespace log
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_HPP

+ 0
- 230
src/debug/logger.cpp View File

@ -1,230 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "logger.hpp"
#include "utility/timestamp.hpp"
#include <iostream>
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace debug {
logger::logger():
os(&std::cout),
auto_newline(true),
timestamp_enabled(true),
indent("| "),
log_prefix(std::string()),
log_postfix(std::string()),
warning_prefix(std::string()),
warning_postfix(std::string()),
error_prefix(std::string()),
error_postfix(std::string()),
success_prefix(std::string()),
success_postfix(std::string())
{}
logger::~logger()
{}
void logger::redirect(std::ostream* stream)
{
os = stream;
}
void logger::log(const std::string& text)
{
if (os)
{
std::string message = "";
// Prepend timestamp
if (timestamp_enabled)
{
message += timestamp();
message += ": ";
}
// Prepend indentation
for (std::size_t i = 0; i < tasks.size(); ++i)
message += indent;
// Append text
message += (log_prefix + text + log_postfix);
// Append newline
if (auto_newline)
message += "\n";
// Add message to log history
const std::lock_guard<std::mutex> history_lock(history_mutex);
history += message;
// Output message
(*os) << message;
// Flush output stream
os->flush();
}
}
void logger::warning(const std::string& text)
{
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN);
#endif
log(warning_prefix + text + warning_postfix);
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
#endif
}
void logger::error(const std::string& text)
{
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);
#endif
log(error_prefix + text + error_postfix);
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
#endif
}
void logger::success(const std::string& text)
{
/*
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);
#endif
*/
log(success_prefix + text + success_postfix);
/*
#if defined(_WIN32)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
#endif
*/
}
void logger::set_auto_newline(bool enabled)
{
auto_newline = enabled;
}
void logger::set_timestamp(bool enabled)
{
timestamp_enabled = enabled;
}
void logger::set_indent(const std::string& indent)
{
this->indent = indent;
}
void logger::set_log_prefix(const std::string& prefix)
{
log_prefix = prefix;
}
void logger::set_log_postfix(const std::string& postfix)
{
log_postfix = postfix;
}
void logger::set_warning_prefix(const std::string& prefix)
{
warning_prefix = prefix;
}
void logger::set_warning_postfix(const std::string& postfix)
{
warning_postfix = postfix;
}
void logger::set_error_prefix(const std::string& prefix)
{
error_prefix = prefix;
}
void logger::set_error_postfix(const std::string& postfix)
{
error_postfix = postfix;
}
void logger::set_success_prefix(const std::string& prefix)
{
success_prefix = prefix;
}
void logger::set_success_postfix(const std::string& postfix)
{
success_postfix = postfix;
}
void logger::push_task(const std::string& description)
{
std::string message = description + " {";
if (!auto_newline)
message += "\n";
log(message);
tasks.push(description);
}
void logger::pop_task(int status, std::string error)
{
if (tasks.empty())
{
return;
}
std::string message = "} => ";
tasks.pop();
if (status == EXIT_SUCCESS)
{
message += "success";
if (!auto_newline)
message += "\n";
success(message);
}
else
{
message += "failure";
if (!error.empty())
message += " (" + error + ")";
if (!auto_newline)
message += "\n";
this->error(message);
}
}
} // namespace debug

+ 0
- 130
src/debug/logger.hpp View File

@ -1,130 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_DEBUG_LOGGER_HPP
#define ANTKEEPER_DEBUG_LOGGER_HPP
#include <list>
#include <ostream>
#include <stack>
#include <string>
#include <mutex>
namespace debug {
/**
* Logs formatted debug messages to an output stream.
*/
class logger
{
public:
/// Creates a logger.
logger();
/// Destroys a logger.
~logger();
/**
* Redirects log output to the specified output stream.
*
* @param stream Output stream to which log text will be written.
*/
void redirect(std::ostream* stream);
/**
* Outputs text to the log.
*/
void log(const std::string& text);
void warning(const std::string& text);
void error(const std::string& text);
void success(const std::string& text);
/**
* Enables or disables automatic appending of newlines to log messages.
*
* @param enabled `true` if auto newline should be enabled, `false` otherwise.
*/
void set_auto_newline(bool enabled);
/**
* Enables or disables prefixing log messages with a timestamp.
*
* @param enabled `true` if timestamps should be enabled, `false` otherwise.
*/
void set_timestamp(bool enabled);
/**
* Sets the indent string which prefixes subtasks. This string will be repeated according to the level of indentation.
*
* @param indent Indent string.
*/
void set_indent(const std::string& indent);
void set_log_prefix(const std::string& prefix);
void set_log_postfix(const std::string& postfix);
void set_warning_prefix(const std::string& prefix);
void set_warning_postfix(const std::string& postfix);
void set_error_prefix(const std::string& prefix);
void set_error_postfix(const std::string& postfix);
void set_success_prefix(const std::string& prefix);
void set_success_postfix(const std::string& postfix);
/**
* Pushes a task onto the stack and outputs it to the log.
*
* @param description Task description.
*/
void push_task(const std::string& description);
/**
* Pops a task off the stack and outputs its status to the log.
*
* @param status Exit status of the task. A value of `0` or `EXIT_SUCCESS` indicates the task exited successfully. A non-zero exit status indicates the task failed.
*/
void pop_task(int status, std::string error = std::string());
const std::string& get_history() const;
private:
std::ostream* os;
bool auto_newline;
bool timestamp_enabled;
std::string indent;
std::string log_prefix;
std::string log_postfix;
std::string warning_prefix;
std::string warning_postfix;
std::string error_prefix;
std::string error_postfix;
std::string success_prefix;
std::string success_postfix;
std::stack<std::string> tasks;
std::string history;
std::mutex history_mutex;
};
inline const std::string& logger::get_history() const
{
return history;
}
} // namespace debug
#endif // ANTKEEPER_DEBUG_LOGGER_HPP

+ 1
- 1
src/debug/performance-sampler.hpp View File

@ -56,7 +56,7 @@ public:
/**
* Returns the mean frame duration.
*/
double mean_frame_duration() const;
[[nodiscard]] double mean_frame_duration() const;
private:
std::vector<double> samples;

+ 104
- 0
src/event/channel.hpp View File

@ -0,0 +1,104 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_CHANNEL_HPP
#define ANTKEEPER_EVENT_CHANNEL_HPP
#include <functional>
#include <list>
#include <memory>
#include <utility>
#include "event/subscriber.hpp"
#include "event/subscription.hpp"
#include "event/queue.hpp"
namespace event {
template <class T>
class publisher;
/**
* Channel through which messages are published.
*
* @tparam T Message type.
*/
template <class T>
class channel
{
public:
/// Message type.
typedef T message_type;
/// Subscriber function object type.
typedef subscriber<message_type> subscriber_type;
/**
* Subscribes a function object to messages published through this channel.
*
* @param subscriber Subscriber function object which will received published messages.
*
* @return Shared subscription object which will unsubscribe the subscriber on destruction.
*/
[[nodiscard]] std::shared_ptr<subscription> subscribe(subscriber_type&& subscriber)
{
// Construct shared pointer to subscriber function
std::shared_ptr<subscriber_type> shared_subscriber = std::make_shared<subscriber_type>(std::move(subscriber));
// Append subscriber to subscriber list and store iterator
auto iterator = subscribers.insert(subscribers.end(), shared_subscriber);
// Construct and return a shared subscription object which removes the subscriber from the subscriber list when unsubscribed or destructed
return std::make_shared<subscription>
(
std::static_pointer_cast<void>(shared_subscriber),
[this, iterator = std::move(iterator)]
{
this->subscribers.erase(iterator);
}
);
}
/**
* Subscribes a message queue to messages published through this channel.
*
* @param queue Message queue which will received published messages.
*
* @return Shared subscription object which will unsubscribe the queue on destruction.
*/
[[nodiscard]] std::shared_ptr<subscription> subscribe(event::queue& queue)
{
return subscribe
(
[&queue](const message_type& message)
{
queue.enqueue<message_type>(message);
}
);
}
private:
friend class publisher<T>;
/// List of subscribers.
std::list<std::shared_ptr<subscriber_type>> subscribers;
};
} // namespace event
#endif // ANTKEEPER_EVENT_CHANNEL_HPP

+ 0
- 125
src/event/event-dispatcher.cpp View File

@ -1,125 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "event-dispatcher.hpp"
event_dispatcher::event_dispatcher():
updating(false)
{}
event_dispatcher::~event_dispatcher()
{
clear();
}
void event_dispatcher::update(double time)
{
updating = true;
// Process pending subscriptions
for (auto it = to_subscribe.begin(); it != to_subscribe.end(); ++it)
{
handler_map[std::get<0>(*it)].push_back(std::get<1>(*it));
}
to_subscribe.clear();
// Process pending unsubscriptions
for (auto it = to_unsubscribe.begin(); it != to_unsubscribe.end(); ++it)
{
handler_map[std::get<0>(*it)].remove(std::get<1>(*it));
}
to_unsubscribe.clear();
// Dispatch queued events
flush();
// For each scheduled event
for (auto event = scheduled_events.begin(); event != scheduled_events.end();)
{
// If the event is due
if (time >= event->first)
{
// Dispatch event
dispatch(*(event->second));
// Delete event
delete event->second;
event = scheduled_events.erase(event);
}
else
{
break;
}
}
updating = false;
}
void event_dispatcher::dispatch(const event_base& event)
{
// Get list of handlers for this type of event
const std::list<event_handler_base*>& handlers = handler_map[event.get_event_type_id()];
// For each handler
for (auto handler = handlers.begin(); handler != handlers.end(); ++handler)
{
// Pass event to the handler
(*handler)->route_event(event);
}
}
void event_dispatcher::flush()
{
// For each event in the queue
for (auto event = queued_events.begin(); event != queued_events.end(); ++event)
{
// Dispatch event
dispatch(**event);
// Delete event
delete (*event);
}
// Clear event queue
queued_events.clear();
}
void event_dispatcher::clear()
{
// For each event in the queue
for (auto event = queued_events.begin(); event != queued_events.end(); ++event)
{
// Delete event
delete (*event);
}
// Clear event queue
queued_events.clear();
// For each scheduled event
for (auto event = scheduled_events.begin(); event != scheduled_events.end(); ++event)
{
// Delete event
delete event->second;
}
// Clear scheduled events
scheduled_events.clear();
}

+ 0
- 140
src/event/event-dispatcher.hpp View File

@ -1,140 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_DISPATCHER_HPP
#define ANTKEEPER_EVENT_DISPATCHER_HPP
#include "event.hpp"
#include "event-handler.hpp"
#include <cstddef>
#include <list>
#include <map>
/**
* Queues events and dispatches them to event handlers.
*/
class event_dispatcher
{
public:
/// Creates an event dispatcher
event_dispatcher();
/// Destroys an event dispatcher
~event_dispatcher();
/**
* Processes all pending subscriptions and unsubscriptions, dispatches queued events, then dispatches due scheduled events.
*
* @param time The current time.
*/
void update(double time);
/**
* Subscribes an event handler to event dispatches.
*
* @param handler Handler to subscribe.
*/
template <typename T>
void subscribe(event_handler<T>* handler);
/**
* Unsubscribes an event handler from event dispatches.
*
* @param handler Handler to unsubscribe.
*/
template <typename T>
void unsubscribe(event_handler<T>* handler);
/**
* Adds an event to the queue.
*
* @param event Event to queue.
*/
void queue(const event_base& event);
/**
* Schedules an event to be dispatched at a specific time.
*
* @param event Event to schedule.
* @param time Time that the event should be dispatched.
*/
void schedule(const event_base& event, double time);
/**
* Dispatches a single event.
*
* @param event Event to dispatch.
*/
void dispatch(const event_base& event);
/**
* Dispatches all events in the queue.
*/
void flush();
/// Removes all queued and scheduled events from the queue without notifying handlers.
void clear();
private:
std::list<std::tuple<std::size_t, event_handler_base*>> to_subscribe;
std::list<std::tuple<std::size_t, event_handler_base*>> to_unsubscribe;
std::map<std::size_t, std::list<event_handler_base*>> handler_map;
std::list<event_base*> queued_events;
std::multimap<double, event_base*> scheduled_events;
bool updating;
};
template <typename T>
void event_dispatcher::subscribe(event_handler<T>* handler)
{
if (updating)
{
to_subscribe.push_back(std::make_tuple(handler->get_handled_event_type_id(), handler));
}
else
{
handler_map[handler->get_handled_event_type_id()].push_back(handler);
}
}
template <typename T>
void event_dispatcher::unsubscribe(event_handler<T>* handler)
{
if (updating)
{
to_unsubscribe.push_back(std::make_tuple(handler->get_handled_event_type_id(), handler));
}
else
{
handler_map[handler->get_handled_event_type_id()].remove(handler);
}
}
inline void event_dispatcher::queue(const event_base& event)
{
queued_events.push_back(event.clone());
}
inline void event_dispatcher::schedule(const event_base& event, double time)
{
scheduled_events.insert(std::pair<double, event_base*>(time, event.clone()));
}
#endif // ANTKEEPER_EVENT_DISPATCHER_HPP

+ 0
- 83
src/event/event-handler.hpp View File

@ -1,83 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_HANDLER_HPP
#define ANTKEEPER_EVENT_HANDLER_HPP
#include "event.hpp"
#include <type_traits>
class event_dispatcher;
/**
* Abstract base class for event handlers.
*/
class event_handler_base
{
private:
friend class event_dispatcher;
/**
* Receives an event, casts it to its derived event type, then handles it.
*
* @param event Received event.
*/
virtual void route_event(const event_base& event) = 0;
};
/**
* Templates abstract base class for event handlers.
*
* @tparam Event type.
*/
template <typename T>
class event_handler: public event_handler_base
{
public:
static_assert(std::is_base_of<event_base, T>::value, "T must be a descendant of event_base.");
/// Returns the unique event type identifier for the event type handled by this event handler.
const std::size_t get_handled_event_type_id() const;
/**
* Handles an event of type T.
*
* @param event Event to handle.
*/
virtual void handle_event(const T& event) = 0;
private:
/// @copydoc event_handler_base::route_event()
virtual void route_event(const event_base& event) final;
};
template <typename T>
inline const std::size_t event_handler<T>::get_handled_event_type_id() const
{
return T::event_type_id;
}
template <typename T>
void event_handler<T>::route_event(const event_base& event)
{
handle_event(static_cast<const T&>(event));
}
#endif // ANTKEEPER_EVENT_HANDLER_HPP

+ 0
- 88
src/event/event.hpp View File

@ -1,88 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_HPP
#define ANTKEEPER_EVENT_HPP
#include <atomic>
#include <cstddef>
/**
* Abstract base class for events.
*/
class event_base
{
public:
/// Destroys an event base.
virtual ~event_base() = default;
/// Returns the unique event type identifier for this event type.
virtual const std::size_t get_event_type_id() const = 0;
/**
* Allocates a copy of this event.
*
* @return Newly allocated copy of this event.
*/
virtual event_base* clone() const = 0;
protected:
/// Returns then increments the next available event type ID.
static std::size_t next_event_type_id();
};
inline std::size_t event_base::next_event_type_id()
{
static std::atomic<std::size_t> next_event_type_id{0};
return next_event_type_id++;
}
/**
* Templated abstract base class for events.
*
* @tparam T The derived class.
*/
template <typename T>
class event: public event_base
{
public:
/// The unique event type identifier for this event type.
static const std::atomic<std::size_t> event_type_id;
/// Destroys an event
virtual ~event() = default;
/// @copydoc event_base::get_event_type_id() const
virtual const std::size_t get_event_type_id() const final;
/// @copydoc event_base::clone() const
virtual event_base* clone() const = 0;
};
template <typename T>
const std::atomic<std::size_t> event<T>::event_type_id{event_base::next_event_type_id()};
template <typename T>
inline const std::size_t event<T>::get_event_type_id() const
{
return event_type_id;
}
#endif // ANTKEEPER_EVENT_HPP

+ 0
- 117
src/event/input-events.cpp View File

@ -1,117 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "input-events.hpp"
event_base* key_pressed_event::clone() const
{
key_pressed_event* event = new key_pressed_event();
event->keyboard = keyboard;
event->scancode = scancode;
return event;
}
event_base* key_released_event::clone() const
{
key_released_event* event = new key_released_event();
event->keyboard = keyboard;
event->scancode = scancode;
return event;
}
event_base* mouse_moved_event::clone() const
{
mouse_moved_event* event = new mouse_moved_event();
event->mouse = mouse;
event->x = x;
event->y = y;
event->dx = dx;
event->dy = dy;
return event;
}
event_base* mouse_button_pressed_event::clone() const
{
mouse_button_pressed_event* event = new mouse_button_pressed_event();
event->mouse = mouse;
event->button = button;
event->x = x;
event->y = y;
return event;
}
event_base* mouse_button_released_event::clone() const
{
mouse_button_released_event* event = new mouse_button_released_event();
event->mouse = mouse;
event->button = button;
event->x = x;
event->y = y;
return event;
}
event_base* mouse_wheel_scrolled_event::clone() const
{
mouse_wheel_scrolled_event* event = new mouse_wheel_scrolled_event();
event->mouse = mouse;
event->x = x;
event->y = y;
return event;
}
event_base* gamepad_connected_event::clone() const
{
gamepad_connected_event* event = new gamepad_connected_event();
event->controller = controller;
event->reconnected = reconnected;
return event;
}
event_base* gamepad_disconnected_event::clone() const
{
gamepad_disconnected_event* event = new gamepad_disconnected_event();
event->controller = controller;
return event;
}
event_base* gamepad_button_pressed_event::clone() const
{
gamepad_button_pressed_event* event = new gamepad_button_pressed_event();
event->controller = controller;
event->button = button;
return event;
}
event_base* gamepad_button_released_event::clone() const
{
gamepad_button_released_event* event = new gamepad_button_released_event();
event->controller = controller;
event->button = button;
return event;
}
event_base* gamepad_axis_moved_event::clone() const
{
gamepad_axis_moved_event* event = new gamepad_axis_moved_event();
event->controller = controller;
event->axis = axis;
event->value = value;
return event;
}

+ 0
- 170
src/event/input-events.hpp View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_EVENTS_HPP
#define ANTKEEPER_INPUT_EVENTS_HPP
#include "event/event.hpp"
#include "input/scancode.hpp"
#include "input/keyboard.hpp"
#include "input/mouse.hpp"
#include "input/gamepad.hpp"
/**
* Input event which indicates a keyboard key has been pressed.
*/
class key_pressed_event: public event<key_pressed_event>
{
public:
virtual event_base* clone() const;
input::keyboard* keyboard;
input::scancode scancode;
};
/**
* Input event which indicates a keyboard key has been released.
*/
class key_released_event: public event<key_released_event>
{
public:
virtual event_base* clone() const;
input::keyboard* keyboard;
input::scancode scancode;
};
/**
* Input event which indicates a mouse has been moved.
*/
class mouse_moved_event: public event<mouse_moved_event>
{
public:
virtual event_base* clone() const;
input::mouse* mouse;
int x;
int y;
int dx;
int dy;
};
/**
* Input event which indicates a mouse button has been pressed.
*/
class mouse_button_pressed_event: public event<mouse_button_pressed_event>
{
public:
virtual event_base* clone() const;
input::mouse* mouse;
int button;
int x;
int y;
};
/**
* Input event which indicates a mouse button has been released.
*/
class mouse_button_released_event: public event<mouse_button_released_event>
{
public:
virtual event_base* clone() const;
input::mouse* mouse;
int button;
int x;
int y;
};
/**
* Input event which indicates a mouse wheel has been scrolled.
*/
class mouse_wheel_scrolled_event: public event<mouse_wheel_scrolled_event>
{
public:
virtual event_base* clone() const;
input::mouse* mouse;
int x;
int y;
};
/**
* Input event which indicates a controller has been connected.
*/
class gamepad_connected_event: public event<gamepad_connected_event>
{
public:
virtual event_base* clone() const;
input::gamepad* controller;
bool reconnected;
};
/**
* Input event which indicates a controller has been disconnected.
*/
class gamepad_disconnected_event: public event<gamepad_disconnected_event>
{
public:
virtual event_base* clone() const;
input::gamepad* controller;
};
/**
* Input event which indicates a controller button has been pressed.
*/
class gamepad_button_pressed_event: public event<gamepad_button_pressed_event>
{
public:
virtual event_base* clone() const;
input::gamepad* controller;
input::gamepad_button button;
};
/**
* Input event which indicates a controller button has been released.
*/
class gamepad_button_released_event: public event<gamepad_button_released_event>
{
public:
virtual event_base* clone() const;
input::gamepad* controller;
input::gamepad_button button;
};
/**
* Input event which indicates a controller axis has been moved.
*/
class gamepad_axis_moved_event: public event<gamepad_axis_moved_event>
{
public:
virtual event_base* clone() const;
input::gamepad* controller;
input::gamepad_axis axis;
float value;
};
#endif // ANTKEEPER_INPUT_EVENTS_HPP

+ 95
- 0
src/event/publisher.hpp View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_PUBLISHER_HPP
#define ANTKEEPER_EVENT_PUBLISHER_HPP
#include <algorithm>
#include <execution>
#include "event/channel.hpp"
namespace event {
/**
* Publishes messages to subscribers.
*
* @tparam T Message type.
*/
template <class T>
class publisher
{
public:
/// Message type.
typedef T message_type;
/// Channel type.
typedef channel<message_type> channel_type;
/**
* Publishes a message.
*
* @tparam ExecutionPolicy Execution policy type.
*
* @param policy Execution policy to use.
* @param message Message to publish.
*/
/// @{
template <class ExecutionPolicy>
void publish(ExecutionPolicy&& policy, const message_type& message) const
{
std::for_each
(
policy,
std::begin(m_channel.subscribers),
std::end(m_channel.subscribers),
[&](const auto& subscriber)
{
(*subscriber)(message);
}
);
}
void publish(const message_type& message) const
{
publish(std::execution::seq, message);
}
/// @}
/**
* Returns the channel through which messages are published.
*/
/// @{
[[nodiscard]] inline const channel_type& channel() const noexcept
{
return m_channel;
}
[[nodiscard]] inline channel_type& channel() noexcept
{
return m_channel;
}
/// @}
private:
channel_type m_channel;
};
} // namespace event
#endif // ANTKEEPER_EVENT_PUBLISHER_HPP

+ 143
- 0
src/event/queue.hpp View File

@ -0,0 +1,143 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_QUEUE_HPP
#define ANTKEEPER_EVENT_QUEUE_HPP
#include <any>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <utility>
#include "event/subscriber.hpp"
#include "event/subscription.hpp"
#include "utility/type-id.hpp"
namespace event {
/**
* Collects messages from publishers to be distributed to subscribers when desired.
*/
class queue
{
public:
/**
* Subscribes a function object to messages published by this queue.
*
* @tparam T Message type.
*
* @param subscriber Function object to subscribe.
*
* @return Shared subscription object which will unsubscribe the subscriber on destruction.
*/
template <class T>
[[nodiscard]] std::shared_ptr<subscription> subscribe(subscriber<T>&& subscriber)
{
// Allocate shared pointer to std::any object containing subscriber
std::shared_ptr<std::any> shared_subscriber = std::make_shared<std::any>(std::make_any<event::subscriber<T>>(std::move(subscriber)));
// Append subscriber to subscriber list and store iterator
auto iterator = subscribers.emplace(type_id<T>, shared_subscriber);
// Construct and return a shared subscription object which removes the subscriber from the subscriber list when unsubscribed or destructed
return std::make_shared<subscription>
(
std::static_pointer_cast<void>(shared_subscriber),
[this, iterator = std::move(iterator)]()
{
this->subscribers.erase(iterator);
}
);
}
/**
* Adds a message to the queue, to be distributed later.
*
* @tparam T Message type.
*
* @param message Message to enqueue.
*/
template <class T>
void enqueue(const T& message)
{
messages.emplace_back
(
[this, message]()
{
this->distribute<T>(message);
}
);
}
/**
* Distributes queued messages in FIFO order to subscribers.
*/
void flush()
{
while (!messages.empty())
{
messages.front()();
messages.pop_front();
}
}
/**
* Removes all messages from the queue.
*/
void clear()
{
messages.clear();
}
/**
* Returns `true` if there are no messages in the queue, `false` otherwise.
*/
[[nodiscard]] inline bool empty() const noexcept
{
return messages.empty();
}
private:
/**
* Distributes a message.
*
* @tparam T Message type.
*
* @param message Message to distribute.
*/
template <class T>
void distribute(const T& message) const
{
// For each subscriber of the given message type
const auto range = subscribers.equal_range(type_id<T>);
for (auto i = range.first; i != range.second; ++i)
{
// Send message to subscriber
std::any_cast<subscriber<T>>(*(i->second))(message);
}
}
std::multimap<type_id_t, std::shared_ptr<std::any>> subscribers;
std::list<std::function<void()>> messages;
};
} // namespace event
#endif // ANTKEEPER_EVENT_QUEUE_HPP

+ 0
- 262
src/event/signal.hpp View File

@ -1,262 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_SIGNAL_HPP
#define ANTKEEPER_EVENT_SIGNAL_HPP
#include <algorithm>
#include <execution>
#include <functional>
#include <list>
#include <memory>
#include <type_traits>
//namespace event {
template <class T>
class signal;
template <class T>
class connector;
/**
* Manages a connection between a signal and handler. A signal will be disconnected from a handler when the connection is destructed or is disconnected manually via connection::disconnect().
*/
class connection
{
public:
/// Signal handler disconnect function type.
typedef std::function<void(std::weak_ptr<void>)> disconnector_type;
/**
* Constructs a connection between a signal and a handler.
*
* @param handler Weak pointer to a signal handler.
* @param disconnector Signal handler disconnect function.
*/
connection(std::weak_ptr<void> handler, disconnector_type disconnector);
/**
* Destructs a connection between a signal and a handler.
*/
~connection();
/**
* Returns `true` if the signal and handler are connected, `false` otherwise.
*/
bool connected() const noexcept;
/**
* Disconnects the signal from the handler.
*/
void disconnect();
private:
template <class T>
friend class signal;
std::weak_ptr<void> handler;
disconnector_type disconnector;
};
/**
* Creates connections between a signal and signal handlers.
*
* @tparam T Signal response type.
* @tparam Args Signal argument types.
*/
template <class T, class... Args>
class connector<T(Args...)>
{
public:
/// Signal response type.
typedef T response_type;
/// Signal type.
typedef signal<T(Args...)> signal_type;
/**
* Constructs a signal connector.
*
* @param signal Signal to which handlers may be connected.
*/
connector(signal_type& signal):
signal(&signal)
{}
/// @copydoc signal::connect(handler_type)
std::shared_ptr<connection> connect(typename signal_type::handler_type handler)
{
return signal->connect(handler);
}
private:
signal_type* signal;
};
/**
* Emits signals to signal handlers.
*
* @tparam T Signal response type.
* @tparam Args Signal argument types.
*/
template <class T, class... Args>
class signal<T(Args...)>
{
public:
/// Signal response type.
typedef T response_type;
/// Signal handler type.
typedef std::function<T(Args...)> handler_type;
/// Signal connector type.
typedef connector<T(Args...)> connector_type;
/**
* Constructs a signal.
*/
signal():
signal_connector(*this)
{}
/**
* Returns the connector for this signal.
*/
connector_type& connector() noexcept
{
return signal_connector;
}
/**
* Connects the signal to a handler.
*
* @param handler Signal handler to connect.
*
* @return Connection between the signal and handler.
*/
std::shared_ptr<connection> connect(handler_type handler)
{
// Allocate shared pointer to handler
std::shared_ptr<handler_type> shared_handler = std::make_shared<handler_type>(handler);
// Add handler to list of connected handlers
connections.push_back(shared_handler);
// Return a shared pointer to the connection between the signal and handler
return std::make_shared<connection>
(
std::static_pointer_cast<void>(shared_handler),
[this](std::weak_ptr<void> handler)
{
this->connections.remove(std::static_pointer_cast<handler_type>(handler.lock()));
}
);
}
/**
* Disconnects the signal from all connected handlers.
*/
void disconnect()
{
connections.clear();
}
/**
* Emits a signal to all connected handlers.
*
* @tparam ExecutionPolicy Execution policy type.
*
* @param policy Execution policy to use.
* @param args Signal arguments.
*/
/// @{
template <class ExecutionPolicy>
void emit(ExecutionPolicy&& policy, Args... args) const
{
std::for_each
(
policy,
std::begin(connections),
std::end(connections),
[&](const auto& handler)
{
(*handler)(args...);
}
);
}
void emit(Args... args) const
{
emit(std::execution::seq, args...);
}
/// @}
/**
* Emits a signal to all connected handlers and relays their responses to a listener.
*
* @tparam ExecutionPolicy Execution policy type.
* @tparam UnaryFunction Listener function object type.
*
* @param policy Execution policy to use.
* @param listener Listener function object.
* @param args Signal arguments.
*/
/// @{
template <class ExecutionPolicy, class UnaryFunction>
void ping(ExecutionPolicy&& policy, UnaryFunction listener, Args... args) const
{
std::for_each
(
policy,
std::begin(connections),
std::end(connections),
[&](const auto& handler)
{
if constexpr(std::is_void_v<T>)
{
(*handler)(args...);
listener();
}
else
{
listener((*handler)(args...));
}
}
);
}
template <class UnaryFunction>
void ping(UnaryFunction listener, Args... args) const
{
ping(std::execution::seq, listener, args...);
}
/// @}
private:
/// List of connected signal handlers.
std::list<std::shared_ptr<handler_type>> connections;
/// Signal connector.
connector_type signal_connector;
};
//} // namespace event
#endif // ANTKEEPER_EVENT_SIGNAL_HPP

src/event/window-events.hpp → src/event/subscriber.hpp View File

@ -17,22 +17,21 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_WINDOW_EVENTS_HPP
#define ANTKEEPER_WINDOW_EVENTS_HPP
#ifndef ANTKEEPER_EVENT_SUBSCRIBER_HPP
#define ANTKEEPER_EVENT_SUBSCRIBER_HPP
#include "event/event.hpp"
#include <functional>
namespace event {
/**
* Input event which indicates a mouse button has been pressed.
* Subscriber function object type.
*
* @tparam T Message type.
*/
class window_resized_event: public event<window_resized_event>
{
public:
virtual event_base* clone() const;
int w;
int h;
};
template <class T>
using subscriber = std::function<void(const T&)>;
#endif // ANTKEEPER_WINDOW_EVENTS_HPP
} // namespace event
#endif // ANTKEEPER_EVENT_SUBSCRIBER_HPP

+ 48
- 0
src/event/subscription.cpp View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "event/subscription.hpp"
#include <utility>
namespace event {
subscription::subscription(std::weak_ptr<void>&& subscriber, unsubscribe_type&& unsubscriber):
subscriber(std::move(subscriber)),
unsubscriber(std::move(unsubscriber))
{}
subscription::~subscription()
{
unsubscribe();
}
bool subscription::expired() const noexcept
{
return subscriber.expired();
}
void subscription::unsubscribe()
{
if (!expired())
{
unsubscriber();
}
}
} // namespace event

+ 67
- 0
src/event/subscription.hpp View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_EVENT_SUBSCRIPTION_HPP
#define ANTKEEPER_EVENT_SUBSCRIPTION_HPP
#include <functional>
#include <memory>
namespace event {
/**
* Subscription object which unsubscribes on destruction.
*/
class subscription
{
public:
/// Unsubscribe function object type.
typedef std::function<void()> unsubscribe_type;
/**
* Constructs a subscription.
*
* @param subscriber Weak pointer to the subscriber.
* @param unsubscriber Unsubscribe function object.
*/
subscription(std::weak_ptr<void>&& subscriber, unsubscribe_type&& unsubscriber);
/**
* Unsubscribes the subscriber and destructs the subscription.
*/
~subscription();
/**
* Returns `true` if the subscription is no longer active, `false` otherwise.
*/
[[nodiscard]] bool expired() const noexcept;
/**
* Unsubscribes the subscriber.
*/
void unsubscribe();
private:
std::weak_ptr<void> subscriber;
unsubscribe_type unsubscriber;
};
} // namespace event
#endif // ANTKEEPER_EVENT_SUBSCRIPTION_HPP

+ 0
- 1
src/game/ant/morphogenesis.cpp View File

@ -23,7 +23,6 @@
#include "math/transform-operators.hpp"
#include "math/quaternion.hpp"
#include <unordered_set>
#include <iostream>
namespace game {
namespace ant {

+ 43
- 37
src/game/context.hpp View File

@ -20,47 +20,45 @@
#ifndef ANTKEEPER_GAME_CONTEXT_HPP
#define ANTKEEPER_GAME_CONTEXT_HPP
#include "utility/fundamental-types.hpp"
#include "resources/string-table.hpp"
#include "animation/tween.hpp"
#include "application.hpp"
#include "debug/performance-sampler.hpp"
#include "entity/id.hpp"
#include "entity/registry.hpp"
#include "event/subscription.hpp"
#include "game/ecoregion.hpp"
#include "game/loop.hpp"
#include "game/state/base.hpp"
#include "geom/aabb.hpp"
#include "gl/framebuffer.hpp"
#include "gl/rasterizer.hpp"
#include "gl/texture-2d.hpp"
#include "gl/vertex-array.hpp"
#include "gl/vertex-buffer.hpp"
#include "gl/texture-2d.hpp"
#include "gl/rasterizer.hpp"
#include "gl/framebuffer.hpp"
#include "input/control-map.hpp"
#include "input/control.hpp"
#include "input/control-set.hpp"
#include "input/listener.hpp"
#include "input/mapper.hpp"
#include "input/event-router.hpp"
#include "animation/tween.hpp"
#include "render/anti-aliasing-method.hpp"
#include "render/material-property.hpp"
#include "render/material.hpp"
#include "resources/json.hpp"
#include "resources/string-table.hpp"
#include "scene/scene.hpp"
#include <optional>
#include "state-machine.hpp"
#include "type/bitmap-font.hpp"
#include "type/typeface.hpp"
#include "utility/fundamental-types.hpp"
#include <AL/al.h>
#include <AL/alc.h>
#include <entt/entt.hpp>
#include <fstream>
#include <filesystem>
#include <forward_list>
#include <memory>
#include <optional>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
#include <queue>
#include "resources/json.hpp"
#include "type/typeface.hpp"
#include "type/bitmap-font.hpp"
#include "render/material.hpp"
#include "render/material-property.hpp"
#include "render/anti-aliasing-method.hpp"
#include "ui/mouse-tracker.hpp"
#include "application.hpp"
#include "game/state/base.hpp"
#include "game/loop.hpp"
#include "game/ecoregion.hpp"
#include "state-machine.hpp"
#include "debug/performance-sampler.hpp"
#include <filesystem>
#include <AL/al.h>
#include <AL/alc.h>
// Forward declarations
class animator;
@ -75,7 +73,6 @@ template class animation;
namespace debug
{
class cli;
class logger;
}
namespace game
@ -129,8 +126,6 @@ struct context
std::function<void()> resume_callback;
/// Debugging
debug::logger* logger;
std::ofstream log_filestream;
debug::performance_sampler performance_sampler;
debug::cli* cli;
@ -144,10 +139,22 @@ struct context
application* app;
// Controls
input::event_router* input_event_router;
input::mapper* input_mapper;
input::listener* input_listener;
std::unordered_map<std::string, input::control*> controls;
input::mapper input_mapper;
std::forward_list<std::shared_ptr<::event::subscription>> control_subscriptions;
input::control_map window_controls;
input::control fullscreen_control;
input::control screenshot_control;
input::control_map menu_controls;
input::control menu_up_control;
input::control menu_down_control;
input::control menu_left_control;
input::control menu_right_control;
input::control menu_select_control;
input::control menu_back_control;
input::control menu_modifier_control;
bool mouse_look;
/// Game loop
@ -232,7 +239,6 @@ struct context
scene::billboard* camera_flash_billboard;
float font_size;
bool dyslexia_font;
ui::mouse_tracker* menu_mouse_tracker;
std::vector<std::function<void()>> menu_select_callbacks;
std::vector<std::function<void()>> menu_left_callbacks;
std::vector<std::function<void()>> menu_right_callbacks;
@ -307,7 +313,7 @@ struct context
bool bloom_enabled;
render::anti_aliasing_method anti_aliasing_method;
std::shared_ptr<connection> ui_resize_connection;
std::shared_ptr<::event::subscription> ui_resize_subscription;
};
} // namespace game

+ 54
- 46
src/game/controls.cpp View File

@ -30,7 +30,7 @@ namespace game {
std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad)
{
return std::filesystem::path("gamepad-" + gamepad.get_guid() + ".json");
return std::filesystem::path("gamepad-" + gamepad.get_uuid().string() + ".json");
}
json default_control_profile()
@ -81,6 +81,7 @@ json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad
bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration)
{
/*
// Determine absolute path to gamepad calibration file
std::filesystem::path path = ctx.controls_path / gamepad_calibration_path(ctx, gamepad);
@ -100,12 +101,14 @@ bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& ga
// Close calibration file
stream.close();
*/
return true;
}
void apply_control_profile(game::context& ctx, const json& profile)
{
/*
// Map gamepad buttons to strings
const std::unordered_map<std::string, input::gamepad_button> gamepad_button_map =
{
@ -144,21 +147,21 @@ void apply_control_profile(game::context& ctx, const json& profile)
}
// Get keyboard and mouse devices
input::keyboard* keyboard = ctx.app->get_keyboard();
input::mouse* mouse = ctx.app->get_mouse();
input::keyboard* keyboard = ctx.app->get_device_manager().get_keyboards().front();
input::mouse* mouse = ctx.app->get_device_manager().get_mice().front();
// Find profile gamepad device
input::gamepad* gamepad = nullptr;
auto gamepad_element = profile.find("gamepad");
if (gamepad_element != profile.end())
{
// Get gamepad GUID
const std::string gamepad_guid = gamepad_element->get<std::string>();
// Get gamepad UUID string
const std::string uuid_string = gamepad_element->get<std::string>();
// Find gamepad with matching GUID
for (input::gamepad* device: ctx.app->get_gamepads())
// Find gamepad with matching UUID
for (input::gamepad* device: ctx.app->get_device_manager().get_gamepads())
{
if (device->get_guid() == gamepad_guid)
if (device->get_uuid().string() == uuid_string)
{
gamepad = device;
break;
@ -193,7 +196,7 @@ void apply_control_profile(game::context& ctx, const json& profile)
{
if (!mapping_element->contains("device"))
{
ctx.logger->warning("Control \"" + control_name + "\" not mapped to a device");
debug::log::warning("Control \"" + control_name + "\" not mapped to a device");
continue;
}
@ -205,7 +208,7 @@ void apply_control_profile(game::context& ctx, const json& profile)
// Parse key name
if (!mapping_element->contains("key"))
{
ctx.logger->warning("Control \"" + control_name + "\" has invalid keyboard mapping");
debug::log::warning("Control \"" + control_name + "\" has invalid keyboard mapping");
continue;
}
std::string key = (*mapping_element)["key"].get<std::string>();
@ -214,14 +217,14 @@ void apply_control_profile(game::context& ctx, const json& profile)
input::scancode scancode = keyboard->get_scancode_from_name(key.c_str());
if (scancode == input::scancode::unknown)
{
ctx.logger->warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
debug::log::warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
continue;
}
// Map control to keyboard key
ctx.input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode));
ctx.logger->log("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
debug::log::info("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
}
else if (device == "mouse")
{
@ -233,58 +236,58 @@ void apply_control_profile(game::context& ctx, const json& profile)
// Map control to mouse button
ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button));
ctx.logger->log("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
debug::log::info("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
}
else if (mapping_element->contains("wheel"))
{
// Parse mouse wheel axis
std::string wheel = (*mapping_element)["wheel"].get<std::string>();
input::mouse_wheel_axis axis;
input::mouse_axis axis;
if (wheel == "x+")
axis = input::mouse_wheel_axis::positive_x;
axis = input::mouse_axis::positive_x;
else if (wheel == "x-")
axis = input::mouse_wheel_axis::negative_x;
axis = input::mouse_axis::negative_x;
else if (wheel == "y+")
axis = input::mouse_wheel_axis::positive_y;
axis = input::mouse_axis::positive_y;
else if (wheel == "y-")
axis = input::mouse_wheel_axis::negative_y;
axis = input::mouse_axis::negative_y;
else
{
ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
continue;
}
// Map control to mouse wheel axis
ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis));
ctx.logger->log("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
debug::log::info("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
}
else if (mapping_element->contains("motion"))
{
std::string motion = (*mapping_element)["motion"].get<std::string>();
input::mouse_motion_axis axis;
input::mouse_axis axis;
if (motion == "x+")
axis = input::mouse_motion_axis::positive_x;
axis = input::mouse_axis::positive_x;
else if (motion == "x-")
axis = input::mouse_motion_axis::negative_x;
axis = input::mouse_axis::negative_x;
else if (motion == "y+")
axis = input::mouse_motion_axis::positive_y;
axis = input::mouse_axis::positive_y;
else if (motion == "y-")
axis = input::mouse_motion_axis::negative_y;
axis = input::mouse_axis::negative_y;
else
{
ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
continue;
}
// Map control to mouse motion axis
ctx.input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis));
ctx.logger->log("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
debug::log::info("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
}
else
{
ctx.logger->warning("Control \"" + control_name + "\" has invalid mouse mapping");
debug::log::warning("Control \"" + control_name + "\" has invalid mouse mapping");
continue;
}
}
@ -298,14 +301,14 @@ void apply_control_profile(game::context& ctx, const json& profile)
auto button_it = gamepad_button_map.find(button);
if (button_it == gamepad_button_map.end())
{
ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
continue;
}
// Map control to gamepad button
ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second));
ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad button " + button);
debug::log::info("Mapped control \"" + control_name + "\" to gamepad button " + button);
}
else if (mapping_element->contains("axis"))
{
@ -316,7 +319,7 @@ void apply_control_profile(game::context& ctx, const json& profile)
auto axis_it = gamepad_axis_map.find(axis_name);
if (axis_it == gamepad_axis_map.end())
{
ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
continue;
}
@ -324,7 +327,7 @@ void apply_control_profile(game::context& ctx, const json& profile)
const char axis_sign = axis.back();
if (axis_sign != '-' && axis_sign != '+')
{
ctx.logger->warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
debug::log::warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
continue;
}
bool axis_negative = (axis_sign == '-');
@ -332,30 +335,32 @@ void apply_control_profile(game::context& ctx, const json& profile)
// Map control to gamepad axis
ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative));
ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
debug::log::info("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
}
else
{
ctx.logger->log("Control \"" + control_name + "\" has invalid gamepad mapping");
debug::log::info("Control \"" + control_name + "\" has invalid gamepad mapping");
continue;
}
}
else
{
ctx.logger->warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
debug::log::warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
}
}
}
}
*/
}
void save_control_profile(game::context& ctx)
{
/*
std::filesystem::path path;
if (ctx.config->contains("control_profile"))
path = ctx.config_path / "controls" / (*ctx.config)["control_profile"].get<std::string>();
ctx.logger->push_task("Saving control profile to \"" + path.string() + "\"");
debug::log::trace("Saving control profile to \"{}\"...", path.string());
try
{
json control_profile;
@ -402,16 +407,16 @@ void save_control_profile(game::context& ctx)
mapping_element["device"] = "mouse";
switch (wheel_mapping->axis)
{
case input::mouse_wheel_axis::negative_x:
case input::mouse_axis::negative_x:
mapping_element["wheel"] = "x-";
break;
case input::mouse_wheel_axis::positive_x:
case input::mouse_axis::positive_x:
mapping_element["wheel"] = "x+";
break;
case input::mouse_wheel_axis::negative_y:
case input::mouse_axis::negative_y:
mapping_element["wheel"] = "y-";
break;
case input::mouse_wheel_axis::positive_y:
case input::mouse_axis::positive_y:
mapping_element["wheel"] = "y+";
break;
default:
@ -427,16 +432,16 @@ void save_control_profile(game::context& ctx)
mapping_element["device"] = "mouse";
switch (motion_mapping->axis)
{
case input::mouse_motion_axis::negative_x:
case input::mouse_axis::negative_x:
mapping_element["motion"] = "x-";
break;
case input::mouse_motion_axis::positive_x:
case input::mouse_axis::positive_x:
mapping_element["motion"] = "x+";
break;
case input::mouse_motion_axis::negative_y:
case input::mouse_axis::negative_y:
mapping_element["motion"] = "y-";
break;
case input::mouse_motion_axis::positive_y:
case input::mouse_axis::positive_y:
mapping_element["motion"] = "y+";
break;
default:
@ -566,13 +571,15 @@ void save_control_profile(game::context& ctx)
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
*/
}
void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration)
{
/*
// Parse and apply activation thresholds
if (calibration.contains("leftx_activation"))
{
@ -661,6 +668,7 @@ void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration)
auto curve = parse_response_curve(calibration["righttrigger_response_curve"].get<std::string>());
gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve);
}
*/
}
} // namespace game

+ 2
- 2
src/game/fonts.cpp View File

@ -74,7 +74,7 @@ void load_fonts(game::context& ctx)
{
if (auto it = ctx.strings->find("font_dyslexia"); it != ctx.strings->end() && !it->second.empty() && it->second[0] != '#')
{
ctx.logger->log(it->second);
debug::log::info(it->second);
ctx.typefaces["dyslexia"] = ctx.resource_manager->load<type::typeface>(it->second);
dyslexia_font_loaded = true;
}
@ -145,7 +145,7 @@ void load_fonts(game::context& ctx)
const float menu_font_size_px = (menu_font_size_pt * dpi) / 72.0f;
const float title_font_size_px = (title_font_size_pt * dpi) / 72.0f;
ctx.logger->log("font size: " + std::to_string(menu_font_size_px));
debug::log::info("font size: " + std::to_string(menu_font_size_px));
// Build debug font
if (auto it = ctx.typefaces.find("monospace"); it != ctx.typefaces.end())

+ 30
- 22
src/game/graphics.cpp View File

@ -18,20 +18,22 @@
*/
#include "game/graphics.hpp"
#include "render/passes/bloom-pass.hpp"
#include "render/passes/fxaa-pass.hpp"
#include "render/passes/final-pass.hpp"
#include "render/passes/resample-pass.hpp"
#include "config.hpp"
#include "debug/log.hpp"
#include "gl/framebuffer.hpp"
#include "gl/texture-2d.hpp"
#include "gl/texture-wrapping.hpp"
#include "gl/texture-filter.hpp"
#include "debug/logger.hpp"
#include "utility/timestamp.hpp"
#include "gl/texture-wrapping.hpp"
#include "render/passes/bloom-pass.hpp"
#include "render/passes/final-pass.hpp"
#include "render/passes/fxaa-pass.hpp"
#include "render/passes/resample-pass.hpp"
#include <chrono>
#include <filesystem>
#include <format>
#include <glad/glad.h>
#include <stb/stb_image_write.h>
#include <thread>
#include <filesystem>
namespace game {
namespace graphics {
@ -40,7 +42,7 @@ static void reroute_framebuffers(game::context& ctx);
void create_framebuffers(game::context& ctx)
{
ctx.logger->push_task("Creating framebuffers");
debug::log::trace("Creating framebuffers...");
// Load render resolution scale from config
ctx.render_scale = 1.0f;
@ -93,12 +95,12 @@ void create_framebuffers(game::context& ctx)
ctx.shadow_map_framebuffer = new gl::framebuffer(shadow_map_resolution, shadow_map_resolution);
ctx.shadow_map_framebuffer->attach(gl::framebuffer_attachment_type::depth, ctx.shadow_map_depth_texture);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Created framebuffers");
}
void destroy_framebuffers(game::context& ctx)
{
ctx.logger->push_task("Destroying framebuffers");
debug::log::trace("Destroying framebuffers...");
// Delete HDR framebuffer and its attachments
delete ctx.hdr_framebuffer;
@ -125,12 +127,12 @@ void destroy_framebuffers(game::context& ctx)
delete ctx.shadow_map_depth_texture;
ctx.shadow_map_depth_texture = nullptr;
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Destroyed framebuffers");
}
void change_render_resolution(game::context& ctx, float scale)
{
ctx.logger->push_task("Changing render resolution");
debug::log::trace("Changing render resolution to {}...", scale);
// Update render resolution scale
ctx.render_scale = scale;
@ -164,20 +166,24 @@ void change_render_resolution(game::context& ctx, float scale)
}
reroute_framebuffers(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Changed render resolution to {}", scale);
}
void save_screenshot(game::context& ctx)
{
// Determine screenshot path
std::string filename = "antkeeper-" + timestamp() + ".png";
std::filesystem::path path = ctx.config_path / "gallery" / filename;
// Determine timestamped screenshot filename
const auto time = std::chrono::floor<std::chrono::milliseconds>(std::chrono::system_clock::now());
const std::string screenshot_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.png", config::application_name, time);
ctx.logger->push_task("Saving screenshot to \"" + path.string() + "\"");
// Determine path to screenshot file
std::filesystem::path screenshot_filepath = ctx.config_path / "gallery" / screenshot_filename;
std::string screenshot_filepath_string = screenshot_filepath.string();
debug::log::debug("Saving screenshot to \"{}\"...", screenshot_filepath_string);
// Get viewport dimensions
const int2& viewport_dimensions = ctx.app->get_viewport_dimensions();
// Allocate image
// Allocate screenshot image
std::shared_ptr<image> frame = std::make_shared<image>();
frame->format(1, 3);
frame->resize(viewport_dimensions.x(), viewport_dimensions.y());
@ -189,14 +195,16 @@ void save_screenshot(game::context& ctx)
// Write screenshot file in separate thread
std::thread
(
[frame, path]
[frame, path = std::move(screenshot_filepath_string)]
{
stbi_flip_vertically_on_write(1);
stbi_write_png(path.string().c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->data(), frame->get_width() * frame->get_channel_count());
stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->data(), frame->get_width() * frame->get_channel_count());
debug::log::debug("Saved screenshot to \"{}\"", path);
}
).detach();
ctx.logger->pop_task(EXIT_SUCCESS);
}
void toggle_bloom(game::context& ctx, bool enabled)

+ 6
- 22
src/game/load.cpp View File

@ -18,44 +18,28 @@
*/
#include "game/load.hpp"
#include "game/world.hpp"
#include "application.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "resources/json.hpp"
#include "resources/resource-manager.hpp"
#include "render/model.hpp"
#include "render/material.hpp"
#include "render/passes/sky-pass.hpp"
#include "render/passes/ground-pass.hpp"
#include "game/system/astronomy.hpp"
#include "game/system/terrain.hpp"
#include "math/noise/noise.hpp"
#include "math/hash/hash.hpp"
#include <fstream>
#include <iostream>
#include <stb/stb_image_write.h>
#include "resources/image.hpp"
#include <algorithm>
#include <execution>
namespace game {
namespace load {
void colony(game::context& ctx, const std::filesystem::path& path)
{
ctx.logger->push_task("Loading colony from \"" + path.string() + "\"");
const std::string path_string = path.string();
debug::log::trace("Loading colony from \"{}\"...", path_string);
try
{
json* data = ctx.resource_manager->load<json>(path);
debug::log::trace("Loaded colony from \"{}\"", path_string);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to load colony from \"{}\"", path_string);
}
ctx.logger->pop_task(EXIT_SUCCESS);
}
} // namespace load

+ 5
- 1
src/game/menu.cpp View File

@ -295,7 +295,8 @@ void fade_out_bg(game::context& ctx)
}
void setup_controls(game::context& ctx)
{
{
/*
ctx.controls["menu_up"]->set_activated_callback
(
[&ctx]()
@ -459,10 +460,12 @@ void setup_controls(game::context& ctx)
}
}
);
*/
}
void clear_controls(game::context& ctx)
{
/*
ctx.controls["menu_up"]->set_activated_callback(nullptr);
ctx.controls["menu_down"]->set_activated_callback(nullptr);
ctx.controls["menu_left"]->set_activated_callback(nullptr);
@ -472,6 +475,7 @@ void clear_controls(game::context& ctx)
ctx.menu_mouse_tracker->set_mouse_moved_callback(nullptr);
ctx.menu_mouse_tracker->set_mouse_button_pressed_callback(nullptr);
*/
}
} // namespace menu

+ 13
- 7
src/game/save.cpp View File

@ -19,7 +19,7 @@
#include "game/save.hpp"
#include "application.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "resources/json.hpp"
#include <fstream>
@ -29,7 +29,9 @@ namespace save {
void colony(game::context& ctx)
{
std::filesystem::path path = ctx.saves_path / "colony.sav";
ctx.logger->push_task("Saving colony to \"" + path.string() + "\"");
const std::string path_string = path.string();
debug::log::trace("Saving colony to \"{}\"...", path_string);
try
{
// Construct JSON data describing the colony
@ -67,28 +69,32 @@ void colony(game::context& ctx)
std::ofstream file(path);
file << data;
debug::log::trace("Saved colony to \"{}\"", path_string);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to save colony to \"{}\"", path_string);
}
ctx.logger->pop_task(EXIT_SUCCESS);
}
void config(game::context& ctx)
{
std::filesystem::path path = ctx.config_path / "config.json";
ctx.logger->push_task("Saving config to \"" + path.string() + "\"");
const std::string path_string = path.string();
debug::log::trace("Saving config to \"{}\"...", path_string);
try
{
std::ofstream file(path);
file << *(ctx.config);
debug::log::trace("Saved config to \"{}\"", path_string);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to save config to \"{}\"", path_string);
}
ctx.logger->pop_task(EXIT_SUCCESS);
}
} // namespace save

+ 184
- 232
src/game/state/boot.cpp View File

@ -17,17 +17,41 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game/state/boot.hpp"
#include "animation/animation.hpp"
#include "animation/animator.hpp"
#include "animation/ease.hpp"
#include "animation/screen-transition.hpp"
#include "animation/timeline.hpp"
#include "application.hpp"
#include "color/color.hpp"
#include "config.hpp"
#include "debug/cli.hpp"
#include "debug/console-commands.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "entity/commands.hpp"
#include "game/context.hpp"
#include "game/controls.hpp"
#include "game/fonts.hpp"
#include "game/graphics.hpp"
#include "game/menu.hpp"
#include "game/save.hpp"
#include "game/state/boot.hpp"
#include "game/state/splash.hpp"
#include "game/system/astronomy.hpp"
#include "game/system/atmosphere.hpp"
#include "game/system/behavior.hpp"
#include "game/system/blackbody.hpp"
#include "game/system/camera.hpp"
#include "game/system/collision.hpp"
#include "game/system/constraint.hpp"
#include "game/system/locomotion.hpp"
#include "game/system/orbit.hpp"
#include "game/system/render.hpp"
#include "game/system/spatial.hpp"
#include "game/system/spring.hpp"
#include "game/system/steering.hpp"
#include "game/system/subterrain.hpp"
#include "game/system/terrain.hpp"
#include "game/system/vegetation.hpp"
#include "gl/framebuffer.hpp"
#include "gl/pixel-format.hpp"
#include "gl/pixel-type.hpp"
@ -38,67 +62,38 @@
#include "gl/vertex-array.hpp"
#include "gl/vertex-attribute.hpp"
#include "gl/vertex-buffer.hpp"
#include "input/gamepad.hpp"
#include "input/keyboard.hpp"
#include "input/mapper.hpp"
#include "input/mouse.hpp"
#include "input/scancode.hpp"
#include "render/compositor.hpp"
#include "render/material-flags.hpp"
#include "render/material-property.hpp"
#include "render/passes/bloom-pass.hpp"
#include "render/passes/clear-pass.hpp"
#include "render/passes/final-pass.hpp"
#include "render/passes/fxaa-pass.hpp"
#include "render/passes/resample-pass.hpp"
#include "render/passes/ground-pass.hpp"
#include "render/passes/material-pass.hpp"
#include "render/passes/outline-pass.hpp"
#include "render/passes/resample-pass.hpp"
#include "render/passes/shadow-map-pass.hpp"
#include "render/passes/sky-pass.hpp"
#include "render/passes/ground-pass.hpp"
#include "render/vertex-attribute.hpp"
#include "render/compositor.hpp"
#include "render/renderer.hpp"
#include "resources/resource-manager.hpp"
#include "render/vertex-attribute.hpp"
#include "resources/file-buffer.hpp"
#include "resources/resource-manager.hpp"
#include "scene/scene.hpp"
#include "game/state/splash.hpp"
#include "game/system/behavior.hpp"
#include "game/system/camera.hpp"
#include "game/system/collision.hpp"
#include "game/system/constraint.hpp"
#include "game/system/locomotion.hpp"
#include "game/system/render.hpp"
#include "game/system/subterrain.hpp"
#include "game/system/terrain.hpp"
#include "game/system/vegetation.hpp"
#include "game/system/spatial.hpp"
#include "game/system/astronomy.hpp"
#include "game/system/blackbody.hpp"
#include "game/system/atmosphere.hpp"
#include "game/system/orbit.hpp"
#include "game/system/steering.hpp"
#include "game/system/spring.hpp"
#include "entity/commands.hpp"
#include "utility/paths.hpp"
#include "event/event-dispatcher.hpp"
#include "input/event-router.hpp"
#include "input/mapper.hpp"
#include "input/listener.hpp"
#include "input/gamepad.hpp"
#include "input/mouse.hpp"
#include "input/keyboard.hpp"
#include "config.hpp"
#include "input/scancode.hpp"
#include "game/fonts.hpp"
#include "game/controls.hpp"
#include "game/save.hpp"
#include "game/menu.hpp"
#include "game/graphics.hpp"
#include "utility/timestamp.hpp"
#include "color/color.hpp"
#include <algorithm>
#include <cxxopts.hpp>
#include <entt/entt.hpp>
#include <execution>
#include <filesystem>
#include <functional>
#include <string>
#include <vector>
#include <execution>
#include <algorithm>
namespace game {
namespace state {
@ -106,15 +101,12 @@ namespace state {
boot::boot(game::context& ctx, int argc, char** argv):
game::state::base(ctx)
{
// Allocate application logger
ctx.logger = new debug::logger();
// Boot process
ctx.logger->push_task("Booting up");
debug::log::trace("Booting up...");
try
{
// Allocate application
ctx.app = new application(*ctx.logger);
ctx.app = new application();
// Parse command line options
parse_options(argc, argv);
@ -137,22 +129,23 @@ boot::boot(game::context& ctx, int argc, char** argv):
}
catch (const std::exception& e)
{
ctx.logger->error("Caught exception: \"" + std::string(e.what()) + "\"");
ctx.logger->pop_task(EXIT_FAILURE);
return;
debug::log::fatal("Boot up failed: unhandled exception: {}", e.what());
throw e;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Boot up complete");
// Push splash state
ctx.state_machine.emplace(new game::state::splash(ctx));
// Enter main loop
debug::log::trace("Entered main loop");
loop();
}
boot::~boot()
{
ctx.logger->push_task("Booting down");
debug::log::trace("Booting down...");
try
{
@ -164,18 +157,16 @@ boot::~boot()
}
catch (const std::exception& e)
{
ctx.logger->error("Caught exception: \"" + std::string(e.what()) + "\"");
ctx.logger->pop_task(EXIT_FAILURE);
return;
debug::log::fatal("Boot down failed: unhandled exception: {}", e.what());
throw e;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Boot down complete");
}
void boot::parse_options(int argc, char** argv)
{
debug::logger* logger = ctx.logger;
logger->push_task("Parsing command line options");
debug::log::trace("Parsing {} command line arguments...", argc);
try
{
@ -221,44 +212,32 @@ void boot::parse_options(int argc, char** argv)
// --windowed
if (result.count("windowed"))
option_windowed = true;
option_windowed = true;
debug::log::trace("Parsed {} command line arguments", argc);
}
catch (const std::exception& e)
{
logger->error("Exception caught: \"" + std::string(e.what()) + "\"");
logger->pop_task(EXIT_FAILURE);
return;
debug::log::warning("Exception caught while parsing command line arguments: {}", e.what());
}
logger->pop_task(EXIT_SUCCESS);
}
void boot::setup_resources()
{
debug::logger* logger = ctx.logger;
// Setup resource manager
ctx.resource_manager = new resource_manager(logger);
// Determine application name
std::string application_name;
#if defined(_WIN32) || defined(__APPLE__)
application_name = "Antkeeper";
#else
application_name = "antkeeper";
#endif
ctx.resource_manager = new resource_manager();
// Detect paths
ctx.data_path = get_data_path(application_name);
ctx.config_path = get_config_path(application_name);
ctx.data_path = get_data_path(config::application_name);
ctx.config_path = get_config_path(config::application_name);
ctx.mods_path = ctx.config_path / "mods";
ctx.saves_path = ctx.config_path / "saves";
ctx.screenshots_path = ctx.config_path / "gallery";
ctx.controls_path = ctx.config_path / "controls";
// Log resource paths
logger->log("Detected data path as \"" + ctx.data_path.string());
logger->log("Detected config path as \"" + ctx.config_path.string());
debug::log::info("Data path: n><span class="se">\"{}\"", ctx.data_path.string());
debug::log::info("Config path: \"{}\"", ctx.config_path.string());
// Create nonexistent config directories
std::vector<std::filesystem::path> config_paths;
@ -271,26 +250,20 @@ void boot::setup_resources()
{
if (!std::filesystem::exists(path))
{
logger->push_task("Creating directory \"" + path.string());
const std::string path_string = path.string();
debug::log::trace("Creating directory \"{}\"...", path_string);
if (std::filesystem::create_directories(path))
{
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Created directory \"{}\"", path_string);
}
else
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to create directory \"{}\"", path_string);
}
}
}
// Redirect logger output to log file on non-debug builds
#if defined(NDEBUG)
std::filesystem::path log_path = ctx.config_path / "log.txt";
ctx.log_filestream.open(log_path);
ctx.log_filestream << logger->get_history();
logger->redirect(&ctx.log_filestream);
#endif
// Scan for mods
std::vector<std::filesystem::path> mod_paths;
for (const auto& directory_entry: std::filesystem::directory_iterator(ctx.mods_path))
@ -328,24 +301,23 @@ void boot::setup_resources()
void boot::load_config()
{
debug::logger* logger = ctx.logger;
logger->push_task("Loading config");
debug::log::trace("Loading config...");
// Load config file
ctx.config = ctx.resource_manager->load<json>("config.json");
if (!ctx.config)
if (ctx.config)
{
logger->pop_task(EXIT_FAILURE);
return;
debug::log::trace("Loaded config");
}
else
{
debug::log::error("Failed to load config");
}
logger->pop_task(EXIT_SUCCESS);
}
void boot::load_strings()
{
debug::logger* logger = ctx.logger;
logger->push_task("Loading strings");
debug::log::trace("Loading strings...");
ctx.string_table = ctx.resource_manager->load<string_table>("strings.csv");
@ -360,19 +332,18 @@ void boot::load_strings()
}
ctx.language_count = (*ctx.string_table)[0].size() - 2;
logger->log("language count: " + std::to_string(ctx.language_count));
logger->log("language index: " + std::to_string(ctx.language_index));
logger->log("language code: " + ctx.language_code);
debug::log::info("Languages available: {}", ctx.language_count);
debug::log::info("Language index: {}", ctx.language_index);
debug::log::info("Language code: {}", ctx.language_code);
ctx.strings = &ctx.string_table_map[ctx.language_code];
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Loaded strings");
}
void boot::setup_window()
{
debug::logger* logger = ctx.logger;
logger->push_task("Setting up window");
debug::log::trace("Setting up window...");
application* app = ctx.app;
json* config = ctx.config;
@ -425,13 +396,12 @@ void boot::setup_window()
app->show_window();
ctx.app->swap_buffers();
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Set up window");
}
void boot::setup_rendering()
{
debug::logger* logger = ctx.logger;
logger->push_task("Setting up rendering");
debug::log::trace("Setting up rendering...");
// Get rasterizer from application
ctx.rasterizer = ctx.app->get_rasterizer();
@ -523,7 +493,6 @@ void boot::setup_rendering()
ctx.underground_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.underground_material_pass->set_fallback_material(ctx.fallback_material);
ctx.app->get_event_dispatcher()->subscribe<mouse_moved_event>(ctx.underground_material_pass);
ctx.underground_compositor = new render::compositor();
ctx.underground_compositor->add_pass(ctx.underground_clear_pass);
@ -549,14 +518,12 @@ void boot::setup_rendering()
ctx.sky_pass = new render::sky_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.sky_pass->set_enabled(false);
ctx.sky_pass->set_magnification(3.0f);
ctx.app->get_event_dispatcher()->subscribe<mouse_moved_event>(ctx.sky_pass);
ctx.ground_pass = new render::ground_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.ground_pass->set_enabled(false);
ctx.surface_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_material_pass->set_fallback_material(ctx.fallback_material);
ctx.app->get_event_dispatcher()->subscribe<mouse_moved_event>(ctx.surface_material_pass);
ctx.surface_outline_pass = new render::outline_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
ctx.surface_outline_pass->set_outline_width(0.25f);
@ -634,13 +601,12 @@ void boot::setup_rendering()
ctx.renderer = new render::renderer();
ctx.renderer->set_billboard_vao(ctx.billboard_vao);
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Set up rendering");
}
void boot::setup_audio()
{
debug::logger* logger = ctx.logger;
logger->push_task("Setting up audio");
debug::log::trace("Setting up audio...");
// Load master volume config
ctx.master_volume = 1.0f;
@ -673,11 +639,11 @@ void boot::setup_audio()
ctx.captions_size = (*ctx.config)["captions_size"].get<float>();
// Open audio device
logger->push_task("Opening audio device");
debug::log::trace("Opening audio device...");
ctx.alc_device = alcOpenDevice(nullptr);
if (!ctx.alc_device)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to open audio device: AL error code {}", alGetError());
return;
}
else
@ -689,30 +655,29 @@ void boot::setup_audio()
if (alcGetError(ctx.alc_device) != AL_NO_ERROR || !alc_device_name)
alc_device_name = alcGetString(ctx.alc_device, ALC_DEVICE_SPECIFIER);
logger->log("Opened audio device \"" + std::string(alc_device_name) + "\"");
logger->pop_task(EXIT_SUCCESS);
// Log audio device name
debug::log::info("Opened audio device \"{}\"", alc_device_name);
}
// Create audio context
logger->push_task("Creating audio context");
debug::log::trace("Creating audio context...");
ctx.alc_context = alcCreateContext(ctx.alc_device, nullptr);
if (!ctx.alc_context)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to create audio context: ALC error code {}", alcGetError(ctx.alc_device));
alcCloseDevice(ctx.alc_device);
return;
}
else
{
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Created audio context");
}
// Make audio context current
logger->push_task("Making audio context current");
debug::log::trace("Making audio context current...");
if (alcMakeContextCurrent(ctx.alc_context) == ALC_FALSE)
{
logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to make audio context current: ALC error code {}", alcGetError(ctx.alc_device));
if (ctx.alc_context != nullptr)
{
alcDestroyContext(ctx.alc_context);
@ -722,16 +687,15 @@ void boot::setup_audio()
}
else
{
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Made audio context current");
}
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Set up audio");
}
void boot::setup_scenes()
{
debug::logger* logger = ctx.logger;
logger->push_task("Setting up scenes");
debug::log::trace("Setting up scenes...");
// Get default framebuffer
const auto& viewport_dimensions = ctx.rasterizer->get_default_framebuffer().get_dimensions();
@ -768,7 +732,6 @@ void boot::setup_scenes()
// Setup UI scene
{
ctx.ui_scene = new scene::collection();
// Menu BG billboard
render::material* menu_bg_material = new render::material();
@ -785,7 +748,6 @@ void boot::setup_scenes()
ctx.menu_bg_billboard->update_tweens();
// Create camera flash billboard
render::material* flash_material = new render::material();
flash_material->set_shader_program(ctx.resource_manager->load<gl::shader_program>("ui-element-untextured.glsl"));
auto flash_tint = flash_material->add_property<float4>("tint");
@ -833,7 +795,7 @@ void boot::setup_scenes()
// Clear active scene
ctx.active_scene = nullptr;
logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Set up scenes");
}
void boot::setup_animation()
@ -935,8 +897,6 @@ void boot::setup_entities()
void boot::setup_systems()
{
event_dispatcher* event_dispatcher = ctx.app->get_event_dispatcher();
const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
float4 viewport = {0.0f, 0.0f, static_cast<float>(viewport_dimensions[0]), static_cast<float>(viewport_dimensions[1])};
@ -957,7 +917,6 @@ void boot::setup_systems()
// Setup camera system
ctx.camera_system = new game::system::camera(*ctx.entity_registry);
ctx.camera_system->set_viewport(viewport);
event_dispatcher->subscribe<window_resized_event>(ctx.camera_system);
// Setup subterrain system
ctx.subterrain_system = new game::system::subterrain(*ctx.entity_registry, ctx.resource_manager);
@ -1014,36 +973,23 @@ void boot::setup_systems()
void boot::setup_controls()
{
event_dispatcher* event_dispatcher = ctx.app->get_event_dispatcher();
// Setup input event routing
ctx.input_event_router = new input::event_router();
ctx.input_event_router->set_event_dispatcher(event_dispatcher);
// Setup input mapper
ctx.input_mapper = new input::mapper();
ctx.input_mapper->set_event_dispatcher(event_dispatcher);
// Setup input listener
ctx.input_listener = new input::listener();
ctx.input_listener->set_event_dispatcher(event_dispatcher);
// Load SDL game controller mappings database
ctx.logger->push_task("Loading SDL game controller mappings from database");
debug::log::trace("Loading SDL game controller mappings...");
file_buffer* game_controller_db = ctx.resource_manager->load<file_buffer>("gamecontrollerdb.txt");
if (!game_controller_db)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to load SDL game controller mappings");
}
else
{
ctx.app->add_game_controller_mappings(game_controller_db->data(), game_controller_db->size());
debug::log::trace("Loaded SDL game controller mappings");
ctx.resource_manager->unload("gamecontrollerdb.txt");
ctx.logger->pop_task(EXIT_SUCCESS);
}
// Load controls
ctx.logger->push_task("Loading controls");
debug::log::trace("Loading controls...");
try
{
// If a control profile is set in the config file
@ -1060,69 +1006,92 @@ void boot::setup_controls()
}
// Calibrate gamepads
for (input::gamepad* gamepad: ctx.app->get_gamepads())
{
ctx.logger->push_task("Loading calibration for gamepad " + gamepad->get_guid());
json* calibration = game::load_gamepad_calibration(ctx, *gamepad);
if (!calibration)
{
ctx.logger->pop_task(EXIT_FAILURE);
// for (input::gamepad* gamepad: ctx.app->get_gamepads())
// {
// const std::string uuid_string = gamepad->get_uuid().to_string();
// debug::log::push_task("Loading calibration for gamepad " + uuid_string);
// json* calibration = game::load_gamepad_calibration(ctx, *gamepad);
// if (!calibration)
// {
// debug::log::pop_task(EXIT_FAILURE);
ctx.logger->push_task("Generating default calibration for gamepad " + gamepad->get_guid());
json default_calibration = game::default_gamepad_calibration();
apply_gamepad_calibration(*gamepad, default_calibration);
// debug::log::push_task("Generating default calibration for gamepad " + uuid_string);
// json default_calibration = game::default_gamepad_calibration();
// apply_gamepad_calibration(*gamepad, default_calibration);
if (!save_gamepad_calibration(ctx, *gamepad, default_calibration))
ctx.logger->pop_task(EXIT_FAILURE);
else
ctx.logger->pop_task(EXIT_SUCCESS);
}
else
{
ctx.logger->pop_task(EXIT_SUCCESS);
apply_gamepad_calibration(*gamepad, *calibration);
}
}
// if (!save_gamepad_calibration(ctx, *gamepad, default_calibration))
// debug::log::pop_task(EXIT_FAILURE);
// else
// debug::log::pop_task(EXIT_SUCCESS);
// }
// else
// {
// debug::log::pop_task(EXIT_SUCCESS);
// apply_gamepad_calibration(*gamepad, *calibration);
// }
// }
// Toggle fullscreen
ctx.controls["toggle_fullscreen"]->set_activated_callback
// Setup fullscreen control
ctx.control_subscriptions.emplace_front
(
[&ctx = this->ctx]()
{
bool fullscreen = !ctx.app->is_fullscreen();
ctx.app->set_fullscreen(fullscreen);
if (!fullscreen)
ctx.fullscreen_control.get_activated_channel().subscribe
(
[&ctx = this->ctx](const auto& event)
{
int2 resolution;
resolution.x() = (*ctx.config)["windowed_resolution"][0].get<int>();
resolution.y() = (*ctx.config)["windowed_resolution"][1].get<int>();
bool fullscreen = !ctx.app->is_fullscreen();
ctx.app->set_fullscreen(fullscreen);
ctx.app->resize_window(resolution.x(), resolution.y());
if (!fullscreen)
{
int2 resolution;
resolution.x() = (*ctx.config)["windowed_resolution"][0].get<int>();
resolution.y() = (*ctx.config)["windowed_resolution"][1].get<int>();
ctx.app->resize_window(resolution.x(), resolution.y());
}
// Save display mode config
(*ctx.config)["fullscreen"] = fullscreen;
game::save::config(ctx);
}
// Save display mode config
(*ctx.config)["fullscreen"] = fullscreen;
game::save::config(ctx);
}
)
);
// Setup screenshot control
ctx.control_subscriptions.emplace_front
(
ctx.screenshot_control.get_activated_channel().subscribe
(
[&ctx = this->ctx](const auto& event)
{
game::graphics::save_screenshot(ctx);
}
)
);
// Screenshot
ctx.controls["screenshot"]->set_activated_callback(std::bind(game::graphics::save_screenshot, std::ref(ctx)));
// Map and enable window controls
ctx.window_controls.add_mapping(ctx.fullscreen_control, input::key_mapping(nullptr, input::scancode::f11, false));
ctx.window_controls.add_mapping(ctx.screenshot_control, input::key_mapping(nullptr, input::scancode::f12, false));
ctx.window_controls.connect(ctx.app->get_device_manager().get_event_queue());
// Set activation threshold for menu navigation controls to mitigate drifting gamepad axes
const float menu_activation_threshold = 0.1f;
ctx.controls["menu_up"]->set_activation_threshold(menu_activation_threshold);
ctx.controls["menu_down"]->set_activation_threshold(menu_activation_threshold);
ctx.controls["menu_left"]->set_activation_threshold(menu_activation_threshold);
ctx.controls["menu_right"]->set_activation_threshold(menu_activation_threshold);
auto menu_control_threshold = [](float x) -> bool
{
return x > 0.1f;
};
ctx.menu_up_control.set_threshold_function(menu_control_threshold);
ctx.menu_down_control.set_threshold_function(menu_control_threshold);
ctx.menu_left_control.set_threshold_function(menu_control_threshold);
ctx.menu_right_control.set_threshold_function(menu_control_threshold);
debug::log::trace("Loaded controls");
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to load controls");
}
ctx.logger->pop_task(EXIT_SUCCESS);
}
void boot::setup_ui()
@ -1138,39 +1107,32 @@ void boot::setup_ui()
ctx.dyslexia_font = (*ctx.config)["dyslexia_font"].get<bool>();
// Load fonts
ctx.logger->push_task("Loading fonts");
debug::log::trace("Loading fonts...");
try
{
game::load_fonts(ctx);
debug::log::trace("Loaded fonts");
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::error("Failed to load fonts");
}
ctx.logger->pop_task(EXIT_SUCCESS);
// Setup UI resize handler
ctx.ui_resize_connection = ctx.app->get_window_size_signal().connect
ctx.ui_resize_subscription = ctx.app->get_window_resized_channel().subscribe
(
[&](int w, int h)
[&](const auto& event)
{
const float clip_left = static_cast<float>(w) * -0.5f;
const float clip_right = static_cast<float>(w) * 0.5f;
const float clip_top = static_cast<float>(h) * -0.5f;
const float clip_bottom = static_cast<float>(h) * 0.5f;
const float clip_left = static_cast<float>(event.viewport_width) * -0.5f;
const float clip_right = static_cast<float>(event.viewport_width) * 0.5f;
const float clip_top = static_cast<float>(event.viewport_height) * -0.5f;
const float clip_bottom = static_cast<float>(event.viewport_height) * 0.5f;
const float clip_near = ctx.ui_camera->get_clip_near();
const float clip_far = ctx.ui_camera->get_clip_far();
ctx.ui_camera->set_orthographic(clip_left, clip_right, clip_top, clip_bottom, clip_near, clip_far);
}
);
// Construct mouse tracker
ctx.menu_mouse_tracker = new ui::mouse_tracker();
ctx.app->get_event_dispatcher()->subscribe<mouse_moved_event>(ctx.menu_mouse_tracker);
ctx.app->get_event_dispatcher()->subscribe<mouse_button_pressed_event>(ctx.menu_mouse_tracker);
ctx.app->get_event_dispatcher()->subscribe<mouse_button_released_event>(ctx.menu_mouse_tracker);
ctx.app->get_event_dispatcher()->subscribe<mouse_wheel_scrolled_event>(ctx.menu_mouse_tracker);
}
void boot::setup_debugging()
@ -1179,13 +1141,7 @@ void boot::setup_debugging()
ctx.performance_sampler.set_sample_size(15);
ctx.cli = new debug::cli();
ctx.cli->register_command("echo", debug::cc::echo);
ctx.cli->register_command("exit", std::function<std::string()>(std::bind(&debug::cc::exit, &ctx)));
ctx.cli->register_command("scrot", std::function<std::string()>(std::bind(&debug::cc::scrot, &ctx)));
ctx.cli->register_command("cue", std::function<std::string(float, std::string)>(std::bind(&debug::cc::cue, &ctx, std::placeholders::_1, std::placeholders::_2)));
//std::string cmd = "cue 20 exit";
//logger->log(cmd);
//logger->log(cli.interpret(cmd));
//debug::log::info(ctx.cli->interpret("echo hi 123"));
}
void boot::setup_loop()
@ -1209,11 +1165,7 @@ void boot::setup_loop()
// Process events
ctx.app->process_events();
ctx.app->get_event_dispatcher()->update(t);
// Update controls
for (const auto& control: ctx.controls)
control.second->update();
ctx.app->get_device_manager().get_event_queue().flush();
// Process function queue
while (!ctx.function_queue.empty())
@ -1287,7 +1239,7 @@ void boot::loop()
void boot::shutdown_audio()
{
ctx.logger->push_task("Shutting down audio");
debug::log::trace("Shutting down audio...");
if (ctx.alc_context)
{
@ -1300,7 +1252,7 @@ void boot::shutdown_audio()
alcCloseDevice(ctx.alc_device);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::trace("Shut down audio");
}
} // namespace state

+ 5
- 5
src/game/state/controls-menu.cpp View File

@ -23,7 +23,7 @@
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"
namespace game {
@ -32,7 +32,7 @@ namespace state {
controls_menu::controls_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering controls menu state");
debug::log::push_task("Entering controls menu state");
// Construct menu item texts
scene::text* keyboard_text = new scene::text();
@ -151,12 +151,12 @@ controls_menu::controls_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
controls_menu::~controls_menu()
{
ctx.logger->push_task("Exiting options menu state");
debug::log::push_task("Exiting options menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -165,7 +165,7 @@ controls_menu::~controls_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 14
- 14
src/game/state/credits.cpp View File

@ -25,7 +25,7 @@
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
namespace game {
namespace state {
@ -33,7 +33,7 @@ namespace state {
credits::credits(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering credits state");
debug::log::push_task("Entering credits state");
// Construct credits text
credits_text.set_material(&ctx.menu_font_material);
@ -75,17 +75,18 @@ credits::credits(game::context& ctx):
credits_fade_in_animation.play();
// Set up credits skipper
ctx.input_listener->set_callback
input_mapped_subscription = ctx.input_mapper.get_input_mapped_channel().subscribe
(
[this, &ctx](const event_base& event)
[this, &ctx](const auto& event)
{
auto id = event.get_event_type_id();
if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id)
auto mapping_type = event.mapping->get_mapping_type();
if (mapping_type != input::mapping_type::gamepad_axis &&
mapping_type != input::mapping_type::mouse_motion &&
mapping_type != input::mapping_type::mouse_scroll)
{
if (this->credits_text.get_color()[3] > 0.0f)
{
ctx.input_listener->set_enabled(false);
// Change state
ctx.state_machine.pop();
ctx.state_machine.emplace(new game::state::extras_menu(ctx));
@ -93,20 +94,19 @@ credits::credits(game::context& ctx):
}
}
);
ctx.input_listener->set_enabled(true);
ctx.input_mapper.connect(ctx.app->get_device_manager().get_event_queue());
ctx.ui_scene->add_object(&credits_text);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
credits::~credits()
{
ctx.logger->push_task("Exiting credits state");
debug::log::push_task("Exiting credits state");
// Disable credits skipper
ctx.input_listener->set_enabled(false);
ctx.input_listener->set_callback(nullptr);
ctx.input_mapper.disconnect();
// Destruct credits text
ctx.ui_scene->remove_object(&credits_text);
@ -114,7 +114,7 @@ credits::~credits()
// Destruct credits animations
ctx.animator->remove_animation(&credits_fade_in_animation);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 2
- 0
src/game/state/credits.hpp View File

@ -23,6 +23,7 @@
#include "game/state/base.hpp"
#include "scene/text.hpp"
#include "animation/animation.hpp"
#include "event/subscription.hpp"
namespace game {
namespace state {
@ -37,6 +38,7 @@ private:
scene::text credits_text;
animation<float> credits_fade_in_animation;
animation<float> credits_scroll_animation;
std::shared_ptr<::event::subscription> input_mapped_subscription;
};
} // namespace state

+ 5
- 5
src/game/state/extras-menu.cpp View File

@ -22,7 +22,7 @@
#include "game/state/credits.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"
#include "game/menu.hpp"
@ -32,7 +32,7 @@ namespace state {
extras_menu::extras_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering extras menu state");
debug::log::push_task("Entering extras menu state");
// Construct menu item texts
scene::text* credits_text = new scene::text();
@ -123,12 +123,12 @@ extras_menu::extras_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
extras_menu::~extras_menu()
{
ctx.logger->push_task("Exiting extras menu state");
debug::log::push_task("Exiting extras menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -137,7 +137,7 @@ extras_menu::~extras_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 12
- 10
src/game/state/gamepad-config-menu.cpp View File

@ -22,7 +22,7 @@
#include "game/context.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "resources/resource-manager.hpp"
#include "game/menu.hpp"
#include "game/controls.hpp"
@ -33,7 +33,7 @@ namespace state {
gamepad_config_menu::gamepad_config_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering gamepad config menu state");
debug::log::push_task("Entering gamepad config menu state");
// Add camera control menu items
add_control_item("move_forward");
@ -108,12 +108,12 @@ gamepad_config_menu::gamepad_config_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
gamepad_config_menu::~gamepad_config_menu()
{
ctx.logger->push_task("Exiting gamepad config menu state");
debug::log::push_task("Exiting gamepad config menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -125,13 +125,13 @@ gamepad_config_menu::~gamepad_config_menu()
// Save control profile
game::save_control_profile(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
std::string gamepad_config_menu::get_binding_string(input::control* control)
{
std::string binding_string;
/*
auto mappings = ctx.input_event_router->get_mappings(control);
for (input::mapping* mapping: *mappings)
{
@ -275,14 +275,14 @@ std::string gamepad_config_menu::get_binding_string(input::control* control)
}
}
}
*/
return binding_string;
}
void gamepad_config_menu::add_control_item(const std::string& control_name)
{
// Get pointer to control
input::control* control = ctx.controls[control_name];
//input::control* control = ctx.controls[control_name];
// Construct texts
scene::text* name_text = new scene::text();
@ -299,9 +299,9 @@ void gamepad_config_menu::add_control_item(const std::string& control_name)
name_text->set_content(control_name);
// Set content of value text
value_text->set_content(get_binding_string(control));
//value_text->set_content(get_binding_string(control));
auto select_callback = [this, &ctx = this->ctx, control, value_text]()
auto select_callback = [this, &ctx = this->ctx, value_text]()
{
// Clear binding string from value text
value_text->set_content((*ctx.strings)["ellipsis"]);
@ -311,6 +311,7 @@ void gamepad_config_menu::add_control_item(const std::string& control_name)
// Disable controls
game::menu::clear_controls(ctx);
/*
// Remove gamepad event mappings from control
ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_axis);
ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_button);
@ -363,6 +364,7 @@ void gamepad_config_menu::add_control_item(const std::string& control_name)
}
);
ctx.input_listener->set_enabled(true);
*/
};
// Register menu item callbacks

+ 18
- 18
src/game/state/graphics-menu.cpp View File

@ -21,7 +21,7 @@
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"
#include "game/menu.hpp"
#include "game/graphics.hpp"
@ -35,7 +35,7 @@ static void update_value_text_content(game::context* ctx);
graphics_menu::graphics_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering graphics menu state");
debug::log::push_task("Entering graphics menu state");
// Construct menu item texts
scene::text* fullscreen_name_text = new scene::text();
@ -112,7 +112,7 @@ graphics_menu::graphics_menu(game::context& ctx):
auto increase_resolution_callback = [this, &ctx]()
{
// Increase resolution
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.render_scale += 0.05f;
else
ctx.render_scale += 0.25f;
@ -136,7 +136,7 @@ graphics_menu::graphics_menu(game::context& ctx):
auto decrease_resolution_callback = [this, &ctx]()
{
// Increase resolution
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.render_scale -= 0.05f;
else
ctx.render_scale -= 0.25f;
@ -242,7 +242,7 @@ graphics_menu::graphics_menu(game::context& ctx):
auto increase_font_size_callback = [this, &ctx]()
{
// Increase font size
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.font_size += 0.01f;
else
ctx.font_size += 0.1f;
@ -258,16 +258,16 @@ graphics_menu::graphics_menu(game::context& ctx):
(*ctx.config)["font_size"] = ctx.font_size;
// Reload fonts
ctx.logger->push_task("Reloading fonts");
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -278,7 +278,7 @@ graphics_menu::graphics_menu(game::context& ctx):
auto decrease_font_size_callback = [this, &ctx]()
{
// Decrease font size
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.font_size -= 0.01f;
else
ctx.font_size -= 0.1f;
@ -294,16 +294,16 @@ graphics_menu::graphics_menu(game::context& ctx):
(*ctx.config)["font_size"] = ctx.font_size;
// Reload fonts
ctx.logger->push_task("Reloading fonts");
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -322,16 +322,16 @@ graphics_menu::graphics_menu(game::context& ctx):
(*ctx.config)["dyslexia_font"] = ctx.dyslexia_font;
// Reload fonts
ctx.logger->push_task("Reloading fonts");
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -400,12 +400,12 @@ graphics_menu::graphics_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
graphics_menu::~graphics_menu()
{
ctx.logger->push_task("Exiting graphics menu state");
debug::log::push_task("Exiting graphics menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -414,7 +414,7 @@ graphics_menu::~graphics_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void graphics_menu::update_value_text_content()

+ 12
- 10
src/game/state/keyboard-config-menu.cpp View File

@ -21,7 +21,7 @@
#include "game/state/controls-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "resources/resource-manager.hpp"
#include "game/menu.hpp"
#include "game/controls.hpp"
@ -32,7 +32,7 @@ namespace state {
keyboard_config_menu::keyboard_config_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering keyboard config menu state");
debug::log::push_task("Entering keyboard config menu state");
// Add camera control menu items
add_control_item("move_forward");
@ -107,12 +107,12 @@ keyboard_config_menu::keyboard_config_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
keyboard_config_menu::~keyboard_config_menu()
{
ctx.logger->push_task("Exiting keyboard config menu state");
debug::log::push_task("Exiting keyboard config menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -124,13 +124,13 @@ keyboard_config_menu::~keyboard_config_menu()
// Save control profile
game::save_control_profile(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
std::string keyboard_config_menu::get_binding_string(input::control* control)
{
std::string binding_string;
/*
auto mappings = ctx.input_event_router->get_mappings(control);
for (input::mapping* mapping: *mappings)
{
@ -217,14 +217,14 @@ std::string keyboard_config_menu::get_binding_string(input::control* control)
}
}
}
*/
return binding_string;
}
void keyboard_config_menu::add_control_item(const std::string& control_name)
{
// Get pointer to control
input::control* control = ctx.controls[control_name];
//input::control* control = ctx.controls[control_name];
// Construct texts
scene::text* name_text = new scene::text();
@ -241,9 +241,9 @@ void keyboard_config_menu::add_control_item(const std::string& control_name)
name_text->set_content(control_name);
// Set content of value text
value_text->set_content(get_binding_string( control));
//value_text->set_content(get_binding_string( control));
auto select_callback = [this, &ctx = this->ctx, control, value_text]()
auto select_callback = [this, &ctx = this->ctx, value_text]()
{
// Clear binding string from value text
value_text->set_content((*ctx.strings)["ellipsis"]);
@ -253,6 +253,7 @@ void keyboard_config_menu::add_control_item(const std::string& control_name)
// Disable controls
game::menu::clear_controls(ctx);
/*
// Remove keyboard and mouse event mappings from control
ctx.input_event_router->remove_mappings(control, input::mapping_type::key);
ctx.input_event_router->remove_mappings(control, input::mapping_type::mouse_motion);
@ -317,6 +318,7 @@ void keyboard_config_menu::add_control_item(const std::string& control_name)
}
);
ctx.input_listener->set_enabled(true);
*/
};
// Register menu item callbacks

+ 13
- 13
src/game/state/language-menu.cpp View File

@ -21,7 +21,7 @@
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "game/fonts.hpp"
#include "game/menu.hpp"
@ -31,7 +31,7 @@ namespace state {
language_menu::language_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering language menu state");
debug::log::push_task("Entering language menu state");
// Construct menu item texts
scene::text* language_name_text = new scene::text();
@ -70,19 +70,19 @@ language_menu::language_menu(game::context& ctx):
// Update language in config
(*ctx.config)["language"] = ctx.language_code;
ctx.logger->log("Language changed to \"" + ctx.language_code + "\"");
debug::log::info("Language changed to \"" + ctx.language_code + "\"");
// Reload fonts
ctx.logger->push_task("Reloading fonts");
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
game::menu::update_text_font(ctx);
this->update_text_content();
@ -104,19 +104,19 @@ language_menu::language_menu(game::context& ctx):
// Update language in config
(*ctx.config)["language"] = ctx.language_code;
ctx.logger->log("Language changed to \"" + ctx.language_code + "\"");
debug::log::info("Language changed to \"" + ctx.language_code + "\"");
// Reload fonts
ctx.logger->push_task("Reloading fonts");
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
game::menu::update_text_font(ctx);
this->update_text_content();
@ -168,12 +168,12 @@ language_menu::language_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
language_menu::~language_menu()
{
ctx.logger->push_task("Exiting language menu state");
debug::log::push_task("Exiting language menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -182,7 +182,7 @@ language_menu::~language_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void language_menu::update_text_content()

+ 4
- 42
src/game/state/main-menu.cpp View File

@ -43,9 +43,6 @@
#include "game/component/transform.hpp"
#include "math/projection.hpp"
#include <limits>
#include <iostream>
#include "event/signal.hpp"
namespace game {
namespace state {
@ -53,42 +50,7 @@ namespace state {
main_menu::main_menu(game::context& ctx, bool fade_in):
game::state::base(ctx)
{
ctx.logger->push_task("Entering main menu state");
viewport_size_connection = ctx.app->get_viewport_size_signal().connect
(
[](int w, int h)
{
std::cout << "viewport resized " << w << "x" << h << std::endl;
}
);
window_motion_connection = ctx.app->get_window_motion_signal().connect
(
[](int x, int y)
{
std::cout << "window moved to " << x << ", " << y << std::endl;
}
);
window_focus_connection = ctx.app->get_window_focus_signal().connect
(
[](bool focus)
{
if (focus)
std::cout << "focus gained" << std::endl;
else
std::cout << "focus lost" << std::endl;
}
);
window_close_connection = ctx.app->get_window_close_signal().connect
(
[]()
{
std::cout << "window closed" << std::endl;
}
);
debug::log::push_task("Entering main menu state");
ctx.ui_clear_pass->set_cleared_buffers(true, true, false);
@ -320,12 +282,12 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
//if (!ctx.menu_bg_billboard->is_active())
// game::menu::fade_in_bg(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
main_menu::~main_menu()
{
ctx.logger->push_task("Exiting main menu state");
debug::log::push_task("Exiting main menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -343,7 +305,7 @@ main_menu::~main_menu()
// Destruct title text
ctx.ui_scene->remove_object(&title_text);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void main_menu::fade_in_title()

+ 0
- 6
src/game/state/main-menu.hpp View File

@ -24,7 +24,6 @@
#include "scene/text.hpp"
#include "animation/animation.hpp"
#include "entity/id.hpp"
#include "event/signal.hpp"
namespace game {
namespace state {
@ -43,11 +42,6 @@ private:
animation<float> title_fade_animation;
entity::id swarm_eid;
std::shared_ptr<connection> window_close_connection;
std::shared_ptr<connection> window_motion_connection;
std::shared_ptr<connection> window_focus_connection;
std::shared_ptr<connection> viewport_size_connection;
};
} // namespace state

+ 16
- 13
src/game/state/nest-selection.cpp View File

@ -51,7 +51,6 @@
#include "application.hpp"
#include "input/mouse.hpp"
#include "math/projection.hpp"
#include <iostream>
#include "game/ant/morphogenesis.hpp"
#include "game/ant/phenome.hpp"
@ -67,20 +66,20 @@ namespace state {
nest_selection::nest_selection(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering nest selection state");
debug::log::push_task("Entering nest selection state");
ctx.logger->push_task("Generating genome");
debug::log::push_task("Generating genome");
std::random_device rng;
ant::genome* genome = ant::cladogenesis(ctx.active_ecoregion->gene_pools[0], rng);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
ctx.logger->push_task("Building worker phenome");
debug::log::push_task("Building worker phenome");
ant::phenome worker_phenome = ant::phenome(*genome, ant::caste::worker);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
ctx.logger->push_task("Generating worker model");
debug::log::push_task("Generating worker model");
render::model* worker_model = ant::morphogenesis(worker_phenome);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
// Create worker entity(s)
entity::id worker_eid = ctx.entity_registry->create();
@ -199,16 +198,16 @@ nest_selection::nest_selection(game::context& ctx):
// Queue control setup
ctx.function_queue.push(std::bind(&nest_selection::enable_controls, this));
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
nest_selection::~nest_selection()
{
ctx.logger->push_task("Exiting nest selection state");
debug::log::push_task("Exiting nest selection state");
destroy_first_person_camera_rig();
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void nest_selection::create_first_person_camera_rig()
@ -383,6 +382,7 @@ void nest_selection::satisfy_first_person_camera_rig_constraints()
void nest_selection::enable_controls()
{
/*
// Reset mouse look
mouse_look = false;
@ -715,7 +715,7 @@ void nest_selection::enable_controls()
{
//ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() - 1.0f);
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() + 2.0f * static_cast<float>(ctx.loop.get_update_period()));
ctx.logger->log("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
}
);
@ -725,13 +725,15 @@ void nest_selection::enable_controls()
{
//ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() + 1.0f);
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() - 2.0f * static_cast<float>(ctx.loop.get_update_period()));
ctx.logger->log("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
}
);
*/
}
void nest_selection::disable_controls()
{
/*
if (mouse_look)
{
mouse_look = false;
@ -763,6 +765,7 @@ void nest_selection::disable_controls()
ctx.controls["pause"]->set_activated_callback(nullptr);
ctx.controls["increase_exposure"]->set_active_callback(nullptr);
ctx.controls["decrease_exposure"]->set_active_callback(nullptr);
*/
}
} // namespace state

+ 10
- 7
src/game/state/nuptial-flight.cpp View File

@ -54,7 +54,6 @@
#include "color/color.hpp"
#include "application.hpp"
#include "input/mouse.hpp"
#include <iostream>
namespace game {
namespace state {
@ -62,7 +61,7 @@ namespace state {
nuptial_flight::nuptial_flight(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering nuptial flight state");
debug::log::push_task("Entering nuptial flight state");
// Init selected picking flag
selected_picking_flag = std::uint32_t{1} << (sizeof(std::uint32_t) * 8 - 1);
@ -144,12 +143,12 @@ nuptial_flight::nuptial_flight(game::context& ctx):
// Queue control setup
ctx.function_queue.push(std::bind(&nuptial_flight::enable_controls, this));
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
nuptial_flight::~nuptial_flight()
{
ctx.logger->push_task("Exiting nuptial flight state");
debug::log::push_task("Exiting nuptial flight state");
// Deselect selected entity
select_entity(entt::null);
@ -157,7 +156,7 @@ nuptial_flight::~nuptial_flight()
destroy_camera_rig();
game::ant::destroy_swarm(ctx, swarm_eid);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void nuptial_flight::create_camera_rig()
@ -389,6 +388,7 @@ void nuptial_flight::satisfy_camera_rig_constraints()
void nuptial_flight::enable_controls()
{
/*
// Reset mouse look
mouse_look = false;
@ -747,7 +747,7 @@ void nuptial_flight::enable_controls()
{
//ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() - 1.0f);
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() + 3.0f * static_cast<float>(ctx.loop.get_update_period()));
ctx.logger->log("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
}
);
@ -757,13 +757,15 @@ void nuptial_flight::enable_controls()
{
//ctx.astronomy_system->set_exposure_offset(ctx.astronomy_system->get_exposure_offset() + 1.0f);
ctx.surface_camera->set_exposure(ctx.surface_camera->get_exposure() - 3.0f * static_cast<float>(ctx.loop.get_update_period()));
ctx.logger->log("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
debug::log::info("EV100: " + std::to_string(ctx.surface_camera->get_exposure()));
}
);
*/
}
void nuptial_flight::disable_controls()
{
/*
if (mouse_look)
{
mouse_look = false;
@ -795,6 +797,7 @@ void nuptial_flight::disable_controls()
ctx.controls["pause"]->set_activated_callback(nullptr);
ctx.controls["increase_exposure"]->set_active_callback(nullptr);
ctx.controls["decrease_exposure"]->set_active_callback(nullptr);
*/
}
void nuptial_flight::select_entity(entity::id entity_id)

+ 5
- 5
src/game/state/options-menu.cpp View File

@ -31,7 +31,7 @@
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
namespace game {
namespace state {
@ -39,7 +39,7 @@ namespace state {
options_menu::options_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering options menu state");
debug::log::push_task("Entering options menu state");
// Construct menu item texts
scene::text* controls_text = new scene::text();
@ -216,12 +216,12 @@ options_menu::options_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
options_menu::~options_menu()
{
ctx.logger->push_task("Exiting options menu state");
debug::log::push_task("Exiting options menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -230,7 +230,7 @@ options_menu::~options_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 10
- 10
src/game/state/pause-menu.cpp View File

@ -27,7 +27,7 @@
#include "animation/animator.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "animation/screen-transition.hpp"
#include "config.hpp"
#include "game/save.hpp"
@ -38,7 +38,7 @@ namespace state {
pause_menu::pause_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering pause menu state");
debug::log::push_task("Entering pause menu state");
// Construct menu item texts
scene::text* resume_text = new scene::text();
@ -72,7 +72,7 @@ pause_menu::pause_menu(game::context& ctx):
auto select_resume_callback = [&ctx]()
{
// Disable unpause control
ctx.controls["pause"]->set_activated_callback(nullptr);
//ctx.controls["pause"]->set_activated_callback(nullptr);
// Disable menu controls
game::menu::clear_controls(ctx);
@ -96,7 +96,7 @@ pause_menu::pause_menu(game::context& ctx):
auto select_options_callback = [&ctx]()
{
// Disable unpause control
ctx.controls["pause"]->set_activated_callback(nullptr);
//ctx.controls["pause"]->set_activated_callback(nullptr);
// Disable menu controls
game::menu::clear_controls(ctx);
@ -122,7 +122,7 @@ pause_menu::pause_menu(game::context& ctx):
auto select_main_menu_callback = [&ctx]()
{
// Disable unpause control
ctx.controls["pause"]->set_activated_callback(nullptr);
//ctx.controls["pause"]->set_activated_callback(nullptr);
// Disable menu controls
game::menu::clear_controls(ctx);
@ -155,7 +155,7 @@ pause_menu::pause_menu(game::context& ctx):
auto select_quit_callback = [&ctx]()
{
// Disable unpause control
ctx.controls["pause"]->set_activated_callback(nullptr);
//ctx.controls["pause"]->set_activated_callback(nullptr);
// Disable menu controls
game::menu::clear_controls(ctx);
@ -192,7 +192,7 @@ pause_menu::pause_menu(game::context& ctx):
[&ctx, select_resume_callback]()
{
// Enable unpause control
ctx.controls["pause"]->set_activated_callback(select_resume_callback);
//ctx.controls["pause"]->set_activated_callback(select_resume_callback);
// Enable menu controls
game::menu::setup_controls(ctx);
@ -207,12 +207,12 @@ pause_menu::pause_menu(game::context& ctx):
// Save colony
game::save::colony(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
pause_menu::~pause_menu()
{
ctx.logger->push_task("Exiting pause menu state");
debug::log::push_task("Exiting pause menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -221,7 +221,7 @@ pause_menu::~pause_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 9
- 9
src/game/state/sound-menu.cpp View File

@ -21,7 +21,7 @@
#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"
namespace game {
@ -30,7 +30,7 @@ namespace state {
sound_menu::sound_menu(game::context& ctx):
game::state::base(ctx)
{
ctx.logger->push_task("Entering sound menu state");
debug::log::push_task("Entering sound menu state");
// Construct menu item texts
scene::text* master_volume_name_text = new scene::text();
@ -80,7 +80,7 @@ sound_menu::sound_menu(game::context& ctx):
auto increase_volume_callback = [this, &ctx](float* volume)
{
// Increase volume
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
*volume += 0.01f;
else
*volume += 0.1f;
@ -96,7 +96,7 @@ sound_menu::sound_menu(game::context& ctx):
auto decrease_volume_callback = [this, &ctx](float* volume)
{
// Decrease volume
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
*volume -= 0.01f;
else
*volume -= 0.1f;
@ -131,7 +131,7 @@ sound_menu::sound_menu(game::context& ctx):
auto increase_captions_size_callback = [this, &ctx]()
{
// Increase size
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.captions_size += 0.01f;
else
ctx.captions_size += 0.1f;
@ -148,7 +148,7 @@ sound_menu::sound_menu(game::context& ctx):
auto decrease_captions_size_callback = [this, &ctx]()
{
// Decrease size
if (ctx.controls["menu_modifier"]->is_active())
if (ctx.menu_modifier_control.is_active())
ctx.captions_size -= 0.01f;
else
ctx.captions_size -= 0.1f;
@ -220,12 +220,12 @@ sound_menu::sound_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
sound_menu::~sound_menu()
{
ctx.logger->push_task("Exiting sound menu state");
debug::log::push_task("Exiting sound menu state");
// Destruct menu
game::menu::clear_controls(ctx);
@ -242,7 +242,7 @@ sound_menu::~sound_menu()
(*ctx.config)["captions"] = ctx.captions;
(*ctx.config)["captions_size"] = ctx.captions_size;
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void sound_menu::update_value_text_content()

+ 32
- 20
src/game/state/splash.cpp View File

@ -26,7 +26,7 @@
#include "application.hpp"
#include "render/passes/clear-pass.hpp"
#include "game/context.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "resources/resource-manager.hpp"
#include "render/material-flags.hpp"
#include "math/linear-algebra.hpp"
@ -35,9 +35,10 @@ namespace game {
namespace state {
splash::splash(game::context& ctx):
game::state::base(ctx)
game::state::base(ctx),
skipped(false)
{
ctx.logger->push_task("Entering splash state");
debug::log::push_task("Entering splash state");
// Enable color buffer clearing in UI pass
ctx.ui_clear_pass->set_cleared_buffers(true, true, false);
@ -131,36 +132,47 @@ splash::splash(game::context& ctx):
splash_fade_in_animation.play();
// Set up splash skipper
ctx.input_listener->set_callback
input_mapped_subscription = ctx.input_mapper.get_input_mapped_channel().subscribe
(
[&ctx](const event_base& event)
[this](const auto& event)
{
auto id = event.get_event_type_id();
if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id)
auto mapping_type = event.mapping->get_mapping_type();
if (!this->skipped &&
mapping_type != input::mapping_type::gamepad_axis &&
mapping_type != input::mapping_type::mouse_motion &&
mapping_type != input::mapping_type::mouse_scroll)
{
// Black out screen
ctx.rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
ctx.rasterizer->clear_framebuffer(true, false, false);
ctx.app->swap_buffers();
this->skipped = true;
// Change to main menu state
ctx.state_machine.pop();
ctx.state_machine.emplace(new game::state::main_menu(ctx, true));
this->ctx.function_queue.emplace
(
[&ctx = this->ctx]()
{
// Black out screen
ctx.rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
ctx.rasterizer->clear_framebuffer(true, false, false);
ctx.app->swap_buffers();
// Change to main menu state
ctx.state_machine.pop();
ctx.state_machine.emplace(new game::state::main_menu(ctx, true));
}
);
}
}
);
ctx.input_listener->set_enabled(true);
ctx.input_mapper.connect(ctx.app->get_device_manager().get_event_queue());
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
splash::~splash()
{
ctx.logger->push_task("Exiting splash state");
debug::log::push_task("Exiting splash state");
// Disable splash skipper
ctx.input_listener->set_enabled(false);
ctx.input_listener->set_callback(nullptr);
ctx.input_mapper.disconnect();
// Remove splash fade animations from animator
ctx.animator->remove_animation(&splash_fade_in_animation);
@ -175,7 +187,7 @@ splash::~splash()
// Disable color buffer clearing in UI pass
ctx.ui_clear_pass->set_cleared_buffers(false, true, false);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace state

+ 3
- 0
src/game/state/splash.hpp View File

@ -24,6 +24,7 @@
#include "render/material.hpp"
#include "scene/billboard.hpp"
#include "animation/animation.hpp"
#include "event/subscription.hpp"
namespace game {
namespace state {
@ -39,6 +40,8 @@ private:
scene::billboard splash_billboard;
animation<float> splash_fade_in_animation;
animation<float> splash_fade_out_animation;
std::shared_ptr<::event::subscription> input_mapped_subscription;
bool skipped;
};
} // namespace state

+ 0
- 1
src/game/system/astronomy.cpp View File

@ -36,7 +36,6 @@
#include "astro/apparent-size.hpp"
#include "geom/solid-angle.hpp"
#include "math/polynomial.hpp"
#include <iostream>
namespace game {
namespace system {

+ 0
- 5
src/game/system/camera.cpp View File

@ -37,10 +37,5 @@ void camera::set_viewport(const float4& viewport)
this->viewport = viewport;
}
void camera::handle_event(const window_resized_event& event)
{
set_viewport({0.0f, 0.0f, static_cast<float>(event.w), static_cast<float>(event.h)});
}
} // namespace system
} // namespace game

+ 1
- 8
src/game/system/camera.hpp View File

@ -21,17 +21,12 @@
#define ANTKEEPER_GAME_SYSTEM_CAMERA_HPP
#include "game/system/updatable.hpp"
#include "event/event-handler.hpp"
#include "event/input-events.hpp"
#include "event/window-events.hpp"
#include "utility/fundamental-types.hpp"
namespace game {
namespace system {
class camera:
public updatable,
public event_handler<window_resized_event>
class camera: public updatable
{
public:
camera(entity::registry& registry);
@ -40,8 +35,6 @@ public:
void set_viewport(const float4& viewport);
private:
virtual void handle_event(const window_resized_event& event);
float4 viewport;
};

+ 0
- 1
src/game/system/orbit.cpp View File

@ -19,7 +19,6 @@
#include "game/system/orbit.hpp"
#include "physics/orbit/orbit.hpp"
#include <iostream>
namespace game {
namespace system {

+ 0
- 1
src/game/system/render.cpp View File

@ -24,7 +24,6 @@
#include "scene/directional-light.hpp"
#include "scene/ambient-light.hpp"
#include "scene/spot-light.hpp"
#include <iostream>
namespace game {
namespace system {

+ 0
- 1
src/game/system/spatial.cpp View File

@ -20,7 +20,6 @@
#include "spatial.hpp"
#include "game/component/transform.hpp"
#include "game/component/constraint-stack.hpp"
#include <iostream>
namespace game {
namespace system {

+ 0
- 1
src/game/system/terrain.cpp View File

@ -31,7 +31,6 @@
#include "utility/fundamental-types.hpp"
#include "math/compile.hpp"
#include <functional>
#include <iostream>
namespace game {
namespace system {

+ 35
- 36
src/game/world.cpp View File

@ -20,7 +20,7 @@
#include "application.hpp"
#include "color/color.hpp"
#include "config.hpp"
#include "debug/logger.hpp"
#include "debug/log.hpp"
#include "entity/archetype.hpp"
#include "entity/commands.hpp"
#include "game/component/atmosphere.hpp"
@ -68,7 +68,6 @@
#include <algorithm>
#include <execution>
#include <fstream>
#include <iostream>
#include <stb/stb_image_write.h>
namespace game {
@ -94,19 +93,19 @@ static void create_moon(game::context& ctx);
void cosmogenesis(game::context& ctx)
{
ctx.logger->push_task("Generating cosmos");
debug::log::push_task("Generating cosmos");
load_ephemeris(ctx);
create_stars(ctx);
create_sun(ctx);
create_earth_moon_system(ctx);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_observer(game::context& ctx)
{
ctx.logger->push_task("Creating observer");
debug::log::push_task("Creating observer");
try
{
@ -139,11 +138,11 @@ void create_observer(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void set_location(game::context& ctx, double elevation, double latitude, double longitude)
@ -171,7 +170,7 @@ void set_location(game::context& ctx, double elevation, double latitude, double
void set_time(game::context& ctx, double t)
{
ctx.logger->push_task("Setting time to UT1 " + std::to_string(t));
debug::log::push_task("Setting time to UT1 " + std::to_string(t));
try
{
ctx.astronomy_system->set_time(t);
@ -179,10 +178,10 @@ void set_time(game::context& ctx, double t)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void set_time(game::context& ctx, int year, int month, int day, int hour, int minute, double second)
@ -221,7 +220,7 @@ void set_time_scale(game::context& ctx, double scale)
void load_ephemeris(game::context& ctx)
{
ctx.logger->push_task("Loading ephemeris");
debug::log::push_task("Loading ephemeris");
try
{
@ -232,8 +231,8 @@ void load_ephemeris(game::context& ctx)
}
else
{
ctx.logger->warning("No ephemeris set in config");
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::warning("No ephemeris set in config");
debug::log::pop_task(EXIT_FAILURE);
return;
}
@ -241,16 +240,16 @@ void load_ephemeris(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_stars(game::context& ctx)
{
ctx.logger->push_task("Generating fixed stars");
debug::log::push_task("Generating fixed stars");
// Load star catalog
string_table* star_catalog = nullptr;
@ -263,8 +262,8 @@ void create_stars(game::context& ctx)
}
else
{
ctx.logger->warning("No star catalog set in config");
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::warning("No star catalog set in config");
debug::log::pop_task(EXIT_FAILURE);
return;
}
@ -272,7 +271,7 @@ void create_stars(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
@ -307,7 +306,7 @@ void create_stars(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->warning("Invalid star catalog item on row " + std::to_string(i));
debug::log::warning("Invalid star catalog item on row " + std::to_string(i));
}
// Convert right ascension and declination from degrees to radians
@ -401,12 +400,12 @@ void create_stars(game::context& ctx)
// Pass starlight illuminance to astronomy system
ctx.astronomy_system->set_starlight_illuminance(starlight_illuminance);
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_sun(game::context& ctx)
{
ctx.logger->push_task("Generating Sun");
debug::log::push_task("Generating Sun");
try
{
@ -449,16 +448,16 @@ void create_sun(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_earth_moon_system(game::context& ctx)
{
ctx.logger->push_task("Generating Earth-Moon system");
debug::log::push_task("Generating Earth-Moon system");
try
{
@ -475,16 +474,16 @@ void create_earth_moon_system(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_earth(game::context& ctx)
{
ctx.logger->push_task("Generating Earth");
debug::log::push_task("Generating Earth");
try
{
@ -498,16 +497,16 @@ void create_earth(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void create_moon(game::context& ctx)
{
ctx.logger->push_task("Generating Moon");
debug::log::push_task("Generating Moon");
try
{
@ -535,11 +534,11 @@ void create_moon(game::context& ctx)
}
catch (const std::exception&)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
@ -600,7 +599,7 @@ void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
*/
ctx.logger->push_task("Entering ecoregion " + ecoregion.name);
debug::log::push_task("Entering ecoregion " + ecoregion.name);
try
{
// Set active ecoregion
@ -644,9 +643,9 @@ void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
}
catch (...)
{
ctx.logger->pop_task(EXIT_FAILURE);
debug::log::pop_task(EXIT_FAILURE);
}
ctx.logger->pop_task(EXIT_SUCCESS);
debug::log::pop_task(EXIT_SUCCESS);
}
} // namespace world

+ 299
- 0
src/input/control-map.cpp View File

@ -0,0 +1,299 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "input/control-map.hpp"
#include <algorithm>
#include <cmath>
#include <type_traits>
#include <utility>
namespace input {
void control_map::connect(::event::queue& queue)
{
subscriptions.emplace_back(queue.subscribe<event::gamepad_axis_moved>(std::bind_front(&control_map::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(queue.subscribe<event::gamepad_button_pressed>(std::bind_front(&control_map::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::gamepad_button_released>(std::bind_front(&control_map::handle_gamepad_button_released, this)));
subscriptions.emplace_back(queue.subscribe<event::key_pressed>(std::bind_front(&control_map::handle_key_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::key_released>(std::bind_front(&control_map::handle_key_released, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_button_pressed>(std::bind_front(&control_map::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_button_released>(std::bind_front(&control_map::handle_mouse_button_released, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_moved>(std::bind_front(&control_map::handle_mouse_moved, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_scrolled>(std::bind_front(&control_map::handle_mouse_scrolled, this)));
}
void control_map::disconnect()
{
subscriptions.clear();
}
void control_map::add_mapping(control& control, const mapping& mapping)
{
switch (mapping.get_mapping_type())
{
case mapping_type::gamepad_axis:
add_mapping(control, static_cast<const gamepad_axis_mapping&>(mapping));
break;
case mapping_type::gamepad_button:
add_mapping(control, static_cast<const gamepad_button_mapping&>(mapping));
break;
case mapping_type::key:
add_mapping(control, static_cast<const key_mapping&>(mapping));
break;
case mapping_type::mouse_button:
add_mapping(control, static_cast<const mouse_button_mapping&>(mapping));
break;
case mapping_type::mouse_motion:
add_mapping(control, static_cast<const mouse_motion_mapping&>(mapping));
break;
case mapping_type::mouse_scroll:
add_mapping(control, static_cast<const mouse_scroll_mapping&>(mapping));
break;
default:
//std::unreachable();
break;
}
}
void control_map::add_mapping(control& control, gamepad_axis_mapping&& mapping)
{
gamepad_axis_mappings.emplace_back(&control, mapping);
}
void control_map::add_mapping(control& control, gamepad_button_mapping&& mapping)
{
gamepad_button_mappings.emplace_back(&control, mapping);
}
void control_map::add_mapping(control& control, key_mapping&& mapping)
{
key_mappings.emplace_back(&control, mapping);
}
void control_map::add_mapping(control& control, mouse_button_mapping&& mapping)
{
mouse_button_mappings.emplace_back(&control, mapping);
}
void control_map::add_mapping(control& control, mouse_motion_mapping&& mapping)
{
mouse_motion_mappings.emplace_back(&control, mapping);
}
void control_map::add_mapping(control& control, mouse_scroll_mapping&& mapping)
{
mouse_scroll_mappings.emplace_back(&control, mapping);
}
void control_map::remove_mappings(control& control, mapping_type type)
{
auto predicate = [&](const auto& tuple) -> bool
{
return std::get<0>(tuple) == &control;
};
switch (type)
{
case mapping_type::gamepad_axis:
std::erase_if(gamepad_axis_mappings, predicate);
break;
case mapping_type::gamepad_button:
std::erase_if(gamepad_button_mappings, predicate);
break;
case mapping_type::key:
std::erase_if(key_mappings, predicate);
break;
case mapping_type::mouse_button:
std::erase_if(mouse_button_mappings, predicate);
break;
case mapping_type::mouse_motion:
std::erase_if(mouse_motion_mappings, predicate);
break;
case mapping_type::mouse_scroll:
std::erase_if(mouse_scroll_mappings, predicate);
break;
default:
//std::unreachable();
break;
}
}
void control_map::remove_mappings(control& control)
{
auto predicate = [&](const auto& tuple) -> bool
{
return std::get<0>(tuple) == &control;
};
std::erase_if(gamepad_axis_mappings, predicate);
std::erase_if(gamepad_button_mappings, predicate);
std::erase_if(key_mappings, predicate);
std::erase_if(mouse_button_mappings, predicate);
std::erase_if(mouse_motion_mappings, predicate);
std::erase_if(mouse_scroll_mappings, predicate);
}
void control_map::remove_mappings()
{
gamepad_axis_mappings.clear();
gamepad_button_mappings.clear();
key_mappings.clear();
mouse_button_mappings.clear();
mouse_motion_mappings.clear();
mouse_scroll_mappings.clear();
}
void control_map::handle_gamepad_axis_moved(const event::gamepad_axis_moved& event)
{
for (const auto& [control, mapping]: gamepad_axis_mappings)
{
if (mapping.axis == event.axis &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
{
if (std::signbit(event.position) == mapping.direction)
{
control->evaluate(std::abs(event.position));
}
else
{
control->evaluate(0.0f);
}
}
}
}
void control_map::handle_gamepad_button_pressed(const event::gamepad_button_pressed& event)
{
for (const auto& [control, mapping]: gamepad_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
{
control->evaluate(1.0f);
}
}
}
void control_map::handle_gamepad_button_released(const event::gamepad_button_released& event)
{
for (const auto& [control, mapping]: gamepad_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.gamepad || mapping.gamepad == event.gamepad))
{
control->evaluate(0.0f);
}
}
}
void control_map::handle_key_pressed(const event::key_pressed& event)
{
for (const auto& [control, mapping]: key_mappings)
{
if (mapping.scancode == event.scancode &&
(!mapping.keyboard || mapping.keyboard == event.keyboard))
{
control->evaluate(1.0f);
}
}
}
void control_map::handle_key_released(const event::key_released& event)
{
for (const auto& [control, mapping]: key_mappings)
{
if (mapping.scancode == event.scancode &&
(!mapping.keyboard || mapping.keyboard == event.keyboard))
{
control->evaluate(0.0f);
}
}
}
void control_map::handle_mouse_moved(const event::mouse_moved& event)
{
for (const auto& [control, mapping]: mouse_motion_mappings)
{
if (!mapping.mouse || mapping.mouse == event.mouse)
{
const float difference = static_cast<float>(event.difference[static_cast<std::underlying_type_t<mouse_motion_axis>>(mapping.axis)]);
if (difference && std::signbit(difference) == mapping.direction)
{
control->evaluate(std::abs(difference));
control->evaluate(0.0f);
}
}
}
}
void control_map::handle_mouse_scrolled(const event::mouse_scrolled& event)
{
for (const auto& [control, mapping]: mouse_scroll_mappings)
{
if (!mapping.mouse || mapping.mouse == event.mouse)
{
const auto velocity = event.velocity[static_cast<std::underlying_type_t<mouse_scroll_axis>>(mapping.axis)];
if (velocity && std::signbit(velocity) == mapping.direction)
{
control->evaluate(std::abs(velocity));
control->evaluate(0.0f);
}
}
}
}
void control_map::handle_mouse_button_pressed(const event::mouse_button_pressed& event)
{
for (const auto& [control, mapping]: mouse_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.mouse || mapping.mouse == event.mouse))
{
control->evaluate(1.0f);
}
}
}
void control_map::handle_mouse_button_released(const event::mouse_button_released& event)
{
for (const auto& [control, mapping]: mouse_button_mappings)
{
if (mapping.button == event.button &&
(!mapping.mouse || mapping.mouse == event.mouse))
{
control->evaluate(0.0f);
}
}
}
} // namespace input

+ 111
- 0
src/input/control-map.hpp View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEER_INPUT_CONTROL_MAP_HPP
#define ANTKEEER_INPUT_CONTROL_MAP_HPP
#include "event/subscription.hpp"
#include "event/queue.hpp"
#include "input/control.hpp"
#include "input/event.hpp"
#include "input/mapping.hpp"
#include <memory>
#include <tuple>
#include <unordered_map>
#include <vector>
namespace input {
/**
* Maps input to a set of contextually-related controls.
*/
class control_map
{
public:
/**
* Connects the input event signals of an event queue to the control map.
*
* @param queue Event queue to connect.
*/
void connect(::event::queue& queue);
/**
* Disconnects all input event signals from the control map.
*/
void disconnect();
/**
* Maps input to a control.
*
* @param control Control to which input will be mapped.
* @param mapping Input mapping to add.
*/
/// @{
void add_mapping(control& control, const mapping& mapping);
void add_mapping(control& control, gamepad_axis_mapping&& mapping);
void add_mapping(control& control, gamepad_button_mapping&& mapping);
void add_mapping(control& control, key_mapping&& mapping);
void add_mapping(control& control, mouse_button_mapping&& mapping);
void add_mapping(control& control, mouse_motion_mapping&& mapping);
void add_mapping(control& control, mouse_scroll_mapping&& mapping);
/// @}
/**
* Unmaps input from a control.
*
* @param control Control from which input will be unmapped.
* @param type Type of input mapping to remove.
*/
void remove_mappings(control& control, mapping_type type);
/**
* Unmaps all input from a control.
*
* @param control Control from which input will be unmapped.
*/
void remove_mappings(control& control);
/**
* Unmaps all input from all controls in the control map.
*/
void remove_mappings();
private:
void handle_gamepad_axis_moved(const event::gamepad_axis_moved& event);
void handle_gamepad_button_pressed(const event::gamepad_button_pressed& event);
void handle_gamepad_button_released(const event::gamepad_button_released& event);
void handle_key_pressed(const event::key_pressed& event);
void handle_key_released(const event::key_released& event);
void handle_mouse_button_pressed(const event::mouse_button_pressed& event);
void handle_mouse_button_released(const event::mouse_button_released& event);
void handle_mouse_moved(const event::mouse_moved& event);
void handle_mouse_scrolled(const event::mouse_scrolled& event);
std::vector<std::shared_ptr<::event::subscription>> subscriptions;
std::vector<std::tuple<control*, gamepad_axis_mapping>> gamepad_axis_mappings;
std::vector<std::tuple<control*, gamepad_button_mapping>> gamepad_button_mappings;
std::vector<std::tuple<control*, key_mapping>> key_mappings;
std::vector<std::tuple<control*, mouse_button_mapping>> mouse_button_mappings;
std::vector<std::tuple<control*, mouse_motion_mapping>> mouse_motion_mappings;
std::vector<std::tuple<control*, mouse_scroll_mapping>> mouse_scroll_mappings;
};
} // namespace input
#endif // ANTKEEER_INPUT_CONTROL_MAP_HPP

+ 0
- 83
src/input/control-set.hpp View File

@ -1,83 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_CONTROL_SET_HPP
#define ANTKEEPER_INPUT_CONTROL_SET_HPP
#include <list>
namespace input {
class control;
/**
* A set of controls which can be managed simultaneously.
*/
class control_set
{
public:
/**
* Adds a control to the control set.
*
* @param control Pointer to the control to add.
*/
void add_control(control* control);
/**
* Removes a control from the control set.
*
* @param control Pointer to the control to remove.
*/
void remove_control(control* control);
/**
* Removes all controls from the control set.
*/
void remove_controls();
/**
* Calls control::update() on each control in this control set.
*/
void update();
/**
* Enables or disables callbacks for all controls in the control set.
*
* @param enabled Whether to enable or disable callbacks for all controls in the control set.
*/
void set_callbacks_enabled(bool enabled);
/**
* Returns the list of controls in the control set.
*/
const std::list<control*>* get_controls() const;
private:
std::list<control*> controls;
};
inline const std::list<control*>* control_set::get_controls() const
{
return &controls;
}
} // namespace input
#endif // ANTKEEPER_INPUT_CONTROL_SET_HPP

+ 33
- 88
src/input/control.cpp View File

@ -17,109 +17,54 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "control.hpp"
#include "input/control.hpp"
namespace input {
static bool default_threshold_function(float x) noexcept
{
return x > 0.0f;
}
control::control():
activation_threshold(0.0f),
current_value(0.0f),
previous_value(0.0f),
reset(false),
activated_callback(nullptr),
deactivated_callback(nullptr),
value_changed_callback(nullptr),
active_callback(nullptr),
callbacks_enabled(true)
threshold_function(default_threshold_function),
active(false),
activated_event{this},
active_event{this, 0.0f},
deactivated_event{this}
{}
control::~control()
{}
void control::set_threshold_function(const threshold_function_type& function)
{
threshold_function = function;
}
void control::update()
void control::evaluate(float value)
{
// Perform callbacks, if enabled
if (callbacks_enabled)
// Store activation state
const bool was_active = active;
// Re-evaluate activation state
active = threshold_function(value);
// Emit events
if (active)
{
if (activated_callback)
{
if (is_active() && !was_active())
{
activated_callback();
}
}
if (deactivated_callback)
{
if (!is_active() && was_active())
{
deactivated_callback();
}
}
if (value_changed_callback)
if (!was_active)
{
if (current_value != previous_value)
{
if (is_active() || was_active())
{
value_changed_callback(current_value);
}
}
activated_publisher.publish(activated_event);
}
if (active_callback && is_active())
{
active_callback(current_value);
}
active_event.input_value = value;
active_publisher.publish(active_event);
}
// Update previous value
previous_value = current_value;
// Reset temporary values
if (reset)
else
{
current_value = 0.0f;
reset = false;
if (was_active)
{
deactivated_publisher.publish(deactivated_event);
}
}
}
void control::set_current_value(float value)
{
current_value = value;
reset = false;
}
void control::set_temporary_value(float value)
{
current_value = value;
reset = true;
}
void control::set_activation_threshold(float threshold)
{
activation_threshold = threshold;
}
void control::set_activated_callback(std::function<void()> callback)
{
this->activated_callback = callback;
}
void control::set_deactivated_callback(std::function<void()> callback)
{
this->deactivated_callback = callback;
}
void control::set_value_changed_callback(std::function<void(float)> callback)
{
this->value_changed_callback = callback;
}
void control::set_active_callback(std::function<void(float)> callback)
{
this->active_callback = callback;
}
} // namespace input

+ 56
- 94
src/input/control.hpp View File

@ -20,123 +20,85 @@
#ifndef ANTKEEPER_INPUT_CONTROL_HPP
#define ANTKEEPER_INPUT_CONTROL_HPP
#include "input/event.hpp"
#include "event/publisher.hpp"
#include <functional>
namespace input {
/**
* A control can be bound to multiple types of input events.
* Generates control-related input events on activation state changes.
*/
class control
{
public:
/// Creates a control.
control();
/// Destroys a control.
virtual ~control();
/**
* Performs callbacks then sets the previous value equal to the current value.
*/
void update();
/**
* Sets the current value of the control.
* Threshold function type.
*
* @param value control value.
* Given an input value, returns `true` if the control should be considered active, and `false` otherwise.
*/
void set_current_value(float value);
/**
* This works the same as setting the current value, but causes the value to be reset on the next call to update.
*/
void set_temporary_value(float value);
typedef std::function<bool(float)> threshold_function_type;
/// Constructs a control.
control();
/**
* Sets the activation threshold. If the current value of the control is not greater than the activation threshold, the control will not be considered active.
* Sets the threshold function.
*
* @param threshold Activation threshold.
* @param function Threshold function.
*/
void set_activation_threshold(float threshold);
/// Sets the callback for when the control is activated.
void set_activated_callback(std::function<void()> callback);
/// Sets the callback for when the control is deactivated.
void set_deactivated_callback(std::function<void()> callback);
/// Sets the callback for when the control value is changed.
void set_value_changed_callback(std::function<void(float)> callback);
void set_threshold_function(const threshold_function_type& function);
/// Sets the callback for while the control is active.
void set_active_callback(std::function<void(float)> callback);
/**
* Enables or disables callbacks.
* Evaluates the activation state of the control, according to its threshold function and an input value.
*
* @param enabled Whether to enable or disable callbacks.
* @param value Input value.
*/
void set_callbacks_enabled(bool enabled);
/// Returns the activation threshold. The default value is 0.0.
float get_activation_threshold() const;
/// Returns the current value of the control.
float get_current_value() const;
/// Returns the previous value of the control.
float get_previous_value() const;
/// Returns true if the control is currently active.
bool is_active() const;
/// Returns true if the control was previously active when update() was last called.
bool was_active() const;
void evaluate(float value);
/// Returns the threshold function.
[[nodiscard]] inline const threshold_function_type& get_threshold_function() const noexcept
{
return threshold_function;
}
/// Returns `true` if the control is active, `false` otherwise.
[[nodiscard]] inline bool is_active() const noexcept
{
return active;
}
/// Returns the channel through which control activated events are published.
[[nodiscard]] inline ::event::channel<event::control_activated>& get_activated_channel() noexcept
{
return activated_publisher.channel();
}
/// Returns the channel through which control active events are published.
[[nodiscard]] inline ::event::channel<event::control_active>& get_active_channel() noexcept
{
return active_publisher.channel();
}
/// Returns the channel through which control deactivated events are published.
[[nodiscard]] inline ::event::channel<event::control_deactivated>& get_deactivated_channel() noexcept
{
return deactivated_publisher.channel();
}
private:
float activation_threshold;
float current_value;
float previous_value;
bool reset;
std::function<void()> activated_callback;
std::function<void()> deactivated_callback;
std::function<void(float)> value_changed_callback;
std::function<void(float)> active_callback;
bool callbacks_enabled;
threshold_function_type threshold_function;
bool active;
event::control_activated activated_event;
event::control_active active_event;
event::control_deactivated deactivated_event;
::event::publisher<event::control_activated> activated_publisher;
::event::publisher<event::control_active> active_publisher;
::event::publisher<event::control_deactivated> deactivated_publisher;
};
inline void control::set_callbacks_enabled(bool enabled)
{
this->callbacks_enabled = enabled;
}
inline float control::get_activation_threshold() const
{
return activation_threshold;
}
inline float control::get_current_value() const
{
return current_value;
}
inline float control::get_previous_value() const
{
return previous_value;
}
inline bool control::is_active() const
{
return current_value > activation_threshold;
}
inline bool control::was_active() const
{
return previous_value > activation_threshold;
}
} // namespace input
#endif // ANTKEEPER_INPUT_CONTROL_HPP

+ 121
- 0
src/input/device-manager.cpp View File

@ -0,0 +1,121 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "input/device-manager.hpp"
namespace input {
void device_manager::register_device(device& device)
{
subscriptions.emplace(&device, device.get_connected_channel().subscribe(event_queue));
subscriptions.emplace(&device, device.get_disconnected_channel().subscribe(event_queue));
switch (device.get_device_type())
{
case device_type::gamepad:
register_gamepad(static_cast<gamepad&>(device));
break;
case device_type::keyboard:
register_keyboard(static_cast<keyboard&>(device));
break;
case device_type::mouse:
register_mouse(static_cast<mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void device_manager::unregister_device(device& device)
{
subscriptions.erase(&device);
switch (device.get_device_type())
{
case device_type::gamepad:
unregister_gamepad(static_cast<gamepad&>(device));
break;
case device_type::keyboard:
unregister_keyboard(static_cast<keyboard&>(device));
break;
case device_type::mouse:
unregister_mouse(static_cast<mouse&>(device));
break;
default:
//std::unreachable();
break;
}
}
void device_manager::register_gamepad(gamepad& gamepad)
{
// Connect gamepad event signals to the event queue
subscriptions.emplace(&gamepad, gamepad.get_axis_moved_channel().subscribe(event_queue));
subscriptions.emplace(&gamepad, gamepad.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&gamepad, gamepad.get_button_released_channel().subscribe(event_queue));
// Add gamepad to list of gamepads
gamepads.emplace(&gamepad);
}
void device_manager::register_keyboard(keyboard& keyboard)
{
// Connect keyboard event signals to the event queue
subscriptions.emplace(&keyboard, keyboard.get_key_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&keyboard, keyboard.get_key_released_channel().subscribe(event_queue));
// Add keyboard to list of keyboards
keyboards.emplace(&keyboard);
}
void device_manager::register_mouse(mouse& mouse)
{
// Connect mouse event signals to the event queue
subscriptions.emplace(&mouse, mouse.get_button_pressed_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_button_released_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_moved_channel().subscribe(event_queue));
subscriptions.emplace(&mouse, mouse.get_scrolled_channel().subscribe(event_queue));
// Add mouse to list of mice
mice.emplace(&mouse);
}
void device_manager::unregister_gamepad(gamepad& gamepad)
{
gamepads.erase(&gamepad);
}
void device_manager::unregister_keyboard(keyboard& keyboard)
{
keyboards.erase(&keyboard);
}
void device_manager::unregister_mouse(mouse& mouse)
{
mice.erase(&mouse);
}
} // namespace input

+ 97
- 0
src/input/device-manager.hpp View File

@ -0,0 +1,97 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
#define ANTKEEPER_INPUT_DEVICE_MANAGER_HPP
#include "input/device.hpp"
#include "input/gamepad.hpp"
#include "input/keyboard.hpp"
#include "input/mouse.hpp"
#include "event/queue.hpp"
#include <map>
#include <memory>
#include <unordered_set>
namespace input {
/**
* Manages virtual input devices.
*/
class device_manager
{
public:
/**
* Registers an input device.
*
* @param device Input device to register.
*/
void register_device(device& device);
/**
* Unregisters an input device.
*
* @param device Input device to unregister.
*/
void unregister_device(device& device);
/**
* Returns the event queue associated with registered input devices.
*/
[[nodiscard]] inline ::event::queue& get_event_queue() noexcept
{
return event_queue;
}
/// Returns the set of registered gamepads.
[[nodiscard]] inline const std::unordered_set<gamepad*>& get_gamepads() noexcept
{
return gamepads;
}
/// Returns the set of registered keyboards.
[[nodiscard]] inline const std::unordered_set<keyboard*>& get_keyboards() noexcept
{
return keyboards;
}
/// Returns the set of registered mice.
[[nodiscard]] inline const std::unordered_set<mouse*>& get_mice() noexcept
{
return mice;
}
private:
void register_gamepad(gamepad& gamepad);
void register_keyboard(keyboard& keyboard);
void register_mouse(mouse& mouse);
void unregister_gamepad(gamepad& gamepad);
void unregister_keyboard(keyboard& keyboard);
void unregister_mouse(mouse& mouse);
::event::queue event_queue;
std::multimap<device*, std::shared_ptr<::event::subscription>> subscriptions;
std::unordered_set<gamepad*> gamepads;
std::unordered_set<keyboard*> keyboards;
std::unordered_set<mouse*> mice;
};
} // namespace input
#endif // ANTKEEPER_INPUT_DEVICE_MANAGER_HPP

+ 40
- 0
src/input/device-type.hpp View File

@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_DEVICE_TYPE_HPP
#define ANTKEEPER_INPUT_DEVICE_TYPE_HPP
namespace input {
/// Input device types.
enum class device_type
{
/// Gamepad input device.
gamepad,
/// Keyboard input device.
keyboard,
/// Mouse input device.
mouse
};
} // namespace input
#endif // ANTKEEPER_INPUT_DEVICE_TYPE_HPP

+ 13
- 6
src/input/device.cpp View File

@ -17,22 +17,29 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "device.hpp"
#include "input/device.hpp"
namespace input {
device::device():
event_dispatcher(nullptr)
connected(true)
{}
void device::set_event_dispatcher(::event_dispatcher* event_dispatcher)
void device::connect()
{
this->event_dispatcher = event_dispatcher;
connected = true;
connected_publisher.publish({this});
}
void device::set_guid(const std::string& guid)
void device::disconnect()
{
this->guid = guid;
connected = false;
disconnected_publisher.publish({this});
}
void device::set_uuid(const ::uuid& id)
{
uuid = id;
}
} // namespace input

+ 55
- 31
src/input/device.hpp View File

@ -20,57 +20,81 @@
#ifndef ANTKEEPER_INPUT_DEVICE_HPP
#define ANTKEEPER_INPUT_DEVICE_HPP
#include "event/event-dispatcher.hpp"
#include "input/device-type.hpp"
#include "input/event.hpp"
#include "event/publisher.hpp"
#include "utility/uuid.hpp"
#include <optional>
#include <string>
namespace input {
/**
* Base class for virtual devices which generate input events.
* Abstract base class for virtual devices that generate input events.
*/
class device
{
public:
/// Constructs an input device.
device();
/// Destructs an input device.
virtual ~device() = default;
void set_event_dispatcher(event_dispatcher* event_dispatcher);
const event_dispatcher* get_event_dispatcher() const;
event_dispatcher* get_event_dispatcher();
/**
* Sets the globally unique identifier (GUID) of this input device.
* Simulates the device being connected.
*/
void connect();
/**
* Simulates the device being disconnected.
*
* @param guid GUID string.
* @note Disconnected devices can still generate input events.
*/
void set_guid(const std::string& guid);
void disconnect();
/// Returns the globally unique identifier (GUID) of this input device.
const std::string& get_guid() const;
protected:
event_dispatcher* event_dispatcher;
/// Returns `true` if the device is currently connected.
[[nodiscard]] inline bool is_connected() const noexcept
{
return connected;
}
/**
* Sets the universally unique identifier (UUID) of this input device.
*
* @param id UUID.
*/
void set_uuid(const ::uuid& id);
/// Returns the universally unique identifier (UUID) of this input device.
[[nodiscard]] inline const ::uuid& get_uuid() const noexcept
{
return uuid;
}
/// Returns the channel through which device connected events are published.
[[nodiscard]] inline ::event::channel<event::device_connected>& get_connected_channel() noexcept
{
return connected_publisher.channel();
}
/// Returns the channel through which device disconnected events are published.
[[nodiscard]] inline ::event::channel<event::device_disconnected>& get_disconnected_channel() noexcept
{
return disconnected_publisher.channel();
}
/// Returns the input device type.
[[nodiscard]] virtual constexpr device_type get_device_type() const noexcept = 0;
private:
std::string guid;
::uuid uuid;
bool connected;
::event::publisher<event::device_connected> connected_publisher;
::event::publisher<event::device_disconnected> disconnected_publisher;
};
inline const event_dispatcher* device::get_event_dispatcher() const
{
return event_dispatcher;
}
inline event_dispatcher* device::get_event_dispatcher()
{
return event_dispatcher;
}
inline const std::string& device::get_guid() const
{
return guid;
}
} // namespace input
#endif // ANTKEEPER_INPUT_DEVICE_HPP

+ 0
- 399
src/input/event-router.cpp View File

@ -1,399 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "event-router.hpp"
#include "control.hpp"
#include "mapping.hpp"
#include "mouse.hpp"
#include "event/event-dispatcher.hpp"
namespace input {
event_router::event_router():
event_dispatcher(nullptr)
{}
event_router::~event_router()
{
remove_mappings();
set_event_dispatcher(nullptr);
}
void event_router::add_mapping(const mapping& mapping)
{
control* control = mapping.control;
switch (mapping.get_type())
{
case mapping_type::key:
{
input::key_mapping* key_mapping = new input::key_mapping(static_cast<const input::key_mapping&>(mapping));
key_mappings.push_back(key_mapping);
controls[control].push_back(key_mapping);
break;
}
case mapping_type::mouse_motion:
{
input::mouse_motion_mapping* mouse_motion_mapping = new input::mouse_motion_mapping(static_cast<const input::mouse_motion_mapping&>(mapping));
mouse_motion_mappings.push_back(mouse_motion_mapping);
controls[control].push_back(mouse_motion_mapping);
break;
}
case mapping_type::mouse_wheel:
{
input::mouse_wheel_mapping* mouse_wheel_mapping = new input::mouse_wheel_mapping(static_cast<const input::mouse_wheel_mapping&>(mapping));
mouse_wheel_mappings.push_back(mouse_wheel_mapping);
controls[control].push_back(mouse_wheel_mapping);
break;
}
case mapping_type::mouse_button:
{
input::mouse_button_mapping* mouse_button_mapping = new input::mouse_button_mapping(static_cast<const input::mouse_button_mapping&>(mapping));
mouse_button_mappings.push_back(mouse_button_mapping);
controls[control].push_back(mouse_button_mapping);
break;
}
case mapping_type::gamepad_axis:
{
input::gamepad_axis_mapping* gamepad_axis_mapping = new input::gamepad_axis_mapping(static_cast<const input::gamepad_axis_mapping&>(mapping));
gamepad_axis_mappings.push_back(gamepad_axis_mapping);
controls[control].push_back(gamepad_axis_mapping);
break;
}
case mapping_type::gamepad_button:
{
input::gamepad_button_mapping* gamepad_button_mapping = new input::gamepad_button_mapping(static_cast<const input::gamepad_button_mapping&>(mapping));
gamepad_button_mappings.push_back(gamepad_button_mapping);
controls[control].push_back(gamepad_button_mapping);
break;
}
default:
break;
}
}
void event_router::remove_mappings(control* control)
{
auto it = controls.find(control);
if (it != controls.end())
{
for (mapping* mapping: it->second)
{
switch (mapping->get_type())
{
case mapping_type::key:
key_mappings.remove(static_cast<key_mapping*>(mapping));
break;
case mapping_type::mouse_motion:
mouse_motion_mappings.remove(static_cast<mouse_motion_mapping*>(mapping));
break;
case mapping_type::mouse_wheel:
mouse_wheel_mappings.remove(static_cast<mouse_wheel_mapping*>(mapping));
break;
case mapping_type::mouse_button:
mouse_button_mappings.remove(static_cast<mouse_button_mapping*>(mapping));
break;
case mapping_type::gamepad_axis:
gamepad_axis_mappings.remove(static_cast<gamepad_axis_mapping*>(mapping));
break;
case mapping_type::gamepad_button:
gamepad_button_mappings.remove(static_cast<gamepad_button_mapping*>(mapping));
break;
default:
break;
}
delete mapping;
}
controls.erase(it);
}
}
void event_router::remove_mappings(control* control, mapping_type type)
{
auto it = controls.find(control);
if (it != controls.end())
{
std::list<mapping*> flagged_mappings;
for (mapping* mapping: it->second)
{
if (mapping->get_type() != type)
continue;
// Flag mapping for deletion
flagged_mappings.push_back(mapping);
switch (mapping->get_type())
{
case mapping_type::key:
key_mappings.remove(static_cast<key_mapping*>(mapping));
break;
case mapping_type::mouse_motion:
mouse_motion_mappings.remove(static_cast<mouse_motion_mapping*>(mapping));
break;
case mapping_type::mouse_wheel:
mouse_wheel_mappings.remove(static_cast<mouse_wheel_mapping*>(mapping));
break;
case mapping_type::mouse_button:
mouse_button_mappings.remove(static_cast<mouse_button_mapping*>(mapping));
break;
case mapping_type::gamepad_axis:
gamepad_axis_mappings.remove(static_cast<gamepad_axis_mapping*>(mapping));
break;
case mapping_type::gamepad_button:
gamepad_button_mappings.remove(static_cast<gamepad_button_mapping*>(mapping));
break;
default:
break;
}
}
// Delete flagged mappings
for (mapping* mapping: flagged_mappings)
{
it->second.remove(mapping);
delete mapping;
}
}
}
void event_router::set_event_dispatcher(::event_dispatcher* event_dispatcher)
{
if (this->event_dispatcher)
{
this->event_dispatcher->unsubscribe<key_pressed_event>(this);
this->event_dispatcher->unsubscribe<key_released_event>(this);
this->event_dispatcher->unsubscribe<mouse_moved_event>(this);
this->event_dispatcher->unsubscribe<mouse_wheel_scrolled_event>(this);
this->event_dispatcher->unsubscribe<mouse_button_pressed_event>(this);
this->event_dispatcher->unsubscribe<mouse_button_released_event>(this);
this->event_dispatcher->unsubscribe<gamepad_axis_moved_event>(this);
this->event_dispatcher->unsubscribe<gamepad_button_pressed_event>(this);
this->event_dispatcher->unsubscribe<gamepad_button_released_event>(this);
}
this->event_dispatcher = event_dispatcher;
if (event_dispatcher)
{
event_dispatcher->subscribe<key_pressed_event>(this);
event_dispatcher->subscribe<key_released_event>(this);
event_dispatcher->subscribe<mouse_moved_event>(this);
event_dispatcher->subscribe<mouse_wheel_scrolled_event>(this);
event_dispatcher->subscribe<mouse_button_pressed_event>(this);
event_dispatcher->subscribe<mouse_button_released_event>(this);
event_dispatcher->subscribe<gamepad_axis_moved_event>(this);
event_dispatcher->subscribe<gamepad_button_pressed_event>(this);
event_dispatcher->subscribe<gamepad_button_released_event>(this);
}
}
void event_router::remove_mappings()
{
for (auto it = controls.begin(); it != controls.end(); ++it)
{
for (mapping* mapping: it->second)
{
delete mapping;
}
}
controls.clear();
key_mappings.clear();
mouse_motion_mappings.clear();
mouse_wheel_mappings.clear();
mouse_button_mappings.clear();
gamepad_axis_mappings.clear();
gamepad_button_mappings.clear();
}
const std::list<mapping*>* event_router::get_mappings(control* control) const
{
auto it = controls.find(control);
if (it == controls.end())
{
return nullptr;
}
return &it->second;
}
void event_router::handle_event(const key_pressed_event& event)
{
for (const key_mapping* mapping: key_mappings)
{
if ((!mapping->keyboard || mapping->keyboard == event.keyboard) && mapping->scancode == event.scancode)
{
mapping->control->set_current_value(1.0f);
}
}
}
void event_router::handle_event(const key_released_event& event)
{
for (const key_mapping* mapping: key_mappings)
{
if ((!mapping->keyboard || mapping->keyboard == event.keyboard) && mapping->scancode == event.scancode)
{
mapping->control->set_current_value(0.0f);
}
}
}
void event_router::handle_event(const mouse_moved_event& event)
{
for (const mouse_motion_mapping* mapping: mouse_motion_mappings)
{
if (!mapping->mouse || mapping->mouse == event.mouse)
{
if (mapping->axis == mouse_motion_axis::negative_x && event.dx < 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() - event.dx);
}
else if (mapping->axis == mouse_motion_axis::positive_x && event.dx > 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() + event.dx);
}
else if (mapping->axis == mouse_motion_axis::negative_y && event.dy < 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() - event.dy);
}
else if (mapping->axis == mouse_motion_axis::positive_y && event.dy > 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() + event.dy);
}
}
}
}
void event_router::handle_event(const mouse_wheel_scrolled_event& event)
{
for (const mouse_wheel_mapping* mapping: mouse_wheel_mappings)
{
if (!mapping->mouse || mapping->mouse == event.mouse)
{
if (mapping->axis == mouse_wheel_axis::negative_x && event.x < 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() - event.x);
}
else if (mapping->axis == mouse_wheel_axis::positive_x && event.x > 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() + event.x);
}
else if (mapping->axis == mouse_wheel_axis::negative_y && event.y < 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() - event.y);
}
else if (mapping->axis == mouse_wheel_axis::positive_y && event.y > 0)
{
mapping->control->set_temporary_value(mapping->control->get_current_value() + event.y);
}
}
}
}
void event_router::handle_event(const mouse_button_pressed_event& event)
{
for (const mouse_button_mapping* mapping: mouse_button_mappings)
{
if ((!mapping->mouse || mapping->mouse == event.mouse) && mapping->button == event.button)
{
mapping->control->set_current_value(1.0f);
}
}
}
void event_router::handle_event(const mouse_button_released_event& event)
{
for (const mouse_button_mapping* mapping: mouse_button_mappings)
{
if ((!mapping->mouse || mapping->mouse == event.mouse) && mapping->button == event.button)
{
mapping->control->set_current_value(0.0f);
}
}
}
void event_router::handle_event(const gamepad_axis_moved_event& event)
{
for (const gamepad_axis_mapping* mapping: gamepad_axis_mappings)
{
if ((!mapping->controller || mapping->controller == event.controller) && mapping->axis == event.axis)
{
if (mapping->negative && event.value >= 0.0f || !mapping->negative && event.value <= 0.0f)
{
mapping->control->set_current_value(0.0f);
}
else
{
mapping->control->set_current_value(std::abs(event.value));
}
}
}
}
void event_router::handle_event(const gamepad_button_pressed_event& event)
{
for (const gamepad_button_mapping* mapping: gamepad_button_mappings)
{
if ((!mapping->controller || mapping->controller == event.controller) && mapping->button == event.button)
{
mapping->control->set_current_value(1.0f);
}
}
}
void event_router::handle_event(const gamepad_button_released_event& event)
{
for (const gamepad_button_mapping* mapping: gamepad_button_mappings)
{
if ((!mapping->controller || mapping->controller == event.controller) && mapping->button == event.button)
{
mapping->control->set_current_value(0.0f);
}
}
}
} // namespace input

+ 0
- 127
src/input/event-router.hpp View File

@ -1,127 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEER_INPUT_EVENT_ROUTER_HPP
#define ANTKEEER_INPUT_EVENT_ROUTER_HPP
#include "event/input-events.hpp"
#include "event/event-handler.hpp"
#include "event/event-dispatcher.hpp"
#include <list>
#include <map>
namespace input {
class control;
enum class mapping_type;
class mapping;
class key_mapping;
class mouse_motion_mapping;
class mouse_wheel_mapping;
class mouse_button_mapping;
class gamepad_axis_mapping;
class gamepad_button_mapping;
enum class mouse_motion_axis;
enum class mouse_wheel_axis;
/**
* Uses input mappings to route input events to controls.
*/
class event_router:
public event_handler<key_pressed_event>,
public event_handler<key_released_event>,
public event_handler<mouse_moved_event>,
public event_handler<mouse_wheel_scrolled_event>,
public event_handler<mouse_button_pressed_event>,
public event_handler<mouse_button_released_event>,
public event_handler<gamepad_axis_moved_event>,
public event_handler<gamepad_button_pressed_event>,
public event_handler<gamepad_button_released_event>
{
public:
/**
* Creates an input router and subscribes it to the input events of the specified event dispatcher.
*/
event_router();
/**
* Destroys an input router and unsubscribes it from input events.
*/
~event_router();
/**
* Adds an input mapping to the router.
*
* @param mapping Input mapping to add.
*/
void add_mapping(const mapping& mapping);
/**
* Removes all input mappings from the router that are associated with the specified control.
*
* @param control Control with which associated input mappings should be removed.
*/
void remove_mappings(control* control);
/**
* Removes all input mappings of a given type from the router that are associated with the specified control.
*
* @param control Control with which associated input mappings should be removed.
* @param type Type of input mapping to be removed.
*/
void remove_mappings(control* control, mapping_type type);
/**
* Removes all input mappings from the router.
*/
void remove_mappings();
/**
* Sets the event dispatcher to which this input event router will subscribe itself.
*/
void set_event_dispatcher(event_dispatcher* event_dispatcher);
/// Returns a list of mappings for the specified control, or nullptr if the control is unmapped.
const std::list<mapping*>* get_mappings(control* control) const;
private:
virtual void handle_event(const key_pressed_event& event);
virtual void handle_event(const key_released_event& event);
virtual void handle_event(const mouse_moved_event& event);
virtual void handle_event(const mouse_wheel_scrolled_event& event);
virtual void handle_event(const mouse_button_pressed_event& event);
virtual void handle_event(const mouse_button_released_event& event);
virtual void handle_event(const gamepad_axis_moved_event& event);
virtual void handle_event(const gamepad_button_pressed_event& event);
virtual void handle_event(const gamepad_button_released_event& event);
event_dispatcher* event_dispatcher;
std::map<control*, std::list<mapping*>> controls;
std::list<key_mapping*> key_mappings;
std::list<mouse_motion_mapping*> mouse_motion_mappings;
std::list<mouse_wheel_mapping*> mouse_wheel_mappings;
std::list<mouse_button_mapping*> mouse_button_mappings;
std::list<gamepad_axis_mapping*> gamepad_axis_mappings;
std::list<gamepad_button_mapping*> gamepad_button_mappings;
};
} // namespace input
#endif // ANTKEEER_INPUT_EVENT_ROUTER_HPP

+ 283
- 0
src/input/event.hpp View File

@ -0,0 +1,283 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_EVENT_HPP
#define ANTKEEPER_INPUT_EVENT_HPP
#include "input/gamepad-axis.hpp"
#include "input/gamepad-button.hpp"
#include "input/mouse-motion-axis.hpp"
#include "input/mouse-scroll-axis.hpp"
#include "input/scancode.hpp"
#include "input/mapping.hpp"
#include "input/modifier-key.hpp"
#include "math/vector.hpp"
#include <cstdint>
#include <memory>
namespace input {
class control;
class device;
class gamepad;
class keyboard;
class mouse;
/**
* Input events.
*/
namespace event {
/**
* Event generated when a control has been activated.
*/
struct control_activated
{
/// Control that was activated.
control* control;
};
/**
* Event generated while a control is active.
*/
struct control_active
{
/// Active control.
control* control;
/// Control input value.
float input_value;
};
/**
* Event generated when a control has been deactivated.
*/
struct control_deactivated
{
/// Control that was deactivated.
control* control;
};
/**
* Event generated when an input mapping has been generated.
*/
struct input_mapped
{
/// Input mapping that was generated.
std::shared_ptr<mapping> mapping;
};
/**
* Event generated when an input device has been connected.
*/
struct device_connected
{
/// Device that was connected.
device* device;
};
/**
* Event generated when an input device has been disconnected.
*/
struct device_disconnected
{
/// Device that was disconnected.
device* device;
};
/**
* Event generated when a gamepad button has been pressed.
*/
struct gamepad_button_pressed
{
/// Gamepad that generated the event.
gamepad* gamepad;
/// Gamepad button being pressed.
gamepad_button button;
};
/**
* Event generated when a gamepad button has been released.
*/
struct gamepad_button_released
{
/// Gamepad that generated the event.
gamepad* gamepad;
/// Gamepad button being released.
gamepad_button button;
};
/**
* Event generated when a gamepad axis has been moved.
*/
struct gamepad_axis_moved
{
/// Gamepad that generated the event.
gamepad* gamepad;
/// Gamepad axis being moved.
gamepad_axis axis;
/// Position of the gamepad axis, on `[-1, 1]`.
float position;
};
/**
* Event generated when a keyboard key has been pressed.
*/
struct key_pressed
{
/// Keyboard that generated the event.
keyboard* keyboard;
/// Scancode of the key being pressed.
scancode scancode;
/// `true` if the key press was generated by a key repeat, `false` otherwise.
bool repeat;
/// Bit mask containing the active modifier keys.
std::uint16_t modifiers;
};
/**
* Event generated when a keyboard key has been released.
*/
struct key_released
{
/// Keyboard that generated the event.
keyboard* keyboard;
/// Scancode of the key being released.
scancode scancode;
/// `true` if the key release was generated by a key repeat, `false` otherwise.
bool repeat;
/// Bit mask containing the active modifier keys.
std::uint16_t modifiers;
};
/**
* Event generated when a mouse has been moved.
*/
struct mouse_moved
{
/// Mouse that generated the event.
mouse* mouse;
/// Mouse position, in pixels, relative to the window.
math::vector<std::int32_t, 2> position;
/// Relative movement of the mouse, in pixels.
math::vector<std::int32_t, 2> difference;
};
/**
* Event generated when a mouse button has been pressed.
*/
struct mouse_button_pressed
{
/// Mouse that generated the event.
mouse* mouse;
/// Mouse position, in pixels, relative to the window, when the button was pressed.
math::vector<std::int32_t, 2> position;
/// Mouse button being pressed.
mouse_button button;
};
/**
* Event generated when a mouse button has been released.
*/
struct mouse_button_released
{
/// Mouse that generated the event.
mouse* mouse;
/// Mouse position, in pixels, relative to the window, when the button was released.
math::vector<std::int32_t, 2> position;
/// Mouse button being released.
mouse_button button;
};
/**
* Event generated when a mouse has been scrolled.
*/
struct mouse_scrolled
{
/// Mouse that generated the event.
mouse* mouse;
/// Mouse position, in pixels, relative to the window, when the mouse was scrolled.
math::vector<std::int32_t, 2> position;
/// Scroll velocity.
math::vector<float, 2> velocity;
};
/**
* Event generated when a window has been closed.
*/
struct window_closed {};
/**
* Event generated when a window has gained or lost focus.
*/
struct window_focus_changed
{
/// `true` if the window is in focus, `false` otherwise.
bool in_focus;
};
/**
* Event generated when a window has been moved.
*/
struct window_moved
{
/// Position of the window, in pixels.
math::vector<std::int32_t, 2> position;
};
/**
* Event generated when a window has been resized.
*/
struct window_resized
{
/// Window width, in pixels.
std::int32_t window_width;
/// Window height, in pixels.
std::int32_t window_height;
/// Viewport width, in pixels.
std::int32_t viewport_width;
/// Viewport height, in pixels.
std::int32_t viewport_height;
};
} // namespace event
} // namespace input
#endif // ANTKEEPER_INPUT_EVENT_HPP

+ 51
- 0
src/input/gamepad-axis.hpp View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_GAMEPAD_AXIS_HPP
#define ANTKEEPER_INPUT_GAMEPAD_AXIS_HPP
#include <cstdint>
namespace input {
/// Gamepad axes.
enum class gamepad_axis: std::uint8_t
{
/// Left stick X-axis.
left_stick_x,
/// Left stick Y-axis.
left_stick_y,
/// Right stick X-axis.
right_stick_x,
/// Right stick Y-axis.
right_stick_y,
/// Left trigger.
left_trigger,
/// Right trigger.
right_trigger
};
} // namespace input
#endif // ANTKEEPER_INPUT_GAMEPAD_AXIS_HPP

+ 78
- 0
src/input/gamepad-button.hpp View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_GAMEPAD_BUTTON_HPP
#define ANTKEEPER_INPUT_GAMEPAD_BUTTON_HPP
#include <cstdint>
namespace input {
/// Gamepad buttons.
enum class gamepad_button: std::uint8_t
{
/// A button.
a,
/// B button.
b,
/// X button.
x,
/// Y button.
y,
/// Back button.
back,
/// Guide button.
guide,
/// Start button.
start,
/// Left stick button.
left_stick,
/// Right stick button.
right_stick,
/// Left shoulder button.
left_shoulder,
/// Right shoulder button.
right_shoulder,
/// D-pad up button.
dpad_up,
/// D-pad down button.
dpad_down,
/// D-pad left button.
dpad_left,
/// D-pad right button.
dpad_right
};
} // namespace input
#endif // ANTKEEPER_INPUT_GAMEPAD_BUTTON_HPP

+ 53
- 108
src/input/gamepad.cpp View File

@ -17,17 +17,15 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gamepad.hpp"
#include "event/input-events.hpp"
#include "event/event-dispatcher.hpp"
#include "input/gamepad.hpp"
#include "math/map.hpp"
#include <algorithm>
#include <type_traits>
#include <cmath>
namespace input {
gamepad::gamepad():
connected(true),
left_deadzone_cross(true),
right_deadzone_cross(true),
left_deadzone_roundness(0.0f),
@ -35,7 +33,7 @@ gamepad::gamepad():
{
for (int i = 0; i < 6; ++i)
{
axis_values[i] = 0.0f;
axis_positions[i] = 0.0f;
axis_activation_min[i] = 0.0f;
axis_activation_max[i] = 1.0f;
axis_response_curves[i] = gamepad_response_curve::linear;
@ -44,13 +42,13 @@ gamepad::gamepad():
void gamepad::set_activation_threshold(gamepad_axis axis, float min, float max)
{
axis_activation_min[static_cast<int>(axis)] = min;
axis_activation_max[static_cast<int>(axis)] = max;
axis_activation_min[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = min;
axis_activation_max[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = max;
}
void gamepad::set_response_curve(gamepad_axis axis, gamepad_response_curve curve)
{
axis_response_curves[static_cast<int>(axis)] = curve;
axis_response_curves[static_cast<std::underlying_type_t<gamepad_axis>>(axis)] = curve;
}
void gamepad::set_left_deadzone_cross(bool cross)
@ -75,60 +73,45 @@ void gamepad::set_right_deadzone_roundness(float roundness)
void gamepad::press(gamepad_button button)
{
if (!device::event_dispatcher)
{
return;
}
gamepad_button_pressed_event event;
event.controller = this;
event.button = button;
device::event_dispatcher->queue(event);
button_pressed_publisher.publish({this, button});
}
void gamepad::release(gamepad_button button)
{
if (!device::event_dispatcher)
{
return;
}
gamepad_button_released_event event;
event.controller = this;
event.button = button;
device::event_dispatcher->queue(event);
button_released_publisher.publish({this, button});
}
void gamepad::move(gamepad_axis axis, float value)
void gamepad::move(gamepad_axis axis, float position)
{
// Update axis value
axis_values[static_cast<int>(axis)] = value;
const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
if (!device::event_dispatcher)
{
/// @TODO Support arbitrary number of gamepad axes.
if (axis_index >= 6)
return;
}
// Update axis position
axis_positions[axis_index] = position;
switch (axis)
{
case gamepad_axis::left_x:
case gamepad_axis::left_y:
case gamepad_axis::left_stick_x:
case gamepad_axis::left_stick_y:
if (left_deadzone_cross)
handle_axial_motion(axis);
else
handle_biaxial_motion(gamepad_axis::left_x, gamepad_axis::left_y);
handle_biaxial_motion(gamepad_axis::left_stick_x, gamepad_axis::left_stick_y);
break;
case gamepad_axis::right_x:
case gamepad_axis::right_y:
case gamepad_axis::right_stick_x:
case gamepad_axis::right_stick_y:
if (right_deadzone_cross)
handle_axial_motion(axis);
else
handle_biaxial_motion(gamepad_axis::right_x, gamepad_axis::right_y);
handle_biaxial_motion(gamepad_axis::right_stick_x, gamepad_axis::right_stick_y);
break;
case gamepad_axis::left_trigger:
case gamepad_axis::right_trigger:
default:
handle_axial_motion(axis);
break;
@ -137,69 +120,58 @@ void gamepad::move(gamepad_axis axis, float value)
void gamepad::handle_axial_motion(gamepad_axis axis)
{
const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
// Get axis parameters
const int axis_index = static_cast<int>(axis);
const float activation_min = axis_activation_min[axis_index];
const float activation_max = axis_activation_max[axis_index];
const float axis_value = axis_values[axis_index];
const float axis_position = axis_positions[axis_index];
const gamepad_response_curve response_curve = axis_response_curves[axis_index];
// Build event
gamepad_axis_moved_event event;
event.controller = this;
event.axis = axis;
if (std::abs(axis_value) > activation_min)
// Remap axis position
float remapped_position = 0.0f;
if (std::abs(axis_position) > activation_min)
{
// Remap response value according to activation thresholds and clamp to `[0, 1]`.
float response = math::map(std::abs(axis_value), activation_min, activation_max, 0.0f, 1.0f);
// Remap position according to activation thresholds and clamp to `[0, 1]`.
float response = math::map(std::abs(axis_position), activation_min, activation_max, 0.0f, 1.0f);
response = std::clamp(response, 0.0f, 1.0f);
// Remap response value according to axis response curve
// Remap position according to axis response curve
response = curve_response(axis, response);
// Restore sign of axis motion
response = (axis_value < 0.0f) ? -response : response;
response = (axis_position < 0.0f) ? -response : response;
event.value = response;
}
else
{
event.value = 0.0f;
remapped_position = response;
}
// Dispatch event
device::event_dispatcher->queue(event);
axis_moved_publisher.publish({this, axis, remapped_position});
}
void gamepad::handle_biaxial_motion(gamepad_axis axis_x, gamepad_axis axis_y)
{
// Get axis parameters
const int x_axis_index = static_cast<int>(axis_x);
const int y_axis_index = static_cast<int>(axis_y);
const int x_axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis_x);
const int y_axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis_y);
const float x_activation_min = axis_activation_min[x_axis_index];
const float x_activation_max = axis_activation_max[x_axis_index];
const float y_activation_min = axis_activation_min[y_axis_index];
const float y_activation_max = axis_activation_max[y_axis_index];
const float x_axis_value = axis_values[x_axis_index];
const float y_axis_value = axis_values[y_axis_index];
const float x_axis_position = axis_positions[x_axis_index];
const float y_axis_position = axis_positions[y_axis_index];
const gamepad_response_curve x_response_curve = axis_response_curves[x_axis_index];
const gamepad_response_curve y_response_curve = axis_response_curves[y_axis_index];
const float deadzone_roundness = (axis_x == gamepad_axis::left_x) ? left_deadzone_roundness : right_deadzone_roundness;
const float deadzone_roundness = (axis_x == gamepad_axis::left_stick_x) ? left_deadzone_roundness : right_deadzone_roundness;
const float radius = std::min<float>(x_activation_min, y_activation_min) * deadzone_roundness;
const float dx = std::max<float>(0.0f, std::abs(x_axis_value) - x_activation_min + radius);
const float dy = std::max<float>(0.0f, std::abs(y_axis_value) - y_activation_min + radius);
const float dx = std::max<float>(0.0f, std::abs(x_axis_position) - x_activation_min + radius);
const float dy = std::max<float>(0.0f, std::abs(y_axis_position) - y_activation_min + radius);
const float distance = std::sqrt(dx * dx + dy * dy) - radius;
// Build event
gamepad_axis_moved_event event;
event.controller = this;
if (distance > 0.0f)
{
const float nx = std::abs(x_axis_value) / distance;
const float ny = std::abs(y_axis_value) / distance;
const float nx = std::abs(x_axis_position) / distance;
const float ny = std::abs(y_axis_position) / distance;
const float ndx = (distance - x_activation_min) / (x_activation_max - x_activation_min);
const float ndy = (distance - y_activation_min) / (y_activation_max - y_activation_min);
@ -210,29 +182,23 @@ void gamepad::handle_biaxial_motion(gamepad_axis axis_x, gamepad_axis axis_y)
response_y = curve_response(axis_y, response_y);
// Restore signs of axis motions
response_x = (x_axis_value < 0.0f) ? -response_x : response_x;
response_y = (y_axis_value < 0.0f) ? -response_y : response_y;
response_x = (x_axis_position < 0.0f) ? -response_x : response_x;
response_y = (y_axis_position < 0.0f) ? -response_y : response_y;
event.value = response_x;
event.axis = axis_x;
device::event_dispatcher->queue(event);
event.value = response_y;
event.axis = axis_y;
device::event_dispatcher->queue(event);
axis_moved_publisher.publish({this, axis_x, response_x});
axis_moved_publisher.publish({this, axis_y, response_y});
}
else
{
event.value = 0.0f;
event.axis = axis_x;
device::event_dispatcher->queue(event);
event.axis = axis_y;
device::event_dispatcher->queue(event);
axis_moved_publisher.publish({this, axis_x, 0.0f});
axis_moved_publisher.publish({this, axis_y, 0.0f});
}
}
float gamepad::curve_response(gamepad_axis axis, float response) const
{
const gamepad_response_curve response_curve = axis_response_curves[static_cast<int>(axis)];
const auto axis_index = static_cast<std::underlying_type_t<gamepad_axis>>(axis);
const gamepad_response_curve response_curve = axis_response_curves[axis_index];
switch (response_curve)
{
@ -251,25 +217,4 @@ float gamepad::curve_response(gamepad_axis axis, float response) const
return response;
}
void gamepad::connect(bool reconnected)
{
connected = true;
gamepad_connected_event event;
event.controller = this;
event.reconnected = reconnected;
device::event_dispatcher->queue(event);
}
void gamepad::disconnect()
{
connected = false;
gamepad_disconnected_event event;
event.controller = this;
device::event_dispatcher->queue(event);
}
} // namespace input

+ 42
- 72
src/input/gamepad.hpp View File

@ -20,52 +20,14 @@
#ifndef ANTKEEPER_INPUT_GAMEPAD_HPP
#define ANTKEEPER_INPUT_GAMEPAD_HPP
#include "device.hpp"
#include "input/device.hpp"
#include "input/event.hpp"
#include "input/gamepad-axis.hpp"
#include "input/gamepad-button.hpp"
#include "event/publisher.hpp"
namespace input {
/// Gamepad buttons.
enum class gamepad_button
{
a,
b,
x,
y,
back,
guide,
start,
left_stick,
right_stick,
left_shoulder,
right_shoulder,
dpad_up,
dpad_down,
dpad_left,
dpad_right
};
/// Gamepad axes.
enum class gamepad_axis
{
/// Left stick x-axis.
left_x,
/// Left stick y-axis.
left_y,
/// Right stick x-axis.
right_x,
/// Right stick y-axis.
right_y,
/// Left trigger.
left_trigger,
/// Right trigger.
right_trigger,
};
/// Gamepad axis activation response curves.
enum class gamepad_response_curve
{
@ -80,17 +42,17 @@ enum class gamepad_response_curve
};
/**
* A virtual gamepad which can generate gamepad-related input events and pass them to an event dispatcher.
* A virtual gamepad which generates gamepad-related input events.
*/
class gamepad: public device
{
public:
/**
* Creates a gamepad input device.
* Constructs a gamepad input device.
*/
gamepad();
/// Destroys a gamepad input device.
/// Destructs a gamepad input device.
virtual ~gamepad() = default;
/**
@ -141,46 +103,55 @@ public:
/**
* Simulates a gamepad button press.
*
* @param button Index of the pressed button.
* @param button Button to press.
*/
void press(gamepad_button button);
/**
* Simulates a gamepad button release.
*
* @param button Index of the released button.
* @param button Button to release.
*/
void release(gamepad_button button);
/**
* Simulates a gamepad axis movement.
*
* @param axis Index of the moved axis.
* @param negative Whether the movement was negative or positive.
* @param value Normalized degree of movement.
*/
void move(gamepad_axis axis, float value);
/**
* Simulates a gamepad being connected.
*
* @param reconnected `true` if the controller is being reconnected, or `false` if the controller is being connected for the first time.
* @param axis Gamepad axis.
* @param position Position on the axis, on `[-1, 1]`.
*/
void connect(bool reconnnected);
/// Simulates a gamepad being disconnected.
void disconnect();
/// Returns `true` if the controller is currently connected.
bool is_connected() const;
void move(gamepad_axis axis, float position);
/// Returns the channel through which gamepad button pressed events are published.
[[nodiscard]] inline ::event::channel<event::gamepad_button_pressed>& get_button_pressed_channel() noexcept
{
return button_pressed_publisher.channel();
}
/// Returns the channel through which gamepad button released events are published.
[[nodiscard]] inline ::event::channel<event::gamepad_button_released>& get_button_released_channel() noexcept
{
return button_released_publisher.channel();
}
/// Returns the channel through which gamepad axis moved events are published.
[[nodiscard]] inline ::event::channel<event::gamepad_axis_moved>& get_axis_moved_channel() noexcept
{
return axis_moved_publisher.channel();
}
/// Returns device_type::gamepad.
[[nodiscard]] inline virtual constexpr device_type get_device_type() const noexcept
{
return device_type::gamepad;
}
private:
void handle_axial_motion(gamepad_axis axis);
void handle_biaxial_motion(gamepad_axis axis_x, gamepad_axis axis_y);
float curve_response(gamepad_axis axis, float response) const;
bool connected;
float axis_values[6];
float axis_positions[6];
float axis_activation_min[6];
float axis_activation_max[6];
gamepad_response_curve axis_response_curves[6];
@ -189,13 +160,12 @@ private:
bool right_deadzone_cross;
float left_deadzone_roundness;
float right_deadzone_roundness;
::event::publisher<event::gamepad_button_pressed> button_pressed_publisher;
::event::publisher<event::gamepad_button_released> button_released_publisher;
::event::publisher<event::gamepad_axis_moved> axis_moved_publisher;
};
inline bool gamepad::is_connected() const
{
return connected;
}
} // namespace input
#endif // ANTKEEPER_INPUT_GAMEPAD_HPP

+ 0
- 38
src/input/input.hpp View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_HPP
#define ANTKEEPER_INPUT_HPP
/// Input devices and events.
namespace input {}
#include "control.hpp"
#include "control-set.hpp"
#include "device.hpp"
#include "event-router.hpp"
#include "gamepad.hpp"
#include "keyboard.hpp"
#include "listener.hpp"
#include "mapper.hpp"
#include "mapping.hpp"
#include "mouse.hpp"
#include "scancode.hpp"
#endif // ANTKEEPER_INPUT_HPP

+ 6
- 315
src/input/keyboard.cpp View File

@ -17,328 +17,19 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "keyboard.hpp"
#include "scancode.hpp"
#include "event/event-dispatcher.hpp"
#include "event/input-events.hpp"
#include "input/keyboard.hpp"
#include "input/scancode.hpp"
namespace input {
const char* keyboard::get_scancode_name(scancode scancode)
void keyboard::press(scancode scancode, bool repeat, std::uint16_t modifiers)
{
return scancode_names[static_cast<std::size_t>(scancode)];
};
scancode keyboard::get_scancode_from_name(const char* name)
{
auto it = scancode_map.find(std::string(name));
if (it == scancode_map.end())
{
return scancode::unknown;
}
return it->second;
key_pressed_publisher.publish({this, scancode, repeat, modifiers});
}
keyboard::keyboard()
{}
keyboard::~keyboard()
{}
void keyboard::press(scancode scancode)
void keyboard::release(scancode scancode, bool repeat, std::uint16_t modifiers)
{
if (!device::event_dispatcher)
{
return;
}
key_pressed_event event;
event.keyboard = this;
event.scancode = scancode;
device::event_dispatcher->queue(event);
key_released_publisher.publish({this, scancode, repeat, modifiers});
}
void keyboard::release(scancode scancode)
{
if (!device::event_dispatcher)
{
return;
}
key_released_event event;
event.keyboard = this;
event.scancode = scancode;
device::event_dispatcher->queue(event);
}
std::map<std::string, scancode> keyboard::build_scancode_map()
{
std::map<std::string, scancode> scancode_map;
for (std::size_t i = 0; i <= static_cast<std::size_t>(scancode::audio_fast_forward); ++i)
{
if (scancode_names[i] != nullptr)
{
std::string scancode_name = scancode_names[i];
scancode_map[scancode_name] = static_cast<scancode>(i);
}
}
return scancode_map;
}
const char* keyboard::scancode_names[] =
{
nullptr, // UNKNOWN
"A", // A
"B", // B
"C", // C
"D", // D
"E", // E
"F", // F
"G", // G
"H", // H
"I", // I
"J", // J
"K", // K
"L", // L
"M", // M
"N", // N
"O", // O
"P", // P
"Q", // Q
"R", // R
"S", // S
"T", // T
"U", // U
"V", // V
"W", // W
"X", // X
"Y", // Y
"Z", // Z
"1", // ONE
"2", // TWO
"3", // THREE
"4", // FOUR
"5", // FIVE
"6", // SIX
"7", // SEVEN
"8", // EIGHT
"9", // NINE
"0", // ZERO
"Enter", // ENTER
"Escape", // ESCAPE
"Backspace", // BACKSPACE
"Tab", // TAB
"Space", // SPACE
"-", // MINUS
"=", // EQUALS
"[", // LEFTBRACKET
"]", // RIGHTBRACKET
"\\", // BACKSLASH
"#", // NONUSHASH
";", // SEMICOLON
"'", // APOSTROPHE
"`", // GRAVE
",", // COMMA
".", // PERIOD
"/", // SLASH
"Caps Lock", // CAPSLOCK
"F1", // F1
"F2", // F2
"F3", // F3
"F4", // F4
"F5", // F5
"F6", // F6
"F7", // F7
"F8", // F8
"F9", // F9
"F10", // F10
"F11", // F11
"F12", // F12
"Print Screen", // PRINTSCREEN
"Scroll Lock", // SCROLLLOCK
"Pause", // PAUSE
"Insert", // INSERT
"Home", // HOME
"Page Up", // PAGEUP
"Delete", // DELETE
"End", // END
"Page Down", // PAGEDOWN
"Right", // RIGHT
"Left", // LEFT
"Down", // DOWN
"Up", // UP
"Num Lock", // NUMLOCKCLEAR
"Keypad /", // KP_DIVIDE
"Keypad *", // KP_MULTIPLY
"Keypad -", // KP_MINUS
"Keypad +", // KP_PLUS
"Keypad Enter", // KP_ENTER
"Keypad 1", // KP_1
"Keypad 2", // KP_2
"Keypad 3", // KP_3
"Keypad 4", // KP_4
"Keypad 5", // KP_5
"Keypad 6", // KP_6
"Keypad 7", // KP_7
"Keypad 8", // KP_8
"Keypad 9", // KP_9
"Keypad 0", // KP_0
"Keypad .", // KP_PERIOD
nullptr, // NONUSBACKSLASH
"Application", // APPLICATION
"Power", // POWER
"Keypad =", // KP_EQUALS
"F13", // F13
"F14", // F14
"F15", // F15
"F16", // F16
"F17", // F17
"F18", // F18
"F19", // F19
"F20", // F20
"F21", // F21
"F22", // F22
"F23", // F23
"F24", // F24
"Execute", // EXECUTE
"Help", // HELP
"Menu", // MENU
"Select", // SELECT
"Stop", // STOP
"Again", // AGAIN
"Undo", // UNDO
"Cut", // CUT
"Copy", // COPY
"Paste", // PASTE
"Find", // FIND
"Mute", // MUTE
"Volume Up", // VOLUMEUP
"Volume Down", // VOLUMEDOWN
nullptr, // LOCKINGCAPSLOCK
nullptr, // LOCKINGNUMLOCK
nullptr, // LOCKINGSCROLLLOCK
"Keypad ,", // KP_COMMA
"Keypad = (AS400)", // KP_EQUALSAS400
nullptr, // INTERNATIONAL1
nullptr, // INTERNATIONAL2
nullptr, // INTERNATIONAL3
nullptr, // INTERNATIONAL4
nullptr, // INTERNATIONAL5
nullptr, // INTERNATIONAL6
nullptr, // INTERNATIONAL7
nullptr, // INTERNATIONAL8
nullptr, // INTERNATIONAL9
nullptr, // LANG1
nullptr, // LANG2
nullptr, // LANG3
nullptr, // LANG4
nullptr, // LANG5
nullptr, // LANG6
nullptr, // LANG7
nullptr, // LANG8
nullptr, // LANG9
"Alt Erase", // ALTERASE
"Sys Req", // SYSREQ
"Cancel", // CANCEL
"Clear", // CLEAR
"Prior", // PRIOR
"Return", // RETURN2
"Separator", // SEPARATOR
"Out", // OUT
"Oper", // OPER
"Clear/Again", // CLEARAGAIN
"CrSel", // CRSEL
"ExSel", // EXSEL
"Keypad 00", // KP_00
"Keypad 000", // KP_000
"Thousands Separator", // THOUSANDSSEPARATOR
"Decimal Separator", // DECIMALSEPARATOR
"Currency Unit", // CURRENCYUNIT
"Currency Sub-Unit", // CURRENCYSUBUNIT
"Keypad (", // KP_LEFTPAREN
"Keypad )", // KP_RIGHTPAREN
"Keypad {", // KP_LEFTBRACE
"Keypad }", // KP_RIGHTBRACE
"Keypad Tab", // KP_TAB
"Keypad Backspace", // KP_BACKSPACE
"Keypad A", // KP_A
"Keypad B", // KP_B
"Keypad C", // KP_C
"Keypad D", // KP_D
"Keypad E", // KP_E
"Keypad F", // KP_F
"Keypad XOR", // KP_XOR
"Keypad ^", // KP_POWER
"Keypad %", // KP_PERCENT
"Keypad <", // KP_LESS
"Keypad >", // KP_GREATER
"Keypad &", // KP_AMPERSAND
"Keypad &&", // KP_DBLAMPERSAND
"Keypad |", // KP_VERTICALBAR
"Keypad ||", // KP_DBLVERTICALBAR
"Keypad :", // KP_COLON
"Keypad #", // KP_HASH
"Keypad Space", // KP_SPACE
"Keypad @", // KP_AT
"Keypad !", // KP_EXCLAM
"Keypad Mem Store", // KP_MEMSTORE
"Keypad Mem Recall", // KP_MEMRECALL
"Keypad Mem Clear", // KP_MEMCLEAR
"Keypad Mem Add", // KP_MEMADD
"Keypad Mem Subtract", // KP_MEMSUBTRACT
"Keypad Mem Multiply", // KP_MEMMULTIPLY
"Keypad Mem Divide", // KP_MEMDIVIDE
"Keypad +/-", // KP_PLUSMINUS
"Keypad Clear", // KP_CLEAR
"Keypad Clear Entry", // KP_CLEARENTRY
"Keypad Binary", // KP_BINARY
"Keypad Octal", // KP_OCTAL
"Keypad Decimal", // KP_DECIMAL
"Keypad Hexadecimal", // KP_HEXADECIMAL
"Left Ctrl", // LCTRL
"Left Shift", // LSHIFT
"Left Alt", // LALT
"Left GUI", // LGUI
"Right Ctrl", // RCTRL
"Right Shift", // RSHIFT
"Right Alt", // RALT
"Right GUI", // RGUI
"Mode Switch", // MODE
"Audio Next", // AUDIONEXT
"Audio Prev", // AUDIOPREV
"Audio Stop", // AUDIOSTOP
"Audio Play", // AUDIOPLAY
"Audio Mute", // AUDIOMUTE
"Media Select", // MEDIASELECT
"WWW", // WWW
"Mail", // MAIL
"Calculator", // CALCULATOR
"Computer", // COMPUTER
"AC Search", // AC_SEARCH
"AC Home", // AC_HOME
"AC Back", // AC_BACK
"AC Forward", // AC_FORWARD
"AC Stop", // AC_STOP
"AC Refresh", // AC_REFRESH
"AC Bookmarks", // AC_BOOKMARKS
"Brightness Down", // BRIGHTNESSDOWN
"Brightness Up", // BRIGHTNESSUP
"Display Switch", // DISPLAYSWITCH
"KBD Illum Toggle", // KBDILLUMTOGGLE
"KBD Illum Down", // KBDILLUMDOWN
"KBD Illum Up", // KBDILLUMUP
"Eject", // EJECT
"Sleep", // SLEEP
"App 1", // APP1
"App 2", // APP2
"Audio Rewind", // AUDIOREWIND
"Audio Fast-Forward", // AUDIOFASTFORWARD
};
std::map<std::string, scancode> keyboard::scancode_map = keyboard::build_scancode_map();
} // namespace input

+ 40
- 33
src/input/keyboard.hpp View File

@ -20,62 +20,69 @@
#ifndef ANTKEEPER_INPUT_KEYBOARD_HPP
#define ANTKEEPER_INPUT_KEYBOARD_HPP
#include "device.hpp"
#include <map>
#include <string>
#include "input/device.hpp"
#include "input/event.hpp"
#include "input/scancode.hpp"
#include "input/modifier-key.hpp"
#include "event/publisher.hpp"
namespace input {
enum class scancode;
/**
* A virtual keyboard which can generate keyboard-related input events and pass them to an event dispatcher.
* A virtual keyboard which generates keyboard-related input events.
*/
class keyboard: public device
{
public:
/**
* Returns the UTF-8 encoded name of a scancode.
*/
static const char* get_scancode_name(scancode scancode);
/**
* Returns the scancode corresponding to a scancode name.
*
* @param name Name of a scancode.
* @return Corresponding scancode, or nullptr if a matching scancode was not found.
*/
static scancode get_scancode_from_name(const char* name);
/**
* Creates a keyboard input device.
* Constructs a keyboard input device.
*/
keyboard();
/// Destroys a keyboard input device.
virtual ~keyboard();
keyboard() = default;
/// Destructs a keyboard input device.
virtual ~keyboard() = default;
/**
* Simulates a key press.
*
* @param scancode scancode of the simulated key press.
* @param scancode Scancode of the key to press.
* @param repeat `true` if the key press is from a key repeat, `false` otherwise.
* @param modifiers Bit mask containing the active modifier keys.
*/
void press(scancode scancode);
void press(scancode scancode, bool repeat = false, std::uint16_t modifiers = modifier_key::none);
/**
* Simulates a key release.
*
* @param scancode scancode of the simulated key release.
* @param scancode Scancode of the key to release.
* @param repeat `true` if the key release is from a key repeat, `false` otherwise.
* @param modifiers Bit mask containing the active modifier keys.
*/
void release(scancode scancode);
void release(scancode scancode, bool repeat = false, std::uint16_t modifiers = modifier_key::none);
/// Returns the channel through which key pressed events are published.
[[nodiscard]] inline ::event::channel<event::key_pressed>& get_key_pressed_channel() noexcept
{
return key_pressed_publisher.channel();
}
/// Returns the channel through which key released events are published.
[[nodiscard]] inline ::event::channel<event::key_released>& get_key_released_channel() noexcept
{
return key_released_publisher.channel();
}
/// Returns device_type::keyboard.
[[nodiscard]] inline virtual constexpr device_type get_device_type() const noexcept
{
return device_type::keyboard;
}
private:
static std::map<std::string, scancode> build_scancode_map();
static const char* scancode_names[];
static std::map<std::string, scancode> scancode_map;
::event::publisher<event::key_pressed> key_pressed_publisher;
::event::publisher<event::key_released> key_released_publisher;
};
} // namespace input
#endif // ANTKEEPER_INPUT_KEYBOARD_HPP

+ 0
- 111
src/input/listener.cpp View File

@ -1,111 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "listener.hpp"
#include "event/event-dispatcher.hpp"
#include <iostream>
namespace input {
listener::listener():
event_dispatcher(nullptr),
callback(nullptr),
enabled(false)
{}
listener::~listener()
{
set_event_dispatcher(nullptr);
}
void listener::set_event_dispatcher(::event_dispatcher* event_dispatcher)
{
if (event_dispatcher != this->event_dispatcher)
{
if (this->event_dispatcher)
{
this->event_dispatcher->unsubscribe<key_pressed_event>(this);
this->event_dispatcher->unsubscribe<mouse_moved_event>(this);
this->event_dispatcher->unsubscribe<mouse_wheel_scrolled_event>(this);
this->event_dispatcher->unsubscribe<mouse_button_pressed_event>(this);
this->event_dispatcher->unsubscribe<gamepad_axis_moved_event>(this);
this->event_dispatcher->unsubscribe<gamepad_button_pressed_event>(this);
}
if (event_dispatcher)
{
event_dispatcher->subscribe<key_pressed_event>(this);
event_dispatcher->subscribe<mouse_moved_event>(this);
event_dispatcher->subscribe<mouse_wheel_scrolled_event>(this);
event_dispatcher->subscribe<mouse_button_pressed_event>(this);
event_dispatcher->subscribe<gamepad_axis_moved_event>(this);
event_dispatcher->subscribe<gamepad_button_pressed_event>(this);
}
this->event_dispatcher = event_dispatcher;
}
}
void listener::set_callback(std::function<void(const event_base&)> callback)
{
this->callback = callback;
}
void listener::set_enabled(bool enabled)
{
this->enabled = enabled;
}
void listener::handle_event(const key_pressed_event& event)
{
if (enabled && callback)
callback(event);
}
void listener::handle_event(const mouse_moved_event& event)
{
if (enabled && callback)
callback(event);
}
void listener::handle_event(const mouse_button_pressed_event& event)
{
if (enabled && callback)
callback(event);
}
void listener::handle_event(const mouse_wheel_scrolled_event& event)
{
if (enabled && callback)
callback(event);
}
void listener::handle_event(const gamepad_button_pressed_event& event)
{
if (enabled && callback)
callback(event);
}
void listener::handle_event(const gamepad_axis_moved_event& event)
{
if (enabled && callback)
callback(event);
}
} // namespace input

+ 0
- 97
src/input/listener.hpp View File

@ -1,97 +0,0 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_LISTENER_HPP
#define ANTKEEPER_INPUT_LISTENER_HPP
#include "event/input-events.hpp"
#include "event/event-handler.hpp"
#include "event/event-dispatcher.hpp"
#include <functional>
namespace input {
/**
* Listens for all types of input events.
*/
class listener:
public event_handler<key_pressed_event>,
public event_handler<mouse_moved_event>,
public event_handler<mouse_wheel_scrolled_event>,
public event_handler<mouse_button_pressed_event>,
public event_handler<gamepad_axis_moved_event>,
public event_handler<gamepad_button_pressed_event>
{
public:
/**
* Creates an input listener.
*/
listener();
/**
* Destroys an input listener.
*/
virtual ~listener();
/**
* Sets the event dispatcher to which this input event router will subscribe itself.
*/
void set_event_dispatcher(event_dispatcher* event_dispatcher);
/**
* Sets the input event callback function.
*
* @param callback Callback function which operates on an input event.
*/
void set_callback(std::function<void(const event_base&)> event);
/**
* Enables or disables the input listening.
*
* @param enabled Whether to enable input listening or not.
*/
void set_enabled(bool enabled);
/**
* Returns true if input listening is enabled.
*/
bool is_enabled() const;
private:
void handle_event(const key_pressed_event& event);
void handle_event(const mouse_moved_event& event);
void handle_event(const mouse_wheel_scrolled_event& event);
void handle_event(const mouse_button_pressed_event& event);
void handle_event(const gamepad_axis_moved_event& event);
void handle_event(const gamepad_button_pressed_event& event);
event_dispatcher* event_dispatcher;
std::function<void(const event_base&)> callback;
bool enabled;
};
inline bool listener::is_enabled() const
{
return enabled;
}
} // namespace input
#endif // ANTKEEPER_INPUT_LISTENER_HPP

+ 31
- 103
src/input/mapper.cpp View File

@ -17,142 +17,70 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mapper.hpp"
#include "mouse.hpp"
#include "event/event-dispatcher.hpp"
#include "input/mapper.hpp"
#include <cmath>
namespace input {
mapper::mapper():
event_dispatcher(nullptr),
control(nullptr),
callback(nullptr),
enabled(false)
{}
mapper::~mapper()
{
set_event_dispatcher(nullptr);
}
void mapper::set_event_dispatcher(::event_dispatcher* event_dispatcher)
{
if (this->event_dispatcher)
{
this->event_dispatcher->unsubscribe<key_pressed_event>(this);
this->event_dispatcher->unsubscribe<mouse_moved_event>(this);
this->event_dispatcher->unsubscribe<mouse_wheel_scrolled_event>(this);
this->event_dispatcher->unsubscribe<mouse_button_pressed_event>(this);
this->event_dispatcher->unsubscribe<gamepad_axis_moved_event>(this);
this->event_dispatcher->unsubscribe<gamepad_button_pressed_event>(this);
}
this->event_dispatcher = event_dispatcher;
if (event_dispatcher)
{
event_dispatcher->subscribe<key_pressed_event>(this);
event_dispatcher->subscribe<mouse_moved_event>(this);
event_dispatcher->subscribe<mouse_wheel_scrolled_event>(this);
event_dispatcher->subscribe<mouse_button_pressed_event>(this);
event_dispatcher->subscribe<gamepad_axis_moved_event>(this);
event_dispatcher->subscribe<gamepad_button_pressed_event>(this);
}
}
void mapper::set_control(input::control* control)
void mapper::connect(::event::queue& queue)
{
this->control = control;
subscriptions.emplace_back(queue.subscribe<event::gamepad_axis_moved>(std::bind_front(&mapper::handle_gamepad_axis_moved, this)));
subscriptions.emplace_back(queue.subscribe<event::gamepad_button_pressed>(std::bind_front(&mapper::handle_gamepad_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::key_pressed>(std::bind_front(&mapper::handle_key_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_button_pressed>(std::bind_front(&mapper::handle_mouse_button_pressed, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_moved>(std::bind_front(&mapper::handle_mouse_moved, this)));
subscriptions.emplace_back(queue.subscribe<event::mouse_scrolled>(std::bind_front(&mapper::handle_mouse_scrolled, this)));
}
void mapper::set_callback(std::function<void(const mapping&)> callback)
void mapper::disconnect()
{
this->callback = callback;
subscriptions.clear();
}
void mapper::set_enabled(bool enabled)
void mapper::handle_gamepad_axis_moved(const event::gamepad_axis_moved& event)
{
this->enabled = enabled;
input_mapped_publisher.publish({std::shared_ptr<mapping>(new gamepad_axis_mapping(event.gamepad, event.axis, std::signbit(event.position)))});
}
void mapper::handle_event(const key_pressed_event& event)
void mapper::handle_gamepad_button_pressed(const event::gamepad_button_pressed& event)
{
if (!is_enabled() || !callback)
{
return;
}
callback(key_mapping(control, event.keyboard, event.scancode));
input_mapped_publisher.publish({std::shared_ptr<mapping>(new gamepad_button_mapping(event.gamepad, event.button))});
}
void mapper::handle_event(const mouse_moved_event& event)
void mapper::handle_key_pressed(const event::key_pressed& event)
{
if (!is_enabled() || !callback)
{
return;
}
if (event.dx != 0)
{
mouse_motion_axis axis = (event.dx < 0) ? mouse_motion_axis::negative_x : mouse_motion_axis::positive_x;
callback(mouse_motion_mapping(control, event.mouse, axis));
}
if (event.dy != 0)
{
mouse_motion_axis axis = (event.dy < 0) ? mouse_motion_axis::negative_y : mouse_motion_axis::positive_y;
callback(mouse_motion_mapping(control, event.mouse, axis));
}
input_mapped_publisher.publish({std::shared_ptr<mapping>(new key_mapping(event.keyboard, event.scancode))});
}
void mapper::handle_event(const mouse_button_pressed_event& event)
void mapper::handle_mouse_button_pressed(const event::mouse_button_pressed& event)
{
if (!is_enabled() || !callback)
{
return;
}
callback(mouse_button_mapping(control, event.mouse, event.button));
input_mapped_publisher.publish({std::shared_ptr<mapping>(new mouse_button_mapping(event.mouse, event.button))});
}
void mapper::handle_event(const mouse_wheel_scrolled_event& event)
void mapper::handle_mouse_moved(const event::mouse_moved& event)
{
if (!is_enabled() || !callback)
if (event.difference.x())
{
return;
input_mapped_publisher.publish({std::shared_ptr<mapping>(new mouse_motion_mapping(event.mouse, mouse_motion_axis::x, std::signbit(static_cast<float>(event.difference.x()))))});
}
if (event.x != 0)
if (event.difference.y())
{
mouse_wheel_axis axis = (event.x < 0) ? mouse_wheel_axis::negative_x : mouse_wheel_axis::positive_x;
callback(mouse_wheel_mapping(control, event.mouse, axis));
}
if (event.y != 0)
{
mouse_wheel_axis axis = (event.y < 0) ? mouse_wheel_axis::negative_y : mouse_wheel_axis::positive_y;
callback(mouse_wheel_mapping(control, event.mouse, axis));
input_mapped_publisher.publish({std::shared_ptr<mapping>(new mouse_motion_mapping(event.mouse, mouse_motion_axis::y, std::signbit(static_cast<float>(event.difference.y()))))});
}
}
void mapper::handle_event(const gamepad_button_pressed_event& event)
void mapper::handle_mouse_scrolled(const event::mouse_scrolled& event)
{
if (!is_enabled() || !callback)
if (event.velocity.x())
{
return;
input_mapped_publisher.publish({std::shared_ptr<mapping>(new mouse_scroll_mapping(event.mouse, mouse_scroll_axis::x, std::signbit(event.velocity.x())))});
}
callback(gamepad_button_mapping(control, event.controller, event.button));
}
void mapper::handle_event(const gamepad_axis_moved_event& event)
{
if (!is_enabled() || !callback)
if (event.velocity.y())
{
return;
input_mapped_publisher.publish({std::shared_ptr<mapping>(new mouse_scroll_mapping(event.mouse, mouse_scroll_axis::y, std::signbit(event.velocity.y())))});
}
callback(gamepad_axis_mapping(control, event.controller, event.axis, (event.value < 0.0f)));
}
} // namespace input

+ 29
- 64
src/input/mapper.hpp View File

@ -20,87 +20,52 @@
#ifndef ANTKEEPER_INPUT_MAPPER_HPP
#define ANTKEEPER_INPUT_MAPPER_HPP
#include "event/subscription.hpp"
#include "event/queue.hpp"
#include "event/publisher.hpp"
#include "input/event.hpp"
#include "input/mapping.hpp"
#include "event/input-events.hpp"
#include "event/event-handler.hpp"
#include "event/event-dispatcher.hpp"
#include <functional>
#include <memory>
#include <vector>
namespace input {
/**
* An input mapper takes a control and listens to input events then generates corresponding input mappings which can be added to an input router.
* Listens for input events and generates corresponding input mappings.
*/
class mapper:
public event_handler<key_pressed_event>,
public event_handler<mouse_moved_event>,
public event_handler<mouse_wheel_scrolled_event>,
public event_handler<mouse_button_pressed_event>,
public event_handler<gamepad_axis_moved_event>,
public event_handler<gamepad_button_pressed_event>
class mapper
{
public:
/**
* Creates an input mapper.
*/
mapper();
/**
* Destroys an input mapper.
*/
virtual ~mapper();
/**
* Sets the event dispatcher to which this input event router will subscribe itself.
*/
void set_event_dispatcher(event_dispatcher* event_dispatcher);
/**
* Sets the control for which input mappings will be generated.
*
* @param control ::control for which input mappings will be generated.
*/
void set_control(input::control* control);
/**
* Sets the callback function to the input mappings generated by this input mapper.
* Connects the input event signals of an event queue to the mapper.
*
* @param callback Callback function operates on an input mapping.
* @param queue Event queue to connect.
*/
void set_callback(std::function<void(const mapping&)> callback);
/**
* Enables or disables the input mapping generation.
*
* @param enabled Whether to enable input mapping generation or not.
*/
void set_enabled(bool enabled);
void connect(::event::queue& queue);
/**
* Returns true if input mapping generation is enabled.
* Disconnects all input event signals from the mapper.
*/
bool is_enabled() const;
void disconnect();
/// Returns the channel through which input mapped events are published.
[[nodiscard]] inline ::event::channel<event::input_mapped>& get_input_mapped_channel() noexcept
{
return input_mapped_publisher.channel();
}
private:
void handle_event(const key_pressed_event& event);
void handle_event(const mouse_moved_event& event);
void handle_event(const mouse_wheel_scrolled_event& event);
void handle_event(const mouse_button_pressed_event& event);
void handle_event(const gamepad_axis_moved_event& event);
void handle_event(const gamepad_button_pressed_event& event);
event_dispatcher* event_dispatcher;
control* control;
std::function<void(const mapping&)> callback;
bool enabled;
void handle_gamepad_axis_moved(const event::gamepad_axis_moved& event);
void handle_gamepad_button_pressed(const event::gamepad_button_pressed& event);
void handle_key_pressed(const event::key_pressed& event);
void handle_mouse_button_pressed(const event::mouse_button_pressed& event);
void handle_mouse_moved(const event::mouse_moved& event);
void handle_mouse_scrolled(const event::mouse_scrolled& event);
std::vector<std::shared_ptr<::event::subscription>> subscriptions;
::event::publisher<event::input_mapped> input_mapped_publisher;
};
inline bool mapper::is_enabled() const
{
return enabled;
}
} // namespace input
#endif // ANTKEEPER_INPUT_MAPPER_HPP

src/input/control-set.cpp → src/input/mapping-type.hpp View File

@ -17,40 +17,37 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "control-set.hpp"
#include "control.hpp"
#ifndef ANTKEEPER_INPUT_MAPPING_TYPE_HPP
#define ANTKEEPER_INPUT_MAPPING_TYPE_HPP
namespace input {
void control_set::add_control(control* control)
{
controls.push_back(control);
}
void control_set::remove_control(control* control)
{
controls.remove(control);
}
void control_set::remove_controls()
{
controls.clear();
}
void control_set::update()
{
for (control* control: controls)
{
control->update();
}
}
void control_set::set_callbacks_enabled(bool enabled)
/**
* Input mapping types.
*
* @see input::mapping
*/
enum class mapping_type
{
for (control* control: controls)
{
control->set_callbacks_enabled(enabled);
}
}
/// Gamepad axis mapping.
gamepad_axis,
/// Gamepad button mapping.
gamepad_button,
/// Key mapping.
key,
/// Mouse button mapping.
mouse_button,
/// Mouse motion mapping.
mouse_motion,
/// Mouse scroll mapping.
mouse_scroll
};
} // namespace input
#endif // ANTKEEPER_INPUT_MAPPING_TYPE_HPP

+ 19
- 105
src/input/mapping.cpp View File

@ -17,128 +17,42 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mapping.hpp"
#include "input/mapping.hpp"
namespace input {
mapping::mapping(input::control* control):
control(control)
gamepad_axis_mapping::gamepad_axis_mapping(input::gamepad* gamepad, gamepad_axis axis, bool direction):
gamepad(gamepad),
axis(axis),
direction(direction)
{}
key_mapping::key_mapping(const key_mapping& mapping)
{
*this = mapping;
}
gamepad_button_mapping::gamepad_button_mapping(input::gamepad* gamepad, gamepad_button button):
gamepad(gamepad),
button(button)
{}
key_mapping::key_mapping(input::control* control, input::keyboard* keyboard, input::scancode scancode):
mapping(control),
key_mapping::key_mapping(input::keyboard* keyboard, input::scancode scancode, bool repeat):
keyboard(keyboard),
scancode(scancode)
scancode(scancode),
repeat(repeat)
{}
key_mapping& key_mapping::operator=(const key_mapping& mapping)
{
control = mapping.control;
keyboard = mapping.keyboard;
scancode = mapping.scancode;
return *this;
}
mouse_motion_mapping::mouse_motion_mapping(const mouse_motion_mapping& mapping)
{
*this = mapping;
}
mouse_motion_mapping::mouse_motion_mapping(input::control* control, input::mouse* mouse, mouse_motion_axis axis):
mapping(control),
mouse_button_mapping::mouse_button_mapping(input::mouse* mouse, mouse_button button):
mouse(mouse),
axis(axis)
button(button)
{}
mouse_motion_mapping& mouse_motion_mapping::operator=(const mouse_motion_mapping& mapping)
{
control = mapping.control;
mouse = mapping.mouse;
axis = mapping.axis;
return *this;
}
mouse_wheel_mapping::mouse_wheel_mapping(const mouse_wheel_mapping& mapping)
{
*this = mapping;
}
mouse_wheel_mapping::mouse_wheel_mapping(input::control* control, input::mouse* mouse, mouse_wheel_axis axis):
mapping(control),
mouse_motion_mapping::mouse_motion_mapping(input::mouse* mouse, mouse_motion_axis axis, bool direction):
mouse(mouse),
axis(axis)
axis(axis),
direction(direction)
{}
mouse_wheel_mapping& mouse_wheel_mapping::operator=(const mouse_wheel_mapping& mapping)
{
control = mapping.control;
mouse = mapping.mouse;
axis = mapping.axis;
return *this;
}
mouse_button_mapping::mouse_button_mapping(const mouse_button_mapping& mapping)
{
*this = mapping;
}
mouse_button_mapping::mouse_button_mapping(input::control* control, input::mouse* mouse, int button):
mapping(control),
mouse_scroll_mapping::mouse_scroll_mapping(input::mouse* mouse, mouse_scroll_axis axis, bool direction):
mouse(mouse),
button(button)
{}
mouse_button_mapping& mouse_button_mapping::operator=(const mouse_button_mapping& mapping)
{
control = mapping.control;
mouse = mapping.mouse;
button = mapping.button;
return *this;
}
gamepad_axis_mapping::gamepad_axis_mapping(const gamepad_axis_mapping& mapping)
{
*this = mapping;
}
gamepad_axis_mapping::gamepad_axis_mapping(input::control* control, gamepad* controller, gamepad_axis axis, bool negative):
mapping(control),
controller(controller),
axis(axis),
negative(negative)
direction(direction)
{}
gamepad_axis_mapping& gamepad_axis_mapping::operator=(const gamepad_axis_mapping& mapping)
{
control = mapping.control;
controller = mapping.controller;
axis = mapping.axis;
negative = mapping.negative;
return *this;
}
gamepad_button_mapping::gamepad_button_mapping(const gamepad_button_mapping& mapping)
{
*this = mapping;
}
gamepad_button_mapping::gamepad_button_mapping(input::control* control, gamepad* controller, gamepad_button button):
mapping(control),
controller(controller),
button(button)
{}
gamepad_button_mapping& gamepad_button_mapping::operator=(const gamepad_button_mapping& mapping)
{
control = mapping.control;
controller = mapping.controller;
button = mapping.button;
return *this;
}
} // namespace input

+ 193
- 119
src/input/mapping.hpp View File

@ -20,30 +20,21 @@
#ifndef ANTKEEPER_INPUT_MAPPING_HPP
#define ANTKEEPER_INPUT_MAPPING_HPP
#include "input/mapping-type.hpp"
#include "input/gamepad-axis.hpp"
#include "input/gamepad-button.hpp"
#include "input/mouse-button.hpp"
#include "input/mouse-motion-axis.hpp"
#include "input/mouse-scroll-axis.hpp"
#include "input/scancode.hpp"
#include <cstdint>
namespace input {
enum class mouse_motion_axis;
enum class mouse_wheel_axis;
enum class scancode;
enum class gamepad_axis;
enum class gamepad_button;
class control;
class gamepad;
class keyboard;
class mouse;
class gamepad;
/**
* Enumerates the supported types of control mappings.
*/
enum class mapping_type
{
key,
mouse_motion,
mouse_wheel,
mouse_button,
gamepad_axis,
gamepad_button
};
/**
* Abstract base class for input mappings.
@ -51,150 +42,233 @@ enum class mapping_type
class mapping
{
public:
/**
* Constructs an input mapping.
*/
mapping() = default;
mapping(input::control* control);
/// Destructs an input mapping.
virtual ~mapping() = default;
/// Returns this control mapping's type.
virtual mapping_type get_type() const = 0;
control* control;
/// Returns the input mapping type.
[[nodiscard]] virtual constexpr mapping_type get_mapping_type() const noexcept = 0;
};
/**
* A mapping between a control and a keyboard key.
* Maps a direction along a gamepad axis to a control input value.
*/
class key_mapping: public mapping
class gamepad_axis_mapping: public mapping
{
public:
key_mapping() = default;
key_mapping(const key_mapping& mapping);
key_mapping(input::control* control, input::keyboard* keyboard, scancode scancode);
virtual ~key_mapping() = default;
key_mapping& operator=(const key_mapping& mapping);
virtual mapping_type get_type() const;
input::keyboard* keyboard;
scancode scancode;
/**
* Constructs a gamepad axis mapping.
*
* @param gamepad Pointer to the gamepad to map, or `nullptr` if input from any gamepad will be mapped.
* @param axis Gamepad axis to map.
* @param direction Sign bit of the direction to map.
*/
/// @{
gamepad_axis_mapping(input::gamepad* gamepad, gamepad_axis axis, bool direction);
gamepad_axis_mapping() = default;
/// @}
/// Destructs a gamepad axis mapping.
virtual ~gamepad_axis_mapping() = default;
/// Returns mapping_type::gamepad_axis.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::gamepad_axis;
}
/// Pointer to the mapped gamepad, or `nullptr` if input from any gamepad is accepted.
input::gamepad* gamepad;
/// Mapped gamepad axis.
gamepad_axis axis;
/// Sign bit of the mapped direction.
bool direction;
};
inline mapping_type key_mapping::get_type() const
{
return mapping_type::key;
}
/**
* A mapping between a control and a mouse motion axis.
* Maps a gamepad button to a control input value.
*/
class mouse_motion_mapping: public mapping
class gamepad_button_mapping: public mapping
{
public:
mouse_motion_mapping() = default;
mouse_motion_mapping(const mouse_motion_mapping& mapping);
mouse_motion_mapping(input::control* control, input::mouse* mouse, mouse_motion_axis axis);
virtual ~mouse_motion_mapping() = default;
mouse_motion_mapping& operator=(const mouse_motion_mapping& mapping);
virtual mapping_type get_type() const;
input::mouse* mouse;
mouse_motion_axis axis;
/**
* Constructs a gamepad button mapping.
*
* @param gamepad Pointer to the gamepad to map, or `nullptr` if input from any gamepad will be mapped.
* @param button Gamepad button to map.
*/
/// @{
gamepad_button_mapping(input::gamepad* gamepad, gamepad_button button);
gamepad_button_mapping() = default;
/// @}
/// Destructs a gamepad button mapping.
virtual ~gamepad_button_mapping() = default;
/// Returns mapping_type::gamepad_button.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::gamepad_button;
}
/// Pointer to the mapped gamepad, or `nullptr` if input from any gamepad is accepted.
input::gamepad* gamepad;
/// Mapped gamepad button.
gamepad_button button;
};
inline mapping_type mouse_motion_mapping::get_type() const
{
return mapping_type::mouse_motion;
}
/**
* A mapping between a control and a mouse wheel axis.
* Maps a keyboard key to a control input value.
*/
class mouse_wheel_mapping: public mapping
class key_mapping: public mapping
{
public:
mouse_wheel_mapping() = default;
mouse_wheel_mapping(const mouse_wheel_mapping& mapping);
mouse_wheel_mapping(input::control* control, input::mouse* mouse, mouse_wheel_axis axis);
virtual ~mouse_wheel_mapping() = default;
mouse_wheel_mapping& operator=(const mouse_wheel_mapping& mapping);
virtual mapping_type get_type() const;
input::mouse* mouse;
mouse_wheel_axis axis;
/**
* Constructs a key mapping.
*
* @param keyboard Pointer to the keyboard to map, or `nullptr` if input from any keyboard will be mapped.
* @param scancode Scancode of the key to map.
* @param repeat `false` if the mapping should ignore key repeats, `true` otherwise.
*/
/// @{
key_mapping(input::keyboard* keyboard, input::scancode scancode, bool repeat = false);
key_mapping() = default;
/// @}
/// Destructs a keyboard key mapping.
virtual ~key_mapping() = default;
/// Returns mapping_type::key.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::key;
}
/// Pointer to the mapped keyboard, or `nullptr` if input from any keyboard is accepted.
input::keyboard* keyboard;
/// Scancode of the mapped key.
scancode scancode;
/// `false` if the mapping ignores key repeats, `true` otherwise.
bool repeat;
};
inline mapping_type mouse_wheel_mapping::get_type() const
{
return mapping_type::mouse_wheel;
}
/**
* A mapping between a control and a mouse button.
* Maps a mouse button to a control input value.
*/
class mouse_button_mapping: public mapping
{
public:
/**
* Constructs a mouse button mapping.
*
* @param mouse Pointer to the mouse to map, or `nullptr` if input from any mouse will be mapped.
* @param button Mouse button to map.
*/
/// @{
mouse_button_mapping(input::mouse* mouse, mouse_button button);
mouse_button_mapping() = default;
mouse_button_mapping(const mouse_button_mapping& mapping);
mouse_button_mapping(input::control* control, input::mouse* mouse, int button);
/// @}
/// Destructs a mouse button mapping.
virtual ~mouse_button_mapping() = default;
mouse_button_mapping& operator=(const mouse_button_mapping& mapping);
virtual mapping_type get_type() const;
/// Returns mapping_type::mouse_button.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::mouse_button;
}
/// Pointer to the mapped mouse, or `nullptr` if input from any mouse is accepted.
input::mouse* mouse;
int button;
/// Mapped mouse button.
mouse_button button;
};
inline mapping_type mouse_button_mapping::get_type() const
{
return mapping_type::mouse_button;
}
/**
* A mapping between a control and a gamepad axis.
* Maps a direction along a mouse motion axis to a control input value.
*/
class gamepad_axis_mapping: public mapping
class mouse_motion_mapping: public mapping
{
public:
gamepad_axis_mapping() = default;
gamepad_axis_mapping(const gamepad_axis_mapping& mapping);
gamepad_axis_mapping(input::control* control, gamepad* controller, gamepad_axis axis, bool negative);
virtual ~gamepad_axis_mapping() = default;
gamepad_axis_mapping& operator=(const gamepad_axis_mapping& mapping);
virtual mapping_type get_type() const;
gamepad* controller;
gamepad_axis axis;
bool negative;
/**
* Constructs a mouse motion mapping.
*
* @param mouse Pointer to the mouse to map, or `nullptr` if input from any mouse will be mapped.
* @param axis Mouse motion axis to map.
* @param direction Sign bit of the direction to map.
*/
/// @{
mouse_motion_mapping(input::mouse* mouse, mouse_motion_axis axis, bool direction);
mouse_motion_mapping() = default;
/// @}
/// Destructs a mouse motion mapping.
virtual ~mouse_motion_mapping() = default;
/// Returns mapping_type::mouse_motion.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::mouse_motion;
}
/// Pointer to the mapped mouse, or `nullptr` if input from any mouse is accepted.
input::mouse* mouse;
/// Mapped mouse motion axis.
mouse_motion_axis axis;
/// Sign bit of the mapped direction.
bool direction;
};
inline mapping_type gamepad_axis_mapping::get_type() const
{
return mapping_type::gamepad_axis;
}
/**
* A mapping between a control and a gamepad button.
* Maps a direction along a mouse scroll axis to a control input value.
*/
class gamepad_button_mapping: public mapping
class mouse_scroll_mapping: public mapping
{
public:
gamepad_button_mapping() = default;
gamepad_button_mapping(const gamepad_button_mapping& mapping);
gamepad_button_mapping(input::control* control, gamepad* controller, gamepad_button button);
virtual ~gamepad_button_mapping() = default;
gamepad_button_mapping& operator=(const gamepad_button_mapping& mapping);
virtual mapping_type get_type() const;
gamepad* controller;
gamepad_button button;
/**
* Constructs a mouse scroll mapping.
*
* @param control Control to which input will be mapped.
* @param mouse Pointer to the mouse to map, or `nullptr` if input from any mouse will be mapped.
* @param axis Mouse scroll axis to map.
* @param direction Sign bit of the direction to map.
*/
/// @{
mouse_scroll_mapping(input::mouse* mouse, mouse_scroll_axis axis, bool direction);
mouse_scroll_mapping() = default;
/// @}
/// Destructs a mouse scroll mapping.
virtual ~mouse_scroll_mapping() = default;
/// Returns mapping_type::mouse_scroll.
[[nodiscard]] inline virtual constexpr mapping_type get_mapping_type() const noexcept
{
return mapping_type::mouse_scroll;
}
/// Pointer to the mapped mouse, or `nullptr` if input from any mouse is accepted.
input::mouse* mouse;
/// Mapped mouse scroll axis.
mouse_scroll_axis axis;
/// Sign bit of the mapped direction.
bool direction;
};
inline mapping_type gamepad_button_mapping::get_type() const
{
return mapping_type::gamepad_button;
}
} // namespace input
#endif // ANTKEEPER_INPUT_MAPPING_HPP

+ 90
- 0
src/input/modifier-key.hpp View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_MODIFIER_KEY_HPP
#define ANTKEEPER_INPUT_MODIFIER_KEY_HPP
#include <cstdint>
namespace input {
/**
* Modifier key bit mask flags.
*/
namespace modifier_key {
/// Modifier key bit flags.
enum: std::uint16_t
{
/// No modifier key is pressed.
none = 0b0000000000000000,
/// Left shift modifier key is pressed.
left_shift = 0b0000000000000001,
/// Right shift modifier key is pressed.
right_shift = 0b0000000000000010,
/// One or both shift modifier keys are pressed.
shift = left_shift | right_shift,
/// Left ctrl modifier key is pressed.
left_ctrl = 0b0000000000000100,
/// Right ctrl modifier key is pressed.
right_ctrl = 0b0000000000001000,
/// One or both ctrl modifier keys are pressed.
ctrl = left_ctrl | right_ctrl,
/// Left alt modifier key is pressed.
left_alt = 0b0000000000010000,
/// Right alt modifier key is pressed.
right_alt = 0b0000000000100000,
/// One or both alt modifier keys are pressed.
alt = left_alt | right_alt,
/// Left gui modifier key is pressed.
left_gui = 0b0000000001000000,
/// Right gui modifier key is pressed.
right_gui = 0b0000000010000000,
/// One or both gui modifier keys are pressed.
gui = left_gui | right_gui,
/// Num lock modifier key is pressed.
num_lock = 0b0000000100000000,
/// Caps lock modifier key is pressed.
caps_lock = 0b0000001000000000,
/// Scroll lock modifier key is pressed.
scroll_lock = 0b0000010000000000,
/// AltGr modifier key is pressed.
alt_gr = 0b0000100000000000
};
} // namespace modifier_key
} // namespace input
#endif // ANTKEEPER_INPUT_MODIFIER_KEY_HPP

+ 42
- 0
src/input/mouse-button.hpp View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_MOUSE_BUTTON_HPP
#define ANTKEEPER_INPUT_MOUSE_BUTTON_HPP
#include <cstdint>
namespace input {
/// Mouse buttons.
enum class mouse_button: std::uint8_t
{
/// Left mouse button.
left = 1,
/// Middle mouse button.
middle = 2,
/// Right mouse button.
right = 3
};
} // namespace input
#endif // ANTKEEPER_INPUT_MOUSE_BUTTON_HPP

src/input/sdl-game-controller-tables.hpp → src/input/mouse-motion-axis.hpp View File

@ -17,18 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_INPUT_SDL_GAMEPAD_TABLES_HPP
#define ANTKEEPER_INPUT_SDL_GAMEPAD_TABLES_HPP
#ifndef ANTKEEPER_INPUT_MOUSE_MOTION_AXIS_HPP
#define ANTKEEPER_INPUT_MOUSE_MOTION_AXIS_HPP
namespace input {
#include <cstdint>
enum class gamepad_button;
enum class gamepad_axis;
namespace input {
extern const gamepad_button sdl_button_table[15];
extern const gamepad_axis sdl_axis_table[6];
/// Mouse motion axes.
enum class mouse_motion_axis: std::uint8_t
{
/// X-axis.
x = 0,
/// Y-axis.
y = 1
};
} // namespace input
#endif // ANTKEEPER_INPUT_SDL_GAMEPAD_TABLES_HPP
#endif // ANTKEEPER_INPUT_MOUSE_MOTION_AXIS_HPP

src/event/window-events.cpp → src/input/mouse-scroll-axis.hpp View File

@ -17,12 +17,23 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "event/window-events.hpp"
#ifndef ANTKEEPER_INPUT_MOUSE_SCROLL_AXIS_HPP
#define ANTKEEPER_INPUT_MOUSE_SCROLL_AXIS_HPP
event_base* window_resized_event::clone() const
#include <cstdint>
namespace input {
/// Mouse scroll axes.
enum class mouse_scroll_axis: std::uint8_t
{
window_resized_event* event = new window_resized_event();
event->w = w;
event->h = h;
return event;
}
/// X-axis.
x = 0,
/// Y-axis.
y = 1
};
} // namespace input
#endif // ANTKEEPER_INPUT_MOUSE_SCROLL_AXIS_HPP

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save