%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Sample Of Child Prolog Process Management %%
%%           2009.07.01 T.Inaba SOFNEC.CO.JP %%

% >azpc -p setof.pl utility.pl mlt_child.pl mlt_parent.pl /i /dcurses /e prolog_c
% >azpc -p setof.pl utility.pl mlt_parent.pl /i /e prolog

% Using Next Special Builtin Predicate
% s_child/5       % 子プロセスを立ち上げる.   s_child(+実行形式プログラム,+[パラメータ並びリスト],-InputStream,-OutputStream,-ProcessID)
% s_kill/2        % プロセスを削除する.       s_kill(+ProcessID,0).  % 第二引数めはＵｎｉｘとの引数互換のためであり、意味はない
% s_flush/0       % 標準出力バッファをフラッシュする.  対向（親）プロセス側で読み込めるようにする。  
% s_can_read/1    % OutputStreamから読み込み可能な場合（子プロセス側で標準出力に戻り値が書き込まれている場合）にのみ成功する。
% s_can_read/2    % OutputStreamから読み込み可能な場合に成功し、読み込み可能バイト数を返す。
% s_sleep/1       % 指定ミリ秒スリープする

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%       ＜＜ 親・子プロセス間のストリーム通信イメージ＞＞
%
%                     【親プロセス】
% writeq/2 入力ストリーム｜ ↑ 出力ストリーム read/2   (ここでの入力、出力ストリームとは子プロセス側から見てのことです）
%                        ｜ ｜
%    (s_childで立ち上げ) ｜ ｜ (親からは、s_killで強制終了。halt/0を送信してもよい)
%                        ｜ ｜
%         read/1 標準入力↓ ｜標準出力 writeq/1        (readで読めるように"."を付加し、バッファをフラッシュする:双方）
%                     【子プロセス】
%              read([X,Y]),call(X),writeq(Y)のループ (mlt_child.pl参照)


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Ｎ個のパラメータなし子プロセスセットを立ち上げる
:- public mlt_proc/2.
mlt_proc(N,L):- mlt_proc(N,prolog_c,'','',L).

%% Ｎ個の子プロセスを立ち上げる. mlt_proc(+Volume,+Exe,+AreaSize,+Parameter,-Processes)
:- public mlt_proc/5.
mlt_proc(0,_,_,_,[]):-!.
mlt_proc(N,Exe,Param1,Param2,[IDIOExeParam|L]):-                   % Param1は -pの前のパラメータ（-g NN などの領域指定）
	term_atomq(Param1,Param2,Param3),                              % Param2は -pの後のパラメータ(consult/1,compile/1など）
	ss_child(Exe,Param3,IDIOExeParam),
	NN is N-1,mlt_proc(NN,Exe,Param1,Param2,L).

:- public mlt_proc2/2.
mlt_proc2([],[]):-!.
mlt_proc2([[Exe,Param1,Param2]|E],[IDIOExeParam|L]):-
	term_atomq(Param1,Param2,Param3),
	ss_child(Exe,Param3,IDIOExeParam),
	mlt_proc2(E,L).

ss_child(Exe,Param,[ID,I,O,Exe,Param]):-
	s_child(Exe,Param,I,O,ID),s_mode(M,on),assert('$proc$'([ID,I,O])),s_mode(_,M).       % 立ち上げたプロセスは失くさないようにアサートしておく

to_atomic_exp([],X-X):-!.
to_atomic_exp(S,[A|X]-X):- name(T,S),term_atom(T,A).

add_param([],S-[],AR,Tail):- !,to_atomic_exp(S,AR-Tail).
add_param([32|L],S-[],AR,Tail):-!,to_atomic_exp(S,AR-R),add_param(L,P-P,R,Tail).
add_param([A|L],S-[A|R],AR,Tail):-add_param(L,S-R,AR,Tail).

term_atomq(P,'',Param):- !,name(P,PL),add_param(PL,S-S,Param,['-child']).
term_atomq(P,A, Param):- 
	atom(A),!,name(P,PL),add_param(PL,S-S,Param,['-child','-p',A]).
term_atomq(P,T, Param):-
	term_atom(T,A),	 
% 	atom_appends(['"',A,'"'],AT),
%	name(P,PL),add_param(PL,S-S,Param,['-child','-p',AT]).
	name(P,PL),add_param(PL,S-S,Param,['-child','-p',A]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットを強制終了する
:- public mlt_kill/1.
mlt_kill([]):-!.
mlt_kill([[ID|_]|L]):- mlt_terminate(ID),mlt_kill(L).

:- public mlt_terminate/1.
mlt_terminate(ID):- retract('$proc$'([ID,I,O])),ss_kill(ID,I,O),!.
mlt_terminate(_).

:- public halt/0.                                         % 組込み述語halt/0のオーバーライト定義
halt:- retract('$proc$'([ID,I,O])),ss_kill(ID,I,O),fail.  % 子プロセスを削除してから親プロセスを終了する
halt:- halt(0).

%% 2010.1.12 s_kill(ID,0)  ==> s_kill(ID,9)
ss_kill(X,I,O):- errormode(M,0),errorset(told(I),_),errorset(seen(O),_),errorset(s_kill(X,9),_),!,errormode(_,M),s_wait(X,_,_).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットに異なるゴール(子プロセスの数ぶんのゴールを要素としたリスト）または、同じゴールを与える

%% 子プロセスに送るゴールは二つの形式がある。
%% 1)ゴールだけ与える方法：                          例：　reconsult('queen.pl')
%% 2)ゴールと戻り値の２要素のリストで与える方法：    例：　[ setof(X,put([1,2,3,4],[],X),L), L ]     % 決定性の戻りとなる
%% 3)ゴールと戻り値の２要素を{Goal,Return}で与える： 例：　{ put([1,2,3,4],[],L), L }
%%       子プロセス内でバックトラックし、複数解が順次戻り、最後はstatus(exec,fail)が返る。
%% どれもゴールが複数のときは、()で囲む。       例：　( generate(8,R),put(R,[],L]),length(L,N) )

:- public mlt_send/2.
mlt_send([],_):-!.
mlt_send([[_,I|_]|L],[GoalOut|Next]):- !,mlt_send_cmd(I,GoalOut),mlt_send(L,Next).  % プロセスごとに異なるゴール
mlt_send([[_,I|_]|L],GoalOut):- mlt_send_cmd(I,GoalOut),mlt_send(L,GoalOut).        % 全プロセスに同一ゴール

:- public mlt_send_cmd/2.
mlt_send_cmd(I,X):- writeq(I,X),write(I,'.'),nl(I).     % ゴール送信の実体はストリームに対するwriteq/1である
                                                        % 子プロセス側の標準入力 read/1 で読めるように"."を付加
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットから出力された結果を読み取る

%% mlt_scan,mlt_scan2,mlt_scan_each,mlt_receive(実体はどれもread)で受け取る戻り値は次のとおり
%% １）ゴールだけ与えられた場合、ゴールが成功したときはstatus(exec,succ)が戻る。
%% ２）ゴールと戻り値の２要素のリストで与えた場合、ゴールが成功したときは指定した戻り値。
%% どちらも送ったゴールがreadエラーのときは、status(read,エラー番号）
%% ゴールが失敗またはエラーの場合、status(exec,{fail,またはエラー番号})が返る。


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットから出力された全結果を読み取る

:- public mlt_receive/2.
mlt_receive([],[]):-!.
mlt_receive([[_,_,O|_]|P],[X|Y]):- read(O,X),mlt_receive(P,Y).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットから全結果を出力順に読み取る(稼動時間付）
%% mlt_receive(+子プロセスセット、+スキャンインターバルミリ秒,-結果）
%% 結果は  プロセスID=[Sendでの戻り指定値,稼動時間]  のリスト

:- public mlt_receive/3.
mlt_receive(P,Wait,Ans):- T is cputime,mlt_receive(T,P,Wait,X),!,Ans=X.

mlt_receive(_,[],_,[]):- !.
mlt_receive(T,P,W,[ID=[X,Tm]|Ans]):- 
	repeat,s_sleep(W),mlt_canread(P,[ID|_],L,O),!,
		read(O,X),Tm is cputime-T,mlt_receive(T,L,W,Ans).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットからもっとも早く出力されたものだけ読み取る
%% mlt_scan(+子プロセスセット、+スキャンインターバルミリ秒,-結果）
%% 結果は  プロセスID=Sendでの戻り指定値 (エラー、Failもある）

:- public mlt_scan/3.
mlt_scan([],_,_):-!,fail.
mlt_scan(Proc,Wait,Ans):- repeat,s_sleep(Wait),mlt_canread(Proc,_,_,O),!,read(O,Ans).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットからFail,Errorを除くもっとも早く出力されたものを読み取る
%% mlt_scan2(+子プロセスセット、+スキャンインターバルミリ秒,-結果）
%% 結果は  プロセスID=Sendでの戻り指定値  (Fail,Errorを除く)

:- public mlt_scan2/3.
mlt_scan2([],_,_):- !,fail.
mlt_scan2(Proc,W,Ans):- 
	repeat,s_sleep(W),mlt_canread(Proc,[ID|_],L,O),
		!,read(O,X),mlt_scan2_sub(ID,X,W,L,Ans).

mlt_scan2_sub(_,status(_,X),W,L,Ans) :- X \== succ,!,mlt_scan2(L,W,Ans).
mlt_scan2_sub(ID,Ans,_,_,ID=Ans).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 子プロセスセットから全結果をバックトラックにより出力順に読み取る
%% ?-mlt_send(I,{Goal,Return}). により起動された結果で必ず最後に status/2が戻ることを前提としている
%% mlt_scan_each(+子プロセスセット,-結果）
%% 2012.5.22 Telly On Linux s_canread/1(select) Can Succ Only One! 
%% So,If a process can read once,it read sequencialy

:- public mlt_scan_each/2.

mlt_scan_each(P,Ans):- mlt_extract(P,PO),mlt_scan_each2(PO,Ans). 

%%%%
mlt_scan_each2([],Ans):-!,fail.
mlt_scan_each2([[O,_]],Ans):- 
	!,read(O,S),mlt_scan_each_sub(S,[O,1],[],Ans).
mlt_scan_each2(P,Ans):- 
	repeat,mlt_canread3(P,L,O),!,read(O,S),mlt_scan_each_sub(S,[O,1],L,Ans).

%%%%
mlt_scan_each_sub(end_of_file,_,L,Ans) :- 
	!,mlt_scan_each2(L,Ans).        % fail,succ,errorなら対象リストから外す
mlt_scan_each_sub(status(_,_),_,L,Ans) :- 
	!,mlt_scan_each2(L,Ans).        % fail,succ,errorなら対象リストから外す
mlt_scan_each_sub(Ans,_,_,Ans).         % 解が戻ってきた
mlt_scan_each_sub(_,O,L,Ans):-  
	mlt_scan_each2([O|L],Ans).      % 少量受信時は継続してReadする。

mlt_canread3([[O,1]|L], L,    O):- !.   % 大容量受信後の再受信チェック
mlt_canread3([[O,0]|L], L,    O):- s_can_read(O),!.   % 大容量受信後の再受信チェック
mlt_canread3([X|L], [X|Y],O):- mlt_canread3(L,Y,O).

%%%%%% 入力ストリームのみ取り出して梱包する
mlt_extract([[_,_,O|_]|L],[[O,0]|R]):- !,mlt_extract(L,R).
mlt_extract([],[]).

%%% 共通部品
%% プロセスリストから読み込み可能なプロセスを取得する
mlt_canread([[D,I,O|P]|L],[D,I,O|P],L,    O):- s_can_read(O),!.
mlt_canread([A|L],        X,        [A|Y],O):- mlt_canread(L,X,Y,O).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 停止していないプロセスを強制終了し同数分だけ新たに立ち上げる

:- public mlt_reflesh/2.

mlt_reflesh([],[]):-!.
mlt_reflesh([[ID,I,O|C]|L],[[ID,I,O|C]|R]):- 
	mlt_send_cmd(I,{true,status(true,true)}),s_sleep(100),
	s_can_read(O),read(O,status(true,true)),!,mlt_reflesh(L,R).
mlt_reflesh([[ID,_,_,Exe,Param]|L],[IDIOExeParam|R]):- 
	mlt_terminate(ID),ss_child(Exe,Param,IDIOExeParam),mlt_reflesh(L,R).

