Commit | Line | Data |
---|---|---|
982ec720 | 1 | -module(life_cell). |
d2a0e2f9 SK |
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 | ||
29199e7e | 18 | -record(state, {cell_id :: integer() |
d2a0e2f9 SK |
19 | ,name :: string() |
20 | ,cell_state :: 0 | 1 | |
21 | ,neighbors :: list(atom()) | |
22 | ,live_neighbors :: integer() | |
23 | ,num_neighbors :: integer() | |
24 | ,replies_pending :: integer() | |
0a9b8c17 | 25 | ,gen_id :: integer() |
b699b8a4 | 26 | ,early_msgs :: list() |
d2a0e2f9 SK |
27 | }). |
28 | ||
29 | ||
30 | %% ============================================================================ | |
31 | %% API | |
32 | %% ============================================================================ | |
33 | ||
29199e7e | 34 | start_link({_, Name, _}=Datum) -> |
d2a0e2f9 SK |
35 | ServerName = {local, Name}, |
36 | Args = [Datum], | |
37 | Opts = [], | |
38 | gen_server:start_link(ServerName, ?MODULE, Args, Opts). | |
39 | ||
40 | ||
90456d08 SK |
41 | %% ============================================================================ |
42 | %% Callbacks (unused) | |
43 | %% ============================================================================ | |
44 | ||
45 | handle_call(_Msg, _From, State) -> {reply, ok, State}. | |
46 | handle_info(_Msg, State) -> {noreply, State}. | |
47 | code_change(_Old, State, _Other) -> {ok, State}. | |
48 | terminate(_Reason, State) -> {ok, State}. | |
49 | ||
50 | ||
d2a0e2f9 SK |
51 | %% ============================================================================ |
52 | %% Callbacks | |
53 | %% ============================================================================ | |
54 | ||
29199e7e SK |
55 | init([{CellID, Name, NeighborNames}]) -> |
56 | State = #state{cell_id = CellID | |
0a9b8c17 SK |
57 | ,name = Name |
58 | ,cell_state = crypto:rand_uniform(0, 2) | |
59 | ,neighbors = NeighborNames | |
60 | ,num_neighbors = length(NeighborNames) | |
61 | ,live_neighbors = 0 | |
62 | ,replies_pending = 0 | |
b699b8a4 | 63 | ,early_msgs = [] |
d2a0e2f9 SK |
64 | }, |
65 | {ok, State}. | |
66 | ||
67 | ||
0a9b8c17 | 68 | handle_cast({next_gen, GenID}, |
b699b8a4 SK |
69 | #state{name=Name |
70 | ,cell_state=CellState | |
d2a0e2f9 SK |
71 | ,neighbors=Neighbors |
72 | ,num_neighbors=NumNeighbors | |
b699b8a4 | 73 | ,early_msgs=EarlyMsgs |
d2a0e2f9 | 74 | }=State) -> |
29199e7e | 75 | |
79a2bc14 | 76 | ok = life_lib:cast_one2all(Neighbors, {state_broadcast, GenID, CellState}), |
b699b8a4 SK |
77 | |
78 | % Put stashed messages back in the mailbox, | |
79 | % now that we're ready to handle them | |
79a2bc14 | 80 | ok = life_lib:cast_all2one(Name, EarlyMsgs), |
b699b8a4 SK |
81 | |
82 | NewState = State#state{replies_pending=NumNeighbors | |
83 | ,gen_id=GenID | |
84 | ,early_msgs=[] | |
85 | }, | |
86 | ||
87 | {noreply, NewState}; | |
d2a0e2f9 SK |
88 | |
89 | ||
b699b8a4 SK |
90 | %% If we receive 'state_broadcast' before we receive 'next_gen', |
91 | %% stash it until we do. | |
92 | %% | |
93 | %% Took me a while to realize this, but sometimes it is possible. The more | |
94 | %% there're cells, the more likely this is to happen. | |
95 | %% | |
ad4f7e77 | 96 | handle_cast({state_broadcast, ReceivedGenID, _NeighborState}=Msg, |
b699b8a4 SK |
97 | #state{gen_id=GenID |
98 | ,early_msgs=EarlyMsgs | |
99 | }=State) when GenID =/= ReceivedGenID -> | |
100 | ||
101 | {noreply, State#state{early_msgs=[Msg|EarlyMsgs]}}; | |
d2a0e2f9 | 102 | |
ad4f7e77 | 103 | |
29199e7e SK |
104 | %% Now that we can be sure that this request is for the current generation, we |
105 | %% can handle it | |
ad4f7e77 | 106 | handle_cast({state_broadcast, GenID, NeighborState}, |
29199e7e SK |
107 | #state{cell_id=CellID |
108 | ,gen_id=GenID | |
d2a0e2f9 SK |
109 | ,replies_pending=Pending |
110 | ,cell_state=CellState | |
111 | ,live_neighbors=LiveNeighbors | |
112 | }=State) -> | |
113 | ||
114 | NewPending = Pending - 1, | |
115 | NewLiveNeighbors = LiveNeighbors + NeighborState, | |
116 | ||
117 | NewState = State#state{replies_pending=NewPending | |
118 | ,live_neighbors=NewLiveNeighbors | |
119 | }, | |
120 | ||
121 | case NewPending of | |
122 | 0 -> | |
123 | NewCellState = new_state(CellState, NewLiveNeighbors), | |
29199e7e | 124 | ok = life_time:report_state(CellID, GenID, NewCellState), |
d2a0e2f9 SK |
125 | |
126 | {noreply, NewState#state{live_neighbors=0 | |
127 | ,cell_state=NewCellState | |
172421cb SK |
128 | } |
129 | }; | |
d2a0e2f9 SK |
130 | |
131 | _N -> | |
132 | {noreply, NewState} | |
133 | end; | |
134 | ||
ad4f7e77 | 135 | |
172421cb SK |
136 | handle_cast(_Msg, State) -> |
137 | {noreply, State}. | |
d2a0e2f9 SK |
138 | |
139 | ||
d2a0e2f9 SK |
140 | %% ============================================================================ |
141 | %% Internal | |
142 | %% ============================================================================ | |
143 | ||
d2a0e2f9 SK |
144 | new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0; |
145 | new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1; | |
146 | new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0; | |
147 | new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1; | |
148 | new_state(State, _LiveNeighbors) -> State. |