I'm trying to run the chess timer example found in the wxErlang - Getting Started booklet written by Arif Ishaq in 2017

56 views Asked by At

In Arif Isaq booklet there is an example where the gen_server behaviour is replace with wx_object. I'm trying to run this example, but somehow or other the message I send to the processes

Eshell V13.1.4 (abort with ^G)

1> c(wo_player).

{ok,wo_player}

2> arbiter:start_link().

{ok,<0.93.0>}

3> gen_server:call(arbiter, {reset, 42}).

ok

4> gen_server:call(player1, move).

ok

do not start the timer.

I'm running the example on Debian buster

erlang@4diac:~$ pkg-config --modversion gtk+-3.0
3.24.5
erlang@4diac:~$ cat /usr/lib/erlang/releases/RELEASES
%% coding: utf-8
[{release,"Erlang/OTP","25","13.1.4",
          [{kernel,"8.5.3","/usr/lib/erlang/lib/kernel-8.5.3"},
           {stdlib,"4.2","/usr/lib/erlang/lib/stdlib-4.2"},
           {sasl,"4.2","/usr/lib/erlang/lib/sasl-4.2"}],
          permanent}].
erlang@4diac:~$ 

The wxWidgets are packaged into the gtk 3.24.5 version indicated above

Here are the Erlang modules for the example

-module(wo_arbiter).

-include_lib("wx/include/wx.hrl").
-behaviour(wx_object).

-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 
         handle_event/2, terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {}).

start_link() ->
    wx_object:start_link({local, ?SERVER}, ?MODULE, [], []).

