Namespace Serenity.Feedback.Infrastructure.Services
Classes
ActingDeviceTracker
Simple holder for the last input device that drove a UI action. Written by the UI input layer (the navigate/submit/cancel callbacks, where the triggering device is known) and read by UnityGamepadTargetResolver when resolving the acting pad. Registered as a single instance in the service locator so both sides share the same value.
DualSenseOutputWriter
Drives vibration, the LED AND both adaptive triggers on a DualSense in a SINGLE HID output report, over USB.
Stock Unity exposes no adaptive-trigger output, and its built-in DualSense vibration/LED reports do not address the trigger region — so any built-in vibration or color update CLOBBERS an active trigger effect (verified on hardware). The DualSense output report is monolithic (one report carries vibration + LED + both triggers), so the only clobber-free option is to own the whole report ourselves. This writer keeps per-device state as the single source of truth and re-emits the full report on every change, exactly like DualShock4OutputWriter does for the DS4.
Two report formats are emitted: the USB report (id 0x02, 48-byte payload) and the Bluetooth
report (id 0x31, 78 bytes, CRC32-suffixed). On Linux both buses are driven through a raw hidraw handle
(Unity's Linux backend routes no HID output). On Windows USB uses Unity's ExecuteCommand, while
Bluetooth — which Unity's built-in DualSense output does not support — is driven through a raw HID
handle that pads the 0x31 report to the OS output-report length (validated on hardware). macOS drives
USB only. Methods return false for non-DualSense pads and on unsupported platforms.
DualShock4OutputWriter
Drives vibration AND the LED on a DualShock 4 in a SINGLE HID output report.
Unity's high-level SetMotorSpeeds and SetLightBarColor each send a separate
report that zeroes the other's bytes (the package even flags this with
////FIXME: SetLightBarColor and SetMotorSpeeds to not mutually respect their settings), so
on a DS4 the two effects clobber each other and the auto-reset of one clips the other. The DS4
hardware itself packs vibration and color into one report — only the wrapper splits them, and the
combining struct (DualShockHIDOutputReport) is internal. This writer mirrors that
report in our own Serenity.Feedback.Infrastructure.Services.DualShock4CombinedOutputReport and sends both together via the
public InputDevice.ExecuteCommand, exactly like native titles do. DualSense already
combines internally, so it does not go through here.
Per-device state is the single source of truth: setting motors preserves the current color
and vice-versa, which is what makes the two channels coexist and stops each reset from blanking
the other. Methods return false for non-DS4 pads (and on non-desktop platforms) so callers
fall back to the standard path.
Transport: USB uses Unity's ExecuteCommand (the 32-byte 0x05 report); Bluetooth uses the
reframed 0x11/CRC32 report sent through a raw HID handle (Serenity.Feedback.Infrastructure.Hid.GamepadHidrawTransport) — on
Linux for both buses, and on Windows for Bluetooth only (Unity's built-in DS4 output is inert over
Bluetooth). The bus is detected once and cached. macOS drives USB only.
UnityFeedbackDefinitionProvider
Unity implementation of IFeedbackDefinitionProvider. Resolves FeedbackDefinition ScriptableObjects by Id or Guid via UnityAssetUtils (Addressables / Resources), then maps them to DTOs.
UnityFeedbackService
Unity MonoBehaviour implementation of IFeedbackService and IFeedbackRuntime. Routes each effect of a definition to every channel that can play it, and owns the single global stop/lifecycle safety net (application focus/pause, scene unload, play-mode exit, disable/destroy, and the vibration-disabled setting). Per-effect timing/restoration is owned by each channel via the coroutine host this class exposes through IFeedbackRuntime.
UnityFeedbackServiceFactory
Factory that creates the UnityFeedbackService as a DontDestroyOnLoad MonoBehaviour.
The component is returned UNINITIALIZED: the installer first constructs the channels (which take the
service as their IFeedbackRuntime), then calls Initialize(IGameSettingsService, IFeedbackSettings, IFeedbackDefinitionProvider, List<IFeedbackChannel>, IEventDispatcherService, ILogService).
UnityGamepadTargetResolver
Resolves a FeedbackTarget or a PlayerInput object to a Unity UnityEngine.InputSystem.Gamepad. Returns null when a gamepad cannot be resolved; callers are expected to no-op on null.
Interfaces
IGamepadOutputBackend
A per-controller output backend that drives vibration and/or the LED on a specific gamepad family
(DualShock 4, DualSense, Switch Pro, ...) through whatever transport that family needs — Unity's HID
ExecuteCommand or a raw OS HID handle. The vibration and LED channels iterate the registered
backends in order and take the FIRST that claims the pad, falling back to Unity's built-in API when
none do.
This is the controller-extension seam: supporting a new controller means adding a backend and
registering it in the installer — no channel changes (open/closed). It is also where a future
transport swap lives (e.g. moving a PlayStation pad off Unity's ExecuteCommand onto raw HID):
add a backend that uses the other transport and the channels are unaffected.
Every method returns false for a pad this backend does not handle (wrong family,
unsupported connection, or unsupported platform) so the caller falls through to the next backend or
the default path.