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
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¶
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¶
-
Declarar MetaKeys como constantes estáticas
-
Usar tipos inmutables cuando sea posible
-
Inicializar con valores por defecto apropiados
-
Usar getIfPresentMetaObject() cuando el null es válido
❌ No Hacer¶
- No crear MetaKeys dinámicamente
- No compartir datos mutables sin sincronización
- No almacenar referencias circulares
- No abusar de metadatos persistentes
Siguiente¶
- MetaKey: Documentación detallada de MetaKey
- IMetaStore: Interfaz de almacenamiento
- MetaRegistry: Registro de MetaKeys
- Tutorial: Metadata Storage: Tutorial práctico
¿Preguntas? Consulta FAQ o Troubleshooting