Cheerp، WebRTC ۽ Firebase سان C++ کان ويب تي ملٽي پليئر گيم پورٽ ڪرڻ

تعارف

اسان جي ڪمپني ليننگ ٽيڪنالاجيز ويب تي روايتي ڊيسڪ ٽاپ ايپليڪيشنن کي پورٽ ڪرڻ لاءِ حل مهيا ڪري ٿي. اسان جو C++ مرتب ڪندڙ خوش ٿيڻ WebAssembly ۽ JavaScript جو هڪ ميلاپ ٺاهي ٿو، جيڪو ٻنهي کي مهيا ڪري ٿو سادي برائوزر رابطي، ۽ اعلي ڪارڪردگي.

ان جي ايپليڪيشن جي مثال طور، اسان ويب تي هڪ multiplayer راند پورٽ ڪرڻ جو فيصلو ڪيو ۽ چونڊيو ٽيوٽل ورلڊ. Teeworlds هڪ ملٽي پليئر XNUMXD ريٽرو راند آهي جنهن ۾ رانديگرن جي هڪ ننڍڙي پر فعال برادري آهي (مون سميت!). اهو ننڍڙو آهي ٻنهي ڊائون لوڊ ڪيل وسيلن ۽ سي پي يو ۽ GPU گهرجن جي لحاظ کان - هڪ مثالي اميدوار.

Cheerp، WebRTC ۽ Firebase سان C++ کان ويب تي ملٽي پليئر گيم پورٽ ڪرڻ
Teeworlds برائوزر ۾ هلندڙ

اسان فيصلو ڪيو ته هن منصوبي کي استعمال ڪرڻ لاء تجربو ڪيو ويب تي نيٽ ورڪ ڪوڊ پورٽ ڪرڻ لاءِ عام حل. اهو عام طور تي هيٺين طريقن سان ڪيو ويندو آهي:

  • XMLHttpRequest/fatch، جيڪڏهن نيٽ ورڪ حصو صرف HTTP درخواستن تي مشتمل آهي، يا
  • WebSockets.

ٻئي حلن کي سرور جي پاسي تي سرور جي جزو جي ميزباني جي ضرورت آهي، ۽ نه ئي ٽرانسپورٽ پروٽوڪول جي طور تي استعمال جي اجازت ڏئي ٿي. يو ايس پي. اهو حقيقي وقت جي ايپليڪيشنن لاءِ اهم آهي جهڙوڪ وڊيو ڪانفرنسنگ سافٽ ويئر ۽ گيمز، ڇاڪاڻ ته اهو پروٽوڪول پيڪٽس جي ترسيل ۽ آرڊر جي ضمانت ڏئي ٿو ٽي پي گھٽ ويڪرائي ۾ رڪاوٽ بڻجي سگھي ٿي.

ٽيون طريقو آهي - برائوزر کان نيٽ ورڪ استعمال ڪريو: WebRTC.

RTCDataChannel اهو ٻنهي قابل اعتماد ۽ ناقابل اعتبار ٽرانسميشن کي سپورٽ ڪري ٿو (بعد ۾ اهو UDP استعمال ڪرڻ جي ڪوشش ڪري ٿو ٽرانسپورٽ پروٽوڪول جي طور تي جڏهن ممڪن هجي)، ۽ ٻنهي کي ريموٽ سرور ۽ برائوزرن جي وچ ۾ استعمال ڪري سگهجي ٿو. ان جو مطلب اهو آهي ته اسان سرور جي جزو سميت پوري ايپليڪيشن کي برائوزر ڏانهن پورٽ ڪري سگهون ٿا!

بهرحال، هي هڪ اضافي مشڪل سان اچي ٿو: ٻه WebRTC ساٿين سان رابطو ڪرڻ کان اڳ، انهن کي ڳنڍڻ لاءِ نسبتاً پيچيده هٿ ملائڻ جي ضرورت آهي، جنهن لاءِ ڪيترن ئي ٽئين پارٽي ادارن جي ضرورت آهي (هڪ سگنلنگ سرور ۽ هڪ يا وڌيڪ سرور اسٽون/ٽرن).

مثالي طور، اسان هڪ نيٽ ورڪ API ٺاهڻ چاهيون ٿا جيڪو اندروني طور تي WebRTC استعمال ڪري ٿو، پر جيترو ممڪن هجي هڪ UDP ساکٽ انٽرفيس جي ويجهو هجي جنهن کي ڪنيڪشن قائم ڪرڻ جي ضرورت ناهي.

