The ImanitySpigot Asynchronous Chunk Load and Generation system is originally inspired by the Paper Team’s 1.13 Patch,
You can see their patch here.
But to port this patch into a 1.8 Spigot... It’s difficult
The Old Algorithm
(Minecraft 1.12 Release, the last version using this algorithm)
The algorithm has separated into 2 stages
Terrain Generation will generate the undulation, caves,
structures (such as village the mountain)’s foundation,
position managing, and biomes
During this stage, Most of the work is done in a ChunkSnapshot
which is a snapshot that contains 16x16x256 blocks
and Terrain generator will only be accessible to ChunkSnapshot
which means this part is overall thread-safety
After we have the terrain and foundations, In this stage it will start populating trees, water lakes, ores, and blocks of all existing structures that are calculated from the last stage, etc
And this stage differs from how the block placing
Since Mojang assume that the block of population stage can be placed across many chunks
Minecraft will push chunks that have terrain generated into the Loaded Chunk List And do the population work based on chunk location
So basically that means each block placing action will need to lookup for the chunk and it can also generate new chunks
(Minecraft 1.13 Release, the last version using this algorithm)
After 1.13 released, Minecraft also kind of redesigned the system, they combined All the Stages into a bundle of Chunk Factory
Within this factory, It stores all work in progress chunks in a separate list, And each chunk will have a stage mark
Before chunk run the stage
The Factory will check if this stage requires any external chunks
And try to process external chunk stages until it's ready and put into a cache chunk bundles for the stage to process
Finally, when chunk has finished finalized stage, It will be pushed into Loaded Chunk List
You should be able to tell the difference from here
Minecraft 1.13 will handle the cache of chunks in a separate list for the population stage
And chunks wouldn't be pushed into Loaded Chunk List before all stages completed
Which is inaccessible before full chunk completed.
which makes it much simpler to move the work off from Main Thread
The Solution of ImanitySpigot
(oh! I have an idea!)
So to achieve Asynchronous chunk generation
We also need to have our caching system and let the population stage works within our cache
Here is a simple chart ans some explaination on how our system works:
(This system is 80% inspired by PaperSpigot & 1.13 Minecraft with minor tweaks)
This is a very simplified chart to let everyone understand the system easily
But there is much more going around it
Current there are 5 stages:
1. Empty - The Stage without any process available, This stage will not be saved
2. Terrain - The Stage that chunk terrain generated
3. Populated - The Stage that population is completed
4. Lighting - The Stage that lighting is populated
But as we making it we are thinking Is it necessary to have it within this system?
After sometimes of analyzing, We think that this feature is useful if you want an accurate as it's not accessible before all completed
But also it could slowdown chunk generate speed, so we leave it toggle-able
5. Spawn Creatures - The Stage that creature being summoned
If Lighting wasn't populated, this stage wouldn't be called.
6. Finalized - The Stage that the chunk has been marked as ready
But there is a downside for the system
If you have looked at the chart carefully, You should notice that getChunkAt Vanilla will block the thread until the async process finished, It is for thread safety sake since normally vanilla getChunkAt expected to getChunkAt Immediately without a callback, And to keep chunk generate safe, All we can do is to block the thread and finish work on the worker, which means the getChunkAt Immediately function is generally much slower then single thread handling
So If there are many calls to the getChunkAt Immediately function, It could cause potential diff spikes!
We also notice the issue, So we will also change the Minecraft structure to fit the callback getChunk, But most of the plugin probably doesn't care about that! So we will also need to create some debug system to log if any getChunkAt Immediately were called so people can recognize what getChunkAt Immediately is unintentional and can cause lag spikes
And we will also need to create a guide on how to optimize server around async chunk...
This feature is getting more complex now :^)
OK, While we understood what and how to achieve it, we begin to work on the system
But we were soon faced with the most difficult problem of all...
I Hate Minecraft
We quickly notice that something seems to be wrong...
Originally we think that basically, we build the system and it could go wild, but afterward, we notice Minecraft’s Block Populator system
Remember that what I say at introducing the Population Stage in the Old algorithm? Yep, Even tho we finished the caching system we also need to make the population stage works with our caching system, And Minecraft literature contains a million of classes sharing the same code bases that aren’t friendly to our async structure
After noticing it, we quickly realize how time-consuming this will be
and why there still isn’t any async chunk generation spigot fork until this day...
But after all... We didn’t give up
we took all of our time to fix this 7-year-old thing to fit our structure
The process is extremely painful, with Hours of repetitive work and sometimes a weird error will appear and you don't even know why it occurs... Definitely not a fun process at all :^)
(This part is kinda for developers)
In the end, We still achieved it in a good way!
By creating new classes for each populator of the original Minecraft code base,
We were able to be compatible with most of the plugin if they do NMS thing to those classes,
But it doesn't mean the Asynchronous Chunk Generation system will work on Custom World Generator Plugins
IT IS IMPOSSIBLE TO MADE PLUGINS WORK AS ASYNCHRONOUS WITHOUT MODIFYING PLUGIN IT SELF
Since most of plugins shares similar idea as Minecraft Generation algorithm, We cannot expect that those plugins can work with our way
And unlike Spigot or PaperSpigot, most of the plugins on Spigot forums or MC-Market forums wouldn't want to collaborate with us and just modify code their self
So it's either we opening pull requests to their repository (But more likely they will reject us) Or we fork them, tho both isn't an easy task too :^)
So if Custom World Generator Plugin is playing a big role in your server this system is not for you, but remember that ImanitySpigot still works with those plugins, just Asynchronous Chunk Generation will not be applied.
Even tho this structure works correctly as we expected, There is still a lot of small issues around it, If you ever seen Minecraft codes, you know that Minecraft likes to share things, A lot of fields were shared between multiple classes/objects in some unnecessary way, makes it very not thread-safe when it comes to multi-threading.
These minor things is actually annoying to say the least, It is very easy to be ignored, sometimes you didn't even modify it, and it just become the source of errors...
(That sounds like a complex feature! But what does it means for the Spigot overall?)
Here is an example video, this is a comparison in between async chunk enabled & disabled
As you can see from the video, while async chunk enabled, the TPS still staying above 19.85TPS
while async chunk disabled, when you just fly out 10 chunks, the TPS flashing number freezes, in this case TPS in siderbar isn't even accurate, since Minecraft doesn't have time to calculate TPS since it's just lagged out
It is normally for both to see void, since LunarClient 8x is really fast, none of them can follow the speed of it
Tho the generation speed on the left is significant faster since async chunk generator wouldn't get slowdown by Main Thread works
But generation speed aside, For the performance it self, Async Chunk basically dominated non-async chunk
These test are done without pre-loaded chunks, view distance is 10. localhost.
Benefit of Asynchronous Chunk Generation:
- Zero Lag spike while loading chunk is possible
- Loading chunk is overall faster compare to disabled
- More flexible feature for customers to utilize!
Downside of Asynchronous Chunk Generation:
- Much more hardware consuming, Requires both more RAM and Better Processor
- tho "Zero Lag spike while loading chunk is possible" but customer also need to know how to utilize it, otherwise it's could done reverse effect
What kind of server might find it useful?
- 2B2T kind of server
- Or any kind of server that need a lot of chunk generations
Alright! That's basically what I have for you today!
I hope you enjoyed this devlog and probably received something from it!