This commit is contained in:
2025-06-16 15:14:23 +02:00
committed by devbeni
parent 60fe4620ff
commit 4ff561284f
3174 changed files with 428263 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 447b4ad1a3db7cf4fa5a0709d297ba9b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,345 @@
using System;
using System.Collections;
using System.Threading;
using Mirror;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Edgegap
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/edgegap-transports/edgegap-relay")]
public class EdgegapLobbyKcpTransport : EdgegapKcpTransport
{
[Header("Lobby Settings")]
[Tooltip("URL to the Edgegap lobby service, automatically filled in after completing the creation process via button below (or enter manually)")]
public string lobbyUrl;
[Tooltip("How long to wait for the relay to be assigned after starting a lobby")]
public float lobbyWaitTimeout = 60;
public LobbyApi Api;
private LobbyCreateRequest? _request;
private string _lobbyId;
private string _playerId;
private TransportStatus _status = TransportStatus.Offline;
public enum TransportStatus
{
Offline,
CreatingLobby,
StartingLobby,
JoiningLobby,
WaitingRelay,
Connecting,
Connected,
Error,
}
public TransportStatus Status
{
get
{
if (!NetworkClient.active && !NetworkServer.active)
{
return TransportStatus.Offline;
}
if (_status == TransportStatus.Connecting)
{
if (NetworkServer.active)
{
switch (((EdgegapKcpServer)this.server).state)
{
case ConnectionState.Valid:
return TransportStatus.Connected;
case ConnectionState.Invalid:
case ConnectionState.SessionTimeout:
case ConnectionState.Error:
return TransportStatus.Error;
}
}
else if (NetworkClient.active)
{
switch (((EdgegapKcpClient)this.client).connectionState)
{
case ConnectionState.Valid:
return TransportStatus.Connected;
case ConnectionState.Invalid:
case ConnectionState.SessionTimeout:
case ConnectionState.Error:
return TransportStatus.Error;
}
}
}
return _status;
}
}
protected override void Awake()
{
base.Awake();
Api = new LobbyApi(lobbyUrl);
}
private void Reset()
{
this.relayGUI = false;
}
public override void ServerStart()
{
if (!_request.HasValue)
{
throw new Exception("No lobby request set. Call SetServerLobbyParams");
}
_status = TransportStatus.CreatingLobby;
Api.CreateLobby(_request.Value, lobby =>
{
_lobbyId = lobby.lobby_id;
_status = TransportStatus.StartingLobby;
Api.StartLobby(new LobbyIdRequest(_lobbyId), () =>
{
StartCoroutine(WaitForLobbyRelay(_lobbyId, true));
}, error =>
{
_status = TransportStatus.Error;
string errorMsg = $"Could not start lobby: {error}";
Debug.LogError(errorMsg);
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
});
},
error =>
{
_status = TransportStatus.Error;
string errorMsg = $"Couldn't create lobby: {error}";
Debug.LogError(errorMsg);
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
});
}
public override void ServerStop()
{
base.ServerStop();
Api.DeleteLobby(_lobbyId, () =>
{
// yay
}, error =>
{
OnServerError?.Invoke(0, TransportError.Unexpected, $"Failed to delete lobby: {error}");
});
}
public override void ClientDisconnect()
{
base.ClientDisconnect();
// this gets called for host mode as well
if (!NetworkServer.active)
{
Api.LeaveLobby(new LobbyJoinOrLeaveRequest
{
player = new LobbyJoinOrLeaveRequest.Player
{
id = _playerId
},
lobby_id = _lobbyId
}, () =>
{
// yay
}, error =>
{
string errorMsg = $"Failed to leave lobby: {error}";
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
Debug.LogError(errorMsg);
});
}
}
public override void ClientConnect(string address)
{
_lobbyId = address;
_playerId = RandomPlayerId();
_status = TransportStatus.JoiningLobby;
Api.JoinLobby(new LobbyJoinOrLeaveRequest
{
player = new LobbyJoinOrLeaveRequest.Player
{
id = _playerId,
},
lobby_id = address
}, () =>
{
StartCoroutine(WaitForLobbyRelay(_lobbyId, false));
}, error =>
{
_status = TransportStatus.Offline;
string errorMsg = $"Failed to join lobby: {error}";
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
Debug.LogError(errorMsg);
OnClientDisconnected?.Invoke();
});
}
private IEnumerator WaitForLobbyRelay(string lobbyId, bool forServer)
{
_status = TransportStatus.WaitingRelay;
double startTime = NetworkTime.localTime;
bool running = true;
while (running)
{
if (NetworkTime.localTime - startTime >= lobbyWaitTimeout)
{
_status = TransportStatus.Error;
string errorMsg = "Timed out waiting for lobby.";
Debug.LogError(errorMsg);
if (forServer)
{
_status = TransportStatus.Error;
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
_status = TransportStatus.Error;
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
yield break;
}
bool waitingForResponse = true;
Api.GetLobby(lobbyId, lobby =>
{
waitingForResponse = false;
if (string.IsNullOrEmpty(lobby.assignment.ip))
{
// no lobby deployed yet, have the outer loop retry
return;
}
relayAddress = lobby.assignment.ip;
foreach (Lobby.Port aport in lobby.assignment.ports)
{
if (aport.protocol == "UDP")
{
if (aport.name == "server")
{
relayGameServerPort = (ushort)aport.port;
}
else if (aport.name == "client")
{
relayGameClientPort = (ushort)aport.port;
}
}
}
bool found = false;
foreach (Lobby.Player player in lobby.players)
{
if (player.id == _playerId)
{
userId = player.authorization_token;
sessionId = lobby.assignment.authorization_token;
found = true;
break;
}
}
running = false;
if (!found)
{
string errorMsg = $"Couldn't find my player ({_playerId})";
Debug.LogError(errorMsg);
if (forServer)
{
_status = TransportStatus.Error;
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
_status = TransportStatus.Error;
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
return;
}
_status = TransportStatus.Connecting;
if (forServer)
{
base.ServerStart();
}
else
{
base.ClientConnect("");
}
}, error =>
{
running = false;
waitingForResponse = false;
_status = TransportStatus.Error;
string errorMsg = $"Failed to get lobby info: {error}";
Debug.LogError(errorMsg);
if (forServer)
{
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
});
while (waitingForResponse)
{
yield return null;
}
yield return new WaitForSeconds(0.2f);
}
}
private static string RandomPlayerId()
{
return $"mirror-player-{Random.Range(1, int.MaxValue)}";
}
public void SetServerLobbyParams(string lobbyName, int capacity)
{
SetServerLobbyParams(new LobbyCreateRequest
{
player = new LobbyCreateRequest.Player
{
id = RandomPlayerId(),
},
annotations = new LobbyCreateRequest.Annotation[]
{
},
capacity = capacity,
is_joinable = true,
name = lobbyName,
tags = new string[]
{
}
});
}
public void SetServerLobbyParams(LobbyCreateRequest request)
{
_playerId = request.player.id;
_request = request;
}
private void OnDestroy()
{
// attempt to clean up lobbies, if active
if (NetworkServer.active)
{
ServerStop();
// Absolutely make sure there's time for the network request to hit edgegap servers.
// sorry. this can go once the lobby service can timeout lobbies itself
Thread.Sleep(300);
}
else if (NetworkClient.active)
{
ClientDisconnect();
// Absolutely make sure there's time for the network request to hit edgegap servers.
// sorry. this can go once the lobby service can timeout lobbies itself
Thread.Sleep(300);
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fa9d4c3f48a245ed89f122f44e1e81ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/EdgegapLobbyKcpTransport.cs
uploadId: 736421

View File

@ -0,0 +1,295 @@
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace Edgegap
{
// Implements the edgegap lobby api: https://docs.edgegap.com/docs/lobby/functions
public class LobbyApi
{
[Header("Lobby Config")]
public string LobbyUrl;
public LobbyBrief[] Lobbies;
public LobbyApi(string url)
{
LobbyUrl = url;
}
private static UnityWebRequest SendJson<T>(string url, T data, string method = "POST")
{
string body = JsonUtility.ToJson(data);
UnityWebRequest request = new UnityWebRequest(url, method);
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body));
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Accept", "application/json");
request.SetRequestHeader("Content-Type", "application/json");
return request;
}
private static bool CheckErrorResponse(UnityWebRequest request, Action<string> onError)
{
#if UNITY_2020_3_OR_NEWER
if (request.result != UnityWebRequest.Result.Success)
{
// how I hate http libs that think they need to be smart and handle status code errors.
if (request.result != UnityWebRequest.Result.ProtocolError || request.responseCode == 0)
{
onError?.Invoke(request.error);
return true;
}
}
#else
if (request.isNetworkError)
{
onError?.Invoke(request.error);
return true;
}
#endif
if (request.responseCode < 200 || request.responseCode >= 300)
{
onError?.Invoke($"non-200 status code: {request.responseCode}. Body:\n {request.downloadHandler.text}");
return true;
}
return false;
}
public void RefreshLobbies(Action<LobbyBrief[]> onLoaded, Action<string> onError)
{
UnityWebRequest request = UnityWebRequest.Get($"{LobbyUrl}/lobbies");
request.SendWebRequest().completed += operation =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
ListLobbiesResponse lobbies = JsonUtility.FromJson<ListLobbiesResponse>(request.downloadHandler.text);
Lobbies = lobbies.data;
onLoaded?.Invoke(lobbies.data);
}
};
}
public void CreateLobby(LobbyCreateRequest createData, Action<Lobby> onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies", createData);
request.SetRequestHeader("Content-Type", "application/json");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
Lobby lobby = JsonUtility.FromJson<Lobby>(request.downloadHandler.text);
onResponse?.Invoke(lobby);
}
};
}
public void UpdateLobby(string lobbyId, LobbyUpdateRequest updateData, Action<LobbyBrief> onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies/{lobbyId}", updateData, "PATCH");
request.SetRequestHeader("Content-Type", "application/json");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
LobbyBrief lobby = JsonUtility.FromJson<LobbyBrief>(request.downloadHandler.text);
onResponse?.Invoke(lobby);
}
};
}
public void GetLobby(string lobbyId, Action<Lobby> onResponse, Action<string> onError)
{
UnityWebRequest request = UnityWebRequest.Get($"{LobbyUrl}/lobbies/{lobbyId}");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
Lobby lobby = JsonUtility.FromJson<Lobby>(request.downloadHandler.text);
onResponse?.Invoke(lobby);
}
};
}
public void JoinLobby(LobbyJoinOrLeaveRequest data, Action onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies:join", data);
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
onResponse?.Invoke();
}
};
}
public void LeaveLobby(LobbyJoinOrLeaveRequest data, Action onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies:leave", data);
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
onResponse?.Invoke();
}
};
}
public void StartLobby(LobbyIdRequest data, Action onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies:start", data);
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
onResponse?.Invoke();
}
};
}
public void DeleteLobby(string lobbyId, Action onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson($"{LobbyUrl}/lobbies/{lobbyId}", "", "DELETE");
request.SetRequestHeader("Content-Type", "application/json");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
onResponse?.Invoke();
}
};
}
struct CreateLobbyServiceRequest
{
public string name;
}
public struct LobbyServiceResponse
{
public string name;
public string url;
public string status;
}
public static void TrimApiKey(ref string apiKey)
{
if (apiKey == null)
{
return;
}
if (apiKey.StartsWith("token "))
{
apiKey = apiKey.Substring("token ".Length);
}
apiKey = apiKey.Trim();
}
public static void CreateAndDeployLobbyService(string apiKey, string name, Action<LobbyServiceResponse> onResponse, Action<string> onError)
{
TrimApiKey(ref apiKey);
// try to get the lobby first
GetLobbyService(apiKey, name, response =>
{
if (response == null)
{
CreateLobbyService(apiKey, name, onResponse, onError);
}
else if (!string.IsNullOrEmpty(response.Value.url))
{
onResponse(response.Value);
}
else
{
DeployLobbyService(apiKey, name, onResponse, onError);
}
}, onError);
}
private static void CreateLobbyService(string apiKey, string name, Action<LobbyServiceResponse> onResponse, Action<string> onError)
{
UnityWebRequest request = SendJson("https://api.edgegap.com/v1/lobbies", new CreateLobbyServiceRequest
{
name = name
});
request.SetRequestHeader("Authorization", $"token {apiKey}");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
DeployLobbyService(apiKey, name, onResponse, onError);
}
};
}
public static void GetLobbyService(string apiKey, string name, Action<LobbyServiceResponse?> onResponse, Action<string> onError)
{
TrimApiKey(ref apiKey);
var request = UnityWebRequest.Get($"https://api.edgegap.com/v1/lobbies/{name}");
request.SetRequestHeader("Authorization", $"token {apiKey}");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (request.responseCode == 404)
{
onResponse(null);
return;
}
if (CheckErrorResponse(request, onError)) return;
LobbyServiceResponse response = JsonUtility.FromJson<LobbyServiceResponse>(request.downloadHandler.text);
onResponse(response);
}
};
}
public static void TerminateLobbyService(string apiKey, string name, Action<LobbyServiceResponse> onResponse, Action<string> onError)
{
TrimApiKey(ref apiKey);
var request = SendJson("https://api.edgegap.com/v1/lobbies:terminate", new CreateLobbyServiceRequest
{
name = name
});
request.SetRequestHeader("Authorization", $"token {apiKey}");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
LobbyServiceResponse response = JsonUtility.FromJson<LobbyServiceResponse>(request.downloadHandler.text);
onResponse?.Invoke(response);
}
};
}
private static void DeployLobbyService(string apiKey, string name, Action<LobbyServiceResponse> onResponse, Action<string> onError)
{
var request = SendJson("https://api.edgegap.com/v1/lobbies:deploy", new CreateLobbyServiceRequest
{
name = name
});
request.SetRequestHeader("Authorization", $"token {apiKey}");
request.SendWebRequest().completed += (op) =>
{
using (request)
{
if (CheckErrorResponse(request, onError)) return;
LobbyServiceResponse response = JsonUtility.FromJson<LobbyServiceResponse>(request.downloadHandler.text);
onResponse?.Invoke(response);
}
};
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 64510fc75d0d75f4185fec1cf4d12206
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/LobbyApi.cs
uploadId: 736421

View File

@ -0,0 +1,138 @@
using System;
using System.Threading;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace Edgegap
{
public class LobbyServiceCreateDialogue : EditorWindow
{
public Action<string> onLobby;
public bool waitingCreate;
public bool waitingStatus;
private string _name;
private string _key;
private string _lastStatus;
private void Awake()
{
minSize = maxSize = new Vector2(450, 300);
titleContent = new GUIContent("Edgegap Lobby Service Setup");
}
private void OnGUI()
{
if (waitingCreate)
{
EditorGUILayout.LabelField("Waiting for lobby to create . . . ");
return;
}
if (waitingStatus)
{
EditorGUILayout.LabelField("Waiting for lobby to deploy . . . ");
EditorGUILayout.LabelField($"Latest status: {_lastStatus}");
return;
}
_key = EditorGUILayout.TextField("Edgegap API key", _key);
LobbyApi.TrimApiKey(ref _key);
EditorGUILayout.HelpBox(new GUIContent("Your API key won't be saved."));
if (GUILayout.Button("I have no api key?"))
{
Application.OpenURL("https://app.edgegap.com/user-settings?tab=tokens");
}
EditorGUILayout.Separator();
EditorGUILayout.HelpBox("There's currently a bug where lobby names longer than 5 characters can fail to deploy correctly and will return a \"503 Service Temporarily Unavailable\"\nIt's recommended to limit your lobby names to 4-5 characters for now", UnityEditor.MessageType.Warning);
_name = EditorGUILayout.TextField("Lobby Name", _name);
EditorGUILayout.HelpBox(new GUIContent("The lobby name is your games identifier for the lobby service"));
if (GUILayout.Button("Create"))
{
if (string.IsNullOrWhiteSpace(_key) || string.IsNullOrWhiteSpace(_name))
{
EditorUtility.DisplayDialog("Error", "Key and Name can't be empty.", "Ok");
}
else
{
waitingCreate = true;
Repaint();
LobbyApi.CreateAndDeployLobbyService(_key.Trim(), _name.Trim(), res =>
{
waitingCreate = false;
waitingStatus = true;
_lastStatus = res.status;
RefreshStatus();
Repaint();
}, error =>
{
EditorUtility.DisplayDialog("Failed to create lobby", $"The following error happened while trying to create (&deploy) the lobby service:\n\n{error}", "Ok");
waitingCreate = false;
});
return;
}
}
if (GUILayout.Button("Cancel"))
Close();
EditorGUILayout.HelpBox(new GUIContent("Note: If you forgot your lobby url simply re-create it with the same name!\nIt will re-use the existing lobby service"));
EditorGUILayout.Separator();
EditorGUILayout.Separator();
if (GUILayout.Button("Terminate existing deploy"))
{
if (string.IsNullOrWhiteSpace(_key) || string.IsNullOrWhiteSpace(_name))
{
EditorUtility.DisplayDialog("Error", "Key and Name can't be empty.", "Ok");
}
else
{
LobbyApi.TerminateLobbyService(_key.Trim(), _name.Trim(), res =>
{
EditorUtility.DisplayDialog("Success", $"The lobby service will start terminating (shutting down the deploy) now", "Ok");
}, error =>
{
EditorUtility.DisplayDialog("Failed to terminate lobby", $"The following error happened while trying to terminate the lobby service:\n\n{error}", "Ok");
});
}
}
EditorGUILayout.HelpBox(new GUIContent("Done with your lobby?\nEnter the same name as creation to shut it down"));
}
private void RefreshStatus()
{
// Stop if window is closed
if (!this)
{
return;
}
LobbyApi.GetLobbyService(_key, _name, res =>
{
if (!res.HasValue)
{
EditorUtility.DisplayDialog("Failed to create lobby", $"The lobby seems to have vanished while waiting for it to deploy.", "Ok");
waitingStatus = false;
Repaint();
return;
}
if (!string.IsNullOrEmpty(res.Value.url))
{
onLobby(res.Value.url);
Close();
return;
}
_lastStatus = res.Value.status;
Repaint();
Thread.Sleep(100); // :( but this is a lazy editor script, its fiiine
RefreshStatus();
}, error =>
{
EditorUtility.DisplayDialog("Failed to create lobby", $"The following error happened while trying to create (&deploy) a lobby:\n\n{error}", "Ok");
waitingStatus = false;
});
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 25579cc004424981bf0b05bcec65df0a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/LobbyServiceCreateDialogue.cs
uploadId: 736421

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Reflection;
using kcp2k;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace Edgegap
{
[CustomEditor(typeof(EdgegapLobbyKcpTransport))]
public class EncryptionTransportInspector : UnityEditor.Editor
{
SerializedProperty lobbyUrlProperty;
SerializedProperty lobbyWaitTimeoutProperty;
private List<SerializedProperty> kcpProperties = new List<SerializedProperty>();
// Assuming proper SerializedProperty definitions for properties
// Add more SerializedProperty fields related to different modes as needed
void OnEnable()
{
lobbyUrlProperty = serializedObject.FindProperty("lobbyUrl");
lobbyWaitTimeoutProperty = serializedObject.FindProperty("lobbyWaitTimeout");
// Get public fields from KcpTransport
kcpProperties.Clear();
FieldInfo[] fields = typeof(KcpTransport).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (var field in fields)
{
SerializedProperty prop = serializedObject.FindProperty(field.Name);
if (prop == null)
{
// callbacks have no property
continue;
}
kcpProperties.Add(prop);
}
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lobbyUrlProperty);
if (GUILayout.Button("Create & Deploy Lobby"))
{
var input = CreateInstance<LobbyServiceCreateDialogue>();
input.onLobby = (url) =>
{
lobbyUrlProperty.stringValue = url;
serializedObject.ApplyModifiedProperties();
};
input.ShowUtility();
}
EditorGUILayout.PropertyField(lobbyWaitTimeoutProperty);
EditorGUILayout.Separator();
foreach (SerializedProperty prop in kcpProperties)
{
EditorGUILayout.PropertyField(prop);
}
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7d7cc53263184754a4682335440df515
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/LobbyTransportInspector.cs
uploadId: 736421

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9b459cf5e084bdd8b196df849a2c519
timeCreated: 1709953502

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions#functions
[Serializable]
public struct ListLobbiesResponse
{
public int count;
public LobbyBrief[] data;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fdb37041d9464f8c90ac86942b940565
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/ListLobbiesResponse.cs
uploadId: 736421

View File

@ -0,0 +1,45 @@
using System;
using UnityEngine;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions#getting-a-specific-lobbys-information
[Serializable]
public struct Lobby
{
[Serializable]
public struct Player
{
public uint authorization_token;
public string id;
public bool is_host;
}
[Serializable]
public struct Port
{
public string name;
public int port;
public string protocol;
}
[Serializable]
public struct Assignment
{
public uint authorization_token;
public string host;
public string ip;
public Port[] ports;
}
public Assignment assignment;
public string name;
public string lobby_id;
public bool is_joinable;
public bool is_started;
public int player_count;
public int capacity;
public int available_slots => capacity - player_count;
public string[] tags;
public Player[] players;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 64db55f096cd4ace83e1aa1c0c0588f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/Lobby.cs
uploadId: 736421

View File

@ -0,0 +1,17 @@
using System;
namespace Edgegap
{
// Brief lobby data, returned by the list function
[Serializable]
public struct LobbyBrief
{
public string lobby_id;
public string name;
public bool is_joinable;
public bool is_started;
public int player_count;
public int capacity;
public int available_slots => capacity - player_count;
public string[] tags;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6018ece006144e719c6b3f0d4e256d7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/LobbyBrief.cs
uploadId: 736421

View File

@ -0,0 +1,27 @@
using System;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions#creating-a-new-lobby
[Serializable]
public struct LobbyCreateRequest
{
[Serializable]
public struct Player
{
public string id;
}
[Serializable]
public struct Annotation
{
public bool inject;
public string key;
public string value;
}
public Annotation[] annotations; // todo
public int capacity;
public bool is_joinable;
public string name;
public Player player;
public string[] tags;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4040c1adafc3449eaebd3bd22aa3ff26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/LobbyCreateRequest.cs
uploadId: 736421

View File

@ -0,0 +1,14 @@
using System;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions/#starting-a-lobby
[Serializable]
public struct LobbyIdRequest
{
public string lobby_id;
public LobbyIdRequest(string lobbyId)
{
lobby_id = lobbyId;
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 219c7fba8724473caf170c6254e6dc45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/LobbyIdRequest.cs
uploadId: 736421

View File

@ -0,0 +1,17 @@
using System;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions#updating-a-lobby
// https://docs.edgegap.com/docs/lobby/functions#leaving-a-lobby
[Serializable]
public struct LobbyJoinOrLeaveRequest
{
[Serializable]
public struct Player
{
public string id;
}
public string lobby_id;
public Player player;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4091d555e62341f0ac30479952d517aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/LobbyJoinOrLeaveRequest.cs
uploadId: 736421

View File

@ -0,0 +1,12 @@
using System;
namespace Edgegap
{
// https://docs.edgegap.com/docs/lobby/functions#updating-a-lobby
[Serializable]
public struct LobbyUpdateRequest
{
public int capacity;
public bool is_joinable;
public string[] tags;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ee158bc379f44cdf9904578f37a5e7a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapLobby/Models/LobbyUpdateRequest.cs
uploadId: 736421

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 62c28e855fc644011b4079c268b46b71
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,141 @@
// overwrite RawSend/Receive
using System;
using System.Net.Sockets;
using Mirror;
using UnityEngine;
using kcp2k;
namespace Edgegap
{
public class EdgegapKcpClient : KcpClient
{
// need buffer larger than KcpClient.rawReceiveBuffer to add metadata
readonly byte[] relayReceiveBuffer;
// authentication
public uint userId;
public uint sessionId;
public ConnectionState connectionState = ConnectionState.Disconnected;
// ping
double lastPingTime;
public EdgegapKcpClient(
Action OnConnected,
Action<ArraySegment<byte>, KcpChannel> OnData,
Action OnDisconnected,
Action<ErrorCode, string> OnError,
KcpConfig config)
: base(OnConnected, OnData, OnDisconnected, OnError, config)
{
relayReceiveBuffer = new byte[config.Mtu + Protocol.Overhead];
}
// custom start function with relay parameters; connects udp client.
public void Connect(string relayAddress, ushort relayPort, uint userId, uint sessionId)
{
// reset last state
connectionState = ConnectionState.Checking;
this.userId = userId;
this.sessionId = sessionId;
// reuse base connect
base.Connect(relayAddress, relayPort);
}
// parse metadata, then pass to kcp
protected override bool RawReceive(out ArraySegment<byte> segment)
{
segment = default;
if (socket == null) return false;
try
{
if (socket.ReceiveNonBlocking(relayReceiveBuffer, out ArraySegment<byte> content))
{
using (NetworkReaderPooled reader = NetworkReaderPool.Get(content))
{
// parse message type
if (reader.Remaining == 0)
{
Debug.LogWarning($"EdgegapClient: message of {content.Count} is too small to parse.");
return false;
}
byte messageType = reader.ReadByte();
// handle message type
switch (messageType)
{
case (byte)MessageType.Ping:
{
// parse state
if (reader.Remaining < 1) return false;
ConnectionState last = connectionState;
connectionState = (ConnectionState)reader.ReadByte();
// log state changes for debugging.
if (connectionState != last) Debug.Log($"EdgegapClient: state updated to: {connectionState}");
// return true indicates Mirror to keep checking
// for further messages.
return true;
}
case (byte)MessageType.Data:
{
segment = reader.ReadBytesSegment(reader.Remaining);
return true;
}
// wrong message type. return false, don't throw.
default: return false;
}
}
}
}
catch (SocketException e)
{
Log.Info($"EdgegapClient: looks like the other end has closed the connection. This is fine: {e}");
Disconnect();
}
return false;
}
protected override void RawSend(ArraySegment<byte> data)
{
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
writer.WriteUInt(userId);
writer.WriteUInt(sessionId);
writer.WriteByte((byte)MessageType.Data);
writer.WriteBytes(data.Array, data.Offset, data.Count);
base.RawSend(writer);
}
}
void SendPing()
{
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
writer.WriteUInt(userId);
writer.WriteUInt(sessionId);
writer.WriteByte((byte)MessageType.Ping);
base.RawSend(writer);
}
}
public override void TickOutgoing()
{
if (connected)
{
// ping every interval for keepalive & handshake
if (NetworkTime.localTime >= lastPingTime + Protocol.PingInterval)
{
SendPing();
lastPingTime = NetworkTime.localTime;
}
}
base.TickOutgoing();
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a0d6fba7098f4ea3949d0195e8276adc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/EdgegapKcpClient.cs
uploadId: 736421

View File

@ -0,0 +1,203 @@
using System;
using System.Net;
using System.Net.Sockets;
using Mirror;
using UnityEngine;
using kcp2k;
namespace Edgegap
{
public class EdgegapKcpServer : KcpServer
{
// need buffer larger than KcpClient.rawReceiveBuffer to add metadata
readonly byte[] relayReceiveBuffer;
// authentication
public uint userId;
public uint sessionId;
public ConnectionState state = ConnectionState.Disconnected;
// server is an UDP client talking to relay
protected Socket relaySocket;
public EndPoint remoteEndPoint;
// ping
double lastPingTime;
// custom 'active'. while connected to relay
bool relayActive;
public EdgegapKcpServer(
Action<int, IPEndPoint> OnConnected,
Action<int, ArraySegment<byte>, KcpChannel> OnData,
Action<int> OnDisconnected,
Action<int, ErrorCode, string> OnError,
KcpConfig config)
// TODO don't call base. don't listen to local UdpServer at all?
: base(OnConnected, OnData, OnDisconnected, OnError, config)
{
relayReceiveBuffer = new byte[config.Mtu + Protocol.Overhead];
}
public override bool IsActive() => relayActive;
// custom start function with relay parameters; connects udp client.
public void Start(string relayAddress, ushort relayPort, uint userId, uint sessionId)
{
// reset last state
state = ConnectionState.Checking;
this.userId = userId;
this.sessionId = sessionId;
// try resolve host name
if (!Common.ResolveHostname(relayAddress, out IPAddress[] addresses))
{
OnError(0, ErrorCode.DnsResolve, $"Failed to resolve host: {relayAddress}");
return;
}
// create socket
remoteEndPoint = new IPEndPoint(addresses[0], relayPort);
relaySocket = new Socket(remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
relaySocket.Blocking = false;
// configure buffer sizes
Common.ConfigureSocketBuffers(relaySocket, config.RecvBufferSize, config.SendBufferSize);
// bind to endpoint for Send/Receive instead of SendTo/ReceiveFrom
relaySocket.Connect(remoteEndPoint);
relayActive = true;
}
public override void Stop()
{
relayActive = false;
}
protected override bool RawReceiveFrom(out ArraySegment<byte> segment, out int connectionId)
{
segment = default;
connectionId = 0;
if (relaySocket == null) return false;
try
{
// TODO need separate buffer. don't write into result yet. only payload
if (relaySocket.ReceiveNonBlocking(relayReceiveBuffer, out ArraySegment<byte> content))
{
using (NetworkReaderPooled reader = NetworkReaderPool.Get(content))
{
// parse message type
if (reader.Remaining == 0)
{
Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse header.");
return false;
}
byte messageType = reader.ReadByte();
// handle message type
switch (messageType)
{
case (byte)MessageType.Ping:
{
// parse state
if (reader.Remaining < 1) return false;
ConnectionState last = state;
state = (ConnectionState)reader.ReadByte();
// log state changes for debugging.
if (state != last) Debug.Log($"EdgegapServer: state updated to: {state}");
// return true indicates Mirror to keep checking
// for further messages.
return true;
}
case (byte)MessageType.Data:
{
// parse connectionId and payload
if (reader.Remaining <= 4)
{
Debug.LogWarning($"EdgegapServer: message of {content.Count} is too small to parse connId.");
return false;
}
connectionId = reader.ReadInt();
segment = reader.ReadBytesSegment(reader.Remaining);
// Debug.Log($"EdgegapServer: receiving from connId={connectionId}: {segment.ToHexString()}");
return true;
}
// wrong message type. return false, don't throw.
default: return false;
}
}
}
}
catch (SocketException e)
{
Log.Info($"EdgegapServer: looks like the other end has closed the connection. This is fine: {e}");
}
return false;
}
protected override void RawSend(int connectionId, ArraySegment<byte> data)
{
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
// Debug.Log($"EdgegapServer: sending to connId={connectionId}: {data.ToHexString()}");
writer.WriteUInt(userId);
writer.WriteUInt(sessionId);
writer.WriteByte((byte)MessageType.Data);
writer.WriteInt(connectionId);
writer.WriteBytes(data.Array, data.Offset, data.Count);
ArraySegment<byte> message = writer;
try
{
relaySocket.SendNonBlocking(message);
}
catch (SocketException e)
{
Log.Error($"KcpRleayServer: RawSend failed: {e}");
}
}
}
void SendPing()
{
using (NetworkWriterPooled writer = NetworkWriterPool.Get())
{
writer.WriteUInt(userId);
writer.WriteUInt(sessionId);
writer.WriteByte((byte)MessageType.Ping);
ArraySegment<byte> message = writer;
try
{
relaySocket.SendNonBlocking(message);
}
catch (SocketException e)
{
Debug.LogWarning($"EdgegapServer: failed to ping. perhaps the relay isn't running? {e}");
}
}
}
public override void TickOutgoing()
{
if (relayActive)
{
// ping every interval for keepalive & handshake
if (NetworkTime.localTime >= lastPingTime + Protocol.PingInterval)
{
SendPing();
lastPingTime = NetworkTime.localTime;
}
}
// base processing
base.TickOutgoing();
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fd8551078397248b0848950352c208ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/EdgegapKcpServer.cs
uploadId: 736421

View File

@ -0,0 +1,162 @@
// edgegap relay transport.
// reuses KcpTransport with custom KcpServer/Client.
//#if MIRROR <- commented out because MIRROR isn't defined on first import yet
using System;
using System.Text.RegularExpressions;
using UnityEngine;
using Mirror;
using kcp2k;
namespace Edgegap
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/edgegap-transports/edgegap-relay")]
public class EdgegapKcpTransport : KcpTransport
{
[Header("Relay")]
public string relayAddress = "127.0.0.1";
public ushort relayGameServerPort = 8888;
public ushort relayGameClientPort = 9999;
// mtu for kcp transport. respects relay overhead.
public const int MaxPayload = Kcp.MTU_DEF - Protocol.Overhead;
[Header("Relay")]
public bool relayGUI = true;
public uint userId = 11111111;
public uint sessionId = 22222222;
// helper
internal static String ReParse(String cmd, String pattern, String defaultValue)
{
Match match = Regex.Match(cmd, pattern);
return match.Success ? match.Groups[1].Value : defaultValue;
}
protected override void Awake()
{
// logging
// Log.Info should use Debug.Log if enabled, or nothing otherwise
// (don't want to spam the console on headless servers)
if (debugLog)
Log.Info = Debug.Log;
else
Log.Info = _ => {};
Log.Warning = Debug.LogWarning;
Log.Error = Debug.LogError;
// create config from serialized settings.
// with MaxPayload as max size to respect relay overhead.
config = new KcpConfig(DualMode, RecvBufferSize, SendBufferSize, MaxPayload, NoDelay, Interval, FastResend, false, SendWindowSize, ReceiveWindowSize, Timeout, MaxRetransmit);
// client (NonAlloc version is not necessary anymore)
client = new EdgegapKcpClient(
() => OnClientConnected.Invoke(),
(message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)),
() => OnClientDisconnected?.Invoke(), // may be null in StopHost(): https://github.com/MirrorNetworking/Mirror/issues/3708
(error, reason) => OnClientError.Invoke(ToTransportError(error), reason),
config
);
// server
server = new EdgegapKcpServer(
(connectionId, endPoint) => OnServerConnectedWithAddress.Invoke(connectionId, endPoint.PrettyAddress()),
(connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
(connectionId, error, reason) => OnServerError.Invoke(connectionId, ToTransportError(error), reason),
config);
if (statisticsLog)
InvokeRepeating(nameof(OnLogStatistics), 1, 1);
Debug.Log("EdgegapTransport initialized!");
}
protected override void OnValidate()
{
// show max message sizes in inspector for convenience.
// 'config' isn't available in edit mode yet, so use MTU define.
ReliableMaxMessageSize = KcpPeer.ReliableMaxMessageSize(MaxPayload, ReceiveWindowSize);
UnreliableMaxMessageSize = KcpPeer.UnreliableMaxMessageSize(MaxPayload);
}
// client overwrites to use EdgegapClient instead of KcpClient
public override void ClientConnect(string address)
{
// connect to relay address:port instead of the expected server address
EdgegapKcpClient client = (EdgegapKcpClient)this.client;
client.userId = userId;
client.sessionId = sessionId;
client.connectionState = ConnectionState.Checking; // reset from last time
client.Connect(relayAddress, relayGameClientPort);
}
public override void ClientConnect(Uri uri)
{
if (uri.Scheme != Scheme)
throw new ArgumentException($"Invalid url {uri}, use {Scheme}://host:port instead", nameof(uri));
// connect to relay address:port instead of the expected server address
EdgegapKcpClient client = (EdgegapKcpClient)this.client;
client.Connect(relayAddress, relayGameClientPort, userId, sessionId);
}
// server overwrites to use EdgegapServer instead of KcpServer
public override void ServerStart()
{
// start the server
EdgegapKcpServer server = (EdgegapKcpServer)this.server;
server.Start(relayAddress, relayGameServerPort, userId, sessionId);
}
void OnGUIRelay()
{
// if (server.IsActive()) return;
GUILayout.BeginArea(new Rect(300, 30, 200, 100));
GUILayout.BeginHorizontal();
GUILayout.Label("SessionId:");
sessionId = Convert.ToUInt32(GUILayout.TextField(sessionId.ToString()));
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("UserId:");
userId = Convert.ToUInt32(GUILayout.TextField(userId.ToString()));
GUILayout.EndHorizontal();
if (NetworkServer.active)
{
EdgegapKcpServer server = (EdgegapKcpServer)this.server;
GUILayout.BeginHorizontal();
GUILayout.Label("State:");
GUILayout.Label(server.state.ToString());
GUILayout.EndHorizontal();
}
else if (NetworkClient.active)
{
EdgegapKcpClient client = (EdgegapKcpClient)this.client;
GUILayout.BeginHorizontal();
GUILayout.Label("State:");
GUILayout.Label(client.connectionState.ToString());
GUILayout.EndHorizontal();
}
GUILayout.EndArea();
}
// base OnGUI only shows in editor & development builds.
// here we always show it because we need the sessionid & userid buttons.
#pragma warning disable CS0109
new void OnGUI()
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
base.OnGUI();
#endif
if (relayGUI) OnGUIRelay();
}
public override string ToString() => "Edgegap Kcp Transport";
}
#pragma warning restore CS0109
}
//#endif MIRROR <- commented out because MIRROR isn't defined on first import yet

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c2d1e0e17f753449798fa27474d6b86b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/EdgegapKcpTransport.cs
uploadId: 736421

View File

@ -0,0 +1,29 @@
// relay protocol definitions
namespace Edgegap
{
public enum ConnectionState : byte
{
Disconnected = 0, // until the user calls connect()
Checking = 1, // recently connected, validation in progress
Valid = 2, // validation succeeded
Invalid = 3, // validation rejected by tower
SessionTimeout = 4, // session owner timed out
Error = 5, // other error
}
public enum MessageType : byte
{
Ping = 1,
Data = 2
}
public static class Protocol
{
// MTU: relay adds up to 13 bytes of metadata in the worst case.
public const int Overhead = 13;
// ping interval should be between 100 ms and 1 second.
// faster ping gives faster authentication, but higher bandwidth.
public const float PingInterval = 0.5f;
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: eac30312ba61470b849e368af3c3b0e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/Protocol.cs
uploadId: 736421

View File

@ -0,0 +1,20 @@
# Edgegap Relay for Mirror
Documentation: https://docs.edgegap.com/docs/distributed-relay-manager/
## Prerequisites
- Unity project set up with the Mirror networking library installed
- Supported Versions: [Mirror](https://assetstore.unity.com/packages/tools/network/mirror-129321) and [Mirror LTS](https://assetstore.unity.com/packages/tools/network/mirror-lts-102631)
- EdgegapTransport module downloaded and extracted
## Steps
1. Open your Unity project and navigate to the "Assets" folder.
2. Locate the "Mirror" folder within "Assets" and open it.
3. Within the "Mirror" folder, open the "Transports" folder.
4. Drag and drop the "Unity" folder from the extracted EdgegapTransport files into the "Transports" folder.
5. Open your NetworkManager script in the Unity Editor and navigate to the "Inspector" panel.
6. In the "Inspector" panel, locate the "Network Manager" component and click the "+" button next to the "Transport" property.
7. In the "Add Component" menu that appears, select "Edgegap Transport" to add it to the NetworkManager.
8. Drag the newly added "Edgegap Transport" component into the "Transport" property in the "Inspector" panel.
## Notes
- The EdgegapTransport module is only compatible with Mirror and Mirror LTS versions.

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8ade7c960d8fe4e94970ddd88ede3bca
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/README.md
uploadId: 736421

View File

@ -0,0 +1,25 @@
// parse session_id and user_id from command line args.
// mac: "open mirror.app --args session_id=123 user_id=456"
using System;
using UnityEngine;
namespace Edgegap
{
public class RelayCredentialsFromArgs : MonoBehaviour
{
void Awake()
{
String cmd = Environment.CommandLine;
// parse session_id via regex
String sessionId = EdgegapKcpTransport.ReParse(cmd, "session_id=(\\d+)", "111111");
String userID = EdgegapKcpTransport.ReParse(cmd, "user_id=(\\d+)", "222222");
Debug.Log($"Parsed sessionId: {sessionId} user_id: {userID}");
// configure transport
EdgegapKcpTransport transport = GetComponent<EdgegapKcpTransport>();
transport.sessionId = UInt32.Parse(sessionId);
transport.userId = UInt32.Parse(userID);
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e9ec7091b26c4d3882f4b42f10f9b8c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 3ea6ff15cda674a57b0c7c8b7dc1878c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/EdgegapRelay/RelayCredentialsFromArgs.cs
uploadId: 736421

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,130 @@
fileFormatVersion: 2
guid: 3ea6ff15cda674a57b0c7c8b7dc1878c
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 16
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Transports/Edgegap/edgegap.png
uploadId: 736421