Packages

Memory handling for hashs and references

Current section

Files

Jump to
memhash src memhash.erl
Raw

src/memhash.erl

-module(memhash).
-author('manuel@altenwald.com').
-export([new/0,
get/2,
keys/1,
rget/2,
get_all/1,
set_val/3,
set_id/3,
remove/2,
destroy/1]).
-opaque memhash() :: {tid, ets:tid()}.
-export_type([memhash/0]).
-spec new() -> memhash().
new() ->
{tid, ets:new(undefined, [ordered_set, public])}.
-spec get(memhash(), Key :: term()) ->
undefined |
{value, memhash_data:table_id(), term()} |
{error, enomemhash}.
get({tid, Mem}, Key) ->
try ets:lookup(Mem, Key) of
[] -> undefined;
[{Key, ID}] -> {value, ID, memhash_data:get(ID)}
catch error:badarg ->
{error, enomemhash}
end.
-spec keys(memhash()) -> [{term(), term()}] | {error, enomemhash}.
keys({tid, Mem}) ->
try
Key = ets:first(Mem),
keys({tid, Mem}, Key, [])
catch error:badarg ->
{error, enomemhash}
end.
-spec keys(memhash(), term(), [{term(), term()}]) -> [{term(), term()}].
keys(_Mem, '$end_of_table', Acc) ->
lists:reverse(Acc);
keys({tid, Mem}, CurrentKey, Acc) ->
NextKey = ets:next(Mem, CurrentKey),
keys({tid, Mem}, NextKey, [CurrentKey|Acc]).
-spec rget(memhash(), Key :: term()) -> term().
rget(Mem, Key) ->
case get(Mem, Key) of
{value, _ID, {tid, _} = Result} ->
get_all(Result);
{value, _ID, Result} ->
Result;
{error, Error} ->
throw({error, Error});
undefined ->
undefined
end.
-spec get_all(memhash()) -> [{term(), term()}].
get_all(Mem) ->
try
get_all(Mem, [])
catch error:badarg ->
throw({error, enomemhash})
end.
-spec get_all(memhash(), [memhash()]) -> [{term(), term()}].
%% @private
get_all({tid, Mem}, Mems) ->
case lists:member(Mem, Mems) of
true -> throw({error, eloop});
false -> ok
end,
ets:foldr(fun({Key, ID}, Acc) ->
case memhash_data:get(ID) of
{tid, _} = Result ->
[{Key, lists:reverse(get_all(Result, [Mem|Mems]))}|Acc];
Result ->
[{Key, Result}|Acc]
end
end, [], Mem).
-spec set_val(memhash(), Key :: term(), Value :: term()) -> ok.
%% @doc set a value inside of the memhash with that value. It's added
%% to the datatable if the key doesn't exist or replace the value
%% in the table otherwise.
set_val({tid, Mem}, Key, Value) ->
case ets:lookup(Mem, Key) of
[] ->
ID = memhash_data:add(Value),
ets:insert(Mem, {Key, ID});
[{Key, ID}] ->
case memhash_data:get(ID) of
{tid, _} = Result ->
destroy(Result);
_Result ->
ok
end,
memhash_data:set(ID, Value)
end,
ok.
-spec set_id(memhash(), Key :: term(), memhash_data:table_id()) -> ok.
%% @doc set a reference inside of the memhash. The reference must be an
%% ID from other part of the memhash (same memhash).
set_id({tid, Mem}, Key, ID) ->
memhash_data:incr(ID),
case ets:lookup(Mem, Key) of
[] ->
ok;
[{Key, ID2}] ->
memhash_data:decr(ID2)
end,
ets:insert(Mem, {Key, ID}),
ok.
-spec remove(memhash(), Key :: term()) -> ok.
remove({tid, Mem}, Key) ->
case ets:lookup(Mem, Key) of
[] ->
ok;
[{_Key, ID}] ->
memhash_data:decr(ID),
ets:delete(Mem, Key),
ok
end.
-spec destroy(memhash()) -> ok.
destroy({tid, _} = Mem) ->
try
destroy(Mem, [])
catch error:badarg ->
ok
end.
-spec destroy(memhash:memhash(), [memhash:memhash()]) -> ok.
%% @private
destroy({tid, Mem}, Mems) ->
case lists:member(Mem, Mems) of
true ->
ok;
false ->
ets:foldl(fun({_Key, ID}, _) ->
Links = memhash_data:get_links(ID),
Data = memhash_data:get(ID),
case {Data, Links} of
{{tid, _} = Result, 1} ->
destroy(Result, [Mem|Mems]);
_Result ->
ok
end,
memhash_data:decr(ID)
end, undefined, Mem),
ets:delete(Mem),
ok
end.