提供: Minecraft Modding Wiki
移動先: 案内検索
(具体例)
1行目: 1行目:
== スケジューラの解説 ==
+
== スケジューラのプログラミング ==
この解説では、Bukkitのスケジューラの利用方法を説明しています。
 
  
== 主な注意事項  ==
+
このチュートリアルでは、bukkit によって提供されているスケジューラの使用方法をガイドします。<br>
 +
スケジューラは、指定の時間後に実行される処理を書くことができます。<br>
 +
これはリスナーやイベントへの応答で実行されるコードの記述方法とは異なります。<br>
 +
また、繰り返しや、遅延の有無を設定したりすることもできます。この繰り返し処理は、処理が完了するか、キャンセルされるか、プラグインが無効になるまで実行できます。
  
主な注意事項は次の2点です。
+
== BukkitRunnable ==
* 他のスレッドからAPIのメソッドを(一部例外あり)を呼び出さない
 
* メインのスレッドをスリープ状態にしてはならない
 
  
== 解説 ==
+
BukkitRunnableは、 抽象クラスRunnableの実装クラスです。<br>
 +
Runnableに無い便利な機能として、自身の処理を再スケジュールしたり、キャンセルしたりできることです。しかし、もしBukkitRunnableが自分自身をスケジュールしていない場合、自分自身でキャンセルを行うことができません。BukkitRunnableはスケジューラではないので、スケジューラのロジックを含んでいません。
  
スケジューラは次の3種類のタスクをオプションとして提供しています。
+
== 定義方法の例 ==
*遅延タスク(Delayed Task)
 
*繰り返しタスク(Repeating Task)
 
*戻り値を返すタスク
 
  
これらのタスクは2種類のスレッドとして実行する事ができます。
+
スケジューラの処理を定義するには、まず BukkitRunnable を extends しましょう。
*メインサーバスレッド
 
*専用スレッド
 
  
=== スレッドの種類  ===
+
 
+
これはスケジュール処理のタスク定義の例です。
スケジューラにどのスレッドタイプを利用すれば良いかが、最も重要な問題です。<br>
 
間違ったスレッドタイプを利用すると、検出が困難な障害を断続的に発生させる原因となります。<br>
 
また、そのような障害は試験サーバでは発生せず、頻繁に連続稼動しているサーバでのみ発生する場合がある等、<br>
 
再現性が低い障害となる場合もあるため、原因の特定を更に困難にします。
 
 
 
Minecraftサーバはマルチスレッドであり、<br>
 
一つのメインサーバーのスレッドと、他のヘルパースレッドが存在します。
 
 
 
多くのデータ構造は、サーバのメインスレッドからのみアクセスされます。<br>
 
別のスレッドからこれらのデータを変更しようとすると、<br>
 
2つのスレッドが同時にデータにアクセスする事になるため、<br>
 
データ破損を引き起こす可能性があります。
 
 
 
メインスレッドは、サーバーのCPU時間の配分を処理します。<br>
 
このスレッドは、他のタスクへCPU時間を配分するために、秒間20回のスリープを実行します。
 
 
 
少数のBukkitAPIはスレッドセーフです。<br>
 
あなたのプラグインが使っているAPIがスレッドセーフなものである事を確認していない限り、<br>
 
基本的にBukkitAPIの命令はスレッドセーフではないものとして開発して下さい
 
 
 
==== 同期タスク ====
 
サーバのメインスレッドが実行するタスクです。
 
 
 
:'''重要''': '''このスレッドが停止すると、サーバがフリーズします。'''
 
 
 
<br> メインスレッドは下記の用途に'''使えます'''
 
* スケジューラメソッドの呼び出し
 
** スケジューラの戻り値を取得する際は注意を要します(後方の説明に記載)
 
*停止・待機処理を行わないタスク
 
 
 
<br> メインスレッドは下記の用途に'''使わなくてはいけません'''
 
*Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除く)
 
 
 
<br> メインスレッドは下記の用途に'''使ってはいけません'''
 
*自スレッドをスリープ状態にするタスク
 
**停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)
 
 
 
 
 
メインスレッド上で同期タスクを実行する事自体は良いのですが、<br>
 
待機や停止の時間がほぼゼロであるものに限ります。<br>
 
:'''訳者注''': 1つの処理ステップが長時間を費やす処理は、サーバ自体をその間待機状態に置いてしまうため、メインスレッド上で行うべきではない事を説明しています。
 
 
 
==== 非同期タスク ====
 
 
 
非同期タスクは、サーバのメインスレッド以外のスレッドによって実行されます。専用のタスクとして実行されるため、同期タスクで説明したような問題を引き起こすことなく、自タスクを停止・スリープ状態にする事ができます。この特性は、同期タスクの真逆に相当します。
 
 
 
