Skip to content

Latest commit

 

History

History
343 lines (260 loc) · 12.9 KB

2-3_计时器的介绍.md

File metadata and controls

343 lines (260 loc) · 12.9 KB

上一节 下一节

第二章 第三节 计时器的介绍

参与编写者:zzz1999

建议学习时间:3分钟

学习要点:了解计时器的功能以及如何使用

一.概念 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&amp;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&lt;ExamplePlugin&gt;{
 *         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种类型,不知道你还能不能想得起来

立即(ServerSchedule#scheduleTask)
延时(ServerSchedule#scheduleDelayedTask)
循环(ServerSchedule#scheduleRepeatingTask)
延时循环(ServerSchedule#scheduleDelayedRepeatingTask)

小明火速写好了一个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方法中填写入代码

      this.getServer().getScheduler().scheduleDelayedTask(new PresentationRestTask(this,event.getPlayer()),54000); 小明找了个玩家小红进入服务器,小红刚进入服务器45分钟后,左上角才被发了消息:"你已经玩了45分钟了,快下线休息一下眼睛吧!".

  • 使用循环类型的计时器
    • 似乎这很不符合我们的常理,并且增加了很多不必要的开销
    • 修改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();
          }
      }

最终从各种角度来看,小明更应该使用第二种方法

上一节 下一节