Chris B.

Terra on Minestom

Last updated on

I’ve been working on a Minecraft server for a while now that uses Minestom as the server software. Minestom is a great server software, but it does not come with world generation out of the box. After some tests with my own world generation, I decided to use Terra because I couldn’t get the performance and polish I wanted with my own world generation in a reasonable amount of time.

There was just one issue: Terra is not compatible with Minestom. This blog post is about how I made it compatible.

Terra

Terra is a world generation library for Minecraft that is fully customizable since it uses add-ons and a configuration pack to generate a world. This makes it very powerful and flexible. Building these config packs way easier and more accessible than writing your own world generation code. It’s also well-documented, which is a big plus. I highly recommend Terra if you want to generate cool worlds for your Minecraft server. It is available for Paper(+), Fabric, Forge, CLI and now also Minestom. Check it out here.

The Terra Generation Process works as follows (simplified): First, a ChunkGenerator generates a ProtoChunk. A ProtoChunk is a chunk that is not yet fully generated, only the base terrain exists here. After a chunk has been generated, it can be used for feature generation. Feature Generation is the process of adding things like trees, ores, and structures to the world. This process assumes a fully generated world for reading and writing blocks, the so-called ProtoWorld because the features just place blocks regardless of chunk borders. But there is one (very big) problem: Minecraft worlds are enormous so generating the whole world at once is not feasible. My current implementation generates ProtoChunks on demand, whenever the feature generation needs them and caches them for later use. This is probably not the most efficient way to do it, but it works for now.

Minestom

Minestom is a Java Library for creating Minecraft servers. It is very fast and lightweight while not implementing many vanilla features, which is why I chose it for my server. The Minestom-Community is also very active and helpful, although the documentation is a bit lacking. You can find more about Minestom here.

Compatibility

Terra is written with multi-platform support in mind. This made it fairly easy to get some basic generation up and running. Just adding the gradle module and the required libraries was enough to get a testserver working. Minestom generates Worlds on a chunk-by-chunk basis, which works nicely with the first ProtoChunk step of Terra. The feature generation step is a bit more complicated since it needs to access out-of-chunk blocks as well as write to them. Writing is fairly easy using the Minestom fork-API. Reading not so much. Currently, I cache the last n generated chunks in a LRU-Cache using the caffeine library and generate the ProtoChunks on demand. After a fair amount of fiddling and help from the Terra Discord, I got this:

Terra on Minestom Terra on Minestom Terra on Minestom

Getting to speed

Before, I mentioned that the generation was slow. I wanted to measure the impact of the cache and try different sizes. For this, I built a simple benchmark that generates a world and measures the time it takes to generate it. The results look something like this:

Preloaded 625 chunks in world in 19722.635233ms. That's 31.689477223319898 Chunks/s
CacheStats{hitCount=39082432, missCount=7656, loadSuccessCount=7656, loadFailureCount=0, totalLoadTime=182010163523, evictionCount=7624, evictionWeight=7624}

This benchmark was run on my local machine. The numbers will vary depending on your hardware and jvm settings. As such, use these numbers in comparison to each other, not as an absolute value.

Now, let’s try different cache sizes:

Cache SizeTime (ms)Chunks/sCache Load CountCache Hit Rate (%)
161080985.74430499.912
244496913.91800399.964
322608123.9851599.981
481704036.6380399.992
641024561.0168199.997
96739384.590399.998
128722786.477299.998
256729085.779599.999
512703688.972799.999

I tried running the generator without any cache, but it was so slow that I didn’t even bother to wait for it to finish. I will not include the numbers here because they are not meaningful.

But since a picture is worth a thousand words, here is a chart of cache size and the time it takes to generate the world:

The chart shows that the time it takes to generate the world decreases with increasing cache size. The sweet spot seems to be around 128 cached chunks. After that, the time it takes to generate the world does not decrease significantly anymore. So let’s stick with 128 chunks for now. With that, the performance seems to be acceptable, for now at least.

How can I use it?

You can now! Since I didn’t want to wait for the pull request to be merged, I uploaded the library to my maven repository.

To use it, add the following to your build.gradle.kts:

repositories {
  maven {
      url = uri("https://mvn.everbuild.org/public")
  }

  maven {
      url = uri("https://repo.codemc.io/repository/maven-public/")
  }
}

or your pom.xml:

<repositories>
    <repository>
        <id>everbuild</id>
        <url>https://mvn.everbuild.org/public</url>
    </repository>
    <repository>
        <id>codemc</id>
        <url>https://repo.codemc.io/repository/maven-public/</url>
    </repository>
</repositories>

Then add the dependency:

dependencies {
    implementation("com.dfsek.terra:minestom:6.6.0-BETA+810d10ac0")
}

or

<dependency>
  <groupId>com.dfsek.terra</groupId>
  <artifactId>minestom</artifactId>
  <version>6.6.0-BETA+d0bc006fa</version>
</dependency>

Now you can use Terra in your Minestom project. To use the generator on an existing instance, you can use the following code:

TerraMinestomWorldBuilder.from(instance)
    .defaultPack()
    .attach();

This will generate a world with the default pack. The following methods are available:

  • defaultPack(): Uses the default pack that comes with Terra.
  • pack(ConfigPack): Uses a custom pack via the Terra API.
  • packById(String): Uses a custom pack via the pack id.
  • seed(long): Sets the seed of the world, if not set, a random seed will be used.
  • entityFactory(EntityFactory): Sets the entity factory for the world. This can be used to add AI to entities.
  • blockEntityFactory(BlockEntityFactory): Sets the block entity factory for the world. This can be used to implement block entities.
  • attach(): Attaches the generator to the instance and returns the Terra ServerWorld.

You’ll need to use one of the pack methods and the attach method to generate a world. The other methods are optional.

Thanks for trying out Terra on Minestom!

Conclusion

I am very happy with the progress I made so far. I learned a lot about world generation and Minestom and got to know the Terra community a bit better. As always I hope that this project will be useful for other Minestom users as well.

If you got any questions or feedback, feel free to open an issue on my fork or join my discord server. I am always happy to help.