例題04a ベースプロセスを持つエージェント

(1)概要

ベースプロセスの利用方法について述べる。

(2)エージェント

エージェント名ファイル名説明
Sample040Sample040.dashベースプロセスを持つエージェント
classes\baseProcess\dashSample\SimpleWindow.class
(src\baseProcess\dashSample\SimpleWindow.javaをコンパイルしたもの)
ベースプロセス

(2.1)Sample040

●知識記述ファイル (Sample040.dash)
     1	(agent Sample040
     2	
     3	  (property
     4	    (create :author "Har@CIT")
     5	  )
     6	
     7	  (initial_facts
     8	    (worker :name suzuki)
     9	    (worker :name hara)
    10	  )
    11	
    12	  (knowledge
    13	
    14	    (rule startup
    15	      (Msg :performative __INIT_I)
    16	      (Status :name ?name)
    17	      -->
    18	      (inspect nonstop)
    19	      (loadBP baseProcess.dashSample.SimpleWindow) // BP読み込み
    20	      (startBP)                          // run()呼び出し
    21	      (control startup ())               // startup()呼び出し
    22	      (control setWindowTitle(?name))    // setWindowTitle()呼び出し
    23	    )
    24	
    25	    (rule worker
    26	      (worker :name ?name)
    27	      ~(Msg :performative __INIT_C)
    28	      -->
    29	      (control println (?name))          // println()呼び出し
    30	    )
    31	
    32	    (rule worker2
    33	      (worker :name ?name1)
    34	      (worker :name ?name2)
    35	      ~(Msg :performative __INIT_C)
    36	      -->
    37	      (bind ?member (control cat (?name1 ?name2)))  // cat()呼び出し
    38	      (make (group :member ?member))
    39	    )
    40	
    41	
    42	    // ベースプロセスからのイベントを処理する
    43	    (rule event-handling
    44	      (Msg :performative __Event :content (?string))
    45	      -->
    46	      (send :performative notify :to _interface :content (?string))
    47	    )
    48	
    49	  )
    50	)
14〜23: ワークプレースエージェントの起動時(15行で判別)に、 ベースプロセスの読み込みと起動を行い、インスペクタを開き、 ベースプロセスのメソッドstartup(), setWindowTitle()を呼び出す。
ワークプレースエージェントの名前は(Status ...)ファクトで取得できる。
25〜30: リポジトリエージェントではない場合(27行で判別)、 ベースプロセスのメソッドprintln()を呼び出す。
32〜39: リポジトリエージェントではない場合に実行する。
37: ベースプロセスのメソッドcat()を呼び出し、 返値を変数?menberにバインドする。
38: ファクトを作る。
43〜47: ベースプロセスが上げたイベントを、 ACLエディタに送信する。
44: イベントはこんな感じで拾う。
46: ACLエディタに送信するには、:toを _interfaceにする。

