import from UniRx and some modified.
parent
d5dab7fd1a
commit
5aaeb13c5d
|
@ -1,21 +1,143 @@
|
|||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
/[Bb]uild/
|
||||
# Unity
|
||||
|
||||
# Autogenerated VS/MD solution and project files
|
||||
*.csproj
|
||||
*.unityproj
|
||||
*.sln
|
||||
*.suo
|
||||
*.tmp
|
||||
*.user
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.booproj
|
||||
*.suo
|
||||
*.userprefs
|
||||
*.vsmdi
|
||||
*.testsettings
|
||||
*/bin
|
||||
*/obj
|
||||
*/publish
|
||||
$tf
|
||||
TestResults
|
||||
!*.sln
|
||||
!*.csproj
|
||||
!*/*.csproj
|
||||
[Ll]ibrary/
|
||||
[Tt]emp/
|
||||
[Oo]bj/
|
||||
|
||||
# Unity3D generated meta files
|
||||
*.pidb.meta
|
||||
# VS2013
|
||||
|
||||
# Unity3D Generated File On Crash Reports
|
||||
sysinfo.txt
|
||||
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# mstest test results
|
||||
TestResults
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
# *.meta # already ignored in Unity section
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
||||
# NuGet Packages Directory
|
||||
packages
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
[Bb]in
|
||||
[Oo]bj
|
||||
sql
|
||||
TestResults
|
||||
[Tt]est[Rr]esult*
|
||||
*.Cache
|
||||
ClientBin
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*.dbmdl
|
||||
Generated_Code #added for RIA/Silverlight projects
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
Assets/WSATestCertificate.pfx
|
||||
.vs/
|
||||
|
||||
Assembly-CSharp\.csproj
|
||||
|
||||
UniRx\.Async\.csproj
|
||||
|
||||
UniRx\.Async\.Editor\.csproj
|
||||
|
||||
UniRx\.Async\.Tests\.csproj
|
||||
|
||||
UniTask\.sln
|
||||
|
||||
RuntimeUnitTestToolkit\.csproj
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 894f21dfce4e82343a91661e1ec1a455
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 959c1472a5d812843bedf9341e87af3b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,133 @@
|
|||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RuntimeUnitTestToolkit.Editor
|
||||
{
|
||||
// functional declarative construction like flutter.
|
||||
|
||||
internal interface IBuilder
|
||||
{
|
||||
GameObject GameObject { get; }
|
||||
T GetComponent<T>();
|
||||
}
|
||||
|
||||
internal class Builder<T> : IBuilder
|
||||
where T : Component
|
||||
{
|
||||
public T Component1 { get; private set; }
|
||||
public GameObject GameObject { get; private set; }
|
||||
|
||||
public Transform Transform { get { return GameObject.transform; } }
|
||||
public RectTransform RectTransform { get { return GameObject.GetComponent<RectTransform>(); } }
|
||||
|
||||
public Action<GameObject> SetTarget
|
||||
{
|
||||
set
|
||||
{
|
||||
value(this.GameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IBuilder Child
|
||||
{
|
||||
set
|
||||
{
|
||||
value.GameObject.transform.SetParent(GameObject.transform);
|
||||
}
|
||||
}
|
||||
|
||||
public IBuilder[] Children
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var item in value)
|
||||
{
|
||||
item.GameObject.transform.SetParent(GameObject.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Builder(string name)
|
||||
{
|
||||
this.GameObject = new GameObject(name);
|
||||
this.Component1 = GameObject.AddComponent<T>();
|
||||
}
|
||||
|
||||
public Builder(string name, out T referenceSelf) // out primary reference.
|
||||
{
|
||||
this.GameObject = new GameObject(name);
|
||||
this.Component1 = GameObject.AddComponent<T>();
|
||||
referenceSelf = this.Component1;
|
||||
}
|
||||
|
||||
public TComponent GetComponent<TComponent>()
|
||||
{
|
||||
return this.GameObject.GetComponent<TComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Builder<T1, T2> : Builder<T1>
|
||||
where T1 : Component
|
||||
where T2 : Component
|
||||
{
|
||||
public T2 Component2 { get; private set; }
|
||||
|
||||
public Builder(string name)
|
||||
: base(name)
|
||||
{
|
||||
this.Component2 = GameObject.AddComponent<T2>();
|
||||
}
|
||||
|
||||
public Builder(string name, out T1 referenceSelf)
|
||||
: base(name, out referenceSelf)
|
||||
{
|
||||
this.Component2 = GameObject.AddComponent<T2>();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Builder<T1, T2, T3> : Builder<T1, T2>
|
||||
where T1 : Component
|
||||
where T2 : Component
|
||||
where T3 : Component
|
||||
{
|
||||
public T3 Component3 { get; private set; }
|
||||
|
||||
public Builder(string name)
|
||||
: base(name)
|
||||
{
|
||||
this.Component3 = GameObject.AddComponent<T3>();
|
||||
}
|
||||
|
||||
public Builder(string name, out T1 referenceSelf)
|
||||
: base(name, out referenceSelf)
|
||||
{
|
||||
this.Component3 = GameObject.AddComponent<T3>();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Builder<T1, T2, T3, T4> : Builder<T1, T2, T3>
|
||||
where T1 : Component
|
||||
where T2 : Component
|
||||
where T3 : Component
|
||||
where T4 : Component
|
||||
{
|
||||
public T4 Component4 { get; private set; }
|
||||
|
||||
public Builder(string name)
|
||||
: base(name)
|
||||
{
|
||||
this.Component4 = GameObject.AddComponent<T4>();
|
||||
}
|
||||
|
||||
public Builder(string name, out T1 referenceSelf)
|
||||
: base(name, out referenceSelf)
|
||||
{
|
||||
this.Component4 = GameObject.AddComponent<T4>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8760bbbab905a534eb6fb7b61b736926
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,341 @@
|
|||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
|
||||
// Settings MenuItems.
|
||||
|
||||
public static partial class UnitTestBuilder
|
||||
{
|
||||
[MenuItem("Test/Settings/ScriptBackend/Mono", validate = true, priority = 1)]
|
||||
static bool ValidateScriptBackendMono()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/ScriptBackend/Mono", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.Mono2x);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/ScriptBackend/Mono", validate = false, priority = 1)]
|
||||
static void ScriptBackendMono()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentScriptBackend = false;
|
||||
settings.ScriptBackend = ScriptingImplementation.Mono2x;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = true, priority = 2)]
|
||||
static bool ValidateScriptBackendIL2CPP()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/ScriptBackend/IL2CPP", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.IL2CPP);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = false, priority = 2)]
|
||||
static void ScriptBackendIL2CPP()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentScriptBackend = false;
|
||||
settings.ScriptBackend = ScriptingImplementation.IL2CPP;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/AutoRunPlayer", validate = true, priority = 3)]
|
||||
static bool ValidateAutoRun()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/AutoRunPlayer", LoadOrGetDefaultSettings().AutoRunPlayer);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/AutoRunPlayer", validate = false, priority = 3)]
|
||||
static void AutoRun()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.AutoRunPlayer = !settings.AutoRunPlayer;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/Headless", validate = true, priority = 4)]
|
||||
static bool ValidateHeadless()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/Headless", LoadOrGetDefaultSettings().Headless);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/Headless", validate = false, priority = 4)]
|
||||
static void Headless()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.Headless = !settings.Headless;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/DisableAutoClose", validate = true, priority = 5)]
|
||||
static bool ValidateDisableAutoClose()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/DisableAutoClose", LoadOrGetDefaultSettings().DisableAutoClose);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/DisableAutoClose", validate = false, priority = 5)]
|
||||
static void DisableAutoClose()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.DisableAutoClose = !settings.DisableAutoClose;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
// generated
|
||||
|
||||
/*
|
||||
*
|
||||
void Main()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var p = 1;
|
||||
foreach (var target in Enum.GetNames(typeof(BuildTarget)))
|
||||
{
|
||||
var path = $"Test/Settings/BuildTarget/{target}";
|
||||
var priority = p++;
|
||||
|
||||
var template = $@"
|
||||
[MenuItem(""{path}"", validate = true, priority = {priority})]
|
||||
static bool ValidateBuildTarget{target}()
|
||||
{{
|
||||
Menu.SetChecked(""{path}"", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.{target});
|
||||
return true;
|
||||
}}
|
||||
|
||||
[MenuItem(""{path}"", validate = false, priority = {priority})]
|
||||
static void BuildTarget{target}()
|
||||
{{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.{target};
|
||||
SaveSettings(settings);
|
||||
}}";
|
||||
|
||||
sb.AppendLine(template);
|
||||
}
|
||||
|
||||
sb.ToString().Dump();
|
||||
}
|
||||
|
||||
public enum BuildTarget
|
||||
{
|
||||
StandaloneWindows,
|
||||
StandaloneWindows64,
|
||||
StandaloneLinux,
|
||||
StandaloneLinux64,
|
||||
StandaloneOSX,
|
||||
WebGL,
|
||||
iOS,
|
||||
Android,
|
||||
WSAPlayer,
|
||||
PS4,
|
||||
XboxOne,
|
||||
Switch,
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = true, priority = 1)]
|
||||
static bool ValidateBuildTargetStandaloneWindows()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = false, priority = 1)]
|
||||
static void BuildTargetStandaloneWindows()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.StandaloneWindows;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = true, priority = 2)]
|
||||
static bool ValidateBuildTargetStandaloneWindows64()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows64);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = false, priority = 2)]
|
||||
static void BuildTargetStandaloneWindows64()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.StandaloneWindows64;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = true, priority = 3)]
|
||||
static bool ValidateBuildTargetStandaloneLinux()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = false, priority = 3)]
|
||||
static void BuildTargetStandaloneLinux()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.StandaloneLinux;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = true, priority = 4)]
|
||||
static bool ValidateBuildTargetStandaloneLinux64()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux64);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = false, priority = 4)]
|
||||
static void BuildTargetStandaloneLinux64()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.StandaloneLinux64;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = true, priority = 5)]
|
||||
static bool ValidateBuildTargetStandaloneOSX()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/StandaloneOSX", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneOSX);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = false, priority = 5)]
|
||||
static void BuildTargetStandaloneOSX()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.StandaloneOSX;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/WebGL", validate = true, priority = 6)]
|
||||
static bool ValidateBuildTargetWebGL()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/WebGL", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WebGL);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/WebGL", validate = false, priority = 6)]
|
||||
static void BuildTargetWebGL()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.WebGL;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/iOS", validate = true, priority = 7)]
|
||||
static bool ValidateBuildTargetiOS()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/iOS", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.iOS);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/iOS", validate = false, priority = 7)]
|
||||
static void BuildTargetiOS()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.iOS;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/Android", validate = true, priority = 8)]
|
||||
static bool ValidateBuildTargetAndroid()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/Android", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Android);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/Android", validate = false, priority = 8)]
|
||||
static void BuildTargetAndroid()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.Android;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = true, priority = 9)]
|
||||
static bool ValidateBuildTargetWSAPlayer()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/WSAPlayer", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WSAPlayer);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = false, priority = 9)]
|
||||
static void BuildTargetWSAPlayer()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.WSAPlayer;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/PS4", validate = true, priority = 10)]
|
||||
static bool ValidateBuildTargetPS4()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/PS4", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.PS4);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/PS4", validate = false, priority = 10)]
|
||||
static void BuildTargetPS4()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.PS4;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/XboxOne", validate = true, priority = 11)]
|
||||
static bool ValidateBuildTargetXboxOne()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/XboxOne", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.XboxOne);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/XboxOne", validate = false, priority = 11)]
|
||||
static void BuildTargetXboxOne()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.XboxOne;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/Switch", validate = true, priority = 12)]
|
||||
static bool ValidateBuildTargetSwitch()
|
||||
{
|
||||
Menu.SetChecked("Test/Settings/BuildTarget/Switch", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Switch);
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Test/Settings/BuildTarget/Switch", validate = false, priority = 12)]
|
||||
static void BuildTargetSwitch()
|
||||
{
|
||||
var settings = LoadOrGetDefaultSettings();
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = BuildTarget.Switch;
|
||||
SaveSettings(settings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 12bdad0556e999f4aa82da29415d361f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,515 @@
|
|||
#if UNITY_EDITOR
|
||||
|
||||
using RuntimeUnitTestToolkit;
|
||||
using RuntimeUnitTestToolkit.Editor;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
internal class RuntimeUnitTestSettings
|
||||
{
|
||||
public ScriptingImplementation ScriptBackend;
|
||||
public bool UseCurrentScriptBackend;
|
||||
public BuildTarget BuildTarget;
|
||||
public bool UseCurrentBuildTarget;
|
||||
|
||||
public bool Headless;
|
||||
public bool AutoRunPlayer;
|
||||
public bool DisableAutoClose;
|
||||
|
||||
public RuntimeUnitTestSettings()
|
||||
{
|
||||
UseCurrentBuildTarget = true;
|
||||
UseCurrentScriptBackend = true;
|
||||
Headless = false;
|
||||
AutoRunPlayer = true;
|
||||
DisableAutoClose = false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ScriptBackend} {BuildTarget} Headless:{Headless} AutoRunPlayer:{AutoRunPlayer} DisableAutoClose:{DisableAutoClose}";
|
||||
}
|
||||
}
|
||||
|
||||
// no namespace(because invoke from commandline)
|
||||
public static partial class UnitTestBuilder
|
||||
{
|
||||
const string SettingsKeyBase = "RuntimeUnitTest.Settings.";
|
||||
|
||||
[MenuItem("Test/BuildUnitTest")]
|
||||
public static void BuildUnitTest()
|
||||
{
|
||||
var settings = new RuntimeUnitTestSettings(); // default
|
||||
|
||||
string buildPath = null;
|
||||
|
||||
if (Application.isBatchMode) // from commandline
|
||||
{
|
||||
settings.AutoRunPlayer = false;
|
||||
settings.DisableAutoClose = false;
|
||||
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
for (int i = 0; i < cmdArgs.Length; i++)
|
||||
{
|
||||
if (string.Equals(cmdArgs[i].Trim('-', '/'), "ScriptBackend", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
settings.UseCurrentScriptBackend = false;
|
||||
var str = cmdArgs[++i];
|
||||
if (str.StartsWith("mono", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
settings.ScriptBackend = ScriptingImplementation.Mono2x;
|
||||
}
|
||||
else if (str.StartsWith("IL2CPP", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
settings.ScriptBackend = ScriptingImplementation.IL2CPP;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.ScriptBackend = (ScriptingImplementation)Enum.Parse(typeof(ScriptingImplementation), str, true);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "BuildTarget", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
settings.UseCurrentBuildTarget = false;
|
||||
settings.BuildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), cmdArgs[++i], true);
|
||||
}
|
||||
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "Headless", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
settings.Headless = true;
|
||||
}
|
||||
else if (string.Equals(cmdArgs[i].Trim('-', '/'), "buildPath", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
buildPath = cmdArgs[++i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = SettingsKeyBase + Application.productName;
|
||||
var settingsValue = EditorPrefs.GetString(key, null);
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(settingsValue))
|
||||
{
|
||||
settings = JsonUtility.FromJson<RuntimeUnitTestSettings>(settingsValue);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings");
|
||||
EditorPrefs.SetString(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.UseCurrentBuildTarget)
|
||||
{
|
||||
settings.BuildTarget = EditorUserBuildSettings.activeBuildTarget;
|
||||
}
|
||||
if (settings.UseCurrentScriptBackend)
|
||||
{
|
||||
settings.ScriptBackend = PlayerSettings.GetScriptingBackend(ToBuildTargetGroup(settings.BuildTarget));
|
||||
}
|
||||
|
||||
if (buildPath == null)
|
||||
{
|
||||
buildPath = $"bin/UnitTest/{settings.BuildTarget}_{settings.ScriptBackend}/test" + (IsWindows(settings.BuildTarget) ? ".exe" : "");
|
||||
}
|
||||
|
||||
var originalScene = SceneManager.GetActiveScene().path;
|
||||
|
||||
BuildUnitTest(buildPath, settings.ScriptBackend, settings.BuildTarget, settings.Headless, settings.AutoRunPlayer, settings.DisableAutoClose);
|
||||
|
||||
// reopen original scene
|
||||
if (!string.IsNullOrWhiteSpace(originalScene))
|
||||
{
|
||||
EditorSceneManager.OpenScene(originalScene, OpenSceneMode.Single);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
|
||||
}
|
||||
}
|
||||
|
||||
static RuntimeUnitTestSettings LoadOrGetDefaultSettings()
|
||||
{
|
||||
var key = SettingsKeyBase + Application.productName;
|
||||
|
||||
var settingsValue = EditorPrefs.GetString(key, null);
|
||||
RuntimeUnitTestSettings settings = null;
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(settingsValue))
|
||||
{
|
||||
settings = JsonUtility.FromJson<RuntimeUnitTestSettings>(settingsValue);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings");
|
||||
EditorPrefs.SetString(key, null);
|
||||
settings = null;
|
||||
}
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
// default
|
||||
settings = new RuntimeUnitTestSettings
|
||||
{
|
||||
UseCurrentBuildTarget = true,
|
||||
UseCurrentScriptBackend = true,
|
||||
Headless = false,
|
||||
AutoRunPlayer = true,
|
||||
};
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
static void SaveSettings(RuntimeUnitTestSettings settings)
|
||||
{
|
||||
var key = SettingsKeyBase + Application.productName;
|
||||
EditorPrefs.SetString(key, JsonUtility.ToJson(settings));
|
||||
}
|
||||
|
||||
public static void BuildUnitTest(string buildPath, ScriptingImplementation scriptBackend, BuildTarget buildTarget, bool headless, bool autoRunPlayer, bool disableAutoClose)
|
||||
{
|
||||
var sceneName = "Assets/TempRuntimeUnitTestScene_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
if (disableAutoClose)
|
||||
{
|
||||
sceneName += "_DisableAutoClose";
|
||||
}
|
||||
sceneName += ".unity";
|
||||
|
||||
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
||||
|
||||
BuildUnitTestRunnerScene();
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorSceneManager.SaveScene(scene, sceneName, false);
|
||||
try
|
||||
{
|
||||
Build(sceneName, buildPath, new RuntimeUnitTestSettings { ScriptBackend = scriptBackend, BuildTarget = buildTarget, Headless = headless, AutoRunPlayer = autoRunPlayer, DisableAutoClose = disableAutoClose });
|
||||
}
|
||||
finally
|
||||
{
|
||||
AssetDatabase.DeleteAsset(sceneName);
|
||||
}
|
||||
}
|
||||
|
||||
public static UnitTestRunner BuildUnitTestRunnerScene()
|
||||
{
|
||||
const string kStandardSpritePath = "UI/Skin/UISprite.psd";
|
||||
const string kBackgroundSpritePath = "UI/Skin/Background.psd";
|
||||
var uisprite = AssetDatabase.GetBuiltinExtraResource<Sprite>(kStandardSpritePath);
|
||||
var background = AssetDatabase.GetBuiltinExtraResource<Sprite>(kBackgroundSpritePath);
|
||||
|
||||
ScrollRect buttonList;
|
||||
VerticalLayoutGroup listLayout;
|
||||
Scrollbar refListScrollbar;
|
||||
ScrollRect logList;
|
||||
Scrollbar refLogScrollbar;
|
||||
Button clearButton;
|
||||
Text logText;
|
||||
|
||||
// Flutter like coded build utility
|
||||
|
||||
var rootObject = new Builder<Camera>("SceneRoot")
|
||||
{
|
||||
Children = new IBuilder[] {
|
||||
new Builder<EventSystem, StandaloneInputModule>("EventSystem"),
|
||||
new Builder<Canvas, CanvasScaler, GraphicRaycaster>("Canvas") {
|
||||
Component1 = { renderMode = RenderMode.ScreenSpaceOverlay },
|
||||
Children = new IBuilder[] {
|
||||
new Builder<HorizontalLayoutGroup, CanvasRenderer>("HorizontalSplitter") {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
|
||||
Component1 = { childControlWidth = true, childControlHeight = true, spacing = 10 },
|
||||
Children = new IBuilder[] {
|
||||
new Builder<ScrollRect, CanvasRenderer>("ButtonList", out buttonList) {
|
||||
RectTransform = { pivot = new Vector2(0.5f, 0.5f) },
|
||||
Component1 = { horizontal =false, vertical = true, movementType = ScrollRect.MovementType.Clamped },
|
||||
Children = new IBuilder[] {
|
||||
new Builder<VerticalLayoutGroup, ContentSizeFitter>("ListLayoutToAttach", out listLayout) {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0, 1) },
|
||||
Component1 = { childControlWidth = true, childControlHeight = true, childForceExpandWidth = true, childForceExpandHeight = false, spacing = 10, padding = new RectOffset(10,20,10,10) },
|
||||
Component2 = { horizontalFit = ContentSizeFitter.FitMode.Unconstrained, verticalFit = ContentSizeFitter.FitMode.PreferredSize },
|
||||
SetTarget = self => { buttonList.content = self.GetComponent<RectTransform>(); },
|
||||
Child = new Builder<Button, Image, LayoutElement>("ClearButton", out clearButton) {
|
||||
Component2 = { sprite = uisprite, type = Image.Type.Sliced },
|
||||
Component3 = { minHeight = 50 },
|
||||
SetTarget = self => { self.GetComponent<Button>().targetGraphic = self.GetComponent<Graphic>(); },
|
||||
Child = new Builder<Text>("ButtonText") {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0.5f, 0.5f) },
|
||||
Component1 = { text = "Clear", color = FromRGB(50, 50, 50), alignment = TextAnchor.MiddleCenter, fontSize = 24, lineSpacing = 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Builder<Scrollbar,Image>("ListScrollbar", out refListScrollbar) {
|
||||
RectTransform = { anchorMin = new Vector2(1, 0), anchorMax = new Vector2(1, 1) },
|
||||
Component1 = { navigation = new Navigation{ mode = Navigation.Mode.None }, direction = Scrollbar.Direction.BottomToTop, size = 1.0f },
|
||||
Component2 = { sprite = background, type = Image.Type.Sliced },
|
||||
SetTarget = self => { buttonList.verticalScrollbar = self.GetComponent<Scrollbar>(); },
|
||||
Child = new Builder<RectTransform>("Sliding Area") {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
|
||||
Child = new Builder<Image>("Handle") {
|
||||
Component1 = { sprite = uisprite, type = Image.Type.Sliced },
|
||||
SetTarget = self =>
|
||||
{
|
||||
refListScrollbar.targetGraphic = self.GetComponent<Graphic>();
|
||||
refListScrollbar.handleRect = self.GetComponent<RectTransform>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Builder<ScrollRect, CanvasRenderer>("ScrollableText", out logList) {
|
||||
RectTransform = { pivot = new Vector2(0.5f, 0.5f) },
|
||||
Component1 = { horizontal =false, vertical = true, movementType = ScrollRect.MovementType.Elastic, elasticity = 0.1f },
|
||||
Children = new IBuilder[] {
|
||||
new Builder<Text, ContentSizeFitter>("Log", out logText) {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0, 1) },
|
||||
Component1 = { fontSize = 24, lineSpacing = 1, supportRichText = true, alignment = TextAnchor.UpperLeft, horizontalOverflow = HorizontalWrapMode.Wrap, verticalOverflow = VerticalWrapMode.Truncate },
|
||||
Component2 = { horizontalFit = ContentSizeFitter.FitMode.Unconstrained, verticalFit = ContentSizeFitter.FitMode.PreferredSize },
|
||||
SetTarget = self => { logList.content = self.GetComponent<RectTransform>(); }
|
||||
},
|
||||
new Builder<Scrollbar,Image>("LogScrollbar", out refLogScrollbar) {
|
||||
RectTransform = { anchorMin = new Vector2(1, 0), anchorMax = new Vector2(1, 1) },
|
||||
Component1 = { navigation = new Navigation{ mode = Navigation.Mode.None }, direction = Scrollbar.Direction.BottomToTop, size = 1.0f },
|
||||
Component2 = { sprite = background, type = Image.Type.Sliced },
|
||||
SetTarget = self => { logList.verticalScrollbar = self.GetComponent<Scrollbar>(); },
|
||||
Child = new Builder<RectTransform>("Sliding Area2") {
|
||||
RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) },
|
||||
Child = new Builder<Image>("Handle2") {
|
||||
Component1 = { sprite = uisprite, type = Image.Type.Sliced },
|
||||
SetTarget = self =>
|
||||
{
|
||||
refLogScrollbar.targetGraphic = self.GetComponent<Graphic>();
|
||||
refLogScrollbar.handleRect = self.GetComponent<RectTransform>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// size modify after build complete:)
|
||||
{
|
||||
var rect = GameObject.Find("HorizontalSplitter").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("ListLayoutToAttach").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("ListScrollbar").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(30, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("ClearButton").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("Sliding Area").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(-20, -20);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("Handle").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(20, 20);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("ButtonText").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("Log").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(15, 0);
|
||||
rect.offsetMax = new Vector2(-20, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("LogScrollbar").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(-30, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(30, 0);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("Sliding Area2").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(-20, -20);
|
||||
}
|
||||
{
|
||||
var rect = GameObject.Find("Handle2").GetComponent<RectTransform>();
|
||||
rect.offsetMin = new Vector2(0, 0);
|
||||
rect.offsetMax = new Vector2(0, 0);
|
||||
rect.sizeDelta = new Vector2(20, 20);
|
||||
}
|
||||
|
||||
// add test script
|
||||
var runner = rootObject.GameObject.AddComponent<UnitTestRunner>();
|
||||
runner.clearButton = clearButton;
|
||||
runner.list = listLayout.gameObject.GetComponent<RectTransform>();
|
||||
runner.listScrollBar = refListScrollbar;
|
||||
runner.logText = logText;
|
||||
runner.logScrollBar = refLogScrollbar;
|
||||
|
||||
return runner;
|
||||
}
|
||||
|
||||
static void Build(string sceneName, string buildPath, RuntimeUnitTestSettings settings)
|
||||
{
|
||||
var options = BuildOptions.BuildScriptsOnly | BuildOptions.IncludeTestAssemblies;
|
||||
if (settings.AutoRunPlayer)
|
||||
{
|
||||
options |= BuildOptions.AutoRunPlayer;
|
||||
}
|
||||
if (settings.Headless)
|
||||
{
|
||||
options |= BuildOptions.EnableHeadlessMode;
|
||||
}
|
||||
|
||||
var targetGroup = ToBuildTargetGroup(settings.BuildTarget);
|
||||
var currentBackend = PlayerSettings.GetScriptingBackend(targetGroup);
|
||||
if (currentBackend != settings.ScriptBackend)
|
||||
{
|
||||
UnityEngine.Debug.Log("Modify ScriptBackend to " + settings.ScriptBackend);
|
||||
PlayerSettings.SetScriptingBackend(targetGroup, settings.ScriptBackend);
|
||||
}
|
||||
|
||||
var buildOptions = new BuildPlayerOptions
|
||||
{
|
||||
target = settings.BuildTarget,
|
||||
targetGroup = targetGroup,
|
||||
options = options,
|
||||
scenes = new[] { sceneName },
|
||||
locationPathName = buildPath
|
||||
};
|
||||
|
||||
UnityEngine.Debug.Log("UnitTest Build Start, " + settings.ToString());
|
||||
|
||||
var buildReport = BuildPipeline.BuildPlayer(buildOptions);
|
||||
|
||||
if (currentBackend != settings.ScriptBackend)
|
||||
{
|
||||
UnityEngine.Debug.Log("Restore ScriptBackend to " + currentBackend);
|
||||
PlayerSettings.SetScriptingBackend(targetGroup, currentBackend);
|
||||
}
|
||||
|
||||
if (buildReport.summary.result != BuildResult.Succeeded)
|
||||
{
|
||||
// Note: show error summary?
|
||||
// Debug.LogError(buildReport.SummarizeErrors());
|
||||
UnityEngine.Debug.LogError("UnitTest Build Failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.Log("UnitTest Build Completed, binary located: " + buildOptions.locationPathName);
|
||||
}
|
||||
}
|
||||
|
||||
static Color FromRGB(int r, int g, int b)
|
||||
{
|
||||
return new Color(r / 255f, g / 255f, b / 255f);
|
||||
}
|
||||
|
||||
static bool IsWindows(BuildTarget buildTarget)
|
||||
{
|
||||
switch (buildTarget)
|
||||
{
|
||||
case BuildTarget.StandaloneWindows:
|
||||
case BuildTarget.StandaloneWindows64:
|
||||
case BuildTarget.WSAPlayer:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static BuildTargetGroup ToBuildTargetGroup(BuildTarget buildTarget)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
switch (buildTarget)
|
||||
{
|
||||
case BuildTarget.StandaloneOSX:
|
||||
case (BuildTarget)3:
|
||||
case BuildTarget.StandaloneOSXIntel:
|
||||
case BuildTarget.StandaloneOSXIntel64:
|
||||
case BuildTarget.StandaloneWindows:
|
||||
case BuildTarget.StandaloneWindows64:
|
||||
case BuildTarget.StandaloneLinux:
|
||||
case BuildTarget.StandaloneLinux64:
|
||||
case BuildTarget.StandaloneLinuxUniversal:
|
||||
return BuildTargetGroup.Standalone;
|
||||
case (BuildTarget)6:
|
||||
case (BuildTarget)7:
|
||||
case BuildTarget.WebGL:
|
||||
return BuildTargetGroup.WebGL;
|
||||
case BuildTarget.iOS:
|
||||
return BuildTargetGroup.iOS;
|
||||
case BuildTarget.PS3:
|
||||
return BuildTargetGroup.PS3;
|
||||
case BuildTarget.PS4:
|
||||
return BuildTargetGroup.PS4;
|
||||
case BuildTarget.XBOX360:
|
||||
return BuildTargetGroup.XBOX360;
|
||||
case BuildTarget.Android:
|
||||
return BuildTargetGroup.Android;
|
||||
case BuildTarget.WSAPlayer:
|
||||
return BuildTargetGroup.WSA;
|
||||
case BuildTarget.WP8Player:
|
||||
return BuildTargetGroup.WP8;
|
||||
case BuildTarget.Tizen:
|
||||
return BuildTargetGroup.Tizen;
|
||||
case BuildTarget.PSP2:
|
||||
return BuildTargetGroup.PSP2;
|
||||
case BuildTarget.PSM:
|
||||
return BuildTargetGroup.PSM;
|
||||
case BuildTarget.XboxOne:
|
||||
return BuildTargetGroup.XboxOne;
|
||||
case BuildTarget.SamsungTV:
|
||||
return BuildTargetGroup.SamsungTV;
|
||||
case BuildTarget.N3DS:
|
||||
return BuildTargetGroup.N3DS;
|
||||
case BuildTarget.WiiU:
|
||||
return BuildTargetGroup.WiiU;
|
||||
case BuildTarget.tvOS:
|
||||
return BuildTargetGroup.tvOS;
|
||||
case BuildTarget.Switch:
|
||||
return BuildTargetGroup.Switch;
|
||||
case BuildTarget.Lumin:
|
||||
return BuildTargetGroup.Lumin;
|
||||
case BuildTarget.BlackBerry:
|
||||
return BuildTargetGroup.BlackBerry;
|
||||
case BuildTarget.NoTarget:
|
||||
default:
|
||||
return BuildTargetGroup.Unknown;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3518da33b6245d341a0ef3670ee9268b
|
||||
timeCreated: 1488689723
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "RuntimeUnitTestToolkit",
|
||||
"references": [
|
||||
],
|
||||
"optionalUnityReferences": [
|
||||
"TestAssemblies"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 14c4fea4b238088479114ba2ffe195f9
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,450 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RuntimeUnitTestToolkit
|
||||
{
|
||||
public class UnitTestRunner : MonoBehaviour
|
||||
{
|
||||
// object is IEnumerator or Func<IEnumerator>
|
||||
Dictionary<string, List<KeyValuePair<string, object>>> tests = new Dictionary<string, List<KeyValuePair<string, object>>>();
|
||||
|
||||
List<Pair> additionalActionsOnFirst = new List<Pair>();
|
||||
|
||||
public Button clearButton;
|
||||
public RectTransform list;
|
||||
public Scrollbar listScrollBar;
|
||||
|
||||
public Text logText;
|
||||
public Scrollbar logScrollBar;
|
||||
|
||||
readonly Color passColor = new Color(0f, 1f, 0f, 1f); // green
|
||||
readonly Color failColor = new Color(1f, 0f, 0f, 1f); // red
|
||||
readonly Color normalColor = new Color(1f, 1f, 1f, 1f); // white
|
||||
|
||||
bool allTestGreen = true;
|
||||
|
||||
void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
UnityEngine.Application.logMessageReceived += (a, b, c) =>
|
||||
{
|
||||
logText.text += "[" + c + "]" + a + "\n";
|
||||
};
|
||||
|
||||
// register all test types
|
||||
foreach (var item in GetTestTargetTypes())
|
||||
{
|
||||
RegisterAllMethods(item);
|
||||
}
|
||||
|
||||
var executeAll = new List<Func<Coroutine>>();
|
||||
foreach (var ___item in tests)
|
||||
{
|
||||
var actionList = ___item; // be careful, capture in lambda
|
||||
|
||||
executeAll.Add(() => StartCoroutine(RunTestInCoroutine(actionList)));
|
||||
Add(actionList.Key, () => StartCoroutine(RunTestInCoroutine(actionList)));
|
||||
}
|
||||
|
||||
var executeAllButton = Add("Run All Tests", () => StartCoroutine(ExecuteAllInCoroutine(executeAll)));
|
||||
|
||||
clearButton.gameObject.GetComponent<Image>().color = new Color(170 / 255f, 170 / 255f, 170 / 255f, 1);
|
||||
executeAllButton.gameObject.GetComponent<Image>().color = new Color(250 / 255f, 150 / 255f, 150 / 255f, 1);
|
||||
executeAllButton.transform.SetSiblingIndex(1);
|
||||
|
||||
additionalActionsOnFirst.Reverse();
|
||||
foreach (var item in additionalActionsOnFirst)
|
||||
{
|
||||
var newButton = GameObject.Instantiate(clearButton);
|
||||
newButton.name = item.Name;
|
||||
newButton.onClick.RemoveAllListeners();
|
||||
newButton.GetComponentInChildren<Text>().text = item.Name;
|
||||
newButton.onClick.AddListener(item.Action);
|
||||
newButton.transform.SetParent(list);
|
||||
newButton.transform.SetSiblingIndex(1);
|
||||
}
|
||||
|
||||
clearButton.onClick.AddListener(() =>
|
||||
{
|
||||
logText.text = "";
|
||||
foreach (var btn in list.GetComponentsInChildren<Button>())
|
||||
{
|
||||
btn.interactable = true;
|
||||
btn.GetComponent<Image>().color = normalColor;
|
||||
}
|
||||
executeAllButton.gameObject.GetComponent<Image>().color = new Color(250 / 255f, 150 / 255f, 150 / 255f, 1);
|
||||
});
|
||||
|
||||
listScrollBar.value = 1;
|
||||
logScrollBar.value = 1;
|
||||
|
||||
if (Application.isBatchMode)
|
||||
{
|
||||
// run immediately in player
|
||||
StartCoroutine(ExecuteAllInCoroutine(executeAll));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Application.isBatchMode)
|
||||
{
|
||||
// when failed(can not start runner), quit immediately.
|
||||
WriteToConsole(ex.ToString());
|
||||
Application.Quit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button Add(string title, UnityAction test)
|
||||
{
|
||||
var newButton = GameObject.Instantiate(clearButton);
|
||||
newButton.name = title;
|
||||
newButton.onClick.RemoveAllListeners();
|
||||
newButton.GetComponentInChildren<Text>().text = title;
|
||||
newButton.onClick.AddListener(test);
|
||||
|
||||
newButton.transform.SetParent(list);
|
||||
return newButton;
|
||||
}
|
||||
|
||||
static IEnumerable<Type> GetTestTargetTypes()
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var n = assembly.FullName;
|
||||
if (n.StartsWith("UnityEngine")) continue;
|
||||
if (n.StartsWith("mscorlib")) continue;
|
||||
if (n.StartsWith("System")) continue;
|
||||
|
||||
foreach (var item in assembly.GetTypes())
|
||||
{
|
||||
foreach (var method in item.GetMethods())
|
||||
{
|
||||
var t1 = method.GetCustomAttribute<TestAttribute>(true);
|
||||
if (t1 != null)
|
||||
{
|
||||
yield return item;
|
||||
break;
|
||||
}
|
||||
var t2 = method.GetCustomAttribute<UnityTestAttribute>(true);
|
||||
if (t2 != null)
|
||||
{
|
||||
yield return item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTest(string group, string title, Action test)
|
||||
{
|
||||
List<KeyValuePair<string, object>> list;
|
||||
if (!tests.TryGetValue(group, out list))
|
||||
{
|
||||
list = new List<KeyValuePair<string, object>>();
|
||||
tests[group] = list;
|
||||
}
|
||||
|
||||
list.Add(new KeyValuePair<string, object>(title, test));
|
||||
}
|
||||
|
||||
public void AddAsyncTest(string group, string title, Func<IEnumerator> asyncTestCoroutine)
|
||||
{
|
||||
List<KeyValuePair<string, object>> list;
|
||||
if (!tests.TryGetValue(group, out list))
|
||||
{
|
||||
list = new List<KeyValuePair<string, object>>();
|
||||
tests[group] = list;
|
||||
}
|
||||
|
||||
list.Add(new KeyValuePair<string, object>(title, asyncTestCoroutine));
|
||||
}
|
||||
|
||||
public void AddCutomAction(string name, UnityAction action)
|
||||
{
|
||||
additionalActionsOnFirst.Add(new Pair { Name = name, Action = action });
|
||||
}
|
||||
|
||||
|
||||
public void RegisterAllMethods<T>()
|
||||
where T : new()
|
||||
{
|
||||
RegisterAllMethods(typeof(T));
|
||||
}
|
||||
|
||||
public void RegisterAllMethods(Type testType)
|
||||
{
|
||||
try
|
||||
{
|
||||
var test = Activator.CreateInstance(testType);
|
||||
|
||||
var methods = testType.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
foreach (var item in methods)
|
||||
{
|
||||
try
|
||||
{
|
||||
var iteratorTest = item.GetCustomAttribute<UnityEngine.TestTools.UnityTestAttribute>(true);
|
||||
if (iteratorTest != null)
|
||||
{
|
||||
if (item.GetParameters().Length == 0 && item.ReturnType == typeof(IEnumerator))
|
||||
{
|
||||
var factory = (Func<IEnumerator>)Delegate.CreateDelegate(typeof(Func<IEnumerator>), test, item);
|
||||
AddAsyncTest(factory.Target.GetType().Name, factory.Method.Name, factory);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.Log(testType.Name + "." + item.Name + " currently does not supported in RuntumeUnitTestToolkit(multiple parameter or return type is invalid).");
|
||||
}
|
||||
}
|
||||
|
||||
var standardTest = item.GetCustomAttribute<NUnit.Framework.TestAttribute>(true);
|
||||
if (standardTest != null)
|
||||
{
|
||||
if (item.GetParameters().Length == 0 && item.ReturnType == typeof(void))
|
||||
{
|
||||
var invoke = (Action)Delegate.CreateDelegate(typeof(Action), test, item);
|
||||
AddTest(invoke.Target.GetType().Name, invoke.Method.Name, invoke);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.Log(testType.Name + "." + item.Name + " currently does not supported in RuntumeUnitTestToolkit(multiple parameter or return type is invalid).");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError(testType.Name + "." + item.Name + " failed to register method, exception: " + e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator ScrollLogToEndNextFrame()
|
||||
{
|
||||
yield return null;
|
||||
yield return null;
|
||||
logScrollBar.value = 0;
|
||||
}
|
||||
|
||||
IEnumerator RunTestInCoroutine(KeyValuePair<string, List<KeyValuePair<string, object>>> actionList)
|
||||
{
|
||||
Button self = null;
|
||||
foreach (var btn in list.GetComponentsInChildren<Button>())
|
||||
{
|
||||
btn.interactable = false;
|
||||
if (btn.name == actionList.Key) self = btn;
|
||||
}
|
||||
if (self != null)
|
||||
{
|
||||
self.GetComponent<Image>().color = normalColor;
|
||||
}
|
||||
|
||||
var allGreen = true;
|
||||
|
||||
logText.text += "<color=yellow>" + actionList.Key + "</color>\n";
|
||||
WriteToConsole("Begin Test Class: " + actionList.Key);
|
||||
yield return null;
|
||||
|
||||
var totalExecutionTime = new List<double>();
|
||||
foreach (var item2 in actionList.Value)
|
||||
{
|
||||
// before start, cleanup
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
logText.text += "<color=teal>" + item2.Key + "</color>\n";
|
||||
yield return null;
|
||||
|
||||
var v = item2.Value;
|
||||
|
||||
var methodStopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
Exception exception = null;
|
||||
if (v is Action)
|
||||
{
|
||||
try
|
||||
{
|
||||
((Action)v).Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var coroutineFactory = (Func<IEnumerator>)v;
|
||||
IEnumerator coroutine = null;
|
||||
try
|
||||
{
|
||||
coroutine = coroutineFactory();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
if (exception == null)
|
||||
{
|
||||
yield return StartCoroutine(UnwrapEnumerator(coroutine, ex =>
|
||||
{
|
||||
exception = ex;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
methodStopwatch.Stop();
|
||||
totalExecutionTime.Add(methodStopwatch.Elapsed.TotalMilliseconds);
|
||||
if (exception == null)
|
||||
{
|
||||
logText.text += "OK, " + methodStopwatch.Elapsed.TotalMilliseconds.ToString("0.00") + "ms\n";
|
||||
WriteToConsoleResult(item2.Key + ", " + methodStopwatch.Elapsed.TotalMilliseconds.ToString("0.00") + "ms", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// found match line...
|
||||
var line = string.Join("\n", exception.StackTrace.Split('\n').Where(x => x.Contains(actionList.Key) || x.Contains(item2.Key)).ToArray());
|
||||
logText.text += "<color=red>" + exception.Message + "\n" + line + "</color>\n";
|
||||
WriteToConsoleResult(item2.Key + ", " + exception.Message, false);
|
||||
WriteToConsole(line);
|
||||
allGreen = false;
|
||||
allTestGreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
logText.text += "[" + actionList.Key + "]" + totalExecutionTime.Sum().ToString("0.00") + "ms\n\n";
|
||||
foreach (var btn in list.GetComponentsInChildren<Button>()) btn.interactable = true;
|
||||
if (self != null)
|
||||
{
|
||||
self.GetComponent<Image>().color = allGreen ? passColor : failColor;
|
||||
}
|
||||
|
||||
yield return StartCoroutine(ScrollLogToEndNextFrame());
|
||||
|
||||
|
||||
}
|
||||
|
||||
IEnumerator ExecuteAllInCoroutine(List<Func<Coroutine>> tests)
|
||||
{
|
||||
allTestGreen = true;
|
||||
|
||||
foreach (var item in tests)
|
||||
{
|
||||
yield return item();
|
||||
}
|
||||
|
||||
if (Application.isBatchMode)
|
||||
{
|
||||
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
bool disableAutoClose = (scene.name.Contains("DisableAutoClose"));
|
||||
|
||||
if (allTestGreen)
|
||||
{
|
||||
WriteToConsole("Test Complete Successfully");
|
||||
if (!disableAutoClose)
|
||||
{
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteToConsole("Test Failed, please see [NG] log.");
|
||||
if (!disableAutoClose)
|
||||
{
|
||||
Application.Quit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator UnwrapEnumerator(IEnumerator enumerator, Action<Exception> exceptionCallback)
|
||||
{
|
||||
var hasNext = true;
|
||||
while (hasNext)
|
||||
{
|
||||
try
|
||||
{
|
||||
hasNext = enumerator.MoveNext();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptionCallback(ex);
|
||||
hasNext = false;
|
||||
}
|
||||
|
||||
if (hasNext)
|
||||
{
|
||||
// unwrap self for bug of Unity
|
||||
// https://issuetracker.unity3d.com/issues/does-not-stop-coroutine-when-it-throws-exception-in-movenext-at-first-frame
|
||||
var moreCoroutine = enumerator.Current as IEnumerator;
|
||||
if (moreCoroutine != null)
|
||||
{
|
||||
yield return StartCoroutine(UnwrapEnumerator(moreCoroutine, ex =>
|
||||
{
|
||||
exceptionCallback(ex);
|
||||
hasNext = false;
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return enumerator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteToConsole(string msg)
|
||||
{
|
||||
if (Application.isBatchMode)
|
||||
{
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteToConsoleResult(string msg, bool green)
|
||||
{
|
||||
if (Application.isBatchMode)
|
||||
{
|
||||
if (!green)
|
||||
{
|
||||
var currentForeground = Console.ForegroundColor;
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Write("[NG]");
|
||||
Console.ForegroundColor = currentForeground;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentForeground = Console.ForegroundColor;
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("[OK]");
|
||||
Console.ForegroundColor = currentForeground;
|
||||
}
|
||||
|
||||
System.Console.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
|
||||
struct Pair
|
||||
{
|
||||
public string Name;
|
||||
public UnityAction Action;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 660baed073888b8438569f57e42679b2
|
||||
timeCreated: 1476793308
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "jp.cysharp.runtimeunittesttoolkit",
|
||||
"displayName": "RuntimeUnitTestToolkit",
|
||||
"version": "2.0.0",
|
||||
"unity": "2018.3",
|
||||
"description": "CLI/GUI Frontend of Unity Test Runner to test on any platform.",
|
||||
"keywords": ["test"],
|
||||
"category": "Tests",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b7883c7ac5d6ea4409a229aeab14e796
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3e4672a57ce755a44805bc58b4ddea29
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UniRx.Async;
|
||||
using UnityEngine;
|
||||
|
||||
public class SandboxMain : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
RunAsync().Forget();
|
||||
}
|
||||
|
||||
async UniTaskVoid RunAsync()
|
||||
{
|
||||
var id = await UniTask.Run(() => System.Threading.Thread.CurrentThread.ManagedThreadId, configureAwait: false);
|
||||
UnityEngine.Debug.Log("ReturnId:" + id);
|
||||
UnityEngine.Debug.Log("CurrentId:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0bc6c75abb2e0b47a25aa49bfd488ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,218 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 3
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 11
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 0
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 0
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 500
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 500
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_UseShadowmask: 1
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &519420028
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 519420032}
|
||||
- component: {fileID: 519420031}
|
||||
- component: {fileID: 519420029}
|
||||
- component: {fileID: 519420030}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &519420029
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
--- !u!114 &519420030
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f0bc6c75abb2e0b47a25aa49bfd488ed, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!20 &519420031
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 2
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 1
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 0
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 0
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 0
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &519420032
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 519420028}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2cda990e2423bbf4892e6590ba056729
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b831a4cf04cbe4b48ae74498484ec8c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,384 @@
|
|||
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Scripting;
|
||||
using UniRx;
|
||||
using UniRx.Async;
|
||||
using UnityEngine.SceneManagement;
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using UnityEngine.Networking;
|
||||
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
using UnityEngine.Experimental.LowLevel;
|
||||
#else
|
||||
using UnityEngine.LowLevel;
|
||||
#endif
|
||||
|
||||
#if !UNITY_WSA
|
||||
using Unity.Jobs;
|
||||
#endif
|
||||
using Unity.Collections;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine.TestTools;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace UniRx.AsyncTests
|
||||
{
|
||||
public class AsyncTest
|
||||
{
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#if !UNITY_WSA
|
||||
|
||||
public struct MyJob : IJob
|
||||
{
|
||||
public int loopCount;
|
||||
public NativeArray<int> inOut;
|
||||
public int result;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
result = 0;
|
||||
for (int i = 0; i < loopCount; i++)
|
||||
{
|
||||
result++;
|
||||
}
|
||||
inOut[0] = result;
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator DelayAnd() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
|
||||
|
||||
var time = Time.realtimeSinceStartup;
|
||||
|
||||
Time.timeScale = 0.5f;
|
||||
try
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(3));
|
||||
|
||||
var elapsed = Time.realtimeSinceStartup - time;
|
||||
((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(6);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Time.timeScale = 1.0f;
|
||||
}
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var time = Time.realtimeSinceStartup;
|
||||
|
||||
Time.timeScale = 0.5f;
|
||||
try
|
||||
{
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(3), ignoreTimeScale: true);
|
||||
|
||||
var elapsed = Time.realtimeSinceStartup - time;
|
||||
((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(3);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Time.timeScale = 1.0f;
|
||||
}
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator WhenAll() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var a = UniTask.FromResult(999);
|
||||
var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask();
|
||||
var c = UniTask.DelayFrame(99);
|
||||
|
||||
var (a2, b2, c2) = await UniTask.WhenAll(a, b, c);
|
||||
a2.Should().Be(999);
|
||||
b2.Should().Be(AsyncUnit.Default);
|
||||
c2.Should().Be(99);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator WhenAny() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var a = UniTask.FromResult(999);
|
||||
var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask();
|
||||
var c = UniTask.DelayFrame(99);
|
||||
|
||||
var (win, a2, b2, c2) = await UniTask.WhenAny(a, b, c);
|
||||
win.Should().Be(0);
|
||||
a2.hasResult.Should().Be(true);
|
||||
a2.result0.Should().Be(999);
|
||||
b2.hasResult.Should().Be(false);
|
||||
c2.hasResult.Should().Be(false);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator BothEnumeratorCheck() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
await ToaruCoroutineEnumerator(); // wait 5 frame:)
|
||||
await ToaruCoroutineEnumerator().ConfigureAwait(PlayerLoopTiming.PostLateUpdate);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator JobSystem() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) };
|
||||
JobHandle.ScheduleBatchedJobs();
|
||||
await job.Schedule();
|
||||
job.inOut[0].Should().Be(999);
|
||||
job.inOut.Dispose();
|
||||
});
|
||||
|
||||
class MyMyClass
|
||||
{
|
||||
public int MyProperty { get; set; }
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator WaitUntil() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
bool t = false;
|
||||
|
||||
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(_ => t = true).Forget();
|
||||
|
||||
var startFrame = Time.frameCount;
|
||||
await UniTask.WaitUntil(() => t, PlayerLoopTiming.EarlyUpdate);
|
||||
|
||||
var diff = Time.frameCount - startFrame;
|
||||
diff.Should().Be(11);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator WaitWhile() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
bool t = true;
|
||||
|
||||
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(_ => t = false).Forget();
|
||||
|
||||
var startFrame = Time.frameCount;
|
||||
await UniTask.WaitWhile(() => t, PlayerLoopTiming.EarlyUpdate);
|
||||
|
||||
var diff = Time.frameCount - startFrame;
|
||||
diff.Should().Be(11);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator WaitUntilValueChanged() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var v = new MyMyClass { MyProperty = 99 };
|
||||
|
||||
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(_ => v.MyProperty = 1000).Forget();
|
||||
|
||||
var startFrame = Time.frameCount;
|
||||
await UniTask.WaitUntilValueChanged(v, x => x.MyProperty, PlayerLoopTiming.EarlyUpdate);
|
||||
|
||||
var diff = Time.frameCount - startFrame;
|
||||
diff.Should().Be(11);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SwitchTo() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
await UniTask.Yield();
|
||||
|
||||
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
UnityEngine.Debug.Log("Before:" + currentThreadId);
|
||||
|
||||
|
||||
await UniTask.SwitchToThreadPool();
|
||||
//await UniTask.SwitchToThreadPool();
|
||||
//await UniTask.SwitchToThreadPool();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var switchedThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
UnityEngine.Debug.Log("After:" + switchedThreadId);
|
||||
|
||||
|
||||
currentThreadId.Should().NotBe(switchedThreadId);
|
||||
|
||||
|
||||
await UniTask.Yield();
|
||||
|
||||
var switchedThreadId2 = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
currentThreadId.Should().Be(switchedThreadId2);
|
||||
});
|
||||
|
||||
//[UnityTest]
|
||||
//public IEnumerator ObservableConversion() => UniTask.ToCoroutine(async () =>
|
||||
//{
|
||||
// var v = await Observable.Range(1, 10).ToUniTask();
|
||||
// v.Is(10);
|
||||
|
||||
// v = await Observable.Range(1, 10).ToUniTask(useFirstValue: true);
|
||||
// v.Is(1);
|
||||
|
||||
// v = await UniTask.DelayFrame(10).ToObservable().ToTask();
|
||||
// v.Is(10);
|
||||
|
||||
// v = await UniTask.FromResult(99).ToObservable();
|
||||
// v.Is(99);
|
||||
//});
|
||||
|
||||
//[UnityTest]
|
||||
//public IEnumerator AwaitableReactiveProperty() => UniTask.ToCoroutine(async () =>
|
||||
//{
|
||||
// var rp1 = new ReactiveProperty<int>(99);
|
||||
|
||||
// UniTask.DelayFrame(100).ContinueWith(x => rp1.Value = x).Forget();
|
||||
|
||||
// await rp1;
|
||||
|
||||
// rp1.Value.Is(100);
|
||||
|
||||
// // var delay2 = UniTask.DelayFrame(10);
|
||||
// // var (a, b ) = await UniTask.WhenAll(rp1.WaitUntilValueChangedAsync(), delay2);
|
||||
|
||||
//});
|
||||
|
||||
//[UnityTest]
|
||||
//public IEnumerator AwaitableReactiveCommand() => UniTask.ToCoroutine(async () =>
|
||||
//{
|
||||
// var rc = new ReactiveCommand<int>();
|
||||
|
||||
// UniTask.DelayFrame(100).ContinueWith(x => rc.Execute(x)).Forget();
|
||||
|
||||
// var v = await rc;
|
||||
|
||||
// v.Is(100);
|
||||
//});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ExceptionlessCancellation() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
UniTask.DelayFrame(10).ContinueWith(_ => cts.Cancel()).Forget();
|
||||
|
||||
var first = Time.frameCount;
|
||||
var (canceled, value) = await UniTask.DelayFrame(100, cancellationToken: cts.Token).SuppressCancellationThrow();
|
||||
|
||||
(Time.frameCount - first).Should().Be(11); // 10 frame canceled
|
||||
canceled.Should().Be(true);
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ExceptionCancellation() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
UniTask.DelayFrame(10).ContinueWith(_ => cts.Cancel()).Forget();
|
||||
|
||||
bool occur = false;
|
||||
try
|
||||
{
|
||||
var value = await UniTask.DelayFrame(100, cancellationToken: cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
occur = true;
|
||||
}
|
||||
occur.Should().BeTrue();
|
||||
});
|
||||
|
||||
IEnumerator ToaruCoroutineEnumerator()
|
||||
{
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ExceptionUnobserved1() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
bool calledEx = false;
|
||||
Action<Exception> action = exx =>
|
||||
{
|
||||
calledEx = true;
|
||||
exx.Message.Should().Be("MyException");
|
||||
};
|
||||
|
||||
UniTaskScheduler.UnobservedTaskException += action;
|
||||
|
||||
var ex = InException1();
|
||||
ex = default(UniTask);
|
||||
|
||||
await UniTask.DelayFrame(3);
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
await UniTask.DelayFrame(1);
|
||||
|
||||
calledEx.Should().BeTrue();
|
||||
|
||||
UniTaskScheduler.UnobservedTaskException -= action;
|
||||
});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ExceptionUnobserved2() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
bool calledEx = false;
|
||||
Action<Exception> action = exx =>
|
||||
{
|
||||
calledEx = true;
|
||||
exx.Message.Should().Be("MyException");
|
||||
};
|
||||
|
||||
UniTaskScheduler.UnobservedTaskException += action;
|
||||
|
||||
var ex = InException2();
|
||||
ex = default(UniTask<int>);
|
||||
|
||||
await UniTask.DelayFrame(3);
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
await UniTask.DelayFrame(1);
|
||||
|
||||
calledEx.Should().BeTrue();
|
||||
|
||||
UniTaskScheduler.UnobservedTaskException -= action;
|
||||
});
|
||||
|
||||
async UniTask InException1()
|
||||
{
|
||||
await UniTask.Yield();
|
||||
throw new Exception("MyException");
|
||||
}
|
||||
|
||||
async UniTask<int> InException2()
|
||||
{
|
||||
await UniTask.Yield();
|
||||
throw new Exception("MyException");
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e9960810a8f0634cb4aea38c563d947
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,96 @@
|
|||
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Scripting;
|
||||
using UniRx;
|
||||
using UniRx.Async;
|
||||
using UnityEngine.SceneManagement;
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
using UnityEngine.Networking;
|
||||
|
||||
#if !UNITY_2019_3_OR_NEWER
|
||||
using UnityEngine.Experimental.LowLevel;
|
||||
#else
|
||||
using UnityEngine.LowLevel;
|
||||
#endif
|
||||
|
||||
#if !UNITY_WSA
|
||||
using Unity.Jobs;
|
||||
#endif
|
||||
using Unity.Collections;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine.TestTools;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace UniRx.AsyncTests
|
||||
{
|
||||
public class RunTest
|
||||
{
|
||||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#if !UNITY_WSA
|
||||
|
||||
//[UnityTest]
|
||||
//public IEnumerator RunThread() => UniTask.ToCoroutine(async () =>
|
||||
//{
|
||||
// var main = Thread.CurrentThread.ManagedThreadId;
|
||||
// var v = await UniTask.Run(() => { return System.Threading.Thread.CurrentThread.ManagedThreadId; }, false);
|
||||
// UnityEngine.Debug.Log("Ret Value is:" + v);
|
||||
// UnityEngine.Debug.Log("Run Here and id:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
// //v.Should().Be(3);
|
||||
// main.Should().NotBe(Thread.CurrentThread.ManagedThreadId);
|
||||
//});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator RunThreadConfigure() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var main = Thread.CurrentThread.ManagedThreadId;
|
||||
var v = await UniTask.Run(() => 3, true);
|
||||
v.Should().Be(3);
|
||||
main.Should().Be(Thread.CurrentThread.ManagedThreadId);
|
||||
});
|
||||
|
||||
//[UnityTest]
|
||||
//public IEnumerator RunThreadException() => UniTask.ToCoroutine(async () =>
|
||||
//{
|
||||
// var main = Thread.CurrentThread.ManagedThreadId;
|
||||
// try
|
||||
// {
|
||||
// await UniTask.Run<int>(() => throw new Exception(), false);
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// main.Should().NotBe(Thread.CurrentThread.ManagedThreadId);
|
||||
// }
|
||||
//});
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator RunThreadExceptionConfigure() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var main = Thread.CurrentThread.ManagedThreadId;
|
||||
try
|
||||
{
|
||||
await UniTask.Run<int>(() => throw new Exception(), true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
main.Should().Be(Thread.CurrentThread.ManagedThreadId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3510f39953ed3074cb2e0f04d3a3f807
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,144 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
|
||||
namespace Xunit
|
||||
{
|
||||
public class FactAttribute : TestAttribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Shims of FluentAssertions
|
||||
namespace FluentAssertions
|
||||
{
|
||||
public static class FluentAssertionsExtensions
|
||||
{
|
||||
public static Int Should(this int value)
|
||||
{
|
||||
return new Int(value);
|
||||
}
|
||||
|
||||
public static Bool Should(this bool value)
|
||||
{
|
||||
return new Bool(value);
|
||||
}
|
||||
|
||||
public static ExceptionAssertion Should(this Exception value)
|
||||
{
|
||||
return new ExceptionAssertion(value);
|
||||
}
|
||||
|
||||
public static Generic<T> Should<T>(this T value)
|
||||
{
|
||||
return new Generic<T>(value);
|
||||
}
|
||||
|
||||
public class Generic<T>
|
||||
{
|
||||
readonly T actual;
|
||||
|
||||
public Generic(T value)
|
||||
{
|
||||
actual = value;
|
||||
}
|
||||
|
||||
public void Be(T expected)
|
||||
{
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void NotBe(T expected)
|
||||
{
|
||||
Assert.AreNotEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void BeNull()
|
||||
{
|
||||
Assert.IsNull(actual);
|
||||
}
|
||||
|
||||
public void NotBeNull()
|
||||
{
|
||||
Assert.IsNotNull(actual);
|
||||
}
|
||||
}
|
||||
|
||||
public class Bool
|
||||
{
|
||||
readonly bool actual;
|
||||
|
||||
public Bool(bool value)
|
||||
{
|
||||
actual = value;
|
||||
}
|
||||
|
||||
public void Be(bool expected)
|
||||
{
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void NotBe(bool expected)
|
||||
{
|
||||
Assert.AreNotEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void BeTrue()
|
||||
{
|
||||
Assert.AreEqual(true, actual);
|
||||
}
|
||||
|
||||
public void BeFalse()
|
||||
{
|
||||
Assert.AreEqual(false, actual);
|
||||
}
|
||||
}
|
||||
|
||||
public class Int
|
||||
{
|
||||
readonly int actual;
|
||||
|
||||
public Int(int value)
|
||||
{
|
||||
actual = value;
|
||||
}
|
||||
|
||||
public void Be(int expected)
|
||||
{
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void NotBe(int expected)
|
||||
{
|
||||
Assert.AreNotEqual(expected, actual);
|
||||
}
|
||||
|
||||
public void BeCloseTo(int expected, int delta)
|
||||
{
|
||||
if (expected - delta <= actual && actual <= expected + delta)
|
||||
{
|
||||
// OK.
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail($"Fail BeCloseTo, actual {actual} but expected:{expected} +- {delta}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExceptionAssertion
|
||||
{
|
||||
readonly Exception actual;
|
||||
|
||||
public ExceptionAssertion(Exception actual)
|
||||
{
|
||||
this.actual = actual;
|
||||
}
|
||||
|
||||
public void BeAssignableTo<T>()
|
||||
{
|
||||
typeof(T).IsAssignableFrom(actual.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f76a88025d2f76a4bae09aaa268da103
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "UniRx.Async.Tests",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner",
|
||||
"UniRx.Async"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9fbc684c31ac15b42809d42b62f6eb5f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,44 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Scripting;
|
||||
using UniRx;
|
||||
using UniRx.Async;
|
||||
using Unity.Collections;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine.TestTools;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace UniRx.AsyncTests
|
||||
{
|
||||
public class WhenAnyTest
|
||||
{
|
||||
[UnityTest]
|
||||
public IEnumerator WhenAnyCanceled() => UniTask.ToCoroutine(async () =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var successDelayTask = UniTask.Delay(TimeSpan.FromSeconds(1));
|
||||
var cancelTask = UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cts.Token);
|
||||
cts.CancelAfterSlim(200);
|
||||
|
||||
try
|
||||
{
|
||||
var r = await UniTask.WhenAny(new[] { successDelayTask, cancelTask });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Should().BeAssignableTo<OperationCanceledException>();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0a07d2ea8baf390408279a29510f4953
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 38249c6f05d584f44a219b4c97790f99
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or
|
||||
|
||||
using System;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public struct AsyncUnit : IEquatable<AsyncUnit>
|
||||
{
|
||||
public static readonly AsyncUnit Default = new AsyncUnit();
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool Equals(AsyncUnit other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "()";
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4f95ac245430d304bb5128d13b6becc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public class CancellationTokenEqualityComparer : IEqualityComparer<CancellationToken>
|
||||
{
|
||||
public static readonly IEqualityComparer<CancellationToken> Default = new CancellationTokenEqualityComparer();
|
||||
|
||||
public bool Equals(CancellationToken x, CancellationToken y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public int GetHashCode(CancellationToken obj)
|
||||
{
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7d739f510b125b74fa7290ac4335e46e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,76 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class CancellationTokenExtensions
|
||||
{
|
||||
static readonly Action<object> cancellationTokenCallback = Callback;
|
||||
|
||||
public static (UniTask, CancellationTokenRegistration) ToUniTask(this CancellationToken cts)
|
||||
{
|
||||
if (cts.IsCancellationRequested)
|
||||
{
|
||||
return (UniTask.FromCanceled(cts), default(CancellationTokenRegistration));
|
||||
}
|
||||
|
||||
var promise = new UniTaskCompletionSource<AsyncUnit>();
|
||||
return (promise.Task, cts.RegisterWithoutCaptureExecutionContext(cancellationTokenCallback, promise));
|
||||
}
|
||||
|
||||
static void Callback(object state)
|
||||
{
|
||||
var promise = (UniTaskCompletionSource<AsyncUnit>)state;
|
||||
promise.TrySetResult(AsyncUnit.Default);
|
||||
}
|
||||
|
||||
public static CancellationTokenRegistration RegisterWithoutCaptureExecutionContext(this CancellationToken cancellationToken, Action callback)
|
||||
{
|
||||
var restoreFlow = false;
|
||||
if (!ExecutionContext.IsFlowSuppressed())
|
||||
{
|
||||
ExecutionContext.SuppressFlow();
|
||||
restoreFlow = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return cancellationToken.Register(callback, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (restoreFlow)
|
||||
{
|
||||
ExecutionContext.RestoreFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CancellationTokenRegistration RegisterWithoutCaptureExecutionContext(this CancellationToken cancellationToken, Action<object> callback, object state)
|
||||
{
|
||||
var restoreFlow = false;
|
||||
if (!ExecutionContext.IsFlowSuppressed())
|
||||
{
|
||||
ExecutionContext.SuppressFlow();
|
||||
restoreFlow = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return cancellationToken.Register(callback, state, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (restoreFlow)
|
||||
{
|
||||
ExecutionContext.RestoreFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4be7209f04146bd45ac5ee775a5f7c26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,48 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UniRx.Async.Triggers;
|
||||
using System;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class CancellationTokenSourceExtensions
|
||||
{
|
||||
public static void CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
var delay = UniTask.Delay(millisecondsDelay, ignoreTimeScale, delayTiming, cts.Token);
|
||||
CancelAfterCore(cts, delay).Forget();
|
||||
}
|
||||
|
||||
public static void CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
var delay = UniTask.Delay(delayTimeSpan, ignoreTimeScale, delayTiming, cts.Token);
|
||||
CancelAfterCore(cts, delay).Forget();
|
||||
}
|
||||
|
||||
static async UniTaskVoid CancelAfterCore(CancellationTokenSource cts, UniTask delayTask)
|
||||
{
|
||||
var alreadyCanceled = await delayTask.SuppressCancellationThrow();
|
||||
if (!alreadyCanceled)
|
||||
{
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, Component component)
|
||||
{
|
||||
RegisterRaiseCancelOnDestroy(cts, component.gameObject);
|
||||
}
|
||||
|
||||
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, GameObject gameObject)
|
||||
{
|
||||
var trigger = gameObject.GetAsyncDestroyTrigger();
|
||||
trigger.AddCancellationTriggerOnDestory(cts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 22d85d07f1e70ab42a7a4c25bd65e661
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64b064347ca7a404494a996b072e2e29
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,18 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
public sealed class AsyncMethodBuilderAttribute : Attribute
|
||||
{
|
||||
public Type BuilderType { get; }
|
||||
|
||||
public AsyncMethodBuilderAttribute(Type builderType)
|
||||
{
|
||||
BuilderType = builderType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02ce354d37b10454e8376062f7cbe57a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,274 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace UniRx.Async.CompilerServices
|
||||
{
|
||||
public struct AsyncUniTaskMethodBuilder
|
||||
{
|
||||
UniTaskCompletionSource promise;
|
||||
Action moveNext;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
public static AsyncUniTaskMethodBuilder Create()
|
||||
{
|
||||
var builder = new AsyncUniTaskMethodBuilder();
|
||||
return builder;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property.
|
||||
[DebuggerHidden]
|
||||
public UniTask Task
|
||||
{
|
||||
get
|
||||
{
|
||||
if (promise != null)
|
||||
{
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
if (moveNext == null)
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
promise = new UniTaskCompletionSource();
|
||||
return promise.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource();
|
||||
}
|
||||
if (exception is OperationCanceledException ex)
|
||||
{
|
||||
promise.TrySetCanceled(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.TrySetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
public void SetResult()
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource();
|
||||
}
|
||||
promise.TrySetResult();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource(); // built future.
|
||||
}
|
||||
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource(); // built future.
|
||||
}
|
||||
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct AsyncUniTaskMethodBuilder<T>
|
||||
{
|
||||
T result;
|
||||
UniTaskCompletionSource<T> promise;
|
||||
Action moveNext;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
public static AsyncUniTaskMethodBuilder<T> Create()
|
||||
{
|
||||
var builder = new AsyncUniTaskMethodBuilder<T>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property.
|
||||
[DebuggerHidden]
|
||||
public UniTask<T> Task
|
||||
{
|
||||
get
|
||||
{
|
||||
if (promise != null)
|
||||
{
|
||||
return new UniTask<T>(promise);
|
||||
}
|
||||
|
||||
if (moveNext == null)
|
||||
{
|
||||
return new UniTask<T>(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
promise = new UniTaskCompletionSource<T>();
|
||||
return new UniTask<T>(promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource<T>();
|
||||
}
|
||||
if (exception is OperationCanceledException ex)
|
||||
{
|
||||
promise.TrySetCanceled(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
promise.TrySetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource<T>();
|
||||
}
|
||||
promise.TrySetResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource<T>(); // built future.
|
||||
}
|
||||
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
if (promise == null)
|
||||
{
|
||||
promise = new UniTaskCompletionSource<T>(); // built future.
|
||||
}
|
||||
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 68d72a45afdec574ebc26e7de2c38330
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,90 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
namespace UniRx.Async.CompilerServices
|
||||
{
|
||||
public struct AsyncUniTaskVoidMethodBuilder
|
||||
{
|
||||
Action moveNext;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
public static AsyncUniTaskVoidMethodBuilder Create()
|
||||
{
|
||||
var builder = new AsyncUniTaskVoidMethodBuilder();
|
||||
return builder;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property(void)
|
||||
public UniTaskVoid Task => default(UniTaskVoid);
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
UniTaskScheduler.PublishUnobservedTaskException(exception);
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
public void SetResult()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (moveNext == null)
|
||||
{
|
||||
var runner = new MoveNextRunner<TStateMachine>();
|
||||
moveNext = runner.Run;
|
||||
runner.StateMachine = stateMachine; // set after create delegate.
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(moveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e891aaac17b933a47a9d7fa3b8e1226f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,23 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async.CompilerServices
|
||||
{
|
||||
internal class MoveNextRunner<TStateMachine>
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
public TStateMachine StateMachine;
|
||||
|
||||
[DebuggerHidden]
|
||||
public void Run()
|
||||
{
|
||||
StateMachine.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 98649642833cabf44a9dc060ce4c84a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,270 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class DiagnosticsExtensions
|
||||
{
|
||||
static bool displayFilenames = true;
|
||||
|
||||
static readonly Regex typeBeautifyRegex = new Regex("`.+$", RegexOptions.Compiled);
|
||||
|
||||
static readonly Dictionary<Type, string> builtInTypeNames = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(Task), "Task" },
|
||||
{ typeof(UniTask), "UniTask" },
|
||||
{ typeof(UniTaskVoid), "UniTaskVoid" }
|
||||
};
|
||||
|
||||
public static string ToStringWithCleanupAsyncStackTrace(this Exception exception)
|
||||
{
|
||||
if (exception == null) return "";
|
||||
|
||||
String message = exception.Message;
|
||||
String s;
|
||||
|
||||
if (message == null || message.Length <= 0)
|
||||
{
|
||||
s = exception.GetType().ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
s = exception.GetType().ToString() + ": " + message;
|
||||
}
|
||||
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
s = s + " ---> " + exception.InnerException.ToString() + Environment.NewLine + " Exception_EndOfInnerExceptionStack";
|
||||
}
|
||||
|
||||
string stackTrace = new StackTrace(exception).CleanupAsyncStackTrace();
|
||||
if (stackTrace != null)
|
||||
{
|
||||
s += Environment.NewLine + stackTrace;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static string CleanupAsyncStackTrace(this StackTrace stackTrace)
|
||||
{
|
||||
if (stackTrace == null) return "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < stackTrace.FrameCount; i++)
|
||||
{
|
||||
var sf = stackTrace.GetFrame(i);
|
||||
|
||||
var mb = sf.GetMethod();
|
||||
|
||||
if (IgnoreLine(mb)) continue;
|
||||
if (IsAsync(mb))
|
||||
{
|
||||
sb.Append("async ");
|
||||
TryResolveStateMachineMethod(ref mb, out var decType);
|
||||
}
|
||||
|
||||
// return type
|
||||
if (mb is MethodInfo mi)
|
||||
{
|
||||
sb.Append(BeautifyType(mi.ReturnType, false));
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
// method name
|
||||
sb.Append(BeautifyType(mb.DeclaringType, false));
|
||||
if (!mb.IsConstructor)
|
||||
{
|
||||
sb.Append(".");
|
||||
}
|
||||
sb.Append(mb.Name);
|
||||
if (mb.IsGenericMethod)
|
||||
{
|
||||
sb.Append("<");
|
||||
foreach (var item in mb.GetGenericArguments())
|
||||
{
|
||||
sb.Append(BeautifyType(item, true));
|
||||
}
|
||||
sb.Append(">");
|
||||
}
|
||||
|
||||
// parameter
|
||||
sb.Append("(");
|
||||
sb.Append(string.Join(", ", mb.GetParameters().Select(p => BeautifyType(p.ParameterType, true) + " " + p.Name)));
|
||||
sb.Append(")");
|
||||
|
||||
// file name
|
||||
if (displayFilenames && (sf.GetILOffset() != -1))
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
try
|
||||
{
|
||||
fileName = sf.GetFileName();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
displayFilenames = false;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
displayFilenames = false;
|
||||
}
|
||||
|
||||
if (fileName != null)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.AppendFormat(CultureInfo.InvariantCulture, "in {0}:{1}", SimplifyPath(fileName), sf.GetFileLineNumber());
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
static bool IsAsync(MethodBase methodInfo)
|
||||
{
|
||||
var declareType = methodInfo.DeclaringType;
|
||||
return typeof(IAsyncStateMachine).IsAssignableFrom(declareType);
|
||||
}
|
||||
|
||||
// code from Ben.Demystifier/EnhancedStackTrace.Frame.cs
|
||||
static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
|
||||
{
|
||||
declaringType = method.DeclaringType;
|
||||
|
||||
var parentType = declaringType.DeclaringType;
|
||||
if (parentType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
if (methods == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var candidateMethod in methods)
|
||||
{
|
||||
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
|
||||
if (attributes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var asma in attributes)
|
||||
{
|
||||
if (asma.StateMachineType == declaringType)
|
||||
{
|
||||
method = candidateMethod;
|
||||
declaringType = candidateMethod.DeclaringType;
|
||||
// Mark the iterator as changed; so it gets the + annotation of the original method
|
||||
// async statemachines resolve directly to their builder methods so aren't marked as changed
|
||||
return asma is IteratorStateMachineAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static string BeautifyType(Type t, bool shortName)
|
||||
{
|
||||
if (builtInTypeNames.TryGetValue(t, out var builtin))
|
||||
{
|
||||
return builtin;
|
||||
}
|
||||
if (t.IsGenericParameter) return t.Name;
|
||||
if (t.IsArray) return BeautifyType(t.GetElementType(), shortName) + "[]";
|
||||
if (t.FullName?.StartsWith("System.ValueTuple") ?? false)
|
||||
{
|
||||
return "(" + string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true))) + ")";
|
||||
}
|
||||
if (!t.IsGenericType) return shortName ? t.Name : t.FullName ?? t.Name;
|
||||
|
||||
var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true)));
|
||||
|
||||
var genericType = t.GetGenericTypeDefinition().FullName;
|
||||
if (genericType == "System.Threading.Tasks.Task`1")
|
||||
{
|
||||
genericType = "Task";
|
||||
}
|
||||
|
||||
return typeBeautifyRegex.Replace(genericType, "") + "<" + innerFormat + ">";
|
||||
}
|
||||
|
||||
static bool IgnoreLine(MethodBase methodInfo)
|
||||
{
|
||||
var declareType = methodInfo.DeclaringType.FullName;
|
||||
if (declareType == "System.Threading.ExecutionContext")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("System.Runtime.CompilerServices"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("UniRx.Async.CompilerServices"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType == "System.Threading.Tasks.AwaitTaskContinuation")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("System.Threading.Tasks.Task"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static string SimplifyPath(string path)
|
||||
{
|
||||
var fi = new FileInfo(path);
|
||||
if (fi.Directory == null)
|
||||
{
|
||||
return fi.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fi.Directory.Name + "/" + fi.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f80fb1c9ed4c99447be1b0a47a8d980b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 275b87293edc6634f9d72387851dbbdf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,64 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Async.Editor
|
||||
{
|
||||
// reflection call of UnityEditor.SplitterGUILayout
|
||||
internal static class SplitterGUILayout
|
||||
{
|
||||
static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
static Lazy<Type> splitterStateType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterState");
|
||||
return type;
|
||||
});
|
||||
|
||||
static Lazy<ConstructorInfo> splitterStateCtor = new Lazy<ConstructorInfo>(() =>
|
||||
{
|
||||
var type = splitterStateType.Value;
|
||||
return type.GetConstructor(flags, null, new Type[] { typeof(float[]), typeof(int[]), typeof(int[]) }, null);
|
||||
});
|
||||
|
||||
static Lazy<Type> splitterGUILayoutType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterGUILayout");
|
||||
return type;
|
||||
});
|
||||
|
||||
static Lazy<MethodInfo> beginVerticalSplit = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = splitterGUILayoutType.Value;
|
||||
return type.GetMethod("BeginVerticalSplit", flags, null, new Type[] { splitterStateType.Value, typeof(GUILayoutOption[]) }, null);
|
||||
});
|
||||
|
||||
static Lazy<MethodInfo> endVerticalSplit = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = splitterGUILayoutType.Value;
|
||||
return type.GetMethod("EndVerticalSplit", flags, null, Type.EmptyTypes, null);
|
||||
});
|
||||
|
||||
public static object CreateSplitterState(float[] relativeSizes, int[] minSizes, int[] maxSizes)
|
||||
{
|
||||
return splitterStateCtor.Value.Invoke(new object[] { relativeSizes, minSizes, maxSizes });
|
||||
}
|
||||
|
||||
public static void BeginVerticalSplit(object splitterState, params GUILayoutOption[] options)
|
||||
{
|
||||
beginVerticalSplit.Value.Invoke(null, new object[] { splitterState, options });
|
||||
}
|
||||
|
||||
public static void EndVerticalSplit()
|
||||
{
|
||||
endVerticalSplit.Value.Invoke(null, Type.EmptyTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 40ef2e46f900131419e869398a8d3c9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "UniRx.Async.Editor",
|
||||
"references": [
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4129704b5a1a13841ba16f230bf24a57
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,180 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UniRx.Async.Internal;
|
||||
using System.Text;
|
||||
|
||||
namespace UniRx.Async.Editor
|
||||
{
|
||||
public class UniTaskTrackerViewItem : TreeViewItem
|
||||
{
|
||||
public string TaskType { get; set; }
|
||||
public string Elapsed { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
string position;
|
||||
public string Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
position = value;
|
||||
PositionFirstLine = GetFirstLine(position);
|
||||
}
|
||||
}
|
||||
|
||||
public string PositionFirstLine { get; private set; }
|
||||
|
||||
static string GetFirstLine(string str)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (str[i] == '\r' || str[i] == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
sb.Append(str[i]);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public UniTaskTrackerViewItem(int id) : base(id)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class UniTaskTrackerTreeView : TreeView
|
||||
{
|
||||
const string sortedColumnIndexStateKey = "UniTaskTrackerTreeView_sortedColumnIndex";
|
||||
|
||||
public IReadOnlyList<TreeViewItem> CurrentBindingItems;
|
||||
|
||||
public UniTaskTrackerTreeView()
|
||||
: this(new TreeViewState(), new MultiColumnHeader(new MultiColumnHeaderState(new[]
|
||||
{
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("TaskType"), width = 20},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Elapsed"), width = 10},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Status"), width = 10},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Position")},
|
||||
})))
|
||||
{
|
||||
}
|
||||
|
||||
UniTaskTrackerTreeView(TreeViewState state, MultiColumnHeader header)
|
||||
: base(state, header)
|
||||
{
|
||||
rowHeight = 20;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
showBorder = true;
|
||||
header.sortingChanged += Header_sortingChanged;
|
||||
|
||||
header.ResizeToFit();
|
||||
Reload();
|
||||
|
||||
header.sortedColumnIndex = SessionState.GetInt(sortedColumnIndexStateKey, 1);
|
||||
}
|
||||
|
||||
public void ReloadAndSort()
|
||||
{
|
||||
var currentSelected = this.state.selectedIDs;
|
||||
Reload();
|
||||
Header_sortingChanged(this.multiColumnHeader);
|
||||
this.state.selectedIDs = currentSelected;
|
||||
}
|
||||
|
||||
private void Header_sortingChanged(MultiColumnHeader multiColumnHeader)
|
||||
{
|
||||
SessionState.SetInt(sortedColumnIndexStateKey, multiColumnHeader.sortedColumnIndex);
|
||||
var index = multiColumnHeader.sortedColumnIndex;
|
||||
var ascending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex);
|
||||
|
||||
var items = rootItem.children.Cast<UniTaskTrackerViewItem>();
|
||||
|
||||
IOrderedEnumerable<UniTaskTrackerViewItem> orderedEnumerable;
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.TaskType) : items.OrderByDescending(item => item.TaskType);
|
||||
break;
|
||||
case 1:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => double.Parse(item.Elapsed)) : items.OrderByDescending(item => double.Parse(item.Elapsed));
|
||||
break;
|
||||
case 2:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.Status) : items.OrderByDescending(item => item.Elapsed);
|
||||
break;
|
||||
case 3:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.Position) : items.OrderByDescending(item => item.PositionFirstLine);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(index), index, null);
|
||||
}
|
||||
|
||||
CurrentBindingItems = rootItem.children = orderedEnumerable.Cast<TreeViewItem>().ToList();
|
||||
BuildRows(rootItem);
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var root = new TreeViewItem { depth = -1 };
|
||||
|
||||
var children = new List<TreeViewItem>();
|
||||
|
||||
TaskTracker.ForEachActiveTask((trackingId, awaiterType, status, created, stackTrace) =>
|
||||
{
|
||||
children.Add(new UniTaskTrackerViewItem(trackingId) { TaskType = awaiterType, Status = status.ToString(), Elapsed = (DateTime.UtcNow - created).TotalSeconds.ToString("00.00"), Position = stackTrace });
|
||||
});
|
||||
|
||||
CurrentBindingItems = children;
|
||||
root.children = CurrentBindingItems as List<TreeViewItem>;
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override bool CanMultiSelect(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
var item = args.item as UniTaskTrackerViewItem;
|
||||
|
||||
for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++)
|
||||
{
|
||||
var rect = args.GetCellRect(visibleColumnIndex);
|
||||
var columnIndex = args.GetColumn(visibleColumnIndex);
|
||||
|
||||
var labelStyle = args.selected ? EditorStyles.whiteLabel : EditorStyles.label;
|
||||
labelStyle.alignment = TextAnchor.MiddleLeft;
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0:
|
||||
EditorGUI.LabelField(rect, item.TaskType, labelStyle);
|
||||
break;
|
||||
case 1:
|
||||
EditorGUI.LabelField(rect, item.Elapsed, labelStyle);
|
||||
break;
|
||||
case 2:
|
||||
EditorGUI.LabelField(rect, item.Status, labelStyle);
|
||||
break;
|
||||
case 3:
|
||||
EditorGUI.LabelField(rect, item.PositionFirstLine, labelStyle);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 52e2d973a2156674e8c1c9433ed031f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,211 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UniRx.Async.Internal;
|
||||
|
||||
namespace UniRx.Async.Editor
|
||||
{
|
||||
public class UniTaskTrackerWindow : EditorWindow
|
||||
{
|
||||
static int interval;
|
||||
|
||||
static UniTaskTrackerWindow window;
|
||||
|
||||
[MenuItem("Window/UniRx/UniTask Tracker")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
|
||||
// will called OnEnable(singleton instance will be set).
|
||||
GetWindow<UniTaskTrackerWindow>("UniTask Tracker").Show();
|
||||
}
|
||||
|
||||
static readonly GUILayoutOption[] EmptyLayoutOption = new GUILayoutOption[0];
|
||||
|
||||
UniTaskTrackerTreeView treeView;
|
||||
object splitterState;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
window = this; // set singleton.
|
||||
splitterState = SplitterGUILayout.CreateSplitterState(new float[] { 75f, 25f }, new int[] { 32, 32 }, null);
|
||||
treeView = new UniTaskTrackerTreeView();
|
||||
TaskTracker.EditorEnableState.EnableAutoReload = EditorPrefs.GetBool(TaskTracker.EnableAutoReloadKey, false);
|
||||
TaskTracker.EditorEnableState.EnableTracking = EditorPrefs.GetBool(TaskTracker.EnableTrackingKey, false);
|
||||
TaskTracker.EditorEnableState.EnableStackTrace = EditorPrefs.GetBool(TaskTracker.EnableStackTraceKey, false);
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
// Head
|
||||
RenderHeadPanel();
|
||||
|
||||
// Splittable
|
||||
SplitterGUILayout.BeginVerticalSplit(this.splitterState, EmptyLayoutOption);
|
||||
{
|
||||
// Column Tabble
|
||||
RenderTable();
|
||||
|
||||
// StackTrace details
|
||||
RenderDetailsPanel();
|
||||
}
|
||||
SplitterGUILayout.EndVerticalSplit();
|
||||
}
|
||||
|
||||
#region HeadPanel
|
||||
|
||||
public static bool EnableAutoReload => TaskTracker.EditorEnableState.EnableAutoReload;
|
||||
public static bool EnableTracking => TaskTracker.EditorEnableState.EnableTracking;
|
||||
public static bool EnableStackTrace => TaskTracker.EditorEnableState.EnableStackTrace;
|
||||
static readonly GUIContent EnableAutoReloadHeadContent = EditorGUIUtility.TrTextContent("Enable AutoReload", "Reload automatically.", (Texture)null);
|
||||
static readonly GUIContent ReloadHeadContent = EditorGUIUtility.TrTextContent("Reload", "Reload View.", (Texture)null);
|
||||
static readonly GUIContent GCHeadContent = EditorGUIUtility.TrTextContent("GC.Collect", "Invoke GC.Collect.", (Texture)null);
|
||||
static readonly GUIContent EnableTrackingHeadContent = EditorGUIUtility.TrTextContent("Enable Tracking", "Start to track async/await UniTask. Performance impact: low", (Texture)null);
|
||||
static readonly GUIContent EnableStackTraceHeadContent = EditorGUIUtility.TrTextContent("Enable StackTrace", "Capture StackTrace when task is started. Performance impact: high", (Texture)null);
|
||||
|
||||
// [Enable Tracking] | [Enable StackTrace]
|
||||
void RenderHeadPanel()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EmptyLayoutOption);
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar, EmptyLayoutOption);
|
||||
|
||||
if (GUILayout.Toggle(EnableAutoReload, EnableAutoReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableAutoReload)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableAutoReload = !EnableAutoReload;
|
||||
}
|
||||
|
||||
if (GUILayout.Toggle(EnableTracking, EnableTrackingHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableTracking)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableTracking = !EnableTracking;
|
||||
}
|
||||
|
||||
if (GUILayout.Toggle(EnableStackTrace, EnableStackTraceHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableStackTrace)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableStackTrace = !EnableStackTrace;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button(ReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption))
|
||||
{
|
||||
TaskTracker.CheckAndResetDirty();
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(GCHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption))
|
||||
{
|
||||
GC.Collect(0);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TableColumn
|
||||
|
||||
Vector2 tableScroll;
|
||||
GUIStyle tableListStyle;
|
||||
|
||||
void RenderTable()
|
||||
{
|
||||
if (tableListStyle == null)
|
||||
{
|
||||
tableListStyle = new GUIStyle("CN Box");
|
||||
tableListStyle.margin.top = 0;
|
||||
tableListStyle.padding.left = 3;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginVertical(tableListStyle, EmptyLayoutOption);
|
||||
|
||||
this.tableScroll = EditorGUILayout.BeginScrollView(this.tableScroll, new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MaxWidth(2000f)
|
||||
});
|
||||
var controlRect = EditorGUILayout.GetControlRect(new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true)
|
||||
});
|
||||
|
||||
|
||||
treeView?.OnGUI(controlRect);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (EnableAutoReload)
|
||||
{
|
||||
if (interval++ % 120 == 0)
|
||||
{
|
||||
if (TaskTracker.CheckAndResetDirty())
|
||||
{
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Details
|
||||
|
||||
static GUIStyle detailsStyle;
|
||||
Vector2 detailsScroll;
|
||||
|
||||
void RenderDetailsPanel()
|
||||
{
|
||||
if (detailsStyle == null)
|
||||
{
|
||||
detailsStyle = new GUIStyle(EditorStyles.wordWrappedLabel);
|
||||
detailsStyle.wordWrap = false;
|
||||
detailsStyle.stretchHeight = true;
|
||||
detailsStyle.margin.right = 15;
|
||||
}
|
||||
|
||||
string message = "";
|
||||
var selected = treeView.state.selectedIDs;
|
||||
if (selected.Count > 0)
|
||||
{
|
||||
var first = selected[0];
|
||||
var item = treeView.CurrentBindingItems.FirstOrDefault(x => x.id == first) as UniTaskTrackerViewItem;
|
||||
if (item != null)
|
||||
{
|
||||
message = item.Position;
|
||||
}
|
||||
}
|
||||
|
||||
detailsScroll = EditorGUILayout.BeginScrollView(this.detailsScroll, EmptyLayoutOption);
|
||||
var vector = detailsStyle.CalcSize(new GUIContent(message));
|
||||
EditorGUILayout.SelectableLabel(message, detailsStyle, new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MinWidth(vector.x),
|
||||
GUILayout.MinHeight(vector.y)
|
||||
});
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5bee3e3860e37484aa3b861bf76d129f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class EnumerableAsyncExtensions
|
||||
{
|
||||
// overload resolver - .Select(async x => { }) : IEnumerable<UniTask<T>>
|
||||
|
||||
public static IEnumerable<UniTask> Select<T>(this IEnumerable<T> source, Func<T, UniTask> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask<TR>> Select<T, TR>(this IEnumerable<T> source, Func<T, UniTask<TR>> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask> Select<T>(this IEnumerable<T> source, Func<T, int, UniTask> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask<TR>> Select<T, TR>(this IEnumerable<T> source, Func<T, int, UniTask<TR>> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff50260d74bd54c4b92cf99895549445
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,149 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using UniRx.Async.Internal;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class EnumeratorAsyncExtensions
|
||||
{
|
||||
public static IAwaiter GetAwaiter(this IEnumerator enumerator)
|
||||
{
|
||||
var awaiter = new EnumeratorAwaiter(enumerator, CancellationToken.None);
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, awaiter);
|
||||
}
|
||||
return awaiter;
|
||||
}
|
||||
|
||||
public static UniTask ToUniTask(this IEnumerator enumerator)
|
||||
{
|
||||
var awaiter = new EnumeratorAwaiter(enumerator, CancellationToken.None);
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, awaiter);
|
||||
}
|
||||
return new UniTask(awaiter);
|
||||
}
|
||||
|
||||
public static UniTask ConfigureAwait(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var awaiter = new EnumeratorAwaiter(enumerator, cancellationToken);
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
PlayerLoopHelper.AddAction(timing, awaiter);
|
||||
}
|
||||
return new UniTask(awaiter);
|
||||
}
|
||||
|
||||
class EnumeratorAwaiter : IAwaiter, IPlayerLoopItem
|
||||
{
|
||||
IEnumerator innerEnumerator;
|
||||
CancellationToken cancellationToken;
|
||||
Action continuation;
|
||||
AwaiterStatus status;
|
||||
ExceptionDispatchInfo exception;
|
||||
|
||||
public EnumeratorAwaiter(IEnumerator innerEnumerator, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
status = AwaiterStatus.Canceled;
|
||||
return;
|
||||
}
|
||||
|
||||
this.innerEnumerator = innerEnumerator;
|
||||
this.status = AwaiterStatus.Pending;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.continuation = null;
|
||||
|
||||
TaskTracker.TrackActiveTask(this, 2);
|
||||
}
|
||||
|
||||
public bool IsCompleted => status.IsCompleted();
|
||||
|
||||
public AwaiterStatus Status => status;
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case AwaiterStatus.Succeeded:
|
||||
break;
|
||||
case AwaiterStatus.Pending:
|
||||
Error.ThrowNotYetCompleted();
|
||||
break;
|
||||
case AwaiterStatus.Faulted:
|
||||
exception.Throw();
|
||||
break;
|
||||
case AwaiterStatus.Canceled:
|
||||
Error.ThrowOperationCanceledException();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
InvokeContinuation(AwaiterStatus.Canceled);
|
||||
return false;
|
||||
}
|
||||
|
||||
var success = false;
|
||||
try
|
||||
{
|
||||
if (innerEnumerator.MoveNext())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
}
|
||||
|
||||
InvokeContinuation(success ? AwaiterStatus.Succeeded : AwaiterStatus.Faulted);
|
||||
return false;
|
||||
}
|
||||
|
||||
void InvokeContinuation(AwaiterStatus status)
|
||||
{
|
||||
this.status = status;
|
||||
var cont = this.continuation;
|
||||
|
||||
// cleanup
|
||||
TaskTracker.RemoveTracking(this);
|
||||
this.continuation = null;
|
||||
this.cancellationToken = CancellationToken.None;
|
||||
this.innerEnumerator = null;
|
||||
|
||||
if (cont != null) cont.Invoke();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
Error.ThrowWhenContinuationIsAlreadyRegistered(this.continuation);
|
||||
this.continuation = continuation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bc661232f11e4a741af54ba1c175d5ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,16 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
using System;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
public static bool IsOperationCanceledException(this Exception exception)
|
||||
{
|
||||
return exception is OperationCanceledException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 930800098504c0d46958ce23a0495202
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,64 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async
|
||||
{
|
||||
public enum AwaiterStatus
|
||||
{
|
||||
/// <summary>The operation has not yet completed.</summary>
|
||||
Pending = 0,
|
||||
/// <summary>The operation completed successfully.</summary>
|
||||
Succeeded = 1,
|
||||
/// <summary>The operation completed with an error.</summary>
|
||||
Faulted = 2,
|
||||
/// <summary>The operation completed due to cancellation.</summary>
|
||||
Canceled = 3
|
||||
}
|
||||
|
||||
public interface IAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
AwaiterStatus Status { get; }
|
||||
bool IsCompleted { get; }
|
||||
void GetResult();
|
||||
}
|
||||
|
||||
public interface IAwaiter<out T> : IAwaiter
|
||||
{
|
||||
new T GetResult();
|
||||
}
|
||||
|
||||
public static class AwaiterStatusExtensions
|
||||
{
|
||||
/// <summary>!= Pending.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCompleted(this AwaiterStatus status)
|
||||
{
|
||||
return status != AwaiterStatus.Pending;
|
||||
}
|
||||
|
||||
/// <summary>== Succeeded.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCompletedSuccessfully(this AwaiterStatus status)
|
||||
{
|
||||
return status == AwaiterStatus.Succeeded;
|
||||
}
|
||||
|
||||
/// <summary>== Canceled.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCanceled(this AwaiterStatus status)
|
||||
{
|
||||
return status == AwaiterStatus.Canceled;
|
||||
}
|
||||
|
||||
/// <summary>== Faulted.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsFaulted(this AwaiterStatus status)
|
||||
{
|
||||
return status == AwaiterStatus.Faulted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3e4d023d8404ab742b5e808c98097c3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 633f49a8aafb6fa43894cd4646c71743
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,152 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
// Same interface as System.Buffers.ArrayPool<T> but only provides Shared.
|
||||
|
||||
internal sealed class ArrayPool<T>
|
||||
{
|
||||
// Same size as System.Buffers.DefaultArrayPool<T>
|
||||
const int DefaultMaxNumberOfArraysPerBucket = 50;
|
||||
|
||||
static readonly T[] EmptyArray = new T[0];
|
||||
|
||||
public static readonly ArrayPool<T> Shared = new ArrayPool<T>();
|
||||
|
||||
readonly MinimumQueue<T[]>[] buckets;
|
||||
readonly SpinLock[] locks;
|
||||
|
||||
ArrayPool()
|
||||
{
|
||||
// see: GetQueueIndex
|
||||
buckets = new MinimumQueue<T[]>[18];
|
||||
locks = new SpinLock[18];
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
buckets[i] = new MinimumQueue<T[]>(4);
|
||||
locks[i] = new SpinLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Rent(int minimumLength)
|
||||
{
|
||||
if (minimumLength < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minimumLength");
|
||||
}
|
||||
else if (minimumLength == 0)
|
||||
{
|
||||
return EmptyArray;
|
||||
}
|
||||
|
||||
var size = CalculateSize(minimumLength);
|
||||
var index = GetQueueIndex(size);
|
||||
if (index != -1)
|
||||
{
|
||||
var q = buckets[index];
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
locks[index].Enter(ref lockTaken);
|
||||
|
||||
if (q.Count != 0)
|
||||
{
|
||||
return q.Dequeue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) locks[index].Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new T[size];
|
||||
}
|
||||
|
||||
public void Return(T[] array, bool clearArray = false)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var index = GetQueueIndex(array.Length);
|
||||
if (index != -1)
|
||||
{
|
||||
if (clearArray)
|
||||
{
|
||||
Array.Clear(array, 0, array.Length);
|
||||
}
|
||||
|
||||
var q = buckets[index];
|
||||
var lockTaken = false;
|
||||
|
||||
try
|
||||
{
|
||||
locks[index].Enter(ref lockTaken);
|
||||
|
||||
if (q.Count > DefaultMaxNumberOfArraysPerBucket)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
q.Enqueue(array);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) locks[index].Exit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CalculateSize(int size)
|
||||
{
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
size += 1;
|
||||
|
||||
if (size < 8)
|
||||
{
|
||||
size = 8;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int GetQueueIndex(int size)
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case 8: return 0;
|
||||
case 16: return 1;
|
||||
case 32: return 2;
|
||||
case 64: return 3;
|
||||
case 128: return 4;
|
||||
case 256: return 5;
|
||||
case 512: return 6;
|
||||
case 1024: return 7;
|
||||
case 2048: return 8;
|
||||
case 4096: return 9;
|
||||
case 8192: return 10;
|
||||
case 16384: return 11;
|
||||
case 32768: return 12;
|
||||
case 65536: return 13;
|
||||
case 131072: return 14;
|
||||
case 262144: return 15;
|
||||
case 524288: return 16;
|
||||
case 1048576: return 17; // max array length
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f83ebad81fb89fb4882331616ca6d248
|
||||
timeCreated: 1532361008
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,112 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class ArrayPoolUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void EnsureCapacity<T>(ref T[] array, int index, ArrayPool<T> pool)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
EnsureCapacityCore(ref array, index, pool);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void EnsureCapacityCore<T>(ref T[] array, int index, ArrayPool<T> pool)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
var newSize = array.Length * 2;
|
||||
var newArray = pool.Rent((index < newSize) ? newSize : (index * 2));
|
||||
Array.Copy(array, 0, newArray, 0, array.Length);
|
||||
|
||||
pool.Return(array, clearArray: !RuntimeHelpersAbstraction.IsWellKnownNoReferenceContainsType<T>());
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
public static RentArray<T> Materialize<T>(IEnumerable<T> source)
|
||||
{
|
||||
if (source is T[] array)
|
||||
{
|
||||
return new RentArray<T>(array, array.Length, null);
|
||||
}
|
||||
|
||||
var defaultCount = 4;
|
||||
if (source is ICollection<T> coll)
|
||||
{
|
||||
defaultCount = coll.Count;
|
||||
var pool = ArrayPool<T>.Shared;
|
||||
var buffer = pool.Rent(defaultCount);
|
||||
coll.CopyTo(buffer, 0);
|
||||
return new RentArray<T>(buffer, coll.Count, pool);
|
||||
}
|
||||
else if (source is IReadOnlyCollection<T> rcoll)
|
||||
{
|
||||
defaultCount = rcoll.Count;
|
||||
}
|
||||
|
||||
if (defaultCount == 0)
|
||||
{
|
||||
return new RentArray<T>(Array.Empty<T>(), 0, null);
|
||||
}
|
||||
|
||||
{
|
||||
var pool = ArrayPool<T>.Shared;
|
||||
|
||||
var index = 0;
|
||||
var buffer = pool.Rent(defaultCount);
|
||||
foreach (var item in source)
|
||||
{
|
||||
EnsureCapacity(ref buffer, index, pool);
|
||||
buffer[index++] = item;
|
||||
}
|
||||
|
||||
return new RentArray<T>(buffer, index, pool);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RentArray<T> : IDisposable
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly int Length;
|
||||
ArrayPool<T> pool;
|
||||
|
||||
public RentArray(T[] array, int length, ArrayPool<T> pool)
|
||||
{
|
||||
this.Array = array;
|
||||
this.Length = length;
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeManually(!RuntimeHelpersAbstraction.IsWellKnownNoReferenceContainsType<T>());
|
||||
}
|
||||
|
||||
public void DisposeManually(bool clearArray)
|
||||
{
|
||||
if (pool != null)
|
||||
{
|
||||
if (clearArray)
|
||||
{
|
||||
System.Array.Clear(Array, 0, Length);
|
||||
}
|
||||
|
||||
pool.Return(Array, clearArray: false);
|
||||
pool = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 424cc208fb61d4e448b08fcfa0eee25e
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,75 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class ArrayUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void EnsureCapacity<T>(ref T[] array, int index)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
EnsureCore(ref array, index);
|
||||
}
|
||||
}
|
||||
|
||||
// rare case, no inlining.
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void EnsureCore<T>(ref T[] array, int index)
|
||||
{
|
||||
var newSize = array.Length * 2;
|
||||
var newArray = new T[(index < newSize) ? newSize : (index * 2)];
|
||||
Array.Copy(array, 0, newArray, 0, array.Length);
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimizing utility to avoid .ToArray() that creates buffer copy(cut to just size).
|
||||
/// </summary>
|
||||
public static (T[] array, int length) Materialize<T>(IEnumerable<T> source)
|
||||
{
|
||||
if (source is T[] array)
|
||||
{
|
||||
return (array, array.Length);
|
||||
}
|
||||
|
||||
var defaultCount = 4;
|
||||
if (source is ICollection<T> coll)
|
||||
{
|
||||
defaultCount = coll.Count;
|
||||
var buffer = new T[defaultCount];
|
||||
coll.CopyTo(buffer, 0);
|
||||
return (buffer, defaultCount);
|
||||
}
|
||||
else if (source is IReadOnlyCollection<T> rcoll)
|
||||
{
|
||||
defaultCount = rcoll.Count;
|
||||
}
|
||||
|
||||
if (defaultCount == 0)
|
||||
{
|
||||
return (Array.Empty<T>(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
var index = 0;
|
||||
var buffer = new T[defaultCount];
|
||||
foreach (var item in source)
|
||||
{
|
||||
EnsureCapacity(ref buffer, index);
|
||||
buffer[index++] = item;
|
||||
}
|
||||
|
||||
return (buffer, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23146a82ec99f2542a87971c8d3d7988
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,32 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class CancellationTokenHelper
|
||||
{
|
||||
public static bool TrySetOrLinkCancellationToken(ref CancellationToken field, CancellationToken newCancellationToken)
|
||||
{
|
||||
if (newCancellationToken == CancellationToken.None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (field == CancellationToken.None)
|
||||
{
|
||||
field = newCancellationToken;
|
||||
return true;
|
||||
}
|
||||
else if (field == newCancellationToken)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
field = CancellationTokenSource.CreateLinkedTokenSource(field, newCancellationToken).Token;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 53d1b536fc7e2d4458294ee2c7d9b743
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,115 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal class ContinuationQueue
|
||||
{
|
||||
const int MaxArrayLength = 0X7FEFFFFF;
|
||||
const int InitialSize = 16;
|
||||
|
||||
SpinLock gate = new SpinLock();
|
||||
bool dequing = false;
|
||||
|
||||
int actionListCount = 0;
|
||||
Action[] actionList = new Action[InitialSize];
|
||||
|
||||
int waitingListCount = 0;
|
||||
Action[] waitingList = new Action[InitialSize];
|
||||
|
||||
public void Enqueue(Action continuation)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
|
||||
if (dequing)
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (waitingList.Length == waitingListCount)
|
||||
{
|
||||
var newLength = waitingListCount * 2;
|
||||
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
|
||||
|
||||
var newArray = new Action[newLength];
|
||||
Array.Copy(waitingList, newArray, waitingListCount);
|
||||
waitingList = newArray;
|
||||
}
|
||||
waitingList[waitingListCount] = continuation;
|
||||
waitingListCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (actionList.Length == actionListCount)
|
||||
{
|
||||
var newLength = actionListCount * 2;
|
||||
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
|
||||
|
||||
var newArray = new Action[newLength];
|
||||
Array.Copy(actionList, newArray, actionListCount);
|
||||
actionList = newArray;
|
||||
}
|
||||
actionList[actionListCount] = continuation;
|
||||
actionListCount++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
if (actionListCount == 0) return;
|
||||
dequing = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < actionListCount; i++)
|
||||
{
|
||||
var action = actionList[i];
|
||||
actionList[i] = null;
|
||||
|
||||
action();
|
||||
}
|
||||
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
dequing = false;
|
||||
|
||||
var swapTempActionList = actionList;
|
||||
|
||||
actionListCount = waitingListCount;
|
||||
actionList = waitingList;
|
||||
|
||||
waitingListCount = 0;
|
||||
waitingList = swapTempActionList;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f66c32454e50f2546b17deadc80a4c77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,63 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class Error
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowArgumentNullException<T>(T value, string paramName)
|
||||
where T : class
|
||||
{
|
||||
if (value == null) ThrowArgumentNullExceptionCore(paramName);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void ThrowArgumentNullExceptionCore(string paramName)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowArgumentException<T>(string message)
|
||||
{
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowNotYetCompleted()
|
||||
{
|
||||
throw new InvalidOperationException("Not yet completed.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T ThrowNotYetCompleted<T>()
|
||||
{
|
||||
throw new InvalidOperationException("Not yet completed.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowWhenContinuationIsAlreadyRegistered<T>(T continuationField)
|
||||
where T : class
|
||||
{
|
||||
if (continuationField != null) ThrowInvalidOperationExceptionCore("continuation is already registered.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void ThrowInvalidOperationExceptionCore(string message)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowOperationCanceledException()
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5f39f495294d4604b8082202faf98554
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,23 @@
|
|||
#if NET_4_6 || NET_STANDARD_2_0 || CSHARP_7_OR_LATER
|
||||
|
||||
using System;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class FuncExtensions
|
||||
{
|
||||
// avoid lambda capture
|
||||
|
||||
internal static Action<T> AsFuncOfT<T>(this Action action)
|
||||
{
|
||||
return new Action<T>(action.Invoke);
|
||||
}
|
||||
|
||||
static void Invoke<T>(this Action action, T unused)
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4d5a9a3e1f0f069478969f752fde29a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,130 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal sealed class LazyPromise : IAwaiter
|
||||
{
|
||||
Func<UniTask> factory;
|
||||
UniTask value;
|
||||
|
||||
public LazyPromise(Func<UniTask> factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
void Create()
|
||||
{
|
||||
var f = Interlocked.Exchange(ref factory, null);
|
||||
if (f != null)
|
||||
{
|
||||
value = f();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
Create();
|
||||
return value.IsCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
public AwaiterStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
Create();
|
||||
return value.Status;
|
||||
}
|
||||
}
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
Create();
|
||||
value.GetResult();
|
||||
}
|
||||
|
||||
void IAwaiter.GetResult()
|
||||
{
|
||||
GetResult();
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
Create();
|
||||
value.GetAwaiter().UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class LazyPromise<T> : IAwaiter<T>
|
||||
{
|
||||
Func<UniTask<T>> factory;
|
||||
UniTask<T> value;
|
||||
|
||||
public LazyPromise(Func<UniTask<T>> factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
void Create()
|
||||
{
|
||||
var f = Interlocked.Exchange(ref factory, null);
|
||||
if (f != null)
|
||||
{
|
||||
value = f();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
Create();
|
||||
return value.IsCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
public AwaiterStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
Create();
|
||||
return value.Status;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
Create();
|
||||
return value.Result;
|
||||
}
|
||||
|
||||
void IAwaiter.GetResult()
|
||||
{
|
||||
GetResult();
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
Create();
|
||||
value.GetAwaiter().UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fe7a4187b7f89f84582fd1e466a7f27e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,122 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
// optimized version of Standard Queue<T>.
|
||||
internal class MinimumQueue<T>
|
||||
{
|
||||
const int MinimumGrow = 4;
|
||||
const int GrowFactor = 200;
|
||||
|
||||
T[] array;
|
||||
int head;
|
||||
int tail;
|
||||
int size;
|
||||
|
||||
public MinimumQueue(int capacity)
|
||||
{
|
||||
if (capacity < 0) throw new ArgumentOutOfRangeException("capacity");
|
||||
array = new T[capacity];
|
||||
head = tail = size = 0;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
#if NET_4_6 || NET_STANDARD_2_0
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
get { return size; }
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (size == 0) ThrowForEmptyQueue();
|
||||
return array[head];
|
||||
}
|
||||
|
||||
#if NET_4_6 || NET_STANDARD_2_0
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (size == array.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
array[tail] = item;
|
||||
MoveNext(ref tail);
|
||||
size++;
|
||||
}
|
||||
|
||||
#if NET_4_6 || NET_STANDARD_2_0
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
public T Dequeue()
|
||||
{
|
||||
if (size == 0) ThrowForEmptyQueue();
|
||||
|
||||
int head = this.head;
|
||||
T[] array = this.array;
|
||||
T removed = array[head];
|
||||
array[head] = default(T);
|
||||
MoveNext(ref this.head);
|
||||
size--;
|
||||
return removed;
|
||||
}
|
||||
|
||||
void Grow()
|
||||
{
|
||||
int newcapacity = (int)((long)array.Length * (long)GrowFactor / 100);
|
||||
if (newcapacity < array.Length + MinimumGrow)
|
||||
{
|
||||
newcapacity = array.Length + MinimumGrow;
|
||||
}
|
||||
SetCapacity(newcapacity);
|
||||
}
|
||||
|
||||
void SetCapacity(int capacity)
|
||||
{
|
||||
T[] newarray = new T[capacity];
|
||||
if (size > 0)
|
||||
{
|
||||
if (head < tail)
|
||||
{
|
||||
Array.Copy(array, head, newarray, 0, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(array, head, newarray, 0, array.Length - head);
|
||||
Array.Copy(array, 0, newarray, array.Length - head, tail);
|
||||
}
|
||||
}
|
||||
|
||||
array = newarray;
|
||||
head = 0;
|
||||
tail = (size == capacity) ? 0 : size;
|
||||
}
|
||||
|
||||
#if NET_4_6 || NET_STANDARD_2_0
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#endif
|
||||
void MoveNext(ref int index)
|
||||
{
|
||||
int tmp = index + 1;
|
||||
if (tmp == array.Length)
|
||||
{
|
||||
tmp = 0;
|
||||
}
|
||||
index = tmp;
|
||||
}
|
||||
|
||||
void ThrowForEmptyQueue()
|
||||
{
|
||||
throw new InvalidOperationException("EmptyQueue");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7d63add489ccc99498114d79702b904d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,153 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal sealed class PlayerLoopRunner
|
||||
{
|
||||
const int InitialSize = 16;
|
||||
|
||||
readonly object runningAndQueueLock = new object();
|
||||
readonly object arrayLock = new object();
|
||||
readonly Action<Exception> unhandledExceptionCallback;
|
||||
|
||||
int tail = 0;
|
||||
bool running = false;
|
||||
IPlayerLoopItem[] loopItems = new IPlayerLoopItem[InitialSize];
|
||||
MinimumQueue<IPlayerLoopItem> waitQueue = new MinimumQueue<IPlayerLoopItem>(InitialSize);
|
||||
|
||||
public PlayerLoopRunner()
|
||||
{
|
||||
this.unhandledExceptionCallback = ex => Debug.LogException(ex);
|
||||
}
|
||||
|
||||
public void AddAction(IPlayerLoopItem item)
|
||||
{
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
waitQueue.Enqueue(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lock (arrayLock)
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (loopItems.Length == tail)
|
||||
{
|
||||
Array.Resize(ref loopItems, checked(tail * 2));
|
||||
}
|
||||
loopItems[tail++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
running = true;
|
||||
}
|
||||
|
||||
lock (arrayLock)
|
||||
{
|
||||
var j = tail - 1;
|
||||
|
||||
// eliminate array-bound check for i
|
||||
for (int i = 0; i < loopItems.Length; i++)
|
||||
{
|
||||
var action = loopItems[i];
|
||||
if (action != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!action.MoveNext())
|
||||
{
|
||||
loopItems[i] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // next i
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loopItems[i] = null;
|
||||
try
|
||||
{
|
||||
unhandledExceptionCallback(ex);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// find null, loop from tail
|
||||
while (i < j)
|
||||
{
|
||||
var fromTail = loopItems[j];
|
||||
if (fromTail != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fromTail.MoveNext())
|
||||
{
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
continue; // next j
|
||||
}
|
||||
else
|
||||
{
|
||||
// swap
|
||||
loopItems[i] = fromTail;
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
goto NEXT_LOOP; // next i
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
try
|
||||
{
|
||||
unhandledExceptionCallback(ex);
|
||||
}
|
||||
catch { }
|
||||
continue; // next j
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
tail = i; // loop end
|
||||
break; // LOOP END
|
||||
|
||||
NEXT_LOOP:
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
running = false;
|
||||
while (waitQueue.Count != 0)
|
||||
{
|
||||
if (loopItems.Length == tail)
|
||||
{
|
||||
Array.Resize(ref loopItems, checked(tail * 2));
|
||||
}
|
||||
loopItems[tail++] = waitQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 340c6d420bb4f484aa8683415ea92571
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,34 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class PromiseHelper
|
||||
{
|
||||
internal static void TrySetResultAll<TPromise, T>(IEnumerable<TPromise> source, T value)
|
||||
where TPromise : class, IResolvePromise<T>
|
||||
{
|
||||
var rentArray = ArrayPoolUtil.Materialize(source);
|
||||
var clearArray = true;
|
||||
try
|
||||
{
|
||||
var array = rentArray.Array;
|
||||
var len = rentArray.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
array[i].TrySetResult(value);
|
||||
array[i] = null;
|
||||
}
|
||||
clearArray = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
rentArray.DisposeManually(clearArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 173f9b763911bf847b7dfbf31ee87fc4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,395 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
// public for some types uses it.
|
||||
|
||||
public abstract class ReusablePromise : IAwaiter
|
||||
{
|
||||
ExceptionDispatchInfo exception;
|
||||
object continuation; // Action or Queue<Action>
|
||||
AwaiterStatus status;
|
||||
|
||||
public UniTask Task => new UniTask(this);
|
||||
|
||||
// can override for control 'start/reset' timing.
|
||||
public virtual bool IsCompleted => status.IsCompleted();
|
||||
|
||||
public virtual void GetResult()
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case AwaiterStatus.Succeeded:
|
||||
return;
|
||||
case AwaiterStatus.Faulted:
|
||||
exception.Throw();
|
||||
break;
|
||||
case AwaiterStatus.Canceled:
|
||||
throw new OperationCanceledException();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Invalid Status:" + status);
|
||||
}
|
||||
|
||||
public AwaiterStatus Status => status;
|
||||
|
||||
void IAwaiter.GetResult()
|
||||
{
|
||||
GetResult();
|
||||
}
|
||||
|
||||
public void ResetStatus(bool forceReset)
|
||||
{
|
||||
if (forceReset)
|
||||
{
|
||||
status = AwaiterStatus.Pending;
|
||||
}
|
||||
else if (status == AwaiterStatus.Succeeded)
|
||||
{
|
||||
status = AwaiterStatus.Pending;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool TrySetCanceled()
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Canceled;
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool TrySetException(Exception ex)
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Faulted;
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool TrySetResult()
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Succeeded;
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TryInvokeContinuation()
|
||||
{
|
||||
if (continuation == null) return;
|
||||
|
||||
if (continuation is Action act)
|
||||
{
|
||||
continuation = null;
|
||||
act();
|
||||
}
|
||||
else
|
||||
{
|
||||
// reuse Queue(don't null clear)
|
||||
var q = (MinimumQueue<Action>)continuation;
|
||||
var size = q.Count;
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
q.Dequeue().Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCompleted(Action action)
|
||||
{
|
||||
UnsafeOnCompleted(action);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action action)
|
||||
{
|
||||
if (continuation == null)
|
||||
{
|
||||
continuation = action;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (continuation is Action act)
|
||||
{
|
||||
var q = new MinimumQueue<Action>(4);
|
||||
q.Enqueue(act);
|
||||
q.Enqueue(action);
|
||||
continuation = q;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
((MinimumQueue<Action>)continuation).Enqueue(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ReusablePromise<T> : IAwaiter<T>
|
||||
{
|
||||
T result;
|
||||
ExceptionDispatchInfo exception;
|
||||
object continuation; // Action or Queue<Action>
|
||||
AwaiterStatus status;
|
||||
|
||||
public UniTask<T> Task => new UniTask<T>(this);
|
||||
|
||||
// can override for control 'start/reset' timing.
|
||||
public virtual bool IsCompleted => status.IsCompleted();
|
||||
|
||||
protected T RawResult => result;
|
||||
|
||||
protected void ForceSetResult(T result)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public virtual T GetResult()
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case AwaiterStatus.Succeeded:
|
||||
return result;
|
||||
case AwaiterStatus.Faulted:
|
||||
exception.Throw();
|
||||
break;
|
||||
case AwaiterStatus.Canceled:
|
||||
throw new OperationCanceledException();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Invalid Status:" + status);
|
||||
}
|
||||
|
||||
public AwaiterStatus Status => status;
|
||||
|
||||
void IAwaiter.GetResult()
|
||||
{
|
||||
GetResult();
|
||||
}
|
||||
|
||||
public void ResetStatus(bool forceReset)
|
||||
{
|
||||
if (forceReset)
|
||||
{
|
||||
status = AwaiterStatus.Pending;
|
||||
}
|
||||
else if (status == AwaiterStatus.Succeeded)
|
||||
{
|
||||
status = AwaiterStatus.Pending;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool TrySetCanceled()
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Canceled;
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool TrySetException(Exception ex)
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Faulted;
|
||||
exception = ExceptionDispatchInfo.Capture(ex);
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool TrySetResult(T result)
|
||||
{
|
||||
if (status == AwaiterStatus.Pending)
|
||||
{
|
||||
status = AwaiterStatus.Succeeded;
|
||||
this.result = result;
|
||||
TryInvokeContinuation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void TryInvokeContinuation()
|
||||
{
|
||||
if (continuation == null) return;
|
||||
|
||||
if (continuation is Action act)
|
||||
{
|
||||
continuation = null;
|
||||
act();
|
||||
}
|
||||
else
|
||||
{
|
||||
// reuse Queue(don't null clear)
|
||||
var q = (MinimumQueue<Action>)continuation;
|
||||
var size = q.Count;
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
q.Dequeue().Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCompleted(Action action)
|
||||
{
|
||||
UnsafeOnCompleted(action);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action action)
|
||||
{
|
||||
if (continuation == null)
|
||||
{
|
||||
continuation = action;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (continuation is Action act)
|
||||
{
|
||||
var q = new MinimumQueue<Action>(4);
|
||||
q.Enqueue(act);
|
||||
q.Enqueue(action);
|
||||
continuation = q;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
((MinimumQueue<Action>)continuation).Enqueue(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PlayerLoopReusablePromiseBase : ReusablePromise, IPlayerLoopItem
|
||||
{
|
||||
readonly PlayerLoopTiming timing;
|
||||
protected readonly CancellationToken cancellationToken;
|
||||
bool isRunning = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
string capturedStackTraceForDebugging;
|
||||
#endif
|
||||
|
||||
public PlayerLoopReusablePromiseBase(PlayerLoopTiming timing, CancellationToken cancellationToken, int skipTrackFrameCountAdditive)
|
||||
{
|
||||
this.timing = timing;
|
||||
this.cancellationToken = cancellationToken;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
this.capturedStackTraceForDebugging = TaskTracker.CaptureStackTrace(skipTrackFrameCountAdditive + 1); // 1 is self,
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Status == AwaiterStatus.Canceled || Status == AwaiterStatus.Faulted) return true;
|
||||
|
||||
if (!isRunning)
|
||||
{
|
||||
isRunning = true;
|
||||
ResetStatus(false);
|
||||
OnRunningStart();
|
||||
#if UNITY_EDITOR
|
||||
TaskTracker.TrackActiveTask(this, capturedStackTraceForDebugging);
|
||||
#endif
|
||||
PlayerLoopHelper.AddAction(timing, this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnRunningStart();
|
||||
|
||||
protected void Complete()
|
||||
{
|
||||
isRunning = false;
|
||||
#if UNITY_EDITOR
|
||||
TaskTracker.RemoveTracking(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public abstract bool MoveNext();
|
||||
}
|
||||
|
||||
public abstract class PlayerLoopReusablePromiseBase<T> : ReusablePromise<T>, IPlayerLoopItem
|
||||
{
|
||||
readonly PlayerLoopTiming timing;
|
||||
protected readonly CancellationToken cancellationToken;
|
||||
bool isRunning = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
string capturedStackTraceForDebugging;
|
||||
#endif
|
||||
|
||||
public PlayerLoopReusablePromiseBase(PlayerLoopTiming timing, CancellationToken cancellationToken, int skipTrackFrameCountAdditive)
|
||||
{
|
||||
this.timing = timing;
|
||||
this.cancellationToken = cancellationToken;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
this.capturedStackTraceForDebugging = TaskTracker.CaptureStackTrace(skipTrackFrameCountAdditive + 1); // 1 is self,
|
||||
#endif
|
||||
}
|
||||
|
||||
public override bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Status == AwaiterStatus.Canceled || Status == AwaiterStatus.Faulted) return true;
|
||||
|
||||
if (!isRunning)
|
||||
{
|
||||
isRunning = true;
|
||||
ResetStatus(false);
|
||||
OnRunningStart();
|
||||
#if UNITY_EDITOR
|
||||
TaskTracker.TrackActiveTask(this, capturedStackTraceForDebugging);
|
||||
#endif
|
||||
PlayerLoopHelper.AddAction(timing, this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnRunningStart();
|
||||
|
||||
protected void Complete()
|
||||
{
|
||||
isRunning = false;
|
||||
#if UNITY_EDITOR
|
||||
TaskTracker.RemoveTracking(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public abstract bool MoveNext();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8cfc99b5928c0242919aac2121b02bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,60 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class RuntimeHelpersAbstraction
|
||||
{
|
||||
// If we can use RuntimeHelpers.IsReferenceOrContainsReferences(.NET Core 2.0), use it.
|
||||
public static bool IsWellKnownNoReferenceContainsType<T>()
|
||||
{
|
||||
return WellKnownNoReferenceContainsType<T>.IsWellKnownType;
|
||||
}
|
||||
|
||||
static bool WellKnownNoReferenceContainsTypeInitialize(Type t)
|
||||
{
|
||||
// The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.
|
||||
if (t.IsPrimitive) return true;
|
||||
|
||||
if (t.IsEnum) return true;
|
||||
if (t == typeof(DateTime)) return true;
|
||||
if (t == typeof(DateTimeOffset)) return true;
|
||||
if (t == typeof(Guid)) return true;
|
||||
if (t == typeof(decimal)) return true;
|
||||
|
||||
// unwrap nullable
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
return WellKnownNoReferenceContainsTypeInitialize(t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
// or add other wellknown types(Vector, etc...) here
|
||||
if (t == typeof(Vector2)) return true;
|
||||
if (t == typeof(Vector3)) return true;
|
||||
if (t == typeof(Vector4)) return true;
|
||||
if (t == typeof(Color)) return true;
|
||||
if (t == typeof(Rect)) return true;
|
||||
if (t == typeof(Bounds)) return true;
|
||||
if (t == typeof(Quaternion)) return true;
|
||||
if (t == typeof(Vector2Int)) return true;
|
||||
if (t == typeof(Vector3Int)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static class WellKnownNoReferenceContainsType<T>
|
||||
{
|
||||
public static readonly bool IsWellKnownType;
|
||||
|
||||
static WellKnownNoReferenceContainsType()
|
||||
{
|
||||
IsWellKnownType = WellKnownNoReferenceContainsTypeInitialize(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 94975e4d4e0c0ea4ba787d3872ce9bb4
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,152 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
// public for add user custom.
|
||||
|
||||
public static class TaskTracker
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
static int trackingId = 0;
|
||||
|
||||
public const string EnableAutoReloadKey = "UniTaskTrackerWindow_EnableAutoReloadKey";
|
||||
public const string EnableTrackingKey = "UniTaskTrackerWindow_EnableTrackingKey";
|
||||
public const string EnableStackTraceKey = "UniTaskTrackerWindow_EnableStackTraceKey";
|
||||
|
||||
public static class EditorEnableState
|
||||
{
|
||||
static bool enableAutoReload;
|
||||
public static bool EnableAutoReload
|
||||
{
|
||||
get { return enableAutoReload; }
|
||||
set
|
||||
{
|
||||
enableAutoReload = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableAutoReloadKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
static bool enableTracking;
|
||||
public static bool EnableTracking
|
||||
{
|
||||
get { return enableTracking; }
|
||||
set
|
||||
{
|
||||
enableTracking = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableTrackingKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
static bool enableStackTrace;
|
||||
public static bool EnableStackTrace
|
||||
{
|
||||
get { return enableStackTrace; }
|
||||
set
|
||||
{
|
||||
enableStackTrace = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableStackTraceKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static List<KeyValuePair<IAwaiter, (int trackingId, DateTime addTime, string stackTrace)>> listPool = new List<KeyValuePair<IAwaiter, (int trackingId, DateTime addTime, string stackTrace)>>();
|
||||
|
||||
static readonly WeakDictionary<IAwaiter, (int trackingId, DateTime addTime, string stackTrace)> tracking = new WeakDictionary<IAwaiter, (int trackingId, DateTime addTime, string stackTrace)>();
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void TrackActiveTask(IAwaiter task, int skipFrame = 1)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
dirty = true;
|
||||
if (!EditorEnableState.EnableTracking) return;
|
||||
var stackTrace = EditorEnableState.EnableStackTrace ? new StackTrace(skipFrame, true).CleanupAsyncStackTrace() : "";
|
||||
tracking.TryAdd(task, (Interlocked.Increment(ref trackingId), DateTime.UtcNow, stackTrace));
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void TrackActiveTask(IAwaiter task, string stackTrace)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
dirty = true;
|
||||
if (!EditorEnableState.EnableTracking) return;
|
||||
var success = tracking.TryAdd(task, (Interlocked.Increment(ref trackingId), DateTime.UtcNow, stackTrace));
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string CaptureStackTrace(int skipFrame)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!EditorEnableState.EnableTracking) return "";
|
||||
var stackTrace = EditorEnableState.EnableStackTrace ? new StackTrace(skipFrame + 1, true).CleanupAsyncStackTrace() : "";
|
||||
return stackTrace;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void RemoveTracking(IAwaiter task)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
dirty = true;
|
||||
if (!EditorEnableState.EnableTracking) return;
|
||||
var success = tracking.TryRemove(task);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool dirty;
|
||||
|
||||
public static bool CheckAndResetDirty()
|
||||
{
|
||||
var current = dirty;
|
||||
dirty = false;
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>(trackingId, awaiterType, awaiterStatus, createdTime, stackTrace)</summary>
|
||||
public static void ForEachActiveTask(Action<int, string, AwaiterStatus, DateTime, string> action)
|
||||
{
|
||||
lock (listPool)
|
||||
{
|
||||
var count = tracking.ToList(ref listPool, clear: false);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string typeName = null;
|
||||
var keyType = listPool[i].Key.GetType();
|
||||
if (keyType.IsNested)
|
||||
{
|
||||
typeName = keyType.DeclaringType.Name + "." + keyType.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeName = keyType.Name;
|
||||
}
|
||||
|
||||
action(listPool[i].Value.trackingId, typeName, listPool[i].Key.Status, listPool[i].Value.addTime, listPool[i].Value.stackTrace);
|
||||
listPool[i] = new KeyValuePair<IAwaiter, (int trackingId, DateTime addTime, string stackTrace)>(null, (0, default(DateTime), null)); // clear
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
listPool.Clear();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a203c73eb4ccdbb44bddfd82d38fdda9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,271 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
internal static class UnityEqualityComparer
|
||||
{
|
||||
public static readonly IEqualityComparer<Vector2> Vector2 = new Vector2EqualityComparer();
|
||||
public static readonly IEqualityComparer<Vector3> Vector3 = new Vector3EqualityComparer();
|
||||
public static readonly IEqualityComparer<Vector4> Vector4 = new Vector4EqualityComparer();
|
||||
public static readonly IEqualityComparer<Color> Color = new ColorEqualityComparer();
|
||||
public static readonly IEqualityComparer<Color32> Color32 = new Color32EqualityComparer();
|
||||
public static readonly IEqualityComparer<Rect> Rect = new RectEqualityComparer();
|
||||
public static readonly IEqualityComparer<Bounds> Bounds = new BoundsEqualityComparer();
|
||||
public static readonly IEqualityComparer<Quaternion> Quaternion = new QuaternionEqualityComparer();
|
||||
|
||||
static readonly RuntimeTypeHandle vector2Type = typeof(Vector2).TypeHandle;
|
||||
static readonly RuntimeTypeHandle vector3Type = typeof(Vector3).TypeHandle;
|
||||
static readonly RuntimeTypeHandle vector4Type = typeof(Vector4).TypeHandle;
|
||||
static readonly RuntimeTypeHandle colorType = typeof(Color).TypeHandle;
|
||||
static readonly RuntimeTypeHandle color32Type = typeof(Color32).TypeHandle;
|
||||
static readonly RuntimeTypeHandle rectType = typeof(Rect).TypeHandle;
|
||||
static readonly RuntimeTypeHandle boundsType = typeof(Bounds).TypeHandle;
|
||||
static readonly RuntimeTypeHandle quaternionType = typeof(Quaternion).TypeHandle;
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
|
||||
public static readonly IEqualityComparer<Vector2Int> Vector2Int = new Vector2IntEqualityComparer();
|
||||
public static readonly IEqualityComparer<Vector3Int> Vector3Int = new Vector3IntEqualityComparer();
|
||||
public static readonly IEqualityComparer<RangeInt> RangeInt = new RangeIntEqualityComparer();
|
||||
public static readonly IEqualityComparer<RectInt> RectInt = new RectIntEqualityComparer();
|
||||
public static readonly IEqualityComparer<BoundsInt> BoundsInt = new BoundsIntEqualityComparer();
|
||||
|
||||
static readonly RuntimeTypeHandle vector2IntType = typeof(Vector2Int).TypeHandle;
|
||||
static readonly RuntimeTypeHandle vector3IntType = typeof(Vector3Int).TypeHandle;
|
||||
static readonly RuntimeTypeHandle rangeIntType = typeof(RangeInt).TypeHandle;
|
||||
static readonly RuntimeTypeHandle rectIntType = typeof(RectInt).TypeHandle;
|
||||
static readonly RuntimeTypeHandle boundsIntType = typeof(BoundsInt).TypeHandle;
|
||||
|
||||
#endif
|
||||
|
||||
static class Cache<T>
|
||||
{
|
||||
public static readonly IEqualityComparer<T> Comparer;
|
||||
|
||||
static Cache()
|
||||
{
|
||||
var comparer = GetDefaultHelper(typeof(T));
|
||||
if (comparer == null)
|
||||
{
|
||||
Comparer = EqualityComparer<T>.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Comparer = (IEqualityComparer<T>)comparer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEqualityComparer<T> GetDefault<T>()
|
||||
{
|
||||
return Cache<T>.Comparer;
|
||||
}
|
||||
|
||||
static object GetDefaultHelper(Type type)
|
||||
{
|
||||
var t = type.TypeHandle;
|
||||
|
||||
if (t.Equals(vector2Type)) return (object)UnityEqualityComparer.Vector2;
|
||||
if (t.Equals(vector3Type)) return (object)UnityEqualityComparer.Vector3;
|
||||
if (t.Equals(vector4Type)) return (object)UnityEqualityComparer.Vector4;
|
||||
if (t.Equals(colorType)) return (object)UnityEqualityComparer.Color;
|
||||
if (t.Equals(color32Type)) return (object)UnityEqualityComparer.Color32;
|
||||
if (t.Equals(rectType)) return (object)UnityEqualityComparer.Rect;
|
||||
if (t.Equals(boundsType)) return (object)UnityEqualityComparer.Bounds;
|
||||
if (t.Equals(quaternionType)) return (object)UnityEqualityComparer.Quaternion;
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
|
||||
if (t.Equals(vector2IntType)) return (object)UnityEqualityComparer.Vector2Int;
|
||||
if (t.Equals(vector3IntType)) return (object)UnityEqualityComparer.Vector3Int;
|
||||
if (t.Equals(rangeIntType)) return (object)UnityEqualityComparer.RangeInt;
|
||||
if (t.Equals(rectIntType)) return (object)UnityEqualityComparer.RectInt;
|
||||
if (t.Equals(boundsIntType)) return (object)UnityEqualityComparer.BoundsInt;
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
sealed class Vector2EqualityComparer : IEqualityComparer<Vector2>
|
||||
{
|
||||
public bool Equals(Vector2 self, Vector2 vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y);
|
||||
}
|
||||
|
||||
public int GetHashCode(Vector2 obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Vector3EqualityComparer : IEqualityComparer<Vector3>
|
||||
{
|
||||
public bool Equals(Vector3 self, Vector3 vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y) && self.z.Equals(vector.z);
|
||||
}
|
||||
|
||||
public int GetHashCode(Vector3 obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2 ^ obj.z.GetHashCode() >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Vector4EqualityComparer : IEqualityComparer<Vector4>
|
||||
{
|
||||
public bool Equals(Vector4 self, Vector4 vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y) && self.z.Equals(vector.z) && self.w.Equals(vector.w);
|
||||
}
|
||||
|
||||
public int GetHashCode(Vector4 obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2 ^ obj.z.GetHashCode() >> 2 ^ obj.w.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ColorEqualityComparer : IEqualityComparer<Color>
|
||||
{
|
||||
public bool Equals(Color self, Color other)
|
||||
{
|
||||
return self.r.Equals(other.r) && self.g.Equals(other.g) && self.b.Equals(other.b) && self.a.Equals(other.a);
|
||||
}
|
||||
|
||||
public int GetHashCode(Color obj)
|
||||
{
|
||||
return obj.r.GetHashCode() ^ obj.g.GetHashCode() << 2 ^ obj.b.GetHashCode() >> 2 ^ obj.a.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RectEqualityComparer : IEqualityComparer<Rect>
|
||||
{
|
||||
public bool Equals(Rect self, Rect other)
|
||||
{
|
||||
return self.x.Equals(other.x) && self.width.Equals(other.width) && self.y.Equals(other.y) && self.height.Equals(other.height);
|
||||
}
|
||||
|
||||
public int GetHashCode(Rect obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.width.GetHashCode() << 2 ^ obj.y.GetHashCode() >> 2 ^ obj.height.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BoundsEqualityComparer : IEqualityComparer<Bounds>
|
||||
{
|
||||
public bool Equals(Bounds self, Bounds vector)
|
||||
{
|
||||
return self.center.Equals(vector.center) && self.extents.Equals(vector.extents);
|
||||
}
|
||||
|
||||
public int GetHashCode(Bounds obj)
|
||||
{
|
||||
return obj.center.GetHashCode() ^ obj.extents.GetHashCode() << 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class QuaternionEqualityComparer : IEqualityComparer<Quaternion>
|
||||
{
|
||||
public bool Equals(Quaternion self, Quaternion vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y) && self.z.Equals(vector.z) && self.w.Equals(vector.w);
|
||||
}
|
||||
|
||||
public int GetHashCode(Quaternion obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2 ^ obj.z.GetHashCode() >> 2 ^ obj.w.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Color32EqualityComparer : IEqualityComparer<Color32>
|
||||
{
|
||||
public bool Equals(Color32 self, Color32 vector)
|
||||
{
|
||||
return self.a.Equals(vector.a) && self.r.Equals(vector.r) && self.g.Equals(vector.g) && self.b.Equals(vector.b);
|
||||
}
|
||||
|
||||
public int GetHashCode(Color32 obj)
|
||||
{
|
||||
return obj.a.GetHashCode() ^ obj.r.GetHashCode() << 2 ^ obj.g.GetHashCode() >> 2 ^ obj.b.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
|
||||
sealed class Vector2IntEqualityComparer : IEqualityComparer<Vector2Int>
|
||||
{
|
||||
public bool Equals(Vector2Int self, Vector2Int vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y);
|
||||
}
|
||||
|
||||
public int GetHashCode(Vector2Int obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Vector3IntEqualityComparer : IEqualityComparer<Vector3Int>
|
||||
{
|
||||
public static readonly Vector3IntEqualityComparer Default = new Vector3IntEqualityComparer();
|
||||
|
||||
public bool Equals(Vector3Int self, Vector3Int vector)
|
||||
{
|
||||
return self.x.Equals(vector.x) && self.y.Equals(vector.y) && self.z.Equals(vector.z);
|
||||
}
|
||||
|
||||
public int GetHashCode(Vector3Int obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.y.GetHashCode() << 2 ^ obj.z.GetHashCode() >> 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RangeIntEqualityComparer : IEqualityComparer<RangeInt>
|
||||
{
|
||||
public bool Equals(RangeInt self, RangeInt vector)
|
||||
{
|
||||
return self.start.Equals(vector.start) && self.length.Equals(vector.length);
|
||||
}
|
||||
|
||||
public int GetHashCode(RangeInt obj)
|
||||
{
|
||||
return obj.start.GetHashCode() ^ obj.length.GetHashCode() << 2;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class RectIntEqualityComparer : IEqualityComparer<RectInt>
|
||||
{
|
||||
public bool Equals(RectInt self, RectInt other)
|
||||
{
|
||||
return self.x.Equals(other.x) && self.width.Equals(other.width) && self.y.Equals(other.y) && self.height.Equals(other.height);
|
||||
}
|
||||
|
||||
public int GetHashCode(RectInt obj)
|
||||
{
|
||||
return obj.x.GetHashCode() ^ obj.width.GetHashCode() << 2 ^ obj.y.GetHashCode() >> 2 ^ obj.height.GetHashCode() >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BoundsIntEqualityComparer : IEqualityComparer<BoundsInt>
|
||||
{
|
||||
public bool Equals(BoundsInt self, BoundsInt vector)
|
||||
{
|
||||
return Vector3IntEqualityComparer.Default.Equals(self.position, vector.position)
|
||||
&& Vector3IntEqualityComparer.Default.Equals(self.size, vector.size);
|
||||
}
|
||||
|
||||
public int GetHashCode(BoundsInt obj)
|
||||
{
|
||||
return Vector3IntEqualityComparer.Default.GetHashCode(obj.position) ^ Vector3IntEqualityComparer.Default.GetHashCode(obj.size) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ebaaf14253c9cfb47b23283218ff9b67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,336 @@
|
|||
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace UniRx.Async.Internal
|
||||
{
|
||||
// Add, Remove, Enumerate with sweep. All operations are thread safe(in spinlock).
|
||||
internal class WeakDictionary<TKey, TValue>
|
||||
where TKey : class
|
||||
{
|
||||
Entry[] buckets;
|
||||
int size;
|
||||
SpinLock gate; // mutable struct(not readonly)
|
||||
|
||||
readonly float loadFactor;
|
||||
readonly IEqualityComparer<TKey> keyEqualityComparer;
|
||||
|
||||
public WeakDictionary(int capacity = 4, float loadFactor = 0.75f, IEqualityComparer<TKey> keyComparer = null)
|
||||
{
|
||||
var tableSize = CalculateCapacity(capacity, loadFactor);
|
||||
this.buckets = new Entry[tableSize];
|
||||
this.loadFactor = loadFactor;
|
||||
this.gate = new SpinLock(false);
|
||||
this.keyEqualityComparer = keyComparer ?? EqualityComparer<TKey>.Default;
|
||||
}
|
||||
|
||||
public bool TryAdd(TKey key, TValue value)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
return TryAddInternal(key, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
if (TryGetEntry(key, out _, out var entry))
|
||||
{
|
||||
value = entry.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default(TValue);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryRemove(TKey key)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
if (TryGetEntry(key, out var hashIndex, out var entry))
|
||||
{
|
||||
Remove(hashIndex, entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool TryAddInternal(TKey key, TValue value)
|
||||
{
|
||||
var nextCapacity = CalculateCapacity(size + 1, loadFactor);
|
||||
|
||||
TRY_ADD_AGAIN:
|
||||
if (buckets.Length < nextCapacity)
|
||||
{
|
||||
// rehash
|
||||
var nextBucket = new Entry[nextCapacity];
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
var e = buckets[i];
|
||||
while (e != null)
|
||||
{
|
||||
AddToBuckets(nextBucket, key, e.Value, e.Hash);
|
||||
e = e.Next;
|
||||
}
|
||||
}
|
||||
|
||||
buckets = nextBucket;
|
||||
goto TRY_ADD_AGAIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add entry
|
||||
var successAdd = AddToBuckets(buckets, key, value, keyEqualityComparer.GetHashCode(key));
|
||||
if (successAdd) size++;
|
||||
return successAdd;
|
||||
}
|
||||
}
|
||||
|
||||
bool AddToBuckets(Entry[] targetBuckets, TKey newKey, TValue value, int keyHash)
|
||||
{
|
||||
var h = keyHash;
|
||||
var hashIndex = h & (targetBuckets.Length - 1);
|
||||
|
||||
TRY_ADD_AGAIN:
|
||||
if (targetBuckets[hashIndex] == null)
|
||||
{
|
||||
targetBuckets[hashIndex] = new Entry
|
||||
{
|
||||
Key = new WeakReference<TKey>(newKey, false),
|
||||
Value = value,
|
||||
Hash = h
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add to last.
|
||||
var entry = targetBuckets[hashIndex];
|
||||
while (entry != null)
|
||||
{
|
||||
if (entry.Key.TryGetTarget(out var target))
|
||||
{
|
||||
if (keyEqualityComparer.Equals(newKey, target))
|
||||
{
|
||||
return false; // duplicate
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(hashIndex, entry);
|
||||
if (targetBuckets[hashIndex] == null) goto TRY_ADD_AGAIN; // add new entry
|
||||
}
|
||||
|
||||
if (entry.Next != null)
|
||||
{
|
||||
entry = entry.Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// found last
|
||||
entry.Next = new Entry
|
||||
{
|
||||
Key = new WeakReference<TKey>(newKey, false),
|
||||
Value = value,
|
||||
Hash = h
|
||||
};
|
||||
entry.Next.Prev = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetEntry(TKey key, out int hashIndex, out Entry entry)
|
||||
{
|
||||
var table = buckets;
|
||||
var hash = keyEqualityComparer.GetHashCode(key);
|
||||
hashIndex = hash & table.Length - 1;
|
||||
entry = table[hashIndex];
|
||||
|
||||
while (entry != null)
|
||||
{
|
||||
if (entry.Key.TryGetTarget(out var target))
|
||||
{
|
||||
if (keyEqualityComparer.Equals(key, target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// sweap
|
||||
Remove(hashIndex, entry);
|
||||
}
|
||||
|
||||
entry = entry.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Remove(int hashIndex, Entry entry)
|
||||
{
|
||||
if (entry.Prev == null && entry.Next == null)
|
||||
{
|
||||
buckets[hashIndex] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry.Prev == null)
|
||||
{
|
||||
buckets[hashIndex] = entry.Next;
|
||||
}
|
||||
if (entry.Prev != null)
|
||||
{
|
||||
entry.Prev.Next = entry.Next;
|
||||
}
|
||||
if (entry.Next != null)
|
||||
{
|
||||
entry.Next.Prev = entry.Prev;
|
||||
}
|
||||
}
|
||||
size--;
|
||||
}
|
||||
|
||||
public List<KeyValuePair<TKey, TValue>> ToList()
|
||||
{
|
||||
var list = new List<KeyValuePair<TKey, TValue>>(size);
|
||||
ToList(ref list, false);
|
||||
return list;
|
||||
}
|
||||
|
||||
// avoid allocate everytime.
|
||||
public int ToList(ref List<KeyValuePair<TKey, TValue>> list, bool clear = true)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
var listIndex = 0;
|
||||
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
var entry = buckets[i];
|
||||
while (entry != null)
|
||||
{
|
||||
if (entry.Key.TryGetTarget(out var target))
|
||||
{
|
||||
var item = new KeyValuePair<TKey, TValue>(target, entry.Value);
|
||||
if (listIndex < list.Count)
|
||||
{
|
||||
list[listIndex++] = item;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(item);
|
||||
listIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// sweap
|
||||
Remove(i, entry);
|
||||
}
|
||||
|
||||
entry = entry.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
|
||||
return listIndex;
|
||||
}
|
||||
|
||||
static int CalculateCapacity(int collectionSize, float loadFactor)
|
||||
{
|
||||
var size = (int)(((float)collectionSize) / loadFactor);
|
||||
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
size += 1;
|
||||
|
||||
if (size < 8)
|
||||
{
|
||||
size = 8;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
class Entry
|
||||
{
|
||||
public WeakReference<TKey> Key;
|
||||
public TValue Value;
|
||||
public int Hash;
|
||||
public Entry Prev;
|
||||
public Entry Next;
|
||||
|
||||
// debug only
|
||||
public override string ToString()
|
||||
{
|
||||
if (Key.TryGetTarget(out var target))
|
||||
{
|
||||
return target + "(" + Count() + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(Dead)";
|
||||
}
|
||||
}
|
||||
|
||||
int Count()
|
||||
{
|
||||
var count = 1;
|
||||
var n = this;
|
||||
while (n.Next != null)
|
||||
{
|
||||
count++;
|
||||
n = n.Next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue