Edgegap Plugin: update to latest version to remove more MIRROR CHANGEs; also supports moving the folder better (#3891)

* Edgegap Plugin: non-breaking updates first

* Edgegap Plugin: update to latest version to remove more MIRROR CHANGEs; also supports moving the folder better

* 2021+

---------

Co-authored-by: mischa <info@noobtuts.com>
Co-authored-by: mischa <16416509+vis2k@users.noreply.github.com>
This commit is contained in:
mischa 2024-08-22 09:09:58 +02:00 committed by GitHub
parent dd4dd78013
commit b1cbab8621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 11167 additions and 1172 deletions

View File

@ -1,3 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 14024ce6d2e64d5ba58ab20409ac648f guid: 14024ce6d2e64d5ba58ab20409ac648f
timeCreated: 1701785018 folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,16 @@
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
COPY Builds/EdgegapServer /root/build/
WORKDIR /root/
RUN chmod +x /root/build/ServerBuild
RUN apt-get update && \
apt-get install -y ca-certificates && \
apt-get clean && \
update-ca-certificates
ENTRYPOINT [ "/root/build/ServerBuild", "-batchmode", "-nographics"]

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 74f140c827788b64ebde10a2197d3c0b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -43,11 +43,11 @@ public static BuildReport BuildServer()
return BuildPipeline.BuildPlayer(options); return BuildPipeline.BuildPlayer(options);
} }
public static async Task<bool> DockerSetupAndInstallationCheck() public static async Task<bool> DockerSetupAndInstallationCheck(string path)
{ {
if (!File.Exists("Dockerfile")) if (!File.Exists(path))
{ {
File.WriteAllText("Dockerfile", dockerFileText); throw new Exception("Dockerfile not found, please notify plugin maintainer about this issue.");
} }
string output = null; string output = null;
@ -77,7 +77,7 @@ static async Task RunCommand_DockerVersion(Action<string> outputReciever = null,
} }
// MIRROR CHANGE // MIRROR CHANGE
public static async Task RunCommand_DockerBuild(string registry, string imageRepo, string tag, Action<string> onStatusUpdate) public static async Task RunCommand_DockerBuild(string dockerfilePath, string registry, string imageRepo, string tag, string projectPath, Action<string> onStatusUpdate)
{ {
string realErrorMessage = null; string realErrorMessage = null;
@ -89,11 +89,11 @@ public static async Task RunCommand_DockerBuild(string registry, string imageRep
string buildCommand = IsArmCPU() ? "buildx build --platform linux/amd64" : "build"; string buildCommand = IsArmCPU() ? "buildx build --platform linux/amd64" : "build";
#if UNITY_EDITOR_WIN #if UNITY_EDITOR_WIN
await RunCommand("docker.exe", $"{buildCommand} -t {registry}/{imageRepo}:{tag} .", onStatusUpdate, await RunCommand("docker.exe", $"{buildCommand} -f \"{dockerfilePath}\" -t \"{registry}/{imageRepo}:{tag}\" \"{projectPath}\"", onStatusUpdate,
#elif UNITY_EDITOR_OSX #elif UNITY_EDITOR_OSX
await RunCommand("/bin/bash", $"-c \"docker {buildCommand} -t {registry}/{imageRepo}:{tag} .\"", onStatusUpdate, await RunCommand("/bin/bash", $"-c \"docker {buildCommand} -f {dockerfilePath} -t {registry}/{imageRepo}:{tag} {projectPath}\"", onStatusUpdate,
#elif UNITY_EDITOR_LINUX #elif UNITY_EDITOR_LINUX
await RunCommand("/bin/bash", $"-c \"docker {buildCommand} -t {registry}/{imageRepo}:{tag} .\"", onStatusUpdate, await RunCommand("/bin/bash", $"-c \"docker {buildCommand} -f {dockerfilePath} -t {registry}/{imageRepo}:{tag} {projectPath}\"", onStatusUpdate,
#endif #endif
(msg) => (msg) =>
{ {
@ -220,25 +220,6 @@ public static void UpdateEdgegapAppTag(string tag)
// throw new NotImplementedException(); // throw new NotImplementedException();
} }
// -batchmode -nographics remains for Unity 2019/2020 support pre-dedicated server builds
static string dockerFileText = @"FROM ubuntu:bionic
ARG DEBIAN_FRONTEND=noninteractive
COPY Builds/EdgegapServer /root/build/
WORKDIR /root/
RUN chmod +x /root/build/ServerBuild
RUN apt-get update && \
apt-get install -y ca-certificates && \
apt-get clean && \
update-ca-certificates
ENTRYPOINT [ ""/root/build/ServerBuild"", ""-batchmode"", ""-nographics""]
";
/// <summary>Run a Docker cmd with streaming log response. TODO: Plugin to other Docker cmds</summary> /// <summary>Run a Docker cmd with streaming log response. TODO: Plugin to other Docker cmds</summary>
/// <returns>Throws if logs contain "ERROR"</returns> /// <returns>Throws if logs contain "ERROR"</returns>
/// ///

View File

@ -1,27 +0,0 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.UIElements;
using Edgegap;
[CustomEditor(typeof(EdgegapToolScript))]
public class EdgegapPluginScriptEditor : Editor
{
VisualElement _serverDataContainer;
private void OnEnable()
{
_serverDataContainer = EdgegapServerDataManager.GetServerDataVisualTree();
EdgegapServerDataManager.RegisterServerDataContainer(_serverDataContainer);
}
private void OnDisable()
{
EdgegapServerDataManager.DeregisterServerDataContainer(_serverDataContainer);
}
public override VisualElement CreateInspectorGUI()
{
return _serverDataContainer;
}
}
#endif

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c4c676ae6dcca0e458c6a8f06571f8fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,6 +4,7 @@
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
using System.IO;
namespace Edgegap namespace Edgegap
{ {
@ -88,10 +89,15 @@ public static class EdgegapServerDataManager
public static Status GetServerStatus() => _serverData; public static Status GetServerStatus() => _serverData;
#if UNITY_EDITOR
internal static string StylesheetPath =>
Path.GetDirectoryName(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets($"t:Script {nameof(EdgegapServerDataManager)}")[0]));
#endif
static EdgegapServerDataManager() static EdgegapServerDataManager()
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
_serverDataStylesheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Edgegap/Editor/EdgegapServerData.uss"); _serverDataStylesheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{StylesheetPath}{Path.DirectorySeparatorChar}EdgegapServerData.uss");
#endif #endif
} }
public static void RegisterServerDataContainer(VisualElement serverDataContainer) public static void RegisterServerDataContainer(VisualElement serverDataContainer)

View File

@ -1,12 +0,0 @@
using UnityEngine;
using Edgegap;
using IO.Swagger.Model;
/// <summary>
/// This script acts as an interface to display and use the necessary variables from the Edgegap tool.
/// The server info can be accessed from the tool window, as well as through the public script property.
/// </summary>
public class EdgegapToolScript : MonoBehaviour
{
public Status ServerStatus => EdgegapServerDataManager.GetServerStatus();
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5963202433da25448a22def99f5a598b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,988 +0,0 @@
// MIRROR CHANGE: disable this completely. otherwise InitUIElements can still throw NRE.
/*
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Net;
using System.Text;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using IO.Swagger.Model;
using UnityEditor.Build.Reporting;
using Application = UnityEngine.Application;
namespace Edgegap
{
public class EdgegapWindow : EditorWindow
{
// MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
// static readonly HttpClient _httpClient = new HttpClient();
// END MIRROR CHANGE
const string EditorDataSerializationName = "EdgegapSerializationData";
const int ServerStatusCronjobIntervalMs = 10000; // Interval at which the server status is updated
// MIRROR CHANGE
// get the path of this .cs file so we don't need to hardcode paths to
// the .uxml and .uss files:
// https://forum.unity.com/threads/too-many-hard-coded-paths-in-the-templates-and-documentation.728138/
// this way users can move this folder without breaking UIToolkit paths.
internal string StylesheetPath =>
Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this)));
// END MIRROR CHANGE
readonly System.Timers.Timer _updateServerStatusCronjob = new System.Timers.Timer(ServerStatusCronjobIntervalMs);
[SerializeField] string _userExternalIp;
[SerializeField] string _apiKey;
[SerializeField] ApiEnvironment _apiEnvironment;
[SerializeField] string _appName;
[SerializeField] string _appVersionName;
[SerializeField] string _deploymentRequestId;
[SerializeField] string _containerRegistry;
[SerializeField] string _containerImageRepo;
[SerializeField] string _containerImageTag;
[SerializeField] bool _autoIncrementTag = true;
VisualTreeAsset _visualTree;
bool _shouldUpdateServerStatus = false;
// Interactable elements
EnumField _apiEnvironmentSelect;
TextField _apiKeyInput;
TextField _appNameInput;
TextField _appVersionNameInput;
TextField _containerRegistryInput;
TextField _containerImageRepoInput;
TextField _containerImageTagInput;
Toggle _autoIncrementTagInput;
Button _connectionButton;
Button _serverActionButton;
Button _documentationBtn;
Button _buildAndPushServerBtn;
// Readonly elements
Label _connectionStatusLabel;
VisualElement _serverDataContainer;
// server data manager
StyleSheet _serverDataStylesheet;
List<VisualElement> _serverDataContainers = new List<VisualElement>();
[Obsolete("See EdgegapWindowV2.ShowEdgegapToolWindow()")]
// [MenuItem("Edgegap/Server Management")]
public static void ShowEdgegapToolWindow()
{
EdgegapWindow window = GetWindow<EdgegapWindow>();
window.titleContent = new GUIContent("Edgegap Hosting"); // MIRROR CHANGE
}
protected void OnEnable()
{
// Set root VisualElement and style
// BEGIN MIRROR CHANGE
_visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{StylesheetPath}/EdgegapWindow.uxml");
StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{StylesheetPath}/EdgegapWindow.uss");
_serverDataStylesheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{StylesheetPath}/EdgegapServerData.uss");
// END MIRROR CHANGE
rootVisualElement.styleSheets.Add(styleSheet);
LoadToolData();
if (string.IsNullOrWhiteSpace(_userExternalIp))
{
_userExternalIp = GetExternalIpAddress();
}
}
protected void Update()
{
if (_shouldUpdateServerStatus)
{
_shouldUpdateServerStatus = false;
UpdateServerStatus();
}
}
public void CreateGUI()
{
rootVisualElement.Clear();
_visualTree.CloneTree(rootVisualElement);
InitUIElements();
SyncFormWithObject();
bool hasActiveDeployment = !string.IsNullOrEmpty(_deploymentRequestId);
if (hasActiveDeployment)
{
RestoreActiveDeployment();
}
else
{
DisconnectCallback();
}
}
protected void OnDestroy()
{
bool deploymentActive = !string.IsNullOrEmpty(_deploymentRequestId);
if (deploymentActive)
{
EditorUtility.DisplayDialog(
"Warning",
$"You have an active deployment ({_deploymentRequestId}) that won't be stopped automatically.",
"Ok"
);
}
}
protected void OnDisable()
{
SyncObjectWithForm();
SaveToolData();
DeregisterServerDataContainer(_serverDataContainer);
}
/// <summary>
/// Binds the form inputs to the associated variables and initializes the inputs as required.
/// Requires the VisualElements to be loaded before this call. Otherwise, the elements cannot be found.
/// </summary>
void InitUIElements()
{
_apiEnvironmentSelect = rootVisualElement.Q<EnumField>("environmentSelect");
_apiKeyInput = rootVisualElement.Q<TextField>("apiKey");
_appNameInput = rootVisualElement.Q<TextField>("appName");
_appVersionNameInput = rootVisualElement.Q<TextField>("appVersionName");
_containerRegistryInput = rootVisualElement.Q<TextField>("containerRegistry");
_containerImageRepoInput = rootVisualElement.Q<TextField>("containerImageRepo");
_containerImageTagInput = rootVisualElement.Q<TextField>("tag");
_autoIncrementTagInput = rootVisualElement.Q<Toggle>("autoIncrementTag");
_connectionButton = rootVisualElement.Q<Button>("connectionBtn");
_serverActionButton = rootVisualElement.Q<Button>("serverActionBtn");
_documentationBtn = rootVisualElement.Q<Button>("documentationBtn");
_buildAndPushServerBtn = rootVisualElement.Q<Button>("buildAndPushBtn");
_buildAndPushServerBtn.clickable.clicked += BuildAndPushServer;
_connectionStatusLabel = rootVisualElement.Q<Label>("connectionStatusLabel");
_serverDataContainer = rootVisualElement.Q<VisualElement>("serverDataContainer");
// Load initial server data UI element and register for updates.
VisualElement serverDataElement = GetServerDataVisualTree();
RegisterServerDataContainer(serverDataElement);
_serverDataContainer.Clear();
_serverDataContainer.Add(serverDataElement);
_documentationBtn.clickable.clicked += OpenDocumentationCallback;
// Init the ApiEnvironment dropdown
_apiEnvironmentSelect.Init(ApiEnvironment.Console);
}
/// <summary>
/// With a call to an external resource, determines the current user's public IP address.
/// </summary>
/// <returns>External IP address</returns>
string GetExternalIpAddress()
{
string externalIpString = new WebClient()
.DownloadString("http://icanhazip.com")
.Replace("\\r\\n", "")
.Replace("\\n", "")
.Trim();
IPAddress externalIp = IPAddress.Parse(externalIpString);
return externalIp.ToString();
}
void OpenDocumentationCallback()
{
// MIRROR CHANGE
// ApiEnvironment selectedApiEnvironment = (ApiEnvironment)_apiEnvironmentSelect.value;
// string documentationUrl = selectedApiEnvironment.GetDocumentationUrl();
//
// if (!string.IsNullOrEmpty(documentationUrl))
// {
// UnityEngine.Application.OpenURL(documentationUrl);
// }
// else
// {
// string apiEnvName = Enum.GetName(typeof(ApiEnvironment), selectedApiEnvironment);
// Debug.LogWarning($"Could not open documentation for api environment {apiEnvName}: No documentation URL.");
// }
// link to the easiest documentation
Application.OpenURL("https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide");
// END MIRROR CHANGE
}
void ConnectCallback()
{
ApiEnvironment selectedApiEnvironment = (ApiEnvironment)_apiEnvironmentSelect.value;
string selectedAppName = _appNameInput.value;
string selectedVersionName = _appVersionNameInput.value;
string selectedApiKey = _apiKeyInput.value;
bool validAppName = !string.IsNullOrEmpty(selectedAppName) && !string.IsNullOrWhiteSpace(selectedAppName);
bool validVersionName = !string.IsNullOrEmpty(selectedVersionName) && !string.IsNullOrWhiteSpace(selectedVersionName);
bool validApiKey = selectedApiKey.StartsWith("token ");
if (validAppName && validVersionName && validApiKey)
{
string apiKeyValue = selectedApiKey.Substring(6);
Connect(selectedApiEnvironment, selectedAppName, selectedVersionName, apiKeyValue);
}
else
{
EditorUtility.DisplayDialog(
"Could not connect - Invalid data",
"The data provided is invalid. " +
"Make sure every field is filled, and that you provide your complete Edgegap API token " +
"(including the \"token\" part).",
"Ok"
);
}
}
// MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
HttpClient CreateHttpClient()
{
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(_apiEnvironment.GetApiUrl());
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string token = _apiKeyInput.value.Substring(6);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", token);
return httpClient;
}
// END MIRROR CHANGE
async void Connect(
ApiEnvironment selectedApiEnvironment,
string selectedAppName,
string selectedAppVersionName,
string selectedApiTokenValue
)
{
SetToolUIState(ToolState.Connecting);
// Make HTTP request
HttpClient _httpClient = CreateHttpClient(); // MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
string path = $"/v1/app/{selectedAppName}/version/{selectedAppVersionName}"; // MIRROR CHANGE: use selectedAppName and selectedAppVersionName instead of _appName & _appVersionName
HttpResponseMessage response = await _httpClient.GetAsync(path);
if (response.IsSuccessStatusCode)
{
SyncObjectWithForm();
SetToolUIState(ToolState.Connected);
}
else
{
int status = (int)response.StatusCode;
string title;
string message;
if (status == 401)
{
string apiEnvName = Enum.GetName(typeof(ApiEnvironment), selectedApiEnvironment);
title = "Invalid credentials";
message = $"Could not find an Edgegap account with this API key for the {apiEnvName} environment.";
}
else if (status == 404)
{
title = "App not found";
message = $"Could not find app {selectedAppName} with version {selectedAppVersionName}.";
}
else
{
title = "Oops";
message = $"There was an error while connecting you to the Edgegap API. Please try again later.";
}
EditorUtility.DisplayDialog(title, message, "Ok");
SetToolUIState(ToolState.Disconnected);
}
}
void DisconnectCallback()
{
if (string.IsNullOrEmpty(_deploymentRequestId))
{
SetToolUIState(ToolState.Disconnected);
}
else
{
EditorUtility.DisplayDialog("Cannot disconnect", "Make sure no server is running in the Edgegap tool before disconnecting", "Ok");
}
}
float ProgressCounter = 0;
// MIRROR CHANGE: added title parameter for more detailed progress while waiting
void ShowBuildWorkInProgress(string title, string status)
{
EditorUtility.DisplayProgressBar(title, status, ProgressCounter++ / 50);
}
// END MIRROR CHANGE
async void BuildAndPushServer()
{
SetToolUIState(ToolState.Building);
SyncObjectWithForm();
ProgressCounter = 0;
Action<string> onError = (msg) =>
{
EditorUtility.DisplayDialog("Error", msg, "Ok");
SetToolUIState(ToolState.Connected);
};
try
{
// check for installation and setup docker file
if (!await EdgegapBuildUtils.DockerSetupAndInstallationCheck())
{
onError("Docker installation not found. Docker can be downloaded from:\n\nhttps://www.docker.com/");
return;
}
// MIRROR CHANGE
// make sure Linux build target is installed before attemping to build.
// if it's not installed, tell the user about it.
if (!BuildPipeline.IsBuildTargetSupported(BuildTargetGroup.Standalone, BuildTarget.StandaloneLinux64))
{
onError($"Linux Build Support is missing.\n\nPlease open Unity Hub -> Installs -> Unity {Application.unityVersion} -> Add Modules -> Linux Build Support (IL2CPP & Mono & Dedicated Server) -> Install\n\nAfterwards restart Unity!");
return;
}
// END MIRROR CHANGE
// create server build
BuildReport buildResult = EdgegapBuildUtils.BuildServer();
if (buildResult.summary.result != BuildResult.Succeeded)
{
onError("Edgegap build failed, please check the Unity console logs.");
return;
}
string registry = _containerRegistry;
string imageName = _containerImageRepo;
string tag = _containerImageTag;
// MIRROR CHANGE ///////////////////////////////////////////////
// registry, repository and tag can not contain whitespaces.
// otherwise the docker command will throw an error:
// "ERROR: "docker buildx build" requires exactly 1 argument."
// catch this early and notify the user immediately.
if (registry.Contains(" "))
{
onError($"Container Registry is not allowed to contain whitespace: '{registry}'");
return;
}
if (imageName.Contains(" "))
{
onError($"Image Repository is not allowed to contain whitespace: '{imageName}'");
return;
}
if (tag.Contains(" "))
{
onError($"Tag is not allowed to contain whitespace: '{tag}'");
return;
}
// END MIRROR CHANGE ///////////////////////////////////////////
// increment tag for quicker iteration
if (_autoIncrementTag)
{
tag = EdgegapBuildUtils.IncrementTag(tag);
}
// create docker image
await EdgegapBuildUtils.RunCommand_DockerBuild(registry, imageName, tag, status => ShowBuildWorkInProgress("Building Docker Image", status));
SetToolUIState(ToolState.Pushing);
// push docker image
(bool result, string error) = await EdgegapBuildUtils.RunCommand_DockerPush(registry, imageName, tag, status => ShowBuildWorkInProgress("Uploading Docker Image (this may take a while)", status));
if (!result)
{
// catch common issues with detailed solutions
if (error.Contains("Cannot connect to the Docker daemon"))
{
onError($"{error}\nTo solve this, you can install and run Docker Desktop from:\n\nhttps://www.docker.com/products/docker-desktop");
return;
}
if (error.Contains("unauthorized to access repository"))
{
onError($"Docker authorization failed:\n\n{error}\nTo solve this, you can open a terminal and enter 'docker login {registry}', then enter your credentials.");
return;
}
// project not found?
if (Regex.IsMatch(error, @".*project .* not found.*", RegexOptions.IgnoreCase))
{
onError($"{error}\nTo solve this, make sure that Image Repository is 'project/game' where 'project' is from the Container Registry page on the Edgegap website.");
return;
}
// otherwise show generic error message
onError($"Unable to push docker image to registry. Please make sure you're logged in to {registry} and check the following error:\n\n{error}");
return;
}
// update edgegap server settings for new tag
ShowBuildWorkInProgress("Build and Push", "Updating server info on Edgegap");
await UpdateAppTagOnEdgegap(tag);
// cleanup
_containerImageTag = tag;
SyncFormWithObject();
SetToolUIState(ToolState.Connected);
Debug.Log("Server built and pushed successfully");
}
catch (Exception ex)
{
Debug.LogError(ex);
onError($"Edgegap build and push failed with Error: {ex}");
}
finally
{
// MIRROR CHANGE: always clear otherwise it gets stuck there forever!
EditorUtility.ClearProgressBar();
}
}
async Task UpdateAppTagOnEdgegap(string newTag)
{
string path = $"/v1/app/{_appName}/version/{_appVersionName}";
// Setup post data
AppVersionUpdatePatchData updatePatchData = new AppVersionUpdatePatchData { DockerImage = _containerImageRepo, DockerRegistry = _containerRegistry, DockerTag = newTag };
string json = JsonConvert.SerializeObject(updatePatchData);
StringContent patchData = new StringContent(json, Encoding.UTF8, "application/json");
// Make HTTP request
HttpClient _httpClient = CreateHttpClient(); // MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), path);
request.Content = patchData;
HttpResponseMessage response = await _httpClient.SendAsync(request);
string content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new Exception($"Could not update Edgegap server tag. Got {(int)response.StatusCode} with response:\n{content}");
}
}
async void StartServerCallback()
{
SetToolUIState(ToolState.ProcessingDeployment); // Prevents being called multiple times.
const string path = "/v1/deploy";
// Setup post data
DeployPostData deployPostData = new DeployPostData(_appName, _appVersionName, new List<string> { _userExternalIp });
string json = JsonConvert.SerializeObject(deployPostData);
StringContent postData = new StringContent(json, Encoding.UTF8, "application/json");
// Make HTTP request
HttpClient _httpClient = CreateHttpClient(); // MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
HttpResponseMessage response = await _httpClient.PostAsync(path, postData);
string content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
// Parse response
Deployment parsedResponse = JsonConvert.DeserializeObject<Deployment>(content);
_deploymentRequestId = parsedResponse.RequestId;
UpdateServerStatus();
StartServerStatusCronjob();
}
else
{
Debug.LogError($"Could not start Edgegap server. Got {(int)response.StatusCode} with response:\n{content}");
SetToolUIState(ToolState.Connected);
}
}
async void StopServerCallback()
{
string path = $"/v1/stop/{_deploymentRequestId}";
// Make HTTP request
HttpClient _httpClient = CreateHttpClient(); // MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
HttpResponseMessage response = await _httpClient.DeleteAsync(path);
if (response.IsSuccessStatusCode)
{
UpdateServerStatus();
SetToolUIState(ToolState.ProcessingDeployment);
}
else
{
// Parse response
string content = await response.Content.ReadAsStringAsync();
Debug.LogError($"Could not stop Edgegap server. Got {(int)response.StatusCode} with response:\n{content}");
}
}
void StartServerStatusCronjob()
{
_updateServerStatusCronjob.Elapsed += (sourceObject, elaspedEvent) => _shouldUpdateServerStatus = true;
_updateServerStatusCronjob.AutoReset = true;
_updateServerStatusCronjob.Start();
}
void StopServerStatusCronjob() => _updateServerStatusCronjob.Stop();
async void UpdateServerStatus()
{
Status serverStatusResponse = await FetchServerStatus();
ToolState toolState;
ServerStatus serverStatus = serverStatusResponse.GetServerStatus();
if (serverStatus == ServerStatus.Terminated)
{
SetServerData(null, _apiEnvironment);
if (_updateServerStatusCronjob.Enabled)
{
StopServerStatusCronjob();
}
_deploymentRequestId = null;
toolState = ToolState.Connected;
}
else
{
SetServerData(serverStatusResponse, _apiEnvironment);
if (serverStatus == ServerStatus.Ready || serverStatus == ServerStatus.Error)
{
toolState = ToolState.DeploymentRunning;
}
else
{
toolState = ToolState.ProcessingDeployment;
}
}
SetToolUIState(toolState);
}
async Task<Status> FetchServerStatus()
{
string path = $"/v1/status/{_deploymentRequestId}";
// Make HTTP request
HttpClient _httpClient = CreateHttpClient(); // MIRROR CHANGE: create HTTPClient in-place to avoid InvalidOperationExceptions when reusing
HttpResponseMessage response = await _httpClient.GetAsync(path);
// Parse response
string content = await response.Content.ReadAsStringAsync();
Status parsedData;
if (response.IsSuccessStatusCode)
{
parsedData = JsonConvert.DeserializeObject<Status>(content);
}
else
{
if ((int)response.StatusCode == 400)
{
Debug.LogError("The deployment that was active in the tool is now unreachable. Considering it Terminated.");
parsedData = new Status() { CurrentStatus = ServerStatus.Terminated.GetLabelText() };
}
else
{
Debug.LogError(
$"Could not fetch status of Edgegap deployment {_deploymentRequestId}. " +
$"Got {(int)response.StatusCode} with response:\n{content}"
);
parsedData = new Status() { CurrentStatus = ServerStatus.NA.GetLabelText() };
}
}
return parsedData;
}
void RestoreActiveDeployment()
{
ConnectCallback();
_shouldUpdateServerStatus = true;
StartServerStatusCronjob();
}
void SyncObjectWithForm()
{
if (_apiKeyInput == null) return; // MIRROR CHANGE: fix NRE when this is called before UI elements were assgned
_apiKey = _apiKeyInput.value;
_apiEnvironment = (ApiEnvironment)_apiEnvironmentSelect.value;
_appName = _appNameInput.value;
_appVersionName = _appVersionNameInput.value;
// MIRROR CHANGE ///////////////////////////////////////////////////
// registry, repository and tag can not contain whitespaces.
// otherwise it'll throw an error:
// "ERROR: "docker buildx build" requires exactly 1 argument."
// trim whitespace in case users accidentally added some.
_containerRegistry = _containerRegistryInput.value.Trim();
_containerImageTag = _containerImageTagInput.value.Trim();
_containerImageRepo = _containerImageRepoInput.value.Trim();
// END MIRROR CHANGE ///////////////////////////////////////////////
_autoIncrementTag = _autoIncrementTagInput.value;
}
void SyncFormWithObject()
{
_apiKeyInput.value = _apiKey;
_apiEnvironmentSelect.value = _apiEnvironment;
_appNameInput.value = _appName;
_appVersionNameInput.value = _appVersionName;
_containerRegistryInput.value = _containerRegistry;
_containerImageTagInput.value = _containerImageTag;
_containerImageRepoInput.value = _containerImageRepo;
_autoIncrementTagInput.value = _autoIncrementTag;
}
void SetToolUIState(ToolState toolState)
{
SetConnectionInfoUI(toolState);
SetConnectionButtonUI(toolState);
SetServerActionUI(toolState);
SetDockerRepoInfoUI(toolState);
}
void SetDockerRepoInfoUI(ToolState toolState)
{
bool connected = toolState.CanStartDeployment();
_containerRegistryInput.SetEnabled(connected);
_autoIncrementTagInput.SetEnabled(connected);
_containerImageRepoInput.SetEnabled(connected);
_containerImageTagInput.SetEnabled(connected);
}
void SetConnectionInfoUI(ToolState toolState)
{
bool canEditConnectionInfo = toolState.CanEditConnectionInfo();
_apiKeyInput.SetEnabled(canEditConnectionInfo);
_apiEnvironmentSelect.SetEnabled(canEditConnectionInfo);
_appNameInput.SetEnabled(canEditConnectionInfo);
_appVersionNameInput.SetEnabled(canEditConnectionInfo);
}
void SetConnectionButtonUI(ToolState toolState)
{
bool canConnect = toolState.CanConnect();
bool canDisconnect = toolState.CanDisconnect();
_connectionButton.SetEnabled(canConnect || canDisconnect);
// A bit dirty, but ensures the callback is not bound multiple times on the button.
_connectionButton.clickable.clicked -= ConnectCallback;
_connectionButton.clickable.clicked -= DisconnectCallback;
if (canConnect || toolState == ToolState.Connecting)
{
_connectionButton.text = "Connect";
_connectionStatusLabel.text = "Awaiting connection";
_connectionStatusLabel.RemoveFromClassList("text--success");
_connectionButton.clickable.clicked += ConnectCallback;
}
else
{
_connectionButton.text = "Disconnect";
_connectionStatusLabel.text = "Connected";
_connectionStatusLabel.AddToClassList("text--success");
_connectionButton.clickable.clicked += DisconnectCallback;
}
}
void SetServerActionUI(ToolState toolState)
{
bool canStartDeployment = toolState.CanStartDeployment();
bool canStopDeployment = toolState.CanStopDeployment();
// A bit dirty, but ensures the callback is not bound multiple times on the button.
_serverActionButton.clickable.clicked -= StartServerCallback;
_serverActionButton.clickable.clicked -= StopServerCallback;
_serverActionButton.SetEnabled(canStartDeployment || canStopDeployment);
_buildAndPushServerBtn.SetEnabled(canStartDeployment);
if (canStopDeployment)
{
_serverActionButton.text = "Stop Server";
_serverActionButton.clickable.clicked += StopServerCallback;
}
else
{
_serverActionButton.text = "Start Server";
_serverActionButton.clickable.clicked += StartServerCallback;
}
}
// server data manager /////////////////////////////////////////////////
public void RegisterServerDataContainer(VisualElement serverDataContainer)
{
_serverDataContainers.Add(serverDataContainer);
}
public void DeregisterServerDataContainer(VisualElement serverDataContainer)
{
_serverDataContainers.Remove(serverDataContainer);
}
public void SetServerData(Status serverData, ApiEnvironment apiEnvironment)
{
EdgegapServerDataManager._serverData = serverData;
RefreshServerDataContainers();
}
public Label GetHeader(string text)
{
Label header = new Label(text);
header.AddToClassList("label__header");
return header;
}
public VisualElement GetHeaderRow()
{
VisualElement row = new VisualElement();
row.AddToClassList("row__port-table");
row.AddToClassList("label__header");
row.Add(new Label("Name"));
row.Add(new Label("External"));
row.Add(new Label("Internal"));
row.Add(new Label("Protocol"));
row.Add(new Label("Link"));
return row;
}
public VisualElement GetRowFromPortResponse(PortMapping port)
{
VisualElement row = new VisualElement();
row.AddToClassList("row__port-table");
row.AddToClassList("focusable");
row.Add(new Label(port.Name));
row.Add(new Label(port.External.ToString()));
row.Add(new Label(port.Internal.ToString()));
row.Add(new Label(port.Protocol));
row.Add(GetCopyButton("Copy", port.Link));
return row;
}
public Button GetCopyButton(string btnText, string copiedText)
{
Button copyBtn = new Button();
copyBtn.text = btnText;
copyBtn.clickable.clicked += () => GUIUtility.systemCopyBuffer = copiedText;
return copyBtn;
}
public Button GetLinkButton(string btnText, string targetUrl)
{
Button copyBtn = new Button();
copyBtn.text = btnText;
copyBtn.clickable.clicked += () => UnityEngine.Application.OpenURL(targetUrl);
return copyBtn;
}
public Label GetInfoText(string innerText)
{
Label infoText = new Label(innerText);
infoText.AddToClassList("label__info-text");
return infoText;
}
VisualElement GetStatusSection()
{
ServerStatus serverStatus = EdgegapServerDataManager._serverData.GetServerStatus();
string dashboardUrl = _apiEnvironment.GetDashboardUrl();
string requestId = EdgegapServerDataManager._serverData.RequestId;
string deploymentDashboardUrl = "";
if (!string.IsNullOrEmpty(requestId) && !string.IsNullOrEmpty(dashboardUrl))
{
deploymentDashboardUrl = $"{dashboardUrl}/arbitrium/deployment/read/{requestId}/";
}
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(GetHeader("Server Status"));
VisualElement row = new VisualElement();
row.AddToClassList("row__status");
// Status pill
Label statusLabel = new Label(serverStatus.GetLabelText());
statusLabel.AddToClassList(serverStatus.GetStatusBgClass());
statusLabel.AddToClassList("label__status");
row.Add(statusLabel);
// Link to dashboard
if (!string.IsNullOrEmpty(deploymentDashboardUrl))
{
row.Add(GetLinkButton("See in the dashboard", deploymentDashboardUrl));
}
else
{
row.Add(new Label("Could not resolve link to this deployment"));
}
container.Add(row);
return container;
}
VisualElement GetDnsSection()
{
string serverDns = EdgegapServerDataManager._serverData.Fqdn;
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(GetHeader("Server DNS"));
VisualElement row = new VisualElement();
row.AddToClassList("row__dns");
row.AddToClassList("focusable");
row.Add(new Label(serverDns));
row.Add(GetCopyButton("Copy", serverDns));
container.Add(row);
return container;
}
VisualElement GetPortsSection()
{
List<PortMapping> serverPorts = EdgegapServerDataManager._serverData.Ports.Values.ToList();
VisualElement container = new VisualElement();
container.AddToClassList("container");
container.Add(GetHeader("Server Ports"));
container.Add(GetHeaderRow());
VisualElement portList = new VisualElement();
if (serverPorts.Count > 0)
{
foreach (PortMapping port in serverPorts)
{
portList.Add(GetRowFromPortResponse(port));
}
}
else
{
portList.Add(new Label("No port configured for this app version."));
}
container.Add(portList);
return container;
}
public VisualElement GetServerDataVisualTree()
{
VisualElement serverDataTree = new VisualElement();
serverDataTree.styleSheets.Add(_serverDataStylesheet);
bool hasServerData = EdgegapServerDataManager._serverData != null;
bool isReady = hasServerData && EdgegapServerDataManager. _serverData.GetServerStatus().IsOneOf(ServerStatus.Ready, ServerStatus.Error);
if (hasServerData)
{
serverDataTree.Add(GetStatusSection());
if (isReady)
{
serverDataTree.Add(GetDnsSection());
serverDataTree.Add(GetPortsSection());
}
else
{
serverDataTree.Add(GetInfoText("Additional information will be displayed when the server is ready."));
}
}
else
{
serverDataTree.Add(GetInfoText("Server data will be displayed here when a server is running."));
}
return serverDataTree;
}
void RefreshServerDataContainers()
{
foreach (VisualElement serverDataContainer in _serverDataContainers)
{
serverDataContainer.Clear();
serverDataContainer.Add(GetServerDataVisualTree()); // Cannot reuse a same instance of VisualElement
}
}
// save & load /////////////////////////////////////////////////////////
/// <summary>
/// Save the tool's serializable data to the EditorPrefs to allow persistence across restarts.
/// Any field with [SerializeField] will be saved.
/// </summary>
void SaveToolData()
{
string data = JsonUtility.ToJson(this, false);
EditorPrefs.SetString(EditorDataSerializationName, data);
}
/// <summary>
/// Load the tool's serializable data from the EditorPrefs to the object, restoring the tool's state.
/// </summary>
void LoadToolData()
{
string data = EditorPrefs.GetString(EditorDataSerializationName, JsonUtility.ToJson(this, false));
JsonUtility.FromJsonOverwrite(data, this);
}
}
}
#endif
*/

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 28bfb13cf2845d04eb5cbee51f9353d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -15,9 +15,7 @@
width: 400px; width: 400px;
top: -10px; top: -10px;
right: -150px; right: -150px;
/* MIRROR CHANGE: disable hardcoded image path. this is inserted from the script now. background-image: url('./Images/logo_transparent_400_alpha25.png?fileID=21300000&guid=b7012da4ebf9008458abc3ef9a741f3c&type=3#logo_transparent_400_alpha25');
background-image: url('project://database/Assets/Edgegap/Editor/Images/logo_transparent_400_alpha25.png?fileID=21300000&guid=b7012da4ebf9008458abc3ef9a741f3c&type=3#logo_transparent_400_alpha25');
*/
-unity-background-scale-mode: scale-and-crop; -unity-background-scale-mode: scale-and-crop;
} }
@ -28,7 +26,7 @@
padding-bottom: 8px; padding-bottom: 8px;
-unity-text-align: middle-center; -unity-text-align: middle-center;
/* MIRROR CHANGE: disable hardcoded font path /* MIRROR CHANGE: disable hardcoded font path
-unity-font: url('project://database/Assets/Edgegap/Editor/Fonts/Src/BaronNeue.otf?fileID=12800000&guid=fb67205c672fbb04d829783b9f771fc9&type=3#BaronNeue'); -unity-font: url('./Fonts/Src/BaronNeue.otf?fileID=12800000&guid=fb67205c672fbb04d829783b9f771fc9&type=3#BaronNeue');
*/ */
} }
@ -122,7 +120,7 @@
font-size: 11px; font-size: 11px;
color: rgb(222, 222, 222); color: rgb(222, 222, 222);
/* MIRROR CHANGE: disable hardcoded font path /* MIRROR CHANGE: disable hardcoded font path
-unity-font-definition: url('project://database/Assets/Edgegap/Editor/Fonts/Spartan-Regular%20SDF.asset?fileID=11400000&guid=8b0fb2c68be09174f8ea5057b27a545c&type=2#Spartan-Regular SDF'); -unity-font-definition: url('./Fonts/Spartan-Regular%20SDF.asset?fileID=11400000&guid=8b0fb2c68be09174f8ea5057b27a545c&type=2#Spartan-Regular SDF');
*/ */
} }
@ -184,7 +182,7 @@ Toggle > #unity-checkmark {
.unity-text-field__input > TextElement { .unity-text-field__input > TextElement {
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
/* MIRROR CHANGE: disable hardcoded font path /* MIRROR CHANGE: disable hardcoded font path
-unity-font-definition: url('project://database/Assets/Edgegap/Editor/Fonts/UbuntuMono-R%20SDF.asset?fileID=11400000&guid=2635d61c9807d6c46bcb00a3d8645b37&type=2#UbuntuMono-R SDF'); -unity-font-definition: url('./Fonts/UbuntuMono-R%20SDF.asset?fileID=11400000&guid=2635d61c9807d6c46bcb00a3d8645b37&type=2#UbuntuMono-R SDF');
*/ */
font-size: 12px; font-size: 12px;
white-space: nowrap; white-space: nowrap;

View File

@ -2,8 +2,8 @@
<Style src="project://database/Assets/Edgegap/Editor/EdgegapWindow.uss?fileID=7433441132597879392&amp;guid=b1a2e4572c5de8840ac8d98377d409ae&amp;type=3#EdgegapWindow" /> <Style src="project://database/Assets/Edgegap/Editor/EdgegapWindow.uss?fileID=7433441132597879392&amp;guid=b1a2e4572c5de8840ac8d98377d409ae&amp;type=3#EdgegapWindow" />
<ui:VisualElement class="content" style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;"> <ui:VisualElement class="content" style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;">
<ui:VisualElement name="HeaderHorizGroup" style="flex-grow: 1; flex-direction: row; height: 100px; background-color: rgb(37, 37, 37); left: 0;"> <ui:VisualElement name="HeaderHorizGroup" style="flex-grow: 1; flex-direction: row; height: 100px; background-color: rgb(37, 37, 37); left: 0;">
<ui:VisualElement name="header-logo-img" style="height: 67px; width: 100px; flex-direction: row; -unity-background-scale-mode: scale-to-fit; -unity-slice-left: 0; -unity-slice-top: 0; -unity-slice-right: 0; -unity-slice-bottom: 0; -unity-background-image-tint-color: rgb(255, 255, 255); -unity-slice-scale: 1px; align-self: center;" /> <ui:VisualElement name="header-logo-img" style="height: 67px; width: 100px; flex-direction: row; background-image: url(&apos;./Images/logo_transparent_400_alpha25.png?fileID=2800000&amp;guid=b7012da4ebf9008458abc3ef9a741f3c&amp;type=3#logo_transparent_400_alpha25&apos;); -unity-background-scale-mode: scale-to-fit; -unity-slice-left: 0; -unity-slice-top: 0; -unity-slice-right: 0; -unity-slice-bottom: 0; -unity-background-image-tint-color: rgb(255, 255, 255); -unity-slice-scale: 1px; align-self: center;" />
<ui:Label text="EDGEGAP" name="header-logo-txt" class="text__title" style="flex-direction: row; flex-grow: 1; color: rgb(255, 255, 255); font-size: 30px; -unity-text-align: middle-left; -unity-font-style: normal; -unity-font: initial; margin-left: 0; align-items: center; height: 74px;"> <ui:Label text="EDGEGAP" name="header-logo-txt" class="text__title" style="flex-direction: row; flex-grow: 1; color: rgb(255, 255, 255); font-size: 30px; -unity-text-align: middle-left; -unity-font-style: normal; -unity-font: initial; -unity-font-definition: url(&apos;./Fonts/Spartan-SemiBold%20SDF.asset?fileID=11400000&amp;guid=7b18949555c60224384ab80e57e1fd68&amp;type=2#Spartan-SemiBold SDF&apos;); margin-left: 0; align-items: center; height: 74px;">
<ui:Button text="DEBUG" parse-escape-sequences="true" display-tooltip-when-elided="true" name="DebugBtn" tooltip="Hide me @ EdgegapWindowMetadata.SHOW_DEBUG_BTN" style="-unity-text-align: middle-left; white-space: normal; text-overflow: clip; justify-content: flex-start; align-self: flex-end; position: absolute; right: 0; top: 0; padding-top: 3px;" /> <ui:Button text="DEBUG" parse-escape-sequences="true" display-tooltip-when-elided="true" name="DebugBtn" tooltip="Hide me @ EdgegapWindowMetadata.SHOW_DEBUG_BTN" style="-unity-text-align: middle-left; white-space: normal; text-overflow: clip; justify-content: flex-start; align-self: flex-end; position: absolute; right: 0; top: 0; padding-top: 3px;" />
</ui:Label> </ui:Label>
</ui:VisualElement> </ui:VisualElement>
@ -83,7 +83,7 @@
<ui:Label name="DeploymentsConnectionStatusLabel" text="Unknown" class="text--muted" style="flex-grow: 1; flex-direction: column; -unity-text-align: middle-left; justify-content: flex-start; align-items: center; width: 86px; margin-right: 15px;" /> <ui:Label name="DeploymentsConnectionStatusLabel" text="Unknown" class="text--muted" style="flex-grow: 1; flex-direction: column; -unity-text-align: middle-left; justify-content: flex-start; align-items: center; width: 86px; margin-right: 15px;" />
<ui:VisualElement name="DeploymentConnectionUrlHorizGroup" style="flex-grow: 1; flex-direction: row; width: 304px; margin-left: 25px; margin-right: 25px; padding-right: 25px; padding-left: 25px;"> <ui:VisualElement name="DeploymentConnectionUrlHorizGroup" style="flex-grow: 1; flex-direction: row; width: 304px; margin-left: 25px; margin-right: 25px; padding-right: 25px; padding-left: 25px;">
<ui:TextField picking-mode="Ignore" name="DeploymentConnectionUrlReadOnlyTxt" readonly="true" tooltip="Selectable" style="padding-left: 0; -unity-text-align: middle-center; width: 297px; align-items: center;"> <ui:TextField picking-mode="Ignore" name="DeploymentConnectionUrlReadOnlyTxt" readonly="true" tooltip="Selectable" style="padding-left: 0; -unity-text-align: middle-center; width: 297px; align-items: center;">
<ui:Button name="DeploymentConnectionCopyUrlBtn" tooltip="Copy" class="text-edgegap bg-purple" style="min-width: 15px; visibility: visible; display: flex; overflow: hidden; width: 35px; -unity-background-scale-mode: scale-to-fit; min-height: 25px; background-color: rgb(44, 30, 210); -unity-background-image-tint-color: rgb(224, 224, 224); -unity-slice-left: 1; -unity-slice-top: 1; -unity-slice-right: 1; -unity-slice-bottom: 1; translate: -40px 0;" /> <ui:Button name="DeploymentConnectionCopyUrlBtn" tooltip="Copy" class="text-edgegap bg-purple" style="min-width: 15px; visibility: visible; display: flex; overflow: hidden; background-image: url(&apos;./Images/clipboard-128.png?fileID=2800000&amp;guid=caa516cdb721dd143bbc8000ca78d50a&amp;type=3#clipboard-128&apos;); width: 35px; -unity-background-scale-mode: scale-to-fit; min-height: 25px; background-color: rgb(44, 30, 210); -unity-background-image-tint-color: rgb(224, 224, 224); -unity-slice-left: 1; -unity-slice-top: 1; -unity-slice-right: 1; -unity-slice-bottom: 1; translate: -40px 0;" />
</ui:TextField> </ui:TextField>
</ui:VisualElement> </ui:VisualElement>
<ui:VisualElement name="DeploymentsConnectionServerStopHorizBtnHorizGroup" style="flex-grow: 1; flex-direction: column; align-items: center; justify-content: center; align-self: auto;"> <ui:VisualElement name="DeploymentsConnectionServerStopHorizBtnHorizGroup" style="flex-grow: 1; flex-direction: column; align-items: center; justify-content: center; align-self: auto;">

View File

@ -63,7 +63,7 @@ public enum LogLevel
public const string EDGEGAP_GET_A_TOKEN_URL = "https://app.edgegap.com/?oneClick=true"; public const string EDGEGAP_GET_A_TOKEN_URL = "https://app.edgegap.com/?oneClick=true";
public const string EDGEGAP_ADD_MORE_GAME_SERVERS_URL = "https://edgegap.com/resources/contact"; public const string EDGEGAP_ADD_MORE_GAME_SERVERS_URL = "https://edgegap.com/resources/contact";
public const string EDGEGAP_DOC_BTN_HOW_TO_LOGIN_VIA_CLI_URL = "https://docs.edgegap.com/docs/container/edgegap-container-registry/#getting-your-credentials"; public const string EDGEGAP_DOC_BTN_HOW_TO_LOGIN_VIA_CLI_URL = "https://docs.edgegap.com/docs/container/edgegap-container-registry/#getting-your-credentials";
private const string DEFAULT_UTM_SOURCE_TAG = "plugin_unity"; private const string DEFAULT_UTM_SOURCE_TAG = "partner_mirror_assetstore_unity";
private const string DEFAULT_UTM_MEDIUM_TAG = "servers_quickstart_plugin"; private const string DEFAULT_UTM_MEDIUM_TAG = "servers_quickstart_plugin";
private const string DEFAULT_UTM_CONTENT_TAG = "plugin_button"; private const string DEFAULT_UTM_CONTENT_TAG = "plugin_button";
public const string DEFAULT_UTM_TAGS = "utm_source=" + DEFAULT_UTM_SOURCE_TAG + public const string DEFAULT_UTM_TAGS = "utm_source=" + DEFAULT_UTM_SOURCE_TAG +
@ -73,6 +73,7 @@ public enum LogLevel
public const string LOADING_RICH_STR = "<i>Loading...</i>"; public const string LOADING_RICH_STR = "<i>Loading...</i>";
public const string PROCESSING_RICH_STR = "<i>Processing...</i>"; public const string PROCESSING_RICH_STR = "<i>Processing...</i>";
public const string DEPLOY_REQUEST_RICH_STR = "<i>Requesting Deploy...</i>"; public const string DEPLOY_REQUEST_RICH_STR = "<i>Requesting Deploy...</i>";
public const string KEY_COMPILER_MACRO = "EDGEGAP_PLUGIN_SERVERS";
#region Colors #region Colors
/// <summary>Earthy lime green</summary> /// <summary>Earthy lime green</summary>

View File

@ -1,4 +1,6 @@
#if UNITY_2021_3_OR_NEWER // MIRROR CHANGE
#if UNITY_EDITOR #if UNITY_EDITOR
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -12,6 +14,7 @@
using Edgegap.Editor.Api.Models.Requests; using Edgegap.Editor.Api.Models.Requests;
using Edgegap.Editor.Api.Models.Results; using Edgegap.Editor.Api.Models.Results;
using UnityEditor; using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting; using UnityEditor.Build.Reporting;
using UnityEditor.UIElements; using UnityEditor.UIElements;
using UnityEngine; using UnityEngine;
@ -98,7 +101,7 @@ public class EdgegapWindowV2 : EditorWindow
private Button _footerDocumentationBtn; private Button _footerDocumentationBtn;
private Button _footerNeedMoreGameServersBtn; private Button _footerNeedMoreGameServersBtn;
#endregion // Vars #endregion // Vars\
// MIRROR CHANGE // MIRROR CHANGE
// get the path of this .cs file so we don't need to hardcode paths to // get the path of this .cs file so we don't need to hardcode paths to
@ -108,13 +111,10 @@ public class EdgegapWindowV2 : EditorWindow
internal string StylesheetPath => internal string StylesheetPath =>
Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this))); Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this)));
// END MIRROR CHANGE // END MIRROR CHANGE
internal string ProjectRootPath => Directory.GetCurrentDirectory();
internal string DockerFilePath => $"{Directory.GetParent(Directory.GetFiles(ProjectRootPath, GetType().Name + ".cs", SearchOption.AllDirectories)[0]).FullName}{Path.DirectorySeparatorChar}Dockerfile";
// MIRROR CHANGE: images are dragged into the script in inspector and assigned to the UI at runtime. this way we don't need to hardcode it. [MenuItem("Tools/Edgegap Hosting")] // MIRROR CHANGE: more obvious title
public Texture2D LogoImage;
public Texture2D ClipboardImage;
// END MIRROR CHANGE
[MenuItem("Edgegap/Edgegap Hosting")] // MIRROR CHANGE: more obvious title
public static void ShowEdgegapToolWindow() public static void ShowEdgegapToolWindow()
{ {
EdgegapWindowV2 window = GetWindow<EdgegapWindowV2>(); EdgegapWindowV2 window = GetWindow<EdgegapWindowV2>();
@ -123,25 +123,52 @@ public static void ShowEdgegapToolWindow()
window.minSize = window.maxSize; window.minSize = window.maxSize;
} }
#region Unity Funcs #region Unity Funcs
[InitializeOnLoadMethod]
public static void AddDefineSymbols()
{
// check if defined first, otherwise adding the symbol causes an infinite loop of recompilation
#if !EDGEGAP_PLUGIN_SERVERS
// Get data about current target group
bool standaloneAndServer = false;
BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget;
BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
if (buildTargetGroup == BuildTargetGroup.Standalone)
{
StandaloneBuildSubtarget standaloneSubTarget = EditorUserBuildSettings.standaloneBuildSubtarget;
if (standaloneSubTarget == StandaloneBuildSubtarget.Server)
standaloneAndServer = true;
}
// Prepare named target, depending on above stuff
NamedBuildTarget namedBuildTarget;
if (standaloneAndServer)
namedBuildTarget = NamedBuildTarget.Server;
else
namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup);
// Set universal compiler macro
PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, $"{PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget)};{EdgegapWindowMetadata.KEY_COMPILER_MACRO}");
#endif
}
protected void OnEnable() protected void OnEnable()
{ {
#if UNITY_2021_3_OR_NEWER // MIRROR CHANGE: only load stylesheet in supported Unity versions, otherwise it shows errors in U2020 #if UNITY_2021_3_OR_NEWER // only load stylesheet in supported Unity versions, otherwise it shows errors in U2020
// Set root VisualElement and style: V2 still uses EdgegapWindow.[uxml|uss] // Set root VisualElement and style: V2 still uses EdgegapWindow.[uxml|uss]
// BEGIN MIRROR CHANGE // BEGIN MIRROR CHANGE
_visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{StylesheetPath}/EdgegapWindow.uxml"); _visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{StylesheetPath}{Path.DirectorySeparatorChar}EdgegapWindow.uxml");
StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{StylesheetPath}/EdgegapWindow.uss"); StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{StylesheetPath}{Path.DirectorySeparatorChar}EdgegapWindow.uss");
// END MIRROR CHANGE // END MIRROR CHANGE
rootVisualElement.styleSheets.Add(styleSheet); rootVisualElement.styleSheets.Add(styleSheet);
#endif #endif
} }
#pragma warning disable CS1998 // MIRROR CHANGE: disable async warning in U2020 #pragma warning disable CS1998 // disable async warning in U2020
public async void CreateGUI() public async void CreateGUI()
#pragma warning restore CS1998 // END MIRROR CHANGE #pragma warning restore CS1998
{ {
// MIRROR CHANGE: the UI requires 'GroupBox', which is not available in Unity 2019/2020. // the UI requires 'GroupBox', which is not available in Unity 2019/2020.
// showing it will break all of Unity's Editor UIs, not just this one. // showing it will break all of Unity's Editor UIs, not just this one.
// instead, show a warning that the Edgegap plugin only works on Unity 2021+ // instead, show a warning that the Edgegap plugin only works on Unity 2021+
#if !UNITY_2021_3_OR_NEWER #if !UNITY_2021_3_OR_NEWER
@ -163,10 +190,9 @@ public async void CreateGUI()
/// <summary>The user closed the window. Save the data.</summary> /// <summary>The user closed the window. Save the data.</summary>
protected void OnDisable() protected void OnDisable()
{ {
#if UNITY_2021_3_OR_NEWER // MIRROR CHANGE: only load stylesheet in supported Unity versions, otherwise it shows errors in U2020 #if UNITY_2021_3_OR_NEWER // only load stylesheet in supported Unity versions, otherwise it shows errors in U2020
// MIRROR CHANGE: sometimes this is called without having been registered, throwing NRE // sometimes this is called without having been registered, throwing NRE
if (_debugBtn == null) return; if (_debugBtn == null) return;
// END MIRROR CHANGE
unregisterClickEvents(); unregisterClickEvents();
unregisterFieldCallbacks(); unregisterFieldCallbacks();
@ -189,7 +215,6 @@ private void InitUIElements()
registerClickCallbacks(); registerClickCallbacks();
registerFieldCallbacks(); registerFieldCallbacks();
initToggleDynamicUi(); initToggleDynamicUi();
AssignImages(); // MIRROR CHANGE
} }
private void closeDisableGroups() private void closeDisableGroups()
@ -203,20 +228,6 @@ private void closeDisableGroups()
_deploymentsFoldout.SetEnabled(false); _deploymentsFoldout.SetEnabled(false);
} }
// MIRROR CHANGE: assign images to the UI at runtime instead of hardcoding it
void AssignImages()
{
// header logo
VisualElement logoElement = rootVisualElement.Q<VisualElement>("header-logo-img");
logoElement.style.backgroundImage = LogoImage;
// clipboard button
VisualElement copyElement = rootVisualElement.Q<VisualElement>("DeploymentConnectionCopyUrlBtn");
copyElement.style.backgroundImage = ClipboardImage;
}
// END MIRROR CHANGE
/// <summary>Set fields referencing UI Builder's fields. In order of appearance from top-to-bottom.</summary> /// <summary>Set fields referencing UI Builder's fields. In order of appearance from top-to-bottom.</summary>
private void setVisualElementsToFields() private void setVisualElementsToFields()
{ {
@ -633,7 +644,6 @@ private async void onContainerBuildAndPushServerBtnClickAsync()
#endregion // Init -> /Button Clicks #endregion // Init -> /Button Clicks
#endregion // Init #endregion // Init
/// <summary>Throw if !appName val</summary> /// <summary>Throw if !appName val</summary>
private void assertAppNameExists() => private void assertAppNameExists() =>
Assert.IsTrue(!string.IsNullOrEmpty(_appNameInput.value), Assert.IsTrue(!string.IsNullOrEmpty(_appNameInput.value),
@ -1147,8 +1157,6 @@ private void openDocumentationWebsite()
string documentationUrl = _apiEnvironment.GetDocumentationUrl() + "?" + string documentationUrl = _apiEnvironment.GetDocumentationUrl() + "?" +
EdgegapWindowMetadata.DEFAULT_UTM_TAGS; EdgegapWindowMetadata.DEFAULT_UTM_TAGS;
// BEGIN MIRROR CHANGE
/*
if (!string.IsNullOrEmpty(documentationUrl)) if (!string.IsNullOrEmpty(documentationUrl))
Application.OpenURL(documentationUrl); Application.OpenURL(documentationUrl);
else else
@ -1157,11 +1165,6 @@ private void openDocumentationWebsite()
Debug.LogWarning($"Could not open documentation for api environment " + Debug.LogWarning($"Could not open documentation for api environment " +
$"{apiEnvName}: No documentation URL."); $"{apiEnvName}: No documentation URL.");
} }
*/
// link to our step by step guide
Application.OpenURL("https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide");
// END MIRROR CHANGE
} }
/// <summary> /// <summary>
@ -1353,10 +1356,7 @@ private void onCreateDeploymentStartServerFail(EdgegapHttpResult<CreateDeploymen
Debug.Log("(!) Check your deployments here: https://app.edgegap.com/deployment-management/deployments/list"); Debug.Log("(!) Check your deployments here: https://app.edgegap.com/deployment-management/deployments/list");
// Shake "need more servers" btn on 403 // Shake "need more servers" btn on 403
// MIRROR CHANGE: use old C# syntax that is supported in Unity 2019
// bool reachedNumDeploymentsHardcap = result is { IsResultCode403: true };
bool reachedNumDeploymentsHardcap = result != null && result.IsResultCode403; bool reachedNumDeploymentsHardcap = result != null && result.IsResultCode403;
// END MIRROR CHANGE
if (reachedNumDeploymentsHardcap) if (reachedNumDeploymentsHardcap)
shakeNeedMoreGameServersBtn(); shakeNeedMoreGameServersBtn();
} }
@ -1519,7 +1519,7 @@ private async Task buildAndPushServerAsync()
try try
{ {
// check for installation and setup docker file // check for installation and setup docker file
if (!await EdgegapBuildUtils.DockerSetupAndInstallationCheck()) if (!await EdgegapBuildUtils.DockerSetupAndInstallationCheck(DockerFilePath))
{ {
onBuildPushError("Docker installation not found. " + onBuildPushError("Docker installation not found. " +
"Docker can be downloaded from:\n\nhttps://www.docker.com/"); "Docker can be downloaded from:\n\nhttps://www.docker.com/");
@ -1593,7 +1593,7 @@ private async Task buildAndPushServerAsync()
// tag, // tag,
// ShowBuildWorkInProgress); // ShowBuildWorkInProgress);
await EdgegapBuildUtils.RunCommand_DockerBuild(registry, imageName, tag, status => ShowBuildWorkInProgress("Building Docker Image", status)); await EdgegapBuildUtils.RunCommand_DockerBuild(DockerFilePath, registry, imageName, tag, ProjectRootPath, status => ShowBuildWorkInProgress("Building Docker Image", status));
} }
else else
@ -1817,3 +1817,4 @@ private void setMaskedApiTokenFromEditorPrefs()
} }
} }
#endif #endif
#endif

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af70148bee18c584898036462be91b19
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1 +0,0 @@
Removed font files to avoid all kinds of runtime errors.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b0fb2c68be09174f8ea5057b27a545c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b18949555c60224384ab80e57e1fd68
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: fb67205c672fbb04d829783b9f771fc9
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Baron Neue
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 3445f8970e8fa734c96cef5ab98ddd61
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Spartan
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 52c456e5bd94b2b4b9045a79143694e9
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Spartan
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: 891349ed6c370194a8c6135995d27d32
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- Ubuntu Mono
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2635d61c9807d6c46bcb00a3d8645b37
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) [year] [fullname] Copyright (c) 2024 Edgegap
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -9,17 +9,35 @@ This plugin is intended to help you:
This plugin does not need to be included in your builds, as it's only a development tool and does not have any runtime features. This plugin does not need to be included in your builds, as it's only a development tool and does not have any runtime features.
## Install the plugin in Unity ## Install using git (recommended)
### Benefits
- Installing our plugin this way will ensure you get the freshest updates the moment they come out, see [the update guide](#update-the-plugin-in-unity).
### Caveats
- Requirement: functioning git client installed, for example [git-scm](https://git-scm.com/).
### Instructions
1. Open your Unity project, 1. Open your Unity project,
2. Select toolbar option **Window** -> **Package Manager**, 2. Select toolbar option **Window** -> **Package Manager**,
3. Click the **+** icon and select **Add package from git URL...**, 3. Click the **+** icon and select **Add package from git URL...**,
4. Input the following URL `https://github.com/edgegap/edgegap-unity-plugin.git`, 4. Input the following URL `https://github.com/edgegap/edgegap-unity-plugin.git`,
5. Click **Add** and wait for the Unity Package Manager to complete the installation. 5. Click **Add** and wait for the Unity Package Manager to complete the installation.
Once you have it, check for **Edgegap** -> **Edgegap Hosting** in Unity's top menu. ## Install via ZIP archive
### Benefits
- Slightly easier as no git client is required.
From here, we recommend following our [Unity Plugin Guide](https://docs.edgegap.com/docs/tools-and-integrations/unity-plugin-guide) to get your first dedicated server deployed. ### Caveats
- Installing our plugin this way will require you to manually replace plugin contents if you [wish to update it](#update-the-plugin-in-unity),
- The newtonsoft package (dependency) version required may not be compatible with your project if you're already using an older version of this package.
### Instructions
1. Select toolbar option **Window** -> **Package Manager**,
2. Click the **+** icon and select **Add package by name...**,
3. Input the name `com.unity.nuget.newtonsoft-json` and wait for the Unity Package Manager to complete the installation.,
4. Back to this github project - make sure you're on the `main` branch,
5. Click **<> Code**, then **Download ZIP**,
6. Paste the contents of the unzipped archive in your `Assets` folder within Unity project root.
## Other sources ## Other sources
@ -29,6 +47,14 @@ The only other official distribution channels for this plugin are:
**WARNING!** The [Edgegap plugin published on Unity Asset Store](https://assetstore.unity.com/packages/tools/network/edgegap-game-server-hosting-212563) is outdated and not supported anymore. If you've previously installed our plugin by another method than described above, please remove any Edgegap files or dependencies related before updating your plugin using the git URL. **WARNING!** The [Edgegap plugin published on Unity Asset Store](https://assetstore.unity.com/packages/tools/network/edgegap-game-server-hosting-212563) is outdated and not supported anymore. If you've previously installed our plugin by another method than described above, please remove any Edgegap files or dependencies related before updating your plugin using the git URL.
## Update the Plugin in Unity ## Next Steps
In order to update our plugin, locate it in Unity's **Package Manager** window and click **Update**. Wait for the process to complete and you're good to go! Once you have it, check for **Tools** -> **Edgegap Hosting** in Unity's top menu.
From here, we recommend following our [Unity Plugin Guide](https://docs.edgegap.com/docs/tools-and-integrations/unity-plugin-guide) to get your first dedicated server deployed.
### Update the Plugin in Unity
If you've installed using git, to update our plugin, locate it in Unity's **Package Manager** window and click **Update**. Wait for the process to complete and you're good to go!
If you've installed by copying, you will have to remove the Edgegap folder and paste the newer copy. Your settings are saved in your Unity version, so they shouldn't be lost in this process.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 34ed2e392924a4517b1410d21bbcdcb7 guid: a22cebde73b10534b99d20367fda7b1f
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -0,0 +1,6 @@
Notes for Mirror integration:
-> Plugin needs to be zip drop in instead of package manager dependency:
because Asset Store doesn't allow Github dependencies.
-> We have a few MIRROR CHANGES in the code for fixes / compatibility.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 57002d60dd8ce46b081a0a0a25e5b52b guid: 3f90e0819d5bc416c8da96885fcc9b72
TextScriptImporter: TextScriptImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@ -0,0 +1,21 @@
{
"name": "com.edgegap.unity-servers-plugin",
"version": "1.0.8",
"displayName": "Edgegap Servers Quickstart",
"description": "Get started quickly with Edgegap Dedicated Server hosting.",
"unity": "2021.3",
"unityRelease": "0f1",
"documentationUrl": "https://docs.edgegap.com/docs/tools-and-integrations/unity-plugin-guide",
"changelogUrl": "https://github.com/edgegap/edgegap-unity-plugin/releases",
"licensesUrl": "https://choosealicense.com/licenses/mit/",
"dependencies": {
"com.unity.nuget.newtonsoft-json": "3.2.1",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6",
"com.unity.modules.uielements": "1.0.0"
},
"author": {
"name": "Edgegap Technologies",
"email": "contact@edgegap.com",
"url": "https://www.edgegap.com"
}
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 92c00ff489f816847b08e40fa333ae7c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: