دستاویزات
🌐 زبان:
مقامی جامد HTML @p2play-js/p2p-game تھیم
⚠️ خبردار: یہ دستاویزات انگریزی سے خودکار طور پر ترجمہ شدہ ہیں۔ غلطیاں ہو سکتی ہیں۔ اصل انگریزی ورژن

تعارف

@p2play-js/p2p-game برائوزر پر مبنی P2P (WebRTC) ملٹی پلیئر گیمز بنانے کے لیے ایک ماڈولر TypeScript لائبریری ہے۔ یہ فراہم کرتی ہے حالت کی ہم آہنگی (مکمل/ڈیلٹا)، مطابقت کی حکمت عملیاں (ٹائم اسٹیمپ/اتھورٹیرین)، کم سے کم WebSocket سگنلنگ اڈاپٹر، موومنٹ ہیلپرز، ہوسٹ انتخاب/منتقلی، اور ایک پنگ اوورلے۔

فوری شروعات

npm install @p2play-js/p2p-game
import { P2PGameLibrary, WebSocketSignaling } from "@p2play-js/p2p-game";

const signaling = new WebSocketSignaling("کھلاڑیA", "کمرہ-42", "wss://آپ-کا-ws.مثال");
const multiP2PGame = new P2PGameLibrary({
  signaling,
  maxPlayers: 4,
  syncStrategy: "delta",
  conflictResolution: "timestamp",
});

await multiP2PGame.start();

multiP2PGame.on("playerMove", (id, pos) => {/* رینڈر */});

ڈیموز

فن تعمیر

  • لائبریری WebSocket سگنلنگ سرور استعمال کرتی ہے تاکہ کمرے سنبھالے، کھلاڑیوں کے شناختی نمبرز کی فہرست رکھے، اور مخصوص پیئرز تک SDP/ICE پیغامات پہنچائے۔
  • پیئرز فل‑میش بناتے ہیں: ہر جوڑی میں وہ پیئر جو playerId کے لغوی ترتیب میں چھوٹا ہو، WebRTC آفّر بناتا ہے — ڈبل آفّر ٹکراؤ سے بچاؤ۔
  • DataChannel قائم ہو جانے کے بعد گیم کے پیغامات پیئر‑ٹو‑پیئر چلتے ہیں؛ سگنلنگ سرور اب ایپ ٹریفک ریلے نہیں کرتا۔
  • ہوسٹ کا انتخاب متعین ہے: سب سے چھوٹا playerId ہوسٹ بنتا ہے۔ جب ہوسٹ نکلے تو اگلا چھوٹا منتخب ہوتا ہے اور نیا مکمل اسنیپ شاٹ بھیجتا ہے۔
سگنلنگ کیا ہے؟
براؤزر WebRTC کنکشن کھولنے سے پہلے میٹاڈیٹا (SDP offers/answers اور ICE candidates) کسی بیرونی چینل سے بدلے بغیر نہیں جڑ سکتے۔ سگنلنگ سرور یہی تبادلہ کرتا ہے اور کمرے کی فہرست رکھتا ہے؛ DataChannel کھلنے کے بعد گیم پلے ٹریفک آگے نہیں بھیجتا۔

سگنلنگ ترتیب

sequenceDiagram participant A as کلائنٹ A participant S as WS سگنلنگ participant B as کلائنٹ B A->>S: register {roomId, from, announce} B->>S: register {roomId, from, announce} S-->>A: sys: roster [A,B] S-->>B: sys: roster [A,B] note over A,B: سب سے چھوٹا playerId آفّر شروع کرتا ہے A->>S: kind: desc (offer), to: B S->>B: kind: desc (offer) from A B->>S: kind: desc (answer), to: A S->>A: kind: desc (answer) from B A->>S: kind: ice, to: B B->>S: kind: ice, to: A note over A,B: DataChannel کھلا → گیم پلے P2P ہو گیا

فل‑میش ٹوپولوجی

graph LR A[کھلاڑی A] --- B[کھلاڑی B] A --- C[کھلاڑی C] B --- C classDef host fill:#2b79c2,stroke:#2a3150,color:#fff; class A host;

حالت کی ہم آہنگی

  • مکمل اسنیپ شاٹس: شامل ہونے/منتقلی پر، اصلاحی ری‑سِنک۔
  • ڈیلٹا اپ ڈیٹس: راستہ‑بنیاد ہدفی تبدیلیاں (عملی طور پر عموماً ہائبرڈ طریقہ)۔
مکمل بمقابلہ ڈیلٹا
مکمل اسنیپ شاٹس سادہ اور مضبوط مگر بھاری؛ ڈیلٹا مختصر اور موثر مگر مستحکم اسٹیٹ اسکیما درکار۔ عملی طور پر زیادہ تر ڈیلٹا بھیجیں، اور پیئر کے شامل ہونے یا ہوسٹ منتقلی کے بعد مکمل اسنیپ شاٹ۔

مطابقت

  • ٹائم اسٹیمپ (طے شدہ): فی‑بھیجنے والے سلسلے پر Last‑Writer‑Wins (LWW)۔
  • اتھورٹیٹو: صرف اختیار (ہوسٹ یا مقررہ آئی ڈی) سے اعمال قبول کریں۔
