diff --git a/README.md b/README.md index bdd5fc939..2b6a933e4 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ can build a single, statically linked and optimised binary. reverse-ssh or similar to forward localhost::3535 and you can remotely connect to a running app from all over the world. +* **Gamepad** support: the default camera can be steered with + a gamepad-just connect your gamepad and you are set; application + windows can decide whether they want to subscribe to gamepad events + - and to which gamepads to subscribe to. + * **Multi-Window** Island allows you to hook up multiple swapchains to a single application. You can dynamically add and remove swapchains while your Island application is running. This is particularly useful diff --git a/modules/le_backend_vk/le_backend_vk.cpp b/modules/le_backend_vk/le_backend_vk.cpp index b8f74d4d2..184313f60 100644 --- a/modules/le_backend_vk/le_backend_vk.cpp +++ b/modules/le_backend_vk/le_backend_vk.cpp @@ -2000,7 +2000,7 @@ static bool backend_poll_frame_fence( le_backend_o* self, size_t frameIndex ) { logger.error( "Poll Frame Fence returned: %s", to_str_vk_result( result ) ); exit( 1 ); } else if ( result != VK_SUCCESS ) { - logger.error( "Poll Frame Fence returned: %s", to_str_vk_result( result ) ); + logger.warn( "Poll Frame Fence returned: %s", to_str_vk_result( result ) ); return false; } else { return true; diff --git a/modules/le_camera/le_camera.cpp b/modules/le_camera/le_camera.cpp index a2a71adb6..2b2b04972 100644 --- a/modules/le_camera/le_camera.cpp +++ b/modules/le_camera/le_camera.cpp @@ -1,6 +1,7 @@ #include "le_camera.h" #include "le_core.h" +#include "modules/le_log/le_log.h" #include "private/le_renderer/le_renderer_types.h" // for le::Viewport #include "le_ui_event.h" @@ -92,7 +93,7 @@ static void update_frustum_planes( le_camera_o* self ) { float fPL[ 6 ]{}; for ( size_t i = 0; i != 6; i++ ) { // get the length (= magnitude of the .xyz part of the row), so that we can normalize later - fPL[ i ] = glm::vec3( fP[ i ].x, fP[ i ].y, fP[ i ].z ).length(); + fPL[ i ] = glm::distance(glm::vec3( fP[ i ].x, fP[ i ].y, fP[ i ].z ), glm::vec3(0)); } for ( size_t i = 0; i < 6; i++ ) { @@ -289,6 +290,19 @@ void camera_translate_xy( le_camera_o* camera, glm::mat4 const& world_to_cam_sta camera->view_matrix = glm::inverse( world_to_cam ); } +// ---------------------------------------------------------------------- +void camera_translate_xyz( le_camera_o* camera, glm::mat4 const& world_to_cam_start, glm::vec3 const& signedNorm, float movement_speed, float pivotDistance ) { + + float distance_to_origin = glm::distance( glm::vec4{ 0, 0, 0, 1 }, world_to_cam_start * glm::vec4( 0, 0, 0, 1 ) ); + movement_speed *= distance_to_origin; + + auto pivot = glm::translate( world_to_cam_start, glm::vec3{ 0, 0, -pivotDistance } ); + pivot = glm::translate( pivot, movement_speed * glm::vec3{ signedNorm.x, signedNorm.y, signedNorm.z } ); + auto world_to_cam = glm::translate( pivot, glm::vec3{ 0, 0, pivotDistance } ); + + camera->view_matrix = glm::inverse( world_to_cam ); +} + // ---------------------------------------------------------------------- void camera_translate_z( le_camera_o* camera, glm::mat4 const& world_to_cam_start, glm::vec3 const& signedNorm, float movement_speed, float pivotDistance ) { @@ -326,6 +340,8 @@ static void camera_controller_update_camera( le_camera_controller_o* controller, } for ( auto const& event : events ) { + glm::vec3 translationDelta{}; + glm::vec3 rotationDelta{}; // -- accumulate mouse state @@ -355,7 +371,6 @@ static void camera_controller_update_camera( le_camera_controller_o* controller, if ( e.action == LeUiEvent::ButtonAction::ePress ) { // set appropriate button flag mouse_state.buttonState |= ( 1 << e.button ); - } else if ( e.action == LeUiEvent::ButtonAction::eRelease ) { // null appropriate button flag mouse_state.buttonState &= ~( 1 << e.button ); @@ -363,13 +378,49 @@ static void camera_controller_update_camera( le_camera_controller_o* controller, controller->mode = le_camera_controller_o::eNeutral; } } break; + case ( LeUiEvent::Type::eGamepad ): { + auto& e = event->gamepad; + + // We must make sure that axes are not drifting ... if any of the values are below axes_epsilon, they shall be zero. + // Therefore, we ignore any values that are within the +- drift range; we then remap the range so that we still cover -1..1 + auto remove_drift = []( glm::vec3 const& vec_input, glm::vec3 const& drift_tolerance = glm::vec3( 0.1f ) ) -> glm::vec3 { + glm::vec3 abs_val = glm::abs( vec_input ); + glm::vec3 sign_val = glm::sign( vec_input ); + + glm::vec3 test_val = abs_val - drift_tolerance; + test_val = max( glm::vec3( 0.f ), test_val ); + test_val = test_val * sign_val; + // todo: map back to original range. + return test_val / ( glm::vec3( 1.f ) - drift_tolerance ); + }; + + glm::vec3 gamepad_x_y_z = { + e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eLeftX ) ], + e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eLeftY ) ], + ( ( e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eLeftTrigger ) ] + 1 ) - + ( e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eRightTrigger ) ] + 1 ) ), + }; + + translationDelta += 0.01f * remove_drift( gamepad_x_y_z, glm::vec3( 0.1 ) ); + ; + + glm::vec3 gamepad_rot = { + e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eRightX ) ], + e.axes[ uint32_t( le::UiEvent::NamedGamepadAxis::eRightY ) ], + 0.f }; + + gamepad_rot = remove_drift( gamepad_rot, glm::vec3( 0.1 ) ); + + rotationDelta.x = glm::two_pi() * -0.0025f * gamepad_rot.x; + rotationDelta.y = glm::two_pi() * 0.0025f * gamepad_rot.y; + + break; + } default: break; } - glm::vec3 rotationDelta; - glm::vec3 translationDelta; - { + if ( mouse_state.buttonState ) { auto mouseInitial = controller->mouse_pos_initial - controlRectCentre; float mouseInitialAngle = glm::two_pi() - fmodf( glm::two_pi() + atan2f( mouseInitial.y, mouseInitial.x ), glm::two_pi() ); // Range is expected to be 0..2pi, ccw @@ -389,6 +440,18 @@ static void camera_controller_update_camera( le_camera_controller_o* controller, switch ( controller->mode ) { case le_camera_controller_o::eNeutral: { + if ( event->event == le::UiEvent::Type::eGamepad ) { + + float movement_speed = 0.5; + static auto logger = le::Log( "le_camera" ); + controller->world_to_cam = glm::inverse( camera->view_matrix ); + camera_translate_xyz( camera, controller->world_to_cam, translationDelta, movement_speed, controller->pivotDistance ); + controller->world_to_cam = glm::inverse( camera->view_matrix ); + camera_orbit_xy( camera, controller->world_to_cam, rotationDelta, controller->pivotDistance ); + controller->world_to_cam = glm::inverse( camera->view_matrix ); + continue; + } + if ( false == is_inside_rect( mouse_state.cursor_pos, controller->controlRect ) ) { // if camera is outside the control rect, we don't care. continue; @@ -458,7 +521,10 @@ static void camera_controller_process_events( le_camera_controller_o* controller filtered_events.reserve( numEvents ); for ( auto event = events; event != events_end; event++ ) { - if ( event->event == LeUiEvent::Type::eCursorPosition || event->event == LeUiEvent::Type::eMouseButton || event->event == LeUiEvent::Type::eKey ) { + if ( event->event == LeUiEvent::Type::eCursorPosition || + event->event == LeUiEvent::Type::eMouseButton || + event->event == LeUiEvent::Type::eKey || + event->event == LeUiEvent::Type::eGamepad ) { filtered_events.emplace_back( event ); } } @@ -510,7 +576,8 @@ static void camera_controller_set_pivot_distance( le_camera_controller_o* self, // ---------------------------------------------------------------------- LE_MODULE_REGISTER_IMPL( le_camera, api ) { - auto& le_camera_i = static_cast( api )->le_camera_i; + auto api_i = static_cast( api ); + auto& le_camera_i = api_i->le_camera_i; le_camera_i.create = le_camera_create; le_camera_i.destroy = le_camera_destroy; @@ -529,7 +596,7 @@ LE_MODULE_REGISTER_IMPL( le_camera, api ) { le_camera_i.get_sphere_in_frustum = camera_get_sphere_in_frustum; le_camera_i.set_is_orthographic = camera_set_is_orthographic; - auto& le_camera_controller_i = static_cast( api )->le_camera_controller_i; + auto& le_camera_controller_i = api_i->le_camera_controller_i; le_camera_controller_i.create = camera_controller_create; le_camera_controller_i.destroy = camera_controller_destroy; diff --git a/modules/le_file_watcher/le_tweakable.h b/modules/le_file_watcher/le_tweakable.h index ee8fcce8e..ab906ec57 100644 --- a/modules/le_file_watcher/le_tweakable.h +++ b/modules/le_file_watcher/le_tweakable.h @@ -34,7 +34,7 @@ # include "le_core.h" # include "le_file_watcher.h" -# include "le_log.h" +# include "le_log.h" ///< if you get an error message saying that this header can't be found, make sure that the module that uses le_tweakable has a line saying `depends_on_island_module(le_log)` in its CMake file. # include # include diff --git a/modules/le_mesh/le_mesh.h b/modules/le_mesh/le_mesh.h index 44ef9ba9f..4b13b7fa6 100644 --- a/modules/le_mesh/le_mesh.h +++ b/modules/le_mesh/le_mesh.h @@ -15,12 +15,12 @@ struct le_mesh_api { void (*clear)(le_mesh_o* self); - void (*get_vertices )( le_mesh_o *self, size_t& count, float const ** vertices); - void (*get_normals )( le_mesh_o *self, size_t& count, float const ** normals ); - void (*get_colours )( le_mesh_o *self, size_t& count, float const ** colours ); - void (*get_uvs )( le_mesh_o *self, size_t& count, float const ** uvs ); - void (*get_tangents )( le_mesh_o *self, size_t& count, float const ** tangents); - void (*get_indices )( le_mesh_o *self, size_t& count, uint16_t const ** indices ); + void (*get_vertices )( le_mesh_o *self, size_t& count, float const ** vertices); // 3 floats per vertex + void (*get_normals )( le_mesh_o *self, size_t& count, float const ** normals ); // 3 floats per vertex + void (*get_colours )( le_mesh_o *self, size_t& count, float const ** colours ); // 4 floats per vertex + void (*get_uvs )( le_mesh_o *self, size_t& count, float const ** uvs ); // 3 floats per vertex + void (*get_tangents )( le_mesh_o *self, size_t& count, float const ** tangents); // 3 floats per vertex + void (*get_indices )( le_mesh_o *self, size_t& count, uint16_t const ** indices ); // 1 uint16_t per index void (*get_data )( le_mesh_o *self, size_t& numVertices, size_t& numIndices, float const** vertices, float const **normals, float const **uvs, float const ** colours, uint16_t const **indices); diff --git a/modules/le_ui_event/le_ui_event.h b/modules/le_ui_event/le_ui_event.h index 725033dd7..04400fbd4 100644 --- a/modules/le_ui_event/le_ui_event.h +++ b/modules/le_ui_event/le_ui_event.h @@ -1,9 +1,41 @@ #pragma once #include -// Todo: move this to a frame-work wide internal header file. - struct LeUiEvent { + enum class NamedGamepadButton : uint32_t { + eA = 0, + eB = 1, + eX = 2, + eY = 3, + eLeftBumper = 4, + eRightBumper = 5, + eBack = 6, + eStart = 7, + eGuide = 8, + eLeftThumb = 9, + eRightThumb = 10, + eDpadUp = 11, + eDpadRight = 12, + eDpadDown = 13, + eDpadLeft = 14, + // + eLast = eDpadLeft, + // + eCross = eA, + eCircle = eB, + eSquare = eX, + eTriangle = eY, + }; + + enum class NamedGamepadAxis : uint32_t { + eLeftX = 0, + eLeftY = 1, + eRightX = 2, + eRightY = 3, + eLeftTrigger = 4, + eRightTrigger = 5, + eLast = eRightTrigger, + }; enum class NamedKey : int32_t { eUnknown = -1, @@ -138,13 +170,15 @@ struct LeUiEvent { }; enum class Type : uint32_t { - eKey = 1 << 0, - eCharacter = 1 << 1, - eCursorPosition = 1 << 2, - eCursorEnter = 1 << 3, - eMouseButton = 1 << 4, - eScroll = 1 << 5, - eDrop = 1 << 6, + eUnknown = 0, + eKey, + eCharacter, + eCursorPosition, + eCursorEnter, + eMouseButton, + eScroll, + eDrop, + eGamepad, }; struct KeyEvent { @@ -183,7 +217,29 @@ struct LeUiEvent { uint64_t paths_count; }; - Type event; + struct GamepadEvent { + float axes[ 6 ]; // -1 to 1 (inclusive for each axis) + uint16_t buttons; // [0] : 0..14 bitset, 0 is least significant bit + uint16_t gamepad_id; // 0..15 + + bool get_button_at( uint8_t index ) const noexcept { + return index < 15 ? ( buttons & ( uint16_t( 1 ) << index ) ) : false; + } + + bool operator==( GamepadEvent const& rhs ) { + return axes[ 0 ] == rhs.axes[ 0 ] && + axes[ 1 ] == rhs.axes[ 1 ] && + axes[ 2 ] == rhs.axes[ 2 ] && + axes[ 3 ] == rhs.axes[ 3 ] && + axes[ 4 ] == rhs.axes[ 4 ] && + axes[ 5 ] == rhs.axes[ 5 ] && + buttons == rhs.buttons; + } + + bool operator!=( GamepadEvent const& rhs ) { + return !( *this == rhs ); + } + }; union { KeyEvent key; @@ -193,7 +249,10 @@ struct LeUiEvent { MouseButtonEvent mouseButton; ScrollEvent scroll; DropEvent drop; + GamepadEvent gamepad; }; + + Type event; }; namespace le { diff --git a/modules/le_window/3rdparty/glfw b/modules/le_window/3rdparty/glfw index 8f470597d..9a8763568 160000 --- a/modules/le_window/3rdparty/glfw +++ b/modules/le_window/3rdparty/glfw @@ -1 +1 @@ -Subproject commit 8f470597d625ae28758c16b4293dd42d63e8a83a +Subproject commit 9a87635686c7fcb63ca63149c5b179b85a53a725 diff --git a/modules/le_window/le_window.cpp b/modules/le_window/le_window.cpp index eed20c696..9061a8170 100644 --- a/modules/le_window/le_window.cpp +++ b/modules/le_window/le_window.cpp @@ -1,14 +1,17 @@ #include "le_window.h" +#include "le_hash_util.h" #include "le_ui_event.h" #include "le_log.h" #include "le_backend_vk.h" #include "assert.h" +#include #include #include #include #include #include +#include // for memcpy #define GLFW_INCLUDE_VULKAN #define GLFW_INCLUDE_NONE @@ -22,14 +25,16 @@ #endif #include "GLFW/glfw3native.h" -constexpr size_t EVENT_QUEUE_SIZE = 100; // Only allocate space for 100 events per-frame +constexpr size_t EVENT_QUEUE_SIZE = ( 4096 * 4 ) / sizeof( LeUiEvent ); // Allocate a few pages for events +constexpr auto GAMEPAD_SUBSCRIBERS_SINGLETON_ID = hash_64_fnv1a_const( "le_window_gamepad_subscribers" ); struct le_window_settings_o { - int width = 640; - int height = 480; - std::string title = "Island default window title"; - GLFWmonitor* monitor = nullptr; - uint32_t useEventsQueue = true; // whether to use an events queue or not + int width = 640; + int height = 480; + std::string title = "Island default window title"; + GLFWmonitor* monitor = nullptr; + bool useEventsQueue = true; // whether to use an events queue or not + uint32_t active_gamepads = ~uint32_t( 0 ); // bitfield; subscribe to gamepad events for gamepads / joysticks with matching id }; struct WindowGeometry { @@ -48,7 +53,7 @@ struct le_window_o { size_t referenceCount = 0; void* user_data = nullptr; - uint32_t eventQueueBack = 0; // Event queue currently used to record events + std::atomic eventQueueBack = 0; // Event queue currently used to record events std::array, 2> numEventsForQueue{ 0, 0 }; // Counter for events per queue (works as arena allocator marker for events queue) std::array, 2> eventQueue; // Events queue is double-bufferd, flip happens on `get_event_queue` @@ -59,6 +64,36 @@ struct le_window_o { bool isFullscreen = false; }; +struct gamepad_events_subscriber_windows_t { + std::vector windows; + std::mutex mtx; +}; + +static gamepad_events_subscriber_windows_t* gamepad_events_subscribers_singleton_get() { + + static auto logger = le::Log( "le_window" ); + + static std::mutex mtx; + + // we need a mutex here to prevent a race condition where two or more threads + // fight for who can first return the singleton - we want to make sure it only + // gets initialised once. + // + std::unique_lock critical_section( mtx ); + + // Attempt to fetch the entry for our singleton from the global, persistent storage. + // + void** dict = le_core_produce_dictionary_entry( GAMEPAD_SUBSCRIBERS_SINGLETON_ID ); + + // If the entry is empty, allocate a new object and update the dictionary entry. + if ( *dict == nullptr ) { + *dict = new gamepad_events_subscriber_windows_t{}; + logger.info( "Created gamepad events subscribers singleton" ); + } + + return static_cast( *dict ); +}; + // ---------------------------------------------------------------------- // Check if there is an available index to write at, given an event counter, // and set eventIdx as a side-effect. @@ -80,6 +115,28 @@ bool event_queue_idx_available( std::atomic& atomicCounter, uint32_t& } } +// ---------------------------------------------------------------------- +static void le_window_gamepad_callback( le_window_o* window, le::UiEvent::GamepadEvent const& ev ) { + + if ( window->mSettings.useEventsQueue ) { + + uint32_t queueIdx = window->eventQueueBack; + uint32_t eventIdx = 0; + + if ( event_queue_idx_available( window->numEventsForQueue[ queueIdx ], eventIdx ) ) { + auto& event = window->eventQueue[ queueIdx ][ eventIdx ]; + event.event = le::UiEvent::Type::eGamepad; + auto& e = event.gamepad; + e = ev; + + static auto logger = le::Log( "le_window" ); + + } else { + // we're over the high - watermark for events, we should probably print a warning. + } + } +} + // ---------------------------------------------------------------------- static void glfw_window_key_callback( GLFWwindow* glfwWindow, int key, int scancode, int action, int mods ) { @@ -403,6 +460,12 @@ static void window_settings_set_height( le_window_settings_o* self_, int height_ // ---------------------------------------------------------------------- +static void window_settings_set_gamepads_active( le_window_settings_o* self, uint32_t gamepads_bitfield ) { + self->active_gamepads = gamepads_bitfield; +} + +// ---------------------------------------------------------------------- + static void window_settings_destroy( le_window_settings_o* self_ ) { delete self_; } @@ -489,8 +552,9 @@ static void window_get_ui_event_queue( le_window_o* self, LeUiEvent const** even // ----------| Invariant: Event queue is in use. // Flip front and back event queue - auto eventQueueFront = self->eventQueueBack; - self->eventQueueBack ^= 1; + // - store old value for queue back in queue front + // - bitwise xor value for queue back with 1 - this performs a flip-flop from 0->1->0 + uint32_t eventQueueFront = self->eventQueueBack.fetch_xor( 1 ); // Note: In the unlikely event that any LeUiEvent will be added asynchronously in between // these two calls it will be added to the very end of the back queue and then implicitly @@ -511,6 +575,55 @@ static void window_get_ui_event_queue( le_window_o* self, LeUiEvent const** even *numEvents = self->numEventsForQueue[ eventQueueFront ]; } +// ---------------------------------------------------------------------- +// Remove the the window from the list of gamepad events subscribers +// in case it is subscribed to gamepad events. +static void window_unsubscribe_from_gamepad_events( le_window_o* window ) { + + static auto gamepad_subscribers = gamepad_events_subscribers_singleton_get(); + + if ( gamepad_subscribers ) { + std::unique_lock lock( gamepad_subscribers->mtx ); + size_t id = 0; + + for ( auto const& w : gamepad_subscribers->windows ) { + if ( w == window ) { + break; + } + id++; + } + + if ( id != gamepad_subscribers->windows.size() ) { + gamepad_subscribers->windows.erase( gamepad_subscribers->windows.begin() + id ); + } + } +} + +// ---------------------------------------------------------------------- +// add window to list of gamepad event subscribers if it is not yet on that +// list +static void window_subscribe_to_gamepad_events( le_window_o* window ) { + + static auto gamepad_subscribers = gamepad_events_subscribers_singleton_get(); + + if ( gamepad_subscribers ) { + std::unique_lock lock( gamepad_subscribers->mtx ); + + // First, make sure that the given window is not already a subscriber + size_t id = 0; + for ( auto const& w : gamepad_subscribers->windows ) { + if ( w == window ) { + break; + } + id++; + } + + if ( id == gamepad_subscribers->windows.size() ) { + gamepad_subscribers->windows.push_back( window ); + } + } +} + // ---------------------------------------------------------------------- static le_window_o* window_create() { @@ -521,6 +634,7 @@ static le_window_o* window_create() { // ---------------------------------------------------------------------- static void window_setup( le_window_o* self, const le_window_settings_o* settings ) { + if ( settings ) { self->mSettings = *settings; } @@ -562,6 +676,12 @@ static void window_setup( le_window_o* self, const le_window_settings_o* setting glfwSetWindowUserPointer( self->window, self ); window_set_callbacks( self ); + + // If window settings subscribes to any gamepad, + // we must add window to gamepad subscribers + if ( self->mSettings.active_gamepads != 0 ) { + window_subscribe_to_gamepad_events( self ); + } } // ---------------------------------------------------------------------- @@ -576,6 +696,8 @@ static void window_destroy( le_window_o* self ) { glfwDestroyWindow( self->window ); } + window_unsubscribe_from_gamepad_events( self ); + delete self; } @@ -608,9 +730,32 @@ static void* window_get_os_native_window_handle( le_window_o* self ) { #endif } +// Callback for when joystick is connected or disconnected, triggered by +// GLFW +static void glfw_joystick_connection_callback( int jid, int event ) { + + static auto logger = le::Log( "glfw" ); + + if ( event == GLFW_CONNECTED ) { + logger.info( "Joystick connected: %d, Name: '%s', GUID: '%s'", jid, glfwGetJoystickName( jid ), glfwGetJoystickGUID( jid ) ); + + // test whether the joystick has a gamepad mapping + + if ( glfwJoystickIsGamepad( jid ) ) { + logger.info( "Joystick has gamepad mapping." ); + } else { + logger.warn( "Joystick does not have gamepad mapping." ); + } + + } else if ( event == GLFW_DISCONNECTED ) { + logger.info( "Joystick disconnected: %d", jid ); + } +} + // ---------------------------------------------------------------------- static int init() { + int result = glfwInit(); assert( result == GLFW_TRUE ); @@ -628,6 +773,25 @@ static int init() { logger.error( "Vulkan not supported." ); } + // initialise gamepad subscriber singleton - we call this method here + // for its side-effect, which is to allocate the subscribers singleton + // if it doesn't exist already. + gamepad_events_subscribers_singleton_get(); + + glfwSetJoystickCallback( ( GLFWjoystickfun )le_core_forward_callback( le_window_api_i->window_callbacks_i.glfw_joystick_connection_callback_addr ) ); + + // We add a manual mapping as there doesn't seem to be a mapping + // for our particular controller in the database. + // you may have to add further mappings for your device in case it is not present + + char const* xbox_series_x_controller_mapping = "050000005e040000130b000015050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux"; + + glfwUpdateGamepadMappings( xbox_series_x_controller_mapping ); + + if ( glfwJoystickPresent( GLFW_JOYSTICK_1 ) ) { + glfw_joystick_connection_callback( GLFW_JOYSTICK_1, GLFW_CONNECTED ); + } + return result; } @@ -638,6 +802,64 @@ static int init() { // to all windows that their event queue is stale at this moment. static void pollEvents() { glfwPollEvents(); + + static auto logger = le::Log( "le_window" ); + + static le::UiEvent::GamepadEvent gamepad_data[ 15 ]; + uint32_t has_gamepad_data = {}; + + GLFWgamepadstate js_state; + + // First iterate over all joysticks and find if there are any joysticks + // which report gamepad data. + // + // If they do, tag at the corresponding position + // we do this in two passes so that we can minimize the time that + // we are holding the lock guarding access to the window callback + // vector in gamepad_subscribers. + for ( auto i = GLFW_JOYSTICK_1; i != GLFW_JOYSTICK_LAST; i++ ) { + + if ( glfwGetGamepadState( i, &js_state ) ) { + + gamepad_data[ i ].gamepad_id = i; + memcpy( gamepad_data[ i ].axes, js_state.axes, sizeof( js_state.axes ) ); + + for ( uint8_t b = 0; b != 15; b++ ) { + gamepad_data[ i ].buttons |= ( uint16_t( js_state.buttons[ b ] ) << b ); + } + has_gamepad_data |= ( 1 << i ); + } + } + + { + // Trigger callbacks on any windows who subscripe to a particular gamepad. + static gamepad_events_subscriber_windows_t* gamepad_subscribers = gamepad_events_subscribers_singleton_get(); + + // critical section + std::unique_lock lock( gamepad_subscribers->mtx ); + + for ( auto& w : gamepad_subscribers->windows ) { + + uint32_t overlap = w->mSettings.active_gamepads & has_gamepad_data; + + uint32_t gamepad_index = 0; + while ( overlap ) { + + // find next 1, beginning from the least significant bit + while ( 0 == ( overlap & ( uint32_t( 1 ) << gamepad_index ) ) ) { + gamepad_index++; + } + + // we must propagate to the window the gamepad state at + // gamepad index + + le_window_gamepad_callback( w, gamepad_data[ gamepad_index ] ); + + // flip that particular entry to mark it as processed for this window + overlap &= ~( 1 << gamepad_index ); + } + } + } } // ---------------------------------------------------------------------- @@ -645,6 +867,16 @@ static void pollEvents() { static void le_terminate() { static auto logger = LeLog( "le_window" ); glfwTerminate(); + { + // destroy list of subscribers + void** dict = le_core_produce_dictionary_entry( GAMEPAD_SUBSCRIBERS_SINGLETON_ID ); + // this must produce an entry, and we can cast what is located at this entry to a + // pointer-to gamepad_events_subscriber_windows_t, which we may delete. + // if that pointer is not set, delete has no effect, as we can delete a nullptr guilt-free. + delete ( static_cast( *dict ) ); + *dict = nullptr; + logger.info( "destroyed gamepade events subscribers singleton" ); + } logger.debug( "Glfw was terminated." ); } @@ -663,15 +895,15 @@ static void set_clipboard_string( char const* str ) { // ---------------------------------------------------------------------- LE_MODULE_REGISTER_IMPL( le_window, api ) { - auto windowApi = static_cast( api ); + auto window_api_i = static_cast( api ); - windowApi->init = init; - windowApi->terminate = le_terminate; - windowApi->pollEvents = pollEvents; - windowApi->get_clipboard_string = get_clipboard_string; - windowApi->set_clipboard_string = set_clipboard_string; + window_api_i->init = init; + window_api_i->terminate = le_terminate; + window_api_i->pollEvents = pollEvents; + window_api_i->get_clipboard_string = get_clipboard_string; + window_api_i->set_clipboard_string = set_clipboard_string; - auto& window_i = windowApi->window_i; + auto& window_i = window_api_i->window_i; window_i.create = window_create; window_i.destroy = window_destroy; window_i.setup = window_setup; @@ -689,14 +921,15 @@ LE_MODULE_REGISTER_IMPL( le_window, api ) { window_i.set_window_size = window_set_window_size; window_i.get_ui_event_queue = window_get_ui_event_queue; - auto& window_settings_i = windowApi->window_settings_i; - window_settings_i.create = window_settings_create; - window_settings_i.destroy = window_settings_destroy; - window_settings_i.set_title = window_settings_set_title; - window_settings_i.set_width = window_settings_set_width; - window_settings_i.set_height = window_settings_set_height; + auto& window_settings_i = window_api_i->window_settings_i; + window_settings_i.create = window_settings_create; + window_settings_i.destroy = window_settings_destroy; + window_settings_i.set_title = window_settings_set_title; + window_settings_i.set_width = window_settings_set_width; + window_settings_i.set_height = window_settings_set_height; + window_settings_i.set_gamepads_active = window_settings_set_gamepads_active; - auto& callbacks_i = windowApi->window_callbacks_i; + auto& callbacks_i = window_api_i->window_callbacks_i; callbacks_i.glfw_key_callback_addr = ( void* )glfw_window_key_callback; callbacks_i.glfw_char_callback_addr = ( void* )glfw_window_character_callback; callbacks_i.glfw_cursor_pos_callback_addr = ( void* )glfw_window_cursor_position_callback; @@ -705,8 +938,9 @@ LE_MODULE_REGISTER_IMPL( le_window, api ) { callbacks_i.glfw_scroll_callback_addr = ( void* )glfw_window_scroll_callback; callbacks_i.glfw_framebuffer_size_callback_addr = ( void* )glfw_framebuffer_resize_callback; callbacks_i.glfw_drop_callback_addr = ( void* )glfw_window_drop_callback; + callbacks_i.glfw_joystick_connection_callback_addr = ( void* )glfw_joystick_connection_callback; #if defined PLUGINS_DYNAMIC -// le_core_load_library_persistently( "libglfw.so" ); + le_core_load_library_persistently( "libglfw.so" ); #endif } diff --git a/modules/le_window/le_window.h b/modules/le_window/le_window.h index 442d9288f..255c4c563 100644 --- a/modules/le_window/le_window.h +++ b/modules/le_window/le_window.h @@ -16,11 +16,12 @@ struct LeUiEvent; // declared in le_ui_event.h struct le_window_api { struct window_settings_interface_t { - le_window_settings_o * ( *create ) (); - void ( *destroy ) ( le_window_settings_o * ); - void ( *set_title ) ( le_window_settings_o *, const char *title_ ); - void ( *set_width ) ( le_window_settings_o *, int width_ ); - void ( *set_height ) ( le_window_settings_o *, int height_ ); + le_window_settings_o * ( *create ) ( ); + void ( *destroy ) ( le_window_settings_o * ); + void ( *set_title ) ( le_window_settings_o *, const char *title_ ); + void ( *set_width ) ( le_window_settings_o *, int width_ ); + void ( *set_height ) ( le_window_settings_o *, int height_ ); + void ( *set_gamepads_active ) ( le_window_settings_o *, uint32_t gamepads_bitfield); }; struct window_interface_t { @@ -43,8 +44,9 @@ struct le_window_api { void ( *toggle_fullscreen ) ( le_window_o* self ); void ( *set_window_size ) ( le_window_o* self, uint32_t width, uint32_t height); - // Returns a sorted array of events pending for the current frame, and the number of events. - // Note that calling this method invalidates any values returned in the previous call to this method. + // Returns a sorted array of events pending since the last call to this method. + // Note that calling this method invalidates any values returned from the previous call to this method. + // You must only call this method once per Frame. void ( *get_ui_event_queue )(le_window_o* self, LeUiEvent const ** events, uint32_t* numEvents); // Return an OS-specific handle for the given window @@ -59,7 +61,8 @@ struct le_window_api { void * glfw_mouse_button_callback_addr; void * glfw_scroll_callback_addr; void * glfw_framebuffer_size_callback_addr; - void * glfw_drop_callback_addr; + void * glfw_drop_callback_addr; + void * glfw_joystick_connection_callback_addr; }; int ( *init ) (); @@ -120,6 +123,10 @@ class Window { le_window::settings_i.set_title( self, title_ ); return *this; } + Settings& setGamepadsActive( uint32_t gamepads_bitfield ) { + le_window::settings_i.set_gamepads_active( self, gamepads_bitfield ); + return *this; + } operator const le_window_settings_o*() const { return self;