forked from Unity-Technologies/Megacity-2019
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathClientMatchmaker.cs
191 lines (166 loc) · 7.02 KB
/
ClientMatchmaker.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using Unity.Services.Matchmaker;
using Unity.Services.Matchmaker.Models;
namespace Unity.Services.Samples.GameServerHosting
{
/// <summary>
/// Represent errors at different stages of the ClientMatchmaker.Matchmake() function.
/// </summary>
public enum MatchmakerPollingResult
{
Success,
TicketCreationError,
TicketCancellationError,
TicketRetrievalError,
MatchAssignmentError
}
/// <summary>
/// Wrapped Results for this ClientMatchmaker wrapper.
/// Use the returned object in your own code, to bubble up errors or feedback to
/// the UI/UX for users, or continue your game logic.
/// </summary>
public class MatchmakingResult
{
public string ip;
public int port;
public MatchmakerPollingResult result;
public string resultMessage;
}
public class ClientMatchmaker : IDisposable
{
const int MaxRetry = 1;
const int k_GetTicketCooldown = 1000;
string m_LastUsedTicket;
bool m_IsMatchmaking = false;
CancellationTokenSource m_CancelToken;
/// <summary>
/// Create a ticket for the one user and begin matchmaking with their preferences
/// </summary>
/// <param name="data">The Client's preferences and ID</param>
public async Task<MatchmakingResult> Matchmake(PlayerProfile data)
{
var retryCount = -1;
MatchmakingResult result = ReturnMatchResult(MatchmakerPollingResult.TicketCancellationError, "Too many retries.");
while (retryCount < MaxRetry)
{
var (tryResult, retry) = await TryMatchmaking(data);
if (tryResult.result == MatchmakerPollingResult.Success)
{
return tryResult;
}
if (!retry)
{
return tryResult;
}
result = tryResult;
retryCount++;
Debug.Log($"[Matchmaker] Retry {retryCount} of {MaxRetry}");
await Task.Delay(1000);
}
return result;
}
private async Task<(MatchmakingResult result, bool retry)> TryMatchmaking(PlayerProfile data)
{
m_CancelToken = new CancellationTokenSource();
var players = new List<Player> { new(data.UASId) };
try
{
m_IsMatchmaking = true;
var createTicketOptions = new CreateTicketOptions();
var createResult = await MatchmakerService.Instance.CreateTicketAsync(players, createTicketOptions);
Debug.Log($"[Matchmaker] Ticket Result: {createResult.Id}");
m_LastUsedTicket = createResult.Id;
}
catch (MatchmakerServiceException e)
{
return (ReturnMatchResult(MatchmakerPollingResult.TicketCreationError, e.ToString()), false);
}
try
{
//Polling Loop, cancelling should take us all the way to the method
while (!m_CancelToken.IsCancellationRequested)
{
var checkTicket = await MatchmakerService.Instance.GetTicketAsync(m_LastUsedTicket);
Debug.Log($"[Matchmaker] Check Ticket: {checkTicket.Value}");
if (checkTicket.Type == typeof(MultiplayAssignment))
{
var matchAssignment = (MultiplayAssignment)checkTicket.Value;
Debug.Log($"[Matchmaker] Match Assignment Ticket Status: {matchAssignment.Status}");
if (matchAssignment.Status == MultiplayAssignment.StatusOptions.Found)
{
return (ReturnMatchResult(MatchmakerPollingResult.Success, "", matchAssignment), false);
}
if (DoNeedRetry(matchAssignment))
{
return (ReturnMatchResult(MatchmakerPollingResult.MatchAssignmentError,
$" Ticket: {m_LastUsedTicket} - {matchAssignment.Status} - {matchAssignment.Message}"), true);
}
if (matchAssignment.Status != MultiplayAssignment.StatusOptions.InProgress)
{
return (ReturnMatchResult(MatchmakerPollingResult.MatchAssignmentError,
$" Ticket: {m_LastUsedTicket} - {matchAssignment.Status} - {matchAssignment.Message}"), false);
}
Debug.Log($"[Matchmaker] Polled Ticket: {m_LastUsedTicket} Status: {matchAssignment.Status} ");
}
await Task.Delay(k_GetTicketCooldown);
}
}
catch (MatchmakerServiceException e)
{
return (ReturnMatchResult(MatchmakerPollingResult.TicketRetrievalError, e.ToString()), false);
}
return (ReturnMatchResult(MatchmakerPollingResult.TicketCancellationError, "Cancelled Matchmaking"), false);
}
private bool DoNeedRetry(MultiplayAssignment matchAssignment)
{
return matchAssignment.Status == MultiplayAssignment.StatusOptions.Failed &&
matchAssignment.Message.StartsWith(
"MultiplayAllocationError: request error: maximum capacity reached");
}
public bool IsMatchmaking => m_IsMatchmaking;
public async Task CancelMatchmaking()
{
if (!m_IsMatchmaking)
return;
m_IsMatchmaking = false;
if (m_CancelToken.Token.CanBeCanceled)
m_CancelToken.Cancel();
if (string.IsNullOrEmpty(m_LastUsedTicket))
return;
Debug.Log($"Cancelling {m_LastUsedTicket}");
await MatchmakerService.Instance.DeleteTicketAsync(m_LastUsedTicket);
}
//Make sure we exit the matchmaking cycle through this method every time.
MatchmakingResult ReturnMatchResult(MatchmakerPollingResult resultErrorType, string message = "",
MultiplayAssignment assignment = null)
{
m_IsMatchmaking = false;
if (assignment != null)
{
return new MatchmakingResult
{
result = MatchmakerPollingResult.Success,
ip = assignment.Ip,
port = assignment.Port ?? -1,
resultMessage = assignment.Message
};
}
return new MatchmakingResult
{
result = resultErrorType,
resultMessage = message
};
}
public void Dispose()
{
#pragma warning disable 4014
CancelMatchmaking();
#pragma warning restore 4014
m_CancelToken?.Dispose();
}
}
}