[core] implement API alternatives for ABMs, nodetimers #2564
Labels
No Label
#P1 CRITICAL
#P2: HIGH
#P3: elevated
#P4 priority: medium
#P6: low
#Review
annoying
API
bug
code quality
combat
commands
compatibility
configurability
contribution inside
controls
core feature
creative mode
delayed for engine release
documentation
duplicate
enhancement
environment
feature request
gameplay
graphics
ground content conflict
GUI/HUD
help wanted
incomplete feature
invalid / won't fix
items
looking for contributor
mapgen
meta
mineclone2+
Minecraft >= 1.13
Minecraft >= 1.17
missing feature
mobile
mobs
mod support
model needed
multiplayer
Needs adoption
needs discussion
needs engine change
needs more information
needs research
nodes
non-mob entities
performance
player
possible close
redstone
release notes
schematics
Skyblock
sounds
Testing / Retest
tools
translation
unconfirmed
mcl5
mcla
Media missing
No Milestone
No project
No Assignees
5 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: VoxeLibre/VoxeLibre#2564
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
problem
we and modders depend too much on ABMs and nodetimers because its easy and familiar. the result is that we use ABMs for mechanics that don't really need ABMs. this pollutes the ABMs runtime space that's better allocated for mechanics that truly needs ABMs.
examples:
kelp currently use nodetimers to detect if its stems is submerged or not. at large scales (think servers), this is going to buckle.
cactus currently use ABMs to detect if its sides have nodes so it would break. this isn't going to break anything. the issue is that accurate, fast responses would require we have a high frequency ABMs.
bubble columns mod abused ABMs for many of its mechanics.
"when all you have is a sledgehammer, everything starts to look like nails".
suggestions
we implement our own APIs for easier implementation of such mechanics. currently the following have been discussed in discord:
node_update
andnode_update_propagate
. this would be immensely useful for many mechanics present, as many of them are triggered by players or other nodes (or automata like redstone). instead of relying on frequency of checks, we would be using callbacks to define the mechanics. @SumianVoice have written the following draft:node_player_globalstep
. @cora if you could say more about this that'll be great.see also
Well similar to like e.g. lava cauldrons do damage to players. The problem is that you need to find the nodes you want to deal with first so what youd generally do is search around a player.
This does however mean it's probably not a good idea to make that as a drop-in abm replacement because the strategies to find these nodes can get expensive and we want to make it so that it does not necessarily searches huge volumes every time.
ABMs are good at running stuff on huge numbers of nodes, node timers are good at running stuff regularly and reliably on a small number of nodes - there's a niche in between them both cant do very well.
nodestep = {
nodes = {},
interval =,
range =, --around players
func = function(pos) end,
}
So basically a player globalstep that searches for the nodes in range with find_nodes_in_area and runs the function on them.
It's not pretty, i agree and it comes with it's own set of compromises but for some things this is probably the best way.
Advantage over ABM:
Advantage over node timers:
Disadvantages
Another thing i've been playing with is artificially making slow abms faster without having the abm run more often by interpolating additional runs in between the actual runs - look at the new mcl_dripping for an example
What I'd suggest is both.
An ABM's job is to essentially just make sure nodes get updated over time regardless of player action right?
An ABM could then call
node_update_propagate
on nodes when they change, but only when they change. Then again I'm not sure I understand ABMs fully yet..Then for player actions and so on, like pistons pushing nodes or nodes being dug, they use
node_update_propagate
too.Essentially just offloading some of the ABM's job onto an explicit function call rather than an always running check.
Well, it's called active block modifier so things will only happen when a player is near usually.
The thing about abms is that they will be dropped when they run longer than 200ms per second total which is why it's generally good to only use them for things where they make sense like plant growth or leaf decay.
mcl2 has been using them for "reactive" nodes e.g. when a node is supposed to do damage to a player which requires them to run very often.
That's essentially what the node globalstep thing is supposed to solve.
node updates
i believe sumi's draft should be enough to get the gist of the idea.
pros
cons
unreliable in edge cases → require cleanup callbacks. identifying and handling those cases mean we no longer need cleanup callbacks.
buckles when scaling in terms of propagation → updates need to be restrictively propagated. otherwise, we risk processing too many nodes.
can cause lag spikes → avoid this by possibly splitting updates to run on the next server step. probably introduces exploits. fun
alternatively, we don't split updates at all. let it update only what it can and let cleanup handle the rest.
approximated+conditional ABMs (and other frequency/interval-based callbacks)
ABMs typically run within the radius of 4 mapblocks from the player (servers may
choose to run at smaller radii, prob 2 mapblocks). what we could do is idle ABMs
if its too far from a certain range (in nodes). we do this by: checking for the
nearest player's position and then deciding instead of doing the accurate
mechanic: no-op or execute an approximated mechanic.
to do this efficiently, we need to maintain a list of player positions so we
don't iterate through players for each running ABM.
generalising further, this radius check is just a predicate. so really, the
check could be anything as long it allows the ABM to choose between accurate
mechanics and no-op/approximated mechanics.
generalising even further, this need not be limited to ABMs, but also applicable
to nodetimers, globalstep, etc.
pros:
cons:
node globalstep → focused area manips
i think the current proposed node globalstep is kinda restrictive. we don't always want to presistently run a callback of a single node within range of a player. i think we can generalise this further into a globalstep-based API: focused area manips (FAMs). the idea is that we want to persistantly run updates within an area of a focus point, nearby a player, until (optionally) a condition is reached.
pros:
very familiar.
expensive. FAMs should give more control to us to prioritize, schedule, etc.
should run. FAMs gives more control over when to run or when to idle and at
what stage. idle earlier, the better.
access for ABMs. with FAMs we can just iterate tens of nodes quickly without
resorting to get_node(). this should also incentivize some other optimizations
i haven't realize yet.
and generic optimizations and edge cases ABMs failed to fulfill. additionally,
i've written such that the API is more dynamic compared to ABMs.
cons:
easy to fuck this up. lag spikes is likely to be introduced.
relevant images from ruben about map read/write (lower is better):
Here's my proposal to throw onto this pile:
Bulk Node Steps (BNS)
Oxidation Example (ABM Replacement):
Furnace Example (Node Timer Replacement):
Pros:
Cons:
I would caution against use of timers. I recently unpicked it from kelp. Basically it used 20% of the CPU because it avoided ABM's. The code was complex, messy, and worse performing than the abm's it was allegedly trying to optimise.
Profiling data here for kelp: #3417
I agree. Node timers would be a terrible idea.
I haven't even started making a prototype of my proposal, but my first though on how to implement it is using a global step or a periodic scheduler task that selectively runs updates on map blocks around players.
A facility like what is described here might be useful in completely eliminating the use of node timers. Provided, of course, that it doesn't have its own performance issues.
On the topic of ABM's though, if we want to improve things, we could potentially make them more frequent with less chance. So if for example, it runs on every node every 10s, making it run on 1/10th of the nodes every second, or 1/20th of the nodes every 0.5s could spread that work out evenly and cut down on lag spikes, or ticks in which ABM's have to abandon from too much processing happening in that tick. Could be worth doing a little analysis on this first for the worst offenders.
It would be great to hear people's views around this.
I think spreading out the load across more time steps could be a good way to reduce lag spikes. I do have some mild concern about making sure that nodes don't get starved of updates. I'm not sure how minetest handles its chance and depending on implementation, some nodes could be processed more, and others less, than an ABM that has a chance=1 would do.
Absolutely worth it in my opinion. First step to resolving performance issues is to get reliable data on where the code is spending its time. Just a matter of prioritizing available developer time, is all.
Some more information about the solution that Exile uses:
kromka-chleba said: