| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "device/gamepad/abstract_haptic_gamepad.h" |
| |
| #include "base/bind.h" |
| #include "device/gamepad/gamepad_data_fetcher.h" |
| |
| namespace device { |
| namespace { |
| constexpr double kMaxDurationMillis = 5000.0; // 5 seconds |
| } // namespace |
| |
| AbstractHapticGamepad::AbstractHapticGamepad() |
| : is_shut_down_(false), sequence_id_(0), weak_factory_(this) {} |
| |
| AbstractHapticGamepad::~AbstractHapticGamepad() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // Shutdown() must be called to allow the device a chance to stop vibration |
| // and release held resources. |
| DCHECK(is_shut_down_); |
| } |
| |
| void AbstractHapticGamepad::Shutdown() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (playing_effect_callback_) { |
| sequence_id_++; |
| SetZeroVibration(); |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(playing_effect_callback_), std::move(callback_runner_), |
| mojom::GamepadHapticsResult::GamepadHapticsResultPreempted); |
| } |
| DoShutdown(); |
| is_shut_down_ = true; |
| } |
| |
| void AbstractHapticGamepad::SetZeroVibration() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| SetVibration(0.0, 0.0); |
| } |
| |
| double AbstractHapticGamepad::GetMaxEffectDurationMillis() { |
| return kMaxDurationMillis; |
| } |
| |
| void AbstractHapticGamepad::PlayEffect( |
| mojom::GamepadHapticEffectType type, |
| mojom::GamepadEffectParametersPtr params, |
| mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback, |
| scoped_refptr<base::SequencedTaskRunner> callback_runner) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!is_shut_down_); |
| if (type != |
| mojom::GamepadHapticEffectType::GamepadHapticEffectTypeDualRumble) { |
| // Only dual-rumble effects are supported. |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(callback), std::move(callback_runner), |
| mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported); |
| return; |
| } |
| |
| int sequence_id = ++sequence_id_; |
| |
| if (playing_effect_callback_) { |
| // An effect is already playing on this device and will be preempted in |
| // order to start the new effect. Finish the playing effect by calling its |
| // callback with a "preempted" result code. Use the |callback_runner_| that |
| // was provided with the playing effect as it may post tasks to a different |
| // sequence than the |callback_runner| for the current effect. |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(playing_effect_callback_), std::move(callback_runner_), |
| mojom::GamepadHapticsResult::GamepadHapticsResultPreempted); |
| } |
| if (params->start_delay > 0.0) |
| SetZeroVibration(); |
| |
| playing_effect_callback_ = std::move(callback); |
| callback_runner_ = std::move(callback_runner); |
| |
| PlayDualRumbleEffect(sequence_id, params->duration, params->start_delay, |
| params->strong_magnitude, params->weak_magnitude); |
| } |
| |
| void AbstractHapticGamepad::ResetVibration( |
| mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback, |
| scoped_refptr<base::SequencedTaskRunner> callback_runner) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!is_shut_down_); |
| sequence_id_++; |
| |
| SetZeroVibration(); |
| if (playing_effect_callback_) { |
| // An effect is already playing on this device and will be preempted in |
| // order to reset vibration. Finish the playing effect by calling its |
| // callback with a "preempted" result code. Use the |callback_runner_| that |
| // was provided with the playing effect as it may post tasks to a different |
| // sequence than the |callback_runner| for the reset. |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(playing_effect_callback_), std::move(callback_runner_), |
| mojom::GamepadHapticsResult::GamepadHapticsResultPreempted); |
| } |
| |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(callback), std::move(callback_runner), |
| mojom::GamepadHapticsResult::GamepadHapticsResultComplete); |
| } |
| |
| void AbstractHapticGamepad::PlayDualRumbleEffect(int sequence_id, |
| double duration, |
| double start_delay, |
| double strong_magnitude, |
| double weak_magnitude) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AbstractHapticGamepad::StartVibration, |
| weak_factory_.GetWeakPtr(), sequence_id, duration, |
| strong_magnitude, weak_magnitude), |
| base::TimeDelta::FromMillisecondsD(start_delay)); |
| } |
| |
| void AbstractHapticGamepad::StartVibration(int sequence_id, |
| double duration, |
| double strong_magnitude, |
| double weak_magnitude) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (is_shut_down_ || sequence_id != sequence_id_) |
| return; |
| SetVibration(strong_magnitude, weak_magnitude); |
| |
| const double max_duration = GetMaxEffectDurationMillis(); |
| if (duration > max_duration) { |
| // The device does not support effects this long. Issue periodic vibration |
| // commands until the effect is complete. |
| double remaining_duration = duration - max_duration; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AbstractHapticGamepad::StartVibration, |
| weak_factory_.GetWeakPtr(), sequence_id, |
| remaining_duration, strong_magnitude, weak_magnitude), |
| base::TimeDelta::FromMillisecondsD(max_duration)); |
| } else { |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AbstractHapticGamepad::FinishEffect, |
| weak_factory_.GetWeakPtr(), sequence_id), |
| base::TimeDelta::FromMillisecondsD(duration)); |
| } |
| } |
| |
| void AbstractHapticGamepad::FinishEffect(int sequence_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (is_shut_down_ || sequence_id != sequence_id_) |
| return; |
| |
| GamepadDataFetcher::RunVibrationCallback( |
| std::move(playing_effect_callback_), std::move(callback_runner_), |
| mojom::GamepadHapticsResult::GamepadHapticsResultComplete); |
| } |
| |
| } // namespace device |