第二章 第三节 计时器的介绍
参与编写者:zzz1999
建议学习时间:3分钟
学习要点:了解计时器的功能以及如何使用
一.概念
1.计时器的作用是 立即,延时,循环或延时循环 某些任务,通过计时器我们可以实现例如 在线奖励 , 倒计时 等插件功能, 是一些复杂插件中经常用到的功能之一.
2.执行计时器代码块的单元叫做任务,我们可以编写任务代码来使用计时器的功能.
3.任务执行分为两种类型,一种是同步,一种是异步.同步通过主线程所调度管理执行,异步通过主线程调度管理,异步线程池执行.所以,太多的任务会导致服务器卡顿.
4.计时器的功能以minecraft刻
(tick)作为单位,一刻为现实时间的0.05秒,现实时间的1秒为20刻,服务器每秒刻数由TPS决定,最高为20.
二.代码
我们先来看一下nukkit的Task类.
1 | package cn.nukkit.scheduler; |
我们可以捕捉到几个关键信息,首先我们可以看到onRun(int)
方法,它是一个抽象方法并且nk开发者很贴心的写上了注释示意我们这是Task中会被运行到代码块.
还有onCancel()
和cancel()
方法,onCancel()内部是一个空代码块,当Task被取消的时候(被任务调度器取消或者手动调用cancel()取消都会触发),这段代码块会被调用
比较常见的用途比如小游戏时间到的时候,一些判定胜负,给予奖励,关闭房间的一些代码可以用Task来实现或者调用.
我们还可以使用cancel()
方法来强制取消一个Task,例如你想要写一个登录插件,每个玩家进入服务器时创建一个Task循环发送提示登录的信息,如果玩家输入了正确的密码并且登录成功才会取消掉这个Task.这时候就可以使用cancel方法了(仅举例,实际生产环境中,每个玩家进入创一个计时器是很费资源的,不会使用这种方法)
Handler是服务器调度Task所用到的一些东西,可以不懂,不妨碍计时器的使用.
开发者可以使用PluginBase#getServer()
方法获取到Server对象,然后使用Server#getSchedule()
获取到计时器的实例类.
1 | public class Main extends PluginBase{ |
获取到后,可以创建 立即,延时,循环或延时循环 类型的计时器
立即(ServerSchedule#scheduleTask)
延时(ServerSchedule#scheduleDelayedTask)
循环(ServerSchedule#scheduleRepeatingTask)
延时循环(ServerSchedule#scheduleDelayedRepeatingTask)
计时器是需要一个被触发的时间间隔的,以tick为单位,如果是循环计时器,则是每次循环的时间,如果是延时,则是延迟多少tick才被触发,如果是延迟循环,则会要求填写延迟间隔与循环间隔.
举例将会在下一小节讲到
3.使用
下面会使用到PluginTask
类,你需要对PluginTask
类的方法做一个初步的了解,以及知道它继承了Task类.
1 | package cn.nukkit.scheduler; |
我们可以通过创建一个继承PluginTask的类来创建一个PluginTask(为什么不直接继承Task类?,因为这个类添加了getOwner()
方法,可以通过构造方法传入一份主类或任意你需要类实例的引用,方便编写代码)
1 |
|
上面代码是一个简单Task类的样例,我们现在带入一个插件情境进一步说明计时器的用法.
小明是一个服主,他非常喜欢玩家来他的服务器玩并且希望玩家每玩45分钟能够停下来休息一下保护视力.于是他想要开发一个插件,当玩家单次进入服务器并且呆了45分钟后,能够发送一条信息提示(Player#sendMessage(String)玩家休息一会)
于是我们来头脑风暴一下,我们知道计时器有4种类型,不知道你还能不能想得起来
立即(ServerSchedule#scheduleTask)
延时(ServerSchedule#scheduleDelayedTask)
循环(ServerSchedule#scheduleRepeatingTask)
延时循环(ServerSchedule#scheduleDelayedRepeatingTask)
小明火速写好了一个Task类
1 | package net.noyark.NukkitLearn; |
以及一个主类
1 |
|
思考过程
- 使用立刻类型的计时器
- 使用立刻类型的计时器,在主类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,加入时间作为判断依据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package 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;
}
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分钟一到就会无尽发送消息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package 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;
}
public void onRun(int i) {
player.sendMessage("你已经玩了45分钟了,快下线休息一下眼睛吧!");
this.cancel();
}
}
最终从各种角度来看,小明更应该使用
第二种
方法