Skip to content

Meta System - Visión General

El Meta System de Hytale es un sistema flexible y potente para almacenar y recuperar metadatos asociados a objetos del juego como entidades, bloques, items y más.

¿Qué es el Meta System?

El Meta System proporciona una forma type-safe y eficiente de asociar datos personalizados a objetos del juego sin modificar sus clases directamente.

Características Principales

  • Type-safe: Los metadatos están tipados en tiempo de compilación
  • Eficiente: Almacenamiento optimizado con arrays internos
  • Flexible: Soporta cualquier tipo de dato
  • Persistente: Opcionalmente puede persistir entre sesiones
  • Thread-safe: Sincronización con locks de lectura/escritura

Arquitectura del Sistema

graph TD
    A[IMetaStore] -->|implementa| B[IMetaStoreImpl]
    B -->|usa| C[MetaKey]
    D[MetaRegistry] -->|crea| C
    C -->|persistente| E[PersistentMetaKey]
    E -->|serializa con| F[Codec]

    style A fill:#9cf,stroke:#333
    style C fill:#f9c,stroke:#333
    style D fill:#fc9,stroke:#333

Componentes Principales

1. MetaKey

Identificador único y tipado para un tipo de metadato.

Ubicación: com.hypixel.hytale.server.core.meta.MetaKey.java

public class MetaKey<T> {
    private final int id;

    public int getId() {
        return this.id;
    }
}

Ejemplo de uso:

// Declarar una MetaKey para almacenar un String
MetaKey<String> CUSTOM_NAME = registry.registerMetaObject(
    entity -> "Sin nombre"
);

// Usar la key
entity.putMetaObject(CUSTOM_NAME, "Jugador123");
String name = entity.getMetaObject(CUSTOM_NAME);

2. IMetaStore

Interfaz que define operaciones para almacenar y recuperar metadatos.

Ubicación: com.hypixel.hytale.server.core.meta.IMetaStore.java

public interface IMetaStore<K> {
    // Obtener valor (con inicialización automática)
    <T> T getMetaObject(MetaKey<T> key);

    // Obtener valor (sin inicialización, puede ser null)
    @Nullable
    <T> T getIfPresentMetaObject(MetaKey<T> key);

    // Establecer valor
    @Nullable
    <T> T putMetaObject(MetaKey<T> key, T obj);

    // Eliminar valor
    @Nullable
    <T> T removeMetaObject(MetaKey<T> key);

    // Verificar existencia
    boolean hasMetaObject(MetaKey<?> key);

    // Iterar todos los metadatos
    void forEachMetaObject(MetaEntryConsumer consumer);
}

3. MetaRegistry

Registro central para crear y gestionar MetaKeys.

Ubicación: com.hypixel.hytale.server.core.meta.MetaRegistry.java

public class MetaRegistry<K> implements IMetaRegistry<K> {
    // Registrar MetaKey no persistente
    <T> MetaKey<T> registerMetaObject(Function<K, T> function);

    // Registrar MetaKey persistente con codec
    <T> MetaKey<T> registerMetaObject(
        Function<K, T> function,
        boolean persistent,
        String keyName,
        Codec<T> codec
    );

    // Crear nuevo objeto meta
    <T> T newMetaObject(MetaKey<T> key, K parent);
}

Flujo de Trabajo

sequenceDiagram
    participant Dev as Desarrollador
    participant Reg as MetaRegistry
    participant Key as MetaKey
    participant Store as IMetaStore
    participant Entity as Entity

    Dev->>Reg: registerMetaObject(supplier)
    Reg->>Key: new MetaKey(id)
    Key-->>Reg: MetaKey creada
    Reg-->>Dev: MetaKey<T>

    Note over Dev,Entity: Uso de la MetaKey

    Dev->>Entity: putMetaObject(key, value)
    Entity->>Store: putMetaObject(key, value)
    Store->>Store: Almacenar en array[key.id]
    Store-->>Entity: Valor anterior (o null)
    Entity-->>Dev: Confirmación

    Dev->>Entity: getMetaObject(key)
    Entity->>Store: getMetaObject(key)

    alt Valor existe
        Store-->>Entity: Valor almacenado
    else Valor no existe
        Store->>Reg: newMetaObject(key)
        Reg-->>Store: Valor por defecto
        Store->>Store: Almacenar valor
        Store-->>Entity: Valor por defecto
    end

    Entity-->>Dev: Valor

Tipos de MetaKeys

MetaKey Normal (No Persistente)

// Crear MetaKey simple
MetaKey<Integer> KILL_COUNT = registry.registerMetaObject(
    entity -> 0  // Valor por defecto
);

// Usar
entity.putMetaObject(KILL_COUNT, 10);
int kills = entity.getMetaObject(KILL_COUNT);

PersistentMetaKey (Persistente)

