在 Arif Isaq 小册子中有一个示例,其中 gen_server 行为被 wx_object 替换。我正在尝试运行这个示例,但不知何故我发送到进程的消息
Eshell V13.1.4(使用 ^G 中止)
1> c(wo_player).
{好的,wo_player}
2> 仲裁器:start_link()。
{好的,<0.93.0>}
3> gen_server:call(仲裁器, {重置, 42}).
好的
4> gen_server:调用(玩家1,移动)。
好的
不要启动计时器。
我正在 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:~$
wxWidgets被打包到上面所示的gtk 3.24.5版本中
这里是示例的 Erlang 模块
-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}.
问题是否与我作为软件包安装的 gtk 3.24.5 版本中包含的 wxWidgets 版本有关?
Stack Overflow 成员,如果我错了,请纠正我。我的理解是,wxErlang 和 wxWidgets 之间的绑定是在调用 wx:new() 时通过端口建立的。通过端口进行的消息交易不能完全确定,无法保证软实时。
Erlang/OPT 行为通过在从邮箱检索消息时根据消息保持一致的时间间隔来确保软实时,并且因为它们的处理顺序与接收消息的顺序相同。为了帮助实现这一点,每当进行 gen_server:call/2 或 gen_server:cast/2 客户端调用时,都会使用handle_call/3和handle_cast/2回调函数检索消息。如果无法管理消息,则会导致运行时错误。为了避免运行时错误,Erlang/OTP 提供了一个包罗万象的handle_info(_Msg, LoopData) 回调。然而,在处理调用和强制转换时,所有请求都应源自行为回调模块,并且应在测试的早期阶段捕获任何未知消息。
GUI 界面不需要是软实时的,因为它们是由用户操作的。 handle_info/2 回调的使用方式与handle_call/3 类似,如下所示。
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}.
要调用这些回调,必须使用 Erlang 消息,而不是 gen_server:call/2,如下所示。
player1 ! move.
Arbiter ! {reset, 10}.
如果您使用
gen_server:call(player1, move).
相反,您会收到回复
ok.
因为你正在调用handle_call/3回调
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.