Instead of spawning these when players first log in and have the
entities update their state on_step, just run a single globalstep
that walks across all players and inventories, and then across all
entities, in a couple of passes, and reconciles everything.
This should also deal with entities that are lost for random or
arbitrary reasons, which seems to be the remaining known cause of
wield items spontaneously disappearing.
The code is also simpler (a single loop with a few linear passes)
and probably more efficient.
Keep a running estimate, synced every step, of the number of total
falling entities, and delay pumice collapse if there are already
too many.
Spread decay more evenly across time with chance/interval.
Pumice that isn't properly supported will collapse, using rules
somewhat similar to FDMCube and FDM 3D printing support.
- Pumice can be supported directly below.
- Pumice can form small "overhangs" if non-falling non-pumice
solid nodes back it.
- Pumice can form small "bridges" over gaps up to 3 nodes between
supports.
This makes it a little easier to target a specific/different region
of the face we're rotating on, since the rotation arrow HUD is
delayed over the network and thus doesn't provide good crisp
feedback for players.
- Things can be registered to rotate
- Rotations apply correctly based on direction arrows
- Doors and optics registered
Known issues:
- param2 equivalence, i.e. ensuring each node has the lowest value
for param2 that is semantically equivalent.
Works:
- Solved rotation direction
- Solved HUDs
To Do:
- Rotatability control by registration (groups)
- Solve facedir calculations
- Hook and apply rotations
Instead of just generating "clouds" of "hint" nodes in the same
general strata where lode ore could also exist, have the hints
directly "radiate" outward from actual ore nodes. This means that
finding a hint node should always guaranteed lead you to an ore
node, as long as you haven't already harvested it, and as long as
you guess the direction correctly.
One overall consequence of this is that the ore hints are a lot
sparser overall, and there are lots of areas where you're near
ore-bearing stone but can't tell because the hints just happened
not to reach that far.
This might be slightly expensive compared to the old method, since
we're essentially casting rays in Lua, but it seems to be
surprisingly efficient in testing so far.
Of course, is_full_sun gets away with it because it's using an
equality comparison, and equality works between numbers and nil,
but a greater-than comparison doesn't.
Specifically for the case of sponge squeezers, not only do we need
enough map around the artificial water to be loaded, we need it to
actually be active, to be certain that the squeezer has an
opportunity to run and keep the water fresh. This change should
prevent specific water nodes from disappearing from sponge
squeezers when they're right on the edge of the active mapblock
range.
This genericizes the API for checking the state of a mapblock, and
adds a function to check if a mapblock is active, not merely loaded.
Any gameplay mechanic that could be done before only in full
direct sunlight now works anywhere using the light from an
immediately adjacent shining lens.
The only things that still require full true sunlight are purely
cosmetic things like ambient sound effects.
Instead of doing 16 pseudo-raycasts per second, lower it to a max
of 4, and don't do "catch up" raycasts if we are stepping at a
slower rate than that, just increase the weight of the current
cast to account for the entire time frame.
This will reduce the precision of radiation detection in the short
run, but is probably still good enough, especially with radiation
simulation already being very probabilistic.
This will hopefully mitigate per-player performance load on more
heavily loaded servers with many players.
Testing did not reveal this to have a meaningful impact; most of
the lag I was hoping to prevent with it is still there. Even though
this is a big operation and runs tens of thousands of iterations
per second in some cases, it's still efficient enough overall that
it doesn't impact lag significantly.
Leaving it disabled would cause there to be bugs/exploits with
tall columns of stuff remaining suspended in mid air if the upper
parts are unloaded when the bottom is dug out. While there is no
way to avoid that entirely, as long as the middle area where
some falling node is floating above air gets loaded eventually,
it will at least self-resolve with the feature intact as
originally.
This reverts commit 580fcc462292f495b531735ca098faae9060b9c7.
Previously, this was triggering "on every load" to ensure that
there is the lowest possible chance that any falling nodes are left
suspended in air, to potentially fall on players and crush or jump
scare them.
However, it seems that LBMs trigger not when a mapblock is loaded
into memory (to be kept there until unloaded after the timeout) but
when they are activated (which can happen repeatedly for the same
mapblock if a player is walking back and forth so that it enters
and leaves their active radius). This was causing a storm of LBM
events to fire, which, while the actions are very small (just
adding a position to a queue) the number of times the engine calls
the method (and the repeated construction and garbage collection
of tens of thousands of vectors) is suspected of causing
significant performance issues.
For now, replace this LBM with a once-ever-per-mapblock LBM which
fires only after a block is initially generated. We will need to
catch anything we miss later with the cleanup ABM, which should be
a lot gentler on performance.
What we were calling "data.n" basically means "an entity already
has this position covered so another is not needed." We check it
below in the "creation" phase to skip creating where an ent already
exists, but we ALSO check it in the "validation" phase, above where
it's set, to catch duplicate entities during the loop pass.
This was a guard against the filtered entity collection being
modified during the pass, which can cause pairs iterators to go
off in some weird direction and miss or double count some entries.
However, we never add/remove luaentities during this iteration,
so it's safe, and we can avoid creating and GCing a separate array
each pass.
In theory this was supposed to be more efficient than walking over
every luaentity, but in practice, eventually the majority of
luaentities end up being visinvs anyway, and the filtration is
cheap enough to perform on the fly.
The bigger issue is that the visinv index was getting out of date
due to not accounting for entities that were being unloaded
passively by the engine rather than actively removed. This would
eventually bog down a server after enough uptime and certain areas
containing lots of visinvs had been unloaded and reloaded a few
times.
Nodes that are stuck in a deferred check state can get unloaded,
and we don't want to keep trying to re-check them again, even if
we are only doing it every few seconds now. Allow them to fall
out of the queue entirely, and we can pick them up again next time
they get loaded.
If a node couldn't be checked for falling due to it having an
unloaded area below it, we were kicking it back into the queue
for immediate recheck on the next step, which caused us to often
process a batch every step when those nodes were in a stable
unloaded state. Instead, kick them to a time deferred queue that
gets merged back into the main queue only every 2 seconds. This
should cut down on how many times we have to keep reshuffling the
batch and garbage-collecting old batch tables.
Some time ago, we were "labeling" callbacks so that we could use
a specially modified profiler to identify time spent per specific
callback. This became largely unnecessary thanks to the
jitprofiler mod, which shows specific line numbers anyway. Now,
the labeling has actually even broken because it interferes with
MT's newest built-in instrumentation, which expects functions, not
callable tables. Since the indirection wasn't helping, and could
only have caused some small performance costs anyway, we might as
well just remove it instead of trying to make it work.
Unfortunately "labeled" callbacks were all over the game, so now
these all have to be removed...
Each of these showed up as ~2% on a flame graph:
- Skip the sanity check for nodecore.func vs nodecore:func
typos, which don't seem to happen in the wild lately.
- Directly construct a vector in unsuspend instead of using the
more expensive abstract vector functions.
- Debounce YCTIWY database saving to cut down on serialization
calls, especially since writes are IIRC debounced by MTE
anyway.
Instead of updating entity age every tick for every envsound
entity, record their "birth" time, compute age ad-hoc, and run
a slower interval to cull expired ones. This should cut down
a lot on the per-step costs of these entities.
The old API simply registered functions, which listened for all
events and received all parameters. This was inefficient because:
- Not all calls actually used the "node" parameter, so if we don't
need it, and the event that triggers the notification doesn't
provide one, we used to have to do a get_node for it.
- A lot of things that can change a node in the world can't
actually cause certain things to change. For example,
remove_node can never cause a cookable node to be in a position
for a recipe check (and making plain air cookable would not
be sane).
Allowing us to selectively ignore certain events, and to indicate
that we aren't using the "node" param so the API doesn't need to go
out of its way to provide it, should make a lot of things more
efficient.
The biggest problems we were seeing were when a lot of liquids were
transforming due to "pumhell" deep underground, where pumice
freezing and melting causes constant reflows of pumwater. This is
an intentional game mechanic to make those areas dangerous to
explore or try to develop without taming them, but the only notify
callback we really need to trigger is the optic revalidation one,
which should just be checking for indexed beams to reevaluate and
finding none in most cases. We were seeing a ton of lag when in
these areas, leading to the hypothesis that it's all the other
unnecessary checks that causes this slowdown.
Instead of using limited-lifetime particles that need to be
refreshed, use sprite entities, which never need to be replaced
as long as they stay loaded.
There was a short "glitch" when replacing the particles where
they could appear twice, or be absent, for a frame or two. If
we're taking the screenshot at insanely high resolution/AA, which
the automation in this mod enables, we may have fractional FPS,
and the time window when the particles are in an invalid state
may expand to the point where it's impossible to capture a
screenshot with the correct smoke particles.
Making the smoke particles stable this way makes it possible to
reliably capture a screenshot with correct smoke particles at
insanely high quality / low framerate.
The documentation says that position is absolute unless you pass
in absolute = false. This is not correct, as passing in
absolute = true seems to be necessary to fix it. That's what was
necessary to get the behavior back in line with what the old
set_bone_position was.
Moving the head up 1/2 unit was apparently some workaround for an
issue that existed in some previous version, but no longer
applies to 5.9.0 at least.