您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

164 行
5.8 KiB

using System;
using System.Text;
using Unity.Services.Core.Networking;
using UnityEditor;
namespace Unity.Services.Core.Editor
{
/// <summary>
/// Fetches and parses JSON config object or returns a newly constructed object on failure.
/// If the config is successfully loaded it will be cached in SessionState
/// </summary>
/// <typeparam name="T">The type of object to deserialize</typeparam>
public class EditorGameServiceRemoteConfiguration<T> where T : new()
{
string m_ConfigUrl;
string m_SessionStateKey;
T m_CachedConfiguration;
bool m_IsConfigurationLoaded;
IAsyncOperation<string> m_FetchOperation;
IHttpClient m_HttpClient;
internal virtual IHttpClient GetHttpClient()
{
if (m_HttpClient == null)
m_HttpClient = new UnityWebRequestClient();
return m_HttpClient;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="configUrl">The url to use to fetch the config</param>
public EditorGameServiceRemoteConfiguration(string configUrl)
{
m_ConfigUrl = configUrl;
m_SessionStateKey = $"EditorGameServiceConfig::{configUrl.GetHashCode()}";
}
/// <summary>
/// Retrieves the configuration from the cache or server and provides it to the caller.
/// Makes an effort to return the cached object first, then checks SessionState, and finally fetches from
/// the server. If all else fails returns a default constructed object.
/// NB: Newly constructed instances of this class will use SessionState if it's available before calling
/// out to the server.
/// </summary>
/// <param name="onGetConfigurationCompleted">
/// Callback action to retrieve the configuration.
/// </param>>
public void GetConfiguration(Action<T> onGetConfigurationCompleted)
{
var configAsyncOp = GetConfiguration();
configAsyncOp.Completed += asyncGetConfigurationResponse
=> onGetConfigurationCompleted?.Invoke(asyncGetConfigurationResponse.Result);
}
internal IAsyncOperation<T> GetConfiguration()
{
var operation = new AsyncOperation<T>();
operation.SetInProgress();
if (!m_IsConfigurationLoaded)
{
m_IsConfigurationLoaded = JsonHelper.TryJsonDeserialize(
SessionState.GetString(m_SessionStateKey, null), ref m_CachedConfiguration);
}
if (m_IsConfigurationLoaded)
{
operation.Succeed(m_CachedConfiguration);
}
else
{
if (m_FetchOperation == null || m_FetchOperation.IsDone)
m_FetchOperation = FetchConfigurationFromCdn();
m_FetchOperation.Completed += OnConfigurationFetched;
}
return operation;
void OnConfigurationFetched(IAsyncOperation<string> fetchOperation)
{
if (!m_IsConfigurationLoaded)
{
var json = fetchOperation.Result;
if (JsonHelper.TryJsonDeserialize(json, ref m_CachedConfiguration))
{
SessionState.SetString(m_SessionStateKey, json);
m_IsConfigurationLoaded = true;
}
else
m_CachedConfiguration = new T();
}
operation.Succeed(m_CachedConfiguration);
}
}
/// <summary>
/// Erases the configuration from SessionState and from memory. Will force the configuration to be fetched
/// from the server the next time it is requested.
/// </summary>
public void ClearCache()
{
SessionState.EraseString(m_SessionStateKey);
m_IsConfigurationLoaded = false;
m_CachedConfiguration = default;
}
IAsyncOperation<string> FetchConfigurationFromCdn()
{
var operation = new AsyncOperation<string>();
operation.SetInProgress();
var configRequest = new HttpRequest()
.AsGet()
.SetUrl(m_ConfigUrl);
configRequest.Options.RedirectLimit = 5;
GetHttpClient().Send(configRequest).Completed += OnRequestCompleted;
return operation;
void OnRequestCompleted(IAsyncOperation<ReadOnlyHttpResponse> configFetchOperation)
{
var config = configFetchOperation.Status == AsyncOperationStatus.Succeeded
? SafeGetUTF8StringFromBytes(configFetchOperation.Result.Data)
: null;
operation.Succeed(config);
}
}
static string SafeGetUTF8StringFromBytes(byte[] bytes)
{
if (bytes != null)
{
try
{
return Encoding.UTF8.GetString(bytes);
}
catch (Exception)
{
// ignored, String decoding failed, we'll return null
}
}
return null;
}
}
/// <summary>
/// Helper class for managing CDN based endpoint configurations
/// </summary>
/// <typeparam name="T">The object to populate with fields from the CDN JSON file</typeparam>
public class CdnConfiguredEndpoint<T> : EditorGameServiceRemoteConfiguration<T> where T : new()
{
const string k_CdnUrl = "https://public-cdn.cloud.unity3d.com/config/proxy/production";
/// <summary>
/// Initializes a new instance of the <see cref="CdnConfiguredEndpoint{T}" /> class.
/// </summary>
public CdnConfiguredEndpoint()
: base(k_CdnUrl) {}
}
}