Current section

Files

Jump to
sorcery lib services sorcery_db reverse_query.ex
Raw

lib/services/sorcery_db/reverse_query.ex

defmodule Sorcery.SorceryDb.ReverseQuery do
@moduledoc false
import Sorcery.Helpers.Ets
# Return the set of {pid, portal_name} pairs that are affected by this diff.
def reverse_query(diff) do
portal_names = get_portal_names_affected_by_diff(diff)
reverse_query(diff, portal_names, [])
end
def reverse_query(diff, [portal_name | portal_names], passing_pid_portals) do
####
# Ok since a process can have multiple portals, we don't actually want to exclude it's pid
# Even though it would give better performance
# Leaving this here in case I come up with a better solution later.
#passing_pids = Enum.map(passing_pid_portals, fn {pid, _portal_name, _query_mod, _args} -> pid end)# &(Enum.at(&1, 0)))
#instances = get_all_portal_instances(portal_name, exclude_pids: passing_pids)
instances = get_all_portal_instances(portal_name, [])
####
ctx = %{diff: diff, portal_name: portal_name}
new_passing_pid_portals = intersect_clauses_and_diffs(instances, ctx)
reverse_query(diff, portal_names, new_passing_pid_portals ++ passing_pid_portals)
end
def reverse_query(_, [], passing_pid_portals), do: passing_pid_portals
def intersect_clauses_and_diffs(portal_instances, %{diff: diff, portal_name: portal_name} = ctx) do
Enum.reduce(portal_instances, [], fn [pid, query_mod, args], acc ->
ctx = Map.merge(ctx, %{pid: pid, query_mod: query_mod, args: args})
clauses = query_mod.clauses(args)
|> Enum.filter(&(&1.tk in diff.tks_affected))
|> Enum.group_by(&(&1.lvar))
intersects? = Enum.any?(diff.rows, fn %{old_entity: old_ent, new_entity: new_ent} ->
Enum.any?([old_ent, new_ent], fn entity ->
Enum.any?(clauses, fn {_lvar, clauses} ->
lclauses = Enum.group_by(clauses, &(&1.other_lvar))
entity_matches_lclauses(entity, lclauses, ctx)
end)
end)
end)
if intersects?, do: [{pid, portal_name, query_mod, args} | acc], else: acc
end)
end
# {{{ get_portal_names_affected_by_diff(diff)
def put_all_portal_names(portal_name) do
ensure_table(:sorcery_portal_names, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
:ets.insert(:sorcery_portal_names, {portal_name})
end
def get_all_portal_names() do
ensure_table(:sorcery_portal_names, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
:ets.match(:sorcery_portal_names, {:"$1"})
|> List.flatten()
end
def get_portal_names_affected_by_diff(%{tks_affected: tks} = _diff) do
difftks = MapSet.new(tks)
get_all_portal_names()
|> Enum.filter(fn name ->
table_name = get_portal_table_name(name)
case :ets.select(table_name, [{ {:_, :"$2", :_}, [], [:"$2"]}], 1) do
{[query_mod], _} ->
qmtks = query_mod.tks_affected() |> MapSet.new()
!MapSet.disjoint?(difftks, qmtks)
_ -> false
end
end)
end
# }}}
# {{{ portal_tables (i.e. {:"sorcery_portals?portal=:get_battle", child_pid, query_mod, args})
def get_portal_table_name(portal_name) do
"sorcery_portals?portal=:#{portal_name}"
|> String.to_atom()
end
#def put_portal_table(portal_name, child_pid, query_mod, args) do
def put_portal_table(%{portal_name: portal_name, child_pid: child_pid, query_module: query_mod, args: args} = _portal) do
table = get_portal_table_name(portal_name)
ensure_table(table, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
put_all_portal_names(portal_name)
:ets.insert(table, {child_pid, query_mod, args})
end
def get_all_portal_instances(portal_name, opts \\ []) do
table_name = get_portal_table_name(portal_name)
guards = []
guards = case Keyword.get(opts, :exclude_pids) do
pids when is_list(pids) ->
li = Enum.map(pids, fn pid ->
{:"=/=", :"$1", pid}
end)
li ++ guards
_ -> guards
end
:ets.select(table_name, [{ {:"$1", :"$2", :"$3"}, guards, [:"$$"] }])
end
# }}}
# {{{ watcher_tables (i.e. {:"sorcery_watchers?portal=:get_battle&lvar=?team", entities_list})
def get_watcher_table_name(portal_name, lvar) do
"sorcery_watchers?portal=#{portal_name}&lvar=#{lvar}"
|> String.to_atom()
end
def repopulate_watcher_table(portal_name, lvar, pid, entities) when is_list(entities) do
table_name = get_watcher_table_name(portal_name, lvar)
# Create the table if it doesn't already exist
case :ets.info(table_name) do
:undefined -> :ets.new(table_name, [:set, :public, :named_table, read_concurrency: true, write_concurrency: true])
_ -> :noop
end
# delete all the entries for this pid
:ets.insert(table_name, {pid, entities})
end
def get_watcher_entities(portal_name, lvar, pid) do
table_name = get_watcher_table_name(portal_name, lvar)
case :ets.info(table_name) do
:undefined -> []
_ ->
case :ets.lookup(table_name, pid) do
[{_pid, entities} | _] -> entities
_ -> []
end
end
end
# }}}
# {{{ entity_matches_lclauses(entity, lclauses, ctx)
def entity_matches_lclauses(entity, lclauses, ctx) do
Enum.all?(lclauses, fn {other_lvark, where_clauses} ->
case "#{other_lvark}" do
"?" <> _ ->
right_ents = get_watcher_entities(ctx.portal_name, other_lvark, ctx.pid)
Enum.any?(right_ents, fn right_entity ->
ctx = Map.put(ctx, :right_entity, right_entity)
Enum.all?(where_clauses, fn clause -> entity_matches_clause(entity, clause, ctx) end)
end)
_ ->
Enum.all?(where_clauses, fn clause -> entity_matches_clause(entity, clause, ctx) end)
end
end)
end
# }}}
# {{{ entity_matches_clause(entity, clause, ctx)
defp get_op(%{op: op}) when op in [:==, :in, :!=, :>, :>=, :<, :<=], do: op
defp get_op(clause), do: raise ":#{clause[:op]} is not a valid SrcQl op atom."
defp apply_olr(:in, left, right), do: left in right
defp apply_olr(op, left, right), do: apply(Kernel, op, [left, right])
def entity_matches_clause(entity, %{right_type: :literal} = clause, _ctx) do
op = get_op(clause)
left = entity[clause.attr]
right = clause.right
apply_olr(op, left, right)
end
def entity_matches_clause(entity, %{right_type: :arg, arg_name: arg_name} = clause, %{args: args}) do
op = get_op(clause)
left = entity[clause.attr]
right = args[arg_name]
apply_olr(op, left, right)
end
def entity_matches_clause(entity, %{right_type: :lvar} = clause, %{right_entity: rent}) do
op = get_op(clause)
left = entity[clause.attr]
right = rent[clause.other_lvar_attr]
apply_olr(op, left, right)
end
# }}}
end