diff options
29 files changed, 631 insertions, 317 deletions
diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 69247b166..73846ed91 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -76,6 +76,9 @@ void Config::ReadValues() {              Settings::values.analogs[i] = default_param;      } +    Settings::values.motion_device = sdl2_config->Get( +        "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); +      // Core      Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index b0a0ebd3b..9ea779dd8 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -43,7 +43,7 @@ button_zr=  button_home=  # for analog input, the following devices are available: -#  - "analog_from_button" (default) for emulating analog input from direction buttons.  Required parameters: +#  - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:  #      - "up", "down", "left", "right": sub-devices for each direction.  #          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"  #      - "modifier": sub-devices as a modifier. @@ -56,6 +56,12 @@ button_home=  circle_pad=  c_stick= +# for motion input, the following devices are available: +#  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: +#      - "update_period": update period in milliseconds (default to 100) +#      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) +motion_device= +  [Core]  # Whether to use the Just-In-Time (JIT) compiler for CPU emulation  # 0: Interpreter (slow), 1 (default): JIT (fast) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index b0f808399..25643715a 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -16,11 +16,12 @@  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #include "network/network.h"  void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {      TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); -    motion_emu->Tilt(x, y); +    InputCommon::GetMotionEmu()->Tilt(x, y);  }  void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { @@ -32,9 +33,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {          }      } else if (button == SDL_BUTTON_RIGHT) {          if (state == SDL_PRESSED) { -            motion_emu->BeginTilt(x, y); +            InputCommon::GetMotionEmu()->BeginTilt(x, y);          } else { -            motion_emu->EndTilt(); +            InputCommon::GetMotionEmu()->EndTilt();          }      }  } @@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {      InputCommon::Init();      Network::Init(); -    motion_emu = std::make_unique<Motion::MotionEmu>(*this); -      SDL_SetMainReady();      // Initialize the window @@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {  EmuWindow_SDL2::~EmuWindow_SDL2() {      SDL_GL_DeleteContext(gl_context);      SDL_Quit(); -    motion_emu = nullptr;      Network::Shutdown();      InputCommon::Shutdown(); diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index 1ce2991f7..3664d2fbe 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -7,7 +7,6 @@  #include <memory>  #include <utility>  #include "core/frontend/emu_window.h" -#include "core/frontend/motion_emu.h"  struct SDL_Window; @@ -57,7 +56,4 @@ private:      using SDL_GLContext = void*;      /// The OpenGL context associated with the window      SDL_GLContext gl_context; - -    /// Motion sensors emulation -    std::unique_ptr<Motion::MotionEmu> motion_emu;  }; diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 30554890f..7107bfc60 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -17,6 +17,7 @@  #include "core/settings.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #include "network/network.h"  EmuThread::EmuThread(GRenderWindow* render_window) @@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {  }  void GRenderWindow::closeEvent(QCloseEvent* event) { -    motion_emu = nullptr;      emit Closed();      QWidget::closeEvent(event);  } @@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {          this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),                             static_cast<unsigned>(pos.y() * pixelRatio));      } else if (event->button() == Qt::RightButton) { -        motion_emu->BeginTilt(pos.x(), pos.y()); +        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());      }  } @@ -230,14 +230,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {      qreal pixelRatio = windowPixelRatio();      this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),                       std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); -    motion_emu->Tilt(pos.x(), pos.y()); +    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());  }  void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {      if (event->button() == Qt::LeftButton)          this->TouchReleased();      else if (event->button() == Qt::RightButton) -        motion_emu->EndTilt(); +        InputCommon::GetMotionEmu()->EndTilt();  }  void GRenderWindow::focusOutEvent(QFocusEvent* event) { @@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(  }  void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { -    motion_emu = std::make_unique<Motion::MotionEmu>(*this);      this->emu_thread = emu_thread;      child->DisablePainting();  }  void GRenderWindow::OnEmulationStopping() { -    motion_emu = nullptr;      emu_thread = nullptr;      child->EnablePainting();  } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 4b3a3b3cc..6974edcbb 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -12,7 +12,6 @@  #include "common/thread.h"  #include "core/core.h"  #include "core/frontend/emu_window.h" -#include "core/frontend/motion_emu.h"  class QKeyEvent;  class QScreen; @@ -158,9 +157,6 @@ private:      EmuThread* emu_thread; -    /// Motion sensors emulation -    std::unique_ptr<Motion::MotionEmu> motion_emu; -  protected:      void showEvent(QShowEvent* event) override;  }; diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 75abb4ce6..6e42db007 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -57,6 +57,11 @@ void Config::ReadValues() {              Settings::values.analogs[i] = default_param;      } +    Settings::values.motion_device = +        qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") +            .toString() +            .toStdString(); +      qt_config->endGroup();      qt_config->beginGroup("Core"); @@ -203,6 +208,7 @@ void Config::SaveValues() {          qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),                              QString::fromStdString(Settings::values.analogs[i]));      } +    qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));      qt_config->endGroup();      qt_config->beginGroup("Core"); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 360f407f3..53bd50eb2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,7 +33,6 @@ set(SRCS              frontend/camera/interface.cpp              frontend/emu_window.cpp              frontend/framebuffer_layout.cpp -            frontend/motion_emu.cpp              gdbstub/gdbstub.cpp              hle/config_mem.cpp              hle/applets/applet.cpp @@ -226,7 +225,6 @@ set(HEADERS              frontend/emu_window.h              frontend/framebuffer_layout.h              frontend/input.h -            frontend/motion_emu.h              gdbstub/gdbstub.h              hle/config_mem.h              hle/function_wrappers.h @@ -388,7 +386,7 @@ set(HEADERS  create_directory_groups(${SRCS} ${HEADERS})  add_library(core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) +target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)  target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)  if (ENABLE_WEB_SERVICE)      target_link_libraries(core PUBLIC json-headers web_service) diff --git a/src/core/core.cpp b/src/core/core.cpp index d08f18623..5332318cf 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -19,6 +19,7 @@  #include "core/loader/loader.h"  #include "core/memory_setup.h"  #include "core/settings.h" +#include "network/network.h"  #include "video_core/video_core.h"  namespace Core { @@ -188,6 +189,10 @@ void System::Shutdown() {      cpu_core = nullptr;      app_loader = nullptr;      telemetry_session = nullptr; +    if (auto room_member = Network::GetRoomMember().lock()) { +        Network::GameInfo game_info{}; +        room_member->SendGameInfo(game_info); +    }      LOG_DEBUG(Core, "Shutdown OK");  } diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 4f7d54a33..60b20d4e2 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -62,29 +62,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {      TouchPressed(framebuffer_x, framebuffer_y);  } -void EmuWindow::AccelerometerChanged(float x, float y, float z) { -    constexpr float coef = 512; - -    std::lock_guard<std::mutex> lock(accel_mutex); - -    // TODO(wwylele): do a time stretch as it in GyroscopeChanged -    // The time stretch formula should be like -    // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity -    accel_x = static_cast<s16>(x * coef); -    accel_y = static_cast<s16>(y * coef); -    accel_z = static_cast<s16>(z * coef); -} - -void EmuWindow::GyroscopeChanged(float x, float y, float z) { -    constexpr float FULL_FPS = 60; -    float coef = GetGyroscopeRawToDpsCoefficient(); -    float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); -    std::lock_guard<std::mutex> lock(gyro_mutex); -    gyro_x = static_cast<s16>(x * coef * stretch); -    gyro_y = static_cast<s16>(y * coef * stretch); -    gyro_z = static_cast<s16>(z * coef * stretch); -} -  void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {      Layout::FramebufferLayout layout;      if (Settings::values.custom_layout == true) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 9414123a4..7bdee251c 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -69,27 +69,6 @@ public:      void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);      /** -     * Signal accelerometer state has changed. -     * @param x X-axis accelerometer value -     * @param y Y-axis accelerometer value -     * @param z Z-axis accelerometer value -     * @note all values are in unit of g (gravitational acceleration). -     *    e.g. x = 1.0 means 9.8m/s^2 in x direction. -     * @see GetAccelerometerState for axis explanation. -     */ -    void AccelerometerChanged(float x, float y, float z); - -    /** -     * Signal gyroscope state has changed. -     * @param x X-axis accelerometer value -     * @param y Y-axis accelerometer value -     * @param z Z-axis accelerometer value -     * @note all values are in deg/sec. -     * @see GetGyroscopeState for axis explanation. -     */ -    void GyroscopeChanged(float x, float y, float z); - -    /**       * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).       * @note This should be called by the core emu thread to get a state set by the window thread.       * @todo Fix this function to be thread-safe. @@ -101,52 +80,6 @@ public:      }      /** -     * Gets the current accelerometer state (acceleration along each three axis). -     * Axis explained: -     *   +x is the same direction as LEFT on D-pad. -     *   +y is normal to the touch screen, pointing outward. -     *   +z is the same direction as UP on D-pad. -     * Units: -     *   1 unit of return value = 1/512 g (measured by hw test), -     *   where g is the gravitational acceleration (9.8 m/sec2). -     * @note This should be called by the core emu thread to get a state set by the window thread. -     * @return std::tuple of (x, y, z) -     */ -    std::tuple<s16, s16, s16> GetAccelerometerState() { -        std::lock_guard<std::mutex> lock(accel_mutex); -        return std::make_tuple(accel_x, accel_y, accel_z); -    } - -    /** -     * Gets the current gyroscope state (angular rates about each three axis). -     * Axis explained: -     *   +x is the same direction as LEFT on D-pad. -     *   +y is normal to the touch screen, pointing outward. -     *   +z is the same direction as UP on D-pad. -     * Orientation is determined by right-hand rule. -     * Units: -     *   1 unit of return value = (1/coef) deg/sec, -     *   where coef is the return value of GetGyroscopeRawToDpsCoefficient(). -     * @note This should be called by the core emu thread to get a state set by the window thread. -     * @return std::tuple of (x, y, z) -     */ -    std::tuple<s16, s16, s16> GetGyroscopeState() { -        std::lock_guard<std::mutex> lock(gyro_mutex); -        return std::make_tuple(gyro_x, gyro_y, gyro_z); -    } - -    /** -     * Gets the coefficient for units conversion of gyroscope state. -     * The conversion formula is r = coefficient * v, -     * where v is angular rate in deg/sec, -     * and r is the gyroscope state. -     * @return float-type coefficient -     */ -    f32 GetGyroscopeRawToDpsCoefficient() const { -        return 14.375f; // taken from hw test, and gyroscope's document -    } - -    /**       * Returns currently active configuration.       * @note Accesses to the returned object need not be consistent because it may be modified in       * another thread @@ -187,12 +120,6 @@ protected:          touch_x = 0;          touch_y = 0;          touch_pressed = false; -        accel_x = 0; -        accel_y = -512; -        accel_z = 0; -        gyro_x = 0; -        gyro_y = 0; -        gyro_z = 0;      }      virtual ~EmuWindow() {} @@ -255,16 +182,6 @@ private:      u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)      u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) -    std::mutex accel_mutex; -    s16 accel_x; ///< Accelerometer X-axis value in native 3DS units -    s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units -    s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units - -    std::mutex gyro_mutex; -    s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units -    s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units -    s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units -      /**       * Clip the provided coordinates to be inside the touchscreen area.       */ diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 0a5713dc0..5916a901d 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -11,6 +11,7 @@  #include <utility>  #include "common/logging/log.h"  #include "common/param_package.h" +#include "common/vector_math.h"  namespace Input { @@ -107,4 +108,22 @@ using ButtonDevice = InputDevice<bool>;   */  using AnalogDevice = InputDevice<std::tuple<float, float>>; +/** + * A motion device is an input device that returns a tuple of accelerometer state vector and + * gyroscope state vector. + * + * For both vectors: + *   x+ is the same direction as LEFT on D-pad. + *   y+ is normal to the touch screen, pointing outward. + *   z+ is the same direction as UP on D-pad. + * + * For accelerometer state vector + *   Units: g (gravitational acceleration) + * + * For gyroscope state vector: + *   Orientation is determined by right-hand rule. + *   Units: deg/sec + */ +using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>; +  } // namespace Input diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp deleted file mode 100644 index 9a5b3185d..000000000 --- a/src/core/frontend/motion_emu.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/math_util.h" -#include "common/quaternion.h" -#include "core/frontend/emu_window.h" -#include "core/frontend/motion_emu.h" - -namespace Motion { - -static constexpr int update_millisecond = 100; -static constexpr auto update_duration = -    std::chrono::duration_cast<std::chrono::steady_clock::duration>( -        std::chrono::milliseconds(update_millisecond)); - -MotionEmu::MotionEmu(EmuWindow& emu_window) -    : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {} - -MotionEmu::~MotionEmu() { -    if (motion_emu_thread.joinable()) { -        shutdown_event.Set(); -        motion_emu_thread.join(); -    } -} - -void MotionEmu::MotionEmuThread(EmuWindow& emu_window) { -    auto update_time = std::chrono::steady_clock::now(); -    Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); -    Math::Quaternion<float> old_q; - -    while (!shutdown_event.WaitUntil(update_time)) { -        update_time += update_duration; -        old_q = q; - -        { -            std::lock_guard<std::mutex> guard(tilt_mutex); - -            // Find the quaternion describing current 3DS tilting -            q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), -                               tilt_angle); -        } - -        auto inv_q = q.Inverse(); - -        // Set the gravity vector in world space -        auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); - -        // Find the angular rate vector in world space -        auto angular_rate = ((q - old_q) * inv_q).xyz * 2; -        angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; - -        // Transform the two vectors from world space to 3DS space -        gravity = QuaternionRotate(inv_q, gravity); -        angular_rate = QuaternionRotate(inv_q, angular_rate); - -        // Update the sensor state -        emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z); -        emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z); -    } -} - -void MotionEmu::BeginTilt(int x, int y) { -    mouse_origin = Math::MakeVec(x, y); -    is_tilting = true; -} - -void MotionEmu::Tilt(int x, int y) { -    constexpr float SENSITIVITY = 0.01f; -    auto mouse_move = Math::MakeVec(x, y) - mouse_origin; -    if (is_tilting) { -        std::lock_guard<std::mutex> guard(tilt_mutex); -        if (mouse_move.x == 0 && mouse_move.y == 0) { -            tilt_angle = 0; -        } else { -            tilt_direction = mouse_move.Cast<float>(); -            tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f, -                                         MathUtil::PI * 0.5f); -        } -    } -} - -void MotionEmu::EndTilt() { -    std::lock_guard<std::mutex> guard(tilt_mutex); -    tilt_angle = 0; -    is_tilting = false; -} - -} // namespace Motion diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h deleted file mode 100644 index 99d41a726..000000000 --- a/src/core/frontend/motion_emu.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once -#include "common/thread.h" -#include "common/vector_math.h" - -class EmuWindow; - -namespace Motion { - -class MotionEmu final { -public: -    MotionEmu(EmuWindow& emu_window); -    ~MotionEmu(); - -    /** -     * Signals that a motion sensor tilt has begun. -     * @param x the x-coordinate of the cursor -     * @param y the y-coordinate of the cursor -     */ -    void BeginTilt(int x, int y); - -    /** -     * Signals that a motion sensor tilt is occurring. -     * @param x the x-coordinate of the cursor -     * @param y the y-coordinate of the cursor -     */ -    void Tilt(int x, int y); - -    /** -     * Signals that a motion sensor tilt has ended. -     */ -    void EndTilt(); - -private: -    Math::Vec2<int> mouse_origin; - -    std::mutex tilt_mutex; -    Math::Vec2<float> tilt_direction; -    float tilt_angle = 0; - -    bool is_tilting = false; - -    Common::Event shutdown_event; -    std::thread motion_emu_thread; - -    void MotionEmuThread(EmuWindow& emu_window); -}; - -} // namespace Motion diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 2014b8461..31f34a7ae 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -7,6 +7,7 @@  #include <cmath>  #include <memory>  #include "common/logging/log.h" +#include "core/core.h"  #include "core/core_timing.h"  #include "core/frontend/emu_window.h"  #include "core/frontend/input.h" @@ -50,10 +51,14 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;  constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;  constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; +constexpr float accelerometer_coef = 512.0f; // measured from hw test result +constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call +  static std::atomic<bool> is_device_reload_pending;  static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>      buttons;  static std::unique_ptr<Input::AnalogDevice> circle_pad; +static std::unique_ptr<Input::MotionDevice> motion_device;  DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {      // 30 degree and 60 degree are angular thresholds for directions @@ -90,6 +95,7 @@ static void LoadInputDevices() {                     buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);      circle_pad = Input::CreateDevice<Input::AnalogDevice>(          Settings::values.analogs[Settings::NativeAnalog::CirclePad]); +    motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);  }  static void UnloadInputDevices() { @@ -97,6 +103,7 @@ static void UnloadInputDevices() {          button.reset();      }      circle_pad.reset(); +    motion_device.reset();  }  static void UpdatePadCallback(u64 userdata, int cycles_late) { @@ -193,10 +200,19 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {      mem->accelerometer.index = next_accelerometer_index;      next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); +    Math::Vec3<float> accel; +    std::tie(accel, std::ignore) = motion_device->GetStatus(); +    accel *= accelerometer_coef; +    // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback +    // The time stretch formula should be like +    // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity +      AccelerometerDataEntry& accelerometer_entry =          mem->accelerometer.entries[mem->accelerometer.index]; -    std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) = -        VideoCore::g_emu_window->GetAccelerometerState(); + +    accelerometer_entry.x = static_cast<s16>(accel.x); +    accelerometer_entry.y = static_cast<s16>(accel.y); +    accelerometer_entry.z = static_cast<s16>(accel.z);      // Make up "raw" entry      // TODO(wwylele): @@ -227,8 +243,14 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {      next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();      GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; -    std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) = -        VideoCore::g_emu_window->GetGyroscopeState(); + +    Math::Vec3<float> gyro; +    std::tie(std::ignore, gyro) = motion_device->GetStatus(); +    double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); +    gyro *= gyroscope_coef * stretch; +    gyroscope_entry.x = static_cast<s16>(gyro.x); +    gyroscope_entry.y = static_cast<s16>(gyro.y); +    gyroscope_entry.z = static_cast<s16>(gyro.z);      // Make up "raw" entry      mem->gyroscope.raw_entry.x = gyroscope_entry.x; @@ -326,7 +348,7 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {      cmd_buff[1] = RESULT_SUCCESS.raw; -    f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); +    f32 coef = gyroscope_coef;      memcpy(&cmd_buff[2], &coef, 4);  } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c007069a9..7aff7f29b 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -20,6 +20,7 @@  #include "core/loader/ncch.h"  #include "core/loader/smdh.h"  #include "core/memory.h" +#include "network/network.h"  ////////////////////////////////////////////////////////////////////////////////////////////////////  // Loader namespace @@ -350,6 +351,13 @@ ResultStatus AppLoader_NCCH::Load() {      Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); +    if (auto room_member = Network::GetRoomMember().lock()) { +        Network::GameInfo game_info; +        ReadTitle(game_info.name); +        game_info.id = ncch_header.program_id; +        room_member->SendGameInfo(game_info); +    } +      is_loaded = true; // Set state to loaded      result = LoadExec(); // Load the executable into memory for booting diff --git a/src/core/settings.h b/src/core/settings.h index ee16bb90a..7e15b119b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -79,6 +79,7 @@ struct Values {      // Controls      std::array<std::string, NativeButton::NumButtons> buttons;      std::array<std::string, NativeAnalog::NumAnalogs> analogs; +    std::string motion_device;      // Core      bool use_cpu_jit; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index e3e36ada7..92792a702 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -2,12 +2,14 @@ set(SRCS              analog_from_button.cpp              keyboard.cpp              main.cpp +            motion_emu.cpp              )  set(HEADERS              analog_from_button.h              keyboard.h              main.h +            motion_emu.h              )  if(SDL2_FOUND) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 699f41e6b..557353740 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -7,6 +7,7 @@  #include "input_common/analog_from_button.h"  #include "input_common/keyboard.h"  #include "input_common/main.h" +#include "input_common/motion_emu.h"  #ifdef HAVE_SDL2  #include "input_common/sdl/sdl.h"  #endif @@ -14,12 +15,16 @@  namespace InputCommon {  static std::shared_ptr<Keyboard> keyboard; +static std::shared_ptr<MotionEmu> motion_emu;  void Init() { -    keyboard = std::make_shared<InputCommon::Keyboard>(); +    keyboard = std::make_shared<Keyboard>();      Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);      Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", -                                                std::make_shared<InputCommon::AnalogFromButton>()); +                                                std::make_shared<AnalogFromButton>()); +    motion_emu = std::make_shared<MotionEmu>(); +    Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); +  #ifdef HAVE_SDL2      SDL::Init();  #endif @@ -29,6 +34,8 @@ void Shutdown() {      Input::UnregisterFactory<Input::ButtonDevice>("keyboard");      keyboard.reset();      Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); +    Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); +    motion_emu.reset();  #ifdef HAVE_SDL2      SDL::Shutdown(); @@ -39,6 +46,10 @@ Keyboard* GetKeyboard() {      return keyboard.get();  } +MotionEmu* GetMotionEmu() { +    return motion_emu.get(); +} +  std::string GenerateKeyboardParam(int key_code) {      Common::ParamPackage param{          {"engine", "keyboard"}, {"code", std::to_string(key_code)}, diff --git a/src/input_common/main.h b/src/input_common/main.h index e7fb3890e..5604f0fa8 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -19,6 +19,11 @@ class Keyboard;  /// Gets the keyboard button device factory.  Keyboard* GetKeyboard(); +class MotionEmu; + +/// Gets the motion emulation factory. +MotionEmu* GetMotionEmu(); +  /// Generates a serialized param package for creating a keyboard button device  std::string GenerateKeyboardParam(int key_code); diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp new file mode 100644 index 000000000..a1761f184 --- /dev/null +++ b/src/input_common/motion_emu.cpp @@ -0,0 +1,165 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <mutex> +#include <thread> +#include <tuple> +#include "common/math_util.h" +#include "common/quaternion.h" +#include "common/thread.h" +#include "common/vector_math.h" +#include "input_common/motion_emu.h" + +namespace InputCommon { + +// Implementation class of the motion emulation device +class MotionEmuDevice { +public: +    MotionEmuDevice(int update_millisecond, float sensitivity) +        : update_millisecond(update_millisecond), +          update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>( +              std::chrono::milliseconds(update_millisecond))), +          sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} + +    ~MotionEmuDevice() { +        if (motion_emu_thread.joinable()) { +            shutdown_event.Set(); +            motion_emu_thread.join(); +        } +    } + +    void BeginTilt(int x, int y) { +        mouse_origin = Math::MakeVec(x, y); +        is_tilting = true; +    } + +    void Tilt(int x, int y) { +        auto mouse_move = Math::MakeVec(x, y) - mouse_origin; +        if (is_tilting) { +            std::lock_guard<std::mutex> guard(tilt_mutex); +            if (mouse_move.x == 0 && mouse_move.y == 0) { +                tilt_angle = 0; +            } else { +                tilt_direction = mouse_move.Cast<float>(); +                tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f, +                                             MathUtil::PI * 0.5f); +            } +        } +    } + +    void EndTilt() { +        std::lock_guard<std::mutex> guard(tilt_mutex); +        tilt_angle = 0; +        is_tilting = false; +    } + +    std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() { +        std::lock_guard<std::mutex> guard(status_mutex); +        return status; +    } + +private: +    const int update_millisecond; +    const std::chrono::steady_clock::duration update_duration; +    const float sensitivity; + +    Math::Vec2<int> mouse_origin; + +    std::mutex tilt_mutex; +    Math::Vec2<float> tilt_direction; +    float tilt_angle = 0; + +    bool is_tilting = false; + +    Common::Event shutdown_event; +    std::thread motion_emu_thread; + +    std::tuple<Math::Vec3<float>, Math::Vec3<float>> status; +    std::mutex status_mutex; + +    void MotionEmuThread() { +        auto update_time = std::chrono::steady_clock::now(); +        Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); +        Math::Quaternion<float> old_q; + +        while (!shutdown_event.WaitUntil(update_time)) { +            update_time += update_duration; +            old_q = q; + +            { +                std::lock_guard<std::mutex> guard(tilt_mutex); + +                // Find the quaternion describing current 3DS tilting +                q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), +                                   tilt_angle); +            } + +            auto inv_q = q.Inverse(); + +            // Set the gravity vector in world space +            auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); + +            // Find the angular rate vector in world space +            auto angular_rate = ((q - old_q) * inv_q).xyz * 2; +            angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; + +            // Transform the two vectors from world space to 3DS space +            gravity = QuaternionRotate(inv_q, gravity); +            angular_rate = QuaternionRotate(inv_q, angular_rate); + +            // Update the sensor state +            { +                std::lock_guard<std::mutex> guard(status_mutex); +                status = std::make_tuple(gravity, angular_rate); +            } +        } +    } +}; + +// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as +// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory +// can forward all the inputs to the implementation only when it is valid. +class MotionEmuDeviceWrapper : public Input::MotionDevice { +public: +    MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { +        device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); +    } + +    std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const { +        return device->GetStatus(); +    } + +    std::shared_ptr<MotionEmuDevice> device; +}; + +std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) { +    int update_period = params.Get("update_period", 100); +    float sensitivity = params.Get("sensitivity", 0.01f); +    auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity); +    // Previously created device is disconnected here. Having two motion devices for 3DS is not +    // expected. +    current_device = device_wrapper->device; +    return std::move(device_wrapper); +} + +void MotionEmu::BeginTilt(int x, int y) { +    if (auto ptr = current_device.lock()) { +        ptr->BeginTilt(x, y); +    } +} + +void MotionEmu::Tilt(int x, int y) { +    if (auto ptr = current_device.lock()) { +        ptr->Tilt(x, y); +    } +} + +void MotionEmu::EndTilt() { +    if (auto ptr = current_device.lock()) { +        ptr->EndTilt(); +    } +} + +} // namespace InputCommon diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h new file mode 100644 index 000000000..7a7e22467 --- /dev/null +++ b/src/input_common/motion_emu.h @@ -0,0 +1,46 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/input.h" + +namespace InputCommon { + +class MotionEmuDevice; + +class MotionEmu : public Input::Factory<Input::MotionDevice> { +public: +    /** +     * Creates a motion device emulated from mouse input +     * @param params contains parameters for creating the device: +     *     - "update_period": update period in milliseconds +     *     - "sensitivity": the coefficient converting mouse movement to tilting angle +     */ +    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; + +    /** +     * Signals that a motion sensor tilt has begun. +     * @param x the x-coordinate of the cursor +     * @param y the y-coordinate of the cursor +     */ +    void BeginTilt(int x, int y); + +    /** +     * Signals that a motion sensor tilt is occurring. +     * @param x the x-coordinate of the cursor +     * @param y the y-coordinate of the cursor +     */ +    void Tilt(int x, int y); + +    /** +     * Signals that a motion sensor tilt has ended. +     */ +    void EndTilt(); + +private: +    std::weak_ptr<MotionEmuDevice> current_device; +}; + +} // namespace InputCommon diff --git a/src/network/packet.cpp b/src/network/packet.cpp index 660e92c0d..cc60f2fbc 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -13,6 +13,18 @@  namespace Network { +#ifndef htonll +u64 htonll(u64 x) { +    return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)); +} +#endif + +#ifndef ntohll +u64 ntohll(u64 x) { +    return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32)); +} +#endif +  void Packet::Append(const void* in_data, std::size_t size_in_bytes) {      if (in_data && (size_in_bytes > 0)) {          std::size_t start = data.size(); @@ -100,6 +112,20 @@ Packet& Packet::operator>>(u32& out_data) {      return *this;  } +Packet& Packet::operator>>(s64& out_data) { +    s64 value; +    Read(&value, sizeof(value)); +    out_data = ntohll(value); +    return *this; +} + +Packet& Packet::operator>>(u64& out_data) { +    u64 value; +    Read(&value, sizeof(value)); +    out_data = ntohll(value); +    return *this; +} +  Packet& Packet::operator>>(float& out_data) {      Read(&out_data, sizeof(out_data));      return *this; @@ -183,6 +209,18 @@ Packet& Packet::operator<<(u32 in_data) {      return *this;  } +Packet& Packet::operator<<(s64 in_data) { +    s64 toWrite = htonll(in_data); +    Append(&toWrite, sizeof(toWrite)); +    return *this; +} + +Packet& Packet::operator<<(u64 in_data) { +    u64 toWrite = htonll(in_data); +    Append(&toWrite, sizeof(toWrite)); +    return *this; +} +  Packet& Packet::operator<<(float in_data) {      Append(&in_data, sizeof(in_data));      return *this; diff --git a/src/network/packet.h b/src/network/packet.h index 94b351ab1..5a2e58dc2 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -72,6 +72,8 @@ public:      Packet& operator>>(u16& out_data);      Packet& operator>>(s32& out_data);      Packet& operator>>(u32& out_data); +    Packet& operator>>(s64& out_data); +    Packet& operator>>(u64& out_data);      Packet& operator>>(float& out_data);      Packet& operator>>(double& out_data);      Packet& operator>>(char* out_data); @@ -89,6 +91,8 @@ public:      Packet& operator<<(u16 in_data);      Packet& operator<<(s32 in_data);      Packet& operator<<(u32 in_data); +    Packet& operator<<(s64 in_data); +    Packet& operator<<(u64 in_data);      Packet& operator<<(float in_data);      Packet& operator<<(double in_data);      Packet& operator<<(const char* in_data); diff --git a/src/network/room.cpp b/src/network/room.cpp index fbbaf8b93..261049ab0 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -4,9 +4,9 @@  #include <algorithm>  #include <atomic> +#include <mutex>  #include <random>  #include <thread> -#include <vector>  #include "enet/enet.h"  #include "network/packet.h"  #include "network/room.h" @@ -29,12 +29,14 @@ public:      struct Member {          std::string nickname;   ///< The nickname of the member. -        std::string game_name;  ///< The current game of the member +        GameInfo game_info;     ///< The current game of the member          MacAddress mac_address; ///< The assigned mac address of the member.          ENetPeer* peer;         ///< The remote peer.      };      using MemberList = std::vector<Member>; -    MemberList members; ///< Information about the members of this room. +    MemberList members;              ///< Information about the members of this room +    mutable std::mutex member_mutex; ///< Mutex for locking the members list +    /// This should be a std::shared_mutex as soon as C++17 is supported      RoomImpl()          : random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {} @@ -147,7 +149,7 @@ void Room::RoomImpl::ServerLoop() {                  case IdJoinRequest:                      HandleJoinRequest(&event);                      break; -                case IdSetGameName: +                case IdSetGameInfo:                      HandleGameNamePacket(&event);                      break;                  case IdWifiPacket: @@ -213,7 +215,10 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {      member.nickname = nickname;      member.peer = event->peer; -    members.push_back(std::move(member)); +    { +        std::lock_guard<std::mutex> lock(member_mutex); +        members.push_back(std::move(member)); +    }      // Notify everyone that the room information has changed.      BroadcastRoomInformation(); @@ -223,12 +228,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {  bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {      // A nickname is valid if it is not already taken by anybody else in the room.      // TODO(B3N30): Check for empty names, spaces, etc. +    std::lock_guard<std::mutex> lock(member_mutex);      return std::all_of(members.begin(), members.end(),                         [&nickname](const auto& member) { return member.nickname != nickname; });  }  bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {      // A MAC address is valid if it is not already taken by anybody else in the room. +    std::lock_guard<std::mutex> lock(member_mutex);      return std::all_of(members.begin(), members.end(),                         [&address](const auto& member) { return member.mac_address != address; });  } @@ -279,6 +286,7 @@ void Room::RoomImpl::SendCloseMessage() {      packet << static_cast<u8>(IdCloseRoom);      ENetPacket* enet_packet =          enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); +    std::lock_guard<std::mutex> lock(member_mutex);      for (auto& member : members) {          enet_peer_send(member.peer, 0, enet_packet);      } @@ -295,10 +303,14 @@ void Room::RoomImpl::BroadcastRoomInformation() {      packet << room_information.member_slots;      packet << static_cast<u32>(members.size()); -    for (const auto& member : members) { -        packet << member.nickname; -        packet << member.mac_address; -        packet << member.game_name; +    { +        std::lock_guard<std::mutex> lock(member_mutex); +        for (const auto& member : members) { +            packet << member.nickname; +            packet << member.mac_address; +            packet << member.game_info.name; +            packet << member.game_info.id; +        }      }      ENetPacket* enet_packet = @@ -335,11 +347,13 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {                                                   ENET_PACKET_FLAG_RELIABLE);      if (destination_address == BroadcastMac) { // Send the data to everyone except the sender +        std::lock_guard<std::mutex> lock(member_mutex);          for (const auto& member : members) {              if (member.peer != event->peer)                  enet_peer_send(member.peer, 0, enet_packet);          }      } else { // Send the data only to the destination client +        std::lock_guard<std::mutex> lock(member_mutex);          auto member = std::find_if(members.begin(), members.end(),                                     [destination_address](const Member& member) -> bool {                                         return member.mac_address == destination_address; @@ -361,6 +375,8 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {      auto CompareNetworkAddress = [event](const Member member) -> bool {          return member.peer == event->peer;      }; + +    std::lock_guard<std::mutex> lock(member_mutex);      const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);      if (sending_member == members.end()) {          return; // Received a chat message from a unknown sender @@ -385,22 +401,32 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {      in_packet.Append(event->packet->data, event->packet->dataLength);      in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type -    std::string game_name; -    in_packet >> game_name; -    auto member = -        std::find_if(members.begin(), members.end(), -                     [event](const Member& member) -> bool { return member.peer == event->peer; }); -    if (member != members.end()) { -        member->game_name = game_name; -        BroadcastRoomInformation(); +    GameInfo game_info; +    in_packet >> game_info.name; +    in_packet >> game_info.id; + +    { +        std::lock_guard<std::mutex> lock(member_mutex); +        auto member = +            std::find_if(members.begin(), members.end(), [event](const Member& member) -> bool { +                return member.peer == event->peer; +            }); +        if (member != members.end()) { +            member->game_info = game_info; +        }      } +    BroadcastRoomInformation();  }  void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {      // Remove the client from the members list. -    members.erase(std::remove_if(members.begin(), members.end(), -                                 [client](const Member& member) { return member.peer == client; }), -                  members.end()); +    { +        std::lock_guard<std::mutex> lock(member_mutex); +        members.erase( +            std::remove_if(members.begin(), members.end(), +                           [client](const Member& member) { return member.peer == client; }), +            members.end()); +    }      // Announce the change to all clients.      enet_peer_disconnect(client, 0); @@ -437,6 +463,19 @@ const RoomInformation& Room::GetRoomInformation() const {      return room_impl->room_information;  } +std::vector<Room::Member> Room::GetRoomMemberList() const { +    std::vector<Room::Member> member_list; +    std::lock_guard<std::mutex> lock(room_impl->member_mutex); +    for (const auto& member_impl : room_impl->members) { +        Member member; +        member.nickname = member_impl.nickname; +        member.mac_address = member_impl.mac_address; +        member.game_info = member_impl.game_info; +        member_list.push_back(member); +    } +    return member_list; +}; +  void Room::Destroy() {      room_impl->state = State::Closed;      room_impl->room_thread->join(); @@ -447,7 +486,10 @@ void Room::Destroy() {      }      room_impl->room_information = {};      room_impl->server = nullptr; -    room_impl->members.clear(); +    { +        std::lock_guard<std::mutex> lock(room_impl->member_mutex); +        room_impl->members.clear(); +    }      room_impl->room_information.member_slots = 0;      room_impl->room_information.name.clear();  } diff --git a/src/network/room.h b/src/network/room.h index 65b0d008a..8285a4d0c 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -7,6 +7,7 @@  #include <array>  #include <memory>  #include <string> +#include <vector>  #include "common/common_types.h"  namespace Network { @@ -21,6 +22,11 @@ struct RoomInformation {      u32 member_slots; ///< Maximum number of members in this room  }; +struct GameInfo { +    std::string name{""}; +    u64 id{0}; +}; +  using MacAddress = std::array<u8, 6>;  /// A special MAC address that tells the room we're joining to assign us a MAC address  /// automatically. @@ -34,7 +40,7 @@ enum RoomMessageTypes : u8 {      IdJoinRequest = 1,      IdJoinSuccess,      IdRoomInformation, -    IdSetGameName, +    IdSetGameInfo,      IdWifiPacket,      IdChatMessage,      IdNameCollision, @@ -51,6 +57,12 @@ public:          Closed, ///< The room is not opened and can not accept connections.      }; +    struct Member { +        std::string nickname;   ///< The nickname of the member. +        GameInfo game_info;     ///< The current game of the member +        MacAddress mac_address; ///< The assigned mac address of the member. +    }; +      Room();      ~Room(); @@ -65,6 +77,11 @@ public:      const RoomInformation& GetRoomInformation() const;      /** +     * Gets a list of the mbmers connected to the room. +     */ +    std::vector<Member> GetRoomMemberList() const; + +    /**       * Creates the socket for this room. Will bind to default address if       * server is empty string.       */ diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index dac9bacae..f229ec6fd 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -5,6 +5,7 @@  #include <atomic>  #include <list>  #include <mutex> +#include <set>  #include <thread>  #include "common/assert.h"  #include "enet/enet.h" @@ -25,6 +26,9 @@ public:      /// Information about the room we're connected to.      RoomInformation room_information; +    /// The current game name, id and version +    GameInfo current_game_info; +      std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.      void SetState(const State new_state);      bool IsConnected() const; @@ -37,6 +41,24 @@ public:      std::unique_ptr<std::thread> loop_thread;      std::mutex send_list_mutex;  ///< Mutex that controls access to the `send_list` variable.      std::list<Packet> send_list; ///< A list that stores all packets to send the async + +    template <typename T> +    using CallbackSet = std::set<CallbackHandle<T>>; +    std::mutex callback_mutex; ///< The mutex used for handling callbacks + +    class Callbacks { +    public: +        template <typename T> +        CallbackSet<T>& Get(); + +    private: +        CallbackSet<WifiPacket> callback_set_wifi_packet; +        CallbackSet<ChatEntry> callback_set_chat_messages; +        CallbackSet<RoomInformation> callback_set_room_information; +        CallbackSet<State> callback_set_state; +    }; +    Callbacks callbacks; ///< All CallbackSets to all events +      void MemberLoop();      void StartLoop(); @@ -84,12 +106,20 @@ public:       * Disconnects the RoomMember from the Room       */      void Disconnect(); + +    template <typename T> +    void Invoke(const T& data); + +    template <typename T> +    CallbackHandle<T> Bind(std::function<void(const T&)> callback);  };  // RoomMemberImpl  void RoomMember::RoomMemberImpl::SetState(const State new_state) { -    state = new_state; -    // TODO(B3N30): Invoke the callback functions +    if (state != new_state) { +        state = new_state; +        Invoke<State>(state); +    }  }  bool RoomMember::RoomMemberImpl::IsConnected() const { @@ -195,9 +225,10 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev      for (auto& member : member_information) {          packet >> member.nickname;          packet >> member.mac_address; -        packet >> member.game_name; +        packet >> member.game_info.name; +        packet >> member.game_info.id;      } -    // TODO(B3N30): Invoke callbacks +    Invoke(room_information);  }  void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { @@ -209,7 +240,7 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {      // Parse the MAC Address from the packet      packet >> mac_address; -    // TODO(B3N30): Invoke callbacks +    SetState(State::Joined);  }  void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { @@ -235,7 +266,7 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {      packet >> wifi_packet.data; -    // TODO(B3N30): Invoke callbacks +    Invoke<WifiPacket>(wifi_packet);  }  void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { @@ -248,7 +279,7 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {      ChatEntry chat_entry{};      packet >> chat_entry.nickname;      packet >> chat_entry.message; -    // TODO(B3N30): Invoke callbacks +    Invoke<ChatEntry>(chat_entry);  }  void RoomMember::RoomMemberImpl::Disconnect() { @@ -276,6 +307,46 @@ void RoomMember::RoomMemberImpl::Disconnect() {      server = nullptr;  } +template <> +RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { +    return callback_set_wifi_packet; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& +RoomMember::RoomMemberImpl::Callbacks::Get() { +    return callback_set_state; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>& +RoomMember::RoomMemberImpl::Callbacks::Get() { +    return callback_set_room_information; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() { +    return callback_set_chat_messages; +} + +template <typename T> +void RoomMember::RoomMemberImpl::Invoke(const T& data) { +    std::lock_guard<std::mutex> lock(callback_mutex); +    CallbackSet<T> callback_set = callbacks.Get<T>(); +    for (auto const& callback : callback_set) +        (*callback)(data); +} + +template <typename T> +RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind( +    std::function<void(const T&)> callback) { +    std::lock_guard<std::mutex> lock(callback_mutex); +    CallbackHandle<T> handle; +    handle = std::make_shared<std::function<void(const T&)>>(callback); +    callbacks.Get<T>().insert(handle); +    return handle; +} +  // RoomMember  RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {      room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -339,6 +410,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv          room_member_impl->SetState(State::Joining);          room_member_impl->StartLoop();          room_member_impl->SendJoinRequest(nick, preferred_mac); +        SendGameInfo(room_member_impl->current_game_info);      } else {          room_member_impl->SetState(State::CouldNotConnect);      } @@ -366,17 +438,53 @@ void RoomMember::SendChatMessage(const std::string& message) {      room_member_impl->Send(std::move(packet));  } -void RoomMember::SendGameName(const std::string& game_name) { +void RoomMember::SendGameInfo(const GameInfo& game_info) { +    room_member_impl->current_game_info = game_info; +    if (!IsConnected()) +        return; +      Packet packet; -    packet << static_cast<u8>(IdSetGameName); -    packet << game_name; +    packet << static_cast<u8>(IdSetGameInfo); +    packet << game_info.name; +    packet << game_info.id;      room_member_impl->Send(std::move(packet));  } +RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged( +    std::function<void(const RoomMember::State&)> callback) { +    return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived( +    std::function<void(const WifiPacket&)> callback) { +    return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( +    std::function<void(const RoomInformation&)> callback) { +    return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved( +    std::function<void(const ChatEntry&)> callback) { +    return room_member_impl->Bind(callback); +} + +template <typename T> +void RoomMember::Unbind(CallbackHandle<T> handle) { +    std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex); +    room_member_impl->callbacks.Get<T>().erase(handle); +} +  void RoomMember::Leave() {      room_member_impl->SetState(State::Idle);      room_member_impl->loop_thread->join();      room_member_impl->loop_thread.reset();  } +template void RoomMember::Unbind(CallbackHandle<WifiPacket>); +template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); +template void RoomMember::Unbind(CallbackHandle<RoomInformation>); +template void RoomMember::Unbind(CallbackHandle<ChatEntry>); +  } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index bc1af3a7e..98770a234 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -4,6 +4,7 @@  #pragma once +#include <functional>  #include <memory>  #include <string>  #include <vector> @@ -53,12 +54,23 @@ public:      struct MemberInformation {          std::string nickname;   ///< Nickname of the member. -        std::string game_name;  ///< Name of the game they're currently playing, or empty if they're +        GameInfo game_info;     ///< Name of the game they're currently playing, or empty if they're                                  /// not playing anything.          MacAddress mac_address; ///< MAC address associated with this member.      };      using MemberList = std::vector<MemberInformation>; +    // The handle for the callback functions +    template <typename T> +    using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>; + +    /** +     * Unbinds a callback function from the events. +     * @param handle The connection handle to disconnect +     */ +    template <typename T> +    void Unbind(CallbackHandle<T> handle); +      RoomMember();      ~RoomMember(); @@ -113,10 +125,49 @@ public:      void SendChatMessage(const std::string& message);      /** -     * Sends the current game name  to the room. -     * @param game_name The game name. +     * Sends the current game info to the room. +     * @param game_info The game information. +     */ +    void SendGameInfo(const GameInfo& game_info); + +    /** +     * Binds a function to an event that will be triggered every time the State of the member +     * changed. The function wil be called every time the event is triggered. The callback function +     * must not bind or unbind a function. Doing so will cause a deadlock +     * @param callback The function to call +     * @return A handle used for removing the function from the registered list +     */ +    CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback); + +    /** +     * Binds a function to an event that will be triggered every time a WifiPacket is received. +     * The function wil be called everytime the event is triggered. +     * The callback function must not bind or unbind a function. Doing so will cause a deadlock +     * @param callback The function to call +     * @return A handle used for removing the function from the registered list +     */ +    CallbackHandle<WifiPacket> BindOnWifiPacketReceived( +        std::function<void(const WifiPacket&)> callback); + +    /** +     * Binds a function to an event that will be triggered every time the RoomInformation changes. +     * The function wil be called every time the event is triggered. +     * The callback function must not bind or unbind a function. Doing so will cause a deadlock +     * @param callback The function to call +     * @return A handle used for removing the function from the registered list +     */ +    CallbackHandle<RoomInformation> BindOnRoomInformationChanged( +        std::function<void(const RoomInformation&)> callback); + +    /** +     * Binds a function to an event that will be triggered every time a ChatMessage is received. +     * The function wil be called every time the event is triggered. +     * The callback function must not bind or unbind a function. Doing so will cause a deadlock +     * @param callback The function to call +     * @return A handle used for removing the function from the registered list       */ -    void SendGameName(const std::string& game_name); +    CallbackHandle<ChatEntry> BindOnChatMessageRecieved( +        std::function<void(const ChatEntry&)> callback);      /**       * Leaves the current room. diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp index d61e6d572..ffd35792a 100644 --- a/src/video_core/swrasterizer/lighting.cpp +++ b/src/video_core/swrasterizer/lighting.cpp @@ -95,6 +95,12 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(                  result = Math::Dot(light_vector, normal);                  break; +            case LightingRegs::LightingLutInput::SP: { +                Math::Vec3<s32> spot_dir{light_config.spot_x.Value(), light_config.spot_y.Value(), +                                         light_config.spot_z.Value()}; +                result = Math::Dot(light_vector, spot_dir.Cast<float>() / 2047.0f); +                break; +            }              default:                  LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input));                  UNIMPLEMENTED(); @@ -125,6 +131,16 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(                     LookupLightingLut(lighting_state, static_cast<size_t>(sampler), index, delta);          }; +        // If enabled, compute spot light attenuation value +        float spot_atten = 1.0f; +        if (!lighting.IsSpotAttenDisabled(num) && +            LightingRegs::IsLightingSamplerSupported( +                lighting.config0.config, LightingRegs::LightingSampler::SpotlightAttenuation)) { +            auto lut = LightingRegs::SpotlightAttenuationSampler(num); +            spot_atten = GetLutValue(lighting.lut_input.sp, lighting.abs_lut_input.disable_sp == 0, +                                     lighting.lut_scale.sp, lut); +        } +          // Specular 0 component          float d0_lut_value = 1.0f;          if (lighting.config1.disable_lut_d0 == 0 && @@ -226,10 +242,10 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(          auto diffuse =              light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f(); -        diffuse_sum += Math::MakeVec(diffuse * dist_atten, 0.0f); +        diffuse_sum += Math::MakeVec(diffuse * dist_atten * spot_atten, 0.0f); -        specular_sum += -            Math::MakeVec((specular_0 + specular_1) * clamp_highlights * dist_atten, 0.0f); +        specular_sum += Math::MakeVec( +            (specular_0 + specular_1) * clamp_highlights * dist_atten * spot_atten, 0.0f);      }      diffuse_sum += Math::MakeVec(lighting.global_ambient.ToVec3f(), 0.0f);  | 
