| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- // Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using UnityEngine;
- using System.IO;
- using UnityEngine.Profiling;
- using System.Collections;
- using Stopwatch = System.Diagnostics.Stopwatch;
- using UnityEngine.Networking;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
- namespace Better.StreamingAssets
- {
- public class BSA_TestSceneGUI : MonoBehaviour
- {
- public UnityEngine.UI.Text InProgressText;
- public string EditorApkPath = "BetterStreamingAssetsTest.apk";
- public int RepetitionCount = 10;
- public bool LogToFile = false;
- private class CoroutineHost : MonoBehaviour { }
- private class TestInfo
- {
- public ReadMode readMode;
- public TestType testType;
- public string path;
- public int attempts;
- public Exception error;
- public TimeSpan duration;
- public long bytesRead;
- public long memoryPeak;
- public long maxMemoryPeak;
- }
- private delegate void TestResultDelegate(TimeSpan avgDuration, long avgBytesRead, long avgMemoryPeak, long maxMemoryPeak, string[] assetNames);
- [Flags]
- private enum ReadMode
- {
- BSA = 1 << 0,
- WWW = 1 << 1,
- Direct = 1 << 5,
- UnityWebRequest = 1 << 6
- }
- [Flags]
- private enum TestType
- {
- CheckIfExists = 1 << 0,
- LoadBytes = 1 << 1,
- }
- private string m_status = string.Empty;
- private TestType m_testModes = TestType.CheckIfExists;
- private ReadMode m_readModes = ReadMode.WWW;
- private CoroutineHost coroutineHost;
- private Vector2 m_assetsScroll;
- private Vector2 m_resultsScroll;
- private string[] m_allStreamingAssets = new string[0];
- private List<TestInfo> m_results = new List<TestInfo>();
- private HashSet<string> m_selectedPaths = new HashSet<string>();
- void OnEnable()
- {
- InProgressText.enabled = false;
- }
- void OnDisable()
- {
- InProgressText.enabled = true;
- }
- void OnGUI()
- {
- using (new GUILayout.AreaScope(new Rect(0, 0, Screen.width, Screen.height)))
- {
- if (string.IsNullOrEmpty(BetterStreamingAssets.Root))
- {
- #if UNITY_EDITOR
- using (new GUILayout.HorizontalScope())
- {
- GUILayout.Label("APK path");
- EditorApkPath = GUILayout.TextField(EditorApkPath);
- }
- if (GUILayout.Button("Use APK (Android like)"))
- {
- BetterStreamingAssets.InitializeWithExternalApk(EditorApkPath);
- Initialize();
- }
- if (GUILayout.Button("Use Assets/StreamingAssets directory (iOS/Standalone like)"))
- {
- BetterStreamingAssets.Initialize();
- Initialize();
- }
- return;
- #else
- BetterStreamingAssets.Initialize();
- Initialize();
- #endif
- }
- if (m_allStreamingAssets.Length == 0)
- {
- GUILayout.Label("No streaming assets found in " + BetterStreamingAssets.Root);
- return;
- }
- GUILayout.Label("Using " + BetterStreamingAssets.Root);
- GUILayout.Label("Discovered streaming assets:");
- using (var scope = new GUILayout.ScrollViewScope(m_assetsScroll, GUILayout.MaxHeight(300)))
- {
- m_assetsScroll = scope.scrollPosition;
- foreach (var path in m_allStreamingAssets)
- {
- var wasSelected = m_selectedPaths.Contains(path);
- if (GUILayout.Toggle(wasSelected, path))
- {
- if (!wasSelected)
- m_selectedPaths.Add(path);
- }
- else
- {
- if (wasSelected)
- m_selectedPaths.Remove(path);
- }
- }
- }
- const float VerticalSpace = 10.0f;
- GUILayout.Space(VerticalSpace);
- using (new GUILayout.HorizontalScope())
- {
- GUILayout.Label("Repetition count: " + RepetitionCount, GUILayout.Width(150.0f));
- RepetitionCount = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)RepetitionCount, 1.0f, 20.0f));
- }
- LogToFile = GUILayout.Toggle(LogToFile, "Log results to file");
- GUILayout.Space(VerticalSpace);
- using (new GUILayout.HorizontalScope())
- {
- GUILayout.Label("Test modes: ", GUILayout.Width(150.0f));
- DoTestTypeToggle(TestType.CheckIfExists);
- DoTestTypeToggle(TestType.LoadBytes);
- }
- GUILayout.Space(VerticalSpace);
- using (new GUILayout.HorizontalScope())
- {
- GUILayout.Label("Read modes: ", GUILayout.Width(150.0f));
- DoReadModeToggle(ReadMode.BSA);
- DoReadModeToggle(ReadMode.WWW);
- DoReadModeToggle(ReadMode.UnityWebRequest);
- #if !UNITY_ANDROID || UNITY_EDITOR
- DoReadModeToggle(ReadMode.Direct);
- #endif
- }
- GUI.enabled = m_selectedPaths.Count > 0;
- if (GUILayout.Button("Test Selected Paths (" + m_selectedPaths.Count + ")"))
- {
- coroutineHost.StartCoroutine(TestAllCoroutine(m_selectedPaths.ToArray(), RepetitionCount, m_readModes, m_testModes, m_results));
- }
- GUI.enabled = true;
- GUILayout.Box(m_status);
- using (var scroll = new GUILayout.ScrollViewScope(m_resultsScroll))
- {
- m_resultsScroll = scroll.scrollPosition;
- GUI.skin.label.alignment = TextAnchor.MiddleLeft;
- GUI.skin.label.clipping = TextClipping.Clip;
- foreach (var result in m_results)
- {
- using (var layout = new GUILayout.HorizontalScope())
- {
- GUILayout.Label(result.path, GUILayout.Width(200));
- GUILayout.Label(result.readMode.ToString(), GUILayout.Width(160));
- GUILayout.Label(result.testType.ToString(), GUILayout.Width(160));
- if (result.error != null)
- {
- GUILayout.Label(result.error.GetType().ToString());
- }
- else
- {
- GUILayout.Label(result.duration.ToString());
- GUILayout.Label((result.memoryPeak / 1024.0 / 1024.0).ToString("F2") + " MB");
- }
- }
- }
- GUI.skin.label.clipping = TextClipping.Overflow;
- GUI.skin.label.alignment = TextAnchor.MiddleCenter;
- }
- }
- }
- private string StreamingAssetsPath
- {
- get
- {
- #if UNITY_EDITOR
- if (BetterStreamingAssets.Root == EditorApkPath)
- {
- return "jar:" + new Uri(EditorApkPath).AbsoluteUri + "!/assets";
- }
- #endif
- return Application.streamingAssetsPath;
- }
- }
- private void Initialize()
- {
- m_allStreamingAssets = BetterStreamingAssets.GetFiles("/", "*", SearchOption.AllDirectories);
- coroutineHost = gameObject.AddComponent<CoroutineHost>();
- // allocate something for mono heap to grow
- var bytes = new byte[200 * 1024 * 1024];
- Debug.LogFormat("Allocated {0}, mono heap size: {1}", bytes.Length, Profiler.GetMonoHeapSizeLong());
- }
- private void DoTestTypeToggle(TestType testMode)
- {
- bool wasSet = (m_testModes & testMode) == testMode;
- if (GUILayout.Toggle(wasSet, testMode.ToString()))
- {
- m_testModes |= testMode;
- }
- else if (wasSet)
- {
- if (m_testModes != testMode)
- m_testModes &= ~testMode;
- }
- }
- private void DoReadModeToggle(ReadMode readMode)
- {
- bool wasSet = (m_readModes & readMode) == readMode;
- if (GUILayout.Toggle(wasSet, readMode.ToString()))
- {
- m_readModes |= readMode;
- }
- else if (wasSet)
- {
- if (m_readModes != readMode)
- m_readModes &= ~readMode;
- }
- }
- private IEnumerator TestAllCoroutine(IEnumerable<string> paths, int attempts, ReadMode readModes, TestType testTypes, List<TestInfo> results)
- {
- LogWorkProgress("starting...");
- string logPath = Path.Combine(Application.persistentDataPath, "BSA_test_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + "_.csv");
- enabled = false;
- results.Clear();
- try
- {
- foreach (var path in paths)
- {
- foreach (ReadMode readMode in Enum.GetValues(typeof(ReadMode)))
- {
- if ((readMode & readModes) != readMode)
- continue;
- foreach (TestType testType in Enum.GetValues(typeof(TestType)))
- {
- if ((testType & testTypes) != testType)
- continue;
- var testInfo = new TestInfo()
- {
- readMode = readMode,
- testType = testType,
- path = path,
- attempts = attempts,
- };
- yield return coroutineHost.StartCoroutine(ErrorCatchingCoroutine(TestHarness(readMode, path, testType, attempts,
- (duration, bytes, memory, maxMemory, names) =>
- {
- testInfo.duration = duration;
- testInfo.bytesRead = bytes;
- testInfo.memoryPeak = memory;
- testInfo.maxMemoryPeak = maxMemory;
- }),
- ex =>
- {
- testInfo.error = ex;
- }
- ));
- results.Add(testInfo);
- }
- }
- }
- }
- finally
- {
- enabled = true;
- if (LogToFile)
- {
- using (var writer = File.CreateText(logPath))
- {
- foreach (var result in results)
- {
- string errorMessage = string.Empty;
- if (result.error != null)
- errorMessage = result.error.ToString().Replace(Environment.NewLine, ";");
- writer.WriteLine("\"{0}\"\t{1}\t{2}\t{3}\t{4}\t\"{5}\"", result.path, result.readMode, result.testType, result.duration, result.memoryPeak, errorMessage);
- }
- }
- LogWorkProgress("Logged at: " + logPath);
- }
- }
- }
- private void LogWorkProgress(string status)
- {
- Debug.Log("WORK PROGRESS: " + status);
- if (string.IsNullOrEmpty(m_status))
- m_status = status;
- else
- m_status += "\n" + status;
- }
- private IEnumerator ErrorCatchingCoroutine(IEnumerator inner, Action<System.Exception> onError)
- {
- m_status = string.Empty;
- for (; ; )
- {
- bool next = false;
- try
- {
- next = inner.MoveNext();
- }
- catch (System.Exception ex)
- {
- onError(ex);
- break;
- }
- if (!next)
- break;
- yield return inner.Current;
- }
- }
- private IEnumerator TestHarness(ReadMode readMode, string path, TestType testType, int attempts, TestResultDelegate callback)
- {
- var stopwatch = new Stopwatch();
- string[] assetNames = null;
- var streamingAssetsUrl = Path.Combine(StreamingAssetsPath, path.TrimStart('/')).Replace('\\', '/');
- long bytesRead = 0;
- long maxMemoryPeak = 0;
- long totalMemoryPeaks = 0;
- for (int i = 0; i < attempts; ++i)
- {
- IDisposable toDispose = null;
- yield return Resources.UnloadUnusedAssets();
- GC.Collect();
- GC.WaitForPendingFinalizers();
- yield return null;
- var memoryUnityBefore = Profiler.GetTotalAllocatedMemoryLong();
- //var memoryMonoBefore = Profiler.GetMonoUsedSizeLong();
- stopwatch.Start();
- if (readMode == ReadMode.WWW)
- {
- #pragma warning disable 0618 // Type or member is obsolete
- var www = new WWW(streamingAssetsUrl);
- #pragma warning restore 0618 // Type or member is obsolete
- toDispose = www;
- {
- yield return www;
- Profiler.BeginSample(testType.ToString());
- switch (testType)
- {
- case TestType.CheckIfExists:
- if (!string.IsNullOrEmpty(www.error))
- throw new System.Exception(www.error);
- break;
- case TestType.LoadBytes:
- bytesRead += www.bytes.Length;
- break;
- default:
- throw new NotSupportedException();
- }
- Profiler.EndSample();
- }
- }
- else if (readMode == ReadMode.BSA)
- {
- Profiler.BeginSample(testType.ToString());
- switch (testType)
- {
- case TestType.CheckIfExists:
- if (!BetterStreamingAssets.FileExists(path))
- throw new System.InvalidOperationException();
- break;
- case TestType.LoadBytes:
- bytesRead += BetterStreamingAssets.ReadAllBytes(path).Length;
- break;
- }
- Profiler.EndSample();
- }
- else if (readMode == ReadMode.Direct)
- {
- var p = streamingAssetsUrl;
- Profiler.BeginSample(testType.ToString());
- switch (testType)
- {
- case TestType.CheckIfExists:
- if (!File.Exists(p))
- throw new System.InvalidOperationException();
- break;
- case TestType.LoadBytes:
- bytesRead += File.ReadAllBytes(p).Length;
- break;
- }
- Profiler.EndSample();
- }
- else if (readMode == ReadMode.UnityWebRequest)
- {
- var www = UnityEngine.Networking.UnityWebRequest.Get(streamingAssetsUrl);
- toDispose = www;
- #pragma warning disable 0618 // Type or member is obsolete
- yield return www.Send();
- #pragma warning restore 0618 // Type or member is obsolete
- Profiler.BeginSample(testType.ToString());
- switch (testType)
- {
- case TestType.CheckIfExists:
- if (!string.IsNullOrEmpty(www.error))
- throw new System.Exception(www.error);
- break;
- case TestType.LoadBytes:
- bytesRead += (int)www.downloadedBytes;
- break;
- default:
- throw new NotSupportedException();
- }
- Profiler.EndSample();
- }
- stopwatch.Stop();
- var memoryPeak = Math.Max(0, Profiler.GetTotalAllocatedMemoryLong() - memoryUnityBefore);
- // + Math.Max(0, Profiler.GetMonoUsedSizeLong() - memoryMonoBefore);
- maxMemoryPeak = System.Math.Max(memoryPeak, maxMemoryPeak);
- totalMemoryPeaks += memoryPeak;
- yield return null;
- if (toDispose != null)
- toDispose.Dispose();
- yield return null;
- }
- yield return Resources.UnloadUnusedAssets();
- GC.Collect();
- GC.WaitForPendingFinalizers();
- yield return null;
- callback(new TimeSpan(stopwatch.ElapsedTicks / attempts), bytesRead / attempts, totalMemoryPeaks / attempts, maxMemoryPeak, assetNames);
- }
- }
- }
|