هي اسان کي اجازت ڏيندو WebRTC جو فائدو وٺڻ کان سواءِ ايپليڪيشن ڪوڊ ۾ پيچيده تفصيلن کي ظاهر ڪرڻ جي (جنهن کي اسان پنهنجي منصوبي ۾ جيترو ممڪن طور تبديل ڪرڻ چاهيون ٿا).

گھٽ ۾ گھٽ WebRTC

WebRTC برائوزرن ۾ موجود APIs جو ھڪڙو سيٽ آھي جيڪو آڊيو، وڊيو ۽ صوابديدي ڊيٽا جي پيئر-ٽو-پيئر ٽرانسميشن مهيا ڪري ٿو.

ساٿين جي وچ ۾ ڪنيڪشن قائم ڪيو ويو آهي (جيتوڻيڪ هڪ يا ٻنهي پاسن تي NAT هجي) STUN ۽/يا TURN سرور استعمال ڪندي ICE نالي هڪ ميکانيزم ذريعي. ساٿي ايس ڊي پي پروٽوڪول جي آڇ ۽ جواب ذريعي ICE معلومات ۽ چينل پيٽرولر مٽائي.

واهه! هڪ وقت ۾ ڪيترا مخفف آهن؟ اچو ته مختصر طور تي وضاحت ڪريون ته انهن اصطلاحن جو مطلب ڇا آهي:

  • NAT لاءِ سيشن ٽرورسل يوٽيلٽيز (اسٽون) - هڪ پروٽوڪول NAT کي بائي پاس ڪرڻ ۽ هڪ جوڙو حاصل ڪرڻ لاءِ (IP، پورٽ) سڌو سنئون ميزبان سان ڊيٽا مٽائڻ لاءِ. جيڪڏهن هو پنهنجي ڪم کي مڪمل ڪرڻ جو انتظام ڪري ٿو، پوء ساٿين کي آزاديء سان هڪ ٻئي سان ڊيٽا مٽائي سگهي ٿو.
  • ٽرورسل استعمال ڪندي ريل جي چوڌاري NAT (ٽرن) NAT ٽرورسل لاءِ پڻ استعمال ڪيو ويندو آهي، پر اهو ان کي لاڳو ڪري ٿو ڊيٽا کي فارورڊ ڪندي پراکسي ذريعي جيڪو ٻنهي ساٿين کي نظر اچي ٿو. اهو دير سان شامل ڪري ٿو ۽ STUN جي ڀيٽ ۾ لاڳو ڪرڻ وڌيڪ قيمتي آهي (ڇاڪاڻ ته اهو سڄي مواصلاتي سيشن ۾ لاڳو ٿئي ٿو)، پر ڪڏهن ڪڏهن اهو واحد اختيار آهي.
  • انٽرايڪٽو ڪنيڪشن اسٽيبلشمينٽ (ICE) ٻن ساٿين کي ڳنڍڻ جو بھترين ممڪن طريقو چونڊڻ لاءِ استعمال ڪيو ويو آھي معلومات جي بنياد تي سڌو سنئون ڳنڍڻ وارن مان حاصل ڪيل معلومات جي بنياد تي، ۽ انهي سان گڏ معلومات حاصل ڪئي وئي ڪنهن به نمبر STUN ۽ TURN سرورز طرفان.
  • سيشن وضاحت پروٽوڪول (ايس ڊي پي) ڪنيڪشن چينل جي پيراگرافن کي بيان ڪرڻ لاءِ هڪ فارميٽ آهي، مثال طور، ICE اميدوار، ملٽي ميڊيا ڪوڊيڪس (آڊيو/وڊيو چينل جي صورت ۾) وغيره... ساٿين مان هڪ هڪ SDP آڇ موڪلي ٿو، ۽ ٻيو SDP جواب سان جواب ڏئي ٿو. . . ان کان پوء، هڪ چينل ٺاهي وئي آهي.

اهڙو ڪنيڪشن ٺاهڻ لاءِ، ساٿين کي اها معلومات گڏ ڪرڻ جي ضرورت آهي جيڪا اهي وصول ڪن ٿا STUN ۽ ٽرن سرورز ۽ ان کي هڪ ٻئي سان مٽائڻ.