// Crear MetaKey persistente
MetaKey<String> PLAYER_TITLE = registry.registerMetaObject(
    player -> "Novato",      // Valor por defecto
    true,                     // Persistente
    "player_title",          // Nombre para serialización
    Codec.STRING             // Codec para serializar
);

// Los valores se guardan automáticamente entre sesiones
player.putMetaObject(PLAYER_TITLE, "Maestro");
// Al reiniciar el servidor, el valor persiste

Uso Básico

Registrar MetaKey

public class MiPlugin {
    // Registro global de MetaKeys
    private static final MetaRegistry<Entity> ENTITY_META =
        new MetaRegistry<>();

    // MetaKeys de ejemplo
    public static final MetaKey<Boolean> IS_CUSTOM =
        ENTITY_META.registerMetaObject(e -> false);

    public static final MetaKey<Long> LAST_INTERACTION =
        ENTITY_META.registerMetaObject(e -> System.currentTimeMillis());

    public static final MetaKey<List<String>> TAGS =
        ENTITY_META.registerMetaObject(e -> new ArrayList<>());
}

Almacenar y Recuperar Datos

public void handleEntity(Entity entity) {
    // Establecer valores
    entity.putMetaObject(IS_CUSTOM, true);
    entity.putMetaObject(LAST_INTERACTION, System.currentTimeMillis());

    // Obtener valores
    boolean isCustom = entity.getMetaObject(IS_CUSTOM);
    long lastTime = entity.getMetaObject(LAST_INTERACTION);

    // Agregar tags
    List<String> tags = entity.getMetaObject(TAGS);
    tags.add("importante");
    tags.add("quest");

    // Verificar existencia
    if (entity.hasMetaObject(IS_CUSTOM)) {
        System.out.println("Entidad tiene metadato IS_CUSTOM");
    }

    // Eliminar metadato
    entity.removeMetaObject(IS_CUSTOM);
}

Obtener sin Inicializar

// getMetaObject() inicializa automáticamente si no existe
String name = entity.getMetaObject(CUSTOM_NAME); // Nunca null

// getIfPresentMetaObject() retorna null si no existe
String nameOrNull = entity.getIfPresentMetaObject(CUSTOM_NAME); // Puede ser null

if (nameOrNull != null) {
    System.out.println("Nombre: " + nameOrNull);
} else {
    System.out.println("Sin nombre asignado");
}

Iterar Metadatos

entity.forEachMetaObject((id, value) -> {
    System.out.println("Meta[" + id + "] = " + value);
});

Casos de Uso

1. Datos Temporales de Entidad

public class CombatSystem {
    // Último atacante de una entidad
    private static final MetaKey<EntityRef> LAST_ATTACKER =
        registry.registerMetaObject(e -> null);

    // Tiempo del último ataque
    private static final MetaKey<Long> LAST_ATTACK_TIME =
        registry.registerMetaObject(e -> 0L);

    public void onEntityDamaged(Entity victim, Entity attacker) {
        victim.putMetaObject(LAST_ATTACKER, attacker.getRef());
        victim.putMetaObject(LAST_ATTACK_TIME, System.currentTimeMillis());
    }

    public Entity getLastAttacker(Entity victim) {
        EntityRef ref = victim.getIfPresentMetaObject(LAST_ATTACKER);
        return ref != null ? ref.getEntity() : null;
    }
}

2. Sistema de Cooldowns

public class CooldownManager {
    private static final MetaKey<Map<String, Long>> COOLDOWNS =
        registry.registerMetaObject(e -> new HashMap<>());

    public void setCooldown(Entity entity, String action, long durationMs) {
        Map<String, Long> cooldowns = entity.getMetaObject(COOLDOWNS);
        cooldowns.put(action, System.currentTimeMillis() + durationMs);
    }

    public boolean isOnCooldown(Entity entity, String action) {
        Map<String, Long> cooldowns = entity.getMetaObject(COOLDOWNS);
        Long expireTime = cooldowns.get(action);

        if (expireTime == null) return false;

        if (System.currentTimeMillis() >= expireTime) {
            cooldowns.remove(action);
            return false;
        }

        return true;
    }
}

3. Datos Persistentes de Jugador

public class PlayerDataSystem {
    // Puntos de experiencia (persistente)
    private static final MetaKey<Integer> EXPERIENCE_POINTS =
        registry.registerMetaObject(
            player -> 0,
            true,              // Persistente
            "xp",              // Nombre
            Codec.INT          // Codec
        );

    // Nivel (persistente)
    private static final MetaKey<Integer> LEVEL =
        registry.registerMetaObject(
            player -> 1,
            true,
            "level",
            Codec.INT
        );

    public void addExperience(Player player, int amount) {
        int currentXp = player.getMetaObject(EXPERIENCE_POINTS);
        int newXp = currentXp + amount;

        player.putMetaObject(EXPERIENCE_POINTS, newXp);

        // Verificar si sube de nivel
        checkLevelUp(player, newXp);
    }

