using System; using System.Collections; using System.Collections.Generic; using System.Text; using Unity.UIWidgets.async2; using Unity.UIWidgets.engine2; using Unity.UIWidgets.foundation; using Unity.UIWidgets.ui; using UnityEngine.Networking; namespace Unity.UIWidgets.services { public abstract class AssetBundle { public abstract Future load(string key); public virtual Future loadString(string key, bool cache = true) { return load(key).then_(data => { if (data == null) throw new UIWidgetsError($"Unable to load asset: {key}"); if (data.Length < 10 * 1024) { return Encoding.UTF8.GetString(data); } return foundation_.compute(Encoding.UTF8.GetString, data, debugLabel: $"UTF8 decode for \"{key}\""); }); } public abstract Future loadStructuredData(string key, Func> parser); public virtual void evict(string key) { } public override string ToString() => $"{foundation_.describeIdentity(this)}()"; } public class NetworkAssetBundle : AssetBundle { public NetworkAssetBundle(Uri baseUrl, IDictionary headers = null) { _baseUrl = baseUrl; this.headers = headers; } public readonly IDictionary headers; readonly Uri _baseUrl; Uri _urlFromKey(string key) => new Uri(_baseUrl, key); IEnumerator _loadCoroutine(string key, Completer completer, Isolate isolate) { var url = _urlFromKey(key); using (var www = UnityWebRequest.Get(url)) { if (headers != null) { foreach (var header in headers) { www.SetRequestHeader(header.Key, header.Value); } } yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { completer.completeError(new UIWidgetsError(new List() { new ErrorSummary($"Unable to load asset: {key}"), new StringProperty("HTTP status code", www.error) })); yield break; } var data = www.downloadHandler.data; using (Isolate.getScope(isolate)) { completer.complete(data); } } } public override Future load(string key) { var completer = Completer.create(); var isolate = Isolate.current; var panel = UIWidgetsPanel.current; panel.StartCoroutine(_loadCoroutine(key, completer, isolate)); return completer.future.to(); } public override Future loadStructuredData(string key, Func> parser) { D.assert(key != null); D.assert(parser != null); return loadString(key).then_(value => parser(value)); } public override string ToString() => $"{foundation_.describeIdentity(this)}({_baseUrl})"; } public abstract class CachingAssetBundle : AssetBundle { readonly Dictionary> _stringCache = new Dictionary>(); readonly Dictionary _structuredDataCache = new Dictionary(); public override Future loadString(string key, bool cache = true) { if (cache) return _stringCache.putIfAbsent(key, () => base.loadString(key)); return base.loadString(key); } public override Future loadStructuredData(string key, Func> parser) { D.assert(key != null); D.assert(parser != null); if (_structuredDataCache.ContainsKey(key)) return _structuredDataCache[key].to(); Completer completer = null; Future result = null; loadString(key, cache: false).then_(value => parser(value)).then_((T value) => { result = new SynchronousFuture(value); _structuredDataCache[key] = result; if (completer != null) { // We already returned from the loadStructuredData function, which means // we are in the asynchronous mode. Pass the value to the completer. The // completer's future is what we returned. completer.complete(FutureOr.value(value)); } return FutureOr.nil; }); if (result != null) { // The code above ran synchronously, and came up with an answer. // Return the SynchronousFuture that we created above. return result; } // The code above hasn't yet run its "then" handler yet. Let's prepare a // completer for it to use when it does run. completer = Completer.create(); _structuredDataCache[key] = result = completer.future.to(); return result; } public override void evict(string key) { _stringCache.Remove(key); _structuredDataCache.Remove(key); } } public class PlatformAssetBundle : CachingAssetBundle { public override Future load(string key) { byte[] encoded = Encoding.UTF8.GetBytes(key); return ServicesBinding.instance.defaultBinaryMessenger.send( "uiwidgets/assets", encoded).then_(asset => { if (asset == null) throw new UIWidgetsError($"Unable to load asset: {key}"); return asset; }); } } public static partial class services_ { static AssetBundle _initRootBundle() { return new PlatformAssetBundle(); } public static readonly AssetBundle rootBundle = _initRootBundle(); } }