مسئلو اهو آهي ته انهن وٽ اڃا تائين سڌي ڳالهه ٻولهه ڪرڻ جي صلاحيت نه آهي، تنهنڪري هن ڊيٽا کي مٽائڻ لاءِ هڪ آئوٽ آف بينڊ ميڪانيزم موجود هجڻ ضروري آهي: هڪ سگنلنگ سرور.

هڪ سگنلنگ سرور تمام سادو ٿي سگهي ٿو ڇاڪاڻ ته ان جو واحد ڪم هٿ ملائڻ واري مرحلي ۾ ساٿين جي وچ ۾ ڊيٽا کي اڳتي وڌائڻ آهي (جيئن هيٺ ڏنل ڊراگرام ۾ ڏيکاريل آهي).

Cheerp، WebRTC ۽ Firebase سان C++ کان ويب تي ملٽي پليئر گيم پورٽ ڪرڻ
آسان WebRTC هٿ ملائڻ جي ترتيب جو خاڪو

Teeworlds نيٽورڪ ماڊل جو جائزو

Teeworlds نيٽ ورڪ فن تعمير تمام سادو آهي:

  • ڪلائنٽ ۽ سرور جا حصا ٻه مختلف پروگرام آهن.
  • ڪلائنٽ ڪيترن ئي سرورن مان هڪ سان ڳنڍڻ سان راند ۾ داخل ٿين ٿا، جن مان هر هڪ هڪ وقت ۾ صرف هڪ راند کي ميزباني ڪري ٿو.
  • راند ۾ سڀ ڊيٽا جي منتقلي سرور ذريعي ڪيو ويندو آهي.
  • ھڪڙو خاص ماسٽر سرور استعمال ڪيو ويندو آھي ھڪڙي فهرست گڏ ڪرڻ لاءِ سڀني پبلڪ سرورز جيڪي ڏيکاريل آھن گيم ڪلائنٽ ۾.

ڊيٽا جي مٽاسٽا لاءِ WebRTC جي استعمال جي مهرباني، اسان راند جي سرور جي حصي کي برائوزر ڏانهن منتقل ڪري سگهون ٿا جتي ڪلائنٽ واقع آهي. هي اسان کي هڪ عظيم موقعو ڏئي ٿو ...

سرورز کان نجات حاصل ڪريو

سرور جي منطق جي کوٽ جو هڪ سٺو فائدو آهي: اسان مڪمل ايپليڪيشن کي جامد مواد طور Github صفحن تي يا Cloudflare جي پويان اسان جي هارڊويئر تي ترتيب ڏئي سگهون ٿا، اهڙيء طرح تيز ڊائون لوڊ ۽ اعلي اپ ٽائم کي مفت ۾ يقيني بڻائي. حقيقت ۾، اسان انهن جي باري ۾ وساري سگهون ٿا، ۽ جيڪڏهن اسان خوش قسمت آهيون ۽ راند مشهور ٿي وڃي، پوء انفراسٹرڪچر کي جديد بڻائڻ جي ضرورت ناهي.

بهرحال، سسٽم ڪم ڪرڻ لاء، اسان کي اڃا تائين هڪ خارجي فن تعمير استعمال ڪرڻو پوندو:

  • ھڪڙو يا وڌيڪ STUN سرور: اسان وٽ ڪيترائي مفت اختيار آھن جن مان چونڊڻ لاءِ.
  • گهٽ ۾ گهٽ هڪ موڙ سرور: هتي ڪي به مفت آپشن نه آهن، تنهنڪري اسان يا ته پنهنجو پاڻ سيٽ ڪري سگهون ٿا يا خدمت لاءِ ادا ڪري سگهون ٿا. خوشقسمتيءَ سان، اڪثر وقت ڪنيڪشن STUN سرورز ذريعي قائم ڪري سگھجي ٿو (۽ صحيح p2p مهيا ڪريو)، پر ٹرن کي فال بيڪ آپشن جي ضرورت آھي.
  • سگنلنگ سرور: ٻين ٻن حصن جي برعڪس، سگنلنگ معياري نه آهي. ڇا سگنلنگ سرور اصل ۾ ذميوار هوندو ڪجهه حد تائين ايپليڪيشن تي منحصر آهي. اسان جي صورت ۾، هڪ ڪنيڪشن قائم ڪرڻ کان اڳ، ان کي ضروري آهي ته ڊيٽا جي هڪ ننڍي رقم مٽائي.
  • Teeworlds ماسٽر سرور: اهو ٻين سرورن طرفان استعمال ڪيو ويندو آهي انهن جي وجود کي اشتهار ڏيڻ لاءِ ۽ گراهڪن طرفان عوامي سرور ڳولڻ لاءِ. جڏهن ته اها گهربل نه آهي (ڪلائنٽ هميشه سرور سان ڳنڍي سگهن ٿا جن جي باري ۾ اهي دستي طور تي ڄاڻن ٿا)، اهو سٺو هوندو ته جيئن رانديگرن کي بي ترتيب ماڻهن سان راندين ۾ حصو وٺن.

