From 7c507198837fba9640e549ac9d596918ab27bd6b Mon Sep 17 00:00:00 2001 From: Mark Mandel Date: Thu, 7 Nov 2019 13:08:57 -0800 Subject: [PATCH] Implementation of GameServer() for Unity (#1169) - Refactored `SendRequestAsync` to return a `AsyncResult` which contains both a success bool, and the returned body text from the http call. - Used the swagger.json to generate the basic structure of the `GameServer` object, and then hand modified as needed. - Had to include third_party/MiniJSON because Unity doesn't natively support `Dictionary`, which means we can't get Label or Annotation data. Then had to manually import the MiniJSON data into GameServer and related model objects. - Used a set of symlinks to share the Agones Unity SDK code with the unity-simple example. Couldn't think of a better way. - Minor version update on the example project as that is the earliest version I could get in 2018.4.x - Added `GameServer` command to the simple-unity example as well. Work on #927 --- examples/unity-simple/.gitignore | 4 + .../Assets/Scripts/Agones/model.meta | 3 + .../Assets/Scripts/Agones/model/GameServer.cs | 1 + .../Scripts/Agones/model/GameServer.cs.meta | 3 + .../Agones/model/GameServerObjectMeta.cs | 1 + .../Agones/model/GameServerObjectMeta.cs.meta | 3 + .../Scripts/Agones/model/GameServerSpec.cs | 1 + .../Agones/model/GameServerSpec.cs.meta | 3 + .../Scripts/Agones/model/GameServerStatus.cs | 1 + .../Agones/model/GameServerStatus.cs.meta | 3 + .../Assets/Scripts/Agones/model/SpecHealth.cs | 1 + .../Scripts/Agones/model/SpecHealth.cs.meta | 3 + .../Assets/Scripts/Agones/model/StatusPort.cs | 1 + .../Scripts/Agones/model/StatusPort.cs.meta | 3 + .../Assets/Scripts/Agones/third_party.meta | 3 + .../Scripts/Agones/third_party/MiniJSON.cs | 1 + .../Agones/third_party/MiniJSON.cs.meta | 11 + .../Assets/Scripts/UdpEchoServer.cs | 8 + examples/unity-simple/Packages/manifest.json | 2 +- .../ProjectSettings/EditorSettings.asset | 44 +- .../ProjectSettings/ProjectVersion.txt | 2 +- examples/unity-simple/README.md | 1 + pkg/sdk/sdk.pb.go | 6 +- sdks/unity/AgonesSdk.cs | 56 +- sdks/unity/model/GameServer.cs | 128 ++++ sdks/unity/model/GameServerObjectMeta.cs | 194 +++++++ sdks/unity/model/GameServerSpec.cs | 93 +++ sdks/unity/model/GameServerStatus.cs | 123 ++++ sdks/unity/model/SpecHealth.cs | 115 ++++ sdks/unity/model/StatusPort.cs | 101 ++++ sdks/unity/third_party/MiniJSON.cs | 547 ++++++++++++++++++ site/content/en/docs/Examples/_index.md | 2 + .../en/docs/Guides/Client SDKs/unity.md | 11 + 33 files changed, 1442 insertions(+), 37 deletions(-) create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs.meta create mode 100644 examples/unity-simple/Assets/Scripts/Agones/third_party.meta create mode 120000 examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs create mode 100644 examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs.meta create mode 100644 sdks/unity/model/GameServer.cs create mode 100644 sdks/unity/model/GameServerObjectMeta.cs create mode 100644 sdks/unity/model/GameServerSpec.cs create mode 100644 sdks/unity/model/GameServerStatus.cs create mode 100644 sdks/unity/model/SpecHealth.cs create mode 100644 sdks/unity/model/StatusPort.cs create mode 100644 sdks/unity/third_party/MiniJSON.cs diff --git a/examples/unity-simple/.gitignore b/examples/unity-simple/.gitignore index 2d79b8d5b9..8a3b351951 100644 --- a/examples/unity-simple/.gitignore +++ b/examples/unity-simple/.gitignore @@ -34,3 +34,7 @@ sysinfo.txt # Builds *.apk *.unitypackage + +# Rider plugins +/Assets/Plugins/ +/Assets/Plugins.meta diff --git a/examples/unity-simple/Assets/Scripts/Agones/model.meta b/examples/unity-simple/Assets/Scripts/Agones/model.meta new file mode 100644 index 0000000000..a2894af8bb --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 500ddd914aef4fcca9861f610b7cd17b +timeCreated: 1572467888 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs b/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs new file mode 120000 index 0000000000..6f7e7070ab --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/GameServer.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs.meta new file mode 100644 index 0000000000..3d26aeeeef --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 03a76250b0ac46ecaeb135236a8b0878 +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs new file mode 120000 index 0000000000..0f8b989169 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/GameServerObjectMeta.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs.meta new file mode 100644 index 0000000000..3adec1b84d --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerObjectMeta.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5622544a94a94bb58fcaf4db56ad613b +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs new file mode 120000 index 0000000000..8a2f554fd8 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/GameServerSpec.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs.meta new file mode 100644 index 0000000000..64c669efa2 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerSpec.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: daf87a9144b54399be072876a7e2118d +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs new file mode 120000 index 0000000000..5deed14070 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/GameServerStatus.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs.meta new file mode 100644 index 0000000000..c80208975c --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/GameServerStatus.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e106e257a123459d899c55216e51c6c3 +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs b/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs new file mode 120000 index 0000000000..189733c72f --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/SpecHealth.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs.meta new file mode 100644 index 0000000000..e02b01daf9 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/SpecHealth.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 521f3915c1264dcdaeeb065f893f4d31 +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs b/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs new file mode 120000 index 0000000000..52fd0bad98 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/model/StatusPort.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs.meta new file mode 100644 index 0000000000..f99676aa9e --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/model/StatusPort.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d4d22e0c916347dfadb56c4991cd8488 +timeCreated: 1572467889 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/third_party.meta b/examples/unity-simple/Assets/Scripts/Agones/third_party.meta new file mode 100644 index 0000000000..b34ccfb0d8 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/third_party.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1425b63e14e44a15b9d375e5a8878d87 +timeCreated: 1572465524 \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs b/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs new file mode 120000 index 0000000000..e8089d153b --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs @@ -0,0 +1 @@ +../../../../../../sdks/unity/third_party/MiniJSON.cs \ No newline at end of file diff --git a/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs.meta b/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs.meta new file mode 100644 index 0000000000..3ff1563cc7 --- /dev/null +++ b/examples/unity-simple/Assets/Scripts/Agones/third_party/MiniJSON.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d76460015a67079e081b30be61e1b287 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/examples/unity-simple/Assets/Scripts/UdpEchoServer.cs b/examples/unity-simple/Assets/Scripts/UdpEchoServer.cs index 933a8ddaa9..65e27037ed 100644 --- a/examples/unity-simple/Assets/Scripts/UdpEchoServer.cs +++ b/examples/unity-simple/Assets/Scripts/UdpEchoServer.cs @@ -73,6 +73,14 @@ async void Update() echoBytes = Encoding.UTF8.GetBytes($"Allocate {ok}"); break; + + case "GameServer": + var gameserver = await agones.GameServer(); + Debug.Log($"Server - GameServer {gameserver}"); + + ok = gameserver != null; + echoBytes = Encoding.UTF8.GetBytes(ok ? $"GameServer() Name: {gameserver.ObjectMeta.Name} {ok}" : $"GameServer(): {ok}"); + break; case "Label": if (recvTexts.Length == 3) diff --git a/examples/unity-simple/Packages/manifest.json b/examples/unity-simple/Packages/manifest.json index 73c0efb557..215430d3e6 100644 --- a/examples/unity-simple/Packages/manifest.json +++ b/examples/unity-simple/Packages/manifest.json @@ -3,7 +3,7 @@ "com.unity.ads": "2.0.8", "com.unity.analytics": "3.2.2", "com.unity.collab-proxy": "1.2.15", - "com.unity.package-manager-ui": "2.0.7", + "com.unity.package-manager-ui": "2.0.8", "com.unity.purchasing": "2.0.3", "com.unity.textmeshpro": "1.4.1", "com.unity.modules.ai": "1.0.0", diff --git a/examples/unity-simple/ProjectSettings/EditorSettings.asset b/examples/unity-simple/ProjectSettings/EditorSettings.asset index 78c18acce6..de17694cb9 100644 --- a/examples/unity-simple/ProjectSettings/EditorSettings.asset +++ b/examples/unity-simple/ProjectSettings/EditorSettings.asset @@ -1,21 +1,23 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!159 &1 -EditorSettings: - m_ObjectHideFlags: 0 - serializedVersion: 7 - m_ExternalVersionControlSupport: Visible Meta Files - m_SerializationMode: 2 - m_LineEndingsForNewScripts: 2 - m_DefaultBehaviorMode: 0 - m_SpritePackerMode: 0 - m_SpritePackerPaddingPower: 1 - m_EtcTextureCompressorBehavior: 1 - m_EtcTextureFastCompressor: 1 - m_EtcTextureNormalCompressor: 2 - m_EtcTextureBestCompressor: 4 - m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd - m_ProjectGenerationRootNamespace: - m_UserGeneratedProjectSuffix: - m_CollabEditorSettings: - inProgressEnabled: 1 +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 7 + m_ExternalVersionControlSupport: Visible Meta Files + m_SerializationMode: 2 + m_LineEndingsForNewScripts: 2 + m_DefaultBehaviorMode: 0 + m_PrefabRegularEnvironment: {fileID: 0} + m_PrefabUIEnvironment: {fileID: 0} + m_SpritePackerMode: 0 + m_SpritePackerPaddingPower: 1 + m_EtcTextureCompressorBehavior: 1 + m_EtcTextureFastCompressor: 1 + m_EtcTextureNormalCompressor: 2 + m_EtcTextureBestCompressor: 4 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef + m_ProjectGenerationRootNamespace: + m_CollabEditorSettings: + inProgressEnabled: 1 + m_EnableTextureStreamingInPlayMode: 1 diff --git a/examples/unity-simple/ProjectSettings/ProjectVersion.txt b/examples/unity-simple/ProjectSettings/ProjectVersion.txt index f20e53672c..12efb654fa 100644 --- a/examples/unity-simple/ProjectSettings/ProjectVersion.txt +++ b/examples/unity-simple/ProjectSettings/ProjectVersion.txt @@ -1 +1 @@ -m_EditorVersion: 2018.4.2f1 +m_EditorVersion: 2018.4.12f1 diff --git a/examples/unity-simple/README.md b/examples/unity-simple/README.md index 25f7629651..f435edfdf9 100644 --- a/examples/unity-simple/README.md +++ b/examples/unity-simple/README.md @@ -53,3 +53,4 @@ $ kubectl create -f gameserver.yaml | Label $1 $2 | SetLabel($1, $2) | | Annotation $1 $2 | SetAnnotation($1, $2) | | Shutdown | Shutdown() | + | GaameServer | GameServer() | \ No newline at end of file diff --git a/pkg/sdk/sdk.pb.go b/pkg/sdk/sdk.pb.go index c05497dd43..adda6f400d 100644 --- a/pkg/sdk/sdk.pb.go +++ b/pkg/sdk/sdk.pb.go @@ -19,10 +19,10 @@ package sdk import ( - fmt "fmt" - math "math" + "fmt" + "math" - proto "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/proto" context "golang.org/x/net/context" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" diff --git a/sdks/unity/AgonesSdk.cs b/sdks/unity/AgonesSdk.cs index 8f0df9b36c..4a64ac9360 100644 --- a/sdks/unity/AgonesSdk.cs +++ b/sdks/unity/AgonesSdk.cs @@ -14,10 +14,14 @@ // limitations under the License. using System; +using System.Collections.Generic; +using System.Net; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using Agones.Model; +using MiniJSON; using UnityEngine; using UnityEngine.Networking; @@ -31,8 +35,7 @@ public class AgonesSdk : MonoBehaviour /// /// Interval of the server sending a health ping to the Agones sidecar. /// - [Range(0.01f, 5)] - public float healthIntervalSecond = 5.0f; + [Range(0.01f, 5)] public float healthIntervalSecond = 5.0f; /// /// Whether the server sends a health ping to the Agones sidecar. @@ -78,7 +81,23 @@ private void OnApplicationQuit() /// public async Task Ready() { - return await SendRequestAsync("/ready", "{}"); + return await SendRequestAsync("/ready", "{}").ContinueWith(task => task.Result.ok); + } + + /// + /// Retrieve the GameServer details + /// + /// The current GameServer configuration + public async Task GameServer() + { + var result = await SendRequestAsync("/gameserver", "{}", UnityWebRequest.kHttpVerbGET); + if (!result.ok) + { + return null; + } + + var data = Json.Deserialize(result.json) as Dictionary; + return new GameServer(data); } /// @@ -89,7 +108,7 @@ public async Task Ready() /// public async Task Shutdown() { - return await SendRequestAsync("/shutdown", "{}"); + return await SendRequestAsync("/shutdown", "{}").ContinueWith(task => task.Result.ok); } /// @@ -100,7 +119,7 @@ public async Task Shutdown() /// public async Task Allocate() { - return await SendRequestAsync("/allocate", "{}"); + return await SendRequestAsync("/allocate", "{}").ContinueWith(task => task.Result.ok); } /// @@ -114,7 +133,8 @@ public async Task Allocate() public async Task SetLabel(string key, string value) { string json = JsonUtility.ToJson(new KeyValueMessage(key, value)); - return await SendRequestAsync("/metadata/label", json, UnityWebRequest.kHttpVerbPUT); + return await SendRequestAsync("/metadata/label", json, UnityWebRequest.kHttpVerbPUT) + .ContinueWith(task => task.Result.ok); } /// @@ -128,7 +148,8 @@ public async Task SetLabel(string key, string value) public async Task SetAnnotation(string key, string value) { string json = JsonUtility.ToJson(new KeyValueMessage(key, value)); - return await SendRequestAsync("/metadata/annotation", json, UnityWebRequest.kHttpVerbPUT); + return await SendRequestAsync("/metadata/annotation", json, UnityWebRequest.kHttpVerbPUT) + .ContinueWith(task => task.Result.ok); } #endregion @@ -150,7 +171,17 @@ private async void HealthCheckAsync() } } - private async Task SendRequestAsync(string api, string json, string method = UnityWebRequest.kHttpVerbPOST) + /// + /// Result of a Async HTTP request + /// + private struct AsyncResult + { + public bool ok; + public string json; + } + + private async Task SendRequestAsync(string api, string json, + string method = UnityWebRequest.kHttpVerbPOST) { // To prevent that an async method leaks after destroying this gameObject. cancellationTokenSource.Token.ThrowIfCancellationRequested(); @@ -164,10 +195,13 @@ private async Task SendRequestAsync(string api, string json, string method await new AgonesAsyncOperationWrapper(req.SendWebRequest()); - bool ok = req.responseCode == (long)System.Net.HttpStatusCode.OK; + var result = new AsyncResult(); + + result.ok = req.responseCode == (long) HttpStatusCode.OK; - if (ok) + if (result.ok) { + result.json = req.downloadHandler.text; Log($"Agones SendRequest ok: {api} {req.downloadHandler.text}"); } else @@ -175,7 +209,7 @@ private async Task SendRequestAsync(string api, string json, string method Log($"Agones SendRequest failed: {api} {req.error}"); } - return ok; + return result; } private void Log(object message) diff --git a/sdks/unity/model/GameServer.cs b/sdks/unity/model/GameServer.cs new file mode 100644 index 0000000000..f424d5cb49 --- /dev/null +++ b/sdks/unity/model/GameServer.cs @@ -0,0 +1,128 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Agones.Model +{ + /// + /// A GameServer Custom Resource Definition object We will only export those resources that make the most sense. Can always expand to more as needed. + /// + public class GameServer : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public GameServer(IReadOnlyDictionary data) + { + if (data == null) return; + + this.ObjectMeta = new GameServerObjectMeta((Dictionary) data["object_meta"]); + this.Spec = new GameServerSpec((Dictionary) data["spec"]); + // Seems possible that the status field could be null, especially for local SDK tooling, + // so don't know an exception here if the conversion fails. + this.Status = new GameServerStatus(data["status"] as Dictionary); + } + + /// + /// Gets or Sets ObjectMeta + /// + public GameServerObjectMeta ObjectMeta { get; set; } + + /// + /// Gets or Sets Spec + /// + public GameServerSpec Spec { get; set; } + + /// + /// Gets or Sets Status + /// + public GameServerStatus Status { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class GameServer {\n"); + sb.Append(" ObjectMeta: ").Append(ObjectMeta).Append("\n"); + sb.Append(" Spec: ").Append(Spec).Append("\n"); + sb.Append(" Status: ").Append(Status).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as GameServer); + } + + /// + /// Returns true if GameServer instances are equal + /// + /// Instance of GameServer to be compared + /// Boolean + public bool Equals(GameServer input) + { + if (input == null) + return false; + + return + ( + this.ObjectMeta == input.ObjectMeta || + (this.ObjectMeta != null && + this.ObjectMeta.Equals(input.ObjectMeta)) + ) && + ( + this.Spec == input.Spec || + (this.Spec != null && + this.Spec.Equals(input.Spec)) + ) && + ( + this.Status == input.Status || + (this.Status != null && + this.Status.Equals(input.Status)) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + if (this.ObjectMeta != null) + hashCode = hashCode * 59 + this.ObjectMeta.GetHashCode(); + if (this.Spec != null) + hashCode = hashCode * 59 + this.Spec.GetHashCode(); + if (this.Status != null) + hashCode = hashCode * 59 + this.Status.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/model/GameServerObjectMeta.cs b/sdks/unity/model/GameServerObjectMeta.cs new file mode 100644 index 0000000000..d3e0b8b2f8 --- /dev/null +++ b/sdks/unity/model/GameServerObjectMeta.cs @@ -0,0 +1,194 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Agones.Model +{ + /// + /// GameServerObjectMeta + /// + public class GameServerObjectMeta : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public GameServerObjectMeta(IReadOnlyDictionary data) + { + this.Name = (string) data["name"]; + this.Namespace = (string) data["namespace"]; + this.Uid = (string) data["uid"]; + this.ResourceVersion = (string) data["resource_version"]; + this.Generation = Int32.Parse((string) data["generation"]); + this.CreationTimestamp = + DateTimeOffset.FromUnixTimeSeconds(long.Parse((string) data["creation_timestamp"])).DateTime; + + if (data.TryGetValue("deletion_timestamp", out var timestamp)) + { + this.DeletionTimestamp = + DateTimeOffset.FromUnixTimeSeconds(long.Parse((string) timestamp)).DateTime; + } + + if (data.TryGetValue("annotations", out var annotations)) + { + this.Annotations = new Dictionary(); + var values = (Dictionary) annotations; + foreach (var item in values) + { + this.Annotations.Add(item.Key, item.Value.ToString()); + } + } + + if (data.TryGetValue("labels", out var labels)) + { + this.Labels = new Dictionary(); + var values = (Dictionary) labels; + foreach (var item in values) + { + this.Labels.Add(item.Key, item.Value.ToString()); + } + } + } + + public string Name { get; } + public string Namespace { get; } + public string Uid { get; } + public string ResourceVersion { get; } + public int Generation { get; } + public DateTime CreationTimestamp { get; } + public DateTime? DeletionTimestamp { get; } + public Dictionary Annotations { get; } + public Dictionary Labels { get; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class GameServerObjectMeta {\n"); + sb.Append(" Name: ").Append(Name).Append("\n"); + sb.Append(" Namespace: ").Append(Namespace).Append("\n"); + sb.Append(" Uid: ").Append(Uid).Append("\n"); + sb.Append(" ResourceVersion: ").Append(ResourceVersion).Append("\n"); + sb.Append(" Generation: ").Append(Generation).Append("\n"); + sb.Append(" CreationTimestamp: ").Append(CreationTimestamp).Append("\n"); + sb.Append(" DeletionTimestamp: ").Append(DeletionTimestamp).Append("\n"); + sb.Append(" Annotations: ").Append(string.Join(";", Annotations)).Append("\n"); + sb.Append(" Labels: ").Append(string.Join(";", Labels)).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as GameServerObjectMeta); + } + + /// + /// Returns true if GameServerObjectMeta instances are equal + /// + /// Instance of GameServerObjectMeta to be compared + /// Boolean + public bool Equals(GameServerObjectMeta input) + { + if (input == null) + return false; + + return + ( + this.Name == input.Name || + (this.Name != null && + this.Name.Equals(input.Name)) + ) && + ( + this.Namespace == input.Namespace || + (this.Namespace != null && + this.Namespace.Equals(input.Namespace)) + ) && + ( + this.Uid == input.Uid || + (this.Uid != null && + this.Uid.Equals(input.Uid)) + ) && + ( + this.ResourceVersion == input.ResourceVersion || + (this.ResourceVersion != null && + this.ResourceVersion.Equals(input.ResourceVersion)) + ) && + ( + this.Generation == input.Generation || + (this.Generation.Equals(input.Generation)) + ) && + ( + this.CreationTimestamp == input.CreationTimestamp || + (this.CreationTimestamp.Equals(input.CreationTimestamp)) + ) && + ( + this.DeletionTimestamp == input.DeletionTimestamp || + (this.DeletionTimestamp != null && + this.DeletionTimestamp.Equals(input.DeletionTimestamp)) + ) && + ( + this.Annotations == input.Annotations || + this.Annotations != null && + this.Annotations.SequenceEqual(input.Annotations) + ) && + ( + this.Labels == input.Labels || + this.Labels != null && + this.Labels.SequenceEqual(input.Labels) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + if (this.Name != null) + hashCode = hashCode * 59 + this.Name.GetHashCode(); + if (this.Namespace != null) + hashCode = hashCode * 59 + this.Namespace.GetHashCode(); + if (this.Uid != null) + hashCode = hashCode * 59 + this.Uid.GetHashCode(); + if (this.ResourceVersion != null) + hashCode = hashCode * 59 + this.ResourceVersion.GetHashCode(); + hashCode = hashCode * 59 + this.Generation.GetHashCode(); + hashCode = hashCode * 59 + this.CreationTimestamp.GetHashCode(); + if (this.DeletionTimestamp != null) + hashCode = hashCode * 59 + this.DeletionTimestamp.GetHashCode(); + if (this.Annotations != null) + hashCode = hashCode * 59 + this.Annotations.GetHashCode(); + if (this.Labels != null) + hashCode = hashCode * 59 + this.Labels.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/model/GameServerSpec.cs b/sdks/unity/model/GameServerSpec.cs new file mode 100644 index 0000000000..b47665a273 --- /dev/null +++ b/sdks/unity/model/GameServerSpec.cs @@ -0,0 +1,93 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Agones.Model +{ + /// + /// GameServerSpec + /// + public class GameServerSpec : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public GameServerSpec(IReadOnlyDictionary data) + { + this.Health = new SpecHealth((Dictionary) data["health"]); + } + + public SpecHealth Health { get; set; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class GameServerSpec {\n"); + sb.Append(" Health: ").Append(Health).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as GameServerSpec); + } + + /// + /// Returns true if GameServerSpec instances are equal + /// + /// Instance of GameServerSpec to be compared + /// Boolean + public bool Equals(GameServerSpec input) + { + if (input == null) + return false; + + return + ( + this.Health == input.Health || + (this.Health != null && + this.Health.Equals(input.Health)) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + if (this.Health != null) + hashCode = hashCode * 59 + this.Health.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/model/GameServerStatus.cs b/sdks/unity/model/GameServerStatus.cs new file mode 100644 index 0000000000..f8adcf3b86 --- /dev/null +++ b/sdks/unity/model/GameServerStatus.cs @@ -0,0 +1,123 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Agones.Model +{ + /// + /// GameServerStatus + /// + public class GameServerStatus : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public GameServerStatus(IReadOnlyDictionary data) + { + if (data == null) return; + + this.State = (string) data["state"]; + this.Address = (string) data["address"]; + + this.Ports = new List(); + var items = (IReadOnlyList) data["ports"]; + foreach (var i in items) + { + var port = new StatusPort((Dictionary) i); + this.Ports.Add(port); + } + } + + public string State { get; } + public string Address { get; } + public List Ports { get; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class GameServerStatus {\n"); + sb.Append(" State: ").Append(State).Append("\n"); + sb.Append(" Address: ").Append(Address).Append("\n"); + sb.Append(" Ports: ").Append(string.Join(";", Ports)).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as GameServerStatus); + } + + /// + /// Returns true if GameServerStatus instances are equal + /// + /// Instance of GameServerStatus to be compared + /// Boolean + public bool Equals(GameServerStatus input) + { + if (input == null) + return false; + + return + ( + this.State == input.State || + (this.State != null && + this.State.Equals(input.State)) + ) && + ( + this.Address == input.Address || + (this.Address != null && + this.Address.Equals(input.Address)) + ) && + ( + this.Ports == input.Ports || + this.Ports != null && + this.Ports.SequenceEqual(input.Ports) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + if (this.State != null) + hashCode = hashCode * 59 + this.State.GetHashCode(); + if (this.Address != null) + hashCode = hashCode * 59 + this.Address.GetHashCode(); + if (this.Ports != null) + hashCode = hashCode * 59 + this.Ports.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/model/SpecHealth.cs b/sdks/unity/model/SpecHealth.cs new file mode 100644 index 0000000000..f2e5f7123d --- /dev/null +++ b/sdks/unity/model/SpecHealth.cs @@ -0,0 +1,115 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Agones.Model +{ + /// + /// SpecHealth + /// + public class SpecHealth : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public SpecHealth(IReadOnlyDictionary data) + { + this.Disabled = data.TryGetValue("disabled", out var disabled) && bool.Parse((string) disabled); + this.PeriodSeconds = (Int64) data["period_seconds"]; + this.FailureThreshold = (Int64) data["failure_threshold"]; + this.InitialDelaySeconds = (Int64) data["initial_delay_seconds"]; + } + + public bool Disabled { get; } + public Int64 PeriodSeconds { get; } + public Int64 FailureThreshold { get; } + public Int64 InitialDelaySeconds { get; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class SpecHealth {\n"); + sb.Append(" Disabled: ").Append(Disabled).Append("\n"); + sb.Append(" PeriodSeconds: ").Append(PeriodSeconds).Append("\n"); + sb.Append(" FailureThreshold: ").Append(FailureThreshold).Append("\n"); + sb.Append(" InitialDelaySeconds: ").Append(InitialDelaySeconds).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as SpecHealth); + } + + /// + /// Returns true if SpecHealth instances are equal + /// + /// Instance of SpecHealth to be compared + /// Boolean + public bool Equals(SpecHealth input) + { + if (input == null) + return false; + + return + ( + this.Disabled == input.Disabled || + (this.Disabled.Equals(input.Disabled)) + ) && + ( + this.PeriodSeconds == input.PeriodSeconds || + (this.PeriodSeconds.Equals(input.PeriodSeconds)) + ) && + ( + this.FailureThreshold == input.FailureThreshold || + (this.FailureThreshold.Equals(input.FailureThreshold)) + ) && + ( + this.InitialDelaySeconds == input.InitialDelaySeconds || + (this.InitialDelaySeconds.Equals(input.InitialDelaySeconds)) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + hashCode = hashCode * 59 + this.Disabled.GetHashCode(); + hashCode = hashCode * 59 + this.PeriodSeconds.GetHashCode(); + hashCode = hashCode * 59 + this.FailureThreshold.GetHashCode(); + hashCode = hashCode * 59 + this.InitialDelaySeconds.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/model/StatusPort.cs b/sdks/unity/model/StatusPort.cs new file mode 100644 index 0000000000..ff514fdf5e --- /dev/null +++ b/sdks/unity/model/StatusPort.cs @@ -0,0 +1,101 @@ +// Copyright 2019 Google LLC +// All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Agones.Model +{ + /// + /// StatusPort + /// + public class StatusPort : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + public StatusPort(IReadOnlyDictionary data) + { + this.Name = (string) data["name"]; + this.Port = (Int64) data["port"]; + } + + public string Name { get; } + public Int64 Port { get; } + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class StatusPort {\n"); + sb.Append(" Name: ").Append(Name).Append("\n"); + sb.Append(" Port: ").Append(Port).Append("\n"); + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object input) + { + return this.Equals(input as StatusPort); + } + + /// + /// Returns true if StatusPort instances are equal + /// + /// Instance of StatusPort to be compared + /// Boolean + public bool Equals(StatusPort input) + { + if (input == null) + return false; + + return + ( + this.Name == input.Name || + (this.Name != null && + this.Name.Equals(input.Name)) + ) && + ( + this.Port == input.Port || + (this.Port.Equals(input.Port)) + ); + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hashCode = 41; + if (this.Name != null) + hashCode = hashCode * 59 + this.Name.GetHashCode(); + hashCode = hashCode * 59 + this.Port.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/sdks/unity/third_party/MiniJSON.cs b/sdks/unity/third_party/MiniJSON.cs new file mode 100644 index 0000000000..513f1c0ceb --- /dev/null +++ b/sdks/unity/third_party/MiniJSON.cs @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2013 Calvin Rien + * + * Based on the JSON parser by Patrick van Bergen + * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + * + * Simplified it so that it doesn't throw exceptions + * and can be used in Unity iPhone with maximum code stripping. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace MiniJSON { + // Example usage: + // + // using UnityEngine; + // using System.Collections; + // using System.Collections.Generic; + // using MiniJSON; + // + // public class MiniJSONTest : MonoBehaviour { + // void Start () { + // var jsonString = "{ \"array\": [1.44,2,3], " + + // "\"object\": {\"key1\":\"value1\", \"key2\":256}, " + + // "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " + + // "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " + + // "\"int\": 65536, " + + // "\"float\": 3.1415926, " + + // "\"bool\": true, " + + // "\"null\": null }"; + // + // var dict = Json.Deserialize(jsonString) as Dictionary; + // + // Debug.Log("deserialized: " + dict.GetType()); + // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); + // Debug.Log("dict['string']: " + (string) dict["string"]); + // Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles + // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs + // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); + // + // var str = Json.Serialize(dict); + // + // Debug.Log("serialized: " + str); + // } + // } + + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. + /// All numbers are parsed to doubles. + /// + public static class Json { + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false + public static object Deserialize(string json) { + // save the string for debug information + if (json == null) { + return null; + } + + return Parser.Parse(json); + } + + sealed class Parser : IDisposable { + const string WORD_BREAK = "{}[],:\""; + + public static bool IsWordBreak(char c) { + return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1; + } + + enum TOKEN { + NONE, + CURLY_OPEN, + CURLY_CLOSE, + SQUARED_OPEN, + SQUARED_CLOSE, + COLON, + COMMA, + STRING, + NUMBER, + TRUE, + FALSE, + NULL + }; + + StringReader json; + + Parser(string jsonString) { + json = new StringReader(jsonString); + } + + public static object Parse(string jsonString) { + using (var instance = new Parser(jsonString)) { + return instance.ParseValue(); + } + } + + public void Dispose() { + json.Dispose(); + json = null; + } + + Dictionary ParseObject() { + Dictionary table = new Dictionary(); + + // ditch opening brace + json.Read(); + + // { + while (true) { + switch (NextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) { + return null; + } + + // : + if (NextToken != TOKEN.COLON) { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; + } + } + } + + List ParseArray() { + List array = new List(); + + // ditch opening bracket + json.Read(); + + // [ + var parsing = true; + while (parsing) { + TOKEN nextToken = NextToken; + + switch (nextToken) { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; + } + } + + return array; + } + + object ParseValue() { + TOKEN nextToken = NextToken; + return ParseByToken(nextToken); + } + + object ParseByToken(TOKEN token) { + switch (token) { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; + } + } + + string ParseString() { + StringBuilder s = new StringBuilder(); + char c; + + // ditch opening quote + json.Read(); + + bool parsing = true; + while (parsing) { + + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + parsing = false; + break; + case '\\': + if (json.Peek() == -1) { + parsing = false; + break; + } + + c = NextChar; + switch (c) { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new char[4]; + + for (int i=0; i< 4; i++) { + hex[i] = NextChar; + } + + s.Append((char) Convert.ToInt32(new string(hex), 16)); + break; + } + break; + default: + s.Append(c); + break; + } + } + + return s.ToString(); + } + + object ParseNumber() { + string number = NextWord; + + if (number.IndexOf('.') == -1) { + long parsedInt; + Int64.TryParse(number, out parsedInt); + return parsedInt; + } + + double parsedDouble; + Double.TryParse(number, out parsedDouble); + return parsedDouble; + } + + void EatWhitespace() { + while (Char.IsWhiteSpace(PeekChar)) { + json.Read(); + + if (json.Peek() == -1) { + break; + } + } + } + + char PeekChar { + get { + return Convert.ToChar(json.Peek()); + } + } + + char NextChar { + get { + return Convert.ToChar(json.Read()); + } + } + + string NextWord { + get { + StringBuilder word = new StringBuilder(); + + while (!IsWordBreak(PeekChar)) { + word.Append(NextChar); + + if (json.Peek() == -1) { + break; + } + } + + return word.ToString(); + } + } + + TOKEN NextToken { + get { + EatWhitespace(); + + if (json.Peek() == -1) { + return TOKEN.NONE; + } + + switch (PeekChar) { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; + } + + switch (NextWord) { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; + } + + return TOKEN.NONE; + } + } + } + + /// + /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string + /// + /// A Dictionary<string, object> / List<object> + /// A JSON encoded string, or null if object 'json' is not serializable + public static string Serialize(object obj) { + return Serializer.Serialize(obj); + } + + sealed class Serializer { + StringBuilder builder; + + Serializer() { + builder = new StringBuilder(); + } + + public static string Serialize(object obj) { + var instance = new Serializer(); + + instance.SerializeValue(obj); + + return instance.builder.ToString(); + } + + void SerializeValue(object value) { + IList asList; + IDictionary asDict; + string asStr; + + if (value == null) { + builder.Append("null"); + } else if ((asStr = value as string) != null) { + SerializeString(asStr); + } else if (value is bool) { + builder.Append((bool) value ? "true" : "false"); + } else if ((asList = value as IList) != null) { + SerializeArray(asList); + } else if ((asDict = value as IDictionary) != null) { + SerializeObject(asDict); + } else if (value is char) { + SerializeString(new string((char) value, 1)); + } else { + SerializeOther(value); + } + } + + void SerializeObject(IDictionary obj) { + bool first = true; + + builder.Append('{'); + + foreach (object e in obj.Keys) { + if (!first) { + builder.Append(','); + } + + SerializeString(e.ToString()); + builder.Append(':'); + + SerializeValue(obj[e]); + + first = false; + } + + builder.Append('}'); + } + + void SerializeArray(IList anArray) { + builder.Append('['); + + bool first = true; + + foreach (object obj in anArray) { + if (!first) { + builder.Append(','); + } + + SerializeValue(obj); + + first = false; + } + + builder.Append(']'); + } + + void SerializeString(string str) { + builder.Append('\"'); + + char[] charArray = str.ToCharArray(); + foreach (var c in charArray) { + switch (c) { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + int codepoint = Convert.ToInt32(c); + if ((codepoint >= 32) && (codepoint <= 126)) { + builder.Append(c); + } else { + builder.Append("\\u"); + builder.Append(codepoint.ToString("x4")); + } + break; + } + } + + builder.Append('\"'); + } + + void SerializeOther(object value) { + // NOTE: decimals lose precision during serialization. + // They always have, I'm just letting you know. + // Previously floats and doubles lost precision too. + if (value is float) { + builder.Append(((float) value).ToString("R")); + } else if (value is int + || value is uint + || value is long + || value is sbyte + || value is byte + || value is short + || value is ushort + || value is ulong) { + builder.Append(value); + } else if (value is double + || value is decimal) { + builder.Append(Convert.ToDouble(value).ToString("R")); + } else { + SerializeString(value.ToString()); + } + } + } + } +} diff --git a/site/content/en/docs/Examples/_index.md b/site/content/en/docs/Examples/_index.md index e4b5eb2ff5..90357dae4a 100644 --- a/site/content/en/docs/Examples/_index.md +++ b/site/content/en/docs/Examples/_index.md @@ -27,6 +27,8 @@ These are all examples of simple game server implementations, that integrate the A simple Node.js example that marks itself as ready, sets some labels and then shutsdown. - {{< ghlink href="examples/rust-simple" >}}Rust Simple{{< /ghlink >}} (Rust) - A simple Rust example that marks itself as ready, sets some labels and then shutsdown. +- {{< ghlink href="examples/unity-simple" >}}Unity Simple{{< /ghlink >}} (Unity3d) - + This is a very simple "unity server" that doesn't do much other than show how the SDK works in Unity. - {{< ghlink href="examples/xonotic" >}}Xonotic{{< /ghlink >}} - Wraps the SDK around the open source FPS game [Xonotic](http://www.xonotic.org) and hosts it on Agones. ## Building on top of Agones diff --git a/site/content/en/docs/Guides/Client SDKs/unity.md b/site/content/en/docs/Guides/Client SDKs/unity.md index f343ffa617..58a41659cc 100644 --- a/site/content/en/docs/Guides/Client SDKs/unity.md +++ b/site/content/en/docs/Guides/Client SDKs/unity.md @@ -40,6 +40,17 @@ async void SomeMethod() } ``` +{{% feature publishVersion="1.2.0" %}} + +To get the details on the [backing `GameServer`]({{< relref "_index.md#gameserver" >}}) call `GameServer()`. + +Will return `null` if there is an error in retrieving the `GameServer` record. + +```csharp +var gameserver = await agones.GameServer(); +``` +{{% /feature %}} + To mark that the [game session is completed]({{< relref "_index.md#shutdown" >}}) and the game server should be shut down call `Shutdown()`. ```csharp