Pie Noon
An open source project by FPL.
 All Classes Pages
gpg_multiplayer.h
1 // Copyright 2014 Google Inc. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // gpg_multiplayer.h
16 //
17 // Multiplayer library using the Nearby Connections API in the Google Play
18 // Games SDK.
19 //
20 // This library wraps the new NearbyConnections library so that game code
21 // doesn't have to worry about:
22 // - callbacks
23 // - synchronization/thread safety
24 // - prompting the user to connect
25 // - error handling (not 100% yet)
26 //
27 // It provides an interface where the game code can just call Update() each
28 // frame, and then retreive incoming messages from a queue whenever is
29 // convenient.
30 //
31 // To start, call Initialize() and pass in a unique service ID for your game.
32 // After this point you should start calling Update() each frame. You can also
33 // call set_my_instance_name() to set a human-readable name for your instance
34 // (maybe your Play Games full name, or your device's name).
35 //
36 // When you want to host a game, call StartAdvertising(). This library handles
37 // prompting the player when someone connects to accept/reject them. When you
38 // have enough connected players simply call StopAdvertising() and you are all
39 // connected up and ready to go.
40 //
41 // When you want to join a game, call StartDiscovery(). This library handles
42 // prompting the player when they find a host to connect to. Once you have
43 // connected to a host and they have accepted you, you will be fully connected.
44 //
45 // To send a message to a specific user (as the host), call SendMessage(). To
46 // send a message to all other users (as either host or client), call
47 // BroadcastMessage. Only the host can see all the players.
48 //
49 // To receive, call HasMessage() to check if there are any messages available,
50 // then GetNextMessage() to get the next incoming message from the queue.
51 
52 #ifndef GPG_MULTIPLAYER_H
53 #define GPG_MULTIPLAYER_H
54 
55 #include <list>
56 #include <map>
57 #include <queue>
58 #include <string>
59 #include <vector>
60 
61 namespace fpl {
62 
64  public:
65  // In the pair, first = the sender's instance_id, second = the message.
66  typedef std::pair<std::string, std::vector<uint8_t>> SenderAndMessage;
67 
68  enum MultiplayerState {
69  // Starting state, you aren't connected, broadcasting, or scanning.
70  kIdle = 0,
71  // The host is advertising its connection.
72  kAdvertising = 1,
73  // The host is advertising its connection and has prompted the user.
74  kAdvertisingPromptedUser = 2,
75  // The client is scanning for hosts.
76  kDiscovering = 3,
77  // The client is asking the user whether to connect to a specific host.
78  kDiscoveringPromptedUser = 4,
79  // The user chose to connect, we are waiting for the host to accept us.
80  kDiscoveringWaitingForHost = 5,
81  // We are fully connected to the other side.
82  kConnected = 6,
83  // We are connected to some clients, but others have disconnected
84  kConnectedWithDisconnections = 7,
85  // A connection error occurred.
86  kError = 8
87  };
88 
89  // The user's response to a connection dialog.
90  enum DialogResponse {
91  // The user responded "No" to the prompt.
92  kDialogNo,
93  // The user responded "Yes" to the prompt.
94  kDialogYes,
95  // The user has not yet responded to the prompt, we are still waiting.
96  kDialogWaiting,
97  };
98 
99  // Initializes mutexes only.
100  GPGMultiplayer();
101 
102  // Initialize the connection manager, set up callbacks, etc.
103  // Call this before doing anything else but after initializing
104  // GameServices. service_id should be unique for your game.
105  bool Initialize(const std::string& service_id);
106 
107  // Add an app identifier that is used for linking to your device's app store,
108  // if a user scanning for games doesn't have this one installed.
109  void AddAppIdentifier(const std::string& identifier);
110 
111  // Update, call this once per frame if you can.
112  void Update();
113 
114  // Broadcast that you are hosting a game. To change the name from the default,
115  // call set_my_instance_name() before calling this.
116  void StartAdvertising();
117  // Stop broadcasting your game; if you have connected instances, the state
118  // will become kConnected, otherwise you will go back to kIdle.
119  void StopAdvertising();
120 
121  // Look for games to join as a client. To change your name from the
122  // default, call set_my_instance_name() before calling this.
123  void StartDiscovery();
124  // Stop looking for games to join, sets the state back to kIdle.
125  void StopDiscovery();
126 
127  // Disconnect a specific other player (host / client).
128  void DisconnectInstance(const std::string& instance_id);
129  // Disconnect all other players (host / client).
130  void DisconnectAll();
131 
132  // Stop whatever you are doing, disconnect all players, and reset back to
133  // idle.
134  void ResetToIdle();
135 
136  // Set the name that will be shown to clients performing discovery,
137  // or shown to hosts when you send a connection request.
138  void set_my_instance_name(const std::string& my_instance_name) {
139  my_instance_name_ = my_instance_name;
140  }
141 
142  // Get the current multiplayer state.
143  MultiplayerState state() const { return state_; }
144 
145  // Are you fully connected and no longer advertising or discovering?
146  bool IsConnected() const {
147  return state() == kConnected || state() == kConnectedWithDisconnections;
148  }
149 
150  // Returns whether you are advertising.
151  bool IsAdvertising() const {
152  return state() == kAdvertising || state() == kAdvertisingPromptedUser;
153  }
154  // Returns whether you are discovering.
155  bool IsDiscovering() const {
156  return state() == kDiscovering || state() == kDiscoveringPromptedUser ||
157  state() == kDiscoveringWaitingForHost;
158  }
159  // Returns whether you have had a connection error.
160  bool HasError() const { return state() == kError; }
161 
162  // Get the number of players we have connected. If you are a client, this will
163  // be at most 1, since you are only connected to the host.
164  int GetNumConnectedPlayers();
165 
166  // Return true if this user is the host, false if you are a client.
167  bool is_hosting() const { return is_hosting_; }
168 
169  // Get the instance ID of a connected instance by player number, or empty if
170  // you pass in an invalid player number. This locks a mutex so you're best off
171  // caching this value for performance.
172  std::string GetInstanceIdByPlayerNumber(unsigned int player);
173 
174  // Get the player number of a connected instance by instance ID (the reverse
175  // of GetInstanceIdByPlayerNumber), or -1 if there is no such connected
176  // instance. This locks a mutex so you're best off caching this value for
177  // performance.
178  int GetPlayerNumberByInstanceId(const std::string& instance_id);
179 
180  // Send a message to a specific instance. Returns false if you are not
181  // connected to that instance (in which case nothing is sent).
182  bool SendMessage(const std::string& instance_id,
183  const std::vector<uint8_t>& payload, bool reliable);
184 
185  // For the host: broadcast to all clients. For the client, sends just to host.
186  void BroadcastMessage(const std::vector<uint8_t>& payload, bool reliable);
187 
188  // Returns true if there are one or more messages available in the queue.
189  // You would then call GetNextMessage() to retrieve the next message.
190  bool HasMessage();
191 
192  // Get the latest incoming message, or a blank sender and message if there are
193  // none.
194  SenderAndMessage GetNextMessage();
195 
196  // Returns true if a player has just reconnected.
197  bool HasReconnectedPlayer();
198 
199  // Gets the player ID of a player that has just reconnected. There may
200  // be more than one, so keep checking this until HasReconnectedPlayer()
201  // returns false (or this returns -1).
202  int GetReconnectedPlayer();
203 
204  // On the host, set the maximum number of players allowed to connect (not
205  // counting the host itself). Set this to a negative number to allow an
206  // unlimited number of players.
207  void set_max_connected_players_allowed(int players) {
208  max_connected_players_allowed_ = players;
209  }
210  int max_connected_players_allowed() const {
211  return max_connected_players_allowed_;
212  }
213 
214  // On the host, set this to true to automatically allow users to connect.
215  // On the client, set this to true to automatically connect to the first host.
216  void set_auto_connect(bool b) { auto_connect_ = b; }
217 
218  // If true, we will automatically connect.
219  bool auto_connect() const { return auto_connect_; }
220 
221  // Set to true to allow a disconnected user to reconnect into their previous
222  // slot. If you allow this, you can find a reconnected player by calling
223  // GetReconnectedPlayer().
224  void set_allow_reconnecting(bool b) { allow_reconnecting_ = b; }
225 
226  // If true, we allow disconnected users to reconnect.
227  bool allow_reconnecting() const { return allow_reconnecting_; }
228 
229  private:
230  typedef std::queue<SenderAndMessage> MessageQueue;
231 
232  // Listens for hosts that are advertising.
233  class DiscoveryListener : public gpg::IEndpointDiscoveryListener {
234  public:
235  explicit DiscoveryListener(
236  std::function<void(gpg::EndpointDetails const&)> endpointfound_callback,
237  std::function<void(const std::string&)> endpointremoved_callback)
238  : endpointfound_callback_(endpointfound_callback),
239  endpointremoved_callback_(endpointremoved_callback) {}
240  void OnEndpointFound(int64_t /*client_id*/,
241  gpg::EndpointDetails const& endpoint_details) {
242  // Ignore client_id because we only have one NearbyConnections client.
243  endpointfound_callback_(endpoint_details);
244  }
245  void OnEndpointLost(int64_t /*client_id*/, const std::string& instance_id) {
246  // Ignore client_id because we only have one NearbyConnections client.
247  endpointremoved_callback_(instance_id);
248  }
249 
250  private:
251  std::function<void(gpg::EndpointDetails const&)> endpointfound_callback_;
252  std::function<void(const std::string&)> endpointremoved_callback_;
253  };
254 
255  // Listens for messages or disconnects from connected instances.
256  class MessageListener : public gpg::IMessageListener {
257  public:
258  explicit MessageListener(
259  std::function<void(const std::string&, std::vector<uint8_t>, bool)>
260  message_received_callback,
261  std::function<void(const std::string&)> disconnected_callback)
262  : message_received_callback_(message_received_callback),
263  disconnected_callback_(disconnected_callback) {}
264  void OnMessageReceived(int64_t /* client_id */,
265  const std::string& instance_id,
266  std::vector<uint8_t> const& payload,
267  bool is_reliable) {
268  // Ignore client_id because we only have one NearbyConnections client.
269  message_received_callback_(instance_id, payload, is_reliable);
270  }
271  void OnDisconnected(int64_t /* client_id */,
272  const std::string& instance_id) {
273  // Ignore client_id because we only have one NearbyConnections client.
274  disconnected_callback_(instance_id);
275  }
276 
277  private:
278  std::function<void(const std::string&, std::vector<uint8_t>, bool)>
279  message_received_callback_;
280  std::function<void(const std::string&)> disconnected_callback_;
281  };
282 
283  // Enter a new state, exiting the previous one first.
284  void TransitionState(MultiplayerState old_state, MultiplayerState new_state);
285 
286  // Queue up the next state to go into at the next Update.
287  void QueueNextState(MultiplayerState next_state);
288 
289  // On the client, request a connection from a host you have discovered.
290  void SendConnectionRequest(const std::string& host_instance_id);
291  // On the host, accept a client's connection request.
292  void AcceptConnectionRequest(const std::string& client_instance_id);
293  // On the host, reject a client's connection request, disconnecting them.
294  void RejectConnectionRequest(const std::string& client_instance_id);
295  // On the host, reject all pending connection requests.
296  void RejectAllConnectionRequests();
297 
298  // Callbacks used by NearbyConnections library.
299  void StartAdvertisingCallback(gpg::StartAdvertisingResult const& info);
300  void ConnectionRequestCallback(
301  gpg::ConnectionRequest const& connection_request);
302  void DiscoveryEndpointFoundCallback(
303  gpg::EndpointDetails const& endpoint_details);
304  void DiscoveryEndpointLostCallback(const std::string& instance_id);
305  void ConnectionResponseCallback(gpg::ConnectionResponse const& response);
306  void MessageReceivedCallback(const std::string& instance_id,
307  std::vector<uint8_t> const& payload,
308  bool is_reliable);
309  void DisconnectedCallback(const std::string& instance_id);
310 
311  // Functions to prompt the user on connection.
312  DialogResponse GetConnectionDialogResponse();
313  bool DisplayConnectionDialog(const char* title, const char* question_text,
314  const char* yes_text, const char* no_text);
315 
316  // Update connected_instances_reverse_ to match to connected_instances_.
317  // Make sure instance_mutex_ is locked when calling.
318  void UpdateConnectedInstances();
319 
320  // Add a new connected instance to the connected_instances_ list.
321  // Returns the new index in connected_instances_ or -1 if it failed.
322  // Make sure instance_mutex_ is locked when calling.
323  int AddNewConnectedInstance(const std::string& instance_id);
324 
325  // Clear the disconnected instances that we were remembering. Also compacts
326  // connected_instances_ to remove holes from disconnected instances.
327  void ClearDisconnectedInstances();
328 
329  // The NearbyConnections library.
330  std::unique_ptr<gpg::NearbyConnections> nearby_connections_;
331  // The DiscoveryListener we've instantiated.
332  std::unique_ptr<DiscoveryListener> discovery_listener_;
333  // The MessageListener we've instantiated.
334  std::unique_ptr<MessageListener> message_listener_;
335 
336  std::string service_id_;
337  std::vector<gpg::AppIdentifier> app_identifiers_;
338 
339  // Keep track of fully-connected instances here. Lock instance_mutex_ before
340  // using.
341  std::vector<std::string> connected_instances_;
342  // Keep a reverse map of instance IDs to vector indices. Lock instance_mutex_
343  // before using.
344  std::map<std::string, int> connected_instances_reverse_;
345  // The host keeps track of instances that are trying to connect. Lock
346  // instance_mutex_ before using.
347  std::list<std::string> pending_instances_;
348  // The client keeps track of the instances it has discovered. Lock
349  // instance_mutex_ before using.
350  std::list<std::string> discovered_instances_;
351  // Keep a mapping of instance IDs to full names. Lock instance_mutex_ before
352  // using.
353  std::map<std::string, std::string> instance_names_;
354  // Any instance that gets disconnected goes here, so it can be allowed to
355  // reconnect and get its old player number. This maps disconnected instance
356  // IDs to their former index in connected_instances_.
357  std::map<std::string, int> disconnected_instances_;
358  // Keep track of which instances we have allowed to reconnect,
359  // so the user code can send them a game state update.
360  std::queue<int> reconnected_players_;
361 
362  // List of incoming messages. Lock message_mutex_ before using.
363  MessageQueue incoming_messages_;
364 
365  // Our current state.
366  MultiplayerState state_;
367  // Our next state(s). Will enter the next one during the next Update().
368  std::queue<MultiplayerState> next_states_;
369 
370  std::string my_instance_name_;
371  int max_connected_players_allowed_; // 0 to allow any number
372 
373  // Mutex for the incoming_messages_ queue.
374  pthread_mutex_t message_mutex_;
375 
376  // Mutex for instance management: connected_instances_, pending_instances_,
377  // discovered_instances, and instance_names_.
378  pthread_mutex_t instance_mutex_;
379 
380  // Mutex for the next_states_ queue. Needed only in rare occasions like a
381  // callback setting an error condition.
382  pthread_mutex_t state_mutex_;
383 
384  bool is_hosting_; // This is set to true if we are the host.
385  bool auto_connect_; // If this is true, connections will be automatically
386  // approved without prompting.
387  bool allow_reconnecting_; // If this is true, a client disconnecting while a
388  // host is connected will have its slot reserved
389  // for reconnection.
390 };
391 
392 } // namespace fpl
393 
394 #endif // GPG_MULTIPLAYER_H
Definition: gpg_multiplayer.h:63