defmodule Membrane.Core.Timer do @moduledoc false alias Membrane.Clock alias Membrane.Core.Message alias Membrane.Time require Membrane.Core.Message @type id :: any() @type interval :: Ratio.t() | Time.non_neg() | :no_interval @type t :: %__MODULE__{ id: id, interval: interval, init_time: Time.t(), clock: Clock.t(), next_tick_time: Time.t(), ratio: Clock.ratio(), timer_ref: reference() | nil, awaiting_message?: boolean() } @enforce_keys [:interval, :clock, :init_time, :id] defstruct @enforce_keys ++ [next_tick_time: 0, ratio: Ratio.new(1), timer_ref: nil, awaiting_message?: false] @spec start(id, interval, Clock.t()) :: t def start(id, interval, clock) do %__MODULE__{id: id, interval: interval, init_time: Time.monotonic_time(), clock: clock} |> tick end @spec stop(t) :: :ok def stop(%__MODULE__{interval: :no_interval}) do :ok end def stop(%__MODULE__{timer_ref: timer_ref}) do _time_left = Process.cancel_timer(timer_ref) :ok end @spec update_ratio(t, Clock.ratio()) :: t def update_ratio(%__MODULE__{} = timer, ratio) do %__MODULE__{timer | ratio: ratio} end @spec handle_message_arrived(t) :: t def handle_message_arrived(%__MODULE__{awaiting_message?: true} = timer) do %{timer | awaiting_message?: false} end @spec tick(t) :: t def tick(%__MODULE__{} = timer) when timer.awaiting_message? or timer.interval == :no_interval do timer end def tick(%__MODULE__{} = timer) do %__MODULE__{ id: id, interval: interval, init_time: init_time, next_tick_time: next_tick_time, ratio: ratio } = timer next_tick_time = Ratio.add(Ratio.new(next_tick_time), Ratio.new(interval)) # Next tick time converted to BEAM clock time beam_next_tick_time = Ratio.add(Ratio.new(init_time), Ratio.div(next_tick_time, ratio)) |> Ratio.floor() |> Time.as_milliseconds(:round) timer_ref = Process.send_after(self(), Message.new(:timer_tick, id), beam_next_tick_time, abs: true) %__MODULE__{ timer | next_tick_time: next_tick_time |> Ratio.floor(), timer_ref: timer_ref, awaiting_message?: true } end @spec set_interval(t, interval) :: t def set_interval(%__MODULE__{interval: :no_interval} = timer, interval) do %__MODULE__{timer | interval: interval} |> tick() end def set_interval(%__MODULE__{} = timer, interval) do %__MODULE__{timer | interval: interval} end end