Related
I am a bit struggling with extracting fields from a binary message. Raw message looks like the following:
<<1,0,97,98,99,100,0,0,0,3,0,0,0,0,0,0,0,0,0,3,32,3,0,0,88,2,0,0>>
I know the order, type and static sizes of fields, some have arbitary sizes thought, so I am trying to do something like the following:
newobj(Data) ->
io:fwrite("NewObj RAW ~p~n",[Data]),
NewObj = {obj,rest(uint16(string(uint16({[],Data},id),type),parent),unparsed)},
io:fwrite("NewObj ~p~n",[NewObj]),
NewObj.
uint16/2, string/2, and rest/2 are actually extraction functions and look like this:
uint16(ListData, Name) ->
{List, Data} = ListData,
case Data of
<<Int:2/little-unsigned-unit:8, Rest/binary>> ->
{List ++ [{Name,Int}], Rest};
<<Int:2/little-unsigned-unit:8>> ->
List ++ [{Name,Int}]
end.
string(ListData, Name) ->
{List, Data} = ListData,
Split = binary:split(Data,<<0>>),
String = lists:nth(1, Split),
if
length(Split) == 2 ->
{List ++ [{Name, String}], lists:nth(2, Split)};
true ->
List ++ [{Name, String}]
end.
rest(ListData, Name) ->
{List, Data} = ListData,
List ++ [{Name, Data}].
This works and looks like:
NewObj RAW <<1,0,97,98,99,100,0,0,0,3,0,0,0,0,0,0,0,0,0,3,32,3,0,0,88,2,0,0>>
NewObj {obj,[{id,1},
{type,<<"abcd">>},
{parent,0},
{unparsed,<<3,0,0,0,0,0,0,0,0,0,3,32,3,0,0,88,2,0,0>>}]}
The reason for this question though is that passing {List, Data} as ListData and then splitting it within the function with {List, Data} = ListData feels clumsy - so is there a better way? I think I can't use static matching because "unparsed" and "type" parts are of arbitary length, so it's not possible to define their respective sizes.
Thanks!
---------------Update-----------------
Trying to take comments below into account - code now looks like the following:
newobj(Data) ->
io:fwrite("NewObj RAW ~p~n",[Data]),
NewObj = {obj,field(
field(
field({[], Data},id,fun uint16/1),
type, fun string/1),
unparsed,fun rest/1)},
io:fwrite("NewObj ~p~n",[NewObj]).
field({List, Data}, Name, Func) ->
{Value,Size} = Func(Data),
case Data of
<<_:Size/binary-unit:8>> ->
[{Name,Value}|List];
<<_:Size/binary-unit:8, Rest/binary>> ->
{[{Name,Value}|List], Rest}
end.
uint16(Data) ->
case Data of
<<UInt16:2/little-unsigned-unit:8, _/binary>> ->
{UInt16,2};
<<UInt16:2/little-unsigned-unit:8>> ->
{UInt16,2}
end.
string(Data) ->
Split = binary:split(Data,<<0>>),
case Split of
[String, Rest] ->
{String,byte_size(String)+1};
[String] ->
{String,byte_size(String)+1}
end.
rest(Data) ->
{Data,byte_size(Data)}.
The code is non idiomatic and some pieces cannot compile as is :-) Here are some comments:
The newobj/1 function makes a reference to a NewObj variable that is unbound. Probably the real code is something like NewObj = {obj,rest(... ?
The code uses list append (++) multiple times. This should be avoided if possible because it performs too much memory copies. The idiomatic way is to add to the head of the list as many times as needed (that is: L2 = [NewThing | L1]) and call lists:reverse/1 at the very end. See any Erlang book or the free Learn Yourself some Erlang for the details.
In a similar vein, lists:nth/2 should be avoided and replaced by pattern matching or a different way to construct the list or parse the binary
Dogbert's suggestion about doing the pattern matching directly in the function argument is a good idiomatic approach and allows to remove some lines from the code.
As last suggestion regarding the approach to debug, consider replacing the fwrite functions with proper unit tests.
Hope this gives some hints for what to look at. Feel free to append to your question the code changes, we can proceed from there.
EDIT
It's looking better. Let's see if we can simplify. Please note that we are doing the work backwards, because we are adding tests after the production code has been written, instead of doing test-driven development.
Step 1: add test.
I also reversed the order of the list because it looks more natural.
-include_lib("eunit/include/eunit.hrl").
happy_input_test() ->
Rest = <<3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 32, 3, 0, 0, 88, 2, 0, 0>>,
Input = <<1, 0,
97, 98, 99, 100, 0,
0, 0,
Rest/binary>>,
Expected = {obj, [{id, 1}, {type, <<"abcd">>}, {parent, 0}, {unparsed, Rest}]},
?assertEqual(Expected, binparse:newobj(Input)).
We can run this, among other ways, with rebar3 eunit (see the rebar3 documentation; I suggest to start with rebar3 new lib mylib to create a skeleton).
Step 2: the absolute minimum
Your description is not enough to understand which fields are mandatory and which are optional and whether there is always something more after the obj.
In the simplest possible case, all your code can be reduced to:
newobj(Bin) ->
<<Id:16/little-unsigned, Rest/binary>> = Bin,
[Type, Rest2] = binary:split(Rest, <<0>>),
<<Parent:16/little-unsigned, Rest3/binary>> = Rest2,
{obj, [{id, Id}, {type, Type}, {parent, Parent}, {unparsed, Rest3}]}.
Quite compact :-)
I find the encoding of the string very bizarre: a binary encoding where the string is NUL-terminated (so forces to walk the binary) instead of being encoded with, say, 2 or 4 bytes to represent the length and then the string itself.
Step 3: input validation
Since we are parsing a binary, this is probably coming from the outside of our system. As such, the let it crash philosophy doesn't apply and we have to perform full input validation.
I make the assumption that all fields are mandatory except unparsed, that can be empty.
missing_unparsed_is_ok_test() ->
Input = <<1, 0,
97, 98, 99, 100, 0,
0, 0>>,
Expected = {obj, [{id, 1}, {type, <<"abcd">>}, {parent, 0}, {unparsed, <<>>}]},
?assertEqual(Expected, binparse:newobj(Input)).
The simple implementation above passes it.
Step 4: malformed parent
We add the tests and we make a API decision: the function will return an error tuple.
missing_parent_is_error_test() ->
Input = <<1, 0,
97, 98, 99, 100, 0>>,
?assertEqual({error, bad_parent}, binparse:newobj(Input)).
malformed_parent_is_error_test() ->
Input = <<1, 0,
97, 98, 99, 100, 0,
0>>,
?assertEqual({error, bad_parent}, binparse:newobj(Input)).
We change the implementation to pass the tests:
newobj(Bin) ->
<<Id:16/little-unsigned, Rest/binary>> = Bin,
[Type, Rest2] = binary:split(Rest, <<0>>),
case Rest2 of
<<Parent:16/little-unsigned, Rest3/binary>> ->
{obj, [{id, Id}, {type, Type}, {parent, Parent}, {unparsed, Rest3}]};
Rest2 ->
{error, bad_parent}
end.
Step 5: malformed type
The new tests:
missing_type_is_error_test() ->
Input = <<1, 0>>,
?assertEqual({error, bad_type}, binparse:newobj(Input)).
malformed_type_is_error_test() ->
Input = <<1, 0,
97, 98, 99, 100>>,
?assertEqual({error, bad_type}, binparse:newobj(Input)).
We could be tempted to change the implementation as follows:
newobj(Bin) ->
<<Id:16/little-unsigned, Rest/binary>> = Bin,
case binary:split(Rest, <<0>>) of
[Type, Rest2] ->
case Rest2 of
<<Parent:16/little-unsigned, Rest3/binary>> ->
{obj, [
{id, Id}, {type, Type},
{parent, Parent}, {unparsed, Rest3}
]};
Rest2 ->
{error, bad_parent}
end;
[Rest] -> {error, bad_type}
end.
Which is an unreadable mess. Just adding functions doesn't help us:
newobj(Bin) ->
<<Id:16/little-unsigned, Rest/binary>> = Bin,
case parse_type(Rest) of
{ok, {Type, Rest2}} ->
case parse_parent(Rest2) of
{ok, Parent, Rest3} ->
{obj, [
{id, Id}, {type, Type},
{parent, Parent}, {unparsed, Rest3}
]};
{error, Reason} -> {error, Reason}
end;
{error, Reason} -> {error, Reason}
end.
parse_type(Bin) ->
case binary:split(Bin, <<0>>) of
[Type, Rest] -> {ok, {Type, Rest}};
[Bin] -> {error, bad_type}
end.
parse_parent(Bin) ->
case Bin of
<<Parent:16/little-unsigned, Rest/binary>> -> {ok, Parent, Rest};
Bin -> {error, bad_parent}
end.
This is a classic problem in Erlang with nested conditionals.
Step 6: regaining sanity
Here is my approach, quite generic so applicable (I think) to many domains. The overall idea is taken from backtracking, as explained in http://rvirding.blogspot.com/2009/03/backtracking-in-erlang-part-1-control.html
We create one function per parse step and pass them, as a list, to call_while_ok/3:
newobj(Bin) ->
Parsers = [fun parse_id/1,
fun parse_type/1,
fun parse_parent/1,
fun(X) -> {ok, {unparsed, X}, <<>>} end
],
case call_while_ok(Parsers, Bin, []) of
{error, Reason} -> {error, Reason};
PropList -> {obj, PropList}
end.
Function call_while_ok/3 is somehow related to lists:foldl and lists:filter:
call_while_ok([F], Seed, Acc) ->
case F(Seed) of
{ok, Value, _NextSeed} -> lists:reverse([Value | Acc]);
{error, Reason} -> {error, Reason}
end;
call_while_ok([F | Fs], Seed, Acc) ->
case F(Seed) of
{ok, Value, NextSeed} -> call_while_ok(Fs, NextSeed, [Value | Acc]);
{error, Reason} -> {error, Reason}
end.
And here are the parsing functions. Note that their signature is always the same:
parse_id(Bin) ->
<<Id:16/little-unsigned, Rest/binary>> = Bin,
{ok, {id, Id}, Rest}.
parse_type(Bin) ->
case binary:split(Bin, <<0>>) of
[Type, Rest] -> {ok, {type, Type}, Rest};
[Bin] -> {error, bad_type}
end.
parse_parent(Bin) ->
case Bin of
<<Parent:16/little-unsigned, Rest/binary>> ->
{ok, {parent, Parent}, Rest};
Bin -> {error, bad_parent}
end.
Step 7: homework
The list [{id, 1}, {type, <<"abcd">>}, {parent, 0}, {unparsed, Rest}] is a proplist (see Erlang documentation), which predates Erlang maps.
Have a look at the documentation for maps and see if it makes sense to return a map instead.
How do you start a child with simple_one_for_one supervisor?
I'm going through the LYSE book and currently on the Dynamic Supervision section: http://learnyousomeerlang.com/supervisors#dynamic-supervision
The author starts the child as follows, but I have no idea where djembe is defined:
1> supervisor:start_child(band_supervisor, [djembe, good]).
Musician Janet Tennelli, playing the djembe entered the room
{ok,<0.690.0>}
2> supervisor:start_child(band_supervisor, [djembe, good]).
{error,{already_started,<0.690.0>}}
Here is my attempt:
2> supervisor:start_link(band_supervisor, jamband).
{ok,<0.40.0>}
3> supervisor:start_child(band_supervisor, [djembe, good]).
=ERROR REPORT==== 28-Feb-2016::03:52:56 ===
** Generic server <0.40.0> terminating
** Last message in was {'EXIT',<0.33.0>,
{{noproc,
{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}},
[{gen_server,call,3,
[{file,"gen_server.erl"},{line,212}]},
{erl_eval,do_apply,6,
[{file,"erl_eval.erl"},{line,673}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,
[{file,"shell.erl"},{line,641}]},
{shell,eval_loop,3,
[{file,"shell.erl"},{line,626}]}]}}
** When Server state == {state,
{<0.40.0>,band_supervisor},
simple_one_for_one,
[{child,undefined,jam_musician,
{musicians,start_link,[]},
temporary,1000,worker,
[musicians]}],
undefined,3,60,[],band_supervisor,jamband}
** Reason for termination ==
** {{noproc,{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}},
[{gen_server,call,3,[{file,"gen_server.erl"},{line,212}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,673}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}
** exception exit: {noproc,{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}}
in function gen_server:call/3 (gen_server.erl, line 212)
Here is how the supervisor looks:
-module(band_supervisor).
-behaviour(supervisor).
-export([start_link/1]).
-export([init/1]).
start_link(Type) ->
supervisor:start_link({local,?MODULE}, ?MODULE, Type).
%% The band supervisor will allow its band members to make a few
%% mistakes before shutting down all operations, based on what
%% mood he's in. A lenient supervisor will tolerate more mistakes
%% than an angry supervisor, who'll tolerate more than a
%% complete jerk supervisor
init(lenient) ->
init({one_for_one, 3, 60});
init(angry) ->
init({rest_for_one, 2, 60});
init(jerk) ->
init({one_for_all, 1, 60});
init(jamband) ->
{ok, {{simple_one_for_one, 3, 60},
[{jam_musician,
{musicians, start_link, []},
temporary, 1000, worker, [musicians]}
]}};
init({RestartStrategy, MaxRestart, MaxTime}) ->
{ok, {{RestartStrategy, MaxRestart, MaxTime},
[{singer,
{musicians, start_link, [singer, good]},
permanent, 1000, worker, [musicians]},
{bass,
{musicians, start_link, [bass, good]},
temporary, 1000, worker, [musicians]},
{drum,
{musicians, start_link, [drum, bad]},
transient, 1000, worker, [musicians]},
{keytar,
{musicians, start_link, [keytar, good]},
transient, 1000, worker, [musicians]}
]}}.
here are the musicians:
-module(musicians).
-behaviour(gen_server).
-export([start_link/2, stop/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-record(state, {name="", role, skill=good}).
-define(DELAY, 750).
start_link(Role, Skill) ->
gen_server:start_link({local, Role}, ?MODULE, [Role, Skill], []).
stop(Role) -> gen_server:call(Role, stop).
init([Role, Skill]) ->
%% To know when the parent shuts down
process_flag(trap_exit, true),
%% sets a seed for random number generation for the life of the process
%% uses the current time to do it. Unique value guaranteed by now()
random:seed(now()),
TimeToPlay = random:uniform(3000),
Name = pick_name(),
StrRole = atom_to_list(Role),
io:format("Musician ~s, playing the ~s entered the room~n",
[Name, StrRole]),
{ok, #state{name=Name, role=StrRole, skill=Skill}, TimeToPlay}.
handle_call(stop, _From, S=#state{}) ->
{stop, normal, ok, S};
handle_call(_Message, _From, S) ->
{noreply, S, ?DELAY}.
handle_cast(_Message, S) ->
{noreply, S, ?DELAY}.
handle_info(timeout, S = #state{name=N, skill=good}) ->
io:format("~s produced sound!~n",[N]),
{noreply, S, ?DELAY};
handle_info(timeout, S = #state{name=N, skill=bad}) ->
case random:uniform(5) of
1 ->
io:format("~s played a false note. Uh oh~n",[N]),
{stop, bad_note, S};
_ ->
io:format("~s produced sound!~n",[N]),
{noreply, S, ?DELAY}
end;
handle_info(_Message, S) ->
{noreply, S, ?DELAY}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(normal, S) ->
io:format("~s left the room (~s)~n",[S#state.name, S#state.role]);
terminate(bad_note, S) ->
io:format("~s sucks! kicked that member out of the band! (~s)~n",
[S#state.name, S#state.role]);
terminate(shutdown, S) ->
io:format("The manager is mad and fired the whole band! "
"~s just got back to playing in the subway~n",
[S#state.name]);
terminate(_Reason, S) ->
io:format("~s has been kicked out (~s)~n", [S#state.name, S#state.role]).
%% Yes, the names are based off the magic school bus characters
%% 10 names!
pick_name() ->
%% the seed must be set for the random functions. Use within the
%% process that started with init/1
lists:nth(random:uniform(10), firstnames())
++ " " ++
lists:nth(random:uniform(10), lastnames()).
firstnames() ->
["Valerie", "Arnold", "Carlos", "Dorothy", "Keesha",
"Phoebe", "Ralphie", "Tim", "Wanda", "Janet"].
lastnames() ->
["Frizzle", "Perlstein", "Ramon", "Ann", "Franklin",
"Terese", "Tennelli", "Jamal", "Li", "Perlstein"].
learnyousomeerlang.com is a good source of information but sometimes I find it too elaborate. I learned that part of OTP directly by reading the supervisor OTP documentation. You probably don't need the gen_server if you just want to understand dynamic children. See my simple implementation:
-module(estp_proj_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1, add/2]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
init([]) ->
Child = ?WORKER(estp_project),
{ok, {{simple_one_for_one, 3, 30}, [Child]}}.
add(Name, Cfg) ->
Server = estp_project:server_name(Name),
State = [{name, Name}|Cfg],
case whereis(Server) of
undefined ->
add_child(Server, State);
Pid ->
delete_child(Server, Pid),
add_child(Server, State)
end.
add_child(Server, State) ->
supervisor:start_child(?SERVER, [Server, State]).
delete_child(Server, Pid) ->
ok = supervisor:terminate_child(?SERVER, Pid).
where the dynamic child name estp_project:server_name/1 is created like this:
server_name(Name) ->
Module = atom_to_binary(?MODULE, utf8),
Binary = atom_to_binary(Name, utf8),
binary_to_atom(<<Module/binary, <<"$">>/binary, Binary/binary>>, utf8).
and worker is defined as:
-define(SHUTDOWN_TIMEOUT, 5000).
-define(WORKER(I), {I, {I, start_link, []}, permanent, ?SHUTDOWN_TIMEOUT, worker, [I]}).
You then add children simply by calling estp_proj_sup:add(Name, Cfg) like in this code:
process(Res) ->
%% Res contains parsed list of project names and their configurations
[set_project(Name, Cfg) || {Name, {ok, Cfg}} <- Res].
set_project(Name, Cfg) ->
case estp_proj_sup:add(Name, Cfg) of
{ok, _Pid} -> ok;
{error, _} = Err -> Err
end.
Anyways, I tried your example and it seems to be working:
4> {ok, S} = band_supervisor:start_link(jamband).
{ok,<0.47.0>}
5> supervisor:start_child(band_supervisor, [djembe, good]).
Musician Wanda Terese, playing the djembe entered the room
{ok,<0.49.0>}
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
6> supervisor:start_child(band_supervisor, [djembe, good]).
{error,{already_started,<0.49.0>}}
Wanda Terese produced sound!
Wanda Terese produced sound!
I'm trying to modify the functionality in my teacher's module for saving tweets from Twitter's Streaming API. The problem is that if I keep the stream open for more than a minute, the API starts sending blank lines to the stream in order to verify its connection.
From what I've understood, the tweets are broken down and then reassembled, but this becomes a problem with the last function in the code below, pop_size.
Pop_size expects the argument to start with a text-representation of a number. So when the blank lines are sent, represented by <<"\r\n">>, the stream crashes with this message:
Error in process <0.118.0> with exit value: {function_clause,
[{twitterminer_source,pop_size,[<<2 bytes>>],[{file,"src/twitterminer_source.erl"}
If I add the line below, does anyone have any suggestions what I should try executing in it?
pop_size(<<"\r\n">>) -> %%Code here!
To clarify: I want the blank line to be disregarded and the loop to continue checking for the next tweets. I'm quite over my head here, but I'll try to answer any follow-up questions as elaborate as I can.
Code: (Three functions)
% Get HTTP chunks and reassemble them into chunks that we get
% as a result of specifying delimited=length.
% https://dev.twitter.com/streaming/overview/processing
split_loop(Sink, Sender, Buffer) ->
case pop_size(Buffer) of
{size, N, Rest} ->
case buffer_pop_n(Rest, N, Sender) of
{pop, Chunk, NewBuf} ->
Sink ! {message, Chunk},
receive next -> ok end,
split_loop(Sink, Sender, NewBuf);
{incomplete, Chunk} -> Sink ! {error, {incomplete, Chunk}};
{terminate, _Chunk} -> Sink ! terminate;
{error, Reason, Chunk} -> Sink ! {error, {Reason, Chunk}}
end;
{more, L} ->
case buffer_pop_n(Buffer, L, Sender) of
{pop, Chunk, NewBuf} ->
split_loop(Sink, Sender, <<Chunk/binary, NewBuf/binary>>);
{incomplete, <<>>} -> Sink ! finished;
{incomplete, Chunk} -> Sink ! {error, {incomplete, Chunk}};
{terminate, _Chunk} -> Sink ! terminate;
{error, Reason, Chunk} -> Sink ! {error, {Reason, Chunk}}
end
end.
% Get a chunk of N bytes from the buffer. If there is not enough data
% in the buffer, get more messages from the pipeline.
buffer_pop_n(B, N, Sender) ->
if
byte_size(B) < N ->
Sender ! next,
receive
{message, Part} ->
Part2 = Part,
buffer_pop_n(<<B/binary, Part2/binary>>, N, Sender);
finished -> {incomplete, B};
terminate -> {terminate, B};
{error, Reason} -> {error, Reason, B}
end;
true -> {pop, binary:part(B, {0, N}), binary:part(B, {N, byte_size(B)-N})}
end.
% We should also support discarding \r\n here
% (see 'blank lines' in https://dev.twitter.com/streaming/overview/messages-types)
pop_size(<<>>) -> {more, 1};
pop_size(<<A,Rest/binary>>) when A >= $0, A =< $9 ->
pop_size((A - $0), 1, Rest);
pop_size(_N, L, <<>>) -> {more, L+1};
pop_size(_N, L, <<"\r">>) -> {more, L+2};
pop_size(N, L, <<A,Rest/binary>>) when A >= $0, A =< $9 ->
pop_size(N * 10 + (A - $0), L+1, Rest);
pop_size(N, _L, <<"\r\n",Rest/binary>>) -> {size, N, Rest}.
I have the following code using a gen_tcp socket, which should receive using {active, true} some binary commands that are always of size 5 (e.g. Command = <<"hello">>)
{ok, ListenSocket} = gen_tcp:listen(5555, [{active, true}, {mode, binary},
{packet, 0}]),
{ok, Socket} = gen_tcp:accept(ListenSocket),
loop().
receive() ->
{tcp, _Socket, Message:4/binary} ->
io:format("Success~n", []),
loop();
{tcp, _Socket, Other} ->
io:format("Received: ~p~n", [Other]),
loop()
end.
But if I try to connect as a client like this:
1> {ok, S} = gen_tcp:connect("localhost", 5555, []).
{ok,#Port<0.582>}
2> gen_tcp:send(S, <<"hello">>).
ok
I have in the receiving part:
Received: {tcp,#Port<0.585>,<<"hello">>}
so I suppose my error is in the pattern matching...but where?
Your receive clause doesn't look right, it should be:
{tcp, _Socket, <<Message:5/binary>>} ->
....
As far as I understood you need gen_tcp:recv function. From documentation:
recv(Socket, Length) -> {ok, Packet} | {error, Reason}
In your code it will be something like:
...
{ok, Socket} = gen_tcp:accept(ListenSocket),
loop(Socket).
loop(Socket) ->
case gen_tcp:recv(Socket, 5) of
{ok, Data} ->
io:format("Success~n", []),
loop(Socket);
{error, closed} ->
ok
end.
startTrains() ->
TotalDist = 100,
Trains = [trainA,trainB ],
PID = spawn(fun() ->
train(1,length(Trains)) end),
[ PID ! {self(),TrainData,TotalDist} || TrainData <- Trains],
receive
{_From, Mesg} ->
error_logger:info_msg("~n Mesg ~p ~n",[Mesg])
after 10500 ->
refresh
end.
so, I created Two Processes named trainA, trainB. I want to increment these process by 5 till it gets 100. I made different processes to make each of the train (process) increments its position parallely. But I was surprised to get the output sequentially i.e process trainA ends then process trainB starts. But I want to increment themselves at simultaneously.
I want to run processes like this
trainA 10 trainB 0
trainA 15 trainB 5
....
trainA 100 trainB 100
but I m getting
trainA 0
....
trainA 90
trainA 95
trainA 100
trainA ends
trainB 0
trainB 5
trainB 10
.....
trainB 100
How to make the processes run parallel/simultaneously? Hope you get my Q's. Please help me.
You spawn only one process initialized by function train/2. Your presented code is incomplete so I can only guess but I think your code is wrong because you have only one train process. For inspiration:
-module(trains).
-export([startTrains/0]).
startTrains() ->
TotalDist = 100,
Names = [trainA,trainB ],
Self = self(),
Trains = [spawn_link(fun() ->
train(Name, Self) end) || Name <- Names],
[ Train ! {start, Self, 0, TotalDist} || Train <- Trains],
ok = collectResults(Names).
collectResults([]) -> ok;
collectResults(Trains) ->
receive
{stop, Name, Pos, Length} ->
io:format("~p stops at ~p (~p)~n", [Name, Pos, Length]),
collectResults(Trains -- [Name]);
Msg ->
io:format("Supervisor received unexpected message ~p~n", [Msg]),
collectResults(Trains)
after 10500 -> timeout
end.
train(Name, Sup) ->
receive
{start, Sup, Pos, Length} -> run_train(Name, Sup, Pos, Length);
Msg ->
io:format("~p received unexpected message ~p~n", [Name, Msg]),
train(Name, Sup)
end.
run_train(Name, Sup, Pos, Length)
when Pos < Length ->
receive after 500 ->
NewPos = Pos + 5,
io:format("~p ~p~n", [Name, Pos]),
run_train(Name, Sup, NewPos, Length)
end;
run_train(Name, Sup, Pos, Length) ->
Sup ! {stop, Name, Pos, Length}.
But if I would think it seriously I should look to gen_fsm and OTP principles. But in your current stage keep play with erlang primitives to take better feeling first.