Skip to content

Commit

Permalink
Add socket tree and schemas caching
Browse files Browse the repository at this point in the history
  • Loading branch information
doorgan committed May 9, 2024
1 parent 0456400 commit 5431cff
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 35 deletions.
37 changes: 37 additions & 0 deletions lib/channel_spec/cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule ChannelSpec.Cache do
@moduledoc """
Cache for ChannelSpec specs.
Settings:
```elixir
config :channel_spec, :cache_adapter, Module
```
ChannelSpec ships with two cache adapters:
* `ChannelSpec.Cache.PersistentTermCache` - default
* `ChannelSpec.Cache.NoneCache` - none cache
If you are constantly modifying specs during development, you can configure the cache adapter
in `dev.exs` as follows to disable caching:
```elixir
config :channel_spec, :cache_adapter, ChannelSpec.Cache.NoneCache
```
"""

@callback get(module) :: nil | map()
@callback put(module, map()) :: :ok
@callback erase(module) :: :ok

@default_adapter ChannelSpec.Cache.PersistentTermCache

@doc """
Get cache adapter
"""
@spec adapter() :: module()
def adapter() do
Application.get_env(:channel_spec, :cache_adapter, @default_adapter)
end
end
23 changes: 23 additions & 0 deletions lib/channel_spec/cache/none_cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule ChannelSpec.Cache.NoneCache do
@moduledoc """
A cache adapter to disable caching. Intended to be used in development.
Configure it with:
```elixir
# config/runtime.exs
config :channel_handler, :cache_adapter, ChannelSpec.Cache.NoneCache
```
"""

@behaviour ChannelSpec.Cache

@impl true
def get(_spec_module), do: nil

@impl true
def put(_spec_module, _spec), do: :ok

@impl true
def erase(_spec_module), do: :ok
end
24 changes: 24 additions & 0 deletions lib/channel_spec/cache/persistent_term_cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule ChannelSpec.Cache.PersistentTermCache do
@moduledoc """
A cache adapter that stores the specs in memory.
This is the default cache adapter.
"""

@behaviour ChannelSpec.Cache

@impl true
def get(spec_module) do
:persistent_term.get(spec_module, nil)
end

@impl true
def put(spec_module, specs) do
:persistent_term.put(spec_module, specs)
end

@impl true
def erase(spec_module) do
:persistent_term.erase(spec_module)
end
end
63 changes: 43 additions & 20 deletions lib/channel_spec/operations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,28 @@ defmodule ChannelSpec.Operations do
_other, lines ->
lines
end)
|> Macro.escape()

quote location: :keep,
bind_quoted: [
name: name,
schema: params,
file: file,
line: line,
module: module,
line_metadata: line_metadata
] do
operation = %{
schema: schema,
file: file,
line: line,
module: module,
line_metadata: line_metadata
}

quote location: :keep do
@operations {unquote(name),
%{
schema: unquote(params),
file: unquote(file),
line: unquote(line),
module: unquote(module),
line_metadata: unquote(Macro.escape(line_metadata))
}}
@operations {name, operation}

def __channel_spec_operation__(unquote(name)), do: unquote(Macro.escape(operation))
end
end

