Skip to content

Regionized Data

Folia uses an internal API it created called RegionizedData. This API is what helps manage localized world data per-region. With Canvas, plugins are capable of interfacing into this API, creating their own regionized data implementations per region.

The interface provided by Canvas for this is called IRegionizedData<T>, which represents a type of region-local data that can be fetched or attached to a region through a few different ways. But first, we need to actually dive into how to create this data.

You can create this data at any point in the server lifecycle. The IRegionizedData interface takes a generic type, T, for the type of object it is. You can create a new regionized data instance via Server#createRegionizedData. Here is an example of how to make a basic IRegionizedData<T> implementation:

static final RegionTickData.IRegionizedData<TestRegionData> EXAMPLE_REGION_DATA = Bukkit.getServer().createRegionizedData(
(tickData, world) -> new TestRegionData(tickData, world), new RegionTickData.IRegionizedData.IRegionizedCallback<>() {
@Override
public void merge(final TestRegionData from, final TestRegionData into, final long fromTickOffset) {
}
@Override
public void split(final TestRegionData from, final int chunkToRegionShift, final Long2ReferenceOpenHashMap<TestRegionData> regionToData, final ReferenceOpenHashSet<TestRegionData> dataSet) {
}
}
);

The createRegionizedData method takes 2 arguments. The first, a bi-function constructor that takes the region tick data that the instance is being created for, and the world it belongs to, and returns a new object of the generic type T. The second argument, is the regionized callback. The regionized callback is an interface that defines the logic for merging and splitting region data when a region splits or merges. On region creation, the constructor is called, and on region death(from unload or something), nothing happens.

The IRegionizedCallback is the interface that is invoked on region split and merge operations that helps to manage regionized data transferring between regions.

Merging is handled via the merge method, which provides the T from, T into, and long fromTickOffset. During this operation, all data from one region should be merged into the other region, combining the two regions. All data should be combined to the into argument, as the from region is dead now, and is descheduled. The fromTickOffset is the addend to absolute tick deadlines stored in the from region to adjust to the into region.

Each region ticks independently, so absolute tick deadlines can’t be compared directly across regions. To convert a deadline from one region to another, we need to account for the difference in their current ticks.

Say we have an absolute deadline, deadlineFrom, in region from, and we want the equivalent deadline in region into. The key here is that the relative deadline(how many ticks remain) should be the same in both regions.

Starting from that idea:

  1. The relative deadline in from is deadlineFrom - currentTickFrom (ticks remaining)
  2. To get the equivalent absolute deadline in into, we add those remaining ticks to intos current tick: deadlineTo = (deadlineFrom - currentTickFrom) + currentTickTo
  3. So, deadlineTo = deadlineFrom + (currentTickTo - currentTickFrom)

The term (currentTickTo - currentTickFrom) is constant for a given pair of regions at a given moment, so we can precompute it as fromTickOffset and simply write:

deadlineTo = deadlineFrom + fromTickOffset
  • You won’t need to calculate the fromTickOffset as Canvas provides this for you in the merge method, which is the only place this value is utilized.

This is used internally for things that store a tick-based deadline, and should be used as described above to maintain consistency of regionized data when conducting merge operations.

Splitting is hanlded via the split method, which provides T from, int chunkToRegionShift, L2RMap regionToData, and RefSet dataSet. In this operation, we are splitting the T from, into the ReferenceOpenHashSet dataSet.

Folia provides us with two utilities to help split this data, the int chunkToRegionShift, and the Long2ReferenceOpenHashMap regionToData. The chunkToRegionShift is the signed right-shift value used to convert chunk coordinates into region section coordinates.

The regiontoData map is a map of region section coordinates to the data that is owned by the region which occupies the region section. Note that all sections are strictly only in the region from the from region, meaning attempting to fetch data from another region not part of the split via this map will result in null returned. This is strictly containing owned sections from the original region to the new split regions. You can utilize these for data that is location-based, like entities and block entities for example.

You can utilize them like so, using an Entity as an example:

Entity entity = ...;
Chunk chunkAt = entity.getChunk();
long sectionKey = getRegionSectionCoordinates(chunkAt.getX(), chunkAt.getZ(), chunkToRegionShift);
T dataAtEntityPosition = regionToData.get(sectionKey);

Unlike merging, there is no absolute tick offset provided. This is because the new regions formed from the split will start at the same tick number, and so no adjustment is required.

To fetch regionized data, it is always recommended to fetch this on the local region. Regionized data can be fetched from other threads that don’t currently own the region, but this is largely discouraged as this defeats the point of regionized threading, and can cause corruption and concurrency issues depending on what you are trying to do. To fetch the current T from an IRegionizedData instance, you can call:

T localData = Bukkit.getServer().getLocalRegionizedData(EXAMPLE_REGION_DATA);

This will throw an IllegalStateException if called on a thread that doesn’t own a region. This could be because the current thread isn’t a region tick runner, or the current thread is processing the global tick.