Oops, it looks like this was already marked as resolved while I was checking and writing the below comment. I probably should have refreshed the page before posting, sorry!
This appears to be fixed in Minecraft version 24w04a. I have tested this on the game client and have looked at the code, which both indicate this being fixed.
The method isStructureChunk in StructurePlacement was refactored so that the logic for using a structure's frequency reduction method is in its own method, called applyAdditionalChunkRestrictions. This method is then used before starting to check for a structure in checkStart.
This is a much better solution than the solution I previously wrote since the logic for the frequency reduction method does not require access to a ChunkGeneratorStructureState, so refactoring it out allows for a much cleaner check in StructureCheck. I wrote my solution was in a way so that the game logic would not have to be copied to the mod containing the fix, and I determined that extracting the logic from isStructureChunk would be difficult. So, instead I used the whole method which required a very messy way of accessing a ChunkGeneratorStructureState and structure placement within StructureCheck.
Since this appears to be fixed as of now, I believe this issue can be marked as resolved.
This issue might be fixed with the latest versions of CEF. According to this commit which removes HiDPI-enabling code in CEF for Windows, Chromium was changed to support HiDPI by default. When testing the latest cefsimple sample application on Fedora Workstation 39, everything seems to be scaled properly. So, sometime in between whatever version I tested 2 years ago and the latest version of CEF, HiDPI support on Linux was fixed. The fixed version of CEF might be around or before April 2023 based on the time the commit linked above was made: I am unsure of the exact version this was fixed since building one version of CEF takes several hours on my machine making a bisect impractical.
On multiplayer servers, I have also experienced this freeze when others were exploring shipwrecks. After some attempts over the months, I think I might have a possible solution.
When using the /locate
command on buried treasure, or when buried treasure maps are generated, eventually the getNearestGeneratedStructure()
method (the random spread one, not the concentric rings one) in net.minecraft.world.level.chunk.ChunkGenerator
is called. This loops over potential structure chunks around the player according to each structure's spread and calls several other methods to determine whether or not the structure can generate there. This includes the findValidGenerationPoint()
method in net.minecraft.world.level.levelgen.structure.Structure
, which is called to return a valid generation point if the generation point is in a valid biome for that structure.
The buried treasure structure has a spread of 1. This means that every single chunk around the player will searched until a buried treasure structure is found. In comparison, other structures usually have much larger spread, meaning only a few specific chunks around the player will be searched. Buried treasure structures are still kept rare despite their small spread using the LEGACY_TYPE_2
frequency reduction method.
While a structure having a spread of 1 alone does not cause a lag spike, its combination with other factors of the buried treasure structure can. The buried treasure structure must calculate the terrain height in order to find its generation point. findGenerationPoint()
calls onTopOfChunkCenter()
in net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure
. And since findGenerationPoint()
is called for every potential structure chunk through findValidGenerationPoint()
, a small spread size of 1 causes the terrain height to be calculated many, many times, creating the observable lag spike.
Similar to normal world generation in the createStructures()
method in net.minecraft.world.level.chunk.ChunkGenerator
, the return value of the method isStructureChunk()
in net.minecraft.world.level.levelgen.structure.placement.StructurePlacement
can be checked before attempting to find a valid generation point in potential structure chunks. This way, potential structure chunks that are not valid structure chunks according to the frequency reduction method are skipped and the terrain height does not have to be calculated many times for buried treasure.
One possible place where this check could be implemented is in the canCreateStructure()
method in net.minecraft.world.level.levelgen.structure.StructureCheck
, which returns whether or not a structure can be generated in a certain chunk.
private boolean canCreateStructure(ChunkPos chunkPos, Structure structure) {
// Get the current structure in a holder.
var structureHolder = this.registryAccess.registryOrThrow(Registries.STRUCTURE).wrapAsHolder(structure);
// Loop through each placement in the structure holder.
for (var placement : this.generatorState.getPlacementsForStructure(structureHolder)) {
// Only if the chunk is a valid structure chunk according to the placement...
if (placement.isStructureChunk(this.generatorState, chunkPos.x, chunkPos.z)) {
// ...continue looking for a valid generation point to determine whether or not a structure will be generated.
return structure.findValidGenerationPoint(new Structure.GenerationContext(this.registryAccess, this.chunkGenerator, this.biomeSource, this.randomState, this.structureTemplateManager, this.seed, chunkPos, this.heightAccessor, structure.biomes()::contains)).isPresent();
}
}
// If all placements were not in structure chunks, return false.
return false;
}
Note that access to the chunk generator state in StructureCheck
is needed in order to call isStructureChunk()
. When constructing net.minecraft.world.level.levelgen.structure.StructureCheck
in net.minecraft.server.level.ServerLevel
, the chunk generator state can passed to StructureCheck
by calling getGeneratorState()
on the field chunkSource
.
In Minecraft 1.20.4, I measured the time it took to locate a buried treasure structure using the /locate
command after creating a fresh world with seed 6071974100908662531
, both without and with the potential fix.
Structure | Without fix | With fix | Difference |
---|---|---|---|
Buried Treasure | 7329ms | 138ms | -7191ms |
At least for this trial, there was a 5211% increase in speed! To be sure that the fix was not negatively impacting other things, I also measured the time to locate other structures both without and with the potential fix.
Structure | Without fix | With fix | Difference |
---|---|---|---|
Shipwreck | 18ms | 34ms | +16ms |
Mansion | 95ms | 81ms | -14ms |
Pillager Outpost | 110ms | 104ms | -6ms |
Mineshaft | 0ms | 0ms | 0ms |
Igloo | 76ms | 68ms | -8ms |
Swamp Hut | 441ms | 445ms | +4ms |
Buried Treasure (again) | 7399ms | 155ms | -7244ms |
Results indicated that there were no significant changes in the speed of locating other structures without and with the potential fix.
I have implemented the fix outlined above in a FabricMC mod for testing purposes, which seems to be successful in resolving the issue. It can be run on both the client and the server. The source code for the mod can be accessed here: https://github.com/bytzo/mc-249136.
Interestingly enough, I have experienced this issue with both HRTF being stuck on and HRTF being stuck off, depending on where I run the game (in a third party launcher HRTF is stuck on, but in FabricMC's Loom dev environment HRTF is stuck off). After some investigation, I think I found the cause of the issue.
Code Analysis
In com.mojang.blaze3d.audio.Library
, the init
method calls the setHrtf
method to set directional audio on or off.
// Inside: public void init(String audioDevice, boolean hrtfEnabled)
this.setHrtf(deviceCapabilities.ALC_SOFT_HRTF && hrtfEnabled);
Inside the setHrtf
method, the alcResetDeviceSOFT
method is called on the current audio device with two attributes. The ALC_HRTF_SOFT
attribute sets HRTF on or off, and the ALC_HRTF_ID_SOFT
attribute sets the index of the desired HRTF file, which is currently hardcoded to 0
.
// Inside: privatevoid setHrtf(boolean hrtfEnabled)
var attributes = memory.callocInt(10)
.put(SOFTHRTF.ALC_HRTF_SOFT).put(hrtfEnabled ? ALC10.ALC_TRUE : ALC10.ALC_FALSE)
.put(SOFTHRTF.ALC_HRTF_ID_SOFT).put(0)
.put(0).flip();
if (!SOFTHRTF.alcResetDeviceSOFT(this.currentDevice, attributes)) {
// ...
}
According to this text document, the alcResetDeviceSOFT
method should be used for resetting an audio device's attributes so that they may be changed during runtime.
While the code above functions correctly in resetting an audio device's attributes with new values, its effect is imminently overridden by the creation of a new audio device context, which replaces the current audio device's attributes.
// Inside: public void init(String audioDevice, boolean hrtfEnabled)
// Attributes enabling/disabling HRTF are set here...
this.setHrtf(deviceCapabilities.ALC_SOFT_HRTF && hrtfEnabled);
try (var memory = MemoryStack.stackPush()) {
var attributes = memoryStack.callocInt(3)
.put(SOFTOutputLimiter.ALC_OUTPUT_LIMITER_SOFT).put(ALC10.ALC_TRUE)
.put(0).flip();
// ...but are immediately overriden here.
this.context = ALC10.alcCreateContext(this.currentDevice, attributes);
}
Before Minecraft snapshot 23w31a, this was not an issue as no attributes were given in the creation of a new context. However, in 23w31a, the ALC_OUTPUT_LIMITER_SOFT
attribute was added to the context creation, nullifying the effect of the setHrtf
method from before.
Probably the most straightforward way to fix this is to move the calling of setHrtf
after the creation of the audio device's context.
However, this creates the opposite problem where the ALC_OUTPUT_LIMITER_SOFT
attribute is nullified by calling setHrtf
. So, it may be simpler to just set all attributes at once.
// Inside: public void init(String audioDevice, boolean hrtfEnabled)
// Deleted:
// this.setHrtf(deviceCapabilities.ALC_SOFT_HRTF && hrtfEnabled);
try (var memory = MemoryStack.stackPush()) {
// int allocation increased from 3 to 7, as there could be 4 additional int values
var attributes = memory.callocInt(7);
attributes.put(SOFTOutputLimiter.ALC_OUTPUT_LIMITER_SOFT).put(ALC10.ALC_TRUE);
// Set attributes enabling or disabling HRTF during context creation
if (ALC10.alcGetInteger(this.currentDevice, SOFTHRTF.ALC_NUM_HRTF_SPECIFIERS_SOFT) > 0) {
attributes.put(SOFTHRTF.ALC_HRTF_SOFT).put(
deviceCapabilities.ALC_SOFT_HRTF && hrtfEnabled ?
ALC10.ALC_TRUE : ALC10.ALC_FALSE);
attributes.put(SOFTHRTF.ALC_HRTF_ID_SOFT).put(0);
}
this.context = ALC10.alcCreateContext(this.currentDevice, attributes.put(0));
}
I have implemented these changes in a FabricMC mod for testing purposes, and it seems to have fixed the issue for me. The source code for the mod can be accessed here: https://github.com/bytzo/mc-265514. Obligatory warning to proceed with caution: while I can trust myself, you can't trust me, so please audit the code yourself before compiling or running.
Passing the --force-device-scale-factor
argument should allow for the scaling to be manually overridden in CEF applications. This works with the CEF sample application and is used in the Spotify Flatpak to support HiDPI displays.
However, this does not appear to work with the Minecraft Launcher. Running minecraft-launcher --force-device-scale-factor=2
has no effect on the scaling of the application. This still does not work when executing the CEF binary directly (located at .minecraft/launcher/minecraft-launcher
) with the argument.
Because of this, I don't think there are any current workarounds for this issue. If possible, having the ability to set the --force-device-scale-factor
argument could be a temporary workaround until a proper fix exists upstream.
This is most likely an issue with CEF. In the latest beta build of the CEF sample application (which you can download from https://cef-builds.spotifycdn.com/index.html), the scaling is not correct on a HiDPI display.
Updated the issue summary, as the issue affects more than just the launcher Debian package. Also updated the issue description with more details.
Starting from launcher version 2.2.2159, I am experiencing this issue again. I think this issue affects all Linux versions of the launcher, not just the Debian one, since I have experienced it on other Linux distributions. If it is helpful, I am using X11 and Mutter. I have attached a screenshot of the issue on version 2.2.2159.
[media]In snapshot 21w03a, it appears only the fix in the rideTick()
method was implemented. This was likely due to the fix in the handleMovePlayer()
method being discovered right before the release of 21w03a. I have opened a second issue at MC-212291 to address this as recommended by a moderator.
Recently on various mini-game servers, I noticed that the client is still able to dismount vehicles and experience a de-sync in some rare scenarios even with the modification containing the current proposed fix. While looking into what caused this, I found a second change that allows the client to dismount an entity, causing the de-sync I experienced. This change was made in snapshot 20w22a and is located in the handleMovePlayer()
method in net.minecraft.client.multiplayer.ClientPacketListener
. In this method, a conditional statement was added that allows the client to dismount an entity if certain conditions are met. In order to completely fix this bug, the change in the handleMovePlayer()
method during 20w22a should be reverted in addition to the current proposed fix in the rideTick()
method.
Unlike the current proposed change in the rideTick()
method, the new proposed change in the handleMovePlayer()
method does resolve MC-191714 and MC-205477. Using an updated version of the Fabric mod on 1.16.5 which reverts the change in the handleMovePlayer()
method, I was unable to reproduce both of these issues. This means that @Daniël Goossens was initially correct that a change allowing clients to dismount entities lead to both MC-191714 and MC-205477, however the mentioned change in the rideTick()
method was not the specific cause.
By implementing the changes above in both the rideTick()
and handleMovePlayer()
methods, there should no longer be any de-sync issues related to entity dismounting, as it should not be possible to dismount entities through the client. In addition, third-party servers regain the ability to keep players mounted on entities, which was used in many different ways (such as preventing players from dismounting minecarts on a roller coaster) before these changes occurred. Many third-party servers still attempt to make use of this behavior, causing undesirable issues when using the newest version of the game compared to prior versions.
Of course, the changes in both the rideTick()
and handleMovePlayer()
methods causing this bug might have been done to fix other bugs or intentionally change behavior, so reverting them would not be beneficial to the game. As done before, we can check the changelog and fixed bugs from 20w22a relating to entity mounting in order to see if this is the case. These are MC-182967, MC-184629, and MC-184640. A change was also made in 20w22a that prevents mounting entities while the shift key is held. I was unable to reproduce these previously resolved issues and was still unable to mount entities when the shift key was held using both proposed fixes above, meaning that the proposed fix in this issue should not conflict with other changes or issues from 20w22a.
Finally, the reproduction steps for this issue can be updated since we know more about the bug.
To observe the impact of the change in the rideTick()
method (reproduction steps by @Daniël Goossens still work, however require a modified server):
Join a world with cheats enabled using version 20w09a or higher.
Run the command /summon pig ~ ~ ~ {Invulnerable:1b,NoAI:1b,PersistenceRequired:1b,Saddle:1} in-game.
Mount the pig.
Pause the server thread to simulate a lag spike.
Dismount the pig using the shift key.
Resume the server thread.
Notice the de-sync. Walking around and attempting to break blocks far away from the pig will cause anti-cheat to prevent the blocks from being broken, as the server still thinks the player remains mounted on the pig. Sneaking causes the player to teleport back to the pig, as the server thinks the player has just dismounted the pig.
To observe the impact of the change in the handleMovePlayer()
method:
Join a world with cheats enabled using version 20w22a or higher.
Run the command /gamerule randomTickSpeed 0 in-game.
Run the command /fill ~-3 ~-1 ~-3 ~3 ~5 ~3 minecraft:glowstone hollow in-game.
Run the command /fill ~-2 ~-1 ~-2 ~2 ~-1 ~2 minecraft:farmland in-game.
Run the command /summon horse ~ ~ ~ {Invulnerable:1b,NoAI:1b,PersistenceRequired:1b,SaddleItem:{id:saddle,Count:1},Tame:1} in-game.
Mount the horse.
Jump at least 1 block in the air with the horse, so the farmland is broken and you are dismounted from the horse.
Notice the de-sync. Walking around and attempting to break blocks far away from the horse will cause anti-cheat to prevent the blocks from being broken, as the server still thinks the player remains mounted on the horse. Sneaking causes the player to teleport back to the horse, as the server thinks the player has just dismounted the horse.
I can confirm the changes in the rideTick()
and handleMovePlayer()
methods that cause this bug still remain in 20w51a, 1.16.5-rc1, and 1.16.5.
After comparing the rideTick()
method across several versions, I can confirm this change was made in snapshot 20w09a. This means that versions 20w09a through 20w49a are affected by this bug.
As a temporary solution, I have created a Fabric mod for 1.16 that implements the proposed change by @Daniël Goossens. Using this mod, players are unable to dismount entities through the client, meaning servers that attempt to block entity dismounting will avoid a de-sync issue with the client. This is especially useful on third-party mini-game servers that make use of this behavior, such as Hypixel's Turbo Kart Racers which had to be disabled on 1.16 and above due to this change. The source for the Fabric mod can be found at https://github.com/bytzo/mc-202202, with pre-compiled releases here.
It is however important to note that the proposed solution does not fix other mentioned issues such as MC-191714 and MC-205477. I was able to reproduce both of these issues with the Fabric mod containing the fix.
Hmm, could this have been caused by the fix for this issue?: MC-186085
Since we know this behavior was changed in 20w09a, we can determine that the fix for MC-186085 was not the cause of this bug, as MC-186085 was fixed in 1.16 Pre-release 3 which is after 20w09a. In addition to this, we can check the fixed bugs from 20w09a to see if any of those fixes may have caused this bug. 20w09a has three fixed bugs related to entity mounting, which are MC-111726, MC-148869, and MC-149042. I was not able to reproduce any of these bugs using the Fabric mod containing the fix. This should mean that this bug was not caused by fixes for other issues.
As of version 2.29.15, the launcher seems to be scaling correctly when using Fedora Workstation 40 at 200% scaling. Is anyone else able to confirm that this is issue fixed?