-module(ids@snowflake). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([generate/1, decode/1, start_with_epoch/2, start/1]). -export_type([message/0, state/0]). -opaque message() :: {generate, gleam@erlang@process:subject(integer())}. -opaque state() :: {state, integer(), integer(), integer(), integer()}. -spec generate(gleam@erlang@process:subject(message())) -> integer(). generate(Channel) -> gleam@otp@actor:call(Channel, fun(Field@0) -> {generate, Field@0} end, 1000). -spec decode(integer()) -> {ok, {integer(), integer(), integer()}} | {error, binary()}. decode(Snowflake) -> case binary:encode_unsigned(Snowflake) of <> -> {ok, {Timestamp, Machine_id, Idx}}; _ -> {error, <<"Error: Couldn't decode snowflake id."/utf8>>} end. -spec update_state(state()) -> state(). update_state(State) -> Now = begin _pipe = os:system_time(millisecond), gleam@int:subtract(_pipe, erlang:element(2, State)) end, case erlang:element(3, State) of Lt when (Lt =:= Now) andalso (erlang:element(5, State) < 4095) -> erlang:setelement(5, State, erlang:element(5, State) + 1); Lt@1 when Lt@1 =:= Now -> update_state(State); _ -> erlang:setelement(3, State, Now) end. -spec handle_msg(message(), state()) -> gleam@otp@actor:next(message(), state()). handle_msg(Msg, State) -> case Msg of {generate, Reply} -> New_state = update_state(State), Snowflake = begin _pipe = erlang:element(3, New_state), _pipe@1 = erlang:'bsl'(_pipe, 22), erlang:'bor'( _pipe@1, begin _pipe@2 = erlang:element(4, New_state), _pipe@3 = erlang:'bsl'(_pipe@2, 12), erlang:'bor'(_pipe@3, erlang:element(5, New_state)) end ) end, gleam@otp@actor:send(Reply, Snowflake), gleam@otp@actor:continue(New_state) end. -spec start_with_epoch(integer(), integer()) -> {ok, gleam@erlang@process:subject(message())} | {error, binary()}. start_with_epoch(Machine_id, Epoch) -> case Epoch > os:system_time(millisecond) of true -> {error, <<"Error: Epoch can't be larger than current time."/utf8>>}; false -> _pipe = {state, Epoch, 0, Machine_id, 0}, _pipe@1 = gleam@otp@actor:start(_pipe, fun handle_msg/2), gleam@result:map_error( _pipe@1, fun(Err) -> <<"Error: Couldn't start actor. Reason: "/utf8, (gleam@string:inspect(Err))/binary>> end ) end. -spec start(integer()) -> {ok, gleam@erlang@process:subject(message())} | {error, binary()}. start(Machine_id) -> start_with_epoch(Machine_id, 0).