اسان گوگل جا مفت STUN سرور استعمال ڪرڻ جو فيصلو ڪيو، ۽ پاڻ هڪ ٽرن سرور مقرر ڪيو.

آخري ٻن پوائنٽن لاءِ اسان استعمال ڪيو باهه:

  • Teeworlds ماسٽر سرور بلڪل آسانيء سان لاڳو ڪيو ويو آھي: شين جي ھڪڙي فهرست جي طور تي معلومات تي مشتمل آھي (نالو، IP، نقشو، موڊ، ...). سرور شايع ڪن ٿا ۽ تازه ڪاري ڪن ٿا پنهنجو اعتراض، ۽ گراهڪ سڄي لسٽ وٺي ۽ ان کي پليئر ڏانهن ڏيکاري. اسان لسٽ کي هوم پيج تي HTML جي طور تي پڻ ڏيکاريون ٿا ته جيئن رانديگرن کي صرف سرور تي ڪلڪ ڪيو وڃي ۽ سڌو سنئون راند ڏانهن وٺي وڃي.
  • سگنلنگ اسان جي ساکٽ تي عمل درآمد سان ويجهي سان لاڳاپيل آهي، ايندڙ حصي ۾ بيان ڪيو ويو آهي.

Cheerp، WebRTC ۽ Firebase سان C++ کان ويب تي ملٽي پليئر گيم پورٽ ڪرڻ
راند جي اندر ۽ هوم پيج تي سرورز جي فهرست

ساکٽ جو نفاذ

اسان هڪ API ٺاهڻ چاهيون ٿا جيڪو ممڪن طور تي Posix UDP ساکٽس جي ويجهو هجي گهربل تبديلين جو تعداد گھٽائڻ لاءِ.

اسان نيٽ ورڪ تي آسان ترين ڊيٽا جي مٽاسٽا لاءِ گهربل گھٽ ۾ گھٽ گهربل لاڳو ڪرڻ پڻ گھرون ٿا.

مثال طور، اسان کي حقيقي رستي جي ضرورت نه آهي: سڀئي ساٿي ساڳيا "مجازي LAN" تي آهن جيڪي هڪ مخصوص فائر بيس ڊيٽابيس مثال سان لاڳاپيل آهن.

تنهن ڪري، اسان کي منفرد IP پتي جي ضرورت ناهي: منفرد فائر بيس اهم قدر (ڊومين نالن وانگر) منفرد طور تي ڀائيوارن کي سڃاڻڻ لاء ڪافي آهن، ۽ هر پير مقامي طور تي "جعلي" IP پتي کي تفويض ڪري ٿو هر ڪنجي کي جيڪو ترجمو ڪرڻ جي ضرورت آهي. اهو مڪمل طور تي عالمي IP پتي جي تفويض جي ضرورت کي ختم ڪري ٿو، جيڪو هڪ غير معمولي ڪم آهي.

ھتي آھي گھٽ ۾ گھٽ API اسان کي لاڳو ڪرڻ جي ضرورت آھي:

// Create and destroy a socket
int socket();
int close(int fd);
// Bind a socket to a port, and publish it on Firebase
int bind(int fd, AddrInfo* addr);
// Send a packet. This lazily create a WebRTC connection to the 
// peer when necessary
int sendto(int fd, uint8_t* buf, int len, const AddrInfo* addr);
// Receive the packets destined to this socket
int recvfrom(int fd, uint8_t* buf, int len, AddrInfo* addr);
// Be notified when new packets arrived
int recvCallback(Callback cb);
// Obtain a local ip address for this peer key
uint32_t resolve(client::String* key);
// Get the peer key for this ip
String* reverseResolve(uint32_t addr);
// Get the local peer key
String* local_key();
// Initialize the library with the given Firebase database and 
// WebRTc connection options
void init(client::FirebaseConfig* fb, client::RTCConfiguration* ice);

