610 lines
21 KiB
C#
610 lines
21 KiB
C#
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement.AsyncOperations
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Handle for internal operations. This allows for reference counting and checking for valid references.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="TObject">The object type of the underlying operation.</typeparam>
|
||
|
public struct AsyncOperationHandle<TObject> : IEnumerator, IEquatable<AsyncOperationHandle<TObject>>
|
||
|
{
|
||
|
internal AsyncOperationBase<TObject> m_InternalOp;
|
||
|
int m_Version;
|
||
|
string m_LocationName;
|
||
|
|
||
|
internal string LocationName
|
||
|
{
|
||
|
get { return m_LocationName; }
|
||
|
set { m_LocationName = value; }
|
||
|
}
|
||
|
|
||
|
bool m_UnloadSceneOpExcludeReleaseCallback;
|
||
|
|
||
|
internal bool UnloadSceneOpExcludeReleaseCallback
|
||
|
{
|
||
|
get { return m_UnloadSceneOpExcludeReleaseCallback; }
|
||
|
set { m_UnloadSceneOpExcludeReleaseCallback = value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Conversion from typed to non typed handles. This does not increment the reference count.
|
||
|
/// To convert from non-typed back, use AsyncOperationHandle.Convert<T>()
|
||
|
/// </summary>
|
||
|
/// <param name="obj">The typed handle to convert.</param>
|
||
|
/// <returns>Returns the converted operation handle.</returns>
|
||
|
static public implicit operator AsyncOperationHandle(AsyncOperationHandle<TObject> obj)
|
||
|
{
|
||
|
return new AsyncOperationHandle(obj.m_InternalOp, obj.m_Version, obj.m_LocationName);
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(AsyncOperationBase<TObject> op)
|
||
|
{
|
||
|
m_InternalOp = op;
|
||
|
m_Version = op?.Version ?? 0;
|
||
|
m_LocationName = null;
|
||
|
m_UnloadSceneOpExcludeReleaseCallback = false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return the current download status for this operation and its dependencies.
|
||
|
/// </summary>
|
||
|
/// <returns>The download status.</returns>
|
||
|
public DownloadStatus GetDownloadStatus()
|
||
|
{
|
||
|
return InternalGetDownloadStatus(new HashSet<object>());
|
||
|
}
|
||
|
|
||
|
internal DownloadStatus InternalGetDownloadStatus(HashSet<object> visited)
|
||
|
{
|
||
|
if (visited == null)
|
||
|
visited = new HashSet<object>();
|
||
|
return visited.Add(InternalOp) ? InternalOp.GetDownloadStatus(visited) : new DownloadStatus() {IsDone = IsDone};
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op)
|
||
|
{
|
||
|
m_InternalOp = (AsyncOperationBase<TObject>)op;
|
||
|
m_Version = op?.Version ?? 0;
|
||
|
m_LocationName = null;
|
||
|
m_UnloadSceneOpExcludeReleaseCallback = false;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, int version)
|
||
|
{
|
||
|
m_InternalOp = (AsyncOperationBase<TObject>)op;
|
||
|
m_Version = version;
|
||
|
m_LocationName = null;
|
||
|
m_UnloadSceneOpExcludeReleaseCallback = false;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, string locationName)
|
||
|
{
|
||
|
m_InternalOp = (AsyncOperationBase<TObject>)op;
|
||
|
m_Version = op?.Version ?? 0;
|
||
|
m_LocationName = locationName;
|
||
|
m_UnloadSceneOpExcludeReleaseCallback = false;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, int version, string locationName)
|
||
|
{
|
||
|
m_InternalOp = (AsyncOperationBase<TObject>)op;
|
||
|
m_Version = version;
|
||
|
m_LocationName = locationName;
|
||
|
m_UnloadSceneOpExcludeReleaseCallback = false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Acquire a new handle to the internal operation. This will increment the reference count, therefore the returned handle must also be released.
|
||
|
/// </summary>
|
||
|
/// <returns>A new handle to the operation. This handle must also be released.</returns>
|
||
|
internal AsyncOperationHandle<TObject> Acquire()
|
||
|
{
|
||
|
InternalOp.IncrementReferenceCount();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Completion event for the internal operation. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame.
|
||
|
/// </summary>
|
||
|
public event Action<AsyncOperationHandle<TObject>> Completed
|
||
|
{
|
||
|
add { InternalOp.Completed += value; }
|
||
|
remove { InternalOp.Completed -= value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Completion event for non-typed callback handlers. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame.
|
||
|
/// </summary>
|
||
|
public event Action<AsyncOperationHandle> CompletedTypeless
|
||
|
{
|
||
|
add { InternalOp.CompletedTypeless += value; }
|
||
|
remove { InternalOp.CompletedTypeless -= value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Debug name of the operation.
|
||
|
/// </summary>
|
||
|
public string DebugName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!IsValid())
|
||
|
return "InvalidHandle";
|
||
|
return ((IAsyncOperation)InternalOp).DebugName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get dependency operations.
|
||
|
/// </summary>
|
||
|
/// <param name="deps">The list of AsyncOperationHandles that are dependencies of a given AsyncOperationHandle</param>
|
||
|
public void GetDependencies(List<AsyncOperationHandle> deps)
|
||
|
{
|
||
|
InternalOp.GetDependencies(deps);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Event for handling the destruction of the operation.
|
||
|
/// </summary>
|
||
|
public event Action<AsyncOperationHandle> Destroyed
|
||
|
{
|
||
|
add { InternalOp.Destroyed += value; }
|
||
|
remove { InternalOp.Destroyed -= value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Provide equality for this struct.
|
||
|
/// </summary>
|
||
|
/// <param name="other">The operation to compare to.</param>
|
||
|
/// <returns>True if the the operation handles reference the same AsyncOperation and the version is the same.</returns>
|
||
|
public bool Equals(AsyncOperationHandle<TObject> other)
|
||
|
{
|
||
|
return m_Version == other.m_Version && m_InternalOp == other.m_InternalOp;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get hash code of this struct.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return m_InternalOp == null ? 0 : m_InternalOp.GetHashCode() * 17 + m_Version;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Synchronously complete the async operation.
|
||
|
/// </summary>
|
||
|
/// <returns>The result of the operation or null.</returns>
|
||
|
public TObject WaitForCompletion()
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
AsyncOperationHandle.IsWaitingForCompletion = true;
|
||
|
try
|
||
|
{
|
||
|
if (IsValid() && !InternalOp.IsDone)
|
||
|
InternalOp.WaitForCompletion();
|
||
|
if (IsValid())
|
||
|
return Result;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
AsyncOperationHandle.IsWaitingForCompletion = false;
|
||
|
m_InternalOp?.m_RM?.Update(Time.unscaledDeltaTime);
|
||
|
}
|
||
|
#else
|
||
|
if (IsValid() && !InternalOp.IsDone)
|
||
|
InternalOp.WaitForCompletion();
|
||
|
|
||
|
m_InternalOp?.m_RM?.Update(Time.unscaledDeltaTime);
|
||
|
if (IsValid())
|
||
|
return Result;
|
||
|
#endif
|
||
|
return default(TObject);
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationBase<TObject> InternalOp
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_InternalOp == null || m_InternalOp.Version != m_Version)
|
||
|
throw new Exception("Attempting to use an invalid operation handle");
|
||
|
return m_InternalOp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// True if the operation is complete.
|
||
|
/// </summary>
|
||
|
public bool IsDone
|
||
|
{
|
||
|
get { return !IsValid() || InternalOp.IsDone; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Check if the handle references an internal operation.
|
||
|
/// </summary>
|
||
|
/// <returns>True if valid.</returns>
|
||
|
public bool IsValid()
|
||
|
{
|
||
|
return m_InternalOp != null && m_InternalOp.Version == m_Version;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The exception for a failed operation. This will be null unless Status is failed.
|
||
|
/// </summary>
|
||
|
public Exception OperationException
|
||
|
{
|
||
|
get { return InternalOp.OperationException; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The progress of the internal operation.
|
||
|
/// This is evenly weighted between all sub-operations. For example, a LoadAssetAsync call could potentially
|
||
|
/// be chained with InitializeAsync and have multiple dependent operations that download and load content.
|
||
|
/// In that scenario, PercentComplete would reflect how far the overal operation was, and would not accurately
|
||
|
/// represent just percent downloaded or percent loaded into memory.
|
||
|
/// For accurate download percentages, use GetDownloadStatus().
|
||
|
/// </summary>
|
||
|
public float PercentComplete
|
||
|
{
|
||
|
get { return InternalOp.PercentComplete; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current reference count of the internal operation.
|
||
|
/// </summary>
|
||
|
internal int ReferenceCount
|
||
|
{
|
||
|
get { return InternalOp.ReferenceCount; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Release the handle. If the internal operation reference count reaches 0, the resource will be released.
|
||
|
/// </summary>
|
||
|
internal void Release()
|
||
|
{
|
||
|
InternalOp.DecrementReferenceCount();
|
||
|
m_InternalOp = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The result object of the operations.
|
||
|
/// </summary>
|
||
|
public TObject Result
|
||
|
{
|
||
|
get { return InternalOp.Result; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The status of the internal operation.
|
||
|
/// </summary>
|
||
|
public AsyncOperationStatus Status
|
||
|
{
|
||
|
get { return InternalOp.Status; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return a Task object to wait on when using async await.
|
||
|
/// </summary>
|
||
|
public System.Threading.Tasks.Task<TObject> Task
|
||
|
{
|
||
|
get { return InternalOp.Task; }
|
||
|
}
|
||
|
|
||
|
object IEnumerator.Current
|
||
|
{
|
||
|
get { return Result; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Overload for <see cref="IEnumerator.MoveNext"/>.
|
||
|
/// </summary>
|
||
|
/// <returns>Returns true if the enumerator can advance to the next element in the collectin. Returns false otherwise.</returns>
|
||
|
bool IEnumerator.MoveNext()
|
||
|
{
|
||
|
return !IsDone;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Overload for <see cref="IEnumerator.Reset"/>.
|
||
|
/// </summary>
|
||
|
void IEnumerator.Reset()
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Non typed operation handle. This allows for reference counting and checking for valid references.
|
||
|
/// </summary>
|
||
|
public struct AsyncOperationHandle : IEnumerator
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
private static bool m_IsWaitingForCompletion = false;
|
||
|
/// <summary>
|
||
|
/// Indicates that the async operation is in the process of being completed synchronously.
|
||
|
/// </summary>
|
||
|
public static bool IsWaitingForCompletion
|
||
|
{
|
||
|
get { return m_IsWaitingForCompletion; }
|
||
|
internal set { m_IsWaitingForCompletion = value; }
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
internal IAsyncOperation m_InternalOp;
|
||
|
int m_Version;
|
||
|
string m_LocationName;
|
||
|
|
||
|
internal string LocationName
|
||
|
{
|
||
|
get { return m_LocationName; }
|
||
|
set { m_LocationName = value; }
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op)
|
||
|
{
|
||
|
m_InternalOp = op;
|
||
|
m_Version = op?.Version ?? 0;
|
||
|
m_LocationName = null;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, int version)
|
||
|
{
|
||
|
m_InternalOp = op;
|
||
|
m_Version = version;
|
||
|
m_LocationName = null;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, string locationName)
|
||
|
{
|
||
|
m_InternalOp = op;
|
||
|
m_Version = op?.Version ?? 0;
|
||
|
m_LocationName = locationName;
|
||
|
}
|
||
|
|
||
|
internal AsyncOperationHandle(IAsyncOperation op, int version, string locationName)
|
||
|
{
|
||
|
m_InternalOp = op;
|
||
|
m_Version = version;
|
||
|
m_LocationName = locationName;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Acquire a new handle to the internal operation. This will increment the reference count, therefore the returned handle must also be released.
|
||
|
/// </summary>
|
||
|
/// <returns>A new handle to the operation. This handle must also be released.</returns>
|
||
|
internal AsyncOperationHandle Acquire()
|
||
|
{
|
||
|
InternalOp.IncrementReferenceCount();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Completion event for the internal operation. If this is assigned on a completed operation, the callback is deferred until the LateUpdate of the current frame.
|
||
|
/// </summary>
|
||
|
public event Action<AsyncOperationHandle> Completed
|
||
|
{
|
||
|
add { InternalOp.CompletedTypeless += value; }
|
||
|
remove { InternalOp.CompletedTypeless -= value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Converts handle to be typed. This does not increment the reference count.
|
||
|
/// To convert back to non-typed, implicit conversion is available.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">The type of the handle.</typeparam>
|
||
|
/// <returns>A new handle that is typed.</returns>
|
||
|
public AsyncOperationHandle<T> Convert<T>()
|
||
|
{
|
||
|
return new AsyncOperationHandle<T>(InternalOp, m_Version, m_LocationName);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Provide equality for this struct.
|
||
|
/// </summary>
|
||
|
/// <param name="other">The operation to compare to.</param>
|
||
|
/// <returns>True if the the operation handles reference the same AsyncOperation and the version is the same.</returns>
|
||
|
public bool Equals(AsyncOperationHandle other)
|
||
|
{
|
||
|
return m_Version == other.m_Version && m_InternalOp == other.m_InternalOp;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Debug name of the operation.
|
||
|
/// </summary>
|
||
|
public string DebugName
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!IsValid())
|
||
|
return "InvalidHandle";
|
||
|
return InternalOp.DebugName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Event for handling the destruction of the operation.
|
||
|
/// </summary>
|
||
|
public event Action<AsyncOperationHandle> Destroyed
|
||
|
{
|
||
|
add { InternalOp.Destroyed += value; }
|
||
|
remove { InternalOp.Destroyed -= value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get dependency operations.
|
||
|
/// </summary>
|
||
|
/// <param name="deps"></param>
|
||
|
public void GetDependencies(List<AsyncOperationHandle> deps)
|
||
|
{
|
||
|
InternalOp.GetDependencies(deps);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get hash code of this struct.
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return m_InternalOp == null ? 0 : m_InternalOp.GetHashCode() * 17 + m_Version;
|
||
|
}
|
||
|
|
||
|
IAsyncOperation InternalOp
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_InternalOp == null || m_InternalOp.Version != m_Version)
|
||
|
throw new Exception("Attempting to use an invalid operation handle");
|
||
|
return m_InternalOp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// True if the operation is complete.
|
||
|
/// </summary>
|
||
|
public bool IsDone
|
||
|
{
|
||
|
get { return !IsValid() || InternalOp.IsDone; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Check if the internal operation is not null and has the same version of this handle.
|
||
|
/// </summary>
|
||
|
/// <returns>True if valid.</returns>
|
||
|
public bool IsValid()
|
||
|
{
|
||
|
return m_InternalOp != null && m_InternalOp.Version == m_Version;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The exception for a failed operation. This will be null unless Status is failed.
|
||
|
/// </summary>
|
||
|
public Exception OperationException
|
||
|
{
|
||
|
get { return InternalOp.OperationException; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The progress of the internal operation.
|
||
|
/// This is evenly weighted between all sub-operations. For example, a LoadAssetAsync call could potentially
|
||
|
/// be chained with InitializeAsync and have multiple dependent operations that download and load content.
|
||
|
/// In that scenario, PercentComplete would reflect how far the overal operation was, and would not accurately
|
||
|
/// represent just percent downloaded or percent loaded into memory.
|
||
|
/// For accurate download percentages, use GetDownloadStatus().
|
||
|
/// </summary>
|
||
|
public float PercentComplete
|
||
|
{
|
||
|
get { return InternalOp.PercentComplete; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return the current download status for this operation and its dependencies. In some instances, the information will not be available. This can happen if the operation
|
||
|
/// is dependent on the initialization operation for addressables. Once the initialization operation completes, the information returned will be accurate.
|
||
|
/// </summary>
|
||
|
/// <returns>The download status.</returns>
|
||
|
public DownloadStatus GetDownloadStatus()
|
||
|
{
|
||
|
return InternalGetDownloadStatus(new HashSet<object>());
|
||
|
}
|
||
|
|
||
|
internal DownloadStatus InternalGetDownloadStatus(HashSet<object> visited)
|
||
|
{
|
||
|
if (visited == null)
|
||
|
visited = new HashSet<object>();
|
||
|
return visited.Add(InternalOp) ? InternalOp.GetDownloadStatus(visited) : new DownloadStatus() {IsDone = IsDone};
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current reference count of the internal operation.
|
||
|
/// </summary>
|
||
|
internal int ReferenceCount
|
||
|
{
|
||
|
get { return InternalOp.ReferenceCount; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Release the handle. If the internal operation reference count reaches 0, the resource will be released.
|
||
|
/// </summary>
|
||
|
internal void Release()
|
||
|
{
|
||
|
InternalOp.DecrementReferenceCount();
|
||
|
m_InternalOp = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The result object of the operations.
|
||
|
/// </summary>
|
||
|
public object Result
|
||
|
{
|
||
|
get { return InternalOp.GetResultAsObject(); }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The status of the internal operation.
|
||
|
/// </summary>
|
||
|
public AsyncOperationStatus Status
|
||
|
{
|
||
|
get { return InternalOp.Status; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return a Task object to wait on when using async await.
|
||
|
/// </summary>
|
||
|
public System.Threading.Tasks.Task<object> Task
|
||
|
{
|
||
|
get { return InternalOp.Task; }
|
||
|
}
|
||
|
|
||
|
object IEnumerator.Current
|
||
|
{
|
||
|
get { return Result; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Overload for <see cref="IEnumerator.MoveNext"/>.
|
||
|
/// </summary>
|
||
|
/// <returns>Returns true if the enumerator can advance to the next element in the collectin. Returns false otherwise.</returns>
|
||
|
bool IEnumerator.MoveNext()
|
||
|
{
|
||
|
return !IsDone;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Overload for <see cref="IEnumerator.Reset"/>.
|
||
|
/// </summary>
|
||
|
void IEnumerator.Reset()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Synchronously complete the async operation.
|
||
|
/// </summary>
|
||
|
/// <returns>The result of the operation or null.</returns>
|
||
|
public object WaitForCompletion()
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
IsWaitingForCompletion = true;
|
||
|
try
|
||
|
{
|
||
|
if (IsValid() && !InternalOp.IsDone)
|
||
|
InternalOp.WaitForCompletion();
|
||
|
if (IsValid())
|
||
|
return Result;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
IsWaitingForCompletion = false;
|
||
|
}
|
||
|
#else
|
||
|
if (IsValid() && !InternalOp.IsDone)
|
||
|
InternalOp.WaitForCompletion();
|
||
|
if (IsValid())
|
||
|
return Result;
|
||
|
#endif
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|