Expand All @@ -106,7 +118,7 @@ defmodule ChannelSpec.Operations do
@doc """
Defines a subscription for the channel.
Subscriptions are messages that are sent from the server to the client, withuot
Subscriptions are messages that are sent from the server to the client, without
the client requesting them. They are defined with a name and a schema that
describes the payload.
Expand All @@ -128,17 +140,28 @@ defmodule ChannelSpec.Operations do
file = __CALLER__.file
line = __CALLER__.line
module = __CALLER__.module
line_metadata = %{"event" => %{line: line}}
line_metadata = Macro.escape(%{"event" => %{line: line}})

quote location: :keep,
bind_quoted: [
event: event,
file: file,
line: line,
module: module,
schema: schema,
line_metadata: line_metadata
] do
subscription = %{
schema: schema,
file: file,
line: line,
module: module,
line_metadata: line_metadata
}

quote do
@subscriptions {unquote(event),
%{
schema: unquote(schema),
file: unquote(file),
line: unquote(line),
module: unquote(module),
line_metadata: unquote(Macro.escape(line_metadata))
}}
@subscriptions {event, subscription}

def __channel_spec_subscription__(unquote(event)), do: unquote(Macro.escape(subscription))
end
end

Expand Down
53 changes: 39 additions & 14 deletions lib/channel_spec/socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule ChannelSpec.Socket do
pattern = String.replace_suffix(topic_pattern, "*", "{string}")
topic_pattern = String.replace(topic_pattern, ~r/\{.*\}.*/, "*")

quote do
quote location: :keep do
opts =
Keyword.update(
unquote(opts),
Expand Down Expand Up @@ -60,11 +60,27 @@ defmodule ChannelSpec.Socket do
end

def __socket_tree__() do
unquote(__MODULE__).build_ops_tree(@__channels)
case ChannelSpec.Cache.adapter().get({__MODULE__, :socket_tree}) do
nil ->
tree = unquote(__MODULE__).build_ops_tree(@__channels)
ChannelSpec.Cache.adapter().put({__MODULE__, :socket_tree}, tree)
tree

tree ->
tree
end
end

def __socket_schemas__() do
unquote(__MODULE__).build_schemas(__socket_tree__())
case ChannelSpec.Cache.adapter().get({__MODULE__, :socket_schemas}) do
nil ->
tree = unquote(__MODULE__).build_schemas(__socket_tree__())
ChannelSpec.Cache.adapter().put({__MODULE__, :socket_schemas}, tree)
tree

tree ->
tree
end
end
end
end
Expand All @@ -79,6 +95,7 @@ defmodule ChannelSpec.Socket do
def build_ops_tree(channels) do
channels =
for {topic, module, _opts} <- channels,
Code.ensure_compiled(module),
function_exported?(module, :spark_dsl_config, 0),
into: %{} do
router = module.spark_dsl_config()[[:router]]
Expand Down Expand Up @@ -235,6 +252,8 @@ defmodule ChannelSpec.Socket do
end

defp get_operations(%ChannelHandler.Dsl.Event{} = event, _router, _prefix) do
Code.ensure_compiled(event.module)

if function_exported?(event.module, :__channel_operations__, 0) do
operations = event.module.__channel_operations__()

Expand All @@ -260,17 +279,23 @@ defmodule ChannelSpec.Socket do
end

defp get_operations(%ChannelHandler.Dsl.Delegate{} = delegate, _router, _prefix) do
operations = delegate.module.__channel_operations__()

for {event, operation} <- operations, is_binary(event) do
%{
event: delegate.prefix <> event,
schema: operation.schema,
module: delegate.module,
function: :handle_in,
file: operation.file,
line: operation.line
}
Code.ensure_compiled(delegate.module)

if function_exported?(delegate.module, :__channel_operations__, 0) do
operations = delegate.module.__channel_operations__()

for {event, operation} <- operations, is_binary(event) do
%{
event: delegate.prefix <> event,
schema: operation.schema,
module: delegate.module,
function: :handle_in,
file: operation.file,
line: operation.line
}
end
else
[]
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/mix/channel_spec_routes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule Mix.Tasks.ChannelSpec.Routes do

for {path, socket_module, _} <- endpoint_module.__sockets__() do
try do
ChannelSpec.Cache.adapter().erase({socket_module, :socket_tree})
socket_tree = socket_module.__socket_tree__()
IO.puts(IO.ANSI.faint() <> "#{path}" <> IO.ANSI.reset())
print_socket_routes(socket_module, socket_tree, opts)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defmodule ChannelSpec.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:channel_handler, "~> 0.1"},
{:channel_handler, "~> 0.6"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:mneme, "~> 0.5", only: [:dev, :test]},
{:json_xema, "~> 0.6"},
Expand Down
23 changes: 23 additions & 0 deletions test/channel_spec/cache/none_cache_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule ChannelSpec.Cache.NoneCacheTest do
use ExUnit.Case, async: true

alias ChannelSpec.Cache.NoneCache

describe "get/1" do
test "returns nil" do
assert is_nil(NoneCache.get(SomeModule))
end
end

describe "put/2" do
test "returns :ok" do
assert :ok = NoneCache.put(SomeModule, %{})
end
end

describe "erase/1" do
test "returns :ok" do
assert :ok = NoneCache.erase(SomeModule)
end
end
end
Empty file.

0 comments on commit 5431cff

Please sign in to comment.