گیمز میں مطابقت
ٹائم اسٹیمپ موڈ میں، ہر بھیجنے والے کی تازہ ترین کارروائی LWW قاعدے سے قبول؛ جس کا seq آخری دیکھے سے کم ہو، مسترد۔ اتھورٹیٹو موڈ میں ایک پیئر (اکثر قابل اعتماد ہوسٹ) تمام اعمال نافذ کرتا ہے؛ باقی ارادے بھیجتے اور اصلاح قبول کرتے ہیں۔

حرکت

ہدف یہ ہے کہ نیٹ ورک جِٹر میں بھی ہموار مگر پیش گوئی کے قابل حرکت ملے۔ اس کے لیے انٹرپولیشن اور حد بند ایکسٹراپولیشن (مختصر پیش گوئی کھڑکیاں) ملائی جاتی ہیں تاکہ تاخیر سے آنے والی اپ ڈیٹس چھپیں اور حقیقت سے دوری نہ بڑھے۔

انٹرپولیشن

نئی ریموٹ پوزیشن آتے ہی جھٹکا نہیں دیتے؛ ہر فریم باقی فاصلہ کا حصہ طے کرتے ہیں۔ smoothing اسی حصے (0..1) کو کنٹرول کرتا ہے — بڑا مان قدرے کم لَیگ مگر کبھی "تیرتا" احساس دے سکتا ہے۔

// Pseudocode
const clampedVx = clamp(velocity.x, -maxSpeed, maxSpeed);
const clampedVy = clamp(velocity.y, -maxSpeed, maxSpeed);
// allowedDtSec ایکسٹراپولیشن حد کو مدنظر رکھتا ہے
position.x += clampedVx * allowedDtSec * smoothing;
position.y += clampedVy * allowedDtSec * smoothing;
// اختیاری Z محور
اسموتھنگ کی ٹیوننگ
0.2–0.3 سے آغاز کریں۔ حرکت اگر ان پٹ سے پیچھے رہتی ہو تو بڑھائیں؛ جھولا/اوور شوٹ ہو تو گھٹا دیں۔

ایکسٹراپولیشن (حد کے ساتھ)

جس فریم میں نئی اپ ڈیٹ نہ ہو، عارضی طور پر آخری معلوم رفتار سے پروجیکٹ کریں؛ extrapolationMs (مثلاً 120–140ms) سے کھڑکی محدود رکھیں — بجٹ ختم ہو تو رک جائیں اور اگلی اتھورٹیٹو اپ ڈیٹ کا انتظار کریں۔

حد کیوں؟
بہت دیر تک پیش گوئی سے نمایاں غلطیاں جنم لیتی ہیں (دیوار پار، تصحیح پر ٹیلے پورٹ)۔ مختصر حد جِٹر چھپاتی اور منظر کو حقیقت کے قریب رکھتی ہے۔

2D بمقابلہ 3D

ڈیفالٹ 2D؛ سادہ 3D کے لیے z شامل کریں۔ اگر worldBounds.depth دیا ہو تو Z پر بھی کلیمپ لگتا ہے۔

ورلڈ باؤنڈز بمقابلہ اوپن ورلڈ

worldBounds کے ساتھ پوزیشنز [0..width] اور [0..height] (اور Z کے لیے [0..depth]) تک محدود؛ اوپن‑ورلڈ میں ignoreWorldBounds: true کریں (صرف کھلاڑی‑بنام‑کھلاڑی تصادم رہتے ہیں)۔

تصادم (دائرہ/کُرہ)

برابر رداس کے دائروں/کُروں کو ہم سیمٹری سے الگ کر کے حل کرتے ہیں۔ مراکز کے بیچ نورملائزڈ ویکٹر نکال کر دونوں کو اوورلیپ/2 سے دھکیلیں — سادہ اور مستحکم۔

// دو کھلاڑی A,B، رداس r
const dx = B.x - A.x, dy = B.y - A.y, dz = (B.z||0) - (A.z||0);
const dist = Math.max(1e-6, Math.hypot(dx, dy, dz));
const overlap = Math.max(0, 2*r - dist) / 2;
const nx = dx / dist, ny = dy / dist, nz = dz / dist;
A.x -= nx * overlap; A.y -= ny * overlap; A.z = (A.z||0) - nz * overlap;
B.x += nx * overlap; B.y += ny * overlap; B.z = (B.z||0) + nz * overlap;
پیچیدگی نوٹ
سادہ الگورتھم تمام جوڑوں کو چیک کرتا ہے (O(n²)) — چھوٹے کمروں میں ٹھیک؛ بھیڑ میں گرڈ/کواڈ ٹری جیسے ڈھانچے شامل کریں۔

فلو: موومنٹ اسٹیپ

flowchart TD A[آخری معلوم حالت] --> B{کیا نئی نیٹ اپ ڈیٹ؟} B -- ہاں --> C[ٹائم اسٹیمپس ری سیٹ؛ انٹرپولیشن] B -- نہیں --> D{کیا ایکسٹراپولیشن بجٹ باقی؟} D -- ہاں --> E[آخری رفتار * smoothing سے پروجیکٹ] D -- نہیں --> F[پوزیشن برقرار] C --> G{ورلڈ باؤنڈز؟} E --> G G -- کلیمپ --> H[کلیمپس لگائیں] G -- اوپن ورلڈ --> I[کلیمپس چھوڑیں] H --> J[تصادم حل] I --> J J --> K[رینڈر]