API سادو آهي ۽ Posix Sockets API سان ملندڙ جلندڙ آهي، پر ڪجھ اهم اختلاف آهن: لاگنگ ڪال بڪ، مقامي IPs تفويض ڪرڻ، ۽ سست ڪنيڪشن.

ڪال بیکس رجسٽر ڪرڻ

جيتوڻيڪ اصل پروگرام غير بلاڪنگ I/O استعمال ڪري ٿو، ڪوڊ کي ويب برائوزر ۾ هلائڻ لاءِ ريفيڪٽر ڪيو وڃي.

هن جو سبب اهو آهي ته برائوزر ۾ واقع لوپ پروگرام کان لڪيل آهي (اهو JavaScript يا WebAssembly هجي).

مقامي ماحول ۾ اسان هن طرح ڪوڊ لکي سگهون ٿا

while(running) {
  select(...); // wait for I/O events
  while(true) {
    int r = readfrom(...); // try to read
    if (r < 0 && errno == EWOULDBLOCK) // no more data available
      break;
    ...
  }
  ...
}

جيڪڏهن واقعي جو لوپ اسان وٽ لڪيل آهي، ته پوءِ اسان کي ان کي ڪجهه هن طرح ۾ تبديل ڪرڻ جي ضرورت آهي:

auto cb = []() { // this will be called when new data is available
  while(true) {
    int r = readfrom(...); // try to read
    if (r < 0 && errno == EWOULDBLOCK) // no more data available
      break;
    ...
  }
  ...
};
recvCallback(cb); // register the callback

مقامي IP تفويض

اسان جي "نيٽ ورڪ" ۾ نوڊ IDs IP پتي نه آهن، پر فائر بيس ڪيز (اهي تار آهن جيڪي هن طرح نظر اچن ٿا: -LmEC50PYZLCiCP-vqde ).

اهو آسان آهي ڇاڪاڻ ته اسان کي IPs کي تفويض ڪرڻ ۽ انهن جي انفراديت کي جانچڻ لاءِ ڪنهن ميکانيزم جي ضرورت ناهي (انهي سان گڏ ڪلائنٽ جي ڌار ٿيڻ کان پوءِ انهن کي ڊسپوز ڪرڻ)، پر اهو اڪثر ضروري هوندو آهي ته ساٿين کي عددي قدر جي ذريعي سڃاڻڻ.

اھو اھو آھي جيڪو ڪم لاء استعمال ڪيو ويندو آھي. resolve и reverseResolve: ايپليڪيشن ڪنهن نه ڪنهن طرح ڪنجي جي اسٽرنگ ويل حاصل ڪري ٿي (يوزر ان پٽ ذريعي يا ماسٽر سرور ذريعي)، ۽ ان کي اندروني استعمال لاءِ IP پتي ۾ تبديل ڪري سگهي ٿي. باقي API به سادگي لاءِ اسٽرنگ بدران هي قدر وصول ڪري ٿي.

اهو ساڳيو آهي DNS ڳولڻ، پر مقامي طور تي ڪلائنٽ تي ڪيو ويو آهي.

اهو آهي، IP پتي مختلف گراهڪن جي وچ ۾ حصيداري نٿا ڪري سگهن، ۽ جيڪڏهن ڪنهن قسم جي عالمي سڃاڻپ ڪندڙ جي ضرورت آهي، اهو هڪ مختلف طريقي سان پيدا ڪرڻو پوندو.

سست ڪنيڪشن

UDP کي ڪنيڪشن جي ضرورت نه آھي، پر جيئن اسان ڏٺو آھي، WebRTC کي ھڪ ڊگھي ڪنيڪشن جي ضرورت آھي ان کان اڳ جو اھو ٻن ساٿين جي وچ ۾ ڊيٽا جي منتقلي شروع ڪري سگھي.

جيڪڏهن اسان تجريد جي ساڳي سطح مهيا ڪرڻ چاهيون ٿا، (sendto/recvfrom اڳواٽ ڪنيڪشن کان سواءِ پاڻمرادو ساٿين سان)، پوءِ انهن کي API جي اندر ”سست“ (تاخير) ڪنيڪشن انجام ڏيڻ لازمي آهي.

