How to keep state between comet and events

26 views
Skip to first unread message

J. Pablo Fernández

unread,
Jan 2, 2010, 10:09:34 AM1/2/10
to Nitrogen Web Framework for Erlang
Hello,

I'm writing a chat web application, similar like Omegle, for which
Erlang and Nitrogen are the best tools. They almost seem designed for
it.

I have an abstraction that is room. A room is a process that whenever
it receives a messages sends it to everybody and handles processes
joining and leaving. The web application needs to send and receive
process to/from it.

Receiving actually was quite trivial, it's the same as the sample,
only a little bit more complex:

event(start_chat) ->
wf:comet(fun() -> chat_client() end);

chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client().

chat_client() ->
receive
{message, Pid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p: ~s", [Pid, Message]) }]),
wf:comet_flush();
end,
chat_client().

The function chat client is run like a comet process, it gets a room,
joins it and from then on puts on the browser every message received.

Now the problem is sending messages. Sending a message works like
this:

room:send_message(Room, Message)

and it should come from the same process that subscribe itself. I have
an event to send messages:

event(send_message) ->
% where is the room?

and there I have a problem, because I don't have the room at that
moment or any way to get ahold of it, and even then, this is another
process. My end solution ended up being quite complex and it's like
this:

chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client(Room).

chat_client(Room) ->
receive
{send_message, Message} ->
room:send_message(Room, Message);
{leave_room} ->
wf:insert_bottom(messages, [#p{}, #span { text="-------" }]),
wf:comet_flush(),
room:leave(Room);
{message, Pid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p: ~s", [Pid, Message]) }]),
wf:comet_flush();
end,
chat_client(Room).

the chat_client function which is run as a comet process no longer is
a receiver, now it's also a sender. Since that's the process that was
registered with the room and contains the room reference it has to do
both things. This looks ugly and unclean to me, what do you think?

But I still have a problem, event(send_message) doesn't know about it.
That means that event(start_chat) has to keep a reference and pass it
around, which means it's now this:

event(start_chat) ->
Client = wf:comet(fun() -> chat_client() end),
wf:wire(send_message, #event { type=click, postback={send_message,
Client} });

It puts a reference to Client in the send_message button, sending it
in the postback, which allows me to write:

event({send_message, Client}) ->
Message = wf:q(message),
Client ! {send_message, Message};

So, I'm passing state through the postback property of a button. That
looks extremely ugly. What do you think?

Is this good style or not? I haven't coded in Erlang in quite a while
and I've never did a web app with it, so I'm not sure if I'm following
patterns or antipatterns here.

Thanks.

PS: Well, the whole code is much more complex. I have many message
types coming and going and I also have to pass the client to the
start_chat button so that it terminates a previous client before
starting the new one:

-module (web_index).
-include_lib ("nitrogen/include/wf.inc").
-compile(export_all).

main() ->
#template { file="./wwwroot/template.html"}.

body() ->
Body=[
#button { id=start_chat, text="Start chatting",
postback=start_chat},
#p{},
#panel { id=messages, class=messages },
#p{},
#textbox { id=message, next=send_message },
#button { id=send_message, text="Send" }
],

wf:render(Body).

event({start_chat, Client}) ->
Client ! {leave_room},
exit(Client, starting_another_chat),
event(start_chat);
event(start_chat) ->
say("start_chat"),
Client = wf:comet(fun() -> chat_client() end),
wf:wire(send_message, #event { type=click, postback={send_message,
Client} }),
wf:wire(start_chat, #event { type=click, postback={start_chat,
Client} });
event({send_message, Client}) ->
Message = wf:q(message),
say("sending message '~s' through client ~p", [Message, Client]),
Client ! {send_message, Message};
event(_Oops) ->
io:format("Unexpected event ~p~n", [_Oops]).

chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client(Room).

chat_client(Room) ->
Self = self(),
receive
{send_message, Message} ->
room:send_message(Room, Message);
{leave_room} ->
wf:insert_bottom(messages, [#p{}, #span { text="-------" }]),
wf:comet_flush(),
room:leave(Room);
{joined, _RemotePid} ->
wf:insert_bottom(messages, [#p{}, #span { text="You are now
talking to a stranger! Say hi!" }]),
wf:comet_flush();
{message, Self, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("You: ~s", [Message]) }]),
wf:comet_flush();
{message, _RemotePid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("Stranger: ~s", [Message]) }]),
wf:comet_flush();
Message ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p", [Message]) }]),
wf:comet_flush()
end,
chat_client(Room).

say(Format) ->
say(Format, []).
say(Format, Data) ->
io:format("~p:~p: ~s~n", [?MODULE, self(), io_lib:format(Format,
Data)]).

J. Pablo Fernández

unread,
Jan 2, 2010, 5:01:49 PM1/2/10
to Nitrogen Web Framework for Erlang
Can I trust that the events are going to always be run in the same
process? At some point I think I've seen them run in different
processes (for the same page/browser/window of course), but I can't
reproduce it. Because if that's the case, then I suppose I could just
use get and put?

J. Pablo Fernández

unread,
Jan 3, 2010, 5:12:01 AM1/3/10
to Nitrogen Web Framework for Erlang
I reposted this question in Stack Overflow trying to simply it a
little bit:
http://stackoverflow.com/questions/1994490/how-to-keep-track-of-a-process-per-browser-window-and-access-it-at-each-event-in

On Jan 2, 4:09 pm, J. Pablo Fernández <pup...@pupeno.com> wrote:

J. Pablo Fernández

unread,
Jan 3, 2010, 5:12:33 AM1/3/10
to Nitrogen Web Framework for Erlang
Reply all
Reply to author
Forward
0 new messages