نیٹ ورکنگ کی تفصیل

  • بیک پریشر حکمت عملیاں: لبریز چینلز کے لیے مرج/ڈراپس۔
  • گنجائش: maxPlayers نفاذ + maxCapacityReached ایونٹ۔
  • STUN/TURN: سخت نیٹ ورکس کے لیے TURN؛ سگنلنگ کے لیے WSS۔
NAT اور TURN
بہت سے ادارہ/ہوٹل نیٹ ورکس براہ راست P2P روکتے ہیں۔ TURN سرور ٹریفک ریلے کرتا ہے تاکہ پیئرز جڑ سکیں، مگر تاخیر/بینڈوڈتھ کی قیمت پر۔ پروڈکشن میں اعتبار کے لیے TURN اسناد فراہم کریں۔

بیک پریشر

بیک پریشر DataChannel کو اوورلوڈ سے بچاتا ہے۔ جب RTCDataChannel.bufferedAmount حد سے اوپر جائے، تو عارضی طور پر بھیجنا روکیں، کم‑قدر پیغامات چھوڑیں، یا کئی اپ ڈیٹس کو تازہ ترین میں سمیٹیں۔

کیسے کام کرتا ہے
ہر send() bufferedAmount بڑھاتا ہے جب تک براؤزر نیٹ ورک پر فِلش نہ کر دے؛ اگر بھیجنا ترسیل سے تیز ہو تو تاخیر بڑھے گی اور ایپ اٹکے گی۔

حکمت عملیاں

  • off: کوئی حفاظتی اقدام نہیں — چھوٹے/کم میعادی پیغامات کے لیے۔
  • drop-moves: حد سے اوپر نئے move چھوڑ دیں۔
  • coalesce-moves: ہر پیئر کا صرف تازہ ترین move قطار میں رکھیں۔
const multiP2PGame = new P2PGameLibrary({
    signaling,
    backpressure: { strategy: 'coalesce-moves', thresholdBytes: 256 * 1024 }
  });
  
سفارش کردہ حدیں
256–512 KB سے آغاز کریں؛ اگر بار بار حد لگے تو فریکوئنسی/سائز گھٹا دیں (ڈیلٹا، binary‑min، ویکٹر کوانٹائز)۔

واقعات اور API (منتخب)

  • on('playerMove')، on('inventoryUpdate')، on('objectTransfer')
  • on('stateSync')، on('stateDelta')، on('hostChange')، on('ping')
  • broadcastMove()، updateInventory()، transferItem()
  • broadcastPayload()، sendPayload()
  • setStateAndBroadcast()، announcePresence()، getHostId()

واقعات کا خلاصہ

ایونٹ سگنیچر تفصیل
playerMove(playerId, position)حرکت لاگو
inventoryUpdate(playerId, items)انوینٹری تازہ
objectTransfer(from, to, item)شے منتقل
sharedPayload(from, payload, channel?)جنرل پے لوڈ موصول
stateSync(state)مکمل اسنیپ شاٹ موصول
stateDelta(delta)اسٹیٹ ڈیلٹا موصول
peerJoin(playerId)پیئر جڑا
peerLeave(playerId)پیئر نکلا
hostChange(hostId)نیا ہوسٹ
ping(playerId, ms)RTT
maxCapacityReached(maxPlayers)گنجائش مکمل؛ نئے کنکشن مسترد

لائف سائیکل اور موجودگی

  • موجودگی: announcePresence(playerId) جلدی کال کریں تاکہ ساتھی فوراً کھلاڑی دکھا سکیں۔
  • peerJoin/peerLeave: UI ہستیوں کو دکھا/چھپا سکتا ہے؛ ہوسٹ‑سائیڈ صفائی کے لیے cleanupOnPeerLeave: true فعال کریں — ہوسٹ ریکارڈ ہٹائے اور ڈیلٹا نشر کرے۔
  • گنجائش حد: maxPlayers طے کریں؛ حد آنے پر نئی ابتدا نہ ہوگی اور زائد offers نظرانداز؛ maxCapacityReached ایونٹ UI کو آگاہ کرے۔

اقسام حوالہ

type SerializationStrategy = "json" | "binary-min";
type SyncStrategy = "full" | "delta"; // مشورہ: 'hybrid' موڈ سوئچ نہیں
type ConflictResolution = "timestamp" | "authoritative";

interface BackpressureOptions {
  strategy?: "off" | "drop-moves" | "coalesce-moves";
  thresholdBytes?: number; // ~256KB
}

interface DebugOptions {
  enabled?: boolean;
  onSend?: (info: {
    type: "broadcast" | "send";
    to: string | "all";
    payloadBytes: number;
    delivered: number;
    queued: number;
    serialization: SerializationStrategy;
    timestamp: number;
  }) => void;
}

interface MovementOptions {
  maxSpeed?: number;
  smoothing?: number; // 0..1
  extrapolationMs?: number;
  worldBounds?: { width: number; height: number; depth?: number };
  ignoreWorldBounds?: boolean;
  playerRadius?: number;
}

