إنتقل إلى المحتوى الرئيسي

الميزات

مرجع الميزات الكامل لـ Valkarn Tasks.


البنية الأساسية غير المتزامنة

بنية ValkarnTask

نوع إرجاع async قائم على struct بدون تخصيص للذاكرة، يحل محل كل من UniTask وAwaitable الخاص بـ Unity.

async ValkarnTask LoadLevel() { ... }
async ValkarnTask<int> CountEnemies() { ... }
  • مسار سريع متزامن بدون تخصيص — إذا اكتملت الطريقة دون تعليق مطلقاً، لا تخصيص على الكومة.
  • مسار async مع pool — إذا تعلقت الطريقة، يُؤخذ runner آلة الحالة من pool محدود وقابل للتقليص. لا boxing، مجمعات محدودة مع قص تلقائي.
  • تجميع IL2CPP-first — عمليات pool على الخيط الرئيسي تستخدم صفر عمليات ذرية. يُعاقب IL2CPP على Volatile بمقدار 9.2× وعلى Interlocked بمقدار 2.9× مقارنة بـ Mono؛ يتجنب Valkarn كليهما في المسار الساخن.
  • أمان الرمز المميز الجيلي — عداد جيل uint لكل فتحة pool. 4,294,967,296 دورة لكل فتحة قبل التصادم — مستحيل عملياً. (يستخدم UniTask رمزاً مميزاً short: التصادم بعد ~18 دقيقة من العمل غير المتزامن النشط.)

Result<T> — معالجة أخطاء بدون استثناءات

var result = await loadTask.AsResult();

if (result.Succeeded) Use(result.Value);
else if (result.IsCanceled) ShowRetry();
else Debug.LogError(result.Error);

Result<T> وResult هي قيم readonly struct تمثل نتيجة مهمة دون رمي استثناءات. كلاهما يدعم التحويل الضمني إلى bool (صحيح عند النجاح).

مصادر الإكمال اليدوي

var promise = new ValkarnTask.Promise<string>();
promise.TrySetResult("done");
string value = await promise.Task;

يدعم TrySetResult وTrySetException وTrySetCanceled. الإبلاغ عن الاستثناءات غير الملاحَظة القائم على finalizer يضمن عدم فقدان الأخطاء بصمت.

مصادر الإكمال مع pool

متغيرات pool ذات إعادة تعيين تلقائية للأنماط المتكررة (تستخدمها القنوات والمدمجات داخلياً):

var source = ValkarnTask.PooledPromise<int>.Create(out uint token);
source.TrySetResult(42);
int value = await source.Task; // يعود source إلى pool تلقائياً

جسر Awaitable

تشغيل بيني شفاف مع Awaitable الخاص بـ Unity — بدون تحويل يدوي:

async ValkarnTask LoadGame()
{
await SceneManager.LoadSceneAsync("Level2"); // Unity Awaitable — يعمل مباشرة
await Resources.LoadAsync<Texture2D>("hero"); // Unity Awaitable — يعمل مباشرة
await ValkarnTask.Delay(1000); // أصلي Valkarn
}

يكتشف المولّد المصدري انتظارات Awaitable ويولّد المحوّل تلقائياً.


إلغاء دورة الحياة

تلقائي (مُولَّد من الكود المصدري)

حدّد الكلاس بـ partial — المولّد المصدري يتولى الباقي:

public partial class EnemySpawner : MonoBehaviour
{
async ValkarnTask Start()
{
while (true)
{
await ValkarnTask.Delay(3000);
SpawnWave();
// يُلغى تلقائياً عند تدمير هذا الـ GameObject.
}
}
}
  • MonoBehaviour — مرتبط بـ destroyCancellationToken
  • ScriptableObject — مرتبط بمدة حياة التطبيق
  • كلاس عادي — لا ربط تلقائي (مطلوب رمز يدوي)

تعطيل

[NoAutoCancel]
async ValkarnTask BackgroundSync()
{
await SyncToServer(); // لا يُلغى تلقائياً عند التدمير
}

تجاوز CancellationToken يدوي

تمرير CancellationToken صريح يتجاوز الرمز المحقون تلقائياً لدورة الحياة:

async ValkarnTask DoWork(CancellationToken ct)
{
await ValkarnTask.Delay(1000, ct);
}

لا إلغاء للمهام الشقيقة

لا تلغي المهام المهام الشقيقة. WhenAll ينتظر جميع المهام؛ WhenAny يُرجع النتيجة الأولى لكن المهام الخاسرة تستمر في التشغيل. هذا يمنع تلف البيانات عندما يكون للمهام آثار جانبية.


الأقسام الحرجة

للعمليات التي يجب ألا تُقطع بإلغاء دورة الحياة:

async ValkarnTask SaveProgress()
{
var data = await LoadPlayerData(); // قابل للإلغاء

await using (ValkarnTask.Critical())
{
await db.Insert(data); // لا يُلغى حتى لو تم تدمير GO
await db.Commit();
} // يُطبَّق الإلغاء المعلق هنا

await SendNotification(); // قابل للإلغاء مجدداً
}

داخل القسم الحرج، يكون الإلغاء مؤجلاً — وليس متجاهلاً. عند انتهاء القسم، يُطبَّق الإلغاء المعلق.


المدمجات Combinators

WhenAll (محدد النوع)

// مباشر — يرمي استثناء إذا فشلت أي مهمة
var (enemies, map) = await ValkarnTask.WhenAll(LoadEnemies(), LoadMap());

// آمن — ادمج مع AsResult() لسلوك بدون رمي
var (a, b) = await ValkarnTask.WhenAll(
LoadEnemies().AsResult(), LoadMap().AsResult());

مسار سريع متزامن بدون تخصيص عندما تكون جميع المهام مكتملة بالفعل. التحميل الزائد IEnumerable<ValkarnTask<T>> يستخدم ArrayPool<T> للمصفوفات الداخلية.

WhenAll (void)

await ValkarnTask.WhenAll(SaveA(), SaveB(), SaveC());
await ValkarnTask.WhenAll(taskList); // IEnumerable<ValkarnTask>

WhenAny

var (winnerIndex, result) = await ValkarnTask.WhenAny(DownloadFromA(), DownloadFromB());

يُرجع أول نتيجة مكتملة. تستمر المهام الخاسرة في التشغيل بشكل طبيعي.

Fire-and-forget

SendAnalytics("event").Forget();

[FireAndForget]
async ValkarnTask SendAnalytics(string eventName) { ... }
// المتصلون لا يحتاجون إلى .Forget() — لا تحذير يُولَّد

.Forget() يوجه الأخطاء إلى ValkarnTask.UnobservedException. لا تُبتلَع بصمت أبداً.

AsNonGeneric

ValkarnTask voidTask = typedTask.AsNonGeneric();

الوقت والتأخير

await ValkarnTask.Delay(1000);                                    // ميلي ثانية
await ValkarnTask.Delay(TimeSpan.FromSeconds(2)); // TimeSpan
await ValkarnTask.Delay(1000, DelayType.UnscaledDeltaTime); // تجاهل timescale
await ValkarnTask.Delay(1000, DelayType.Realtime); // قائم على Stopwatch

await ValkarnTask.Yield(); // علامة PlayerLoop التالية
await ValkarnTask.Yield(PlayerLoopTiming.FixedUpdate); // توقيت محدد
await ValkarnTask.NextFrame(); // الإطار التالي مضمون
await ValkarnTask.DelayFrame(5); // N إطاراً

await ValkarnTask.WaitUntil(() => player.IsReady);
await ValkarnTask.WaitWhile(() => isLoading);

التبديل بين الخيوط

async ValkarnTask ProcessData()
{
var raw = await DownloadData();

await ValkarnTask.SwitchToThreadPool();
var processed = HeavyComputation(raw); // خيط خلفي

await ValkarnTask.SwitchToMainThread();
ApplyToGameObject(processed); // الخيط الرئيسي
}

16 توقيتاً لـ PlayerLoop

المجموعةالتوقيتات
InitializationInitialization، LastInitialization
EarlyUpdateEarlyUpdate، LastEarlyUpdate
FixedUpdateFixedUpdate، LastFixedUpdate
PreUpdatePreUpdate، LastPreUpdate
UpdateUpdate، LastUpdate
PreLateUpdatePreLateUpdate، LastPreLateUpdate
PostLateUpdatePostLateUpdate، LastPostLateUpdate
TimeUpdateTimeUpdate، LastTimeUpdate

تستخدم جميع العمليات PlayerLoopTiming.Update افتراضياً ما لم يُحدَّد غير ذلك.


القنوات

// غير محدود
var channel = ValkarnTask.Channel.CreateUnbounded<EnemySpawnRequest>();

// محدود — backpressure عند الامتلاء
var channel = ValkarnTask.Channel.CreateBounded<LogEntry>(capacity: 100);

// متعدد المستهلكين
var channel = ValkarnTask.Channel.CreateUnbounded<WorkItem>(multiConsumer: true);

// المنتج
await channel.Writer.WriteAsync(entry);
bool accepted = channel.Writer.TryWrite(entry);

// المستهلك
await foreach (var item in channel.Reader.ReadAllAsync())
Process(item);

// الإكمال
channel.Writer.Complete();
await channel.Reader.Completion;

الاختبار الحتمي (TestClock)

