caparol6991

trade fsm

Dec 6th, 2019
164
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -module(trade_fsm).
  2. -behaviour(gen_fsm).
  3.  
  4. %% public API
  5. -export([start/1, start_link/1, trade/2, accept_trade/1,
  6. make_offer/2, retract_offer/2, ready/1, cancel/1]).
  7. %% gen_fsm callbacks
  8. -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3,
  9. terminate/3, code_change/4,
  10. % custom state names
  11. idle/2, idle/3, idle_wait/2, idle_wait/3, negotiate/2,
  12. negotiate/3, wait/2, ready/2, ready/3]).
  13.  
  14. %%% PUBLIC API
  15. start(Name) ->
  16. gen_fsm:start(?MODULE, [Name], []).
  17.  
  18. start_link(Name) ->
  19. gen_fsm:start_link(?MODULE, [Name], []).
  20.  
  21. %% ask for a begin session. Returns when/if the other accepts
  22. trade(OwnPid, OtherPid) ->
  23. gen_fsm:sync_send_event(OwnPid, {negotiate, OtherPid}, 30000).
  24.  
  25. %% Accept someone's trade offer.
  26. accept_trade(OwnPid) ->
  27. gen_fsm:sync_send_event(OwnPid, accept_negotiate).
  28.  
  29. %% Send an item on the table to be traded
  30. make_offer(OwnPid, Item) ->
  31. gen_fsm:send_event(OwnPid, {make_offer, Item}).
  32.  
  33. %% Cancel trade offer
  34. retract_offer(OwnPid, Item) ->
  35. gen_fsm:send_event(OwnPid, {retract_offer, Item}).
  36.  
  37. %% Mention that you're ready for a trade. When the other
  38. %% player also declares being ready, the trade is done
  39. ready(OwnPid) ->
  40. gen_fsm:sync_send_event(OwnPid, ready, infinity).
  41.  
  42. %% Cancel the transaction.
  43. cancel(OwnPid) ->
  44. gen_fsm:sync_send_all_state_event(OwnPid, cancel).
  45.  
  46. %% Ask the other FSM's Pid for a trade session
  47. ask_negotiate(OtherPid, OwnPid) ->
  48. gen_fsm:send_event(OtherPid, {ask_negotiate, OwnPid}).
  49.  
  50. %% Forward the client message accepting the transaction
  51. accept_negotiate(OtherPid, OwnPid) ->
  52. gen_fsm:send_event(OtherPid, {accept_negotiate, OwnPid}).
  53.  
  54. %% forward a client's offer
  55. do_offer(OtherPid, Item) ->
  56. gen_fsm:send_event(OtherPid, {do_offer, Item}).
  57.  
  58. %% forward a client's offer cancellation
  59. undo_offer(OtherPid, Item) ->
  60. gen_fsm:send_event(OtherPid, {undo_offer, Item}).
  61.  
  62. %% Ask the other side if he's ready to trade.
  63. are_you_ready(OtherPid) ->
  64. gen_fsm:send_event(OtherPid, are_you_ready).
  65.  
  66. %% Reply that the side is not ready to trade
  67. %% i.e. is not in 'wait' state.
  68. not_yet(OtherPid) ->
  69. gen_fsm:send_event(OtherPid, not_yet).
  70.  
  71. %% Tells the other fsm that the user is currently waiting
  72. %% for the ready state. State should transition to 'ready'
  73. am_ready(OtherPid) ->
  74. gen_fsm:send_event(OtherPid, 'ready!').
  75.  
  76. %% Acknowledge that the fsm is in a ready state.
  77. ack_trans(OtherPid) ->
  78. gen_fsm:send_event(OtherPid, ack).
  79.  
  80. %% ask if ready to commit
  81. ask_commit(OtherPid) ->
  82. gen_fsm:sync_send_event(OtherPid, ask_commit).
  83.  
  84. %% begin the synchronous commit
  85. do_commit(OtherPid) ->
  86. gen_fsm:sync_send_event(OtherPid, do_commit).
  87.  
  88. notify_cancel(OtherPid) ->
  89. gen_fsm:send_all_state_event(OtherPid, cancel).
  90.  
  91. -record(state, {name="",
  92. other,
  93. ownitems=[],
  94. otheritems=[],
  95. monitor,
  96. from}).
  97.  
  98. init(Name) ->
  99. {ok, idle, #state{name=Name}}.
  100.  
  101. %% Send players a notice. This could be messages to their clients
  102. %% but for our purposes, outputting to the shell is enough.
  103. notice(#state{name=N}, Str, Args) ->
  104. io:format("~s: "++Str++"~n", [N|Args]).
  105.  
  106. %% Unexpected allows to log unexpected messages
  107. unexpected(Msg, State) ->
  108. io:format("~p received unknown event ~p while in state ~p~n",
  109. [self(), Msg, State]).
  110.  
  111. idle({ask_negotiate, OtherPid}, S=#state{}) ->
  112. Ref = monitor(process, OtherPid),
  113. notice(S, "~p asked for a trade negotiation", [OtherPid]),
  114. {next_state, idle_wait, S#state{other=OtherPid, monitor=Ref}};
  115. idle(Event, Data) ->
  116. unexpected(Event, idle),
  117. {next_state, idle, Data}.
  118.  
  119. idle({negotiate, OtherPid}, From, S=#state{}) ->
  120. ask_negotiate(OtherPid, self()),
  121. notice(S, "asking user ~p for a trade", [OtherPid]),
  122. Ref = monitor(process, OtherPid),
  123. {next_state, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
  124. idle(Event, _From, Data) ->
  125. unexpected(Event, idle),
  126. {next_state, idle, Data}.
  127.  
  128. idle_wait({ask_negotiate, OtherPid}, S=#state{other=OtherPid}) ->
  129. gen_fsm:reply(S#state.from, ok),
  130. notice(S, "starting negotiation", []),
  131. {next_state, negotiate, S};
  132. %% The other side has accepted our offer. Move to negotiate state
  133. idle_wait({accept_negotiate, OtherPid}, S=#state{other=OtherPid}) ->
  134. gen_fsm:reply(S#state.from, ok),
  135. notice(S, "starting negotiation", []),
  136. {next_state, negotiate, S};
  137. idle_wait(Event, Data) ->
  138. unexpected(Event, idle_wait),
  139. {next_state, idle_wait, Data}.
  140.  
  141. idle_wait(accept_negotiate, _From, S=#state{other=OtherPid}) ->
  142. accept_negotiate(OtherPid, self()),
  143. notice(S, "accepting negotiation", []),
  144. {reply, ok, negotiate, S};
  145. idle_wait(Event, _From, Data) ->
  146. unexpected(Event, idle_wait),
  147. {next_state, idle_wait, Data}.
  148.  
  149. %% adds an item to an item list
  150. add(Item, Items) ->
  151. [Item | Items].
  152.  
  153. %% remove an item from an item list
  154. remove(Item, Items) ->
  155. Items -- [Item].
  156.  
  157. negotiate({make_offer, Item}, S=#state{ownitems=OwnItems}) ->
  158. do_offer(S#state.other, Item),
  159. notice(S, "offering ~p", [Item]),
  160. {next_state, negotiate, S#state{ownitems=add(Item, OwnItems)}};
  161. %% Own side retracting an item offer
  162. negotiate({retract_offer, Item}, S=#state{ownitems=OwnItems}) ->
  163. undo_offer(S#state.other, Item),
  164. notice(S, "cancelling offer on ~p", [Item]),
  165. {next_state, negotiate, S#state{ownitems=remove(Item, OwnItems)}};
  166. %% other side offering an item
  167. negotiate({do_offer, Item}, S=#state{otheritems=OtherItems}) ->
  168. notice(S, "other player offering ~p", [Item]),
  169. {next_state, negotiate, S#state{otheritems=add(Item, OtherItems)}};
  170. %% other side retracting an item offer
  171. negotiate({undo_offer, Item}, S=#state{otheritems=OtherItems}) ->
  172. notice(S, "Other player cancelling offer on ~p", [Item]),
  173. {next_state, negotiate, S#state{otheritems=remove(Item, OtherItems)}};
  174.  
  175. negotiate(are_you_ready, S=#state{other=OtherPid}) ->
  176. io:format("Other user ready to trade.~n"),
  177. notice(S,
  178. "Other user ready to transfer goods:~n"
  179. "You get ~p, The other side gets ~p",
  180. [S#state.otheritems, S#state.ownitems]),
  181. not_yet(OtherPid),
  182. {next_state, negotiate, S};
  183. negotiate(Event, Data) ->
  184. unexpected(Event, negotiate),
  185. {next_state, negotiate, Data}.
  186.  
  187. negotiate(ready, From, S = #state{other=OtherPid}) ->
  188. are_you_ready(OtherPid),
  189. notice(S, "asking if ready, waiting", []),
  190. {next_state, wait, S#state{from=From}};
  191. negotiate(Event, _From, S) ->
  192. unexpected(Event, negotiate),
  193. {next_state, negotiate, S}.
  194.  
  195. wait({do_offer, Item}, S=#state{otheritems=OtherItems}) ->
  196. gen_fsm:reply(S#state.from, offer_changed),
  197. notice(S, "other side offering ~p", [Item]),
  198. {next_state, negotiate, S#state{otheritems=add(Item, OtherItems)}};
  199. wait({undo_offer, Item}, S=#state{otheritems=OtherItems}) ->
  200. gen_fsm:reply(S#state.from, offer_changed),
  201. notice(S, "Other side cancelling offer of ~p", [Item]),
  202. {next_state, negotiate, S#state{otheritems=remove(Item, OtherItems)}};
  203.  
  204. wait(are_you_ready, S=#state{}) ->
  205. am_ready(S#state.other),
  206. notice(S, "asked if ready, and I am. Waiting for same reply", []),
  207. {next_state, wait, S};
  208.  
  209. wait(not_yet, S = #state{}) ->
  210. notice(S, "Other not ready yet", []),
  211. {next_state, wait, S};
  212.  
  213. wait('ready!', S=#state{}) ->
  214. am_ready(S#state.other),
  215. ack_trans(S#state.other),
  216. gen_fsm:reply(S#state.from, ok),
  217. notice(S, "other side is ready. Moving to ready state", []),
  218. {next_state, ready, S};
  219. %% DOn't care about these!
  220. wait(Event, Data) ->
  221. unexpected(Event, wait),
  222. {next_state, wait, Data}.
  223.  
  224. priority(OwnPid, OtherPid) when OwnPid > OtherPid -> true;
  225. priority(OwnPid, OtherPid) when OwnPid < OtherPid -> false.
  226.  
  227. ready(ack, S=#state{}) ->
  228. case priority(self(), S#state.other) of
  229. true ->
  230. try
  231. notice(S, "asking for commit", []),
  232. ready_commit = ask_commit(S#state.other),
  233. notice(S, "ordering commit", []),
  234. ok = do_commit(S#state.other),
  235. notice(S, "committing...", []),
  236. commit(S),
  237. {stop, normal, S}
  238. catch Class:Reason ->
  239. %% abort! Either ready_commit or do_commit failed
  240. notice(S, "commit failed", []),
  241. {stop, {Class, Reason}, S}
  242. end;
  243. false ->
  244. {next_state, ready, S}
  245. end;
  246. ready(Event, Data) ->
  247. unexpected(Event, ready),
  248. {next_state, ready, Data}.
  249.  
  250. ready(ask_commit, _From, S) ->
  251. notice(S, "replying to ask_commit", []),
  252. {reply, ready_commit, ready, S};
  253.  
  254. ready(do_commit, _From, S) ->
  255. notice(S, "committing...", []),
  256. commit(S),
  257. {stop, normal, ok, S};
  258. ready(Event, _From, Data) ->
  259. unexpected(Event, ready),
  260. {next_state, ready, Data}.
  261.  
  262.  
  263. commit(S = #state{}) ->
  264. io:format("Transaction completed for ~s. "
  265. "Items sent are:~n~p,~n received are:~n~p.~n"
  266. "This operation should have some atomic save "
  267. "in a database.~n",
  268. [S#state.name, S#state.ownitems, S#state.otheritems]).
  269.  
  270. %% The other player has sent this cancel event
  271. %% stop whatever we're doing and shut down!
  272. handle_event(cancel, _StateName, S=#state{}) ->
  273. notice(S, "received cancel event", []),
  274. {stop, other_cancelled, S};
  275. handle_event(Event, StateName, Data) ->
  276. unexpected(Event, StateName),
  277. {next_state, StateName, Data}.
  278.  
  279. %% This cancel event comes from the client. We must warn the other
  280. %% player that we have a quitter!
  281. handle_sync_event(cancel, _From, _StateName, S = #state{}) ->
  282. notify_cancel(S#state.other),
  283. notice(S, "cancelling trade, sending cancel event", []),
  284. {stop, cancelled, ok, S};
  285. %% Note: DO NOT reply to unexpected calls. Let the call-maker crash!
  286. handle_sync_event(Event, _From, StateName, Data) ->
  287. unexpected(Event, StateName),
  288. {next_state, StateName, Data}.
  289.  
  290. handle_info({'DOWN', Ref, process, Pid, Reason}, _, S=#state{other=Pid, monitor=Ref}) ->
  291. notice(S, "Other side dead", []),
  292. {stop, {other_down, Reason}, S};
  293. handle_info(Info, StateName, Data) ->
  294. unexpected(Info, StateName),
  295. {next_state, StateName, Data}.
  296.  
  297. code_change(_OldVsn, StateName, Data, _Extra) ->
  298. {ok, StateName, Data}.
  299.  
  300. %% Transaction completed.
  301. terminate(normal, ready, S=#state{}) ->
  302. notice(S, "FSM leaving.", []);
  303. terminate(_Reason, _StateName, _StateData) ->
  304. ok.
RAW Paste Data