interface GameLibOptions {
  maxPlayers?: number;
  syncStrategy?: SyncStrategy; // آپ طے کریں کب full بمقابلہ delta
  conflictResolution?: ConflictResolution;
  authoritativeClientId?: string;
  serialization?: SerializationStrategy;
  iceServers?: RTCIceServer[];
  cleanupOnPeerLeave?: boolean;
  debug?: DebugOptions;
  backpressure?: BackpressureOptions;
  pingOverlay?: { enabled?: boolean; position?: "top-left"|"top-right"|"bottom-left"|"bottom-right"; canvas?: HTMLCanvasElement | null };
  movement?: MovementOptions;
}

type EventMap = {
  playerMove: (playerId: string, position: { x:number; y:number; z?:number }) => void;
  inventoryUpdate: (playerId: string, items: Array<{ id:string; type:string; quantity:number }>) => void;
  objectTransfer: (fromId: string, toId: string, item: { id:string; type:string; quantity:number }) => void;
  stateSync: (state: GlobalGameState) => void;
  stateDelta: (delta: StateDelta) => void;
  peerJoin: (playerId: string) => void;
  peerLeave: (playerId: string) => void;
  hostChange: (hostId: string) => void;
  ping: (playerId: string, ms: number) => void;
  sharedPayload: (from: string, payload: unknown, channel?: string) => void;
  maxCapacityReached: (maxPlayers: number) => void;
};

interface GlobalGameState {
  players: Record;
  inventories: Record>;
  objects: Record }>;
  tick: number;
}

interface StateDelta { tick:number; changes: Array<{ path:string; value:unknown }> }

ڈیلٹا راستوں کے قواعد

  • راستے dot‑separated آبجیکٹ کیز ہیں (ارے انڈیکس معاون نہیں)۔
  • ہدفی اپ ڈیٹس کے لیے ساخت کو کم گہرا اور keyed رکھیں (جیسے objects.chest.42)؛ گہرے ارریز سے گریز کریں۔
// اچھا: آبجیکٹ میپ
{ path: 'objects.chest.42', value: { id:'chest.42', kind:'chest', data:{ opened:true } } }

// معاون نہیں: 'objects[3]' یا 'players.list.0' جیسے ارے انڈیکس راستے

P2PGameLibrary

کنسٹرکٹر

new P2PGameLibrary(options: GameLibOptions & { signaling: WebSocketSignaling | SignalingAdapter })

لائف سائیکل

await start(): Promise
on(name: N, handler: EventMap[N]): () => void
getState(): GlobalGameState
getHostId(): string | undefined
setPingOverlayEnabled(enabled: boolean): void
tick(now?: number): void // فی فریم انٹرپولیشن/تصادم

اسٹیٹ یوٹیلٹیز

setStateAndBroadcast(selfId: string, changes: Array<{ path:string; value:unknown }>): string[]
broadcastFullState(selfId: string): void
broadcastDelta(selfId: string, paths: string[]): void

گیم پلے APIs

announcePresence(selfId: string, position = { x:0, y:0 }): void
broadcastMove(selfId: string, position: {x:number;y:number;z?:number}, velocity?: {x:number;y:number;z?:number}): void
updateInventory(selfId: string, items: Array<{ id:string; type:string; quantity:number }>): void
transferItem(selfId: string, to: string, item: { id:string; type:string; quantity:number }): void

پے لوڈ APIs

broadcastPayload(selfId: string, payload: unknown, channel?: string): void
sendPayload(selfId: string, to: string, payload: unknown, channel?: string): void

پیغامات (ٹرانسپورٹ)

// NetMessage union (منتخب)
type NetMessage =
  | { t:"move"; from:string; ts:number; seq?:number; position:{x:number;y:number;z?:number}; velocity?:{x:number;y:number;z?:number} }
  | { t:"inventory"; from:string; ts:number; seq?:number; items:Array<{id:string;type:string;quantity:number}> }
  | { t:"transfer"; from:string; ts:number; seq?:number; to:string; item:{id:string;type:string;quantity:number} }
  | { t:"state_full"; from:string; ts:number; seq?:number; state: GlobalGameState }
  | { t:"state_delta"; from:string; ts:number; seq?:number; delta: StateDelta }
  | { t:"payload"; from:string; ts:number; seq?:number; payload: unknown; channel?: string };

// Serialization
// strategy: "json" (string frames) یا "binary-min" (ArrayBuffer UTF-8 JSON)

سگنلنگ اڈاپٹر

لائبریری اس تجرید کو استعمال کرتی ہے تاکہ کسی بھی بیک اینڈ (WebSocket، REST وغیرہ) کے ذریعے SDP/ICE بدلے جائیں۔

interface SignalingAdapter {
  localId: string;
  roomId?: string;
  register(): Promise; // کمرے میں شامل ہوں اور روستر پائیں
  announce(desc: RTCSessionDescriptionInit, to?: string): Promise;
  onRemoteDescription(cb: (desc: RTCSessionDescriptionInit, from: string) => void): void;
  onIceCandidate(cb: (candidate: RTCIceCandidateInit, from: string) => void): void;
  onRoster(cb: (roster: string[]) => void): void;
  sendIceCandidate(candidate: RTCIceCandidateInit, to?: string): Promise;
}

مثال: کم از کم کسٹم اڈاپٹر (WebSocket)

سادہ WebSocket سگنلنگ سرور کے ساتھ انٹرفیس کی مختصر عمل کاری۔

class SimpleWsSignaling implements SignalingAdapter {
  constructor(public localId: string, public roomId: string, private url: string) {
    this.ws = new WebSocket(this.url);
  }
  private ws: WebSocket;
  private rosterCb?: (list: string[]) => void;
  private descCb?: (d: RTCSessionDescriptionInit, from: string) => void;
  private iceCb?: (c: RTCIceCandidateInit, from: string) => void;

  async register(): Promise {
    await new Promise((resolve) => {
      this.ws.addEventListener('open', () => {
        this.ws.send(JSON.stringify({ kind:'register', roomId: this.roomId, from: this.localId, announce:true }));
        resolve();
      });
    });
    this.ws.addEventListener('message', (ev) => {
      const msg = JSON.parse(ev.data);
      if (msg.sys === 'roster' && this.rosterCb) this.rosterCb(msg.roster);
      if (msg.kind === 'desc' && this.descCb) this.descCb(msg.payload, msg.from);
      if (msg.kind === 'ice' && this.iceCb) this.iceCb(msg.payload, msg.from);
    });
  }

  onRoster(cb: (roster: string[]) => void){ this.rosterCb = cb; }
  onRemoteDescription(cb: (desc: RTCSessionDescriptionInit, from: string) => void){ this.descCb = cb; }
  onIceCandidate(cb: (candidate: RTCIceCandidateInit, from: string) => void){ this.iceCb = cb; }

  async announce(desc: RTCSessionDescriptionInit, to?: string): Promise {
    this.ws.send(JSON.stringify({ kind:'desc', roomId:this.roomId, from:this.localId, to, payload: desc }));
  }
  async sendIceCandidate(candidate: RTCIceCandidateInit, to?: string): Promise {
    this.ws.send(JSON.stringify({ kind:'ice', roomId:this.roomId, from:this.localId, to, payload: candidate }));
  }
}

// لائبریری کے ساتھ استعمال
const signaling = new SimpleWsSignaling('alice','room-1','wss://your-signal.example');
await signaling.register();
const multiP2PGame = new P2PGameLibrary({ signaling });
await multiP2PGame.start();

مثال: REST + لانگ‑پولنگ اڈاپٹر

جہاں WebSocket دستیاب نہ ہو، HTTP اینڈ پوائنٹس اور پولنگ لوپ سے پیغامات لیں۔

class RestPollingSignaling implements SignalingAdapter {
  constructor(public localId: string, public roomId: string, private baseUrl: string) {}
  private rosterCb?: (list: string[]) => void;
  private descCb?: (d: RTCSessionDescriptionInit, from: string) => void;
  private iceCb?: (c: RTCIceCandidateInit, from: string) => void;
  private polling = false;

  async register(): Promise {
    await fetch(`${this.baseUrl}/register`, { method:'POST', headers:{ 'content-type':'application/json' }, body: JSON.stringify({ roomId:this.roomId, from:this.localId, announce:true }) });
    this.polling = true;
    void this.poll();
  }
  private async poll(): Promise {
    while (this.polling) {
      try {
        const res = await fetch(`${this.baseUrl}/poll?roomId=${encodeURIComponent(this.roomId)}&from=${encodeURIComponent(this.localId)}`);
        if (!res.ok) { await new Promise(r=>setTimeout(r,1000)); continue; }
        const msgs = await res.json();
        for (const msg of msgs) {
          if (msg.sys === 'roster' && this.rosterCb) this.rosterCb(msg.roster);
          if (msg.kind === 'desc' && this.descCb) this.descCb(msg.payload, msg.from);
          if (msg.kind === 'ice' && this.iceCb) this.iceCb(msg.payload, msg.from);
        }
      } catch {
        await new Promise(r=>setTimeout(r,1000));
      }
    }
  }
  onRoster(cb: (roster: string[]) => void){ this.rosterCb = cb; }
  onRemoteDescription(cb: (desc: RTCSessionDescriptionInit, from: string) => void){ this.descCb = cb; }
  onIceCandidate(cb: (candidate: RTCIceCandidateInit, from: string) => void){ this.iceCb = cb; }
  async announce(desc: RTCSessionDescriptionInit, to?: string): Promise {
    await fetch(`${this.baseUrl}/send`, { method:'POST', headers:{ 'content-type':'application/json' }, body: JSON.stringify({ kind:'desc', roomId:this.roomId, from:this.localId, to, payload: desc }) });
  }
  async sendIceCandidate(candidate: RTCIceCandidateInit, to?: string): Promise {
    await fetch(`${this.baseUrl}/send`, { method:'POST', headers:{ 'content-type':'application/json' }, body: JSON.stringify({ kind:'ice', roomId:this.roomId, from:this.localId, to, payload: candidate }) });
  }
}

// استعمال
const restSignaling = new RestPollingSignaling('alice','room-1','https://your-signal.example');
await restSignaling.register();
const multiP2PGame2 = new P2PGameLibrary({ signaling: restSignaling });
await multiP2PGame2.start();

WebSocketSignaling

مثالوں میں استعمال ہونے والی ریفرنس عمل کاری؛ پروٹوکول: { sys:'roster', roster:string[] } براڈکاسٹ؛ ہدفی پیغامات to کے ذریعے۔

