each remote system it is communicating with • Sockets are two-way, supporting both the sending and receiving of data • Data can arrive on any socket at anytime
.. . defstruct mode: :ready, name: nil, ui: nil, me: nil def start_link(options) do GenServer.start_link( __MODULE__ , options , name: Keyword.get(options, :name, __MODULE__) ) end def init(options) do case Keyword.get(options, :ui) do ui when is_atom(ui) - > me = Keyword.get(options, :name, __MODULE__) || self() {:ok, %__MODULE__{ui: ui, me: me}} _no_ui - > {:stop, "ConnectionManager must be started with a UI module"} end end end
.. . defstruct mode: :ready, name: nil, ui: nil, me: nil def start_link(options) do GenServer.start_link( __MODULE__ , options , name: Keyword.get(options, :name, __MODULE__) ) end def init(options) do case Keyword.get(options, :ui) do ui when is_atom(ui) - > me = Keyword.get(options, :name, __MODULE__) || self() {:ok, %__MODULE__{ui: ui, me: me}} _no_ui - > {:stop, "ConnectionManager must be started with a UI module"} end end end
.. . defstruct mode: :ready, name: nil, ui: nil, me: nil def start_link(options) do GenServer.start_link( __MODULE__ , options , name: Keyword.get(options, :name, __MODULE__) ) end def init(options) do case Keyword.get(options, :ui) do ui when is_atom(ui) - > me = Keyword.get(options, :name, __MODULE__) || self() {:ok, %__MODULE__{ui: ui, me: me}} _no_ui - > {:stop, "ConnectionManager must be started with a UI module"} end end end
.. . defstruct mode: :ready, name: nil, ui: nil, me: nil def start_link(options) do GenServer.start_link( __MODULE__ , options , name: Keyword.get(options, :name, __MODULE__) ) end def init(options) do case Keyword.get(options, :ui) do ui when is_atom(ui) - > me = Keyword.get(options, :name, __MODULE__) || self() {:ok, %__MODULE__{ui: ui, me: me}} _no_ui - > {:stop, "ConnectionManager must be started with a UI module"} end end end
.. . defstruct mode: :ready, name: nil, ui: nil, me: nil def start_link(options) do GenServer.start_link( __MODULE__ , options , name: Keyword.get(options, :name, __MODULE__) ) end def init(options) do case Keyword.get(options, :ui) do ui when is_atom(ui) - > me = Keyword.get(options, :name, __MODULE__) || self() {:ok, %__MODULE__{ui: ui, me: me}} _no_ui - > {:stop, "ConnectionManager must be started with a UI module"} end end end
port, name) do GenServer.call(manager, {:listen, port, name}) end def handle_call({:listen, port, name}, _from, state) do case start_listening(port, state.me) do :ok - > {:reply, :ok, %__MODULE__{state | mode: :host, name: name}} error - > {:reply, error, state} end end end
port, name) do GenServer.call(manager, {:listen, port, name}) end def handle_call({:listen, port, name}, _from, state) do case start_listening(port, state.me) do :ok - > {:reply, :ok, %__MODULE__{state | mode: :host, name: name}} error - > {:reply, error, state} end end end
port, name) do GenServer.call(manager, {:listen, port, name}) end def handle_call({:listen, port, name}, _from, state) do case start_listening(port, state.me) do :ok - > {:reply, :ok, %__MODULE__{state | mode: :host, name: name}} error - > {:reply, error, state} end end end
port, name) do GenServer.call(manager, {:listen, port, name}) end def handle_call({:listen, port, name}, _from, state) do case start_listening(port, state.me) do :ok - > {:reply, :ok, %__MODULE__{state | mode: :host, name: name}} error - > {:reply, error, state} end end end
they come in • A program can "accept" them to complete the connection • The result of accepting a connection is a read/write socket used to communicate with the remote program • Erlang (now) provides a low-level `socket`, but the higher-level `gen_tcp` adds several niceties
me) do case :gen_tcp.listen( port , [:binary, packet: @packet_size, active: false, reuseaddr: true] ) do {:ok, listening_socket} - > Listener.listen(listening_socket, me) error - > erro r end end end
me) do case :gen_tcp.listen( port , [:binary, packet: @packet_size, active: false, reuseaddr: true] ) do {:ok, listening_socket} - > Listener.listen(listening_socket, me) error - > erro r end end end
me) do case :gen_tcp.listen( port , [:binary, packet: @packet_size, active: false, reuseaddr: true] ) do {:ok, listening_socket} - > Listener.listen(listening_socket, me) error - > erro r end end end
me) do case :gen_tcp.listen( port , [:binary, packet: @packet_size, active: false, reuseaddr: true] ) do {:ok, listening_socket} - > Listener.listen(listening_socket, me) error - > erro r end end end
# .. . def listen(listening_socket, manager) do case DynamicSupervisor.start_child( ConnectionSupervisor , {__MODULE__, [listening_socket, manager]} ) do {:ok, listener} - > transfer_control(listening_socket, listener) error - > erro r end end end
# .. . def listen(listening_socket, manager) do case DynamicSupervisor.start_child( ConnectionSupervisor , {__MODULE__, [listening_socket, manager]} ) do {:ok, listener} - > transfer_control(listening_socket, listener) error - > erro r end end end
# .. . def listen(listening_socket, manager) do case DynamicSupervisor.start_child( ConnectionSupervisor , {__MODULE__, [listening_socket, manager]} ) do {:ok, listener} - > transfer_control(listening_socket, listener) error - > erro r end end end
case :gen_tcp.controlling_process(listening_socket, listener) do :ok - > accept(listener) :ok error - > close(listener) erro r end end defp accept(listener), do: GenServer.cast(listener, :accept) end
case :gen_tcp.controlling_process(listening_socket, listener) do :ok - > accept(listener) :ok error - > close(listener) erro r end end defp accept(listener), do: GenServer.cast(listener, :accept) end
case :gen_tcp.controlling_process(listening_socket, listener) do :ok - > accept(listener) :ok error - > close(listener) erro r end end defp accept(listener), do: GenServer.cast(listener, :accept) end
case :gen_tcp.controlling_process(listening_socket, listener) do :ok - > accept(listener) :ok error - > close(listener) erro r end end defp accept(listener), do: GenServer.cast(listener, :accept) end
• Timeout long running code or execute it in a linked `Task` • `Process.send_after/2` and `:timer.send_interval/2` can repeat message sends • Process other messages between calls Danger Zone!
do case :gen_tcp.connect( String.to_charlist(host) , port , [:binary, packet: @packet_size, active: false] ) do {:ok, socket} - > Connection.listen(socket, me) error - > erro r end end end
do case :gen_tcp.connect( String.to_charlist(host) , port , [:binary, packet: @packet_size, active: false] ) do {:ok, socket} - > Connection.listen(socket, me) error - > erro r end end end
do case :gen_tcp.connect( String.to_charlist(host) , port , [:binary, packet: @packet_size, active: false] ) do {:ok, socket} - > Connection.listen(socket, me) error - > erro r end end end
delivering messages over an unreliable network • How do we know what a full message is though? • Delimit messages with something like newlines • Send the length of the message, then the message (`:gen_tcp` does this!) • How do we know what's in a message? • Erlang's `term_to_binary/1`
case DynamicSupervisor.start_child( ConnectionSupervisor , {__MODULE__, [socket, manager]} ) do {:ok, connection} - > transfer_control(socket, connection) error - > erro r end end end
case DynamicSupervisor.start_child( ConnectionSupervisor , {__MODULE__, [socket, manager]} ) do {:ok, connection} - > transfer_control(socket, connection) error - > erro r end end end
:inet.setopts(state.socket, active: :once) {:noreply, state} end defp transfer_control(socket, connection) do case :gen_tcp.controlling_process(socket, connection) do :ok - > activate(connection) :ok error - > close(connection) erro r end end defp activate(connection), do: GenServer.cast(connection, :activate) end
:inet.setopts(state.socket, active: :once) {:noreply, state} end defp transfer_control(socket, connection) do case :gen_tcp.controlling_process(socket, connection) do :ok - > activate(connection) :ok error - > close(connection) erro r end end defp activate(connection), do: GenServer.cast(connection, :activate) end
:inet.setopts(state.socket, active: :once) {:noreply, state} end defp transfer_control(socket, connection) do case :gen_tcp.controlling_process(socket, connection) do :ok - > activate(connection) :ok error - > close(connection) erro r end end defp activate(connection), do: GenServer.cast(connection, :activate) end
message, from) do GenServer.cast(manager, {:receive_message, message, from}) end def handle_cast({:receive_message, message, from}, state) do if state.mode == :host do for_active_connections(fn {:unde fi ned, pid, :worker, [Connection]} when pid != from - > Connection.queue_send(pid, message) _listener_or_from - > :ok end) end {name, content} = :erlang.binary_to_term(message) state.ui.show_chat_message(name, content) {:noreply, state} end end
message, from) do GenServer.cast(manager, {:receive_message, message, from}) end def handle_cast({:receive_message, message, from}, state) do if state.mode == :host do for_active_connections(fn {:unde fi ned, pid, :worker, [Connection]} when pid != from - > Connection.queue_send(pid, message) _listener_or_from - > :ok end) end {name, content} = :erlang.binary_to_term(message) state.ui.show_chat_message(name, content) {:noreply, state} end end
message, from) do GenServer.cast(manager, {:receive_message, message, from}) end def handle_cast({:receive_message, message, from}, state) do if state.mode == :host do for_active_connections(fn {:unde fi ned, pid, :worker, [Connection]} when pid != from - > Connection.queue_send(pid, message) _listener_or_from - > :ok end) end {name, content} = :erlang.binary_to_term(message) state.ui.show_chat_message(name, content) {:noreply, state} end end
message, from) do GenServer.cast(manager, {:receive_message, message, from}) end def handle_cast({:receive_message, message, from}, state) do if state.mode == :host do for_active_connections(fn {:unde fi ned, pid, :worker, [Connection]} when pid != from - > Connection.queue_send(pid, message) _listener_or_from - > :ok end) end {name, content} = :erlang.binary_to_term(message) state.ui.show_chat_message(name, content) {:noreply, state} end end
message, from) do GenServer.cast(manager, {:receive_message, message, from}) end def handle_cast({:receive_message, message, from}, state) do if state.mode == :host do for_active_connections(fn {:unde fi ned, pid, :worker, [Connection]} when pid != from - > Connection.queue_send(pid, message) _listener_or_from - > :ok end) end {name, content} = :erlang.binary_to_term(message) state.ui.show_chat_message(name, content) {:noreply, state} end end
|> DynamicSupervisor.which_children() |> Enum. fi lter(fn {:unde fi ned, pid_or_restarting, :worker, _modules} - > is_pid(pid_or_restarting) end) |> Enum.each(func) end end
|> DynamicSupervisor.which_children() |> Enum. fi lter(fn {:unde fi ned, pid_or_restarting, :worker, _modules} - > is_pid(pid_or_restarting) end) |> Enum.each(func) end end
|> DynamicSupervisor.which_children() |> Enum. fi lter(fn {:unde fi ned, pid_or_restarting, :worker, _modules} - > is_pid(pid_or_restarting) end) |> Enum.each(func) end end
to the same `GenServer` in another call (deadlock) • When nesting calls to other processes, consider the effect on timeouts • You can always nest a "cast" or `send/2` (`handle_info/2`) • Remember to report cast failure out-of-band when needed • Consider adding backpressure to protect the receiver from drowning
message) do GenServer.call(manager, {:send_to_all, message}) end def handle_call({:send_to_all, message}, _from, state) do result = queue_all_sends(message, state) {:reply, result, state} end end
message) do GenServer.call(manager, {:send_to_all, message}) end def handle_call({:send_to_all, message}, _from, state) do result = queue_all_sends(message, state) {:reply, result, state} end end
queue_send(connection, nil, message) def queue_send(connection, message_id, message) do GenServer.cast(connection, {:queue_send, message_id, message}) end def handle_cast({:queue_send, message_id, message}, state) do case :gen_tcp.send(state.socket, message) do :ok - > :ok error - > if is_reference(message_id) do ConnectionManager.receive_send_error(state.manager, message_id, error) end end {:noreply, state} end end
queue_send(connection, nil, message) def queue_send(connection, message_id, message) do GenServer.cast(connection, {:queue_send, message_id, message}) end def handle_cast({:queue_send, message_id, message}, state) do case :gen_tcp.send(state.socket, message) do :ok - > :ok error - > if is_reference(message_id) do ConnectionManager.receive_send_error(state.manager, message_id, error) end end {:noreply, state} end end
queue_send(connection, nil, message) def queue_send(connection, message_id, message) do GenServer.cast(connection, {:queue_send, message_id, message}) end def handle_cast({:queue_send, message_id, message}, state) do case :gen_tcp.send(state.socket, message) do :ok - > :ok error - > if is_reference(message_id) do ConnectionManager.receive_send_error(state.manager, message_id, error) end end {:noreply, state} end end
message_id, error) do GenServer.cast(manager, {:receive_send_error, message_id, error}) end def handle_cast({:receive_send_error, message_id, _error}, state) do state.ui.show_send_failure(message_id) {:noreply, state} end end
message_id, error) do GenServer.cast(manager, {:receive_send_error, message_id, error}) end def handle_cast({:receive_send_error, message_id, _error}, state) do state.ui.show_send_failure(message_id) {:noreply, state} end end
Terminals default to operation in "cooked" mode • This reads lines of input at a time and more • It's possible to switch to "raw" mode, say by shelling out to `stty` • In raw mode, you can read a character at a time • This allows keeping track of what has been entered so you can clear the screen and rerender as messages arrive
:wxEvtHandler.connect(gui.window, :close_window) :wxEvtHandler.connect(gui.input, :key_down, skip: true) :wxEvtHandler.connect(gui.button, :command_button_clicked) gu i end end
:wxEvtHandler.connect(gui.window, :close_window) :wxEvtHandler.connect(gui.input, :key_down, skip: true) :wxEvtHandler.connect(gui.button, :command_button_clicked) gu i end end
:wxEvtHandler.connect(gui.window, :close_window) :wxEvtHandler.connect(gui.input, :key_down, skip: true) :wxEvtHandler.connect(gui.button, :command_button_clicked) gu i end end
{name, fi elds} -> Record.defrecordp(name, fi elds) end) def handle_info( wx( id: _id , obj: window , userData: _userData , event: wxClose(type: :close_window) ) , %__MODULE__{window: window} = stat e ) do quit(state) {:noreply, state} end defp quit(state) do ConnectionManager.reset() :wxWindow.destroy(state.window) :wx.destroy() System.stop(0) end end
{name, fi elds} -> Record.defrecordp(name, fi elds) end) def handle_info( wx( id: _id , obj: window , userData: _userData , event: wxClose(type: :close_window) ) , %__MODULE__{window: window} = stat e ) do quit(state) {:noreply, state} end defp quit(state) do ConnectionManager.reset() :wxWindow.destroy(state.window) :wx.destroy() System.stop(0) end end
{name, fi elds} -> Record.defrecordp(name, fi elds) end) def handle_info( wx( id: _id , obj: window , userData: _userData , event: wxClose(type: :close_window) ) , %__MODULE__{window: window} = stat e ) do quit(state) {:noreply, state} end defp quit(state) do ConnectionManager.reset() :wxWindow.destroy(state.window) :wx.destroy() System.stop(0) end end
{name, fi elds} -> Record.defrecordp(name, fi elds) end) def handle_info( wx( id: _id , obj: window , userData: _userData , event: wxClose(type: :close_window) ) , %__MODULE__{window: window} = stat e ) do quit(state) {:noreply, state} end defp quit(state) do ConnectionManager.reset() :wxWindow.destroy(state.window) :wx.destroy() System.stop(0) end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
= case state.input |> :wxTextCtrl.getValue() |> to_string() do "/" <> command - > process_command(command, state) state.active_send s message when byte_size(message) > 0 - > case ConnectionManager.send_to_all(message) do {ref, name} - > append_message(name, message, state) now = System.monotonic_time(:second) Map.put(state.active_sends, ref, {message, now}) nil - > state.active_send s end "" - > state.active_send s end :wxTextCtrl.clear(state.input) new_active_send s end end
state) do case Regex.named_captures(~r{\A\s+(?<port>\d+)\s+(?<name>\S.*)\z}, args) do %{"port" => port, "name" => name} - > case ConnectionManager.listen(String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Listening\n", state.italic) _error - > append_text_with_font( state.chat , "Error: listening failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /listen PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures(~r{\A\s+(?<port>\d+)\s+(?<name>\S.*)\z}, args) do %{"port" => port, "name" => name} - > case ConnectionManager.listen(String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Listening\n", state.italic) _error - > append_text_with_font( state.chat , "Error: listening failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /listen PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures(~r{\A\s+(?<port>\d+)\s+(?<name>\S.*)\z}, args) do %{"port" => port, "name" => name} - > case ConnectionManager.listen(String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Listening\n", state.italic) _error - > append_text_with_font( state.chat , "Error: listening failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /listen PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures(~r{\A\s+(?<port>\d+)\s+(?<name>\S.*)\z}, args) do %{"port" => port, "name" => name} - > case ConnectionManager.listen(String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Listening\n", state.italic) _error - > append_text_with_font( state.chat , "Error: listening failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /listen PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures( ~r{\A\s+(?<host>\S+)\s+(?<port>\d+)\s+(?<name>\S.*)\z} , arg s ) do %{"host" => host, "port" => port, "name" => name} - > case ConnectionManager.connect(host, String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Connected\n", state.italic) _error - > append_text_with_font( state.chat , "Error: connecting failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /connect HOST PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures( ~r{\A\s+(?<host>\S+)\s+(?<port>\d+)\s+(?<name>\S.*)\z} , arg s ) do %{"host" => host, "port" => port, "name" => name} - > case ConnectionManager.connect(host, String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Connected\n", state.italic) _error - > append_text_with_font( state.chat , "Error: connecting failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /connect HOST PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures( ~r{\A\s+(?<host>\S+)\s+(?<port>\d+)\s+(?<name>\S.*)\z} , arg s ) do %{"host" => host, "port" => port, "name" => name} - > case ConnectionManager.connect(host, String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Connected\n", state.italic) _error - > append_text_with_font( state.chat , "Error: connecting failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /connect HOST PORT NAME\n" , state.itali c ) end end end
state) do case Regex.named_captures( ~r{\A\s+(?<host>\S+)\s+(?<port>\d+)\s+(?<name>\S.*)\z} , arg s ) do %{"host" => host, "port" => port, "name" => name} - > case ConnectionManager.connect(host, String.to_integer(port), name) do :ok - > append_text_with_font(state.chat, "Connected\n", state.italic) _error - > append_text_with_font( state.chat , "Error: connecting failed\n" , state.itali c ) end nil - > append_text_with_font( state.chat , "Usage: /connect HOST PORT NAME\n" , state.itali c ) end end end
{:show_send_failure, ref}) end def handle_cast({:show_send_failure, ref}, state) do case Map.pop(state.active_sends, ref) do {{message, _timestamp}, new_active_sends} - > append_text_with_font( state.chat , "The following message was not received by all participants: " < > "#{message}\n" , state.itali c ) {:noreply, %__MODULE__{state | active_sends: new_active_sends}} {nil, _active_sends} - > {:noreply, state} end end end
{:show_send_failure, ref}) end def handle_cast({:show_send_failure, ref}, state) do case Map.pop(state.active_sends, ref) do {{message, _timestamp}, new_active_sends} - > append_text_with_font( state.chat , "The following message was not received by all participants: " < > "#{message}\n" , state.itali c ) {:noreply, %__MODULE__{state | active_sends: new_active_sends}} {nil, _active_sends} - > {:noreply, state} end end end
{:show_send_failure, ref}) end def handle_cast({:show_send_failure, ref}, state) do case Map.pop(state.active_sends, ref) do {{message, _timestamp}, new_active_sends} - > append_text_with_font( state.chat , "The following message was not received by all participants: " < > "#{message}\n" , state.itali c ) {:noreply, %__MODULE__{state | active_sends: new_active_sends}} {nil, _active_sends} - > {:noreply, state} end end end
GenServer.cast(__MODULE__, {:show_chat_message, name, content}) end def handle_cast({:show_chat_message, name, action}, state) when is_atom(action) do append_text_with_font(state.chat, "#{name} #{action}\n", state.italic) {:noreply, state} end def handle_cast({:show_chat_message, name, content}, state) do append_message(name, content, state) {:noreply, state} end end
GenServer.cast(__MODULE__, {:show_chat_message, name, content}) end def handle_cast({:show_chat_message, name, action}, state) when is_atom(action) do append_text_with_font(state.chat, "#{name} #{action}\n", state.italic) {:noreply, state} end def handle_cast({:show_chat_message, name, content}, state) do append_message(name, content, state) {:noreply, state} end end
GenServer.cast(__MODULE__, {:show_chat_message, name, content}) end def handle_cast({:show_chat_message, name, action}, state) when is_atom(action) do append_text_with_font(state.chat, "#{name} #{action}\n", state.italic) {:noreply, state} end def handle_cast({:show_chat_message, name, content}, state) do append_message(name, content, state) {:noreply, state} end end
GenServer.cast(__MODULE__, {:show_chat_message, name, content}) end def handle_cast({:show_chat_message, name, action}, state) when is_atom(action) do append_text_with_font(state.chat, "#{name} #{action}\n", state.italic) {:noreply, state} end def handle_cast({:show_chat_message, name, content}, state) do append_message(name, content, state) {:noreply, state} end end