532 lines
25 KiB
C#
532 lines
25 KiB
C#
// ReSharper disable Unity.RedundantHideInInspectorAttribute
|
|
#if PRIME_TWEEN_SAFETY_CHECKS && UNITY_ASSERTIONS
|
|
#define SAFETY_CHECKS
|
|
#endif
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using JetBrains.Annotations;
|
|
using UnityEngine;
|
|
using Debug = UnityEngine.Debug;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace PrimeTween {
|
|
[AddComponentMenu("")]
|
|
internal class PrimeTweenManager : MonoBehaviour {
|
|
internal static PrimeTweenManager Instance;
|
|
#if UNITY_EDITOR
|
|
static bool isHotReload = true;
|
|
#endif
|
|
internal static int customInitialCapacity = -1;
|
|
|
|
/// Item can be null if the list is accessed from the <see cref="ReusableTween.updateAndCheckIfRunning"/> via onValueChange() or onComplete()
|
|
/// Changing list to array gives about 8% performance improvement and is possible to do in the future
|
|
/// The current implementation is simpler and PrimeTweenManagerInspector can draw tweens with no additional code
|
|
#if UNITY_2021_3_OR_NEWER
|
|
[ItemCanBeNull]
|
|
#endif
|
|
[SerializeField] internal List<ReusableTween> tweens;
|
|
[SerializeField] internal List<ReusableTween> lateUpdateTweens;
|
|
[SerializeField] internal List<ReusableTween> fixedUpdateTweens;
|
|
[NonSerialized] internal List<ReusableTween> pool;
|
|
/// startValue can't be replaced with 'Tween lastTween'
|
|
/// because the lastTween may already be dead, but the tween before it is still alive (count >= 1)
|
|
/// and we can't retrieve the startValue from the dead lastTween
|
|
internal Dictionary<(Transform, TweenType), (ValueContainer startValue, int count)> shakes;
|
|
internal int currentPoolCapacity { get; private set; }
|
|
internal int maxSimultaneousTweensCount { get; private set; }
|
|
|
|
[HideInInspector]
|
|
internal long lastId = 1;
|
|
internal Ease defaultEase = Ease.OutQuad;
|
|
internal _UpdateType defaultUpdateType = _UpdateType.Update;
|
|
internal const Ease defaultShakeEase = Ease.OutQuad;
|
|
internal bool warnTweenOnDisabledTarget = true;
|
|
internal bool warnZeroDuration = true;
|
|
internal bool warnStructBoxingAllocationInCoroutine = true;
|
|
internal bool warnBenchmarkWithAsserts = true;
|
|
internal bool validateCustomCurves = true;
|
|
internal bool warnEndValueEqualsCurrent = true;
|
|
int processedCount;
|
|
int lateUpdateTweensProcessedCount;
|
|
int fixedUpdateTweensProcessedCount;
|
|
int maxLateUpdateCount;
|
|
internal int updateDepth;
|
|
internal static readonly object dummyTarget = new object();
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
static void beforeSceneLoad() {
|
|
#if UNITY_EDITOR
|
|
isHotReload = false;
|
|
#endif
|
|
Assert.IsNull(Instance);
|
|
var go = new GameObject(nameof(PrimeTweenManager));
|
|
DontDestroyOnLoad(go);
|
|
var instance = go.AddComponent<PrimeTweenManager>();
|
|
const int defaultInitialCapacity = 200;
|
|
instance.init(customInitialCapacity != -1 ? customInitialCapacity : defaultInitialCapacity);
|
|
Instance = instance;
|
|
}
|
|
|
|
void init(int capacity) {
|
|
tweens = new List<ReusableTween>(capacity);
|
|
lateUpdateTweens = new List<ReusableTween>(capacity);
|
|
fixedUpdateTweens = new List<ReusableTween>(capacity);
|
|
pool = new List<ReusableTween>(capacity);
|
|
for (int i = 0; i < capacity; i++) {
|
|
pool.Add(new ReusableTween());
|
|
}
|
|
shakes = new Dictionary<(Transform, TweenType), (ValueContainer, int)>(capacity);
|
|
currentPoolCapacity = capacity;
|
|
}
|
|
|
|
const string manualInstanceCreationIsNotAllowedMessage = "Please don't create the " + nameof(PrimeTweenManager) + " instance manually.";
|
|
void Awake() => Assert.IsNull(Instance, manualInstanceCreationIsNotAllowedMessage);
|
|
|
|
#if UNITY_EDITOR
|
|
[InitializeOnLoadMethod]
|
|
static void iniOnLoad() {
|
|
EditorApplication.playModeStateChanged += state => {
|
|
if (state == PlayModeStateChange.EnteredEditMode) {
|
|
Instance = null;
|
|
customInitialCapacity = -1;
|
|
}
|
|
};
|
|
if (!isHotReload) {
|
|
return;
|
|
}
|
|
if (!Application.isPlaying) {
|
|
return;
|
|
}
|
|
Assert.IsNull(Instance);
|
|
var foundInScene =
|
|
#if UNITY_2023_1_OR_NEWER
|
|
FindAnyObjectByType
|
|
#else
|
|
FindObjectOfType
|
|
#endif
|
|
<PrimeTweenManager>();
|
|
Assert.IsNotNull(foundInScene);
|
|
#if PRIME_TWEEN_INSPECTOR_DEBUGGING
|
|
Debug.LogError("PRIME_TWEEN_INSPECTOR_DEBUGGING doesn't work with 'Recompile And Continue Playing' because Tween.id is serializable but Tween.tween is not.");
|
|
return;
|
|
#endif
|
|
var count = foundInScene.tweensCount;
|
|
if (count > 0) {
|
|
Debug.Log($"All tweens ({count}) were stopped because of 'Recompile And Continue Playing'.");
|
|
}
|
|
foundInScene.init(foundInScene.currentPoolCapacity);
|
|
Instance = foundInScene;
|
|
}
|
|
|
|
void Reset() {
|
|
Assert.IsFalse(Application.isPlaying);
|
|
Debug.LogError(manualInstanceCreationIsNotAllowedMessage);
|
|
DestroyImmediate(this);
|
|
}
|
|
#endif
|
|
|
|
void Start() {
|
|
#if SAFETY_CHECKS
|
|
// Selection.activeGameObject = gameObject;
|
|
#endif
|
|
Assert.AreEqual(Instance, this, manualInstanceCreationIsNotAllowedMessage);
|
|
}
|
|
|
|
internal void FixedUpdate() => UpdateTweens(_UpdateType.FixedUpdate);
|
|
|
|
/// <summary>
|
|
/// The most common tween lifecycle:
|
|
/// 1. User's script creates a tween in Update() in frame N.
|
|
/// 2. PrimeTweenManager.LateUpdate() applies the 'startValue' to the tween in the SAME FRAME N. This guarantees that the animation is rendered at the 'startValue' in the same frame the tween is created.
|
|
/// 3. PrimeTweenManager.Update() executes the first animation step on frame N+1. PrimeTweenManager's execution order is -2000, this means that
|
|
/// all tweens created in previous frames will already be updated before user's script Update() (if user's script execution order is greater than -2000).
|
|
/// 4. PrimeTweenManager.Update() completes the tween on frame N+(duration*targetFrameRate) given that targetFrameRate is stable.
|
|
/// </summary>
|
|
internal void Update() => UpdateTweens(_UpdateType.Update);
|
|
|
|
void update(List<ReusableTween> tweens, float deltaTime, float unscaledDeltaTime, out int processedCount, int? maxCount = null) {
|
|
if (updateDepth != 0) {
|
|
throw new Exception("updateDepth != 0");
|
|
}
|
|
updateDepth++;
|
|
// onComplete and onValueChange can create new tweens. Cache count to process only those tweens that were present when the update started
|
|
int oldCount = maxCount < tweens.Count ? maxCount.Value : tweens.Count;
|
|
var numRemoved = 0;
|
|
// Process tweens in the order of creation.
|
|
// This allows to create tween duplicates because the latest tween on the same value will overwrite the previous ones.
|
|
for (int i = 0; i < oldCount; i++) {
|
|
var tween = tweens[i];
|
|
var newIndex = i - numRemoved;
|
|
#if SAFETY_CHECKS
|
|
Assert.IsNotNull(tween);
|
|
if (numRemoved > 0) {
|
|
Assert.IsNull(tweens[newIndex]);
|
|
}
|
|
#endif
|
|
// ReSharper disable once PossibleNullReferenceException
|
|
// delay release for one frame if coroutineEnumerator.resetEnumerator()
|
|
if (tween.updateAndCheckIfRunning(tween.settings.useUnscaledTime ? unscaledDeltaTime : deltaTime) || tween.coroutineEnumerator.resetEnumerator()) {
|
|
if (i != newIndex) {
|
|
tweens[i] = null;
|
|
tweens[newIndex] = tween;
|
|
}
|
|
continue;
|
|
}
|
|
releaseTweenToPool(tween);
|
|
tweens[i] = null; // set to null after releaseTweenToPool() so in case of an exception, the tween will stay inspectable via Inspector
|
|
numRemoved++;
|
|
}
|
|
processedCount = oldCount - numRemoved;
|
|
#if SAFETY_CHECKS
|
|
for (int i = oldCount - numRemoved; i < oldCount; i++) { // Check removed tweens are shifted to the left and are null
|
|
Assert.IsNull(tweens[i]);
|
|
}
|
|
for (int i = oldCount; i < tweens.Count; i++) { // Check all newly created tweens are not null
|
|
Assert.IsNotNull(tweens[i]);
|
|
}
|
|
#endif
|
|
updateDepth--;
|
|
if (numRemoved != 0) {
|
|
var newCount = tweens.Count;
|
|
for (int i = oldCount; i < newCount; i++) {
|
|
var tween = tweens[i];
|
|
var newIndex = i - numRemoved;
|
|
#if SAFETY_CHECKS
|
|
Assert.IsNotNull(tween);
|
|
#endif
|
|
tweens[newIndex] = tween;
|
|
}
|
|
tweens.RemoveRange(newCount - numRemoved, numRemoved);
|
|
Assert.AreEqual(tweens.Count, newCount - numRemoved);
|
|
#if SAFETY_CHECKS
|
|
foreach (var t in tweens) {
|
|
Assert.IsNotNull(t);
|
|
}
|
|
// Check no duplicates
|
|
hashSet.Clear();
|
|
hashSet.UnionWith(tweens);
|
|
Assert.AreEqual(hashSet.Count, tweens.Count);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if SAFETY_CHECKS
|
|
readonly HashSet<ReusableTween> hashSet = new HashSet<ReusableTween>();
|
|
#endif
|
|
|
|
internal void LateUpdate() {
|
|
UpdateTweens(_UpdateType.LateUpdate);
|
|
ApplyStartValues(_UpdateType.Update);
|
|
ApplyStartValues(_UpdateType.LateUpdate);
|
|
}
|
|
|
|
internal void ApplyStartValues(_UpdateType updateType) {
|
|
switch (updateType) {
|
|
case _UpdateType.Default:
|
|
Debug.LogError("Please provide non-default update type.");
|
|
break;
|
|
case _UpdateType.Update:
|
|
ApplyStartValuesInternal(tweens, processedCount);
|
|
break;
|
|
case _UpdateType.LateUpdate:
|
|
ApplyStartValuesInternal(lateUpdateTweens, lateUpdateTweensProcessedCount);
|
|
break;
|
|
case _UpdateType.FixedUpdate:
|
|
ApplyStartValuesInternal(fixedUpdateTweens, fixedUpdateTweensProcessedCount);
|
|
break;
|
|
default: throw new Exception($"Invalid update type: {updateType}");
|
|
}
|
|
|
|
void ApplyStartValuesInternal(List<ReusableTween> list, int processedCount) {
|
|
updateDepth++;
|
|
int cachedCount = list.Count;
|
|
for (int i = processedCount; i < cachedCount; i++) {
|
|
var tween = list[i];
|
|
// ReSharper disable once PossibleNullReferenceException
|
|
if (tween._isAlive && !tween.startFromCurrent && tween.settings.startDelay == 0 && !tween.isUnityTargetDestroyed() && !tween.isAdditive
|
|
&& tween.canManipulate()
|
|
&& tween.elapsedTimeTotal == 0f) {
|
|
tween.SetElapsedTimeTotal(0f);
|
|
}
|
|
}
|
|
updateDepth--;
|
|
}
|
|
}
|
|
|
|
internal void UpdateTweens(_UpdateType updateType, float? deltaTime = null, float? unscaledDeltaTime = null) {
|
|
switch (updateType) {
|
|
case _UpdateType.Default:
|
|
Debug.LogError("Please provide non-default update type.");
|
|
break;
|
|
case _UpdateType.Update:
|
|
update(tweens, deltaTime ?? Time.deltaTime, unscaledDeltaTime ?? Time.unscaledDeltaTime, out processedCount);
|
|
break;
|
|
case _UpdateType.LateUpdate:
|
|
update(lateUpdateTweens, deltaTime ?? Time.deltaTime, unscaledDeltaTime ?? Time.unscaledDeltaTime, out lateUpdateTweensProcessedCount, maxLateUpdateCount);
|
|
maxLateUpdateCount = lateUpdateTweens.Count;
|
|
break;
|
|
case _UpdateType.FixedUpdate:
|
|
update(fixedUpdateTweens, deltaTime ?? Time.fixedDeltaTime, unscaledDeltaTime ?? Time.fixedUnscaledDeltaTime, out fixedUpdateTweensProcessedCount);
|
|
break;
|
|
default: throw new Exception($"Invalid update type: {updateType}");
|
|
}
|
|
}
|
|
|
|
void releaseTweenToPool([NotNull] ReusableTween tween) {
|
|
#if SAFETY_CHECKS
|
|
checkNotInSequence(tweens);
|
|
checkNotInSequence(lateUpdateTweens);
|
|
checkNotInSequence(fixedUpdateTweens);
|
|
void checkNotInSequence(List<ReusableTween> list) {
|
|
foreach (var t in list) {
|
|
if (t != null) {
|
|
Assert.AreNotEqual(tween.id, t.next.id);
|
|
Assert.AreNotEqual(tween.id, t.nextSibling.id);
|
|
Assert.AreNotEqual(tween.id, t.prev.id);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
tween.Reset();
|
|
pool.Add(tween);
|
|
}
|
|
|
|
/// Returns null if target is a destroyed UnityEngine.Object
|
|
internal static Tween? delayWithoutDurationCheck([CanBeNull] object target, float duration, bool useUnscaledTime) {
|
|
#if UNITY_EDITOR
|
|
if (Constants.warnNoInstance) {
|
|
return null;
|
|
}
|
|
#endif
|
|
var tween = fetchTween();
|
|
var settings = new TweenSettings {
|
|
duration = duration,
|
|
ease = Ease.Linear,
|
|
useUnscaledTime = useUnscaledTime
|
|
};
|
|
tween.Setup(target, ref settings, _ => {}, null, false, TweenType.Delay);
|
|
var result = addTween(tween);
|
|
// ReSharper disable once RedundantCast
|
|
return result.IsCreated ? result : (Tween?)null;
|
|
}
|
|
|
|
[NotNull]
|
|
internal static ReusableTween fetchTween() {
|
|
#if UNITY_EDITOR
|
|
if (Constants.warnNoInstance) {
|
|
return new ReusableTween();
|
|
}
|
|
#endif
|
|
return Instance.fetchTween_internal();
|
|
}
|
|
|
|
[NotNull]
|
|
ReusableTween fetchTween_internal() {
|
|
ReusableTween result;
|
|
if (pool.Count == 0) {
|
|
result = new ReusableTween();
|
|
if (tweensCount + 1 > currentPoolCapacity) {
|
|
var newCapacity = currentPoolCapacity == 0 ? 4 : currentPoolCapacity * 2;
|
|
Debug.LogWarning($"Tweens capacity has been increased from {currentPoolCapacity} to {newCapacity}. Please increase the capacity manually to prevent memory allocations at runtime by calling {Constants.setTweensCapacityMethod}.\n" +
|
|
$"To know the highest number of simultaneously running tweens, please observe the '{nameof(PrimeTweenManager)}/{Constants.maxAliveTweens}' in Inspector.\n");
|
|
currentPoolCapacity = newCapacity;
|
|
}
|
|
} else {
|
|
var lastIndex = pool.Count - 1;
|
|
result = pool[lastIndex];
|
|
pool.RemoveAt(lastIndex);
|
|
}
|
|
Assert.AreEqual(-1, result.id);
|
|
result.id = lastId;
|
|
return result;
|
|
}
|
|
|
|
internal static Tween Animate([NotNull] ReusableTween tween) {
|
|
checkDuration(tween.target, tween.settings.duration);
|
|
return addTween(tween);
|
|
}
|
|
|
|
internal static void checkDuration<T>([CanBeNull] T target, float duration) where T : class {
|
|
#if UNITY_EDITOR
|
|
if (Constants.noInstance) {
|
|
return;
|
|
}
|
|
#endif
|
|
if (Instance.warnZeroDuration && duration <= 0) {
|
|
Debug.LogWarning($"Tween duration ({duration}) <= 0. {Constants.buildWarningCanBeDisabledMessage(nameof(warnZeroDuration))}", target as UnityEngine.Object);
|
|
}
|
|
}
|
|
|
|
internal static Tween addTween([NotNull] ReusableTween tween) {
|
|
#if UNITY_EDITOR
|
|
if (Constants.noInstance) {
|
|
return default;
|
|
}
|
|
#endif
|
|
return Instance.addTween_internal(tween);
|
|
}
|
|
|
|
Tween addTween_internal([NotNull] ReusableTween tween) {
|
|
Assert.IsNotNull(tween);
|
|
Assert.IsTrue(tween.id > 0);
|
|
if (tween.target == null || tween.isUnityTargetDestroyed()) {
|
|
Debug.LogError($"Tween's target is null: {tween.GetDescription()}. This error can mean that:\n" +
|
|
"- The target reference is null.\n" +
|
|
"- UnityEngine.Object target reference is not populated in the Inspector.\n" +
|
|
"- UnityEngine.Object target has been destroyed.\n" +
|
|
"Please ensure you're using a valid target.\n");
|
|
tween.kill();
|
|
releaseTweenToPool(tween);
|
|
return default;
|
|
}
|
|
if (warnTweenOnDisabledTarget) {
|
|
if (tween.target is Component comp && !comp.gameObject.activeInHierarchy) {
|
|
Debug.LogWarning($"Tween is started on GameObject that is not active in hierarchy: {comp.name}. {Constants.buildWarningCanBeDisabledMessage(nameof(warnTweenOnDisabledTarget))}", comp);
|
|
}
|
|
}
|
|
if (tween.settings._updateType == _UpdateType.Default) {
|
|
tween.settings._updateType = defaultUpdateType;
|
|
}
|
|
switch (tween.settings._updateType) {
|
|
case _UpdateType.Update:
|
|
tweens.Add(tween);
|
|
break;
|
|
case _UpdateType.LateUpdate:
|
|
lateUpdateTweens.Add(tween);
|
|
break;
|
|
case _UpdateType.FixedUpdate:
|
|
fixedUpdateTweens.Add(tween);
|
|
break;
|
|
default:
|
|
Debug.LogError($"Invalid update type: {tween.settings._updateType}");
|
|
return default;
|
|
}
|
|
#if SAFETY_CHECKS
|
|
// Debug.Log($"[{Time.frameCount}] created: {tween.GetDescription()}", tween.unityTarget);
|
|
StackTraces.Record(tween.id);
|
|
#endif
|
|
lastId++; // increment only when tween added successfully
|
|
#if UNITY_ASSERTIONS && !PRIME_TWEEN_DISABLE_ASSERTIONS
|
|
maxSimultaneousTweensCount = Math.Max(maxSimultaneousTweensCount, tweensCount);
|
|
if (warnBenchmarkWithAsserts && maxSimultaneousTweensCount > 50000) {
|
|
warnBenchmarkWithAsserts = false;
|
|
var msg = "PrimeTween detected more than 50000 concurrent tweens. If you're running benchmarks, please add the PRIME_TWEEN_DISABLE_ASSERTIONS to the 'ProjectSettings/Player/Script Compilation' to disable assertions. This will ensure PrimeTween runs with the release performance.\n" +
|
|
"Also disable optional convenience features: PrimeTweenConfig.warnZeroDuration and PrimeTweenConfig.warnTweenOnDisabledTarget.\n";
|
|
if (Application.isEditor) {
|
|
msg += "Please also run the tests in real builds, not in the Editor, to measure the performance correctly.\n";
|
|
}
|
|
msg += $"{Constants.buildWarningCanBeDisabledMessage(nameof(PrimeTweenConfig.warnBenchmarkWithAsserts))}\n";
|
|
Debug.LogError(msg);
|
|
}
|
|
#endif
|
|
return new Tween(tween);
|
|
}
|
|
|
|
internal static int processAll([CanBeNull] object onTarget, [NotNull] Predicate<ReusableTween> predicate, bool allowToProcessTweensInsideSequence) {
|
|
#if UNITY_EDITOR
|
|
if (Constants.warnNoInstance) {
|
|
return default;
|
|
}
|
|
#endif
|
|
return Instance.processAll_internal(onTarget, predicate, allowToProcessTweensInsideSequence);
|
|
}
|
|
|
|
internal static bool logCantManipulateError = true;
|
|
|
|
int processAll_internal([CanBeNull] object onTarget, [NotNull] Predicate<ReusableTween> predicate, bool allowToProcessTweensInsideSequence) {
|
|
return processInList(tweens) + processInList(lateUpdateTweens) + processInList(fixedUpdateTweens);
|
|
int processInList(List<ReusableTween> tweens) {
|
|
int numProcessed = 0;
|
|
int totalCount = 0;
|
|
var count = tweens.Count; // this is not an optimization, OnComplete() may create new tweens
|
|
for (var i = 0; i < count; i++) {
|
|
var tween = tweens[i];
|
|
if (tween == null) {
|
|
continue;
|
|
}
|
|
totalCount++;
|
|
if (onTarget != null) {
|
|
if (tween.target != onTarget) {
|
|
continue;
|
|
}
|
|
if (!allowToProcessTweensInsideSequence && tween.IsInSequence()) {
|
|
// To support stopping sequences by target, I can add new API 'Sequence.Create(object sequenceTarget)'.
|
|
// But 'sequenceTarget' is a different concept to tween's target, so I should not mix these two concepts together:
|
|
// 'sequenceTarget' serves the purpose of unique 'id', while tween's target is the animated object.
|
|
// In my opinion, the benefits of this new API don't outweigh the added complexity. A much more simpler approach is to store the Sequence reference and call sequence.Stop() directly.
|
|
Assert.IsFalse(tween.isMainSequenceRoot());
|
|
if (logCantManipulateError) {
|
|
Assert.LogError(Constants.cantManipulateNested, tween.id);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if (tween._isAlive && predicate(tween)) {
|
|
numProcessed++;
|
|
}
|
|
}
|
|
if (onTarget == null) {
|
|
return totalCount;
|
|
}
|
|
return numProcessed;
|
|
}
|
|
}
|
|
|
|
internal void SetTweensCapacity(int capacity) {
|
|
var runningTweens = tweensCount;
|
|
if (capacity < runningTweens) {
|
|
Debug.LogError($"New capacity ({capacity}) should be greater than the number of currently running tweens ({runningTweens}).\n" +
|
|
$"You can use {nameof(Tween)}.{nameof(Tween.StopAll)}() to stop all running tweens.");
|
|
return;
|
|
}
|
|
tweens.Capacity = capacity;
|
|
lateUpdateTweens.Capacity = capacity;
|
|
fixedUpdateTweens.Capacity = capacity;
|
|
#if UNITY_2021_2_OR_NEWER
|
|
shakes.EnsureCapacity(capacity);
|
|
#endif
|
|
resizeAndSetCapacity(pool, capacity - runningTweens, capacity);
|
|
currentPoolCapacity = capacity;
|
|
Assert.AreEqual(capacity, tweens.Capacity);
|
|
Assert.AreEqual(capacity, lateUpdateTweens.Capacity);
|
|
Assert.AreEqual(capacity, fixedUpdateTweens.Capacity);
|
|
Assert.AreEqual(capacity, pool.Capacity);
|
|
}
|
|
|
|
internal int tweensCount => tweens.Count + lateUpdateTweens.Count + fixedUpdateTweens.Count;
|
|
|
|
internal static void resizeAndSetCapacity([NotNull] List<ReusableTween> list, int newCount, int newCapacity) {
|
|
Assert.IsTrue(newCapacity >= newCount);
|
|
int curCount = list.Count;
|
|
if (curCount > newCount) {
|
|
var numToRemove = curCount - newCount;
|
|
list.RemoveRange(newCount, numToRemove);
|
|
list.Capacity = newCapacity;
|
|
} else {
|
|
list.Capacity = newCapacity;
|
|
if (newCount > curCount) {
|
|
var numToCreate = newCount - curCount;
|
|
for (int i = 0; i < numToCreate; i++) {
|
|
list.Add(new ReusableTween());
|
|
}
|
|
}
|
|
}
|
|
Assert.AreEqual(newCount, list.Count);
|
|
Assert.AreEqual(newCapacity, list.Capacity);
|
|
}
|
|
|
|
[Conditional("UNITY_ASSERTIONS")]
|
|
internal void warnStructBoxingInCoroutineOnce(long id) {
|
|
if (!warnStructBoxingAllocationInCoroutine) {
|
|
return;
|
|
}
|
|
warnStructBoxingAllocationInCoroutine = false;
|
|
Assert.LogWarning("Please use Tween/Sequence." + nameof(Tween.ToYieldInstruction) + "() when waiting for a Tween/Sequence in coroutines to prevent struct boxing.\n" +
|
|
Constants.buildWarningCanBeDisabledMessage(nameof(PrimeTweenConfig.warnStructBoxingAllocationInCoroutine)) + "\n", id);
|
|
}
|
|
}
|
|
}
|