From 89bf6851ec5a0f5b233537c2a3a84851d71dfeb5 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:18:07 -0400 Subject: [PATCH 01/16] add backfill DTOs --- Runtime/Matchmaking/Api.cs | 24 ++++++++++++++ .../Matchmaking/DTOs/BackfillRequestDTO.cs | 33 +++++++++++++++++++ .../DTOs/BackfillRequestDTO.cs.meta | 2 ++ .../Matchmaking/DTOs/BackfillResponseDTO.cs | 31 +++++++++++++++++ .../DTOs/BackfillResponseDTO.cs.meta | 2 ++ .../DTOs/BackfillTicketMemberDTO.cs | 28 ++++++++++++++++ .../DTOs/BackfillTicketMemberDTO.cs.meta | 2 ++ 7 files changed, 122 insertions(+) create mode 100644 Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs create mode 100644 Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs.meta create mode 100644 Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs create mode 100644 Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs.meta create mode 100644 Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs create mode 100644 Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta diff --git a/Runtime/Matchmaking/Api.cs b/Runtime/Matchmaking/Api.cs index 6a67e30..488a6ba 100644 --- a/Runtime/Matchmaking/Api.cs +++ b/Runtime/Matchmaking/Api.cs @@ -19,6 +19,7 @@ public class Api internal string PATH_TICKETS = "tickets"; internal string PATH_GROUP_TICKETS = "group-tickets"; internal string PATH_GROUP_UP = "groups"; + internal string PATH_BACKFILL = "backfills"; public Api(MonoBehaviour parent, string authToken, string baseUrl) { @@ -27,6 +28,7 @@ public Api(MonoBehaviour parent, string authToken, string baseUrl) BaseUrl = baseUrl; } + #region Utility public void GetMonitor( Action onSuccessDelegate, Action onErrorDelegate @@ -82,7 +84,9 @@ Action onErrorDelegate onErrorDelegate ); } + #endregion + #region Server-to-Server Tickets public void CreateTicketAsync( T ticket, Action onSuccessDelegate, @@ -190,7 +194,9 @@ Action onErrorDelegate onErrorDelegate ); } + #endregion + #region Client Group-Up public void CreateGroup( G group, Action onSuccessDelegate, @@ -381,5 +387,23 @@ Action onErrorDelegate onErrorDelegate ); } + #endregion + + #region Server Backfill + public void CreateBackfill() + { + //MTODO + } + + public void GetBackfill() + { + //MTODO + } + + public void DeleteBackfill() + { + //MTODO + } + #endregion } } diff --git a/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs new file mode 100644 index 0000000..1a714f8 --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Edgegap.Matchmaking +{ + public class BackfillRequestDTO + { + [JsonProperty("profile")] + public string Profile; + + [JsonProperty("attributes")] + public BackfillAttributes Attributes; + + [JsonProperty("tickets")] + public Dictionary> Tickets; + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } + + public class BackfillAttributes + { + [JsonProperty("assignment")] + public DeploymentDTO Assignment; + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } +} diff --git a/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs.meta b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs.meta new file mode 100644 index 0000000..7d1d311 --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d60bff1acfb4f41489d10742e451f007 \ No newline at end of file diff --git a/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs new file mode 100644 index 0000000..3f32d66 --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Edgegap.Matchmaking +{ + public class BackfillResponseDTO + { + [JsonProperty("id")] + public string ID; + + [JsonProperty("profile")] + public string Profile; + + [JsonProperty("tickets")] + public Dictionary> Tickets; + + [JsonProperty("status")] + public string Status; + +#nullable enable + [JsonProperty("assigned_ticket")] + public BackfillTicketMemberDTO? AssignedTicket; + +#nullable disable + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } +} diff --git a/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs.meta b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs.meta new file mode 100644 index 0000000..5795b57 --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 16a1338f92fc32845872dd17ca46d5e4 \ No newline at end of file diff --git a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs new file mode 100644 index 0000000..953a62d --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs @@ -0,0 +1,28 @@ +using System; +using Newtonsoft.Json; + +namespace Edgegap.Matchmaking +{ + public class BackfillTicketMemberDTO + { + [JsonProperty("id")] + public string ID; + + [JsonProperty("created_at")] + public DateTime CreatedAt; + + [JsonProperty("player_ip")] + public string PlayerIP; + + [JsonProperty("group_id")] + public string GroupID; + + [JsonProperty("attributes")] + public A Attributes; + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } +} diff --git a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta new file mode 100644 index 0000000..030ba5d --- /dev/null +++ b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cbc6b32b2493a454d9331889e18e5bee \ No newline at end of file From 1f4b084f7450422b5dfc1573d8a43e6458ac3e67 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Tue, 30 Jun 2026 10:16:17 -0400 Subject: [PATCH 02/16] api methods --- Runtime/Matchmaking/Api.cs | 76 +++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/Runtime/Matchmaking/Api.cs b/Runtime/Matchmaking/Api.cs index 488a6ba..88d8939 100644 --- a/Runtime/Matchmaking/Api.cs +++ b/Runtime/Matchmaking/Api.cs @@ -390,19 +390,83 @@ Action onErrorDelegate #endregion #region Server Backfill - public void CreateBackfill() + public void CreateBackfill( + B backfill, + Action, UnityWebRequest> onSuccessDelegate, + Action onErrorDelegate + ) + where B : BackfillRequestDTO { - //MTODO + Request.Post( + $"{BaseUrl}/{PATH_BACKFILL}", + AuthToken, + JsonConvert.SerializeObject(backfill), + (string response, UnityWebRequest request) => + { + try + { + BackfillResponseDTO backfillRes = JsonConvert.DeserializeObject< + BackfillResponseDTO + >(response); + onSuccessDelegate(backfillRes, request); + } + catch (Exception e) + { + L.Error( + $"Couldn't parse backfill, consider updating Matchmaking SDK. {e.Message}" + ); + throw; + } + }, + onErrorDelegate + ); } - public void GetBackfill() + public void GetBackfill( + string backfillID, + Action, UnityWebRequest> onSuccessDelegate, + Action onErrorDelegate + ) { - //MTODO + Request.Get( + $"{BaseUrl}/{PATH_BACKFILL}/{backfillID}", + AuthToken, + (string response, UnityWebRequest request) => + { + try + { + BackfillResponseDTO backfillRes = JsonConvert.DeserializeObject< + BackfillResponseDTO + >(response); + onSuccessDelegate(backfillRes, request); + } + catch (Exception e) + { + L.Error( + $"Couldn't parse backfill, consider updating Matchmaking SDK. {e.Message}" + ); + throw; + } + }, + onErrorDelegate + ); } - public void DeleteBackfill() + public void DeleteBackfill( + string backfillID, + Action onSuccessDelegate, + Action onErrorDelegate + ) { - //MTODO + Request.Delete( + $"{BaseUrl}/{PATH_BACKFILL}/{backfillID}", + AuthToken, + (string response, UnityWebRequest request) => + { + onSuccessDelegate(request); + }, + onErrorDelegate + ); } #endregion } From 6834d396e7d34b824e38bd43907fb062dc4bd914 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Tue, 30 Jun 2026 12:00:43 -0400 Subject: [PATCH 03/16] start server --- Runtime/Matchmaking/Server.cs | 172 +++++++++++++++++++++++++++++ Runtime/Matchmaking/Server.cs.meta | 2 + 2 files changed, 174 insertions(+) create mode 100644 Runtime/Matchmaking/Server.cs create mode 100644 Runtime/Matchmaking/Server.cs.meta diff --git a/Runtime/Matchmaking/Server.cs b/Runtime/Matchmaking/Server.cs new file mode 100644 index 0000000..8cc140b --- /dev/null +++ b/Runtime/Matchmaking/Server.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.Networking; + +namespace Edgegap.Matchmaking +{ + using L = Logger; + + public class Server + where B : BackfillRequestDTO + { + private Api MatchmakingApi; + + public MonoBehaviour Handler; + public int TargetPlayerCount; + + // BaseUrl may only be set with constructor + public string BaseUrl { get; } + public string AuthToken { private get; set; } + + public int RequestTimeoutSeconds; + public float PollingBackoffSeconds; + public int MaxConsecutivePollingErrors; + + public bool LogBackfillUpdates; + public bool LogPollingUpdates; + + public Observable Monitor { get; private set; } = + new Observable() { }; + public Observable> Backfill { get; private set; } = + new Observable>() { }; + + private protected DeploymentEnvironmentDTO DeploymentEnvs; + private protected MatchEnvironmentDTO MatchEnvs; + private protected bool Polling = false; + + public Server( + MonoBehaviour handler, + string baseUrl, + string authToken, + int targetPlayerCount = -1, + int requestTimeoutSeconds = 3, + float pollingBackoffSeconds = 1f, + int maxConsecutivePollingErrors = 10, + bool logBackfillUpdates = true, + bool logPollingUpdates = false + ) + { + if (handler == null) + { + throw new Exception("MatchmakingServer Handler not assigned."); + } + + Handler = handler; + TargetPlayerCount = targetPlayerCount; + + BaseUrl = baseUrl; + AuthToken = authToken; + + RequestTimeoutSeconds = requestTimeoutSeconds; + PollingBackoffSeconds = pollingBackoffSeconds; + MaxConsecutivePollingErrors = maxConsecutivePollingErrors; + + LogBackfillUpdates = logBackfillUpdates; + LogPollingUpdates = logPollingUpdates; + } + + #region Server API + public void Status() + { + MatchmakingApi.GetMonitor( + (MonitorResponseDTO monitor, UnityWebRequest request) => + { + if (monitor.Status.ToLower() == "healthy") + { + Monitor._Update(monitor, "healthy"); + } + else + { + Monitor._Update(monitor, "unhealthy"); + } + }, + (string error, UnityWebRequest request) => + { + Monitor._Error($"get monitor failed (unexpected error)\n{error}", null); + } + ); + } + + public void AddBackfill(B backfill) + { + // MTODO + //check current tickets + TargetPlayerCount + //api request + //call TrackTicket(ticket) on success + } + + public void RemoveBackfill(string backfillID) + { + // MTODO + //check backfills => api request + //else check currents/on api success => UntrackTicket(backfillID) + } + + public void RemoveAllBackfills() + { + Polling = false; + // MTODO foreach ticket call RemoveBackfill in parallel + } + #endregion + + #region Initialization + public void Initialize( + UnityAction< + Observable, + ObservableActionType, + string + > onMonitorUpdate, + UnityAction< + Observable>, + ObservableActionType, + string + > onBackfillUpdate + ) + { + if (string.IsNullOrEmpty(BaseUrl.Trim())) + { + throw new Exception("BaseUrl not declared."); + } + + if (string.IsNullOrEmpty(AuthToken.Trim())) + { + throw new Exception("AuthToken not declared."); + } + + LoadEnvs(); + MatchmakingApi = new Api(Handler, AuthToken, BaseUrl); + + L.SubscribeLogger(Monitor, "MM", "Monitor"); + Monitor.Subscribe(onMonitorUpdate); + + L.SubscribeLogger(Backfill, "MM", "Backfill", LogBackfillUpdates); + Backfill.Subscribe(onBackfillUpdate); + + Status(); + } + #endregion + + #region Internals + internal void LoadEnvs() + { + IDictionary envs = Environment.GetEnvironmentVariables(); + DeploymentEnvs = new DeploymentEnvironmentDTO(envs); + MatchEnvs = new MatchEnvironmentDTO(envs); + + // MTODO foreach ticket TrackTicket(convertedTicket) + } + + internal void TrackTicket(BackfillTicketMemberDTO ticket) + { + // MTODO add ticket to currents + } + + internal void UntrackTicket(string ticketID) + { + // MTODO remove ticket from currents + } + #endregion + } +} diff --git a/Runtime/Matchmaking/Server.cs.meta b/Runtime/Matchmaking/Server.cs.meta new file mode 100644 index 0000000..86e412c --- /dev/null +++ b/Runtime/Matchmaking/Server.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9f3574739e980ef498f789e433eb64ea \ No newline at end of file From 1fb154f38ad89d7cd0ca511af14c2c92380170c0 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:22:59 -0400 Subject: [PATCH 04/16] remove almost identical DTO, continue server --- .../Matchmaking/DTOs/BackfillRequestDTO.cs | 43 ++++- .../Matchmaking/DTOs/BackfillResponseDTO.cs | 4 +- .../DTOs/BackfillTicketMemberDTO.cs | 28 ---- .../DTOs/BackfillTicketMemberDTO.cs.meta | 2 - Runtime/Matchmaking/DTOs/InjectedTicketDTO.cs | 7 +- Runtime/Matchmaking/Server.cs | 113 ++++++++++--- Runtime/SharedDTOs/DeploymentDTO.cs | 13 ++ .../SharedDTOs/DeploymentEnvironmentDTO.cs | 9 ++ .../BackfillServerHandlerExample.cs | 148 ++++++++++++++++++ 9 files changed, 307 insertions(+), 60 deletions(-) delete mode 100644 Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs delete mode 100644 Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta create mode 100644 Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs diff --git a/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs index 1a714f8..d2193e4 100644 --- a/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs +++ b/Runtime/Matchmaking/DTOs/BackfillRequestDTO.cs @@ -3,7 +3,7 @@ namespace Edgegap.Matchmaking { - public class BackfillRequestDTO + public abstract class BackfillRequestDTO { [JsonProperty("profile")] public string Profile; @@ -12,7 +12,13 @@ public class BackfillRequestDTO public BackfillAttributes Attributes; [JsonProperty("tickets")] - public Dictionary> Tickets; + public Dictionary> Tickets; + + public BackfillRequestDTO(string profile, BackfillAttributes attributes) + { + Profile = profile; + Attributes = attributes; + } public override string ToString() { @@ -25,9 +31,42 @@ public class BackfillAttributes [JsonProperty("assignment")] public DeploymentDTO Assignment; + public BackfillAttributes(DeploymentDTO assignment) + { + Assignment = assignment; + } + public override string ToString() { return JsonConvert.SerializeObject(this); } } + + public class SimpleBackfillRequestDTO : BackfillRequestDTO + { + public SimpleBackfillRequestDTO( + string profile, + BackfillAttributes attributes, + Dictionary> tickets + ) + : base(profile, attributes) + { + Tickets = tickets; + } + } + + public class BackfillTicketAttributesDTO : LatenciesAttributesDTO + { + [JsonProperty("backfill_group_size")] + public string[] BackfillGroupSize; + + public BackfillTicketAttributesDTO( + Dictionary beacons, + string[] backfillGroupSize + ) + : base(beacons) + { + BackfillGroupSize = backfillGroupSize; + } + } } diff --git a/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs index 3f32d66..2693632 100644 --- a/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs +++ b/Runtime/Matchmaking/DTOs/BackfillResponseDTO.cs @@ -12,14 +12,14 @@ public class BackfillResponseDTO public string Profile; [JsonProperty("tickets")] - public Dictionary> Tickets; + public Dictionary> Tickets; [JsonProperty("status")] public string Status; #nullable enable [JsonProperty("assigned_ticket")] - public BackfillTicketMemberDTO? AssignedTicket; + public InjectedTicketDTO? AssignedTicket; #nullable disable diff --git a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs deleted file mode 100644 index 953a62d..0000000 --- a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace Edgegap.Matchmaking -{ - public class BackfillTicketMemberDTO - { - [JsonProperty("id")] - public string ID; - - [JsonProperty("created_at")] - public DateTime CreatedAt; - - [JsonProperty("player_ip")] - public string PlayerIP; - - [JsonProperty("group_id")] - public string GroupID; - - [JsonProperty("attributes")] - public A Attributes; - - public override string ToString() - { - return JsonConvert.SerializeObject(this); - } - } -} diff --git a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta b/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta deleted file mode 100644 index 030ba5d..0000000 --- a/Runtime/Matchmaking/DTOs/BackfillTicketMemberDTO.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: cbc6b32b2493a454d9331889e18e5bee \ No newline at end of file diff --git a/Runtime/Matchmaking/DTOs/InjectedTicketDTO.cs b/Runtime/Matchmaking/DTOs/InjectedTicketDTO.cs index 1046888..969b7b3 100644 --- a/Runtime/Matchmaking/DTOs/InjectedTicketDTO.cs +++ b/Runtime/Matchmaking/DTOs/InjectedTicketDTO.cs @@ -17,8 +17,11 @@ public class InjectedTicketDTO [JsonProperty("group_id")] public string GroupID; - [JsonProperty("team_id")] - public string TeamID; +#nullable enable + [JsonProperty("team_id", NullValueHandling = NullValueHandling.Ignore)] + public string? TeamID; + +#nullable disable [JsonProperty("attributes")] public A Attributes; diff --git a/Runtime/Matchmaking/Server.cs b/Runtime/Matchmaking/Server.cs index 8cc140b..1dea863 100644 --- a/Runtime/Matchmaking/Server.cs +++ b/Runtime/Matchmaking/Server.cs @@ -1,5 +1,8 @@ using System; using System.Collections; +using System.Collections.Generic; +using UnityEditor.PackageManager; +using UnityEditor.PackageManager.Requests; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; @@ -23,17 +26,25 @@ public class Server public int RequestTimeoutSeconds; public float PollingBackoffSeconds; public int MaxConsecutivePollingErrors; + public float RemoveBackfillSeconds; public bool LogBackfillUpdates; public bool LogPollingUpdates; public Observable Monitor { get; private set; } = new Observable() { }; - public Observable> Backfill { get; private set; } = + public Observable> Backfills { get; private set; } = new Observable>() { }; - private protected DeploymentEnvironmentDTO DeploymentEnvs; - private protected MatchEnvironmentDTO MatchEnvs; + public DeploymentEnvironmentDTO DeploymentEnvs { get; private set; } + public MatchEnvironmentDTO MatchEnvs { get; private set; } + + public Dictionary> AssignedTickets { get; private set; } = + new Dictionary>(); + + public Dictionary> OngoingBackfills { get; private set; } = + new Dictionary>(); + private protected bool Polling = false; public Server( @@ -67,6 +78,11 @@ public Server( LogPollingUpdates = logPollingUpdates; } + public void RemoveAssignedTicket(string ticketID) + { + AssignedTickets.Remove(ticketID); + } + #region Server API public void Status() { @@ -91,23 +107,74 @@ public void Status() public void AddBackfill(B backfill) { - // MTODO - //check current tickets + TargetPlayerCount - //api request - //call TrackTicket(ticket) on success + if (AssignedTickets.Count + OngoingBackfills.Count >= TargetPlayerCount) + { + Backfills._Error("maximum capacity currently reached"); + return; + } + + MatchmakingApi.CreateBackfill( + backfill, + (BackfillResponseDTO backfillRes, UnityWebRequest request) => + { + OngoingBackfills[backfillRes.ID] = backfillRes; + Backfills._Update(backfillRes, "created"); + //MTODO delay polling new backfill + }, + (string error, UnityWebRequest request) => + { + Backfills._Error($"backfill create failed\n{error}"); + } + ); } - public void RemoveBackfill(string backfillID) + public void GetBackfill(string backfillID) { - // MTODO - //check backfills => api request - //else check currents/on api success => UntrackTicket(backfillID) + //MTODO + } + + public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) + { + if (!OngoingBackfills.ContainsKey(backfillID)) + { + Backfills._Error("backfill not found"); + return; + } + + MatchmakingApi.DeleteBackfill( + backfillID, + (UnityWebRequest request) => + { + Backfills._Update(null, "abandoned"); + + if (onCompletedDelegate is not null) + { + onCompletedDelegate(); + } + }, + (string error, UnityWebRequest request) => + { + if (request.responseCode == 404) + { + Backfills._Update(null, $"abandon failed (ID {backfillID} not found)"); + } + else + { + Backfills._Error($"abandon failed\n{error}"); + } + + if (onCompletedDelegate is not null) + { + onCompletedDelegate(); + } + } + ); } public void RemoveAllBackfills() { Polling = false; - // MTODO foreach ticket call RemoveBackfill in parallel + //MTODO foreach ticket call RemoveBackfill in parallel } #endregion @@ -135,14 +202,19 @@ public void Initialize( throw new Exception("AuthToken not declared."); } - LoadEnvs(); MatchmakingApi = new Api(Handler, AuthToken, BaseUrl); L.SubscribeLogger(Monitor, "MM", "Monitor"); Monitor.Subscribe(onMonitorUpdate); - L.SubscribeLogger(Backfill, "MM", "Backfill", LogBackfillUpdates); - Backfill.Subscribe(onBackfillUpdate); + L.SubscribeLogger(Backfills, "MM", "Backfill", LogBackfillUpdates); + Backfills.Subscribe(onBackfillUpdate); + + LoadEnvs(); + foreach (KeyValuePair> t in MatchEnvs.Tickets) + { + AddAssignedTicket(t.Value); + } Status(); } @@ -154,18 +226,11 @@ internal void LoadEnvs() IDictionary envs = Environment.GetEnvironmentVariables(); DeploymentEnvs = new DeploymentEnvironmentDTO(envs); MatchEnvs = new MatchEnvironmentDTO(envs); - - // MTODO foreach ticket TrackTicket(convertedTicket) - } - - internal void TrackTicket(BackfillTicketMemberDTO ticket) - { - // MTODO add ticket to currents } - internal void UntrackTicket(string ticketID) + internal void AddAssignedTicket(InjectedTicketDTO ticket) { - // MTODO remove ticket from currents + AssignedTickets[ticket.ID] = ticket; } #endregion } diff --git a/Runtime/SharedDTOs/DeploymentDTO.cs b/Runtime/SharedDTOs/DeploymentDTO.cs index 2eef11e..f651282 100644 --- a/Runtime/SharedDTOs/DeploymentDTO.cs +++ b/Runtime/SharedDTOs/DeploymentDTO.cs @@ -17,6 +17,19 @@ public class DeploymentDTO [JsonProperty("location")] public LocationDTO Location; + public DeploymentDTO( + string fqdn, + string publicIP, + Dictionary ports, + LocationDTO location + ) + { + Fqdn = fqdn; + PublicIP = publicIP; + Ports = ports; + Location = location; + } + public override string ToString() { return JsonConvert.SerializeObject(this); diff --git a/Runtime/SharedDTOs/DeploymentEnvironmentDTO.cs b/Runtime/SharedDTOs/DeploymentEnvironmentDTO.cs index 7d56d0f..fb8d7ae 100644 --- a/Runtime/SharedDTOs/DeploymentEnvironmentDTO.cs +++ b/Runtime/SharedDTOs/DeploymentEnvironmentDTO.cs @@ -32,6 +32,8 @@ public class DeploymentEnvironmentDTO public string Fqdn => string.IsNullOrEmpty(RequestID) ? "" : $"{RequestID}.pr.edgegap.net"; + public DeploymentDTO Deployment { get; private set; } + public DeploymentEnvironmentDTO(IDictionary env) { foreach (DictionaryEntry entry in env) @@ -106,6 +108,8 @@ public DeploymentEnvironmentDTO(IDictionary env) { port.Link ??= $"{Fqdn}:{port.External}"; } + + StoreDeploymentDTO(); } public static string TryParseEnvVariableString(DictionaryEntry keyValuePair) @@ -152,6 +156,11 @@ public override string ToString() { return JsonConvert.SerializeObject(this); } + + internal void StoreDeploymentDTO() + { + Deployment = new DeploymentDTO(Fqdn, PublicIP, PortMapping, Location); + } } internal class PortMappingEnvironmentVariable diff --git a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs new file mode 100644 index 0000000..0bb10fe --- /dev/null +++ b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs @@ -0,0 +1,148 @@ +using Edgegap; +using Edgegap.Matchmaking; +using System.Collections.Generic; +using UnityEngine; +using L = Edgegap.Logger; +using MyBackfillRequestDTO = Edgegap.Matchmaking.SimpleBackfillRequestDTO; +using MyTicketsAttributes = Edgegap.Matchmaking.BackfillTicketAttributesDTO; + +// todo replace BackfillTicketAttributesDTO with CustomTicketsAttributes +// todo replace SimpleBackfillRequestDTO with CustomBackfillRequestDTO + +public class BackfillServerHandlerExample : MonoBehaviour +{ + public static BackfillServerHandlerExample Instance { get; private set; } + + #region Matchmaking Configuration + + [Header("Matchmaker Instance")] + public string BaseUrl; + public string AuthToken; + public int TargetPlayerCount = -1; + + [Header("Exponential Retry")] + public int RequestTimeoutSeconds = 3; + public float PollingBackoffSeconds = 1f; + public int MaxConsecutivePollingErrors = 10; + + [Header("Expiration and Cleanup")] + public bool DeleteBackfillOnQuit = true; + + [Header("Logging")] + public bool LogBackfillUpdates = true; + public bool LogTicketUpdates = true; + public bool LogPollingUpdates = false; + #endregion + + public Server< + MyBackfillRequestDTO, + MyTicketsAttributes + > MatchmakingServer; + + public bool BackfillStopped = false; + private int OngoingRequests = 0; + private BackfillAttributes BackfillAttributes; + + private void Awake() + { + if (Instance != null && Instance != this) + { + Destroy(this); + } + else + { + Instance = this; + } + } + + // Start is called once before the first execution of Update after the MonoBehaviour is created + void Start() + { + MatchmakingServer = new Server< + MyBackfillRequestDTO, + MyTicketsAttributes + >( + this, + BaseUrl, + AuthToken, + TargetPlayerCount, + RequestTimeoutSeconds, + PollingBackoffSeconds, + MaxConsecutivePollingErrors, + LogBackfillUpdates, + LogPollingUpdates + ); + + MatchmakingServer.Initialize( + // handle service monitoring + ( + Observable monitor, + ObservableActionType action, + string message + ) => + { + //MTODO + }, + // handle backfill assignment + ( + Observable> backfill, + ObservableActionType action, + string message + ) => + { + //MTODO + /* + if backfill create fail || backfill assigned -> --OngoingRequests + */ + + if ( + action == ObservableActionType.Error + && message.Contains("create failed") + ) + { + --OngoingRequests; + } + } + ); + + BackfillAttributes = new BackfillAttributes(MatchmakingServer.DeploymentEnvs.Deployment); + } + + // Update is called once per frame + void Update() + { + if (!BackfillStopped) + { + if (MatchmakingServer.AssignedTickets.Count + OngoingRequests < TargetPlayerCount) + { + StartNewBackfill(); + } + } + } + + public void OnApplicationQuit() + { + if (!DeleteBackfillOnQuit) + return; + StopBackfill(); + } + + public void StartNewBackfill() + { + ++OngoingRequests; + + MyBackfillRequestDTO backfill = new MyBackfillRequestDTO( + MatchmakingServer.MatchEnvs.MatchProfile, + BackfillAttributes, + MatchmakingServer.AssignedTickets + ); + + MatchmakingServer.AddBackfill(backfill); + } + + public void StopBackfill() + { + BackfillStopped = true; + MatchmakingServer.RemoveAllBackfills(); + } +} From aaf30974d60e554f23d8b0ea7e5e97eb6b56a9a3 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Thu, 2 Jul 2026 11:10:30 -0400 Subject: [PATCH 05/16] continue server --- Runtime/Matchmaking/Server.cs | 92 ++++++++++++++++--- .../BackfillServerHandlerExample.cs | 27 +++--- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/Runtime/Matchmaking/Server.cs b/Runtime/Matchmaking/Server.cs index 1dea863..6189e54 100644 --- a/Runtime/Matchmaking/Server.cs +++ b/Runtime/Matchmaking/Server.cs @@ -1,11 +1,10 @@ using System; using System.Collections; using System.Collections.Generic; -using UnityEditor.PackageManager; -using UnityEditor.PackageManager.Requests; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; +using Random = UnityEngine.Random; namespace Edgegap.Matchmaking { @@ -119,7 +118,8 @@ public void AddBackfill(B backfill) { OngoingBackfills[backfillRes.ID] = backfillRes; Backfills._Update(backfillRes, "created"); - //MTODO delay polling new backfill + Polling = true; + Handler.StartCoroutine(DelayPollingBackfill(backfillRes.ID)); }, (string error, UnityWebRequest request) => { @@ -128,16 +128,11 @@ public void AddBackfill(B backfill) ); } - public void GetBackfill(string backfillID) - { - //MTODO - } - public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) { if (!OngoingBackfills.ContainsKey(backfillID)) { - Backfills._Error("backfill not found"); + Backfills._Error($"{backfillID} not found"); return; } @@ -145,7 +140,8 @@ public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) backfillID, (UnityWebRequest request) => { - Backfills._Update(null, "abandoned"); + Backfills._Update(null, $"{backfillID} abandoned"); + RemoveOngoingBackfill(backfillID); if (onCompletedDelegate is not null) { @@ -156,11 +152,12 @@ public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) { if (request.responseCode == 404) { - Backfills._Update(null, $"abandon failed (ID {backfillID} not found)"); + Backfills._Update(null, $"{backfillID} abandon failed (not found)"); + RemoveOngoingBackfill(backfillID); } else { - Backfills._Error($"abandon failed\n{error}"); + Backfills._Error($"{backfillID} abandon failed\n{error}"); } if (onCompletedDelegate is not null) @@ -232,6 +229,77 @@ internal void AddAssignedTicket(InjectedTicketDTO ticket) { AssignedTickets[ticket.ID] = ticket; } + + internal void RemoveOngoingBackfill(string backfillID) + { + OngoingBackfills.Remove(backfillID); + } + + internal void StartPollingBackfill(string backfillID, int consecutiveErrors = 0) + { + if (!Polling) + { + if (LogPollingUpdates) + { + Backfills._Notify($"polling {backfillID} stopped"); + } + return; + } + + if (LogPollingUpdates) + { + Backfills._Notify( + $"polling {backfillID} [{consecutiveErrors + 1}/{MaxConsecutivePollingErrors}]" + ); + } + + MatchmakingApi.GetBackfill( + backfillID, + (BackfillResponseDTO backfillRes, UnityWebRequest request) => + { + if (backfillRes.Status == "ASSIGNED") + { + AddAssignedTicket(backfillRes.AssignedTicket); + RemoveOngoingBackfill(backfillID); + Backfills._Update(backfillRes, $"{backfillID} assigned"); + } + else + { + Handler.StartCoroutine(DelayPollingBackfill(backfillID)); + } + }, + (string error, UnityWebRequest request) => + { + if (consecutiveErrors + 1 > MaxConsecutivePollingErrors) + { + Backfills._Error( + $"polling {backfillID} failed, reached maximum retries\n{error}" + ); + RemoveBackfill(backfillID); + } + else + { + if (request.responseCode == 429 || request.responseCode >= 500) + { + Handler.StartCoroutine( + DelayPollingBackfill(backfillID, consecutiveErrors + 1) + ); + } + else + { + Backfills._Error($"polling {backfillID} failed\n{error}"); + RemoveBackfill(backfillID); + } + } + } + ); + } + + internal IEnumerator DelayPollingBackfill(string backfillID, int consecutiveErrors = 0) + { + yield return new WaitForSeconds(PollingBackoffSeconds + (0.1f * Random.value)); + StartPollingBackfill(backfillID, consecutiveErrors); + } #endregion } } diff --git a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs index 0bb10fe..024f808 100644 --- a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs @@ -1,8 +1,6 @@ using Edgegap; using Edgegap.Matchmaking; -using System.Collections.Generic; using UnityEngine; -using L = Edgegap.Logger; using MyBackfillRequestDTO = Edgegap.Matchmaking.SimpleBackfillRequestDTO; using MyTicketsAttributes = Edgegap.Matchmaking.BackfillTicketAttributesDTO; @@ -90,14 +88,9 @@ string message string message ) => { - //MTODO - /* - if backfill create fail || backfill assigned -> --OngoingRequests - */ - if ( - action == ObservableActionType.Error - && message.Contains("create failed") + (action == ObservableActionType.Error && message.Contains("create failed")) + || (action == ObservableActionType.Update && message.Contains("assigned")) ) { --OngoingRequests; @@ -111,13 +104,15 @@ string message // Update is called once per frame void Update() { - if (!BackfillStopped) + if ( + !BackfillStopped + && MatchmakingServer.AssignedTickets.Count + OngoingRequests < TargetPlayerCount + ) { - if (MatchmakingServer.AssignedTickets.Count + OngoingRequests < TargetPlayerCount) - { - StartNewBackfill(); - } + StartNewBackfill(); } + + // todo check for leaving players => MatchmakingServer.RemoveAssignedTicket(ticketID); } public void OnApplicationQuit() @@ -133,8 +128,8 @@ public void StartNewBackfill() MyBackfillRequestDTO backfill = new MyBackfillRequestDTO( MatchmakingServer.MatchEnvs.MatchProfile, - BackfillAttributes, - MatchmakingServer.AssignedTickets + BackfillAttributes, + MatchmakingServer.AssignedTickets ); MatchmakingServer.AddBackfill(backfill); From f82d71bac5b701b2fe8525b9ea55e81a7243d6c9 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:46:50 -0400 Subject: [PATCH 06/16] remove all backfills --- Runtime/Matchmaking/Server.cs | 13 +++++++++- .../BackfillServerHandlerExample.cs | 24 ++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Runtime/Matchmaking/Server.cs b/Runtime/Matchmaking/Server.cs index 6189e54..8aa37dd 100644 --- a/Runtime/Matchmaking/Server.cs +++ b/Runtime/Matchmaking/Server.cs @@ -171,7 +171,11 @@ public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) public void RemoveAllBackfills() { Polling = false; - //MTODO foreach ticket call RemoveBackfill in parallel + + foreach (KeyValuePair> b in OngoingBackfills) + { + Handler.StartCoroutine(RemoveBackfillRoutine(b.Key)); + } } #endregion @@ -300,6 +304,13 @@ internal IEnumerator DelayPollingBackfill(string backfillID, int consecutiveErro yield return new WaitForSeconds(PollingBackoffSeconds + (0.1f * Random.value)); StartPollingBackfill(backfillID, consecutiveErrors); } + + internal IEnumerator RemoveBackfillRoutine(string backfillID) + { + L.Log($"Removing backfill {backfillID}"); + RemoveBackfill(backfillID); + yield return null; + } #endregion } } diff --git a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs index 024f808..c15a316 100644 --- a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs @@ -1,6 +1,7 @@ using Edgegap; using Edgegap.Matchmaking; using UnityEngine; +using L = Edgegap.Logger; using MyBackfillRequestDTO = Edgegap.Matchmaking.SimpleBackfillRequestDTO; using MyTicketsAttributes = Edgegap.Matchmaking.BackfillTicketAttributesDTO; @@ -28,7 +29,6 @@ public class BackfillServerHandlerExample : MonoBehaviour [Header("Logging")] public bool LogBackfillUpdates = true; - public bool LogTicketUpdates = true; public bool LogPollingUpdates = false; #endregion @@ -37,7 +37,7 @@ public Server< MyTicketsAttributes > MatchmakingServer; - public bool BackfillStopped = false; + private bool BackfillRunning = false; private int OngoingRequests = 0; private BackfillAttributes BackfillAttributes; @@ -78,8 +78,20 @@ void Start() ObservableActionType action, string message ) => - { - //MTODO + { + if (action == ObservableActionType.Update) + { + if (message == "healthy") + { + BackfillRunning = true; + } + else if (message != "healthy") + { + // todo handle outage/maintenance + L.Error($"Matchmaking error.\n{monitor.Current}"); + StopBackfill(); + } + } }, // handle backfill assignment ( @@ -105,7 +117,7 @@ string message void Update() { if ( - !BackfillStopped + BackfillRunning && MatchmakingServer.AssignedTickets.Count + OngoingRequests < TargetPlayerCount ) { @@ -137,7 +149,7 @@ public void StartNewBackfill() public void StopBackfill() { - BackfillStopped = true; + BackfillRunning = false; MatchmakingServer.RemoveAllBackfills(); } } From 88b553605f46c1e389d20c4f4c83225b62401b08 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:16:28 -0400 Subject: [PATCH 07/16] client handler start --- Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs | 12 ++ .../BackfillClientHandlerExample.cs | 121 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs diff --git a/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs b/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs index 2d3e0cf..b49a0c9 100644 --- a/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs +++ b/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs @@ -27,6 +27,18 @@ public SimpleGroupUpRequestDTO( } } + public class BackfillGroupUpRequestDTO : GroupUpRequestDTO + { + public BackfillGroupUpRequestDTO( + Dictionary latencyBeacons, + string[] backfillGroupSize + ) + : base("backfill-example") + { + Attributes = new BackfillTicketAttributesDTO(latencyBeacons, backfillGroupSize); + } + } + public class AdvancedGroupUpRequestDTO : GroupUpRequestDTO { public AdvancedGroupUpRequestDTO( diff --git a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs new file mode 100644 index 0000000..9e61766 --- /dev/null +++ b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using Edgegap; +using Edgegap.Matchmaking; +using UnityEngine; +using UnityEngine.Networking; +using UnityEngine.UI; +using L = Edgegap.Logger; +using MyGroupUpRequestDTO = Edgegap.Matchmaking.BackfillGroupUpRequestDTO; +using MyTicketsAttributes = Edgegap.Matchmaking.BackfillTicketAttributesDTO; + +// todo replace BackfillTicketAttributesDTO with CustomTicketsAttributes +// todo replace BackfillGroupUpRequestDTO with CustomGroupUpRequestDTO + +public class BackfillClientHandlerExample : MonoBehaviour +{ + public static BackfillClientHandlerExample Instance { get; private set; } + + #region Matchmaking Configuration + + [Header("Matchmaker Instance")] + public string BaseUrl; + public string AuthToken; + + [Header("Exponential Retry")] + public int RequestTimeoutSeconds = 3; + public float PollingBackoffSeconds = 1f; + public int MaxConsecutivePollingErrors = 10; + + [Header("Expiration and Cleanup")] + public float RemoveAssignmentSeconds = 30f; + public bool DeleteGroupOnPause = false; + public bool DeleteGroupOnQuit = true; + + [Header("Logging")] + public bool LogGroupUpdates = true; + public bool LogPollingUpdates = false; + #endregion + + public GroupClient< + MyGroupUpRequestDTO, + MyTicketsAttributes + > MatchmakingClient; + + private void Awake() + { + if (Instance != null && Instance != this) + { + Destroy(this); + } + else + { + Instance = this; + } + } + + // Start is called once before the first execution of Update after the MonoBehaviour is created + void Start() + { + // configure Matchmaking + MatchmakingClient = new GroupClient< + MyGroupUpRequestDTO, + MyTicketsAttributes + >( + this, + BaseUrl, + AuthToken, + RequestTimeoutSeconds, + PollingBackoffSeconds, + MaxConsecutivePollingErrors, + RemoveAssignmentSeconds, + LogGroupUpdates, + LogPollingUpdates + ); + + // initialize Matchmaking + MatchmakingClient.Initialize( + // handle service monitoring + ( + Observable monitor, + ObservableActionType action, + string message + ) => + { + //MTODO + }, + // handle group assignment + ( + Observable group, + ObservableActionType action, + string message + ) => + { + //MTODO + } + ); + } + + public void OnApplicationPause(bool pause) + { + if (!DeleteGroupOnPause || MatchmakingClient.Group.Current is null) + return; + StopMatchmaking(); + } + + public void OnApplicationQuit() + { + if (!DeleteGroupOnQuit) + return; + StopMatchmaking(); + } + + public void StartMatchmaking() + { + //MTODO + } + + public void StopMatchmaking() + { + //MTODO + } +} From 460ec92d85cbf1de2a0325de59f02080dec72a06 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:31:24 -0400 Subject: [PATCH 08/16] client group update handling --- Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs | 5 +- .../BackfillClientHandlerExample.cs | 99 +++++++++++++++++-- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs b/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs index b49a0c9..70f1228 100644 --- a/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs +++ b/Runtime/Matchmaking/DTOs/GroupUpRequestDTO.cs @@ -31,9 +31,10 @@ public class BackfillGroupUpRequestDTO : GroupUpRequestDTO latencyBeacons, - string[] backfillGroupSize + string[] backfillGroupSize, + bool isReady = false ) - : base("backfill-example") + : base("backfill-example", isReady) { Attributes = new BackfillTicketAttributesDTO(latencyBeacons, backfillGroupSize); } diff --git a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs index 9e61766..cfb97a6 100644 --- a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs @@ -20,6 +20,7 @@ public class BackfillClientHandlerExample : MonoBehaviour [Header("Matchmaker Instance")] public string BaseUrl; public string AuthToken; + public string[] BackfillGroupSize = { "new", "1" }; [Header("Exponential Retry")] public int RequestTimeoutSeconds = 3; @@ -41,6 +42,11 @@ public GroupClient< MyTicketsAttributes > MatchmakingClient; + #region UI + public Text StatusDisplay; + private string StatusDisplayDefaultPath = "/Canvas/StatusTxt"; + #endregion + private void Awake() { if (Instance != null && Instance != this) @@ -50,6 +56,19 @@ private void Awake() else { Instance = this; + + if (StatusDisplay == null) + { + L.Log("MM ClientHandler | No Status Display provided, using default."); + StatusDisplay = GameObject.Find(StatusDisplayDefaultPath)?.GetComponent(); + + if (StatusDisplay == null) + { + L.Warn( + $"MM ClientHandler | Unable to find default component {StatusDisplayDefaultPath} in scene." + ); + } + } } } @@ -81,7 +100,41 @@ void Start() string message ) => { - //MTODO + if (action == ObservableActionType.Update) + { + if (message == "healthy") + { + // todo update UI + StatusDisplay.text = "Fetching beacons..."; + + MatchmakingClient.Beacons( + (BeaconsResponseDTO beacons) => + { + Debug.Log($"beacons: {beacons}"); + + MatchmakingClient.MeasureBeaconsRoundTripTime( + beacons.Beacons, + (Dictionary pings) => + { + StatusDisplay.text = "Starting matchmaking."; + StartMatchmaking(pings, true); + } + ); + }, + (string error, UnityWebRequest request) => + { + // todo handle beacon downtime, create tickets without beacons? + Debug.Log($"beacon error: {request}"); + } + ); + } + else if (message != "healthy") + { + // todo handle outage/maintenance + Debug.LogError($"Matchmaking error.\n{monitor.Current}"); + MatchmakingClient.StopMatchmaking(); + } + } }, // handle group assignment ( @@ -89,8 +142,39 @@ string message ObservableActionType action, string message ) => - { - //MTODO + { + if ( + action == ObservableActionType.Update + && ( + message.Contains("created") + || message.Contains("joined") + || message.Contains("updated") + || message.Contains("abandon") + ) + ) + { + // todo update UI + } + + if ( + action == ObservableActionType.Update + && message.Contains("updated") + && group.Current.Status == "MATCH_FOUND" + ) + { + StatusDisplay.text = "Match found, awaiting assignment."; + } + + if ( + action == ObservableActionType.Update + && message.Contains("updated") + && group.Current.Status == "HOST_ASSIGNED" + ) + { + // todo join game on pre-defined game port + StatusDisplay.text = $"Host assigned, joining game.\n{group.Current.Assignment.Fqdn}"; + L.Log($"joining game: {group.Current.Assignment.Ports["gameport"].Link}"); + } } ); } @@ -109,13 +193,16 @@ public void OnApplicationQuit() StopMatchmaking(); } - public void StartMatchmaking() + public void StartMatchmaking(Dictionary pings, bool isReady) { - //MTODO + MatchmakingClient.CreateGroup(new MyGroupUpRequestDTO(pings, BackfillGroupSize, isReady)); } public void StopMatchmaking() { - //MTODO + if (enabled) + { + MatchmakingClient.StopMatchmaking(); + } } } From 4cfd811b40b91ac3685be239cae97838d2b6de1d Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:24:18 -0400 Subject: [PATCH 09/16] start scene --- Samples~/MatchmakingBackfill/Backfill.unity | 686 ++++++++++++++++++ .../BackfillClientHandlerExample.cs | 1 + .../BackfillServerHandlerExample.cs | 3 + 3 files changed, 690 insertions(+) create mode 100644 Samples~/MatchmakingBackfill/Backfill.unity diff --git a/Samples~/MatchmakingBackfill/Backfill.unity b/Samples~/MatchmakingBackfill/Backfill.unity new file mode 100644 index 0000000..3c7dfae --- /dev/null +++ b/Samples~/MatchmakingBackfill/Backfill.unity @@ -0,0 +1,686 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 2 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &140943384 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 140943386} + - component: {fileID: 140943385} + m_Layer: 0 + m_Name: MMClientHandler + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &140943385 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 140943384} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8ed5835abafab584fa372ee85dcdc85c, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::BackfillClientHandlerExample + BaseUrl: + AuthToken: + BackfillGroupSize: + - new + - 1 + RequestTimeoutSeconds: 3 + PollingBackoffSeconds: 1 + MaxConsecutivePollingErrors: 10 + RemoveAssignmentSeconds: 30 + DeleteGroupOnPause: 0 + DeleteGroupOnQuit: 1 + LogGroupUpdates: 1 + LogPollingUpdates: 0 + StatusDisplay: {fileID: 0} +--- !u!4 &140943386 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 140943384} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 516.7947, y: 266.4271, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &194269197 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 194269198} + - component: {fileID: 194269200} + - component: {fileID: 194269199} + m_Layer: 5 + m_Name: StatusTxt + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &194269198 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 194269197} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1142204741} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: -3.4035} + m_SizeDelta: {x: 675.69, y: 371.41} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &194269199 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 194269197} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Text + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 32 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 3 + m_MaxSize: 42 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: STATUS TXT +--- !u!222 &194269200 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 194269197} + m_CullTransparentMesh: 1 +--- !u!1 &579410740 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 579410742} + - component: {fileID: 579410741} + - component: {fileID: 579410743} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &579410741 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 579410740} + m_Enabled: 1 + serializedVersion: 12 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize2D: {x: 0.5, y: 0.5} + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &579410742 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 579410740} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &579410743 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 579410740} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_CustomShadowLayers: 0 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 0 + m_RenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_ShadowRenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_Version: 4 + m_LightLayerMask: 1 + m_ShadowLayerMask: 1 + m_RenderingLayers: 1 + m_ShadowRenderingLayers: 1 +--- !u!1 &682726594 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 682726596} + - component: {fileID: 682726595} + m_Layer: 0 + m_Name: MMServerHandler + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &682726595 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682726594} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 403efcce096739c4a80384d137db9f59, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::BackfillServerHandlerExample + BaseUrl: + AuthToken: + TargetPlayerCount: 3 + RequestTimeoutSeconds: 3 + PollingBackoffSeconds: 1 + MaxConsecutivePollingErrors: 10 + DeleteBackfillOnQuit: 1 + LogBackfillUpdates: 1 + LogPollingUpdates: 0 +--- !u!4 &682726596 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 682726594} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 516.7947, y: 266.4271, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1142204737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1142204741} + - component: {fileID: 1142204740} + - component: {fileID: 1142204739} + - component: {fileID: 1142204738} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1142204738 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1142204737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1142204739 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1142204737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1142204740 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1142204737} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 1882797712} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 25 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1142204741 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1142204737} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 194269198} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1882797710 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1882797713} + - component: {fileID: 1882797712} + - component: {fileID: 1882797711} + - component: {fileID: 1882797714} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1882797711 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1882797710} + m_Enabled: 1 +--- !u!20 &1882797712 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1882797710} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.04905659, g: 0.04905659, b: 0.04905659, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1882797713 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1882797710} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1882797714 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1882797710} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1882797713} + - {fileID: 579410742} + - {fileID: 1142204741} + - {fileID: 140943386} + - {fileID: 682726596} diff --git a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs index cfb97a6..c21b8fd 100644 --- a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs @@ -43,6 +43,7 @@ public GroupClient< > MatchmakingClient; #region UI + [Header("UI")] public Text StatusDisplay; private string StatusDisplayDefaultPath = "/Canvas/StatusTxt"; #endregion diff --git a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs index c15a316..77ef2f8 100644 --- a/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillServerHandlerExample.cs @@ -41,6 +41,7 @@ public Server< private int OngoingRequests = 0; private BackfillAttributes BackfillAttributes; +#if UNITY_SERVER private void Awake() { if (Instance != null && Instance != this) @@ -118,6 +119,7 @@ void Update() { if ( BackfillRunning + && TargetPlayerCount > 0 && MatchmakingServer.AssignedTickets.Count + OngoingRequests < TargetPlayerCount ) { @@ -152,4 +154,5 @@ public void StopBackfill() BackfillRunning = false; MatchmakingServer.RemoveAllBackfills(); } +#endif } From c74a8b46670aab34f035b2204017e21b63666f3f Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:58:05 -0400 Subject: [PATCH 10/16] extra status text --- .../BackfillClientHandlerExample.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs index c21b8fd..fc2670e 100644 --- a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs @@ -48,6 +48,7 @@ public GroupClient< private string StatusDisplayDefaultPath = "/Canvas/StatusTxt"; #endregion +#if !UNITY_SERVER private void Awake() { if (Instance != null && Instance != this) @@ -69,6 +70,10 @@ private void Awake() $"MM ClientHandler | Unable to find default component {StatusDisplayDefaultPath} in scene." ); } + else + { + StatusDisplay.text = ""; + } } } } @@ -125,14 +130,16 @@ string message (string error, UnityWebRequest request) => { // todo handle beacon downtime, create tickets without beacons? - Debug.Log($"beacon error: {request}"); + StatusDisplay.text = "Beacon downtime."; + L.Log($"beacon error: {request}"); } ); } else if (message != "healthy") { // todo handle outage/maintenance - Debug.LogError($"Matchmaking error.\n{monitor.Current}"); + StatusDisplay.text = "Matchmaking error."; + L.Error($"Matchmaking error.\n{monitor.Current}"); MatchmakingClient.StopMatchmaking(); } } @@ -206,4 +213,5 @@ public void StopMatchmaking() MatchmakingClient.StopMatchmaking(); } } +#endif } From ca709979b222aa070cad789291bda7103a7435b8 Mon Sep 17 00:00:00 2001 From: edge-marge <141053702+edge-marge@users.noreply.github.com> Date: Wed, 15 Jul 2026 10:44:20 -0400 Subject: [PATCH 11/16] add client disconnect w todo, missing server abandon check --- Runtime/Matchmaking/Server.cs | 4 +- .../BackfillClientHandlerExample.cs | 39 +++++++++++++++++++ .../BackfillServerHandlerExample.cs | 5 ++- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Runtime/Matchmaking/Server.cs b/Runtime/Matchmaking/Server.cs index 8aa37dd..e333b2d 100644 --- a/Runtime/Matchmaking/Server.cs +++ b/Runtime/Matchmaking/Server.cs @@ -79,6 +79,7 @@ public Server( public void RemoveAssignedTicket(string ticketID) { + L.Log($"Removing assigned ticket {ticketID}"); AssignedTickets.Remove(ticketID); } @@ -153,13 +154,14 @@ public void RemoveBackfill(string backfillID, Action onCompletedDelegate = null) if (request.responseCode == 404) { Backfills._Update(null, $"{backfillID} abandon failed (not found)"); - RemoveOngoingBackfill(backfillID); } else { Backfills._Error($"{backfillID} abandon failed\n{error}"); } + RemoveOngoingBackfill(backfillID); + if (onCompletedDelegate is not null) { onCompletedDelegate(); diff --git a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs index fc2670e..a8f92d9 100644 --- a/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs +++ b/Samples~/MatchmakingBackfill/BackfillClientHandlerExample.cs @@ -45,9 +45,14 @@ public GroupClient< #region UI [Header("UI")] public Text StatusDisplay; + public Button DisconnectButton; + private string StatusDisplayDefaultPath = "/Canvas/StatusTxt"; + private string DisconnectBtnDefaultPath = "/Canvas/DisconnectBtn"; #endregion + private string TicketID; + #if !UNITY_SERVER private void Awake() { @@ -75,6 +80,24 @@ private void Awake() StatusDisplay.text = ""; } } + + if (DisconnectButton == null) + { + L.Log("MM ClientHandler | Disconnect Button provided, using default."); + DisconnectButton = GameObject.Find(DisconnectBtnDefaultPath)?.GetComponent