init([]) ->
    wx:new(),
    %% Env = wx:get_env(),
    %% build and layout the GUI components
    Frame = wxFrame:new(wx:null(), 1, "Countdown"),
    Player1 = wo_player:start_link(player1, Frame, ?SERVER),
    Player2 = wo_player:start_link(player2, Frame, ?SERVER),
    %% Player1 = gen_server:call(player1, get_panel),
    %% Player2 = gen_server:call(player2, get_panel),

    MainSizer = wxBoxSizer:new(?wxHORIZONTAL),
    wxSizer:add(MainSizer, Player1, [{proportion, 1}, {flag, ?wxALL}, {border,5}]),
    wxSizer:add(MainSizer, Player2, [{proportion, 1}, {flag, ?wxALL}, {border,5}]),
    
    wxWindow:setSizer(Frame, MainSizer),
    wxSizer:setSizeHints(MainSizer, Frame),
    wxWindow:setMinSize(Frame, wxWindow:getSize(Frame)),
    
    wxFrame:connect(Frame, close_window),

    wxFrame:show(Frame),
    {Frame, #state{}}.

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({reset, N}, State) ->
    player1 ! {reset, N},
    player2 ! {reset, N},
    {noreply, State};

handle_info({moved, player1}, State) ->
    player2 ! move,
    {noreply, State};
handle_info({moved, player2}, State) ->
    player1 ! move,
    {noreply, State};

handle_info({ilose, player1}, State) ->
    player2 ! youwin,
    {noreply, State};
handle_info({ilose, player2}, State) ->
    player1 ! youwin,
    {noreply, State};

handle_info(Msg, State) ->
    io:format("frame got unexpected message ~p~n", [Msg]),
    {noreply, State}.

handle_event(#wx{event = #wxClose{}}, State) ->
    {stop, normal, State}.
    terminate(_Reason, _State) ->
    %% sys:terminate(player1, Reason),
    %% sys:terminate(player2, Reason),
    wx:destroy(),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
-module(wo_player).

-behaviour(wx_object).

-export([start_link/3]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         handle_event/2, terminate/2, code_change/3]).

-include_lib("wx/include/wx.hrl").

-record(state, {panel, counter, button, tref, whoami, arbiter}).

start_link(Name, Frame, Arbiter) ->
    wx_object:start_link({local, Name}, ?MODULE, [Name, Frame, Arbiter], []).

init([Name, Frame, Arbiter]) ->
    %% wx:set_env(Env),
    Panel = wxPanel:new(Frame),
    
    %% build and layout the GUI components
    Label = wxStaticText:new(Panel, ?wxID_ANY, "Seconds remaining", [{style, ?wxALIGN_RIGHT}]),
    wxStaticText:wrap(Label,100),
    Counter = wxTextCtrl:new(Panel, ?wxID_ANY, [{value, "42"}, {style, ?wxTE_RIGHT}]),
    Font = wxFont:new(42, ?wxFONTFAMILY_DEFAULT, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
    wxTextCtrl:setFont(Counter, Font),
    wxTextCtrl:setEditable(Counter, false),
    
    Button = wxButton:new(Panel, ?wxID_ANY, [{label, "Moved"}]),
    wxButton:disable(Button),
    
    CounterSizer = wxBoxSizer:new(?wxHORIZONTAL),
    wxSizer:add(CounterSizer, Label, [{flag, ?wxALL bor ?wxALIGN_CENTRE},{border, 5}]),
    wxSizer:add(CounterSizer, Counter, [{proportion,1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
    
    MainSizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(MainSizer, CounterSizer, [{flag, ?wxEXPAND}]),
    wxSizer:add(MainSizer, Button, [{flag, ?wxEXPAND bor ?wxALL}, {border,5}]),
    
    wxWindow:setSizer(Panel, MainSizer),
    wxSizer:setSizeHints(MainSizer, Panel),
    wxWindow:setMinSize(Panel, wxWindow:getSize(Panel)),
    
    wxButton:connect(Button, command_button_clicked),

    
    {Panel, #state{panel = Panel,
            counter = Counter,
            button = Button,
            whoami = Name,
            arbiter = Arbiter}}.

handle_call(get_panel, _From, #state{panel = Panel} = State) ->
    {reply, Panel, State};
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({reset,N}, #state{counter = Counter, button = Button} = State) ->
    wxTextCtrl:setValue(Counter, integer_to_list(N)),
    wxButton:disable(Button),
    {noreply, State};

handle_info(youwin, #state{counter = Counter} = State) ->
    wxTextCtrl:setValue(Counter, "win"),
    {noreply, State};

handle_info(move, #state{button = Button} = State) ->
    io:format("Got move event", []),
    wxButton:enable(Button),
    TRef = erlang:send_after(1000, self(), update_gui),
    {noreply, State#state{tref = TRef}};
handle_info(update_gui, #state{button = Button, counter = Counter, whoami = Name, arbiter = Arbiter} = State) ->
    Value = wxTextCtrl:getValue(Counter),
    case list_to_integer(Value) of
        1 ->
            wxTextCtrl:setValue(Counter, "0"),
            wxButton:disable(Button),
            Arbiter ! {ilose, Name},
            {noreply, State};
        N ->
            wxTextCtrl:setValue(Counter, integer_to_list(N-1)),
            TRef = erlang:send_after(1000, self(), update_gui),
            {noreply, State#state{tref = TRef}}
    end.

handle_event(#wx{obj = Button, event = #wxCommand{type = command_button_clicked}},
             #state{tref = TRef, whoami = Name, arbiter = Arbiter} = State) ->
    erlang:cancel_timer(TRef),
    wxButton:disable(Button),
    Arbiter ! {moved, Name},
    {noreply, State#state{tref = undefined}}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

Would the problem be related to the version of wxWidgets included in the gtk 3.24.5 version I installed as a package?

1

There are 1 answers

0
Alberto Perri On

My understanding is that the binding between wxErlang and wxWidgets is established by a port as soon as wx:new() is called. Transactions of messages through ports cannot completely deterministic to ensure soft real-time.

Erlang/OPT behaviours ensure soft real-time by maintaining a consistent time interval, depending on the message, when retrieving messages from mailboxes, and because they are handled in same order in which they are received. To aid in this, messages are retrieved using the handle_call/3 and handle_cast/2 callback functions whenever a gen_server:call/2 or a gen_server:cast/2 client calls are made. If a message can not be managed it results in a runtime error. To avoid runtime errors Erlang/OTP offers a catch-all handle_info(_Msg, LoopData) callback. According to Erlang/OTP dealing with calls and casts, all requests should originate form the behaviour callback module and any unknown messages should be caught in the early stages of testing.

GUI interfaces need not be soft real-time as they are operated on by the user. The handle_info/2 callback can be used similar to handle_call/3 as shown below.

handle_info({reset, N}, State) ->
    player1 ! {reset, N},
    player2 ! {reset, N},
   {noreply, State};

handle_info({moved, player1}, State) ->
    player2 ! move,
    {noreply, State};
handle_info({moved, player2}, State) ->
    player1 ! move,
    {noreply, State};
handle_info({ilose, player1}, State) ->
    player2 ! youwin,
    {noreply, State};
handle_info({ilose, player2}, State) ->
    player1 ! youwin,
    {noreply, State};
handle_info(Msg, State) ->
    io:format("frame got unexpected message ~p~n", [Msg]),
    {noreply, State}.

To invoke these callbacks Erlang messages must be used and not gen_server:call/2 calls as shown below.

player1 ! move.
Arbiter ! {reset, 10}. 

If you use

gen_server:call(player1, move).

instead you will get the reply

ok.

because you be invoking the handle_call/3 callback

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.