src/hyper_register.erl

%% @doc: If you wish to implement your own backend for storing
%% registers, your module needs to implement these interfaces. The
%% backend modules have quite a lot of responsibility (detailed below)
%% to allow for backend-specific optimizations.
-module(hyper_register).

-type t() :: module().

-export_type([t/0]).

-export([
    new/2,
    set/4,
    compact/2,
    max_merge/2, max_merge/3,
    reduce_precision/3,
    register_sum/2,
    register_histogram/2,
    zero_count/2,
    encode_registers/2,
    decode_registers/3,
    bytes/2,
    precision/2
]).

-callback set(
    Index :: integer(),
    Value :: integer(),
    hyper:registers()
) ->
    hyper:registers().

-callback new(P :: hyper:precision()) ->
    hyper:registers().

-callback compact(hyper:registers()) ->
    hyper:registers().

-callback max_merge([hyper:registers()]) ->
    hyper:registers().

-callback max_merge(
    hyper:registers(),
    hyper:registers()
) ->
    hyper:registers().

-callback reduce_precision(
    hyper:precision(),
    hyper:registers()
) ->
    hyper:registers().

-callback register_sum(hyper:registers()) ->
    float().

-callback register_histogram(hyper:registers()) -> map().

-callback zero_count(hyper:registers()) ->
    integer().

-callback encode_registers(hyper:registers()) ->
    binary().

-callback decode_registers(binary(), hyper:precision()) ->
    hyper:registers().

-callback bytes(hyper:registers()) ->
    integer().

-callback precision(hyper:registers()) -> integer().

%% @doc: Creates a new instance of the backend. The return value of
%% this function will be passed to all functions in this module.
-spec new(t(), P :: hyper:precision()) -> hyper:registers().
new(RegisterImpl, P) ->
    RegisterImpl:new(P).

%% @doc: Set the register to the given value, *only* if the value
%% already stored is lower than the new value. The backend needs to
%% ensure the register value is only allowed to increase.
-spec set(t(), Index :: integer(), Value :: integer(), hyper:registers()) ->
    hyper:registers().
set(RegisterImpl, Index, Value, Registers) ->
    RegisterImpl:set(Index, Value, Registers).

%% @doc: Compact is always called before any attempt at reading (sum,
%% zero count, etc) or merging. It is intended to give backends that
%% buffer the writes a chance to flush the buffer before the registers
%% are needed.
-spec compact(t(), hyper:registers()) -> hyper:registers().
compact(RegisterImpl, Registers) ->
    RegisterImpl:compact(Registers).

%% @doc: Merge any number of registers, used to calculate the
%% union. For two register values at the same index, the max value
%% must be in the resulting register.
-spec max_merge(t(), [hyper:registers()]) -> hyper:registers().
max_merge(RegisterImpl, RegisterLists) ->
    RegisterImpl:max_merge(RegisterLists).

%% @doc: Same as max_merge/1 but used when we know only two filters
%% are merged.
-spec max_merge(t(), hyper:registers(), hyper:registers()) -> hyper:registers().
max_merge(RegisterImpl, Registers1, Registers2) ->
    RegisterImpl:max_merge(Registers1, Registers2).

%% @doc: Reduce the precision of the registers. Used for mixed-precision
%% union by first reducing the precision to the lowest of all filters.
-spec reduce_precision(t(), hyper:precision(), hyper:registers()) -> hyper:registers().
reduce_precision(RegisterImpl, Precision, Registers) ->
    RegisterImpl:reduce_precision(Precision, Registers).

%% @doc: Sum of 2^-R where R is the value in each register.
-spec register_sum(t(), hyper:registers()) -> float().
register_sum(RegisterImpl, Registers) ->
    RegisterImpl:register_sum(Registers).

%% @doc: A map with key the values possible for the register and values the
%% number of register with that value.
-spec register_histogram(t(), hyper:registers()) -> map().
register_histogram(RegisterImpl, Registers) ->
    RegisterImpl:register_histogram(Registers).

%% @doc: Count of registers set to 0.
-spec zero_count(t(), hyper:registers()) -> integer().
zero_count(RegisterImpl, Registers) ->
    RegisterImpl:zero_count(Registers).

%% @doc: Encode and decode are called to convert the in-memory
%% representation of the backend to the serialized format. Must return
%% one binary where each register is encoded as an 8-bit integer.
-spec encode_registers(t(), hyper:registers()) -> binary().
encode_registers(RegisterImpl, Registers) ->
    RegisterImpl:encode_registers(Registers).

-spec decode_registers(t(), binary(), hyper:precision()) -> hyper:registers().
decode_registers(RegisterImpl, Encoded, Precision) ->
    RegisterImpl:decode_registers(Encoded, Precision).

%% @doc: Size in bytes used to represent the registers in memory.
-spec bytes(t(), hyper:registers()) -> integer().
bytes(RegisterImpl, Registers) ->
    RegisterImpl:bytes(Registers).

-spec precision(t(), hyper:registers()) -> integer().
precision(RegisterImpl, Registers) ->
    RegisterImpl:precision(Registers).