Lag-free background computation with subtick timing.
- Installation
- Configuration
- Usage
- User functions
- Chat triggers
- Scoreboard objectives
- Entity tags
- Event hooks
- FAQ
Run function tickbuster:.module/setup
to add required scoreboard objectives. There are no other datapack dependencies.
The module may be configured via triggers by anyone assigned as a module admin, however player tags must be assigned directly by server operators.
The target tick time is a threshold, in milliseconds, that tells the system when to break from the subtick loop. As soon as a tick lasts up to this threshold, the system will yield control back to the game.
The default target tick time is set well below the game's built-in 50ms target:
trigger tkb.target set 20
Something a bit less conservative:
trigger tkb.target set 40
Pushing the game to its limit:
trigger tkb.target set 50
Setting the target 0
will effectively disable any background computation:
trigger tkb.target set 0
Allowed values are 0..50
and anything outside this range will wrap accordingly - unless overclocking is enabled, in which case anything within 0..1000
is permitted.
Normally the target tick time is capped at 50
but there is a toggle that will allow you to go beyond. Generally this is not recommended because it will directly result in lag and high CPU usage. Use at your own risk.
Toggle overclocking:
trigger tkb.overclock
Allow a player to manage the module:
tag <player> add tickbuster.admin
Expose a player to debugging mechanisms:
tag <player> add tickbuster.debug
There are two important things you need to do in order to integrate with the sub-tick loop:
- Add a single function to the
#tickbuster:vote
tag that conditionally callstickbuster:api/vote/in
. You should only vote when there's work to do in the current tick. These votes are used to determine whether to run the sub-tick loop at all and when to break it. - Add any other functions to the
#tickbuster:loop
tag to include them as background computation. Keep in mind that these functions may run even if you have not voted-in. The recommended best-practice is to keep track of whether you have voted-in, and use this as an escape condition in your entry point to the sub-tick loop.
It is recommended you split your background computation into small, dividable slices, and only run what is necessary each iteration. Large/complex background functions have the potential to hog the pipeline and may still cause lag on their own.
In any given tick, the sub-tick loop will not run unless there is at least one vote to do so. This can be achieved by calling tickbuster:api/vote/in
- from a function tagged with #tickbuster:vote
- once for each module (or any other unit of computation) that wishes to utilize the sub-tick loop in the current tick.
Once the sub-tick loop has begun, it will continue until either:
- the target tick time has been reached, causing the sub-tick loop to run off the end; or
- all votes have been removed, causing the sub-tick loop to break early.
It may be useful to break out of the sub-tick loop early if there is no further computation to be done in the current tick. However, because several modules may be using the sub-tick loop in any given tick, it is generally unwise to assume you are the only one using it. Instead, you may vote to break out of the sub-tick loop (usually in your #tickbuster:loop
handle) by calling tickbuster:api/vote/out
. If all of the modules that opted-in also opt-out, the sub-tick loop will break early.
Note that generally you should not remove any more votes than you added; i.e. calling vote/out
more than vote/in
in any given tick.
Function | Description |
---|---|
tickbuster:-user\001_admin |
Mark the executing player as a module admin. |
Trigger | Description |
---|---|
tkb.overclock |
Toggle overclocking. |
tkb.target |
Set the target tick time. |
Objective | Criteria | Usage | Description |
---|---|---|---|
tkb.config |
dummy |
Input | Reserved for configuration options. |
tkb.math |
dummy |
Read-only | Reserved for internal calculations. |
Entity Tag | Description |
---|---|
tickbuster.admin |
Present on players who are managing the module. |
tickbuster.debug |
Present on players who are debugging the module. |
Function Tag | Description |
---|---|
#tickbuster:after_loop |
Run once per tick, after the target tick time has been met and the subtick loop runs off. |
#tickbuster:before_loop |
Run once per tick, before entering the subtick loop. |
#tickbuster:break_loop |
Run at most once per tick whenever the sub-tick loop is broken, before after_loop is run. |
#tickbuster:loop |
The main subtick loop, run an arbitrary number of times each tick until the target tick time is met. |
#tickbuster:vote |
The callback voting procedure, used to collect votes before determining whether to run the sub-tick loop. |
- What does it do?
- How do I use it?
- How does it work?
- Can I use the worldborder for anything else?
- Why are some of my computations being cut-off?
- Why is the after-loop hook not running?
- Where did the idea come from?
It lets you run tagged functions as many times per tick as possible without causing lag.
The idea is that all tagged functions will run as many times per tick as possible, via round-robin, without causing lag.
All you need to do is tag your background functions with the appropriate function tag, and vote for the sub-tick loop each tick you want it to run. See the usage section for details.
It works thanks to the fact that the worldborder runs on system time and updates asynchronous to the main gameloop.
Unlike anything else in commands, this allows us to determine the amount of time that passes between commands in the same tick (subtick timing). This comes at the cost of using the worldborder for its intended purpose, hence the requirement for exclusive worldborder control.
No, this module requires exclusive control of the worldborder.
Unfortunately the worldborder is our only viable means of measuring real-time passage with commands. If any commands outside this module modify the worldborder, something is almost guaranteed to go wrong.
Note that other commands may safely continue to query the worldborder via worldborder get
; the danger is in setting the worldborder such as with worldborder add
and worldborder set
.
Most likely because maxCommandChainLength
is being hit during the subtick loop.
Depending on how inexpensive your background computation is, you may need to increase the maxCommandChainLength
to prevent the subtick loop from hitting the command cap before the target tick time. This usually won't happen with the default value unless you're running many small operations per iteration.
See: Why are some of my computations being cut-off?
If you run into an issue where #tickbuster:after_loop
does not run, it's probably because the subtick loop is hitting maxCommandChainLength
during the subtick loop and being cut-off before actually reaching the target tick time. In this case, the after-loop hook will never even get a chance to run.
Based on Dr. Brian Lorgon111's lagless prioritized command scheduler concept to "maximize command programming CPU utilization without introducing game lag." The concept has been simplified and adapted for general use in the form of a datapack.