Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
kizzx2 committed Jan 2, 2012
0 parents commit 3b32f7c
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
*.beam
5 changes: 5 additions & 0 deletions ebin/client-alice.erl
@@ -0,0 +1,5 @@
#!/usr/bin/env escript
%%! -sname client-alice

main([]) ->
c_client_ui:main({127, 0, 0, 1}, "Alice").
5 changes: 5 additions & 0 deletions ebin/client-david.erl
@@ -0,0 +1,5 @@
#!/usr/bin/env escript
%%! -sname client-david

main([]) ->
c_client_ui:main({127, 0, 0, 1}, "David").
6 changes: 6 additions & 0 deletions ebin/server.erl
@@ -0,0 +1,6 @@
#!/usr/bin/env escript
%%! -sname server
main([]) ->
c_server_udp:start_link(),
io:format("Server is serving...~n"),
timer:sleep(infinity).
87 changes: 87 additions & 0 deletions src/c_client.erl
@@ -0,0 +1,87 @@
-module(c_client).
-behavior(gen_server).

-export([ code_change/3
, handle_call/3
, handle_cast/2
, handle_info/2
, init/1
, terminate/2
]).

-export([ start_link/0
, show/2
, change_name/2
, connect/2
, disconnect/1
, speak/2
]).

-record(state, {name, server}).

% API

start_link() ->
gen_server:start_link(?MODULE, [], []).

show(ClientPid, Line) ->
gen_server:cast(ClientPid, {show, Line}).

change_name(ClientPid, NewName) ->
gen_server:call(ClientPid, {change_name, NewName}).

connect(ClientPid, ServerPid) ->
gen_server:call(ClientPid, {connect, ServerPid}).

disconnect(ClientPid) ->
gen_server:call(ClientPid, disconnect).

speak(ClientPid, Msg) ->
gen_server:call(ClientPid, {speak, Msg}).

% Controls

