<commit_before>#include <mirrage/input/input_manager.hpp>

#include <mirrage/input/input_mapping.hpp>

#include <mirrage/asset/asset_manager.hpp>
#include <mirrage/utils/ranges.hpp>

#include <glm/glm.hpp>
#include <sf2/sf2.hpp>

#include <SDL2/SDL_gesture.h>

#ifdef EMSCRIPTEN
#include <emscripten/html5.h>
#endif


namespace mirrage::input {

	using namespace util::unit_literals;

	class Input_manager::Gamepad {
	  public:
		Gamepad(Input_source src_id, SDL_GameController*, Input_mapper& mapper);
		~Gamepad();

		void force_feedback(float force);

		auto button_pressed(Pad_button b) const noexcept -> bool;
		auto button_released(Pad_button b) const noexcept -> bool;

		auto button_down(Pad_button b) const noexcept -> bool;
		auto button_up(Pad_button b) const noexcept -> bool;
		auto trigger(Pad_button b) const noexcept -> float;

		void update(util::Time dt);

		auto axis(Pad_stick stick) -> glm::vec2;

		auto id() const noexcept { return _id; }

	  private:
		Input_source        _src_id;
		int                 _id;
		SDL_GameController* _sdl_controller;
		SDL_Haptic*         _haptic;
		Input_mapper&       _mapper;

		uint8_t   _button_state[pad_button_count]{};
		float     _button_value[pad_button_count]{};
		glm::vec2 _stick_state[pad_stick_count]{};

		float _stick_dead_zone = 0.2f, _stick_max = 32767.f;

		float      _current_force = 0.f;
		util::Time _force_reset_timer{0.f};
	};

	namespace {
		inline auto from_sdl_keycode(SDL_Keycode key) { return static_cast<Key>(key); }

		inline auto to_sdl_pad_button(Pad_button b) { return static_cast<SDL_GameControllerButton>(b); }
		inline bool is_trigger(Pad_button b)
		{
			return b == Pad_button::left_trigger || b == Pad_button::right_trigger;
		}

		inline auto to_sdl_axis_x(Pad_stick s)
		{
			switch(s) {
				case Pad_stick::left: return SDL_CONTROLLER_AXIS_LEFTX;
				case Pad_stick::right: return SDL_CONTROLLER_AXIS_RIGHTX;
				default: return SDL_CONTROLLER_AXIS_INVALID;
			}
		}
		inline auto to_sdl_axis_y(Pad_stick s)
		{
			switch(s) {
				case Pad_stick::left: return SDL_CONTROLLER_AXIS_LEFTY;
				case Pad_stick::right: return SDL_CONTROLLER_AXIS_RIGHTY;
				default: return SDL_CONTROLLER_AXIS_INVALID;
			}
		}
	} // namespace


	Sdl_event_filter::Sdl_event_filter(Input_manager& e) : _manager(&e) { _manager->add_event_filter(*this); }
	Sdl_event_filter::Sdl_event_filter(Sdl_event_filter&& rhs) noexcept : _manager(rhs._manager)
	{
		if(_manager) {
			_manager->remove_event_filter(rhs);
			_manager->add_event_filter(*this);
		}
	}
	Sdl_event_filter& Sdl_event_filter::operator=(Sdl_event_filter&& rhs) noexcept
	{
		if(_manager)
			_manager->remove_event_filter(*this);

		if(rhs._manager)
			rhs._manager->remove_event_filter(rhs);

		_manager     = rhs._manager;
		rhs._manager = nullptr;

		if(_manager)
			_manager->add_event_filter(*this);

		return *this;
	}

	Sdl_event_filter::~Sdl_event_filter()
	{
		if(_manager) {
			_manager->remove_event_filter(*this);
		}
	}


	Input_manager::Input_manager(util::Message_bus& bus, asset::Asset_manager& assets)
	  : _mailbox(bus)
	  , _screen_to_world_coords(&util::identity<glm::vec2>)
	  , _mapper(std::make_unique<Input_mapper>(bus, assets))
	{

		SDL_JoystickEventState(SDL_ENABLE);
		SDL_GameControllerEventState(SDL_ENABLE);

		SDL_RecordGesture(-1);

		_mailbox.subscribe<Force_feedback, 4>(8, [&](auto& m) {
			auto handle = [&] {
				if(m.src > 0 && m.src - 1 < static_cast<int>(this->_gamepads.size())) {
					auto& pad = this->_gamepads.at(std::size_t(m.src - 1));
					if(pad)
						pad->force_feedback(m.force);
				}

#ifdef EMSCRIPTEN
				if(m.src == 0 && m.force > 0.5)
					emscripten_vibrate(200);
#endif
			};

			if(m.src >= 0)
				handle();

			else {
				for(auto i = 0u; i < this->_gamepads.size() + 1; i++) {
					m.src = gsl::narrow<Input_source>(i);
					handle();
				}
			}
		});
	}

	Input_manager::~Input_manager() noexcept {}

	void Input_manager::add_event_filter(Sdl_event_filter& f) { _event_filter.push_back(&f); }
	void Input_manager::remove_event_filter(Sdl_event_filter& f) { util::erase_fast(_event_filter, &f); }

	void Input_manager::capture_mouse(bool enable)
	{
		if(_mouse_captured != enable) {
			SDL_SetRelativeMouseMode(enable ? SDL_TRUE : SDL_FALSE);
			_mouse_captured = enable;

			if(!enable && _sdl_window) {
				SDL_WarpMouseInWindow(
				        _sdl_window, int(_mouse_capture_screen_pos.x), int(_mouse_capture_screen_pos.y));
			}
			_mouse_capture_screen_pos = _pointer_screen_pos[0];
		}
	}

	void Input_manager::update(util::Time dt)
	{
		_poll_events();

		for(auto i : util::range(std::size_t(_max_pointers)))
			_pointer_world_pos[i] = _screen_to_world_coords(_pointer_screen_pos[i]);

		for(auto& gp : _gamepads)
			gp->update(dt);

		_mailbox.update_subscriptions();
	}

	void Input_manager::_poll_events()
	{
		for(auto& f : _event_filter) {
			f->pre_input_events();
		}

		SDL_Event event;
		while(SDL_PollEvent(&event)) {
			auto filtered = std::find_if(_event_filter.begin(),
			                             _event_filter.end(),
			                             [&](auto f) { return !f->propagate(event); })
			                != _event_filter.end();

			if(!filtered) {
				_handle_event(event);
			}
		}

		for(auto& f : _event_filter) {
			f->post_input_events();
		}
	}
<commit_msg>fixed a crash if a gamepad was removed<commit_after>#include <mirrage/input/input_manager.hpp>

#include <mirrage/input/input_mapping.hpp>

#include <mirrage/asset/asset_manager.hpp>
#include <mirrage/utils/ranges.hpp>

#include <glm/glm.hpp>
#include <sf2/sf2.hpp>

#include <SDL2/SDL_gesture.h>

#ifdef EMSCRIPTEN
#include <emscripten/html5.h>
#endif


namespace mirrage::input {

	using namespace util::unit_literals;

	class Input_manager::Gamepad {
	  public:
		Gamepad(Input_source src_id, SDL_GameController*, Input_mapper& mapper);
		~Gamepad();

		void force_feedback(float force);

		auto button_pressed(Pad_button b) const noexcept -> bool;
		auto button_released(Pad_button b) const noexcept -> bool;

		auto button_down(Pad_button b) const noexcept -> bool;
		auto button_up(Pad_button b) const noexcept -> bool;
		auto trigger(Pad_button b) const noexcept -> float;

		void update(util::Time dt);

		auto axis(Pad_stick stick) -> glm::vec2;

		auto id() const noexcept { return _id; }

	  private:
		Input_source        _src_id;
		int                 _id;
		SDL_GameController* _sdl_controller;
		SDL_Haptic*         _haptic;
		Input_mapper&       _mapper;

		uint8_t   _button_state[pad_button_count]{};
		float     _button_value[pad_button_count]{};
		glm::vec2 _stick_state[pad_stick_count]{};

		float _stick_dead_zone = 0.2f, _stick_max = 32767.f;

		float      _current_force = 0.f;
		util::Time _force_reset_timer{0.f};
	};

	namespace {
		inline auto from_sdl_keycode(SDL_Keycode key) { return static_cast<Key>(key); }

		inline auto to_sdl_pad_button(Pad_button b) { return static_cast<SDL_GameControllerButton>(b); }
		inline bool is_trigger(Pad_button b)
		{
			return b == Pad_button::left_trigger || b == Pad_button::right_trigger;
		}

