← تمام مضامین پر واپس

ایک ایجنٹ PR سویپ کا جائزہ: 27 پل ریکوئسٹس، چار قسم کے بگ، اور وہ سطر جو ایک مرج میں گم ہو گئی

شائع کردہ:
candyrushagent-runevidenceunity

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 اور چلانے کے لیے بعد کی commits21
مرج کے بعد ملنے والی رن ٹائم خرابیاں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 نے ایک AnimationTriggers constants کلاس متعارف کرائی، جو chest ونڈو میں UnityEngine.UI.AnimationTriggers سے ٹکرا گئی۔
  • #120 نے WaveData کے fields کو properties بنا دیا؛ spawner اور دو ٹیسٹ فائلیں ابھی تک fields استعمال کر رہی تھیں۔
  • ایک باس behaviour میں دہرے fields اور ایک دہرا Construct method آ گیا۔

کل اکیس 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 لوٹا دے۔