●ベースプロセス (SimpleWindow.java)
     1	package baseProcess.dashSample;
     2	
     3	import java.util.*;
     4	import java.awt.*;
     5	import java.awt.event.*;
     6	import javax.swing.*;
     7	import javax.swing.border.*;
     8	import dash.*;
     9	
    10	public class SimpleWindow extends JFrame implements DashBP, ActionListener {
    11	  
    12	  private DashAgent agent;
    13	  
    14	  JTextField textfield;
    15	  JTextArea  textarea;
    16	  JButton button;
    17	  JPanel panel;
    18	
    19	  /** コンストラクタ。アクション(loadBP)実行時に呼ばれる。 */
    20	  public SimpleWindow(){
    21	    super( "SimpleWindow" );
    22	  }
    23	
    24	  /** アクション(loadBP)実行時に呼ばれる。*/
    25	  public void setAgent(DashAgent agent){
    26	    this.agent = agent;
    27	  }
    28	
    29	  /** アクション(startBP)実行時に呼ばれる */
    30	  public void run(){
    31	    setup();
    32	  }
    33	
    34	  /** エージェントの終了時に呼ばれる */
    35	  public void finalizeBP(){
    36	    dispose();
    37	  }
    38	
    39	  /** エージェントにイベントを上げる */
    40	  public void actionPerformed( ActionEvent ae ){
    41	    if(agent != null){
    42	      agent.raiseEvent(textfield.getText());
    43	    }
    44	  }
    45	
    46	  /** ルールstartup内で呼び出される */
    47	  public void startup() {
    48	    textarea.append( "起動\n" );
    49	  }
    50	  
    51	  /** ルールworker内で呼び出される */
    52	  public void println( Object[] str){
    53	    textarea.append( (String)str[0] + "\n" );
    54	  }
    55	  
    56	  /** ルールworker2内で呼び出される */
    57	  public String cat(Object[] str){
    58	    return ( str[0] + " " + str[1] );
    59	  }
    60	  
    61	  /** ルールstartup内で呼び出される */
    62	  public void setWindowTitle( Object[] str1 ){
    63	    setTitle("sWin:"+str1[0]);
    64	  }
    65	  
    66	  /** ウィンドウを作る */
    67	  private void setup(){
    68	    
    69	    textfield = new JTextField();
    70	    textfield.setBorder( new TitledBorder( "入力" ) );
    71	
    72	    textarea = new JTextArea( 6, 20 );
    73	    JScrollPane sc = new JScrollPane( textarea );
    74	    sc.setBorder( new TitledBorder( "出力" ) );
    75	
    76	    button = new JButton( "エージェントに通知" );
    77	    button.addActionListener( this );
    78	
    79	    getContentPane().add( textfield, BorderLayout.NORTH );
    80	    getContentPane().add( sc,        BorderLayout.CENTER );
    81	    getContentPane().add( button,    BorderLayout.SOUTH );
    82	    
    83	    pack();
    84	    setVisible( true );
    85	  }
    86	  
    87	  /** テスト用 */
    88	  public static void main( String[] args ){
    89	    SimpleWindow window = new SimpleWindow();
    90	    window.setup();
    91	  }
    92	}
1: パッケージ名はbaseProcessでなくても可。
10: インタフェースdash.DashBPをimplementsする必要がある。
12: エージェント本体。
ベースプロセスからエージェントにイベントを上げるときに使う(42行)。
20〜22: 引数無しのコンストラクタが必要。
コンストラクタの中では、ウィンドウは作らない方がよい。
インスタンス生成後にrun()メソッドが呼び出されるので、そこで作る。
25〜27: コンストラクタの次に呼ばれる。 エージェント本体を受け取るので、 こんな感じで保管しておく。
30〜32: アクション(startBP)実行時に呼ばれる。
ベースプロセスはスレッドとして動作する。 ここからベースプロセスの処理を記述する。 ただし、この例ではウィンドウ作成処理(67行〜)をした後 直ちに終了する(ウィンドウ内で独自にスレッドをもつため)
35〜37: 終了時にエージェントから呼び出される。
40〜44: ベースプロセスからエージェントにイベントを上げる。
ルールevent-handling(43行)で処理される。
47〜49: ルールstartupの21行でエージェントから呼ばれるメソッド。
52〜54: ルールworkerの29行でエージェントから呼ばれるメソッド。
57〜59: ルールworker2の37行でエージェントから呼ばれるメソッド。
62〜64: ルールstartupの22行でエージェントから呼ばれるメソッド。

(3)起動方法

まず、次の手順でエージェントをワークプレースに起動する。
  1. IDEAを起動し、動作シミュレート工程に進む。
  2. ワークプレースのSelアイコンでSample040エージェントを選択し、起動する。
  3. エージェントがワークプレースに生成され、動作を開始する。 エージェント名はSample040.時刻:w1:lynxになる。
  4. ルールstartupにおいて、
    1. (inspect nonstop)によりインスペクタが開き、
    2. (loadBP ..)によりベースプロセスのクラスファイル読み込み・ インスタンス生成が行われ、
    3. (startBP)によりrun()メソッドを呼んで ベースプロセスが小さなウィンドウを開き、
    4. 2つの(control)がベースプロセスのメソッドを呼び出す。
  5. ルールworker, worker2が発火する。
この時点で、 ここで、SimpleWindowの入力エリアにABCと入力して「エージェントに通知」 ボタンを押すと、

(4)ベースプロセスとエージェント

ベースプロセスとは、エージェントから起動されるプロセスを指す。 DASH言語で記述できない処理(I/O処理やネットワーク通信)を行う。

