git » sdk » main » tree

[main] / borogove / ChannelPinger.hx

package borogove;

import borogove.Chat;

using Lambda;

private typedef ScheduledChannel = {
	final channel: Channel;
	var deadline: Float;
}

class ChannelPinger {
	private static inline final PING_INTERVAL_MS = 5 * 60 * 1000;
	private static inline final CHECK_INTERVAL_MS = 60 * 1000;
	private static inline final PING_TRIGGER_WINDOW_MS = 30 * 1000;
	private static inline final COALESCE_WINDOW_MS = PING_TRIGGER_WINDOW_MS * 2;

	private final now: ()->Float;
	private final scheduledChannels = new Map<String, ScheduledChannel>();
	private var timer: Null<haxe.Timer> = null;

	public function new(?now: ()->Float) {
		this.now = now ?? (() -> haxe.Timer.stamp() * 1000);
	}

	public function startTimer(): Void {
		if (timer != null) throw "ChannelPinger timer already exists";
		timer = new haxe.Timer(CHECK_INTERVAL_MS);
		timer.run = pingPastDue;
	}

	public function stopTimer(): Void {
		timer?.stop();
		timer = null;
	}

	public function schedule(channel: Channel, ?deadline: Float): Void {
		if (channel.uiState != Open) return;

		scheduledChannels.set(channel.chatId, {
			channel: channel,
			deadline: deadline ?? now() + PING_INTERVAL_MS,
		});
	}

	public function remove(channel: Channel): Void {
		scheduledChannels.remove(channel.chatId);
	}

	public function pingPastDue(): Void {
		pingDueBy(now());
	}

	public function pingDueInWindow(): Void {
		final currentTime = now();
		if (getDueBy(currentTime + PING_TRIGGER_WINDOW_MS).length > 0) {
			pingDueBy(currentTime + COALESCE_WINDOW_MS);
		}
	}

	private function pingDueBy(cutoff: Float): Void {
		final due = getDueBy(cutoff);

		for (scheduled in due) scheduled.channel.selfPing(false);
	}

	private function getDueBy(cutoff: Float): Array<ScheduledChannel> {
		return scheduledChannels.filter(sc -> sc.deadline <= cutoff);
	}
}