geckosio / geckos.io

🦎 Real-time client/server communication over UDP using WebRTC and Node.js http://geckos.io
BSD 3-Clause "New" or "Revised" License
1.34k stars 84 forks source link

Separate (underlying) datachannels for unreliable and reliable messages #269

Open yandeu opened 11 months ago

yandeu commented 11 months ago

Discussed in https://github.com/geckosio/geckos.io/discussions/263

Originally posted by **reececomo** July 25, 2023 I think Geckos.io would benefit from creating two separate datachannels per connection, one for unreliable and one for reliable. That would allow certain messages to leverage the existing (super fast) reliable mode at the network layer instead of reimplementing it at the application layer. ```ts // UDP-like channel for realtime data (e.g. binary, serialized game state packets). const unreliable = RTCPeerConnection.createDataChannel("unreliable", { ordered: false, maxRetransmits: 0 }); // TCP-like channel for reliable data (e.g. chat message, ad-hoc game state events). const reliable = RTCPeerConnection.createDataChannel("reliable", { ordered: true, // optional? maxRetransmits: 10 // Use constant }); ```
reececomo commented 11 months ago

This might be tough to do elegantly within Geckos.io's existing abstraction/conventions.

A: Keep current abstraction

const channel = geckos({ /* ... */ });
channel.sendReliable('msg', { ...data });

where

public async sendReliable(msg: string, data?: any): void
{
  // Wait for first-time setup if needed.
  this._reliableDataChannel = this._reliableDataChannel ?? await this.createReliableChannel();
  this._reliableDataChannel.emit(msg, data);
}

and integrate both channels with existing event handlers.

B: User Creates/Manages Non-Default DataChannels

You could either expose or hide the "threading"/channels.

Maybe something like:

const connection = geckos({
  // ...
});

connection.main.on('event1', () => {});
connection.dataChannel('2').on('event2', () => {});

or have data channel be a separate argument, like:

channel.on('event', (e) => this.handle(e), { dataChannel: '2' });
reececomo commented 7 months ago

Just an update @yandeu, our game has been running a hacked-together version of this for some time and it appears to work great.

Previously on the geckos default implementation of reliable (with default settings) we did see the occasional hitch. Mostly when sending or recv too many reliable packets at once. In fairness we probably could have done some version of Nagle's/batching and sent multiple json blobs in one packet (i.e. what Glenn Fielder suggests).

We were a little worried using a seperate reliable channel might block or interrupt the main (unreliable) channel, but it seems to work great.

No idea if there are any SCTP/DTLS congestion control or scaling issues to be aware of, but after a couple months can highly recommend making the switch.