Current section

Files

Jump to
rivet_utils lib interval tick.ex
Raw

lib/interval/tick.ex

defmodule Rivet.Utils.Interval.Tick do
@moduledoc """
Similar to Interval, but tick-driven, where it has a tick frequency which is
more frequent than the interval frequency -- allowing for interruptions to
the interval and the subsequent intervals to start from there forward.
Seconds used as precision, not milliseconds
Contributor: Brandon Gillespie
"""
import Rivet.Utils.Time, only: [epoch_time: 1]
require Logger
@margin 2
defmacro __using__(_) do
quote location: :keep do
def handle_info({:interval_tick, method}, state) do
case get_in(state, [:intervals, method]) do
{_, {inter, freq, next}} ->
now = epoch_time(:second)
# IO.puts("#{now} <> #{inter}/#{freq}/#{next}")
{next, flag} =
if now >= next do
{now + inter, :tock}
else
{next, :tick}
end
# queue the next tick
state = Rivet.Utils.Interval.Tick.queue(state, method, inter, freq, next)
# and see if we should do something on this one
# IO.puts("#{epoch_time(:second)} #{flag} #{method}")
with {:ok, state} <- apply(__MODULE__, method, [state, flag]) do
{:noreply, state}
else
error ->
IO.inspect(error, label: "Unexpected result from Interval call")
{:noreply, state}
end
nil ->
{:noreply, state}
end
rescue
err ->
Logger.error(Exception.format(:error, err, __STACKTRACE__))
{:noreply, state}
end
end
end
def start(state, method, interval, freq) do
last =
case get_in(state, [:intervals, method]) do
nil -> 0
{last, _} -> last
end
now = epoch_time(:second)
if now - last < interval * @margin do
# we've seen it within 2x the interval, so it's likely already running,
# don't start another...
state
else
queue(state, method, interval, freq, now + freq)
end
rescue
err ->
Logger.error(Exception.format(:error, err, __STACKTRACE__))
state
end
def stop(state, method) do
intervals =
Map.get(state, :intervals, %{})
|> Map.delete(method)
Map.put(state, :intervals, intervals)
end
def queue(state, method, interval, frequency, next) do
# IO.puts("#{epoch_time(:second)} queue")
now = epoch_time(:second)
Process.send_after(self(), {:interval_tick, method}, frequency * 1000)
intervals =
Map.get(state, :intervals, %{})
|> Map.put(method, {now, {interval, frequency, next}})
Map.put(state, :intervals, intervals)
end
def reset_next_interval(state, method) do
case get_in(state, [:intervals, method]) do
nil ->
Logger.error("Cannot reset interval that is not configured: #{method}")
state
{_, {inter, freq, _}} ->
# IO.puts("#{epoch_time(:second)} resetting interval")
now = epoch_time(:second)
put_in(state, [:intervals, method], {now, {inter, freq, now + inter}})
end
end
end