参与编写者:zzz1999
一.概念 1.计时器的作用是 立即,延时,循环或延时循环 某些任务,通过计时器我们可以实现例如 在线奖励 , 倒计时 等插件功能, 是一些复杂插件中经常用到的功能之一.
2.执行计时器代码块的单元叫做任务,我们可以编写任务代码来使用计时器的功能.
3.任务执行分为两种类型,一种是同步,一种是异步.同步通过主线程所调度管理执行,异步通过主线程调度管理,异步线程池执行.所以,太多的任务会导致服务器卡顿.
4.计时器的功能以minecraft刻
(tick)作为单位,一刻为现实时间的0.05秒,现实时间的1秒为20刻,服务器每秒刻数由TPS决定,最高为20.
二.代码
我们先来看一下nukkit的Task类.
package cn.nukkit.scheduler;
import cn.nukkit.Server;
/**
* 表达一个任务的类。<br>A class that describes a task.
*
* <p>一个任务可以被Nukkit服务器立即,延时,循环或延时循环执行。参见:{@link ServerScheduler}<br>
* A task can be executed by Nukkit server with a/an express, delay, repeat or delay&repeat.
* See:{@link ServerScheduler}</p>
*
* <p>对于插件开发者,为确保自己任务能够在安全的情况下执行(比如:在插件被禁用时不执行),
* 建议让任务继承{@link PluginTask}类而不是这个类。<br>
* For plugin developers: To make sure your task will only be executed in the case of safety
* (such as: prevent this task from running if its owner plugin is disabled),
* it's suggested to use {@link PluginTask} instead of extend this class.</p>
*
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract class Task implements Runnable {
private TaskHandler taskHandler = null;
public final TaskHandler getHandler() {
return this.taskHandler;
}
public final int getTaskId() {
return this.taskHandler != null ? this.taskHandler.getTaskId() : -1;
}
public final void setHandler(TaskHandler taskHandler) {
if (this.taskHandler == null || taskHandler == null) {
this.taskHandler = taskHandler;
}
}
/**
* 这个任务被执行时,会调用的过程。<br>
* What will be called when the task is executed.
*
* @param currentTick 服务器从开始运行到现在所经过的tick数,20ticks = 1秒,1tick = 0.05秒。<br>
* The elapsed tick count from the server is started. 20ticks = 1second, 1tick = 0.05second.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract void onRun(int currentTick);
@Override
public final void run() {
this.onRun(taskHandler.getLastRunTick());
}
public void onCancel() {
}
public void cancel() {
try {
this.getHandler().cancel();
} catch (RuntimeException ex) {
Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex);
}
}
}
我们可以捕捉到几个关键信息,首先我们可以看到onRun(int)
方法,它是一个抽象方法并且nk开发者很贴心的写上了注释示意我们这是Task中会被运行到代码块.
还有onCancel()
和cancel()
方法,onCancel()内部是一个空代码块,当Task被取消的时候(被任务调度器取消或者手动调用cancel()取消都会触发),这段代码块会被调用
比较常见的用途比如小游戏时间到的时候,一些判定胜负,给予奖励,关闭房间的一些代码可以用Task来实现或者调用.
我们还可以使用cancel()
方法来强制取消一个Task,例如你想要写一个登录插件,每个玩家进入服务器时创建一个Task循环发送提示登录的信息,如果玩家输入了正确的密码并且登录成功才会取消掉这个Task.这时候就可以使用cancel方法了(仅举例,实际生产环境中,每个玩家进入创一个计时器是很费资源的,不会使用这种方法)
Handler是服务器调度Task所用到的一些东西,可以不懂,不妨碍计时器的使用.
开发者可以使用PluginBase#getServer()
方法获取到Server对象,然后使用Server#getSchedule()
获取到计时器的实例类.
public class Main extends PluginBase{
@Override
public void onEnable(){
ServerScheduler scheduler = this.getServer().getScheduler();
}
}
获取到后,可以创建 立即,延时,循环或延时循环 类型的计时器 立即(ServerSchedule#scheduleTask) 延时(ServerSchedule#scheduleDelayedTask) 循环(ServerSchedule#scheduleRepeatingTask) 延时循环(ServerSchedule#scheduleDelayedRepeatingTask)
计时器是需要一个被触发的时间间隔的,以tick为单位,如果是循环计时器,则是每次循环的时间,如果是延时,则是延迟多少tick才被触发,如果是延迟循环,则会要求填写延迟间隔与循环间隔.
举例将会在下一小节讲到
3.使用
下面会使用到PluginTask
类,你需要对PluginTask
类的方法做一个初步的了解,以及知道它继承了Task类.
package cn.nukkit.scheduler;
import cn.nukkit.plugin.Plugin;
/**
* 插件创建的任务。<br>Task that created by a plugin.
*
* <p>对于插件作者,通过继承这个类创建的任务,可以在插件被禁用时不被执行。<br>
* For plugin developers: Tasks that extend this class, won't be executed when the plugin is disabled.</p>
*
* <p>另外,继承这个类的任务可以通过{@link #getOwner()}来获得这个任务所属的插件。<br>
* Otherwise, tasks that extend this class can use {@link #getOwner()} to get its owner.</p>
*
* 下面是一个插件创建任务的例子:<br>An example for plugin create a task:
* <pre>
* public class ExampleTask extends PluginTask<ExamplePlugin>{
* public ExampleTask(ExamplePlugin plugin){
* super(plugin);
* }
*
* {@code @Override}
* public void onRun(int currentTick){
* getOwner().getLogger().info("Task is executed in tick "+currentTick);
* }
* }
* </pre>
*
* <p>如果要让Nukkit能够延时或循环执行这个任务,请使用{@link ServerScheduler}。<br>
* If you want Nukkit to execute this task with delay or repeat, use {@link ServerScheduler}.</p>
*
* @param <T> 这个任务所属的插件。<br>The plugin that owns this task.
* @author MagicDroidX(code) @ Nukkit Project
* @author 粉鞋大妈(javadoc) @ Nukkit Project
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public abstract class PluginTask<T extends Plugin> extends Task {
protected final T owner;
/**
* 构造一个插件拥有的任务的方法。<br>Constructs a plugin-owned task.
*
* @param owner 这个任务的所有者插件。<br>The plugin object that owns this task.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public PluginTask(T owner) {
this.owner = owner;
}
/**
* 返回这个任务的所有者插件。<br>
* Returns the owner of this task.
*
* @return 这个任务的所有者插件。<br>The plugin that owns this task.
* @since Nukkit 1.0 | Nukkit API 1.0.0
*/
public final T getOwner() {
return this.owner;
}
}
我们可以通过创建一个继承PluginTask的类来创建一个PluginTask(为什么不直接继承Task类?,因为这个类添加了getOwner()
方法,可以通过构造方法传入一份主类或任意你需要类实例的引用,方便编写代码)
package net.noyark.NukkitLearn;
import cn.nukkit.scheduler.PluginTask;
public class NukkitLearnExampleTask extends PluginTask<Main> {
public NukkitLearnExampleTask(Main owner) {
super(owner);
}
@Override
public void onRun(int i) {
}
}
上面代码是一个简单Task类的样例,我们现在带入一个插件情境进一步说明计时器的用法.
小明是一个服主,他非常喜欢玩家来他的服务器玩并且希望玩家每玩45分钟能够停下来休息一下保护视力.于是他想要开发一个插件,当玩家单次进入服务器并且呆了45分钟后,能够发送一条信息提示(Player#sendMessage(String)玩家休息一会)
于是我们来头脑风暴一下,我们知道计时器有4种类型,不知道你还能不能想得起来
小明火速写好了一个Task类
package net.noyark.NukkitLearn;
import cn.nukkit.Player;
import cn.nukkit.scheduler.PluginTask;
public class PresentationRestTask extends PluginTask<Main> {
private Player player;
public PresentationRestTask(Main owner, Player player) {
super(owner);
this.player = player;
}
@Override
public void onRun(int i) {
player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!");
}
}
以及一个主类
package net.noyark.NukkitLearn;
import cn.nukkit.event.EventHandler;
import cn.nukkit.event.EventPriority;
import cn.nukkit.event.Listener;
import cn.nukkit.event.player.PlayerJoinEvent;
import cn.nukkit.plugin.PluginBase;
public class Main extends PluginBase implements Listener {
@EventHandler(priority = EventPriority.LOW,ignoreCancelled = true)
public void onJoin(PlayerJoinEvent event){
}
}
- 使用立刻类型的计时器
- 使用立刻类型的计时器,在主类onJoin方法中填写入代码
this.getServer().getScheduler().scheduleTask(new PresentationRestTask(this,event.getPlayer())); 小明找了个玩家小红进入服务器,小红刚进入服务器,左上角就被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!".
- 使用立刻类型的计时器,在主类onJoin方法中填写入代码
- 使用延迟类型的计时器
- 使用延时类型的计时器,在主类onJoin方法中填写入代码
this.getServer().getScheduler().scheduleDelayedTask(new PresentationRestTask(this,event.getPlayer()),54000); 小明找了个玩家小红进入服务器,小红刚进入服务器45分钟后,左上角才被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!".
- 使用延时类型的计时器,在主类onJoin方法中填写入代码
- 使用循环类型的计时器
- 似乎这很不符合我们的常理,并且增加了很多不必要的开销
- 修改Task,加入时间作为判断依据
package net.noyark.NukkitLearn; import cn.nukkit.Player; import cn.nukkit.scheduler.PluginTask; public class PresentationRestTask extends PluginTask<Main> { private Player player; private long stp; public PresentationRestTask(Main owner, Player player,long stp) { super(owner); this.player = player; this.stp = stp; } @Override public void onRun(int i) { if (System.currentTimeMillis() >= stp) { player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!"); this.cancel(); } } }
- 使用循环类型的计时器,在主类onJoin方法中填写入代码
this.getServer().getScheduler().scheduleRepeatingTask(new PresentationRestTask(this,event.getPlayer(),System.currentTimeMillis() + (45 * 60 * 1000)),60*20); 可以达成效果,但是实在是
太麻烦
- 使用延时循环类型的计时器,在主类onJoin方法中填写入代码
this.getServer().getScheduler().scheduleDelayedRepeatingTask(new PresentationRestTask(this,event.getPlayer()),45 * 60 * 20,0); 同时计时器也需要修改,否则就会45分钟一到就会无尽发送消息
package net.noyark.NukkitLearn; import cn.nukkit.Player; import cn.nukkit.scheduler.PluginTask; public class PresentationRestTask extends PluginTask<Main> { private Player player; public PresentationRestTask(Main owner, Player player) { super(owner); this.player = player; } @Override public void onRun(int i) { player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!"); this.cancel(); } }
最终从各种角度来看,小明更应该使用
第二种
方法