Current section

Files

Jump to
rak lib rak.ex
Raw

lib/rak.ex

defmodule Rak do
@moduledoc """
Rak Game Server Maker
The top level module is used to define how your server will be composed.
In here, we define which services to use, and how they will connect to each other.
```elixir
defmodule MyApp do
use Rak,
metrics: Rak.Metrics.Prometheus,
service: MyApp.Service,
module: :auto,
discovery: Rak.Discovery.Consul,
connector: [
module: Rak.Connector.Web,
message: Rak.Message.Raw,
session: Rak.Session.Basic
]
end
```
You may specify multiple entries for each components,
or remove them if you do not use them.
`module: :auto` is a wildcard configuration entry that lets
Rak know to load all top-level modules automatically.
Finally, you may want to configure which modules to start depending
on the environment you are running. For instance, you may not
want to start the discovery service when developing locally. In
such cases, you may move the configuration to your `config/config.exs`
and override it depending on the environment.
> lib/my_app.ex
```elixir
defmodule MyApp do
use Rak
end
```
> config/config.exs
```elixir
use Mix.Config
config :my_app,
rak: [
metrics: Rak.Metrics.Prometheus,
service: MyApp.Service,
module: :auto,
discovery: Rak.Discovery.Consul,
connector: [
module: Rak.Connector.Web,
message: Rak.Message.Raw,
session: Rak.Session.Basic
]
]
"""
@components [
metrics: "Metrics system",
service: "Top-level application service",
module: "Application module",
session: "Session module",
discovery: "Discovery and clustering (optional)",
connector: "Network connector for clients to connect to the server",
]
defmacro __using__(config) do
children = config_children(config)
quote do
use Application
use Supervisor
@doc """
Start
"""
def start({:takeover, node}, _) do
raise Rak.Exception,
message: "Takeover is not supported",
details: "Ref: https://hexdocs.pm/elixir/Application.html#callbacks"
end
def start({:failover, node}, _) do
raise Rak.Exception,
message: "Failover is not supported",
details: "Ref: https://hexdocs.pm/elixir/Application.html#callbacks"
end
def start(:normal, args) do
args = parse_args(args)
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
end
def init(args) do
# Flattening the list to allow for runtime generators
# See: get_module_autoload_child_spec/0
children = List.flatten(unquote(children))
Supervisor.init(children, strategy: :one_for_one)
end
end
end
@doc false
def autoload_modules(app_module) do
app_module_name = Atom.to_string(app_module)
app_module_depth = app_module_name
|> String.split(".")
|> length
:code.all_loaded
|> Stream.map(fn({ module, _ }) ->
module_name = Atom.to_string(module)
{ module, module_name }
end)
|> Stream.filter(fn({ _, module_name }) ->
String.starts_with?(module_name, app_module_name)
end)
|> Stream.filter(fn({ _, module_name }) ->
module_depth = module_name
|> String.split(".")
|> length
module_depth == app_module_depth + 1
end)
|> Enum.filter(fn({ module, _ }) ->
Keyword.has_key?(module.__info__(:attributes), :rak_module)
end)
end
defp config_children(config) do
session_modules = get_session_modules(config)
app_modules = get_app_modules(config)
config = Keyword.drop(:module)
config = app_modules
++ session_modules
++ config
@components
|> Keyword.keys
|> Enum.reduce([], fn(component, children) ->
children ++ config
|> Keyword.get_values(component)
|> Enum.map(fn (config) ->
get_child_spec(component, config)
end)
end)
end
defp get_app_modules(config) do
modules = config
|> Keyword.get_values(:module)
auto_index = Enum.find_index(modules, fn (val) ->
val == :auto
end)
if auto_index do
{pre, [_|post]} = Enum.split(modules, auto_index)
pre ++ get_module_autoload_child_spec() ++ post
else
modules
end
end
defp get_session_modules(config) do
config
|> Keyword.get_values(:connector)
|> Enum.map(&get_session_module/1)
end
defp get_session_module(%{ session: session }), do: {:session, session}
defp get_session_module(%{ module: module }) do
raise Rak.Exception,
message: "Missing session module for connector #{module}",
details: "Without a session module, connections cannot be handled"
end
defp get_child_spec(:connector, config) do
module = config[:module]
config = Keyword.drop(config, [:module])
quote do
unquote(module).child_spec(unquote(config))
end
end
defp get_child_spec(_, module) do
quote do
unquote(module).child_spec([])
end
end
defp get_module_autoload_child_spec() do
quote do
Rak.autoload_modules(__MODULE__)
end
end
end