		inline auto to_sdl_axis_x(Pad_stick s)
		{
			switch(s) {
				case Pad_stick::left: return SDL_CONTROLLER_AXIS_LEFTX;
				case Pad_stick::right: return SDL_CONTROLLER_AXIS_RIGHTX;
				default: return SDL_CONTROLLER_AXIS_INVALID;
			}
		}
		inline auto to_sdl_axis_y(Pad_stick s)
		{
			switch(s) {
				case Pad_stick::left: return SDL_CONTROLLER_AXIS_LEFTY;
				case Pad_stick::right: return SDL_CONTROLLER_AXIS_RIGHTY;
				default: return SDL_CONTROLLER_AXIS_INVALID;
			}
		}
	} // namespace


	Sdl_event_filter::Sdl_event_filter(Input_manager& e) : _manager(&e) { _manager->add_event_filter(*this); }
	Sdl_event_filter::Sdl_event_filter(Sdl_event_filter&& rhs) noexcept : _manager(rhs._manager)
	{
		if(_manager) {
			_manager->remove_event_filter(rhs);
			_manager->add_event_filter(*this);
		}
	}
	Sdl_event_filter& Sdl_event_filter::operator=(Sdl_event_filter&& rhs) noexcept
	{
		if(_manager)
			_manager->remove_event_filter(*this);

		if(rhs._manager)
			rhs._manager->remove_event_filter(rhs);

		_manager     = rhs._manager;
		rhs._manager = nullptr;

		if(_manager)
			_manager->add_event_filter(*this);

		return *this;
	}

	Sdl_event_filter::~Sdl_event_filter()
	{
		if(_manager) {
			_manager->remove_event_filter(*this);
		}
	}


	Input_manager::Input_manager(util::Message_bus& bus, asset::Asset_manager& assets)
	  : _mailbox(bus)
	  , _screen_to_world_coords(&util::identity<glm::vec2>)
	  , _mapper(std::make_unique<Input_mapper>(bus, assets))
	{

		SDL_JoystickEventState(SDL_ENABLE);
		SDL_GameControllerEventState(SDL_ENABLE);

		SDL_RecordGesture(-1);

		_mailbox.subscribe<Force_feedback, 4>(8, [&](auto& m) {
			auto handle = [&] {
				if(m.src > 0 && m.src - 1 < static_cast<int>(this->_gamepads.size())) {
					auto& pad = this->_gamepads.at(std::size_t(m.src - 1));
					if(pad)
						pad->force_feedback(m.force);
				}

#ifdef EMSCRIPTEN
				if(m.src == 0 && m.force > 0.5)
					emscripten_vibrate(200);
#endif
			};

			if(m.src >= 0)
				handle();

			else {
				for(auto i = 0u; i < this->_gamepads.size() + 1; i++) {
					m.src = gsl::narrow<Input_source>(i);
					handle();
				}
			}
		});
	}

	Input_manager::~Input_manager() noexcept {}

	void Input_manager::add_event_filter(Sdl_event_filter& f) { _event_filter.push_back(&f); }
	void Input_manager::remove_event_filter(Sdl_event_filter& f) { util::erase_fast(_event_filter, &f); }

	void Input_manager::capture_mouse(bool enable)
	{
		if(_mouse_captured != enable) {
			SDL_SetRelativeMouseMode(enable ? SDL_TRUE : SDL_FALSE);
			_mouse_captured = enable;

			if(!enable && _sdl_window) {
				SDL_WarpMouseInWindow(
				        _sdl_window, int(_mouse_capture_screen_pos.x), int(_mouse_capture_screen_pos.y));
			}
			_mouse_capture_screen_pos = _pointer_screen_pos[0];
		}
	}

	void Input_manager::update(util::Time dt)
	{
		_poll_events();

		for(auto i : util::range(std::size_t(_max_pointers)))
			_pointer_world_pos[i] = _screen_to_world_coords(_pointer_screen_pos[i]);

		for(auto& gp : _gamepads)
			if(gp){
				gp->update(dt);
			}

		_mailbox.update_subscriptions();
	}

	void Input_manager::_poll_events()
	{
		for(auto& f : _event_filter) {
			f->pre_input_events();
		}

		SDL_Event event;
		while(SDL_PollEvent(&event)) {
			auto filtered = std::find_if(_event_filter.begin(),
			                             _event_filter.end(),
			                             [&](auto f) { return !f->propagate(event); })
			                != _event_filter.end();

			if(!filtered) {
				_handle_event(event);
			}
		}

		for(auto& f : _event_filter) {
			f->post_input_events();
		}
	}

{'fraction_non_alphanumeric': 0.13037760661281234, 'fraction_numerical': 0.0061995115536351685, 'mean_word_length': 4.096696984202968, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1675566', 'n_tokens_mistral': 4539, 'n_tokens_neox': 3923, 'n_words': 624}