Skip to main content

Lightweight and Controllable Delayed Tasks

Introduction

As we know, TaskKit provides a utility module combining tasks, timing, delay listeners, timeout listeners, and more. You can run delayed tasks with runOnce, for example:

TaskKit.runOnce(() -> log.info("2 Seconds"), 2, TimeUnit.SECONDS);
TaskKit.runOnce(() -> log.info("1 Minute"), 1, TimeUnit.MINUTES)

But in some cases, we need controllable delayed tasks, where delay time can change based on later business logic, for example: increasing delay, reducing delay, cancelling tasks, and other controllable operations.


Lightweight controllable delayed task - features

  • Single responsibility principle.
  • Task executes when the specified time is reached.
  • Task can be cancelled.
  • Task can be overwritten.
  • Delay time can be increased.
  • Delay time can be reduced.
  • Task listener callback can be configured.
  • Internally uses Netty HashedWheelTimer, easily supporting millions of tasks.

Usage Scenarios

  • Scenario 1: when a player uses an item, cooldown is reduced; or some events increase cooldown. During delayed task execution, temporary buffs can be applied to players, such as temporary double attack.
  • Scenario 2: before the game starts, after room creation, auto-dissolve room after 1 minute (execute task at a future time). Before dissolving, whenever a player joins, postpone dissolve by 30 seconds (increase delay). When room size is greater than 5, cancel the auto-dissolve rule (cancel task).

These two scenarios mainly describe that delayed tasks need to support increasing/reducing delay time and support cancellation.

Usage Example

Demo - Delayed Task

The sample task executes after 1 second.

  • code 6: increase delay by 1 second.
  • code 7: start the task.
public void runDelayTask() {
DelayTaskKit.of(() -> {
// your code
log.info("1 seconds");
})
.plusTime(Duration.ofSeconds(1))
.task();
}

Demo - Increase Delay Time

This sample demonstrates increasing delay time. The delayed task finally executes after 1.5 seconds.

  • code 7: add 1 second delay.
  • code 10: add 0.5 second delay.
public void plusDelayTime() {
long timeMillis = System.currentTimeMillis();
DelayTask delayTask = DelayTaskKit.of(() -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();

delayTask.plusTimeMillis(500);
}

Demo - Decrease Delay Time

This sample demonstrates reducing delay time. The delayed task finally executes after 0.5 seconds.

  • code 8: add 1 second delay.
  • code 11: reduce delay by 0.5 second.
public void minusDelayTime() {
long timeMillis = System.currentTimeMillis();

DelayTask delayTask = DelayTaskKit.of(() -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();

delayTask.minusTimeMillis(500);
}

Demo - Overwrite Delayed Task

This sample demonstrates overwriting delayed tasks.

  • code 5: start task.
  • code 11: because taskId is the same, it overwrites the previous delayed task, and the overwritten task is removed.
public void coverDelayTask() throws InterruptedException {
String taskId = "1";
DelayTaskKit.of(taskId, () -> log.info("task - 1"))
.plusTime(Duration.ofSeconds(2))
.task();

TimeUnit.MILLISECONDS.sleep(500);

long timeMillis = System.currentTimeMillis();

DelayTask delayTask = DelayTaskKit.of(taskId, () -> {
long value = System.currentTimeMillis() - timeMillis;
log.info("{}", value);
})
.plusTime(Duration.ofSeconds(1))
.task();
}

Demo - Cancel Delayed Task

This sample demonstrates cancelling delayed tasks.

  • code 11: cancel task through DelayTask.
  • code 22: cancel task through taskId.
public void cancelDelayTask() throws InterruptedException {
DelayTask delayTask = DelayTaskKit.of(() -> {
log.info("delayTask.cancel");
})
.plusTime(Duration.ofSeconds(2))
.task();

TimeUnit.MILLISECONDS.sleep(500);

delayTask.isActive(); // true
delayTask.cancel();
delayTask.isActive(); // false

// ------------ taskId ----------
String taskId = "1";
DelayTaskKit.of(taskId, () -> log.info("DelayTaskKit.cancel"))
.plusTime(Duration.ofSeconds(1))
.task();

TimeUnit.MILLISECONDS.sleep(500);

DelayTaskKit.cancel(taskId);
}

Demo - Find Delayed Task

This sample demonstrates finding delayed tasks.

  • code 9: find the delayed task by taskId.
  • code 15: find delayed task by taskId; if present, execute given logic.
public void optionalDelayTask() {
String newTaskId = "1";
DelayTaskKit.of(newTaskId, () -> log.info("hello DelayTask"))
.plusTime(Duration.ofSeconds(1))
.plusTime(Duration.ofMillis(1000))
.plusTimeMillis(500)
.task();

Optional<DelayTask> optionalDelayTask = DelayTaskKit.optional(newTaskId);
if (optionalDelayTask.isPresent()) {
DelayTask delayTask = optionalDelayTask.get();
log.info("{}", delayTask);
}

DelayTaskKit.ifPresent(newTaskId, delayTask -> {
// your code
delayTask.plusTimeMillis(500);
});
}

Demo - Enhanced TaskListener

For more details on the TaskListener interface, see TaskKit.

 public void customTaskListener() {
DelayTaskKit.of(new TaskListener() {
@Override
public void onUpdate() {
log.info("TaskListener");
}

@Override
public boolean triggerUpdate() {
return TaskListener.super.triggerUpdate();
}

@Override
public Executor getExecutor() {
return TaskListener.super.getExecutor();
}

@Override
public void onException(Throwable e) {
TaskListener.super.onException(e);
}
})
.plusTime(Duration.ofMillis(1700))
.task();
}

Comprehensive Example

A comprehensive sample is used here. Business content:

  1. Simulate enhancement when a player uses an item.
  2. When true, cooldown is reduced by 0.5 seconds and a luck bonus is applied.

The following starts a delayed task. When triggered, ShootTaskListener.onUpdate() executes. ShootTaskListener simulates bullet attacks on enemies. If an item is used (that is, luck == true), attack increases.

The logic is simple: use a random value to simulate whether an item is used. When true, trigger time is shortened and ShootTaskListener is enhanced.

  • code 2: add ShootTaskListener.
  • code 3: trigger after 1.5 seconds.
  • code 9: get TaskListener.
public void example() {
DelayTask delayTask = DelayTaskKit.of(new ShootTaskListener("Enemy"))
.plusTimeMillis(1500)
.task();

if (RandomKit.randomBoolean()) {
delayTask.minusTime(Duration.ofMillis(500));

ShootTaskListener shootTaskListener = delayTask.getTaskListener();
shootTaskListener.setLuck(true);
}
}

@Slf4j
final class ShootTaskListener implements TaskListener {
final String targetEntity;
boolean luck;
int attack = 10;

ShootTaskListener(String targetEntity) {
this.targetEntity = targetEntity;
}

@Override
public void onUpdate() {
int value = luck ? attack * 2 : attack;
log.info("value", value);
}
}

Summary

As you can see, lightweight controllable delayed tasks are simple to use in just 3 steps:

  1. Set listener TaskListener.
  2. Add delay time.
  3. Start task.

Developers can apply different controls based on business changes. Since the listener reuses the TaskListener interface, more flexible settings are possible. For example: specify executor, control whether to trigger onUpdate() callback, exception callback, and more.

tip

If delay time is not set, the task executes immediately.

public void hello() {
DelayTaskKit.of(() -> {
log.info("hello");
}).task();
}