Change gen_fsm state to a function in a different

2019-08-16 00:46发布

问题:

We have a fairly large USSD application that uses Erlang's gen_fsm module to manage the menu options.

The current version has a single menus_fsm.erl file that contains 5000+ lines gen_fsm related code. Our next version gives us an opportunity to split menus_fsm.erl into separate files to make it more maintainable in the future.

In the old version, to display the help menu we do the following (help_menu/1 gets called from code not shown that displays the main menu):

-module(menus_fsm).    
% Snipped some irrelvant code

help_menu(StateData) ->
    % Display the first menu
    send_menu(StateData, "Please Select:\n1. Option 1\n2. Option 2"),
    {next_state, waitHelpMenuChoice, StateData, ?MENU_TOUT};

waitHelpMenuChoice(Params, StateData) ->
    io:format("Got Help menu response: ~p", [Params]),
    doTerminate(ok,"Help Menu", StateData).

I've left out a lot of code that shows the entry point into the FSM and so on.

In the new version, we'd want to move help_menu/1 and waitHelpMenuChoice/2 to a new module help_menu, which gets called from menus_fsm, like so:

-module( help_menu ).    
% Snipped some irrelevant code

help_menu(StateData) ->
    menus_fsm:send_menu(StateData, "Please Select:\n1. Option 1\n2. Option 2"),
    {next_state, waitHelpMenuChoice, StateData, ?MENU_TOUT};

waitHelpMenuChoice(Params, StateData) ->
    io:format("Got Help menu response: ~p", [Params]),
    menus_fsm:doTerminate(ok,"Help Menu", StateData).

The problem is with the line {next_state, waitHelpMenuChoice, StateData, ?MENU_TOUT};: gen_fsm expects the waitHelpMenuChoice to be in the module menus_fsm which takes me back to where we started.

I've tried to replace the problematic line with

{next_state, fun help_menu:waitHelpMenuChoice/2, StateData, ?MENU_TOUT};

but that just leas to an error like the following: {badarg,[{erlang,apply,[conv_fsm,#Fun<help_menu.waitHelpMenuChoice.2>,[]]}

Does anyone have any suggestions of how to get around this?

回答1:

Maybe you could use http://www.erlang.org/doc/man/gen_fsm.html#enter_loop-6 to do that? Not sure if it would work to call it inside of another fsm, but it might be worth a try.



回答2:

I managed to find a solution to my own question. If this seems obvious, it could be because I'm a bit new to Erlang.

I added a new function wait_for_menu_response/2 to module menus_fsm that handles state transitions on behalf of the other modules.

-module(menus_fsm),
-export([wait_for_menu_response/2]).
% ...snip...
wait_for_menu_response(Params, {Function, StateData}) ->
    Function(Params, StateData).

Then the help_menu module was changed as follows:

-module( help_menu ).    
% ...snip...

help_menu(StateData) ->
    menus_fsm:send_menu(StateData, "Please Select:\n1. Option 1\n2. Option 2"),
    {next_state, wait_for_menu_response, {fun waitHelpMenuChoice/2, StateData}, ?MENU_TOUT}.

waitHelpMenuChoice(Params, StateData) ->
    io:format("Got Help menu response: ~p", [Params]),
    menus_fsm:doTerminate(ok,"Help Menu", StateData).

so gen_fsm stays within the menus_fsm module when it invokes wait_for_menu_response, but wait_for_menu_response is now free to invoke help_menu:waitHelpMenuChoice/2. help_menu:waitHelpMenuChoice/2 did not need to be modified in any way.

Actually, in my final version, the menus_fsm:send_menu function was modified to accept the fun waitHelpMenuChoice/2 as its third parameter, so that the help_menu function simply becomes:

help_menu(StateData) ->
    menus_fsm:send_menu(StateData, "Please Select:\n1. Option 1\n2. Option 2", 
        fun waitHelpMenuChoice/2).

but I think my explanation above illustrates the idea better.