Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / platform / gamepad / cocoa / GameControllerGamepadProvider.mm
1 /*
2  * Copyright (C) 2016 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "GameControllerGamepadProvider.h"
28
29 #if ENABLE(GAMEPAD) && (defined(__LP64__) || PLATFORM(IOS))
30
31 #import "GameControllerGamepad.h"
32 #import "GamepadProviderClient.h"
33 #import "Logging.h"
34 #import <GameController/GameController.h>
35 #import <wtf/NeverDestroyed.h>
36 #import <wtf/SoftLinking.h>
37
38 SOFT_LINK_FRAMEWORK_OPTIONAL(GameController)
39 SOFT_LINK_CLASS_OPTIONAL(GameController, GCController);
40 SOFT_LINK_CONSTANT_MAY_FAIL(GameController, GCControllerDidConnectNotification, NSString *)
41 SOFT_LINK_CONSTANT_MAY_FAIL(GameController, GCControllerDidDisconnectNotification, NSString *)
42
43 namespace WebCore {
44
45 static const Seconds inputNotificationDelay { 16_ms };
46
47 GameControllerGamepadProvider& GameControllerGamepadProvider::singleton()
48 {
49     static NeverDestroyed<GameControllerGamepadProvider> sharedProvider;
50     return sharedProvider;
51 }
52
53 GameControllerGamepadProvider::GameControllerGamepadProvider()
54     : m_inputNotificationTimer(RunLoop::current(), this, &GameControllerGamepadProvider::inputNotificationTimerFired)
55
56 {
57 }
58
59 void GameControllerGamepadProvider::controllerDidConnect(GCController *controller, ConnectionVisibility visibility)
60 {
61     LOG(Gamepad, "GameControllerGamepadProvider controller %p added", controller);
62
63     unsigned index = indexForNewlyConnectedDevice();
64     auto gamepad = std::make_unique<GameControllerGamepad>(controller, index);
65
66     if (m_gamepadVector.size() <= index)
67         m_gamepadVector.resize(index + 1);
68
69     m_gamepadVector[index] = gamepad.get();
70     m_gamepadMap.set(controller, WTFMove(gamepad));
71
72
73     if (visibility == ConnectionVisibility::Invisible) {
74         m_invisibleGamepads.add(m_gamepadVector[index]);
75         return;
76     }
77
78     makeInvisibileGamepadsVisible();
79
80     for (auto& client : m_clients)
81         client->platformGamepadConnected(*m_gamepadVector[index]);
82 }
83
84 void GameControllerGamepadProvider::controllerDidDisconnect(GCController *controller)
85 {
86     LOG(Gamepad, "GameControllerGamepadProvider controller %p removed", controller);
87
88     auto removedGamepad = m_gamepadMap.take(controller);
89     ASSERT(removedGamepad);
90
91     auto i = m_gamepadVector.find(removedGamepad.get());
92     if (i != notFound)
93         m_gamepadVector[i] = nullptr;
94
95     m_invisibleGamepads.remove(removedGamepad.get());
96
97     for (auto& client : m_clients)
98         client->platformGamepadDisconnected(*removedGamepad);
99 }
100
101 void GameControllerGamepadProvider::startMonitoringGamepads(GamepadProviderClient& client)
102 {
103     ASSERT(!m_clients.contains(&client));
104     m_clients.add(&client);
105
106     if (m_connectObserver)
107         return;
108
109     if (canLoadGCControllerDidConnectNotification()) {
110         m_connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:getGCControllerDidConnectNotification() object:nil queue:nil usingBlock:^(NSNotification *notification) {
111             GameControllerGamepadProvider::singleton().controllerDidConnect(notification.object, ConnectionVisibility::Visible);
112         }];
113     }
114
115     if (canLoadGCControllerDidDisconnectNotification()) {
116         m_disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:getGCControllerDidDisconnectNotification() object:nil queue:nil usingBlock:^(NSNotification *notification) {
117             GameControllerGamepadProvider::singleton().controllerDidDisconnect(notification.object);
118         }];
119     }
120
121     for (GCController *controller in [getGCControllerClass() controllers])
122         controllerDidConnect(controller, ConnectionVisibility::Invisible);
123 }
124
125 void GameControllerGamepadProvider::stopMonitoringGamepads(GamepadProviderClient& client)
126 {
127     ASSERT(m_clients.contains(&client));
128     m_clients.remove(&client);
129
130     if (!m_connectObserver || !m_clients.isEmpty())
131         return;
132
133     [[NSNotificationCenter defaultCenter] removeObserver:m_connectObserver.get()];
134     [[NSNotificationCenter defaultCenter] removeObserver:m_disconnectObserver.get()];
135 }
136
137 unsigned GameControllerGamepadProvider::indexForNewlyConnectedDevice()
138 {
139     unsigned index = 0;
140     while (index < m_gamepadVector.size() && m_gamepadVector[index])
141         ++index;
142
143     return index;
144 }
145
146 void GameControllerGamepadProvider::gamepadHadInput(GameControllerGamepad&, bool hadButtonPresses)
147 {
148     if (!m_inputNotificationTimer.isActive())
149         m_inputNotificationTimer.startOneShot(inputNotificationDelay);
150
151     if (hadButtonPresses)
152         m_shouldMakeInvisibileGamepadsVisible = true;
153 }
154
155 void GameControllerGamepadProvider::makeInvisibileGamepadsVisible()
156 {
157     for (auto* gamepad : m_invisibleGamepads) {
158         for (auto& client : m_clients)
159             client->platformGamepadConnected(*gamepad);
160     }
161
162     m_invisibleGamepads.clear();
163 }
164
165 void GameControllerGamepadProvider::inputNotificationTimerFired()
166 {
167     if (m_shouldMakeInvisibileGamepadsVisible) {
168         setShouldMakeGamepadsVisibile();
169         makeInvisibileGamepadsVisible();
170     }
171
172     m_shouldMakeInvisibileGamepadsVisible = false;
173
174     dispatchPlatformGamepadInputActivity();
175 }
176
177 } // namespace WebCore
178
179 #endif // ENABLE(GAMEPAD) && (defined(__LP64__) || PLATFORM(IOS))