use std::cell::Cell;
use std::collections::BTreeMap;
use std::ptr::NonNull;
use std::sync::mpsc::{self, Receiver, Sender};
use crate::{Axis, Button, Event, EventType, GamepadId, Update};
use block2::RcBlock;
use objc2::{MainThreadMarker, rc::Retained, runtime::Bool};
use objc2_game_controller::{
GCController, GCControllerAxisInput, GCControllerButtonInput, GCDevice, GCExtendedGamepad,
};
type ButtonHandler = RcBlock<dyn Fn(NonNull<GCControllerButtonInput>, f32, Bool)>;
type AxisHandler = RcBlock<dyn Fn(NonNull<GCControllerAxisInput>, f32)>;
struct Pad {
controller: Retained<GCController>,
buttons: Vec<(Retained<GCControllerButtonInput>, ButtonHandler)>,
axes: Vec<(Retained<GCControllerAxisInput>, AxisHandler)>,
}
impl Pad {
fn new(
controller: Retained<GCController>,
profile: &GCExtendedGamepad,
id: GamepadId,
sender: &Sender<Update>,
) -> Self {
let (buttons, axes) = unsafe {
let dpad = profile.dpad();
let mut buttons = vec![
(Button::South, profile.buttonA()),
(Button::East, profile.buttonB()),
(Button::West, profile.buttonX()),
(Button::North, profile.buttonY()),
(Button::DPadUp, dpad.up()),
(Button::DPadDown, dpad.down()),
(Button::DPadLeft, dpad.left()),
(Button::DPadRight, dpad.right()),
(Button::LeftTrigger, profile.leftShoulder()),
(Button::RightTrigger, profile.rightShoulder()),
(Button::LeftTrigger2, profile.leftTrigger()),
(Button::RightTrigger2, profile.rightTrigger()),
(Button::Start, profile.buttonMenu()),
];
for (button, input) in [
(Button::Select, profile.buttonOptions()),
(Button::Mode, profile.buttonHome()),
(Button::LeftThumb, profile.leftThumbstickButton()),
(Button::RightThumb, profile.rightThumbstickButton()),
] {
if let Some(input) = input {
buttons.push((button, input));
}
}
let left = profile.leftThumbstick();
let right = profile.rightThumbstick();
(
buttons,
vec![
(Axis::LeftStickX, left.xAxis()),
(Axis::LeftStickY, left.yAxis()),
(Axis::RightStickX, right.xAxis()),
(Axis::RightStickY, right.yAxis()),
],
)
};
let mut button_handlers = Vec::with_capacity(buttons.len());
for (button, input) in buttons {
let events = sender.clone();
let handler: ButtonHandler = RcBlock::new(
move |_: NonNull<GCControllerButtonInput>, _: f32, pressed: Bool| {
let event = if pressed.as_bool() {
EventType::ButtonPressed(button)
} else {
EventType::ButtonReleased(button)
};
let _ = events.send(Update::Input(Event { id, event }));
},
);
unsafe {
input.setPressedChangedHandler(RcBlock::as_ptr(&handler));
}
if unsafe { input.isPressed() } {
let _ = sender.send(Update::Input(Event {
id,
event: EventType::ButtonPressed(button),
}));
}
button_handlers.push((input, handler));
}
let mut axis_handlers = Vec::with_capacity(axes.len());
for (axis, input) in axes {
let events = sender.clone();
let handler: AxisHandler =
RcBlock::new(move |_: NonNull<GCControllerAxisInput>, value: f32| {
let _ = events.send(Update::Input(Event {
id,
event: EventType::AxisChanged(axis, value),
}));
});
unsafe {
input.setValueChangedHandler(RcBlock::as_ptr(&handler));
let _ = sender.send(Update::Input(Event {
id,
event: EventType::AxisChanged(axis, input.value()),
}));
}
axis_handlers.push((input, handler));
}
Self {
controller,
buttons: button_handlers,
axes: axis_handlers,
}
}
}
impl Drop for Pad {
fn drop(&mut self) {
unsafe {
for (input, handler) in &self.buttons {
if std::ptr::eq(input.pressedChangedHandler(), RcBlock::as_ptr(handler)) {
input.setPressedChangedHandler(std::ptr::null_mut());
}
}
for (input, handler) in &self.axes {
if std::ptr::eq(input.valueChangedHandler(), RcBlock::as_ptr(handler)) {
input.setValueChangedHandler(std::ptr::null_mut());
}
}
}
}
}
pub(crate) struct Backend {
_main_thread: MainThreadMarker,
connections: Connections<Pad>,
sender: Sender<Update>,
receiver: Receiver<Update>,
}
impl Backend {
pub(crate) fn new() -> Result<Self, String> {
let main_thread =
MainThreadMarker::new().ok_or("Create gamepad input on the window's main thread")?;
BACKGROUND_EVENTS.with(|owners| unsafe {
owners.acquire(
|| GCController::shouldMonitorBackgroundEvents(),
|on| GCController::setShouldMonitorBackgroundEvents(on),
);
});
let (sender, receiver) = mpsc::channel();
Ok(Self {
_main_thread: main_thread,
connections: Connections {
pads: BTreeMap::new(),
next_id: 0,
},
sender,
receiver,
})
}
pub(crate) fn poll(&mut self) -> Vec<Update> {
let array = unsafe { GCController::controllers() };
let controllers: Vec<_> = (0..array.count()).map(|i| array.objectAtIndex(i)).collect();
let sender = &self.sender;
self.connections.poll(
controllers,
&self.receiver,
|pad, controller| {
std::ptr::eq(
Retained::as_ptr(controller),
Retained::as_ptr(&pad.controller),
)
},
|controller, id| {
let profile = unsafe { controller.extendedGamepad() }?;
let name = unsafe { controller.vendorName() }
.map_or_else(|| "Controller".into(), |s| s.to_string());
Some((Pad::new(controller, &profile, id, sender), name))
},
)
}
}
struct Connections<P> {
pads: BTreeMap<GamepadId, P>,
next_id: usize,
}
impl<P> Connections<P> {
fn poll<C>(
&mut self,
present: Vec<C>,
receiver: &Receiver<Update>,
same: impl Fn(&P, &C) -> bool,
mut connect: impl FnMut(C, GamepadId) -> Option<(P, String)>,
) -> Vec<Update> {
let mut updates: Vec<_> = receiver.try_iter().collect();
self.pads.retain(|id, pad| {
let kept = present.iter().any(|controller| same(pad, controller));
if !kept {
updates.push(Update::Input(Event {
id: *id,
event: EventType::Disconnected,
}));
}
kept
});
for controller in present {
if self.pads.values().any(|pad| same(pad, &controller)) {
continue;
}
let id = GamepadId(self.next_id);
let Some((pad, name)) = connect(controller, id) else {
continue;
};
self.next_id += 1;
updates.push(Update::Connected(id, name));
self.pads.insert(id, pad);
}
updates.extend(receiver.try_iter());
updates
}
}
impl Drop for Backend {
fn drop(&mut self) {
self.connections.pads.clear();
BACKGROUND_EVENTS.with(|owners| unsafe {
owners.release(|on| GCController::setShouldMonitorBackgroundEvents(on));
});
}
}
thread_local! {
static BACKGROUND_EVENTS: BackgroundEvents = const { BackgroundEvents::new() };
}
struct BackgroundEvents {
owners: Cell<usize>,
previous: Cell<bool>,
}
impl BackgroundEvents {
const fn new() -> Self {
Self {
owners: Cell::new(0),
previous: Cell::new(false),
}
}
fn acquire(&self, read: impl FnOnce() -> bool, set: impl FnOnce(bool)) {
if self.owners.get() == 0 {
self.previous.set(read());
set(true);
}
self.owners.set(self.owners.get() + 1);
}
fn release(&self, set: impl FnOnce(bool)) {
let owners = self.owners.get() - 1;
self.owners.set(owners);
if owners == 0 {
set(self.previous.get());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Button, State};
#[test]
fn replacing_a_live_owner_keeps_background_events_on() {
let flag = Cell::new(false);
let owners = BackgroundEvents::new();
let acquire = || owners.acquire(|| flag.get(), |on| flag.set(on));
let release = || owners.release(|on| flag.set(on));
acquire();
assert!(flag.get());
acquire();
release();
assert!(flag.get(), "the live owner keeps background events");
release();
assert!(!flag.get(), "the last owner restores the original value");
flag.set(true);
acquire();
release();
assert!(flag.get());
}
#[test]
fn input_queued_before_an_unplug_precedes_its_disconnect() {
let (sender, receiver) = mpsc::channel();
let mut connections = Connections {
pads: BTreeMap::new(),
next_id: 0,
};
let mut state = State::default();
let mut reported = Vec::new();
let mut poll = |present: Vec<u32>| {
let updates = connections.poll(
present,
&receiver,
|pad: &u32, controller: &u32| pad == controller,
|controller, _| Some((controller, "pad".into())),
);
reported.extend(updates.into_iter().filter_map(|u| state.apply(u)));
};
let input = |id, event| Update::Input(Event { id, event });
let (old, new) = (GamepadId(0), GamepadId(1));
poll(vec![1]);
sender
.send(input(old, EventType::ButtonPressed(Button::South)))
.unwrap();
sender
.send(input(old, EventType::ButtonReleased(Button::South)))
.unwrap();
poll(vec![2]);
sender
.send(input(old, EventType::ButtonPressed(Button::South)))
.unwrap();
poll(vec![2]);
let event = |id, event| Event { id, event };
assert_eq!(
reported,
[
event(old, EventType::Connected),
event(old, EventType::ButtonPressed(Button::South)),
event(old, EventType::ButtonReleased(Button::South)),
event(old, EventType::Disconnected),
event(new, EventType::Connected),
]
);
assert!(!state.pads[&new].is_pressed(Button::South));
}
}