init([]) -> {ok, #state{}}.

% User input -> Client

handle_call({connect, ServerPid}, _From, State) ->
{reply,
c_server:connect(ServerPid, "Anonymous"),
State#state{server = ServerPid}};

handle_call({speak, Msg}, _From, #state{server = Server} = State) ->
{reply,
c_server:speak(Server, Msg),
State};

handle_call({cmd, Cmd, Args}, _From, #state{server = Server} = State) ->
{reply,
c_server:cmd(Server, Cmd, Args),
State};

handle_call({change_name, NewName}, _From, #state{server = Server} = State) ->
{reply,
c_server:change_name(Server, NewName), State};

handle_call(disconnect, _From, #state{server = Server} = State) ->
c_server:disconnect(Server),
{stop, shutdown, State}.

% Server -> Client

handle_cast({show, Line}, State) ->
io:format("~s~n", [Line]),
{noreply, State}.

% Misc.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

25 changes: 25 additions & 0 deletions src/c_client_udp.erl
@@ -0,0 +1,25 @@
-module(c_client_udp).

-export([ connect/2
, start_link/0
, show/2
, change_name/2
, disconnect/1
, speak/2
]).

-define(REEXPORT_0(M, F), F() -> M:F()).
-define(REEXPORT_1(M, F), F(A0) -> M:F(A0)).
-define(REEXPORT_2(M, F), F(A0, A1) -> M:F(A0, A1)).

% API

connect(ClientPid, ServerAddr) ->
{ok, ServerPid} = c_server_udp:get_pid(ServerAddr),
c_client:connect(ClientPid, ServerPid).

?REEXPORT_0(c_client, start_link).
?REEXPORT_2(c_client, show).
?REEXPORT_2(c_client, change_name).
?REEXPORT_1(c_client, disconnect).
?REEXPORT_2(c_client, speak).
22 changes: 22 additions & 0 deletions src/c_client_ui.erl
@@ -0,0 +1,22 @@
-module(c_client_ui).
-export([main/2]).

get_line(Name) ->
io:format("~s> ", [Name]),
io:get_line("").

main(ServerAddr, Name) ->
{ok, Client} = c_client_udp:start_link(),
c_client_udp:connect(Client, ServerAddr),
c_client_udp:change_name(Client, Name),
loop(Client, Name).

loop(Client, Name) ->
Msg = string:strip(get_line(Name), right, $\n),
case Msg of
"" -> loop(Client, Name);
"/disconnect" -> c_client_udp:disconnect(Client);
_ ->
c_client_udp:speak(Client, Msg),
loop(Client, Name)
end.
103 changes: 103 additions & 0 deletions src/c_server.erl
@@ -0,0 +1,103 @@
-module(c_server).
-behavior(gen_server).

-export([ code_change/3
, handle_call/3
, handle_cast/2
, handle_info/2
, init/1
, terminate/2
]).

-export([ start_link/0
, connect/2
, disconnect/1
, cmd/3
, speak/2
, change_name/2
]).

-record(state, {clients}).

% API

start_link() ->
gen_server:start_link(?MODULE, [], []).

connect(ServerPid, ClientName) ->
gen_server:call(ServerPid, {connect, ClientName}).

disconnect(ServerPid) ->
gen_server:call(ServerPid, disconnect).

cmd(ServerPid, Cmd, Args) ->
gen_server:call(ServerPid, {cmd, Cmd, Args}).

speak(ServerPid, Msg) ->
gen_server:cast(ServerPid, {speak, self(), Msg}).

broadcast(ServerPid, Msg) ->
gen_server:cast(ServerPid, {broadcast, Msg}).

change_name(ServerPid, NewName) ->
gen_server:call(ServerPid, {change_name, NewName}).

% Controls

init([]) ->
{ok, #state{clients = dict:new()}}.

% Client -> Server

handle_call({connect, ClientName}, {ClientPid, _Tag}, #state{clients = Clients} = State) ->
io:format("~s has joined the room~n", [ClientName]),
broadcast(self(), ClientName ++ " has joined the room"),
{reply, ok, State#state{clients = dict:append(ClientPid, ClientName, Clients)}};

handle_call(disconnect, {ClientPid, _Tag}, #state{clients = Clients} = State) ->
{ok, Name} = dict:find(ClientPid, Clients),
io:format("~s has left the room~n", [Name]),
{reply, ok, State#state{clients = dict:erase(ClientPid, Clients)}};

handle_call({change_name, NewName}, {ClientPid, _Tag}, #state{clients = Clients} = State) ->
{ok, Name} = dict:find(ClientPid, Clients),
io:format("~s changes name to ~s~n", [Name, NewName]),
broadcast(self(), Name ++ " changes name to " ++ NewName),
{reply, ok, State#state{clients = dict:store(ClientPid, NewName, Clients)}};

handle_call({cmd, Cmd, _Args}, {_ClientPid, _Tag}, State) ->
Reply = case Cmd of
_Unknown -> {error, unknown_command}
end,
{reply, Reply, State}.

handle_cast({broadcast, Msg}, #state{clients = Clients} = State) ->
do_broadcast(Msg, Clients),
{noreply, State};

handle_cast({speak, ClientPid, Msg}, #state{clients = Clients} = State) ->
{ok, Name} = dict:find(ClientPid, Clients),
io:format("~s: ~s~n", [Name, Msg]),
Line = io_lib:format("~s: ~s", [Name, Msg]),
do_broadcast(Line, Clients, [ClientPid]),
{noreply, State}.

% Misc.

handle_info(_Info, State) ->
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

% Internal

do_broadcast(Line, Clients) ->
do_broadcast(Line, Clients, []).

do_broadcast(Line, Clients, Excludes) ->
[ c_client:show(Client, Line)
|| Client <- lists:subtract(dict:fetch_keys(Clients), Excludes) ].
1 change: 1 addition & 0 deletions src/c_server_protocol.hrl
@@ -0,0 +1 @@
-define(SERVER_PORT_NUMBER, 13337).
65 changes: 65 additions & 0 deletions src/c_server_udp.erl
@@ -0,0 +1,65 @@
-module(c_server_udp).
-behavior(gen_server).

-include("c_server_protocol.hrl").

-export([ code_change/3
, handle_call/3
, handle_cast/2
, handle_info/2
, init/1
, terminate/2
]).

-export([ start_link/0
, stop/1
, get_pid/1
]).

-record(state, {port, server_core}).

% API

start_link() ->
gen_server:start_link(?MODULE, [], []).

stop(ServerPid) ->
gen_server:call(ServerPid, stop).

get_pid(ServerAddr) ->
{ok, SendPort} = gen_udp:open(0, [binary]),
Ref = make_ref(),
Packet = term_to_binary({get_pid, {self(), Ref}}),
gen_udp:send(SendPort, ServerAddr, ?SERVER_PORT_NUMBER, Packet),
receive
{set_pid, ServerPid, Ref} -> {ok, ServerPid};
_Unk -> error(bad_response)
end.

% gen_server

init([]) ->
{ok, ServerCore} = c_server:start_link(),
{ok, #state
{ port = gen_udp:open(?SERVER_PORT_NUMBER, [binary])
, server_core = ServerCore
}}.

handle_call(stop, _From, State) ->
{stop, shutdown, State}.

handle_cast(_Req, _State) ->
error(invalid_cast).

handle_info({udp, _Socket, _IP, _InPortNo, Packet}, #state{server_core = ServerCorePid} = State) ->
case binary_to_term(Packet) of
{get_pid, {ClientPid, Ref} } -> ClientPid ! {set_pid, ServerCorePid, Ref};
_Unk -> error(bad_udp)
end,
{noreply, State}.

terminate(_Reason, _State) ->
ok.

code_change(_OldVsn, State, _Extra) ->
{ok, State}.

0 comments on commit 3b32f7c

Please sign in to comment.