EsMultiStreamChannel
Description
A class that multiplexes multiple virtual channels across a single underlying transport layer.
This should be connected to another <EsMultiStreamChannel> on the other end of the underlying channel. It starts with a single default virtual channel, accessible via #stream and #sink. Additional virtual channels can be created with #virtualChannel.
When a virtual channel is created by one endpoint, the other must connect to it before messages may be sent through it. The first endpoint passes its EsVirtualStreamChannel>>id to the second, which then creates a channel from that id also using #virtualChannel. For example:
// First endpoint
virtual := multiChannel virtualChannel.
multiChannel sink add: (LookupTable new at: 'channel' put: virtual id; yourself)
 
// Second endpoint
multiChannel stream listen: [:message | | virtual |
virtual := multiChannel virtualChannel: (message at: 'channel')
...
].
Sending errors across an <EsMultiStreamChannel> is not supported. Any errors from the underlying stream will be reported only via the default EsMultiStreamChannel>>stream.
Each virtual channel may be closed individually. When all of them are closed, the underlying <EsStreamSink> is closed automatically.
Instance State
inner: <EsStreamChannel> or nil. The inner channel over which all communication is conducted. This will be nil if the underlying communication channel is closed.
innerStreamSubscription: <EsStreamSubscription> or nil. The subscription to the inner stream.
mainController: <EsStreamChannelController> The controller for this channel.
controllers: <LookupTable> of <Integer>, <EsStreamChannelController> key values. A map from input IDs to <EsStreamChannelController>s that should be used to communicate over those channels.
pendingIds: <Set> of <Integer>s. Input IDs of controllers in controllers that we've received messages for but that have not yet had a local #virtualChannel created.
closeIds: <Set> of <Integer>s. Input IDs of virtual channels that used to exist but have since been closed.
nextId: The next id to use for a local virtual channel. Ids are used to identify virtual channels. Each message is tagged with an id; the receiving <EsMultiStreamChannel> uses this id to look up which
<EsVirtualStreamChannel> the message should be dispatched to. The id scheme for virtual channels is somewhat complicated. This is necessary to ensure that there are no conflicts even when both endpoints have virtual channels with the same id; since both endpoints can send and receive messages across each virtual channel, a naive scheme would make it impossible to tell whether a message was from a channel that originated in the remote endpoint or a reply on a channel that originated in the local endpoint. The trick is that each endpoint only uses odd ids for its own channels. When sending a message over a channel that was created by the remote endpoint, the channel's id plus one is used. This way each <EsMultiStreamChannel> knows that if an incoming message has an odd id, it's coming from a channel that was originally created remotely, but if it has an even id, it's coming from a channel that was originally created locally.
Class Methods
channel:
  Creates a new <EsMultiStreamChannel> that sends and receives messages over @innerChannel

     Arguments: 
        innerChannel - <EsStreamChannel>
     Answers:
        <EsMultiStreamChannel>
Instance Methods
sink
  The sink for sending values to the other endpoint.

     Answers:
        <EsStreamSink>
stream
  The single-subscription stream that emits values from the other endpoint.

     Answers:
        <EsStream>
virtualChannel
  Creates a new virtual channel.

     Creates a virtual channel from scratch. Before
     it's used, its EsVirtualStreamChannel>>id must be sent to the remote endpoint
     where #virtualChannel: should be called with that id.

     Answers:   
        <EsVirtualStreamChannel>
     Raises:
        <EsAsyncArgumentError> if a virtual channel already exists for @anId.
        <EsAsyncStateError> if the underlying channel is closed.
virtualChannel:
  Creates a new virtual channel.

     If @anId is nil, this creates a virtual channel from scratch. Before
     it's used, its EsVirtualStreamChannel>>id must be sent to the remote endpoint
     where #virtualChannel: should be called with that id.

     If @anId is passed, this creates a virtual channel corresponding to the
     channel with that id on the remote channel.

     Arguments:
        anId - <Integer>
     Answers:   
        <EsVirtualStreamChannel>
     Raises:
        <EsAsyncArgumentError> if a virtual channel already exists for @anId.
        <EsAsyncStateError> if the underlying channel is closed.
Last modified date: 04/21/2022