Stashing early msgs instead of immediately resending.
[cellular-automata.git] / 001 / src / life_cell.erl
1 -module(life_cell).
2 -behaviour(gen_server).
3
4
5 %% API
6 -export([start_link/1]).
7
8 %% Callbacks
9 -export([init/1
10 ,handle_call/3
11 ,handle_cast/2
12 ,handle_info/2
13 ,terminate/2
14 ,code_change/3
15 ]).
16
17
18 -record(state, {cell_id :: integer()
19 ,name :: string()
20 ,cell_state :: 0 | 1
21 ,neighbors :: list(atom())
22 ,live_neighbors :: integer()
23 ,num_neighbors :: integer()
24 ,replies_pending :: integer()
25 ,gen_id :: integer()
26 ,early_msgs :: list()
27 }).
28
29
30 %% ============================================================================
31 %% API
32 %% ============================================================================
33
34 start_link({_, Name, _}=Datum) ->
35 ServerName = {local, Name},
36 Args = [Datum],
37 Opts = [],
38 gen_server:start_link(ServerName, ?MODULE, Args, Opts).
39
40
41 %% ============================================================================
42 %% Callbacks
43 %% ============================================================================
44
45 init([{CellID, Name, NeighborNames}]) ->
46 State = #state{cell_id = CellID
47 ,name = Name
48 ,cell_state = crypto:rand_uniform(0, 2)
49 ,neighbors = NeighborNames
50 ,num_neighbors = length(NeighborNames)
51 ,live_neighbors = 0
52 ,replies_pending = 0
53 ,early_msgs = []
54 },
55 {ok, State}.
56
57
58 terminate(_Reason, State) ->
59 {ok, State}.
60
61
62 code_change(_Old, State, _Other) ->
63 {ok, State}.
64
65
66 handle_call(_Msg, _From, State) ->
67 {reply, ok, State}.
68
69
70 handle_cast({next_gen, GenID},
71 #state{name=Name
72 ,cell_state=CellState
73 ,neighbors=Neighbors
74 ,num_neighbors=NumNeighbors
75 ,early_msgs=EarlyMsgs
76 }=State) ->
77
78 ok = cast_all(Neighbors, {state_broadcast, GenID, CellState}),
79
80 % Put stashed messages back in the mailbox,
81 % now that we're ready to handle them
82 ok = cast_to(Name, EarlyMsgs),
83
84 NewState = State#state{replies_pending=NumNeighbors
85 ,gen_id=GenID
86 ,early_msgs=[]
87 },
88
89 {noreply, NewState};
90
91
92 %% If we receive 'state_broadcast' before we receive 'next_gen',
93 %% stash it until we do.
94 %%
95 %% Took me a while to realize this, but sometimes it is possible. The more
96 %% there're cells, the more likely this is to happen.
97 %%
98 handle_cast({state_broadcast, ReceivedGenID, _NeighborState}=Msg,
99 #state{gen_id=GenID
100 ,early_msgs=EarlyMsgs
101 }=State) when GenID =/= ReceivedGenID ->
102
103 {noreply, State#state{early_msgs=[Msg|EarlyMsgs]}};
104
105
106 %% Now that we can be sure that this request is for the current generation, we
107 %% can handle it
108 handle_cast({state_broadcast, GenID, NeighborState},
109 #state{cell_id=CellID
110 ,gen_id=GenID
111 ,replies_pending=Pending
112 ,cell_state=CellState
113 ,live_neighbors=LiveNeighbors
114 }=State) ->
115
116 NewPending = Pending - 1,
117 NewLiveNeighbors = LiveNeighbors + NeighborState,
118
119 NewState = State#state{replies_pending=NewPending
120 ,live_neighbors=NewLiveNeighbors
121 },
122
123 case NewPending of
124 0 ->
125 NewCellState = new_state(CellState, NewLiveNeighbors),
126 ok = life_time:report_state(CellID, GenID, NewCellState),
127
128 {noreply, NewState#state{live_neighbors=0
129 ,cell_state=NewCellState
130 }
131 };
132
133 _N ->
134 {noreply, NewState}
135 end;
136
137
138 handle_cast(_Msg, State) ->
139 {noreply, State}.
140
141
142 handle_info(_Msg, State) ->
143 {noreply, State}.
144
145
146 %% ============================================================================
147 %% Internal
148 %% ============================================================================
149
150 % Cast different messages to a single destination
151 cast_to(_, []) -> ok;
152 cast_to(Server, [Msg | Msgs]) ->
153 ok = gen_server:cast(Server, Msg),
154 cast_to(Server, Msgs).
155
156
157 % Cast the same message to multiple destinations
158 cast_all([], _) -> ok;
159 cast_all([Server | Servers], Msg) ->
160 ok = gen_server:cast(Server, Msg),
161 cast_all(Servers, Msg).
162
163
164 new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0;
165 new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1;
166 new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0;
167 new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1;
168 new_state(State, _LiveNeighbors) -> State.
This page took 0.108363 seconds and 5 git commands to generate.