gen_fsm

httpd の source を見てたら Supervisor Behaviour を使っていて…Supervisor Behaviour を読んでいたら、supervision tree の考え方と実装方法を理解しなきゃ駄目っぽい気がしたので、OTP Design Principles の Overview に目を通してみた。

で、以前、gen_server で sample を作ったので、今回は、gen_fsm を試しに使ってみた。

source

-module(code_lock).
-behaviour(gen_fsm).

-export([start_link/1, stop/0]).
-export([button/1, get_state/0]).
-export([init/1, locked/2, open/2]).
-export([handle_event/3, terminate/3]).
-export([handle_sync_event/4, handle_info/3, code_change/4]).

start_link(Code) -> gen_fsm:start_link({local, code_lock}, code_lock, Code, []).

stop() -> gen_fsm:send_all_state_event(code_lock, stop).

button(String) -> gen_fsm:send_event(code_lock, {button, String}).

get_state() -> gen_fsm:sync_send_all_state_event(code_lock, get_state).

init(Code) -> {ok, locked, {[], Code}}.

locked({button, String}, {SoFar, Code}) ->
    case SoFar ++ String of
        Code ->
            io:fwrite("open!!~n"),
            {next_state, open, {[], Code}, 3000};
        Incomplete when length(Incomplete)<length(Code) ->
            {next_state, locked, {Incomplete, Code}};
        _Wrong ->
            {next_state, locked, {[], Code}}
    end.

open(timeout, StateData) ->
    io:fwrite("close!!~n"),
    {next_state, locked, StateData}.

handle_event(stop, _StateName, StateData) -> {stop, normal, StateData}.
terminate(normal, _StateName, _StateData) -> ok.

handle_sync_event(get_state, _From, StateName, StateData) ->
    {reply, StateName, StateName, StateData}.

handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}.

開始

start_link/1 で gen_fsm:start_link/4 を呼ぶ。
gen_fsm:start_link/4 は、gen_server:start_link/4 と説明が重複するので省略。
id:cooldaemon:20070625 参照の事。

init(Code) -> {ok, locked, {[], Code}}.

init/1 は、gen_fsm:start_link/4 から呼ばれる。
{ok, StateName, StateData} を返すようにする。

イベントを送る

button(String) -> gen_fsm:send_event(code_lock, {button, String}).

gen_fsm:send_event/2 は、StateName(Event, StateData) を非同期で呼ぶ。
同期で呼び出して戻り値を受け取りたい場合は、gen_fsm:sync_send_event/2 や gen_fsm:sync_send_event/3 を使う。

button イベントが発生した際、状態が locked であれば呼ばれる関数は、下記のように書く。

locked({button, String}, {SoFar, Code}) ->
    ...

戻り値として、{next_state, NewStateName, NewStateData} を返すと状態が NewStateName に変わる。
{next_state, NewStateName, NewStateData, MSec} を返すと、MSec ミリ秒後に状態が変わる。
その場合は、StateName(timeout, StateData) が呼ばれる。

状態に左右されないイベントを送る

非同期の場合は、gen_fsm:send_all_state_event/2 を使い、同期の場合は、gen_fsm:sync_send_all_state_event/2 を使う。

Supervision Tree 配下ではなく、Stand Alone で動作している場合は、gen_fsm:send_all_state_event/2 を利用して、stop を実装できる。

停止

stop() -> gen_fsm:send_all_state_event(code_lock, stop).
handle_event(stop, _StateName, StateData) -> {stop, normal, StateData}.
terminate(normal, _StateName, _StateData) -> ok.

gen_server の説明と重複するので省略。

その他

相変わらず突っ込み大歓迎。