/** @module Channel */
import { Timeout } from "./utils.js";
/**
* A class that contains the timeouts for a packet in a channel.
* @private
*/
class PacketTimeouts{
/** @member {Timeout} - The timeout for damaging a packet. */
damage;
/** @member {Timeout} - The timeout for losing a packet. */
lose;
/** @member {Timeout} - The timeout for deliverig a packet */
deliver;
/** Pauses all the timeouts. */
pause(){
this.damage?.pause();
this.lose?.pause();
this.deliver?.pause();
}
/** Resumes all the timeouts. */
resume(){
this.damage?.resume();
this.lose?.resume();
this.deliver?.resume();
}
/** Stops all the timeouts. */
stop(){
this.damage?.stop();
this.lose?.stop();
this.deliver?.stop();
}
}
/**
* A class that represents the channels through which the packets travel.
*/
class Channel{
/**
* @member {number} - The time in milliseconds that takes the channel to
* deliver the packet.
*/
delay;
/** @member {number} - The probability of losing a packet. */
lossProb;
/** @member {number} - The probability of damaging a packet. */
damageProb;
/**
* @member {Map} - A map with the current traveling packets as keys and
* PacketTimeouts as values.
* @private
*/
_travelingPackets = new Map();
/**
* Creates a new channel.
* @param {Object} [options] - The options for the constructor.
* @param {number} [options.delay=1000] - The time that takes the channel to
* deliver the packet.
* @param {number} [options.lossProb=0] - The probability between 0 and 1 to
* lose a packet.
* @param {number} [options.damageProb=0] - The probability between 0 and 1
* of damaging a packet.
*/
constructor({
delay = 1000,
lossProb = 0,
damageProb = 0
}={}){
this.delay = delay;
this.lossProb = lossProb;
this.damageProb = damageProb;
}
/**
* Returns an iterator for the current traveling packets.
* @returns {Iterator} - The returned iterator.
*/
getTravelingPackets(){
return this._travelingPackets.keys();
}
/**
* Returns a number between 1/4 and 3/4 of the delay. Used to randomly lose
* or damage a packet around the middle of the trajectory, so suddenly
* losing or damaging a packet just before delivery or after emission
* doesn't happen in the visualization.
* @returns {number} - The random time.
* @private
*/
_getRandomTime(){
return this.delay / 4 + Math.random() * this.delay / 2;
}
/**
* Sends a packet through the channel.
* @param {Packet} packet - The packet to send.
*/
send(packet){
const timeouts = new PacketTimeouts();
if(Math.random() < this.lossProb){
// The packet is going to be lost
timeouts.lose = new Timeout(this._getRandomTime(),
() => this.losePacket(packet));
}else{
if(Math.random() < this.damageProb){
// The packet is going to be damaged
timeouts.damage = new Timeout(this._getRandomTime(),
() => this.damagePacket(packet));
}
timeouts.deliver = new Timeout(this.delay, () => {
this._travelingPackets.delete(packet);
packet.receiver.receive(packet);
});
}
this._travelingPackets.set(packet, timeouts);
}
/**
* Stops sending the current traveling packets, preventing them from
* arriving at their receiver. It executes onPacketStopped() for each of the
* stopped packets and removes the packets from the channel.
*/
stop(){
this._travelingPackets.forEach((timeouts, packet) =>{
timeouts.stop();
this.onPacketStopped(packet);
});
this._travelingPackets.clear();
}
/**
* A callback that is called when a packet is stopped.
* @param {Packet} packet - The stopped packet.
*/
onPacketStopped(packet){
return;
}
/**
* Loses a packet, preventing it from arriving at its receiver, and
* callining onPacketLost() with the packet as parameter. If the packet is
* not currently traveling in this channel, then it does nothing. Returns
* true if the packet was traveling and was stopped, and false if the packet
* was not traveling.
* @param {Packet} packet - The packet to lose.
* @returns {boolean} - true if the packet was traveling, false if not.
*/
losePacket(packet){
const timeouts = this._travelingPackets.get(packet);
if(timeouts !== undefined){
this._travelingPackets.delete(packet);
timeouts.stop();
this.onPacketLost(packet);
return true;
}
return false;
}
/**
* A callback that is called every time a packet is lost.
* @param {Packet} packet - The lost packet.
*/
onPacketLost(packet){
return;
}
/**
* Damages a packet. Does nothing if the packet is not currently traveling
* in this channel. If the packet is traveling and is damaged, calls
* onPacketDamaged() and returns true, otherwise returns false.
* @param {Packet} packet - The packet to damage.
* @returns {boolean} - true if the packet was traveling, false if not.
*/
damagePacket(packet){
const timeouts = this._travelingPackets.get(packet);
if(timeouts !== undefined){
timeouts.damage?.stop();;
packet.damage();
this.onPacketDamaged(packet);
return true;
}
return false;
}
/**
* A callback that is called every time a packet is damaged.
* @param {Packet} packet - The damaged packet.
*/
onPacketDamaged(packet){
return;
}
/**
* Pauses the timeouts of the current traveling packets.
*/
pause(){
this._travelingPackets.forEach(timeouts => timeouts.pause());
}
/**
* Resumes the timeouts of the current traveling packets.
*/
resume(){
this._travelingPackets.forEach(timeouts => timeouts.resume());
}
}
export default Channel;