abstract_haptic_gamepad.cc 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "device/gamepad/abstract_haptic_gamepad.h"
  5. #include "base/bind.h"
  6. #include "base/threading/thread_task_runner_handle.h"
  7. #include "device/gamepad/gamepad_data_fetcher.h"
  8. namespace device {
  9. namespace {
  10. constexpr double kMaxDurationMillis = 5000.0; // 5 seconds
  11. bool IsValidEffectType(mojom::GamepadHapticEffectType type) {
  12. return type == mojom::GamepadHapticEffectType::
  13. GamepadHapticEffectTypeDualRumble ||
  14. type == mojom::GamepadHapticEffectType::
  15. GamepadHapticEffectTypeTriggerRumble;
  16. }
  17. } // namespace
  18. AbstractHapticGamepad::AbstractHapticGamepad() = default;
  19. AbstractHapticGamepad::~AbstractHapticGamepad() {
  20. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  21. // Shutdown() must be called to allow the device a chance to stop vibration
  22. // and release held resources.
  23. DCHECK(is_shut_down_);
  24. }
  25. void AbstractHapticGamepad::Shutdown() {
  26. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  27. DCHECK(!is_shutting_down_);
  28. is_shutting_down_ = true;
  29. // If an effect is still playing, try to stop vibration. This may fail if the
  30. // gamepad is already disconnected.
  31. if (playing_effect_callback_) {
  32. sequence_id_++;
  33. SetZeroVibration();
  34. GamepadDataFetcher::RunVibrationCallback(
  35. std::move(playing_effect_callback_), std::move(callback_runner_),
  36. mojom::GamepadHapticsResult::GamepadHapticsResultPreempted);
  37. }
  38. DoShutdown();
  39. // No vibration effects may be played once shutdown is complete.
  40. is_shut_down_ = true;
  41. }
  42. void AbstractHapticGamepad::SetZeroVibration() {
  43. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  44. SetVibration(mojom::GamepadEffectParameters::New());
  45. }
  46. double AbstractHapticGamepad::GetMaxEffectDurationMillis() {
  47. return kMaxDurationMillis;
  48. }
  49. void AbstractHapticGamepad::PlayEffect(
  50. mojom::GamepadHapticEffectType type,
  51. mojom::GamepadEffectParametersPtr params,
  52. mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback,
  53. scoped_refptr<base::SequencedTaskRunner> callback_runner) {
  54. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  55. DCHECK(!is_shut_down_);
  56. if (!IsValidEffectType(type)) {
  57. // Only dual-rumble and trigger-rumble effects are supported.
  58. GamepadDataFetcher::RunVibrationCallback(
  59. std::move(callback), std::move(callback_runner),
  60. mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
  61. return;
  62. }
  63. int sequence_id = ++sequence_id_;
  64. if (playing_effect_callback_) {
  65. // An effect is already playing on this device and will be preempted in
  66. // order to start the new effect. Finish the playing effect by calling its
  67. // callback with a "preempted" result code. Use the |callback_runner_| that
  68. // was provided with the playing effect as it may post tasks to a different
  69. // sequence than the |callback_runner| for the current effect.
  70. GamepadDataFetcher::RunVibrationCallback(
  71. std::move(playing_effect_callback_), std::move(callback_runner_),
  72. mojom::GamepadHapticsResult::GamepadHapticsResultPreempted);
  73. }
  74. if (params->start_delay > 0.0)
  75. SetZeroVibration();
  76. playing_effect_callback_ = std::move(callback);
  77. callback_runner_ = std::move(callback_runner);
  78. PlayVibrationEffect(sequence_id, std::move(params));
  79. }
  80. void AbstractHapticGamepad::ResetVibration(
  81. mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback,
  82. scoped_refptr<base::SequencedTaskRunner> callback_runner) {
  83. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  84. DCHECK(!is_shut_down_);
  85. sequence_id_++;
  86. SetZeroVibration();
  87. if (playing_effect_callback_) {
  88. // An effect is already playing on this device and will be preempted in
  89. // order to reset vibration. Finish the playing effect by calling its
  90. // callback with a "preempted" result code. Use the |callback_runner_| that
  91. // was provided with the playing effect as it may post tasks to a different
  92. // sequence than the |callback_runner| for the reset.
  93. GamepadDataFetcher::RunVibrationCallback(
  94. std::move(playing_effect_callback_), std::move(callback_runner_),
  95. mojom::GamepadHapticsResult::GamepadHapticsResultPreempted);
  96. }
  97. GamepadDataFetcher::RunVibrationCallback(
  98. std::move(callback), std::move(callback_runner),
  99. mojom::GamepadHapticsResult::GamepadHapticsResultComplete);
  100. }
  101. void AbstractHapticGamepad::PlayVibrationEffect(
  102. int sequence_id,
  103. mojom::GamepadEffectParametersPtr params) {
  104. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  105. double duration = params->duration;
  106. double start_delay = params->start_delay;
  107. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  108. FROM_HERE,
  109. base::BindOnce(&AbstractHapticGamepad::StartVibration, GetWeakPtr(),
  110. sequence_id, duration, std::move(params)),
  111. base::Milliseconds(start_delay));
  112. }
  113. void AbstractHapticGamepad::StartVibration(
  114. int sequence_id,
  115. double duration,
  116. mojom::GamepadEffectParametersPtr params) {
  117. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  118. if (is_shut_down_ || sequence_id != sequence_id_)
  119. return;
  120. SetVibration(params.Clone());
  121. const double max_duration = GetMaxEffectDurationMillis();
  122. if (duration > max_duration) {
  123. // The device does not support effects this long. Issue periodic vibration
  124. // commands until the effect is complete.
  125. double remaining_duration = duration - max_duration;
  126. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  127. FROM_HERE,
  128. base::BindOnce(&AbstractHapticGamepad::StartVibration, GetWeakPtr(),
  129. sequence_id, remaining_duration, params.Clone()),
  130. base::Milliseconds(max_duration));
  131. } else {
  132. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  133. FROM_HERE,
  134. base::BindOnce(&AbstractHapticGamepad::FinishEffect, GetWeakPtr(),
  135. sequence_id),
  136. base::Milliseconds(duration));
  137. }
  138. }
  139. void AbstractHapticGamepad::FinishEffect(int sequence_id) {
  140. DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  141. if (is_shut_down_ || sequence_id != sequence_id_)
  142. return;
  143. GamepadDataFetcher::RunVibrationCallback(
  144. std::move(playing_effect_callback_), std::move(callback_runner_),
  145. mojom::GamepadHapticsResult::GamepadHapticsResultComplete);
  146. }
  147. } // namespace device