237 lines
7.8 KiB
C#
237 lines
7.8 KiB
C#
|
// using UnityEngine;
|
||
|
//
|
||
|
// using System;
|
||
|
// using System.Collections.Generic;
|
||
|
// using Cysharp.Threading.Tasks;
|
||
|
// using Cysharp.Threading.Tasks.Triggers;
|
||
|
// using TMPro;
|
||
|
// using UnityEngine;
|
||
|
// using UnityEngine.Pool;
|
||
|
// using System.Threading;
|
||
|
// using System.Runtime.CompilerServices;
|
||
|
// using Cysharp.Text;
|
||
|
//
|
||
|
// namespace text
|
||
|
// {
|
||
|
// public static class ZeroGCTypewriterPro
|
||
|
// {
|
||
|
// #region 核心结构体
|
||
|
//
|
||
|
// private struct TypewriterHandle : IEquatable<TypewriterHandle>
|
||
|
// {
|
||
|
// public int InstanceID;
|
||
|
// public int Version;
|
||
|
//
|
||
|
// public bool Equals(TypewriterHandle other) =>
|
||
|
// InstanceID == other.InstanceID && Version == other.Version;
|
||
|
// }
|
||
|
//
|
||
|
// private struct TypewriterJob
|
||
|
// {
|
||
|
// public TypewriterHandle Handle;
|
||
|
// public TMP_Text Target;
|
||
|
// public string Content;
|
||
|
// public float Speed;
|
||
|
// public int LoopCount;
|
||
|
// public Action<int> OnUpdate;
|
||
|
// public Action OnComplete;
|
||
|
// public CancellationToken ExternalToken;
|
||
|
// }
|
||
|
//
|
||
|
// #endregion
|
||
|
//
|
||
|
// #region 状态管理
|
||
|
//
|
||
|
// private static readonly Dictionary<int, ActiveJob> activeJobs =
|
||
|
// new Dictionary<int, ActiveJob>(32);
|
||
|
//
|
||
|
// private struct ActiveJob
|
||
|
// {
|
||
|
// public int Version;
|
||
|
// public CancellationTokenSource Cts;
|
||
|
// }
|
||
|
//
|
||
|
// private static readonly ObjectPool<CancellationTokenSource> ctsPool =
|
||
|
// ObjectPool<CancellationTokenSource>(
|
||
|
// createFunc: () => new CancellationTokenSource(),
|
||
|
// actionOnRelease: cts => cts.Cancel(),
|
||
|
// collectionCheck: false
|
||
|
// );
|
||
|
//
|
||
|
// #endregion
|
||
|
//
|
||
|
// #region 公开接口
|
||
|
//
|
||
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
// public static void Play(
|
||
|
// this TMP_Text text,
|
||
|
// string content,
|
||
|
// float charsPerSecond = 30,
|
||
|
// int loopCount = 0,
|
||
|
// Action<int> onUpdate = null,
|
||
|
// Action onComplete = null,
|
||
|
// CancellationToken externalToken = default)
|
||
|
// {
|
||
|
// var instanceID = text.GetInstanceID();
|
||
|
//
|
||
|
// // 停止当前任务(如果有)
|
||
|
// if (activeJobs.TryGetValue(instanceID, out var existingJob))
|
||
|
// {
|
||
|
// ctsPool.Release(existingJob.Cts);
|
||
|
// activeJobs.Remove(instanceID);
|
||
|
// }
|
||
|
//
|
||
|
// // 从对象池获取CTS
|
||
|
// var cts = ctsPool.Get();
|
||
|
// var linkedToken = CombineTokens(
|
||
|
// text.GetCancellationTokenOnDestroy(),
|
||
|
// externalToken,
|
||
|
// cts.Token
|
||
|
// );
|
||
|
//
|
||
|
// // 创建新任务
|
||
|
// var newVersion = activeJobs.TryGetValue(instanceID, out var job) ? job.Version + 1 : 1;
|
||
|
//
|
||
|
// activeJobs[instanceID] = new ActiveJob
|
||
|
// {
|
||
|
// Version = newVersion,
|
||
|
// Cts = cts
|
||
|
// };
|
||
|
//
|
||
|
// RunJob(new TypewriterJob
|
||
|
// {
|
||
|
// Handle = new TypewriterHandle
|
||
|
// {
|
||
|
// InstanceID = instanceID,
|
||
|
// Version = newVersion
|
||
|
// },
|
||
|
// Target = text,
|
||
|
// Content = content,
|
||
|
// Speed = Mathf.Max(0.01f, charsPerSecond),
|
||
|
// LoopCount = loopCount,
|
||
|
// OnUpdate = onUpdate,
|
||
|
// OnComplete = onComplete,
|
||
|
// ExternalToken = linkedToken
|
||
|
// }).Forget();
|
||
|
// }
|
||
|
//
|
||
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
// public static void Stop(this TMP_Text text)
|
||
|
// {
|
||
|
// var instanceID = text.GetInstanceID();
|
||
|
// if (activeJobs.TryGetValue(instanceID, out var job))
|
||
|
// {
|
||
|
// ctsPool.Release(job.Cts);
|
||
|
// activeJobs.Remove(instanceID);
|
||
|
// ResetTextState(text);
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// #endregion
|
||
|
//
|
||
|
// #region 核心逻辑
|
||
|
//
|
||
|
// private static async UniTaskVoid RunJob(TypewriterJob job)
|
||
|
// {
|
||
|
// try
|
||
|
// {
|
||
|
// var target = job.Target;
|
||
|
// using (var sb = ZString.CreateStringBuilder())
|
||
|
// {
|
||
|
// sb.Append(job.Content);
|
||
|
// target.text = sb.ToString();
|
||
|
// target.ForceMeshUpdate();
|
||
|
//
|
||
|
// int currentLoop = 0;
|
||
|
// var chars = sb.AsSpan();
|
||
|
// var interval = 1f / job.Speed;
|
||
|
// var timer = 0f;
|
||
|
//
|
||
|
// while (IsLoopValid(currentLoop, job.LoopCount))
|
||
|
// {
|
||
|
// target.maxVisibleCharacters = 0;
|
||
|
//
|
||
|
// for (int i = 0; i < chars.Length; i++)
|
||
|
// {
|
||
|
// // 版本校验
|
||
|
// if (!IsHandleValid(job.Handle)) return;
|
||
|
//
|
||
|
// // 基于时间的更新
|
||
|
// timer += Time.deltaTime;
|
||
|
// var required = (int)(timer * job.Speed);
|
||
|
//
|
||
|
// if (required > i)
|
||
|
// {
|
||
|
// target.maxVisibleCharacters = required;
|
||
|
// job.OnUpdate?.Invoke(required);
|
||
|
// }
|
||
|
//
|
||
|
// await UniTask.Yield(PlayerLoopTiming.Update, job.ExternalToken);
|
||
|
// }
|
||
|
//
|
||
|
// currentLoop++;
|
||
|
// timer = 0f;
|
||
|
// job.OnComplete?.Invoke();
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
// finally
|
||
|
// {
|
||
|
// if (IsHandleValid(job.Handle))
|
||
|
// {
|
||
|
// activeJobs.Remove(job.Handle.InstanceID);
|
||
|
// ResetTextState(job.Target);
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// #endregion
|
||
|
//
|
||
|
// #region 工具方法
|
||
|
//
|
||
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
// private static bool IsHandleValid(TypewriterHandle handle)
|
||
|
// {
|
||
|
// return activeJobs.TryGetValue(handle.InstanceID, out var job) &&
|
||
|
// job.Version == handle.Version;
|
||
|
// }
|
||
|
//
|
||
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
// private static bool IsLoopValid(int current, int max) =>
|
||
|
// max < 0 || current <= max;
|
||
|
//
|
||
|
// private static CancellationToken CombineTokens(
|
||
|
// CancellationToken token1,
|
||
|
// CancellationToken token2,
|
||
|
// CancellationToken token3)
|
||
|
// {
|
||
|
// if (token1.CanBeCanceled && token2.CanBeCanceled && token3.CanBeCanceled)
|
||
|
// return CancellationTokenSource.CreateLinkedTokenSource(
|
||
|
// token1, token2, token3).Token;
|
||
|
//
|
||
|
// if (token1.CanBeCanceled && token2.CanBeCanceled)
|
||
|
// return CancellationTokenSource.CreateLinkedTokenSource(
|
||
|
// token1, token2).Token;
|
||
|
//
|
||
|
// return token1.CanBeCanceled ? token1 : token2;
|
||
|
// }
|
||
|
//
|
||
|
// private static void ResetTextState(TMP_Text text)
|
||
|
// {
|
||
|
// if (text != null)
|
||
|
// {
|
||
|
// text.maxVisibleCharacters = int.MaxValue;
|
||
|
// text.SetVerticesDirty();
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// private static CancellationToken GetCancellationTokenOnDestroy(this TMP_Text text)
|
||
|
// {
|
||
|
// return text.gameObject.GetCancellationTokenOnDestroy();
|
||
|
// }
|
||
|
//
|
||
|
// #endregion
|
||
|
// }
|
||
|
//
|
||
|
// }
|