agones.dev/agones@v1.54.0/sdks/unity/AgonesBetaSdk.cs (about) 1 // Copyright 2022 Google LLC 2 // All Rights Reserved. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 using System; 17 using System.Collections.Generic; 18 using System.Linq; 19 using System.Net; 20 using System.Runtime.CompilerServices; 21 using System.Text; 22 using System.Threading; 23 using System.Threading.Tasks; 24 using Agones.Model; 25 using JetBrains.Annotations; 26 using MiniJSON; 27 using UnityEngine; 28 using UnityEngine.Networking; 29 30 namespace Agones 31 { 32 /// <summary> 33 /// Agones Beta SDK for Unity. 34 /// </summary> 35 public class AgonesBetaSdk : AgonesSdk 36 { 37 #region AgonesRestClient Public Methods 38 39 /// <summary> 40 /// GetCounterCountAsync returns the Count for a Counter, given the Counter's key (name). 41 /// Always returns 0 if the key was not predefined in the GameServer resource on creation. 42 /// </summary> 43 /// <returns>The counter's count</returns> 44 public async Task<long> GetCounterCount(string key) 45 { 46 var result = await SendRequestAsync($"/v1beta1/counters/{key}", "{}", UnityWebRequest.kHttpVerbGET); 47 if (!result.ok) 48 { 49 return 0; 50 } 51 52 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 53 || !data.TryGetValue("count", out object countObject) 54 || countObject is not string countString 55 || !long.TryParse(countString, out long count)) 56 { 57 return 0; 58 } 59 60 return count; 61 } 62 63 private struct CounterUpdateRequest 64 { 65 public long countDiff; 66 } 67 68 /// <summary> 69 /// IncrementCounterAsync increases a counter by the given nonnegative integer amount. 70 /// Will execute the increment operation against the current CRD value. Will max at max(int64). 71 /// Throws error if the key was not predefined in the GameServer resource on creation. 72 /// Throws error if the count is at the current capacity (to the latest knowledge of the SDK), 73 /// and no increment will occur. 74 /// 75 /// Note: A potential race condition here is that if count values are set from both the SDK and 76 /// through the K8s API (Allocation or otherwise), since the SDK append operation back to the CRD 77 /// value is batched asynchronous any value incremented past the capacity will be silently truncated. 78 /// </summary> 79 /// <returns> 80 /// A task that represents the asynchronous operation and returns true if the request was successful. 81 /// </returns> 82 public async Task<bool> IncrementCounter(string key, long amount) 83 { 84 if (amount < 0) 85 { 86 throw new ArgumentOutOfRangeException($"CountIncrement amount must be a positive number, found {amount}"); 87 } 88 89 string json = JsonUtility.ToJson(new CounterUpdateRequest {countDiff = amount }); 90 return await SendRequestAsync($"/v1beta1/counters/{key}", json, "PATCH").ContinueWith(task => task.Result.ok); 91 } 92 93 /// <summary> 94 /// DecrementCounterAsync decreases the current count by the given nonnegative integer amount. 95 /// The Counter will not go below 0. Will execute the decrement operation against the current CRD value. 96 /// Throws error if the count is at 0 (to the latest knowledge of the SDK), and no decrement will occur. 97 /// </summary> 98 /// <returns> 99 /// A task that represents the asynchronous operation and returns true if the request was successful. 100 /// </returns> 101 public async Task<bool> DecrementCounter(string key, long amount) 102 { 103 if (amount < 0) 104 { 105 throw new ArgumentOutOfRangeException($"CountIncrement amount must be a positive number, found {amount}"); 106 } 107 108 string json = JsonUtility.ToJson(new CounterUpdateRequest {countDiff = amount * -1}); 109 return await SendRequestAsync($"/v1beta1/counters/{key}", json, "PATCH").ContinueWith(task => task.Result.ok); 110 } 111 112 private struct CounterSetRequest { 113 public long count; 114 } 115 116 /// <summary> 117 /// SetCounterCountAsync sets a count to the given value. Use with care, as this will 118 /// overwrite any previous invocations’ value. Cannot be greater than Capacity. 119 /// </summary> 120 /// <returns> 121 /// A task that represents the asynchronous operation and returns true if the request was successful. 122 /// </returns> 123 public async Task<bool> SetCounterCount(string key, long amount) 124 { 125 string json = JsonUtility.ToJson(new CounterSetRequest {count = amount}); 126 return await SendRequestAsync($"/v1beta1/counters/{key}", json, "PATCH").ContinueWith(task => task.Result.ok); 127 } 128 129 /// <summary> 130 /// GetCounterCapacityAsync returns the Capacity for a Counter, given the Counter's key (name). 131 /// Always returns 0 if the key was not predefined in the GameServer resource on creation. 132 /// </summary> 133 /// <returns>The Counter's capacity</returns> 134 public async Task<long> GetCounterCapacity(string key) 135 { 136 var result = await SendRequestAsync($"/v1beta1/counters/{key}", "{}", UnityWebRequest.kHttpVerbGET); 137 if (!result.ok) 138 { 139 return 0; 140 } 141 142 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 143 || !data.TryGetValue("capacity", out object capacityObject) 144 || capacityObject is not string capacityString 145 || !long.TryParse(capacityString, out long capacity)) 146 { 147 return 0; 148 } 149 150 return capacity; 151 } 152 153 private struct CounterSetCapacityRequest { 154 public long capacity; 155 } 156 157 /// <summary> 158 /// SetCounterCapacityAsync sets the capacity for the given Counter. 159 /// A capacity of 0 is no capacity. 160 /// </summary> 161 /// <returns> 162 /// A task that represents the asynchronous operation and returns true if the request was successful. 163 /// </returns> 164 public async Task<bool> SetCounterCapacity(string key, long amount) 165 { 166 string json = JsonUtility.ToJson(new CounterSetCapacityRequest {capacity = amount}); 167 return await SendRequestAsync($"/v1beta1/counters/{key}", json, "PATCH").ContinueWith(task => task.Result.ok); 168 } 169 170 /// <summary> 171 /// GetListCapacityAsync returns the Capacity for a List, given the List's key (name). 172 /// Always returns 0 if the key was not predefined in the GameServer resource on creation. 173 /// </summary> 174 /// <returns>The List's capacity</returns> 175 public async Task<long> GetListCapacity(string key) 176 { 177 var result = await SendRequestAsync($"/v1beta1/lists/{key}", "{}", UnityWebRequest.kHttpVerbGET); 178 if (!result.ok) 179 { 180 return 0; 181 } 182 183 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 184 || !data.TryGetValue("capacity", out object capacityObject) 185 || capacityObject is not string capacityString 186 || !long.TryParse(capacityString, out long capacity)) 187 { 188 return 0; 189 } 190 191 return capacity; 192 } 193 194 private struct ListSetCapacityRequest { 195 public long capacity; 196 } 197 198 /// <summary> 199 /// SetListCapacityAsync sets the capacity for a given list. Capacity must be between 0 and 1000. 200 /// Always returns false if the key was not predefined in the GameServer resource on creation. 201 /// </summary> 202 /// <returns> 203 /// A task that represents the asynchronous operation and returns true if the request was successful. 204 /// </returns> 205 public async Task<bool> SetListCapacity(string key, long amount) 206 { 207 string json = JsonUtility.ToJson(new ListSetCapacityRequest { 208 capacity = amount 209 }); 210 return await SendRequestAsync($"/v1beta1/lists/{key}", json, "PATCH").ContinueWith(task => task.Result.ok); 211 } 212 213 /// <summary> 214 /// ListContainsAsync returns if a string exists in a List's values list, given the List's key 215 /// and the string value. Search is case-sensitive. 216 /// Always returns false if the key was not predefined in the GameServer resource on creation. 217 /// </summary> 218 /// <returns>True if the value is found in the List</returns> 219 public async Task<bool> ListContains(string key, string value) 220 { 221 var result = await SendRequestAsync($"/v1beta1/lists/{key}", "{}", UnityWebRequest.kHttpVerbGET); 222 223 if (!result.ok) 224 { 225 return false; 226 } 227 228 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 229 || !data.TryGetValue("values", out object listObject) 230 || listObject is not List<object> list) 231 { 232 return false; 233 } 234 235 return list.Where(l => l is string).Select(l => l.ToString()).Contains(value); 236 } 237 238 /// <summary> 239 /// GetListLengthAsync returns the length of the Values list for a List, given the List's key. 240 /// Always returns 0 if the key was not predefined in the GameServer resource on creation. 241 /// </summary> 242 /// <returns>The length of List's values array</returns> 243 public async Task<int> GetListLength(string key) 244 { 245 var result = await SendRequestAsync($"/v1beta1/lists/{key}", "{}", UnityWebRequest.kHttpVerbGET); 246 247 if (!result.ok) 248 { 249 return 0; 250 } 251 252 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 253 || !data.TryGetValue("values", out object listObject) 254 || listObject is not List<object> list) 255 { 256 return 0; 257 } 258 259 return list.Count(); 260 } 261 262 /// <summary> 263 /// GetListValuesAsync returns the Values for a List, given the List's key (name). 264 /// Always returns an empty list if the key was not predefined in the GameServer resource on creation. 265 /// </summary> 266 /// <returns>The List's values array</returns> 267 public async Task<List<string>> GetListValues(string key) 268 { 269 var result = await SendRequestAsync($"/v1beta1/lists/{key}", "{}", UnityWebRequest.kHttpVerbGET); 270 271 if (!result.ok) 272 { 273 return new List<string>(); 274 } 275 276 if (Json.Deserialize(result.json) is not Dictionary<string, object> data 277 || !data.TryGetValue("values", out object listObject) 278 || listObject is not List<object> list) 279 { 280 return new List<string>(); 281 } 282 283 return list.Where(l => l is string).Select(l => l.ToString()).ToList(); 284 } 285 286 private struct ListUpdateValuesRequest 287 { 288 public string value; 289 } 290 291 /// <summary> 292 /// AppendListValueAsync appends a string to a List's values list, given the List's key (name) 293 /// and the string value. Throws error if the string already exists in the list. 294 /// Always returns false if the key was not predefined in the GameServer resource on creation. 295 /// </summary> 296 /// <returns> 297 /// A task that represents the asynchronous operation and returns true if the request was successful. 298 /// </returns> 299 public async Task<bool> AppendListValue(string key, string value) 300 { 301 string json = JsonUtility.ToJson(new ListUpdateValuesRequest {value = value}); 302 return await SendRequestAsync($"/v1beta1/lists/{key}:addValue", json, "POST").ContinueWith(task => task.Result.ok); 303 } 304 305 /// <summary> 306 /// DeleteListValueAsync removes a string from a List's values list, given the List's key 307 /// and the string value. Throws error if the string does not exist in the list. 308 /// Always returns false if the key was not predefined in the GameServer resource on creation. 309 /// </summary> 310 /// <returns> 311 /// A task that represents the asynchronous operation and returns true if the request was successful. 312 /// </returns> 313 public async Task<bool> DeleteListValue(string key, string value) 314 { 315 string json = JsonUtility.ToJson(new ListUpdateValuesRequest {value = value}); 316 return await SendRequestAsync($"/v1beta1/lists/{key}:removeValue", json, "POST").ContinueWith(task => task.Result.ok); 317 } 318 319 #endregion 320 321 } 322 }