new WebSocketSignaling(localId: string, roomId: string, serverUrl: string)
await signaling.register();
signaling.onRoster((list) => {/* UI اپ ڈیٹ */});
signaling.onRemoteDescription((desc, from) => {/* PeerManager کو دیں */});
signaling.onIceCandidate((cand, from) => {/* PeerManager کو دیں */});

پیغام کی صورتیں

// کلائنٹ → سرور (register)
{ roomId: string, from: string, announce: true, kind: 'register' }

// سرور → کلائنٹس (roster broadcast)
{ sys: 'roster', roomId: string, roster: string[] }

// کلائنٹ → سرور (SDP/ICE، ہدفی یا کمرے میں براڈکاسٹ)
{ kind: 'desc'|'ice', roomId: string, from: string, to?: string, payload: any, announce?: true }

دیکھیے examples/server/ws-server.mjs۔

PeerManager (اندرونی)

  • ہر پیئر کے لیے ایک RTCPeerConnection اور ایک RTCDataChannel رکھتا ہے، درکار کال بیکس وائر کرتا ہے۔
  • ہر جوڑی میں چھوٹا playerId آفّر بنا کر آغاز کرتا ہے؛ دوسرا جواب دیتا ہے — بیک وقت آفّر سے بچاؤ۔
  • peerJoin، peerLeave، hostChange، ping ایونٹس نکالتا ہے؛ ڈیکوڈڈ نیٹ پیغامات کو netMessage کے طور پر آگے بھیجتا ہے۔
  • بیک پریشر:
    • off: جب چینل کھلا ہو ہمیشہ بھیجیں۔
    • drop-moves: bufferedAmount حد سے اوپر ہو تو نئے move چھوڑیں۔
    • coalesce-moves: قطار میں پرانا move تازہ ترین سے بدلیں۔
  • گنجائش: maxPlayers نافذ؛ زائد آغاز نظرانداز؛ maxCapacityReached(maxPlayers) ایونٹ۔

EventBus (اندرونی)

class EventBus {
  on(name: N, fn: EventMap[N]): () => void
  off(name: N, fn: EventMap[N]): void
  emit(name: N, ...args: Parameters): void
}

عموماً آپ P2PGameLibrary.on() کے ذریعے سبسکرائب کرتے ہیں جو اندرونی بس کو ڈیلیگیٹ کرتا ہے۔

PingOverlay

یہ اوورلے صفحے پر ایک چھوٹا ڈیش بورڈ بناتا ہے جو ہر منسلک پیئر تک راؤنڈ‑ٹرپ ٹائم (RTT) ٹریک کرتا ہے اور مختصر تاریخ (~60 نمونے) رکھتا ہے۔ ڈیولپمنٹ میں اسپائکس دیکھنے، TURN کے استعمال کی تصدیق اور پیئرز کا موازنہ کرنے میں مفید۔

اختیارات

{
  enabled?: boolean; // ڈیفالٹ false
  position?: 'top-left'|'top-right'|'bottom-left'|'bottom-right'; // ڈیفالٹ 'top-right'
  canvas?: HTMLCanvasElement | null; // اپنا canvas دیں یا اوورلے بنائے
}

استعمال

const multiP2PGame = new P2PGameLibrary({ signaling, pingOverlay: { enabled: true, position: 'top-right' } });
// رن ٹائم پر ٹوگل
multiP2PGame.setPingOverlayEnabled(false);
چارٹ پڑھنا
ہر رنگین لکیر ایک پیئر ہے۔ کم اور ہموار اقدار بہتر؛ آری دانت یا اچانک جستیں بھیڑ یا ریلے (TURN) کا اشارہ۔ اگر کوئی پیئر مسلسل زیادہ ہو تو کردار از سر نو متوازن کریں (مثلاً اسے ہوسٹ نہ بنائیں)۔

تسلسل

  • حکمت عملیاں: json (string frames) یا binary-min (ArrayBuffer UTF‑8 JSON)۔
  • نامعلوم حکمت عملی پر خطا پھینکی جاتی ہے۔
interface Serializer {
  encode(msg: NetMessage): string | ArrayBuffer;
  decode(data: string | ArrayBuffer): NetMessage;
}

function createSerializer(strategy: 'json'|'binary-min' = 'json'): Serializer

مثالیں

اتھورٹیٹو ہوسٹ ارادے نافذ کرتا ہے

const isHost = () => multiP2PGame.getHostId() === localId;
multiP2PGame.on("sharedPayload", (from, payload, channel) => {
  if (!isHost()) return;
  if (channel === "move-intent" && typeof payload === "object") {
    const p = payload as { pos:{x:number;y:number}; vel?:{x:number;y:number} };
    multiP2PGame.broadcastMove(multiP2PGame.getHostId()!, p.pos, p.vel);
  }
});

عارضی پے لوڈ کو مشترکہ اسٹیٹ میں محفوظ کرنا

multiP2PGame.on("sharedPayload", (from, payload, channel) => {
  if (channel !== "status") return;
  if (payload && typeof payload === "object" && "hp" in (payload as any)) {
    multiP2PGame.setStateAndBroadcast(multiP2PGame.getHostId()!, [
      { path: `objects.playerStatus.${from}`, value: { id:`playerStatus.${from}`, kind:"playerStatus", data:{ hp:(payload as any).hp } } }
    ]);
  }
});

انتخابی ڈیلٹا اپ ڈیٹس