<br> 非同期タスクは下記の用途に'''利用できます'''
 
 
 
*スケジューラメソッドの呼び出し
 
*プラグインAPIを利用しないタスク
 
 
 
<br> 非同期タスクは下記の用途に'''利用しなければなりません'''
 
 
 
*自スレッドをスリープ状態にするタスク
 
**停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)
 
 
 
<br> 非同期タスクは下記の用途に'''利用してはいけません'''
 
 
 
*Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除いては、下記参照)
 
 
 
<br>
 
 
 
== タスクの種類 ==
 
タスクには、繰り返しタスク, 遅延タスク, 戻り値を返すタスク, の3種類があります。
 
 
 
APIのインタフェースの仕様については[[https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/scheduler/BukkitScheduler.java こちら]]を参照して下さい。
 
 
 
=== 遅延タスク ===
 
 
 
一定の遅延を置いてから実行されるタスクです。遅延時間にはゼロを指定可能です。
 
 
 
==== 具体例 ====
 
 
 
この方法では、3秒のディレイ(遅延)経過後にプラグインのメインスレッド上でタスクを実行します。
 
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
myPlugin.getServer().getScheduler().scheduleSyncDelayedTask(myPlugin, new Runnable() {
+
import org.bukkit.Bukkit;
 
+
import org.bukkit.plugin.java.JavaPlugin;
  public void run() {
+
import org.bukkit.scheduler.BukkitRunnable;
      getServer().broadcastMessage("This message is broadcast by the main thread");
+
  }
+
public class ExampleTask extends BukkitRunnable {
}, 60L);
+
 +
    private final JavaPlugin plugin;
 +
 +
    public ExampleTask(JavaPlugin plugin) {
 +
        this.plugin = plugin;
 +
    }
 +
 +
    public void run() {
 +
        // スケジュールで実行する処理の内容をここに書きます。
 +
        plugin.getServer().broadcastMessage("サーバーへようこそ!説明文をちゃんと読んでね!");
 +
    }
 +
}
 
</source></blockquote>
 
</source></blockquote>
ディレイには、 Server Tick 単位で 60 を指定しています。Server Tick は 1/20 秒に相当するので、今回の例では 60/20 = 3秒 となります。
 
  
This code could be executed by another thread. If the code
 
<blockquote><source lang="java">
 
getServer().broadcastMessage("This message is broadcast by an async thread");
 
</source></blockquote>
 
was executed by another thread, it could cause problems since the broadcastMessage method is being executed by another thread.
 
  
=== 繰り返しタスク ===
+
== 作業のスケジューリング ==
  
繰り返し実行されるタスクです。遅延時間も設定可能です。
+
タスクを定義した後、プラグインは、タスクをスケジュールする必要があります。<br>
 +
BukkitRunnable は、指定の時間になったら、タスクのインスタンスが起動され、メソッドが実行されます。<br>
 +
詳細は、BukkitRunnableのjavadoc を参照してください。<br>
 +
これらのメソッドは共通して、BukkitTaskクラスのオブジェクトを返します。
  
==== 具体例 ====
+
:'''重要''': '''非同期実行タスクは、BukkitのAPIを直接実行してはいけません。'''
  
この方法は、非同期タスクを 3 秒のディレイ(遅延)経過後に 10 秒間隔で連続実行します。
+
This submits a task to an async thread to be executed after 3 seconds with a period of 10 seconds.
+
これは、20ticks(=1秒)後にタスクを実行するスケジューラを登録する実装例です。
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
myPlugin.getServer().getScheduler().scheduleAsyncRepeatingTask(myPlugin, new Runnable() {
+
import org.bukkit.event.EventHandler;
 
+
import org.bukkit.event.Listener;
  public void run() {
+
import org.bukkit.event.player.PlayerJoinEvent;
      System.out.println("このメッセージは非同期スレッドから出力されています。");
+
import org.bukkit.plugin.java.JavaPlugin;
  }
+
import org.bukkit.scheduler.BukkitRunnable;
}, 60L, 200L);
+
import org.bukkit.scheduler.BukkitTask;
 +
 +
public final class ExamplePlugin extends JavaPlugin {
 +
 +
    @Override
 +
    public void onEnable() {
 +
        new ExampleListener(this);
 +
    }
 +
}
 +
 +
class ExampleListener implements Listener {
 +
 +
    private final ExamplePlugin plugin;
 +
 +
    public ExampleListener(ExamplePlugin plugin) {
 +
        this.plugin = plugin;
 +
        plugin.getServer().getPluginManager().registerEvents(this, plugin);
 +
    }
 +
 +
    @EventHandler
 +
    public void onJoin(PlayerJoinEvent event) {
 +
        BukkitTask task = new ExampleTask(this).runTaskLater(plugin, 20);
 +
    }
 +
}
 
