Fabric API

Fabric API

106M Downloads

Hooks to allow registration of custom tracked data fields on entities.

i509VCB opened this issue ยท 3 comments

commented

Note half the stuff here may be gibberish, this is just a concept

Some context

In vanilla Minecraft, tracked data is typically used to sync data values of an entity to the client.

For example this is used for tracking the amount of stingers or arrows a living entity has stuck in itself.

The problem

However there are two fundamental issues with this system that make it harder for mods to use this system for adding custom data to track on existing entities:

Tracked data values are keyed via network id, between 0 and 254. So each entity is only allowed to have 254 registered tracked data values at once. The id of each tracked data value is assigned is based on registration order at .

Mods CANNOT register their tracked data values before vanilla or they risk having the client crash. Typically the solution to this has been to create the tracked data fields in a mixin so load order isn't affected. And then injecting at TAIL of initDataTracker to start tracking those values.

So a slightly different load order can cause a tracked data value to be incorrectly tracked and cause the client to crash due to de-serializing tracked data of the wrong type. This makes the built-in tracked data system fragile if mods directly touch entity code for these matters.

Also custom entities with their own tracked data is another important aspect to consider if we wish to use a system to work around vanilla's system.

What about working with vanilla's system

In my opinion, vanilla's system could work but we would need to do the following:

  • Abstract the entire process of registering mod added tracked data onto an entity.
  • In the case of mismatches, remap any mismatched custom tracked data keys (whether we do this on client or server is a different question).

Issues

  • Mods which currently mixin to add custom tracked data are effectively in the wild west till all those mods adopt this system.
  • Vanilla system has a limit of 255 entries per entity. In some absurdly large modpacks this could cause issues
  • Clients joining other servers with more mods than server could has tracked data id mismatches which are very hard to track.

Summary of vanilla

The above explanation leads me to believe salvaging vanilla's system is inadequate due to size limit and reliance on int ids.

A new subsystem

So I shall propose a concept for a separate subsystem that replicates the functionality of tracked data while being much more flexible and compatible.

Setting course

In order for this new system to be resilient to the flaws that the vanilla data tracker system has, we should avoid use of int value keys for syncing. Unless we have a specific sync packet that will ensure we have the proper client-server mapping

Using registry(ies)?

Possibly handle these via registries of custom tracked data values? This would allow us to use more efficient int ids for synchronizing tracked data since registry sync will handle this. However registry sync mismatches like the block id mismatch could be fatal in this case due to serialization of data.

Thinking simply

To keep things simple, I recommend that custom tracked data should be registered via an identifier key with a corresponding (de)serializer. The tracked data serializer would be similar to the following:

interface TrackedDataSerializer<T> {
    void serialize(PacketByteBuf buf, T value, DynamicRegistryManager registryManager);

    T deserialize(PacketByteBuf buf, DynamicRegistryManager registryManager) throws DataDeserializationException;
}

The dynamic registry manager is present in case implementors of api wish to track values in the dynamic registries.

The exception is not final in name or even existence, the idea there is to throw an exception if the value could not be de-serialized for any reason.

We need to determine how to react to this kind of error (please comment below with ideas).

Serializers would be registered via a simple static method:

public static <T> TrackedDataType<T> registerTrackedDataType(Identifier id, TrackedDataSerializer<T> serializer) {
    [...]
}

Duplicate entries are not allowed in the method.

At login (this would depend on networking v1), the client will validate it supports all the registered tracked data types the server notifies the client about. If there are any missing tracked data types, then disconnect the client with and make disconnect message the list of missing tracked data types.

Registering tracked data onto the entity

For use with entities, there are multiple ways to approach this. Fabric's own tracked data should respect inheritance like in vanilla.

This needs further discussion but registration using entity classes may be a solution.

Using this on entities

Since the value has been registered, you would use the TrackedDataType to obtain the value from the entity. To do this, get the TrackedEntityData from the entity. With this interface, you can query multiple aspects about an entity's tracked data.

interface TrackedEntityData {
    boolean supports(TrackedDataType<T> dataType);

    void setValue(TrackedDataType<T> dataType, T value);

    void getValue(TrackedDataType<T> dataType);
}

When getting/setting a value, if the tracked data type is not supported, an exception should be thrown.

An example of this in use:

class MyEntity extends LivingEntity {
    private Foo foo = DefaultFoo.FOO;
    [...]

    void doThing() {
        if (!world.isClient()) {
            // Send the current foo value to the client
            TrackedEntityData.get(this).setValue(MyTrackedDataTypes.FOO, this.foo)
        }
    }
}

Networking internals

Since Fabric API will manage the creation and handling of packets for these values, the contents of the packet will be mostly internal. My proposal uses the fabric:private/tracked_data as the channel name. This is in the private section since implementors of the api are not intended to send the packet.

The packet is defined using the following data structure:

fabric:private/tracked_data {
    Int // Refers to network id used to identify entity on client
    Identifier // Id of tracked data
    PacketByteBuf // Serialized value of tracked data
}

If the entity referred to in the packet via network id is not present, the implementation should release the payload and fail silently.

If the data cannot be de-serialized, log the error and ignore the packet.

commented

hmm yea the v1 suffix is probably important

commented

Given the nature of vanilla's entity data tracker system (dependence on hierarchy), I strongly oppose reusing the vanilla system. Fabric can just make another type of packet channel to communicate these data updates stored in some other form.

commented

Also for packet, fabric namespace is bad: should be the mod id of the specific implementation mod. private/ prefix is unnecessary too. I suggest add like /v1 suffix etc so when we have v2 format, we can still receive the v1 format while not sending it.