اھو اھو آھي جيڪو "سرور" ۽ "ڪلائنٽ" جي وچ ۾ عام رابطي دوران UDP استعمال ڪندي، ۽ اسان جي لائبريري کي ڇا ڪرڻ گھرجي:

  • سرور ڪالون bind()آپريٽنگ سسٽم کي ٻڌائڻ لاءِ ته اهو مخصوص پورٽ تي پيڪيٽ وصول ڪرڻ چاهي ٿو.

ان جي بدران، اسان هڪ کليل پورٽ شايع ڪنداسين فائر بيس ڏانهن سرور جي هيٺان ۽ ان جي ذيلي وڻ ۾ واقعن لاء ٻڌندا.

  • سرور ڪالون recvfrom()هن بندرگاهه تي ڪنهن به ميزبان کان ايندڙ پيڪيٽ قبول ڪرڻ.

اسان جي حالت ۾، اسان کي چيڪ ڪرڻ جي ضرورت آهي ايندڙ قطار جي پيڪرن جي موڪليل هن بندرگاهه ڏانهن.

هر بندرگاهه جي پنهنجي قطار آهي، ۽ اسان WebRTC ڊيٽاگرام جي شروعات ۾ ماخذ ۽ منزل جي بندرگاهن کي شامل ڪريون ٿا ته جيئن اسان کي خبر پوي ته نئين پيڪٽ جي اچڻ تي ڪهڙي قطار کي اڳتي وڌڻو آهي.

ڪال غير بلاڪنگ آهي، تنهنڪري جيڪڏهن ڪو به پيڪيٽ نه آهي، اسان صرف واپسي -1 ۽ سيٽ ڪريو errno=EWOULDBLOCK.

  • ڪلائنٽ سرور جي IP ۽ بندرگاهن کي ڪجهه خارجي ذريعن، ۽ ڪالن ذريعي حاصل ڪري ٿو sendto(). اهو پڻ اندروني ڪال ڪري ٿو. bind()تنهن ڪري بعد ۾ recvfrom() واضح طور تي پابند ڪرڻ کان سواءِ جواب وصول ڪندو.

اسان جي حالت ۾، ڪلائنٽ خارجي طور تي حاصل ڪري ٿو سٽرنگ ڪي ۽ فنڪشن استعمال ڪري ٿو resolve() هڪ IP پتو حاصل ڪرڻ لاء.

هن موقعي تي، اسان هڪ WebRTC هٿ ملائڻ شروع ڪريون ٿا جيڪڏهن ٻه ساٿي اڃا تائين هڪ ٻئي سان ڳنڍيل نه آهن. ساڳئي پير صاحب جي مختلف بندرگاهن سان ڪنيڪشن ساڳيا WebRTC DataChannel استعمال ڪن ٿا.

اسان پڻ اڻ سڌي طرح انجام ڏيون ٿا bind()ته جيئن سرور ٻيهر ڳنڍي سگھي sendto() جيڪڏهن اهو ڪنهن سبب لاء بند ٿي ويو.

سرور کي ڪلائنٽ جي ڪنيڪشن بابت اطلاع ڏنو ويندو آهي جڏهن ڪلائنٽ پنهنجي SDP آڇ کي لکي ٿو سرور پورٽ جي معلومات هيٺ Firebase ۾، ۽ سرور ان جي جواب سان جواب ڏئي ٿو.

هيٺ ڏنل ڊراگرام ڏيکاري ٿو پيغام جي وهڪري جو هڪ مثال ساکٽ اسڪيم لاءِ ۽ پهرين پيغام جي ڪلائنٽ کان سرور ڏانهن منتقلي:

Cheerp، WebRTC ۽ Firebase سان C++ کان ويب تي ملٽي پليئر گيم پورٽ ڪرڻ
ڪلائنٽ ۽ سرور جي وچ ۾ ڪنيڪشن جي مرحلي جو مڪمل خاڪو

ٿڪل

جيڪڏھن توھان ھي پري پڙھيو آھي، توھان کي شايد دلچسپي آھي نظريي کي عمل ۾ ڏسڻ ۾. راند کيڏي سگهجي ٿو تي teeworlds.leaningtech.com، ڪوشش ڪريو!


ساٿين جي وچ ۾ دوستانه ميچ

نيٽ ورڪ لائبريري ڪوڊ آزاد طور تي دستياب آهي GitHub. اسان جي چينل تي گفتگو ۾ شامل ٿيو ڀٽائي!

جو ذريعو: www.habr.com

تبصرو شامل ڪريو