const paths = multiP2PGame.setStateAndBroadcast(localId, [
  { path:"objects.chest.42", value:{ id:"chest.42", kind:"chest", data:{ opened:true } } }
]);
// paths == ["objects.chest.42"]

واقعات کا حوالہ

playerMove

game.on('playerMove', (playerId, position) => {
    drawAvatar(playerId, position);
  });
  

inventoryUpdate

game.on('inventoryUpdate', (playerId, items) => {
    ui.updateInventory(playerId, items);
  });
  

objectTransfer

game.on('objectTransfer', (from, to, item) => {
    ui.toast(`${from} نے ${item.id} دیا ${to} کو`);
  });
  

sharedPayload

game.on('sharedPayload', (from, payload, channel) => {
    if (channel === 'chat') chat.add(from, (payload as any).text);
  });
  

stateSync

game.on('stateSync', (state) => {
    world.hydrate(state);
  });
  

stateDelta

game.on('stateDelta', (delta) => {
    world.applyDelta(delta);
  });
  

peerJoin / peerLeave

game.on('peerJoin', (id) => ui.addPeer(id));
  game.on('peerLeave', (id) => ui.removePeer(id));
  

hostChange

game.on('hostChange', (hostId) => ui.setHost(hostId));
  

ping

game.on('ping', (id, ms) => ui.setPing(id, ms));
  

maxCapacityReached

game.on('maxCapacityReached', (max) => ui.alert(`کمرہ بھر گیا (${max})`));
  

پروڈکشن نوٹس

  • ICE (TURN) اور محفوظ سگنلنگ (WSS) مہیا کریں۔
  • انصاف کے لیے قابل اعتماد/ہیڈ لیس ہوسٹ کے ساتھ authoritative موڈ پر غور کریں۔
  • RTCDataChannel.bufferedAmount دیکھیے اور بیک پریشر ٹیون کریں۔

ریکनेकٹ اور UX چیک لسٹ

  • پیئرز کے ڈِس کنیکٹ پر "reconnecting" UI دکھائیں؛ واپسی جانچنے کو روستر پر انحصار کریں۔
  • مائیگریشن کے بعد ہوسٹ نیا state_full بھیج کر کلائنٹس سیدھ میں لاتا ہے۔
  • اختیاری طور پر cleanupOnPeerLeave فعال کریں تاکہ (صرف ہوسٹ) نکلنے پر اسٹیٹ چھانٹے۔

ڈی بگنگ

const game = new P2PGameLibrary({
  signaling,
  debug: {
    enabled: true,
    onSend(info){
      console.log('[send]', info.type, 'to', info.to, 'bytes=', info.payloadBytes, 'queued=', info.queued);
    }
  }
});

براؤزر مطابقت

  • حالیہ Chrome/Firefox/Edge/Safari DataChannels سپورٹ کرتے ہیں؛ Safari کو پروڈکشن میں HTTPS/WSS درکار۔
  • ادارہ/ہوٹل نیٹ ورکس کے لیے TURN تعینات کریں؛ ریلے پر تاخیر زیادہ۔

مسائل کا حل

WebRTC کنکشن قائم نہیں ہوتا

  • مخلوط مواد: یقینی بنائیں صفحہ اور سگنلنگ دونوں HTTPS/WSS ہیں (براؤزر HTTPS صفحے سے WS روکتے ہیں)۔
  • TURN کی کمی: ادارہ/ہوٹل نیٹ ورکس میں براہ راست P2P مسدود؛ iceServers میں TURN اسناد دیں۔
  • CORS/فائر وال: سگنلنگ اینڈ پوائنٹ کو اصل (origin) قبول کرنا چاہیے؛ ریورس پراکسی قواعد اور کھلے پورٹس (TLS 443) دیکھیں۔

DataChannel رُک رُک کر (زیادہ تاخیر، ان پٹ لیٹ)

  • بیک پریشر: coalesce-moves یا drop-moves فعال کریں اور thresholdBytes سیٹ کریں (256–512 KB سے شروع)۔
  • پیغام سائز گھٹائیں: ڈیلٹا اپنائیں؛ binary‑min؛ ویکٹر کوانٹائز۔
  • بھیجنے کی شرح گھٹائیں: موومنٹ براڈکاسٹ (مثلاً 30–60 Hz) محدود کریں اور انٹرپولیشن پر بھروسہ کریں۔

ہوسٹ بدلنے کے بعد عدم مطابقت

  • یقینی بنائیں نیا ہوسٹ state_full نشر کرے (لائبریری خود ٹرگر کرتی ہے)۔
  • کلائنٹس مکمل اسنیپ شاٹ لاگو کریں اور مقامی کیش صاف کریں (کچھ فریم انٹرپولیشن کو سنبھلنے دیں)۔

Safari مخصوص مسائل

  • localhost کے سوا HTTPS/WSS درکار۔
  • دیکھیے STUN/TURN URLs میں ٹرانسپورٹ پیرا میٹرز ہیں (مثلاً ?transport=udp) اگر ریلے کو درکار ہو۔

گیم ورک فلوز

اینڈ‑ٹو‑اینڈ پیٹرنز جو مختلف اصناف کے لیے نیٹ ورکنگ، مطابقت اور اسٹیٹ جوڑتے ہیں۔

