Manipulateurs de données

Accéder aux données et les modifier

Un manipulateur de données représente un certain composant et toutes ses données. Il stocke une représentation des données et peut être offert ou créé à partir d’un data holder qui possède un composant correspondant. Encore une fois, nous allons utiliser un exemple. Et encore une fois nous allons essayer de soigner quelqu’un (ou quelque chose).

Exemple: Soigner avec les manipulateurs de données

import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.DataTransactionResult;
import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;
import org.spongepowered.api.data.value.mutable.MutableBoundedValue;

import java.util.Optional;

public static DataTransactionResult heal(DataHolder target) {
    Optional<HealthData> healthOptional = target.get(HealthData.class);
    if (healthOptional.isPresent()) {
        HealthData healthData = healthOptional.get();

        double maxHealth = healthData.maxHealth().get();
        MutableBoundedValue<Double> currentHealth = healthData.health();
        currentHealth.set(maxHealth);
        healthData.set(currentHealth);

        target.offer(healthData);
    }
}

D’abord nous avons besoin de vérifier ci notre cible a des données sur la santé. Nous le ferons en lui demandant d’abord de nous fournir ses données de santé en lui passant sa classe à la méthode get(). Nous récupérons un Optional que nous pouvons utiliser pour notre vérification. Cet Optional sera absent si notre cible ne supporte pas HealthData ou si elle le support mais à l’heure actuelle ne détient aucune donnée de santé.

Si les données de santé sont présentes, elles contiennent maintenant une copie mutable des données présentes dans le data holder. Nous faisons nos retouches et rendons finalement les données changées à notre cible, où elle est acceptée (encore, offer() va retourner un DataTransactionResult que nous allons ne pas tenir compte dans cet exemple et y revenir plus tard).

Comme vous pouvez le voir, les résultats pour health() et maxHealth() sont encore des valeurs clés qu’on obtient du DataHolder. Comme le MutableBoundedValue que nous recevons en appelant health() contient encore seulement une copie des données, nous avons d’abord besoin d’appliquer nos changements au DataManipulator avant que nous puissions offrir le healthData à notre cible.

Astuce

Règle #1 de l’API de données : Tout ce que vous recevez est une copie. Donc à chaque fois que vous modifiez quelque chose, assurez-vous que vos changements sont retournés à l’endroit d’où la valeur d’origine venait.

Une autre modification possible est d’enlever complètement un DataManipulator. Cela se fait via la méthode remove() qui accepte une référence de classe pour le type de DataManipulator à supprimer. Certaines données ne peuvent pas être supprimées et les tentatives pour le faire retourneront toujours un DataTransactionResult indiquant un échec. Le code suivant tente de supprimer un custom name d’un DataHolder donné. Encore une fois, le résultat de la transaction est ignoré.

Exemple de Code : Supprimer un display name personnalisé

import org.spongepowered.api.data.manipulator.mutable.DisplayNameData;

public void removeName(DataHolder target) {
    target.remove(DisplayNameData.class);
}

DataManipulator vs. Keys

Si vous avez comparé les deux exemples de guérison, vous pouvez vous demander « Pourquoi s’embêter avec les data manipulators, les keys sont bien plus simples” et avoir raison - pour obtenir et définir les valeurs uniques. Mais le vrai mérite d’un data manipulator est qu’il contient toutes les données se rapportant à un certain composant. Prenons un autre exemple.

Exemple de Code : Échanger la santé de deux data holders

public void swapHealth(DataHolder targetA, DataHolder targetB) {
    if (targetA.supports(HealthData.class) && targetB.supports(HealthData.class)) {
        HealthData healthA = targetA.getOrCreate(HealthData.class).get();
        HealthData healthB = targetB.getOrCreate(HealthData.class).get();
        targetA.offer(healthB);
        targetB.offer(healthA);
    }
}

Tout d’abord, nous vérifions si les deux cibles supportent HealthData. Si c’est le cas, nous enregistrons la santé des deux dans une variable chacun. Nous n’avons pas besoin de nous ennuyer avec les Optional cette fois puisque nous avons vérifié que le HealthData est supporté et que la méthode getOrCreate() garantit que même si aucune donnée n’est présente, des valeurs par défaut sont générées.

Puis nous offrons simplement les données de santé sauvegardées aux autres cibles, donc en changeant leur état de santé entre eux.

Cet exemple fait avec des Keys aurait été un peu plus long et plus compliqué car il faudrait prendre soin de chaque clé individuelle par nous-mêmes. Et si à la place de la santé nous avions échangé un autre data manipulator contenant encore plus de données (peut-être InvisibilityData qui contient même une liste), nous aurions eu plus de travail à faire. Mais puisque le data holder prend lui-même sui de toutes les données se rapportant à lui, nous pourrions même modifier la function ci-dessus pour échanger les données arbitraires entre deux holders.

Exemple de Code : Échanger des data manipulators

import org.spongepowered.api.data.manipulator.DataManipulator;

public  <T extends DataManipulator<?,?>> void swapData(DataHolder targetA, DataHolder targetB, Class<T> dataClass) {
   if (targetA.supports(dataClass) && targetB.supports(dataClass)) {
       T dataA = targetA.getOrCreate(dataClass).get();
       T dataB = targetB.getOrCreate(dataClass).get();
       targetA.offer(dataB);
       targetB.offer(dataA);
   }
}

L’abilité d’écrire une fonction qui peut simplement échanger n’importe quelles données d’un data holder avec les mêmes données sur un autre data holder démontre l’objectif de design principale de l’API de données : Une compatibilité maximale dans l’ensemble de l’API.

Data Manipulators Mutables vs. Immuables

Pour chaque data manipulator, il y a un ImmutableDataManipulator correspondant. Par exemple, le HealthData et le ImmutableHealthData contiennent les mêmes données, seulement, le dernier retourne de nouvelles instances lors de requêtes de données modifiées.

La conversion entre les data manipulators mutables et immuables se fait via les méthodes asImmutable() et asMutable(), qui renvoient chacun une copie des données. La seule façon d’obtenir un data manipulator immuable depuis un data holder est d’en obtenir un qui est mutable et puis d’utiliser asImmutable().

Un possible cas d’utilisation pour ceci serait un événement personnalisé appelé quand un joueur est guéri. Il devrait fournir des copies des données sur la santé d’avant et d’après, mais les events listeners ne devraient pas pouvoir les modifier. Par conséquent, nous pouvons écrire notre événemnt pour fournir uniquement des instances de ImmutableHealthData. De cette façon, même si un code tiers peut interagir avec nos données, nous pouvons être assurés qu’elles ne seront pas modifiées.

Données Absentes

Comme mentionné ci-dessus, la méthode get() peut retourner un Optional vide si l’une des conditions suivantes est vraie :

  • Le DataHolder ne supporte pas le DataManipulator donné

  • Le DataHolder supporte le DataManipulator, mais ne détient actuellement aucune donnée de ce type

Il y a une grande différence sémantique entre les données étant absentes et les données composées des valeurs par défaut. Alors que le dernier est toujours possible, il y a des cas où il est impossible pour un DataHolder de supporter un type de données et donc de ne pas les garder. Des exemples de ceux-ci incluent :

  • La HealthData est toujours présent sur tous les DataHolder (vanilla) qui le supportent

  • Le DisplayNameData est toujours présent sur un Player, mais peut être absent sur d’autres entités.