</source></blockquote>
 
</source></blockquote>
  
どちらの時間も Server Tick 単位の値のため、初回ディレイは 60/20 = 3秒、実行間隔は 200/20 = 10 秒となります。
+
== BukkitScheduler ==
上記のコードは、メッセージ "このメッセージは非同期スレッドから出力されています。" を10秒毎に出力します。
 
 
 
=== 戻り値を返すタスク ===
 
 
 
スケジューラから戻り値を返す事ができるタスクです。メインスレッドから実行可能なタスクです。
 
 
 
この仕組みの目的は、他タスクから呼ばれたAPIのメソッドに戻り値を返させる事です。
 
  
スケジューラは、戻り値として返されるであろうオブジェクトを取得可能にします。
+
BukkitSchedulerは、[http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/lang/Runnable.html Runnable]クラスや[http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/util/concurrent/Callable.html Callable]クラスの、どちらかまたは両方を実装したクラスを、一定時間後に実行する機能を提供します。<br>
 
+
詳細は、[http://jd.bukkit.org/rb/apidocs/index.html?org/bukkit/scheduler/BukkitScheduler.html BukkitSchedulerのjavadoc] を参照してください。
==== 具体例 ====
 
<blockquote><source lang="java">
 
Future<String> returnFuture = myPlugin.getServer().getScheduler().callSyncMethod(myPlugin, new Callable<String>() {
 
 
 
  public String call() {
 
      return "This is the string to return";
 
  }
 
 
 
});
 
 
 
try {
 
    // This will block the current thread
 
    String returnValue = returnFuture.get();
 
    System.out.println(returnValue);
 
} catch (InterruptedException e) {
 
    System.out.println("Interrupt triggered which waiting on callable to return");
 
} catch (ExecutionException e) {
 
    System.out.println("Callable task threw an exception");
 
    ee.getCause().printStackTrace();
 
}
 
</source></blockquote>
 
  
This will submit the callable and then use the Future.get() method. This method sleeps the current thread until the Callable has returned a value and gets the result.
+
:'''重要''': '''非同期実行タスクは、BukkitのAPIを直接実行してはいけません。'''
  
===== メインスレッドからの利用 =====
+
=== BukkitTask ===
この方法は'''非推奨'''です。
 
この仕組みは、メインスレッドから利用される事を想定したものではありません。
 
  
<code>''.get()''</code>メソッドを利用している場合、メインスレッドがこのタスクを完了するまでの間、現行スレッド(=メインスレッド)をスリープ状態にします。
+
BukkitTaskクラスのオブジェクトは、Runnableがスケジュールされたときに返されます。<br>
 +
このオブジェクトは、スケジューラにより実行されるスケジュールタスクを表現しています。<br>
 +
詳細は、[http://jd.bukkit.org/rb/apidocs/index.html?org/bukkit/scheduler/BukkitTask.html BukkitTaskのjavadoc] を参照してください。
  
どうしてもメインスレッド上で使用する必要がある場合は、返されるであろうスケジューラの戻り値に対する<code>''.get()''</code>メソッドを呼び出す前に、<code>''.isDone()''</code>メソッドをチェックしなければなりません。さもなくばメインスレッドは、メインスレッド自身がタスクを完了させるまでスリープ状態になります。
+
=== Callable と Future  ===
  
== Thread Safe API Methods  ==
+
Callableクラスは、同期処理を呼び出したときに、[http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/util/concurrent/Future.html Future]クラスのオブジェクトを返します。<br>
 +
これはJavaで提供されている機能やクラスです。<br>
 +
詳細は、[http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/util/concurrent/Callable.html Callableのjavadoc] や、[http://docs.oracle.com/javase/1.5.0/docs/api/index.html?java/util/concurrent/Future.html Futureのjavadoc] を参照してください。
  
Bukkit API methods which are thread safe are:
+
== スレッドの安全性のためのヒント ==
  
*All the scheduler methods
+
1. 非同期実行タスクは、BukkitのAPIを直接実行してはいけません。<br>
*[http://jd.bukkit.org/apidocs/org/bukkit/Bukkit.html#getServer() Bukkit.getServer()]
+
2. 非同期タスクのコレクションにアクセスしたり、内容を変更しないでください。通常のコレクションはスレッドセーフではありません。<br>
*[http://jd.bukkit.org/apidocs/org/bukkit/Server.html#getBukkitVersion() Server.getBukkitVersion()]
+
3. 非同期タスクは、同期タスクをスケジュールすることができます。<br>
*[http://jd.bukkit.org/apidocs/org/bukkit/World.html#getUID() World.getUID()]
+
4. 同期タスク非同期タスクをスケジュールすることができます。
*[http://jd.bukkit.org/apidocs/org/bukkit/World.html#getMaxHeight() World.getMaxHeight()]
 
*[http://jd.bukkit.org/apidocs/org/bukkit/World.html#getSeed() World.getSeed()]
 
*[http://jd.bukkit.org/apidocs/src-html/org/bukkit/World.html#line.51 World.getBlockTypeIdAt(int x, int y, int z)]
 
*[http://jd.bukkit.org/apidocs/org/bukkit/entity/Entity.html#getEntityId() Entity.getEntityId()]
 
*[http://jd.bukkit.org/apidocs/org/bukkit/entity/Entity.html#getUniqueId() Entity.getUniqueId()]
 
**This list is not complete....
 

2013年4月28日 (日) 13:21時点における版

スケジューラのプログラミング

このチュートリアルでは、bukkit によって提供されているスケジューラの使用方法をガイドします。
スケジューラは、指定の時間後に実行される処理を書くことができます。
これはリスナーやイベントへの応答で実行されるコードの記述方法とは異なります。
また、繰り返しや、遅延の有無を設定したりすることもできます。この繰り返し処理は、処理が完了するか、キャンセルされるか、プラグインが無効になるまで実行できます。

BukkitRunnable

BukkitRunnableは、 抽象クラスRunnableの実装クラスです。
Runnableに無い便利な機能として、自身の処理を再スケジュールしたり、キャンセルしたりできることです。しかし、もしBukkitRunnableが自分自身をスケジュールしていない場合、自分自身でキャンセルを行うことができません。BukkitRunnableはスケジューラではないので、スケジューラのロジックを含んでいません。

定義方法の例

スケジューラの処理を定義するには、まず BukkitRunnable を extends しましょう。

例 これはスケジュール処理のタスク定義の例です。

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
 
public class ExampleTask extends BukkitRunnable {
 
    private final JavaPlugin plugin;
 
    public ExampleTask(JavaPlugin plugin) {
        this.plugin = plugin;
    }
 
    public void run() {
        // スケジュールで実行する処理の内容をここに書きます。
        plugin.getServer().broadcastMessage("サーバーへようこそ!説明文をちゃんと読んでね!");
    }
}


作業のスケジューリング

タスクを定義した後、プラグインは、タスクをスケジュールする必要があります。
BukkitRunnable は、指定の時間になったら、タスクのインスタンスが起動され、メソッドが実行されます。
詳細は、BukkitRunnableのjavadoc を参照してください。
これらのメソッドは共通して、BukkitTaskクラスのオブジェクトを返します。

重要: 非同期実行タスクは、BukkitのAPIを直接実行してはいけません。

例 これは、20ticks(=1秒)後にタスクを実行するスケジューラを登録する実装例です。

import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
 
public final class ExamplePlugin extends JavaPlugin {
 
    @Override
    public void onEnable() {
        new ExampleListener(this);
    }
}
 
class ExampleListener implements Listener {
 
    private final ExamplePlugin plugin;
 
    public ExampleListener(ExamplePlugin plugin) {
        this.plugin = plugin;
        plugin.getServer().getPluginManager().registerEvents(this, plugin);
    }
 
    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        BukkitTask task = new ExampleTask(this).runTaskLater(plugin, 20);
    }
}

BukkitScheduler

BukkitSchedulerは、RunnableクラスやCallableクラスの、どちらかまたは両方を実装したクラスを、一定時間後に実行する機能を提供します。
詳細は、BukkitSchedulerのjavadoc を参照してください。

重要: 非同期実行タスクは、BukkitのAPIを直接実行してはいけません。

BukkitTask

BukkitTaskクラスのオブジェクトは、Runnableがスケジュールされたときに返されます。
このオブジェクトは、スケジューラにより実行されるスケジュールタスクを表現しています。
詳細は、BukkitTaskのjavadoc を参照してください。

Callable と Future

Callableクラスは、同期処理を呼び出したときに、Futureクラスのオブジェクトを返します。
これはJavaで提供されている機能やクラスです。
詳細は、Callableのjavadoc や、Futureのjavadoc を参照してください。

スレッドの安全性のためのヒント

1. 非同期実行タスクは、BukkitのAPIを直接実行してはいけません。
2. 非同期タスクのコレクションにアクセスしたり、内容を変更しないでください。通常のコレクションはスレッドセーフではありません。
3. 非同期タスクは、同期タスクをスケジュールすることができます。
4. 同期タスク非同期タスクをスケジュールすることができます。