Ripster

Ripster

Function generation / dynamic dispatch

I am writing an application that reads incoming packets and I’d like to decode them and route them to the appropriate handler.

The header of each packet is what defines its type.
So for example a user login packet may look like this:

<<
  header::integer-little-8,
  ulen::unsigned-little-integer-32,
  user::binary-size(ulen),
  plen::unsigned-little-integer-32,
  pass::binary-size(plen)
>>  

I’d like to be able to do something like this in my receive function:

<<
  header::integer-little-8,
  data::bytes
>> = received

{:ok, packet} = Packet.parse(header, data)
:ok = packet.run()

Where Packet.parse/2 is defined at compile time.

defmodule Packet do
   defmacro __using__(header) do
     # track __CALLER__ and header?
     @behaviour Packet
   end

   @callback parse(BitString) :: {:ok, Map}
   @callback def run() :: :ok | {:error, reason}

   # Generate these at compile time
   def parse(1, data), do: Packets.Login.parse(data)
   def parse(2, data), do: Packets.Chat.parse(data)
end

defmodule Packets.Login do
  use Packet, header: 1

  defstruct :username, :password

  def parse(packet_data), do: # decode packet
  def run, do: # run code to handle packet
end

Is there a way to do this using macros or a better way that wouldn’t require macros?

Marked As Solved

OvermindDL1

OvermindDL1

That says they are either on the wrong file or not put in early in the compilation process.

Honestly I’m thinking this is just a bit odd of a design overall regardless, I would probably just hardcode those all in as it’s not like they are going to be changing often anyway and adding new ones is easy. ^.^;

If you really want to go the more complex route (which be sure that you do), I’d really say take a look at my afore-mentioned protocol_ex, your solution might look similar to (typed in-post so likely has syntax errors):

# Define the main module:
import ProtocolEx
defprotocolEx Blah do
  def parse(header, data) # Since no fallback body then this must be handled by the implementations

  def parse(<<header::integer-little-8, data::bytes), do: parse(header, data)
end

Then just write the implementations like:

import ProtocolEx
defimplEx Login, 1, for: Packet do
  def parse(_, data) do
    # whatever you need here...
  end
  # You could add more `parse/2` callbacks here that handle other numbers as well if you want,
  # or make other implementations like below
end

Then maybe another one somewhere else:

import ProtocolEx
defimplEx Chat, 2, for: Packet do
  def parse(_, data) do
    # whatever you need here...
  end
end

Etc…

And of course you can add a run function and all sorts as well too. Whatever. (If you need new features in protocol_ex, just ask). It has a lot of other abilities and optimizations and such you can do as well, but as-is it already does what you posted that you want.

Don’t forget to add the compile for protocol_ex to the mix.exs file as per the documentation too (or you can always handle it manually too if you want).

(EDIT: Updated examples to be actual code rather than just placeholder code)

Also Liked

OvermindDL1

OvermindDL1

I really should release my library of mine that does that at compile-time… >.>

Hmm, actually you could use my ProtocolEx (it’s like the Protocol Library built into elixir, except it is based on matchers and guards instead of types, and has the ability to be faster too by inlining and more) library if you don’t mind putting your parse functions in implementation modules (you can of course put multiples of the parse functions in a single implementation if you want, so you can group like-functionality. :slight_smile:

kip

kip

ex_cldr Core Team

I don’t think you need a macro for this. Something like this should do it. Of course you would probably
encapsulate the header->function mapping differently in real use rather than a module attribute.

defmodule Dynamic do
  @function_map [
    {1, Packets.Login},
    {2, Packets.Chat}
  ]

  def process_packet(<< header::integer-little-8, data::bytes >>) do
    process_packet(header, data)
  end

  for {header, module} <- @function_map do
    function_name = Module.concat(module, "parse")
    def process_packet(unquote(header), data), do: unquote(function_name)(data)
  end
end
mischov

mischov

My current favorite library to read for learning about neat code generation is NimbleParsec. It might give you some ideas. :slight_smile:

Edit: The Elixir Forum thread for the library is also good- searching it for “quoted expressions” might give you a few more ideas.

Last Post!

OvermindDL1

OvermindDL1

We try, don’t hesitate to ask! :slight_smile:

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement