src/swirl_ets_manager.erl

-module(swirl_ets_manager).
-include("swirl.hrl").

%% internal
-export([
    new_table/3,
    start_link/0,
    table/3
]).

-behaviour(gen_server).
-export([
    init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
]).

-define(SERVER, ?MODULE).

-record(state, {
    tables
}).

%% internal
-spec new_table(atom(), list(atom() | tuple()), atom() | pid()) -> ok.
new_table(Name, Options, Server) ->
    gen_server:cast(?SERVER, {new_table, {Name, Options, Server}}).

-spec start_link() -> {ok, pid()}.
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

-spec table(atom(), list(atom() | tuple()), atom() | pid()) -> ok.
table(Name, Options, Server) ->
    gen_server:cast(?SERVER, {table, {Name, Options, Server}}).

%% gen_server callbacks
init([]) ->
    {ok, #state {
        tables = dict:new()
    }}.

handle_call(Request, _From, State) ->
    io:format("unexpected message: ~p~n", [Request]),
    {reply, ok, State}.

handle_cast({new_table, {Name, Options, Server} = Data}, #state {
        tables = Tables
    } = State) ->

    TableId = ets_new(Name, Options, Data),
    Tables2 = dict:erase({Server, Name}, Tables),
    Tables3 = dict:append({Server, Name}, TableId, Tables2),
    ets_give_away(Server, TableId, Data),

    {noreply, State#state {
        tables = Tables3
    }};
handle_cast({table, {Name, Options, Server} = Data}, #state {
        tables = Tables
    } = State) ->

    TableId =
        case swirl_utils:safe_dict_fetch({Server, Name}, Tables) of
            undefined -> ets_new(Name, Options, Data);
            [TableId2] -> TableId2
        end,
    Tables2 = dict:erase({Server, Name}, Tables),
    Tables3 = dict:append({Server, Name}, TableId, Tables2),
    ets_give_away(Server, TableId, Data),

    {noreply, State#state {
        tables = Tables3
    }};
handle_cast(Msg, State) ->
    io:format("unexpected message: ~p~n", [Msg]),
    {noreply, State}.

handle_info({'ETS-TRANSFER', _TableId, _Pid, _Data}, State) ->
    {noreply, State};
handle_info(Msg, State) ->
    io:format("unexpected message: ~p~n", [Msg]),
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% private
ets_new(Name, Options, Data) ->
    TableId = ets:new(Name, Options),
    true = ets:setopts(TableId, {heir, self(), Data}),
    TableId.

ets_give_away(Server, TableId, Data) ->
    ServerPid = server_pid(Server),
    ets:give_away(TableId, ServerPid, Data).

server_pid(Server) when is_pid(Server) ->
    Server;
server_pid(Server) when is_atom(Server) ->
    whereis(Server).