ایک ایجنٹ PR سویپ کا جائزہ: 27 پل ریکوئسٹس، چار قسم کے بگ، اور وہ سطر جو ایک مرج میں گم ہو گئی
18 نومبر 2025 کو CandyRush پر 26 پل ریکوئسٹس چار منٹ انیس سیکنڈ میں کھولی گئیں
(#117 تا #142، UTC 08:23:42 تا 08:28:01)، اور ستائیسویں (#143) 20 نومبر کو آئی۔
ہر ایک ایک AI ایجنٹ کی طرف سے اپنی الگ claude/* برانچ پر تھی، اور ہر ایک صرف
ایک کام کرتی تھی: پول کا ایک بگ ٹھیک کرنا، ایک lookup بدلنا، ایونٹ ہینڈلرز کی
سبسکرپشن ختم کرنا، ایک DI رجسٹریشن سخت کرنا، ہجے درست کرنا، ٹیسٹ شامل کرنا۔
اس سویپ کی سمت میں نے طے کی اور جو کچھ اندر گیا اس کا گیٹ میں نے رکھا۔ نتیجہ یہ
رہا، سب کا سب GitHub API اور git log سے:
| تعداد | |
|---|---|
| کھولی گئی پل ریکوئسٹس | 27 |
| جوں کی توں مرج ہوئیں | 19 (20 نومبر کو 3، 21 نومبر کو دو منٹ کے اندر 16) |
| مرج conflicts کی وجہ سے بند، پھر #144 میں دوبارہ یکجا | 8 |
| 22 نومبر کو ملے ہوئے نتیجے کو compile اور چلانے کے لیے بعد کی commits | 21 |
| مرج کے بعد ملنے والی رن ٹائم خرابیاں | 1 (دشمنوں کی لہریں بننا بند) |
اس سویپ نے چار حقیقی قسم کے بگ ٹھیک کیے۔ لیکن زیادہ کارآمد سبق گیٹ کے بارے میں ہے: ہر diff اکیلے میں معقول تھا، اور مسائل ان کے ملاپ میں تھے۔
بگ کی چار قسمیں
ایک پول جو ایک چیز زیادہ بناتا تھا (#126)
آبجیکٹ پول کے constructors اس وقت تک loop چلاتے تھے جب تک i کی قدر
startingSize سے کم یا اس کے برابر رہے، اس لیے 50 کے لیے ترتیب دیا گیا پول 51
بناتا تھا۔ درستی دو جگہوں پر ایک ایک حرف کی ہے:
- for (int i = 0; i <= startingSize; i++)
+ for (int i = 0; i < startingSize; i++)
اسی PR نے ہر پول شدہ آبجیکٹ کے لیے GetComponent کے نتائج cache کیے، اور EditMode
ٹیسٹس کی 293 سطریں شامل کیں جو entities کی بالکل درست تعداد کی تصدیق کرتی ہیں۔ PR
کی تفصیل دعویٰ کرتی ہے کہ cache کے بعد component lookups "50-100 گنا تیز" ہیں۔ یہ
کسی نے نہیں ناپا؛ یہ ایجنٹ کا اندازہ ہے۔ درستی اور cache دونوں آج بھی پول میں
موجود ہیں، جو اب میرے مشترکہ UTools پیکیج میں ہے۔
ایک lookup جس پر O(n³) کا لیبل تھا (#131)
HasEvolution لیول اپ کارڈز کے لیے بتاتا ہے کہ "کیا یہ صلاحیت ارتقا پاتی ہے، اور
کس کے ساتھ جوڑا بنتا ہے؟"۔ پرانا ورژن ہر صلاحیت پر چلتا، پھر ہر ارتقا کی شرائط
پر، پھر شرائط پر دوبارہ۔ PR نے اسے ایک dictionary سے بدل دیا جو init پر ایک بار
بنتی ہے۔
عنوان کہتا ہے O(n³) سے O(1)۔ دوبارہ پڑھنے پر یہ مبالغہ لگتا ہے: ہر ارتقا کی صرف دو شرائط ہیں، اس لیے اندرونی loops مستقل ہیں اور پرانا کوڈ صلاحیتوں کی تعداد کے لحاظ سے linear تھا، جلد واپسی کے ساتھ۔ dictionary پھر بھی بہتر ساخت ہے۔ PR کی تفصیل میں "frame time میں متوقع بہتری" کبھی ناپی نہیں گئی۔
اس diff نے خاموشی سے ایک رویہ بھی بدلا۔ پرانا loop پہلا match لوٹاتا تھا؛ dictionary آخری لکھی گئی قدر رکھتی ہے۔ اگر کوئی صلاحیت کبھی دو ارتقاؤں کی شرط بنے تو جواب بدل جائے گا۔ PR نے ان دونوں صورتوں میں سے کسی کے لیے ٹیسٹ شامل نہیں کیا۔
ہینڈلرز جو اپنی ونڈوز کے بعد بھی زندہ رہے (#123)
کئی UI ونڈوز ایونٹس کو سبسکرائب کرتی تھیں اور کبھی ان سبسکرائب نہیں کرتی تھیں،
اس لیے تباہ شدہ ونڈوز publisher سے قابلِ رسائی رہتیں اور تباہی کے بعد بھی
callbacks وصول کر سکتی تھیں۔ درستی نے سات UI behaviours (GameScreen، Chest، Lobby،
Settings، Upgrades، Characters، اور باس کی ہیلتھ بار) میں OnDestroy صفائی شامل
کی، ساتھ ایک ایڈیٹر ٹول جو اسکرپٹس میں ایسے += ڈھونڈتا تھا جن کا مماثل -=
نہ ہو۔
PR کی تفصیل کہتی ہے "10 مراحل کے بعد 1000+ لٹکتے ریفرنسز، جن سے میموری کریش تک بڑھتی ہے"۔ یہ تفصیل میں کیا گیا حساب ہے، پیمائش نہیں، اور میں اسے حقیقت کے طور پر نہیں دہراؤں گا۔ صفائی بہرحال درست ہے۔ وہ اسکینر خود ستمبر 2026 میں ٹولنگ کی ایک کٹوتی (#949) میں حذف کر دیا گیا۔
ایک DI factory جو null لوٹاتی تھی (#137)
composition root نے IUpgradesManager کو ایک factory کے ساتھ رجسٹر کیا تھا جو
کنفیگریشن asset نہ ملنے پر ایک warning لاگ کر کے null لوٹا دیتی تھی۔ پھر ہر
استعمال کنندہ بعد میں، اصل وجہ سے دور، NullReferenceException کے ساتھ ناکام
ہوتا۔ درستی شرط کو الٹ کر startup پر ہی exception پھینکتی ہے:
if (upgradesConfig?.upgradesDatabase == null)
{
throw new System.InvalidOperationException(
"[RootLifetimeScope] UpgradesConfiguration or database not found ...");
}
return new UpgradesService(saveManager, upgradesConfig.upgradesDatabase);
چھ سطریں شامل، پانچ حذف۔ DI container کو کسی رجسٹرڈ سروس کے لیے کبھی null نہیں
دینا چاہیے؛ composition کے وقت ناکام ہونا اصل مسئلے کا نام لیتا ہے۔
سویپ کا باقی حصہ صفائی ستھرائی تھا: magic numbers کی جگہ layer constants (#117)،
پلیٹ فارم Strategy pattern (#118)، ایڈیٹر اسکرپٹس کے guards (#127)، interface
coverage ٹیسٹس (#129، 336 سطریں)، PlayMode DI integration ٹیسٹس (#134، 851
سطریں)، null operators کی جدید کاری (#133)، کلاس ناموں کے ہجوں کی درستی (#138)،
43 ScriptableObject فائلوں پر validation attributes (#141)، XML دستاویزات (#142)،
اور ایک .editorconfig (#143)۔
گیٹ نے کیا پکڑا
CI اچھے اور برے میں فرق نہیں کر سکتا تھا
اس بیچ میں جن پل ریکوئسٹس کو میں نے جانچا، ہر ایک پر Unity ٹیسٹ job ناکام تھا، بشمول #137 کی چھ سطری درستی۔ جو چیک ہر چیز کے لیے سرخ ہو وہ کچھ بھی نہیں روکتا۔ اصل گیٹ ایڈیٹر تھا: مرج، compile، کھیل۔
آٹھ PRs جو ٹکراؤ میں نہ بچ سکیں
پہلی 16 کے مرج ہونے کے بعد، باقی میں سے 8 PRs اب لاگو نہیں ہو رہی تھیں۔ ہر ایک
کو اس نوٹ کے ساتھ بند کیا گیا کہ کام بعد کے لیے محفوظ کر لیا گیا ہے۔ اسی سہ پہر
انہیں cherry-pick کر کے ایک یکجا کرنے والی PR، #144 (+1,280/-948، 76 فائلیں)، میں
ڈالا گیا اور conflicts ہاتھ سے حل کیے گئے۔ اس کی تفصیل ان breaking changes کی
فہرست دیتی ہے جو وہ ساتھ لائی: ایک public API کا نام بدلنا (Aquired سے
Acquired، 103 مقامات)، اور lifetime scopes جنہیں اب Resources.Load کی جگہ
Inspector میں assets تفویض کرنے کی ضرورت تھی۔
معقول حصے، مگر مجموعہ compile نہیں ہوا
22 نومبر کو ایک ایجنٹ سیشن نے ایک ایک کر کے وہ سب ٹھیک کیا جو ملاپ نے توڑا تھا۔ commits ایسے تعاملات کی فہرست لگتی ہیں جو کوئی اکیلی PR نہیں دکھا سکتی تھی:
#126نے پول میںusing System;شامل کیا، جس سےObject.InstantiateاورObject.Destroy،System.ObjectاورUnityEngine.Objectکے درمیان مبہم ہو گئے۔#121نے ایکAnimationTriggersconstants کلاس متعارف کرائی، جو chest ونڈو میںUnityEngine.UI.AnimationTriggersسے ٹکرا گئی۔#120نےWaveDataکے fields کو properties بنا دیا؛ spawner اور دو ٹیسٹ فائلیں ابھی تک fields استعمال کر رہی تھیں۔- ایک باس behaviour میں دہرے fields اور ایک دہرا
Constructmethod آ گیا۔
کل اکیس commits، جن میں #132 کے نشان زدہ [Obsolete] کوڈ کو ہٹانا،
Resources.Load سے منتقلی مکمل کرنا، اور ہر compile warning صاف کرنا شامل تھا۔
وہ سطر جو ایک مرج میں گم ہو گئی
سب سے اہم مسئلہ بالکل ٹھیک compile ہوا۔
#122 نے Timeline playable behaviours میں service locator کی جگہ VContainer
انجیکشن لگایا، اور نئے ITimelineBehaviorInjector کو GameLifetimeScope میں رجسٹر
کیا۔ جب #144 کے conflicts ہاتھ سے حل کیے گئے تو رجسٹریشن کی وہ ایک سطر گم ہو گئی۔
میں نے اسے تاریخ میں تلاش کیا:
2273228d3 #122 merged GameLifetimeScope registers ITimelineBehaviorInjector: yes
bb3128679 #144 merged registration: no
c142d6d7e Resources migration registration: no
707c08c18 fix commit (Nov 22) registration: yes
compile کے وقت کچھ ناکام نہیں ہوا۔ رن ٹائم پر wave behaviours کو ان کی dependencies نہیں ملیں۔ جس commit نے یہ سطر واپس لگائی وہ علامات درج کرتی ہے: Timeline behaviours کی انجیکشن ناکام، اور کوئی دشمن پیدا نہیں ہو رہا۔ compiler گمشدہ رجسٹریشن نہیں دیکھ سکتا؛ گیم چلانا دیکھ سکتا ہے۔ درستی وہی ایک سطر تھی، واپس لگائی گئی۔
میں نے اس سے کیا سیکھا
چھوٹی یک مقصدی PRs کا جائزہ آسان ہے اور انہیں ملانا مشکل۔ ان میں سے ہر ایک ایک منٹ میں پڑھی جا سکتی تھی۔ لاگت مرج کی طرف منتقل ہو گئی، جہاں کوئی diff کو اسی توجہ سے نہیں پڑھتا۔ ہاتھ سے حل کیا گیا یکجا مرج ایک نئی تبدیلی ہے اور اسے اتنے ہی جائزے کا حق ہے جتنا ان PRs کو جن کی وہ جگہ لیتا ہے۔
PR کی تفصیل میں کیے گئے دعوے مفروضے ہیں۔ "50-100 گنا"، "O(n³)"، "1000+ لٹکتے ریفرنسز": ہر ایک تبدیلی کے مصنف نے لکھا، اور کوئی بھی ناپا نہیں گیا۔ اب میں تفصیل میں موجود کسی غیر ناپے عدد کو یا تو تصدیق کرنے کی چیز سمجھتا ہوں یا حذف کرنے کی۔
سرخ گیٹ کوئی گیٹ نہیں۔ جب ہر PR پر CI ناکام ہو تو واحد اشارہ compile کرنا اور کھیلنا تھا۔ ٹیسٹ سویٹ کو سبز اور تیز بنانا بعد میں ایک الگ منصوبہ بنا؛ دیکھیں پائپ لائن کی تنظیمِ نو۔
composition roots کو رن ٹائم جانچ کی ضرورت ہے۔ گمشدہ رجسٹریشن compile ہو جاتی ہے۔ اس سویپ میں یہ خلا "کوئی دشمن نہیں" کی صورت میں سامنے آیا، اور ایسی ناکامی کی ذمہ داری کسی انسان کے بجائے container validation ٹیسٹ یا smoke ٹیسٹ کی ہونی چاہیے۔
جڑ پر جلد ناکام ہوں۔ #137 وہ pattern ہے جو رکھنے کے قابل ہے: ایک factory جو
startup پر گمشدہ asset کے راستے کے ساتھ exception پھینکے، اس factory سے زیادہ قیمتی
ہے جو لاگ کر کے null لوٹا دے۔