[Test]
public void Respawn_WaitsThreeSeconds()
{
var clock = new TestClock();
var task = spawner.RespawnEnemy();

clock.Advance(TimeSpan.FromSeconds(2));
Assert.IsFalse(task.IsCompleted);

clock.Advance(TimeSpan.FromSeconds(1));
Assert.IsTrue(task.IsCompleted);
}

جميع العمليات الزمنية تقرأ من TimeProvider.Current. في الاختبارات، استبدلها بـ TestClock. AdvanceFrame() يحاكي علامة player loop واحدة.


جسر نظام Job

var job = new PathfindingJob { start = a, end = b, results = results };
await job.ScheduleAsync();
// NativeArray الخاصة بـ results قابلة للقراءة فوراً بعد await

await job.ScheduleParallelAsync(dataCount, batchSize); // IJobParallelFor

// الإلغاء — أكمل job handle قبل الإبلاغ عن الإلغاء (بدون تسرب job)
await job.ScheduleAsync(cancellationToken);

التشخيصات في وقت الترجمة

الكودالخطورةالوصف
TT001تحذيرDouble-await / use-after-free على ValkarnTask
TT002خطأنتيجة async ValkarnTask مستخدمة كجملة تعبير — يجب انتظارها أو .Forget()
TT010معلومةطريقة async في MonoBehaviour تُلغى تلقائياً عند Destroy
TT011تحذيريحتوي WhenAll على مهام بدورات حياة مختلفة
TT012تحذيرحلقة async بدون فحص إلغاء (حلقة زومبي محتملة)
TT013تحذيرValkarnTask مُرجَع لكن لم يُنتظر ولم يُتجاهل صراحةً
TT014تحذير[NoAutoCancel] بدون معامل CancellationToken يدوي
TT015معلومةانتظار Awaitable داخل ValkarnTask — تم توليد محوّل bridge
TT016تحذيرطريقة async بدون تعبير await
TT017تحذير[FireAndForget] على ValkarnTask<T> — يتجاهل قيمة الإرجاع

إدارة Pool

كل runner لطريقة async مُجمَّع عبر ValkarnPool<T>:

  • الخيط الرئيسي — مكدس خالٍ من القفل، صفر عمليات ذرية
  • الخيوط الخلفية — Treiber lock-free stack مع عمليات CAS
  • قص قائم على الإطار — كل 300 إطار (~5 ثوانٍ عند 60fps)، تُحرَّر الكائنات الزائدة تدريجياً
  • لا تنخفض أبداً دون الحد الأدنى القابل للتهيئة (الافتراضي: 8)

راقب في وقت التشغيل:

foreach (var (type, size, maxSize) in ValkarnTask.GetPoolInfo())
Debug.Log($"{type}: {size}/{maxSize}");

ValkarnTaskSettings

قم بالتهيئة عبر ScriptableObject (Assets > Create > Valkarn > Tasks > Task Settings، ضعه في Resources/):

الإعدادالافتراضيالوصف
DefaultMaxPoolSize256الحد الأقصى للعناصر لكل نوع pool
MinPoolSize8لا تقص أبداً دون هذا الحد
TrimCheckInterval300الإطارات بين فحوصات القص
TrimHysteresisCount2فحوصات متتالية قبل القص
TrimReleaseRatio0.25نسبة الفائض المُحرَّر لكل دورة
EnableAutoCanceltrueربط مهام MonoBehaviour تلقائياً بـ destroyCancellationToken
LogUnobservedCancellationsfalseتسجيل الإلغاءات غير الملاحَظة كتحذيرات
MaxExceptionLogsPerFrame10حد سجلات الاستثناء لكل إطار

معالجة الأخطاء

// الاستثناءات غير الملاحَظة — تُطلَق بشكل حتمي عند إرجاع pool
ValkarnTask.UnobservedException += ex =>
Debug.LogError($"Unobserved: {ex}");
  • WhenAll — يرمي أول استثناء، يوجه الاستثناءات الإضافية إلى UnobservedException
  • WhenAny — يُرمى استثناء الفائز؛ أعطال الخاسرين تذهب إلى UnobservedException
  • إلغاء دورة الحياة — OperationCanceledException مُكبَّت افتراضياً (قابل للتهيئة)

طرق المصنع

ValkarnTask.CompletedTask          // void، بدون تخصيص
ValkarnTask.FromResult<T>(value) // محدد النوع، بدون تخصيص
ValkarnTask.FromException(ex) // معطوب
ValkarnTask.FromException<T>(ex)
ValkarnTask.FromCanceled() // مُلغى
ValkarnTask.FromCanceled<T>()
ValkarnTask.Never // لا يكتمل أبداً (sentinel لـ WhenAny)