    private void checkLevelUp(Player player, int xp) {
        int level = player.getMetaObject(LEVEL);
        int xpNeeded = level * 100;

        if (xp >= xpNeeded) {
            player.putMetaObject(LEVEL, level + 1);
            player.sendMessage("¡Nivel subido! Nuevo nivel: " + (level + 1));
        }
    }
}

4. Estado de Interacción

public class InteractionSystem {
    // Contexto de interacción actual
    private static final MetaKey<InteractionContext> CURRENT_INTERACTION =
        Interaction.CONTEXT_META_REGISTRY.registerMetaObject(ctx -> null);

    // Entidad objetivo
    private static final MetaKey<EntityRef> TARGET =
        Interaction.TARGET_ENTITY;

    // Bloque objetivo
    private static final MetaKey<BlockPosition> TARGET_BLOCK =
        Interaction.TARGET_BLOCK;

    public void startInteraction(InteractionContext ctx, Entity target) {
        ctx.putMetaObject(TARGET, target.getRef());
    }

    public Entity getInteractionTarget(InteractionContext ctx) {
        EntityRef ref = ctx.getIfPresentMetaObject(TARGET);
        return ref != null ? ref.getEntity() : null;
    }
}

Registros Predefinidos

Hytale proporciona algunos registros predefinidos:

Interaction Context Meta Registry

Ubicación: Interaction.java:566

public static final MetaRegistry<InteractionContext> CONTEXT_META_REGISTRY =
    new MetaRegistry<>();

// MetaKeys predefinidas
public static final MetaKey<Ref<EntityStore>> TARGET_ENTITY;
public static final MetaKey<Vector4d> HIT_LOCATION;
public static final MetaKey<String> HIT_DETAIL;
public static final MetaKey<BlockPosition> TARGET_BLOCK;
public static final MetaKey<BlockPosition> TARGET_BLOCK_RAW;
public static final MetaKey<Integer> TARGET_SLOT;

Uso:

InteractionContext ctx = ...;

// Obtener entidad objetivo
Ref<EntityStore> target = ctx.getMetaObject(Interaction.TARGET_ENTITY);

// Obtener posición del impacto
Vector4d hitLoc = ctx.getMetaObject(Interaction.HIT_LOCATION);

// Obtener bloque objetivo
BlockPosition block = ctx.getMetaObject(Interaction.TARGET_BLOCK);

Interaction Meta Registry

public static final MetaRegistry<Interaction> META_REGISTRY =
    new MetaRegistry<>();

public static final MetaKey<Float> TIME_SHIFT;
public static final MetaKey<Damage> DAMAGE;

Rendimiento

Almacenamiento Interno

El Meta System usa arrays para almacenamiento eficiente:

// IMetaStoreImpl.java (simplificado)
public class IMetaStoreImpl<K> {
    private Object[] metaObjects;  // Array indexado por MetaKey.id

    public <T> T getMetaObject(MetaKey<T> key) {
        int id = key.getId();

        if (id >= metaObjects.length || metaObjects[id] == null) {
            // Inicializar con valor por defecto
            T value = registry.newMetaObject(key, parent);
            putMetaObject(key, value);
            return value;
        }

        return (T) metaObjects[id];
    }
}

Complejidad

  • getMetaObject(): O(1)
  • putMetaObject(): O(1)
  • hasMetaObject(): O(1)
  • removeMetaObject(): O(1)

Thread Safety

El MetaRegistry usa locks de lectura/escritura:

// MetaRegistry.java:16
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public <T> MetaKey<T> registerMetaObject(...) {
    lock.writeLock().lock();
    try {
        // Registrar MetaKey
    } finally {
        lock.writeLock().unlock();
    }
}

public <T> T newMetaObject(MetaKey<T> key, K parent) {
    lock.readLock().lock();
    try {
        // Crear objeto
    } finally {
        lock.readLock().unlock();
    }
}

Mejores Prácticas

✅ Hacer

  1. Declarar MetaKeys como constantes estáticas

    public static final MetaKey<String> PLAYER_RANK =
        registry.registerMetaObject(p -> "Novato");
    

  2. Usar tipos inmutables cuando sea posible

    MetaKey<String> NAME = ...;  // Bien
    MetaKey<StringBuilder> BUILDER = ...;  // Evitar
    

  3. Inicializar con valores por defecto apropiados

    MetaKey<List<String>> ITEMS =
        registry.registerMetaObject(e -> new ArrayList<>());
    

  4. Usar getIfPresentMetaObject() cuando el null es válido

    String optional = entity.getIfPresentMetaObject(OPTIONAL_DATA);
    if (optional != null) { ... }
    

❌ No Hacer

  1. No crear MetaKeys dinámicamente
  2. No compartir datos mutables sin sincronización
  3. No almacenar referencias circulares
  4. No abusar de metadatos persistentes

Siguiente


¿Preguntas? Consulta FAQ o Troubleshooting