ベースプロセスはエージェントのタスク処理モジュール(TPM)により、 起動/監視/制御/終了される。TPMから直接起動できるのはJavaプログラムのみである。 他の言語で記述したプログラムをベースプロセスとしたい場合には、 そのプログラムを起動するJavaプログラムを作成し、 それをTPMから起動することで行う。

リポジトリエージェントはベースプロセスを持たない。

(5)ベースプロセスの作成方法

ベースプロセスを起動するには、次のものを記述する。

●ベースプロセス

●知識記述

(6)エージェントからベースプロセスのメソッドを呼び出す方法

アクションcontrolを使うことで、 エージェントはベースプロセスのメソッドを呼び出すことができる。

呼び出しが可能なメソッドの条件を以下に示す。

アクションcontrolは、(control メソッド名 リスト)と記述する。 次のように使う。

(7)ベースプロセスからエージェントにイベントを上げる方法

イベントとは、ベースプロセスからエージェントへ通知される情報である。

上記の例題(SimpleWindow.java)の41行目のように、 setAgent()で受け取ったエージェントに対し、 そのメソッドraiseEvent()を呼び出すことで行う。

public void raiseEvent(String string)は、クラスdash.DashAgentに定義されている。 raiseEvent()を呼び出すと、ワーキングメモリに次のようなファクトが生成される。

 (Msg :performative __Event … :content (raiseEventの文字列))

イベント処理は、このファクトを通常のメッセージ処理と同じように処理すれば できる。Sample040.agtでは、次のようなルールで イベントをACLエディタに送信している。

(rule sample
  (Msg :performative __Event :content (?string))
  -->
  (send :performative notify :to _interface :content (?string))
)

raiseEvent()で渡せるもの:

raiseEvent()の引数は1つの文字列(java.lang.String)のみであるが、 文字列を改行や空白で区切ったり、:をつけることで、 イベントの:content(...)にリストやOAVデータを渡すことができる。 改行を含むか否かで、渡す内容がおおきく異なる。

(1)改行を含む場合: 各行から改行を除いた文字列を要素とするリストを渡すことができる。

リスト1
呼出agent.raiseEvnet("one\ntwo\n");
イベント(Msg :performative __EVENT :content (one two))
"one", "two" という文字列を要素に持つリスト。

リスト2(改行のみの行を含む)
呼出agent.raiseEvnet("\none\n\ntwo\n\n");
イベント(Msg :performative __EVENT :content ("" one "" two ""))
"", "one", "", "two", "" という文字列を要素に持つリスト。 System.out.println("\none\n\ntwo\n\n");すると
(空行)
one
(空行)
two
(空行)
となるが、このとき空行となる部分は""という文字列となる。

リスト3
呼出agent.raiseEvnet("one 1\n(two :num 2)\n");
イベント(Msg :performative __EVENT :content ("one 1" "(two :num 2)"))
各行がそのまま文字列となる。 以下で述べるリストやOAVデータへの変換は行わない。

(2)改行を含まない場合: 次のようなデータを渡すことができる。
1つの文字列
呼出agent.raiseEvnet("one");
イベント(Msg :performative __EVENT :content (one))
"one"という文字列を要素に持つリスト。

リスト
呼出agent.raiseEvnet("one two three")
イベント(Msg :performative __EVENT :content (one two threee))
"one", "two", "three"という文字列を要素に持つリスト。

OAVデータ
呼出agent.raiseEvnet("obj :attr1 v1 :attr2 v2")
イベント(Msg :performative __EVENT :content (obj :attr1 v1 :attr2 v2))
オブジェクト = "object", 属性:attr1 = "v1", 属性:attr2 = "v2"というOAVデータ。

入れ子構造のデータ
呼出agent.raiseEvnet("obj :oav (o :p q) :list (1 2 3))")
イベント(Msg :performative __EVENT
:content (obj :oav (o :p q) :list (1 2 3)))
属性が「:oav」や「:list」でなくても(期待どおり)渡すことができる。

1つの文字列
呼出agent.raiseEvnet("\"one two three\"")
イベント(Msg :performative __EVENT :content (one two threee))
"one two three"という文字列を要素に持つリスト。 ""で囲むと、リストやOAVデータへの変換を抑制できる。