1) ریئل‑ٹائم ایرینا (ایکشن/شوٹر)

  • مطابقت: timestamp سے شروع؛ قابل اعتماد ہوسٹ ہو تو authoritative۔
  • سِنک: معمول میں ڈیلٹا؛ ہوسٹ منتقلی پر کبھی کبھار مکمل اسنیپ شاٹ۔
  • بیک پریشر: coalesce-moves — صرف تازہ ترین حرکت رکھیں۔
// سیٹ اپ
const multiP2PGame = new P2PGameLibrary({ signaling, conflictResolution: 'timestamp', backpressure: { strategy: 'coalesce-moves' }, movement: { smoothing: 0.2, extrapolationMs: 120 } });
await multiP2PGame.start();

// لوکل ان پٹ → حرکت نشر (کلائنٹ پیش گوئی آپ کے رینڈرر میں)
function onInput(vec){
  const pos = getPredictedPosition(vec);
  multiP2PGame.broadcastMove(localId, pos, vec);
}

multiP2PGame.on('playerMove', (id, pos) => renderPlayer(id, pos));

2) کو آپ RPG (انوینٹری، ہوسٹ ارادے نافذ کرتا ہے)

  • مطابقت: authoritative (آئٹم استعمال، دروازے، چیسٹ کی توثیق)۔
  • پروٹوکول: کلائنٹس ارادے (پے لوڈ) بھیجیں؛ ہوسٹ مشترکہ اسٹیٹ بدلے اور ڈیلٹا نشر کرے۔
// کلائنٹ صرف ہوسٹ کو ارادہ بھیجتا ہے
function usePotion(){
  multiP2PGame.sendPayload(localId, multiP2PGame.getHostId()!, { action: 'use-item', itemId: 'potion' }, 'intent');
}

// ہوسٹ ارادہ سنبھالتا اور اسٹیٹ بدلتا
const isHost = () => multiP2PGame.getHostId() === localId;
multiP2PGame.on('sharedPayload', (from, payload, channel) => {
  if (!isHost() || channel !== 'intent') return;
  if ((payload as any).action === 'use-item') {
    const inv = getInventoryAfterUse(from, (payload as any).itemId);
    multiP2PGame.setStateAndBroadcast(localId, [ { path: `inventories.${from}`, value: inv } ]);
  }
});

3) ٹرن‑بیسڈ ٹیکٹکس (متعین، مکمل اسنیپ شاٹس)

  • مطابقت: اتھورٹیٹو ہوسٹ قواعد اور ٹرن آرڈر نافذ کرتا ہے۔
  • سِنک: فی حرکت چھوٹا ڈیلٹا؛ ہر N ٹرن پر ایک مکمل اسنیپ شاٹ بطور حفاظت۔
interface TurnMove { unitId:string; to:{x:number;y:number} }

multiP2PGame.on('sharedPayload', (from, payload, channel) => {
  if (channel !== 'turn-move' || multiP2PGame.getHostId() !== localId) return;
  const mv = payload as TurnMove;
  const ok = validateMove(currentState, from, mv);
  if (!ok) return; // غیر قانونی حرکت مسترد
  applyMove(currentState, mv);
  multiP2PGame.setStateAndBroadcast(localId, [ { path: `players.${from}.lastMove`, value: mv } ]);
});

// ہر 10 ٹرن پر مکمل اسنیپ شاٹ
if (currentState.tick % 10 === 0) multiP2PGame.broadcastFullState(localId);

4) پارٹی گیم + لا بی (گنجائش اور منتقلی)

  • maxPlayers طے کریں؛ maxCapacityReached پر صارف کو آگاہ کریں۔
  • روستر سے لا بی دکھائیں؛ نکلنے پر ہوسٹ خودکار منتقل۔
const multiP2PGame = new P2PGameLibrary({ signaling, maxPlayers: 8 });

multiP2PGame.on('maxCapacityReached', (max) => showToast(`Room is full (${max})`));

multiP2PGame.on('hostChange', (host) => updateLobbyHost(host));

5) اوپن‑ورلڈ سینڈ باکس (بلا حدود، Z محور)

  • ورلڈ باؤنڈز غیر فعال؛ صرف کھلاڑی‑کھلاڑی تصادم۔
  • کثرت اپ ڈیٹس ہوں تو binary-min سے پے لوڈ کم کریں۔
const multiP2PGame = new P2PGameLibrary({
  signaling,
  serialization: 'binary-min',
  movement: { ignoreWorldBounds: true, playerRadius: 20, smoothing: 0.25, extrapolationMs: 140 }
});

لغت

  • SDP: Session Description Protocol; WebRTC کے میڈیا/ڈیٹا سیشن کے پیرامیٹرز۔
  • ICE: Interactive Connectivity Establishment; پیئرز کے بیچ نیٹ ورک راستے (STUN/TURN کے ذریعے)۔
  • STUN: سرور جو کلائنٹ کو اس کا عوامی پتہ معلوم کرنے میں مدد دے؛ NAT ٹریورسل۔
  • TURN: ریلے سرور جو براہ راست P2P نہ ہو سکے تو ٹریفک آگے بڑھائے۔
  • DataChannel: WebRTC دو طرفہ ڈیٹا ٹرانسپورٹ برائے گیم پیغامات۔
  • LWW: Last‑Writer‑Wins; فی‑بھیجنے والا سلسلہ جس میں تازہ ترین اپ ڈیٹ غالب۔