Arquitectura
Página pensada para quien quiera contribuir código al mod o entender su funcionamiento interno.
Tabla de contenido
- Estructura de paquetes
- Ciclo de vida de un mensaje
- Subsistema de texturas
- HTTP dentro del classloader de Minecraft
- Contribuir
Estructura de paquetes
com.nexuschat
├── NexusChat · ModInitializer (común: carga la config, arranca el hot-reload)
├── NexusChatClient · ClientModInitializer (HUD, keybinds, tick de cliente, registra comandos)
├── NexusChatConstants · Client ID de Twitch embebido (ofuscado con XOR)
├── chat/ · Dispatcher, cola de mensajes, filtros
├── command/ · Comandos — todos client-side (ver nota más abajo)
├── config/ · POJOs de Gson + ConfigManager (hot-reload, perfiles, cifrado de secretos)
├── keybind/ · Keybinds configurables
├── mixin/ · ChatScreenScrollMixin (intercepta scroll/click/right-click del chat vanilla)
├── model/ · Records inmutables (UnifiedChatMessage, MessageSegment, ...)
├── platform/ · Un cliente por plataforma
│ ├── twitch/ · IRC + OAuth (Device Code + PKCE) + acciones de moderación (Helix) + polling de encuestas
│ ├── kick/ · WebSocket Pusher + resolución de chatroom
│ ├── youtube/ · Polling InnerTube
│ ├── tiktok/ · Adaptador EulerStream + parser + resolución de emotes
│ ├── obs/ · Cliente OBS-WebSocket
│ └── alerts/ · Puente Streamlabs/StreamElements (Socket.IO), ver [Alertas](alertas.html)
├── render/ · ChatOverlayRenderer, MessageRenderer, EmoteRenderer, BadgeRenderer, ...
├── report/ · BugReportSender — `/nexuschat report`, envío a un webhook de Discord
├── screen/ · Pantallas de configuración (Screen de Minecraft), incl. WhatsNewScreen
├── texture/ · TextureCache (LRU + liberación GL), TextureFetcher (HTTP + GIF/WebP), TextureDiskCache, RemoteTexture (atlas 2D)
├── update/ · WhatsNewLoader — lee `whatsnew.json` empaquetado y decide si mostrar WhatsNewScreen
├── util/ · ColorUtil, EmojiPreprocessor, ThreadUtil, ChatNotifier, TtsUtil, ...
└── web/ · Servidor local + asistente de conexión web (`/conectar`)
Todos los comandos (
/nexuschat,/conectar,simulate,profile,report…) se registran víaClientCommandRegistrationCallbackenNexusChatClientCommands.register()(llamado desdeNexusChatClient). En algún momento hubo también un árbol registrado del lado servidor con las mismas subrutassimulate/profile— como Fabric prioriza el árbol client-side para el literal raíznexuschat, esas subrutas nunca eran alcanzables desde el chat y dabanCommandSyntaxException. Se eliminó el registro servidor: si vas a añadir un comando nuevo, hazlo enNexusChatClientCommands, no crees un segundo árbol.
Puntos de entrada
| Clase | Rol |
|---|---|
NexusChat |
onInitialize — carga la config, arranca el hot-reload. |
NexusChatClient |
onInitializeClient — keybinds, HUD, tick de cliente, conecta plataformas, registra comandos. |
PlatformManager |
Singleton que arranca/detiene cada ChatPlatform. |
Hilos
- Render thread (Minecraft) — el único autorizado a tocar GL y
TextureManager. ThreadUtil.IO_POOL—FixedThreadPool(8)para HTTP y decodificación de imágenes.ThreadUtil.SCHEDULER—SingleThreadScheduledExecutorpara polling y reconexiones.
Todo flujo que toque texturas usa ThreadUtil.onRenderThread(...) o
MinecraftClient.getInstance().execute(...).
Ciclo de vida de un mensaje
- La plataforma parsea un evento (IRC/WS/InnerTube/EulerStream) →
UnifiedChatMessageconsegments,badgesyMessageType. Los emotes de 7TV/BTTV/FFZ ya llegan resueltos comoEmoteSegment. ChatMessageDispatcher.dispatch(msg):- Filtros de lista negra.
- Pre-carga de texturas de avatar y badges.
- Throttle de duplicados (
duplicateThresholdSec) → contadorxN. - Detección de menciones: streamer (rojo) > jugador (amarillo) → marcador
en
rawContent. - Reproduce sonido de notificación (en el hilo de cliente vía
mc.execute). queue.add(msg).
- Render en
HudRenderCallback, cada frame:ChatOverlayRenderer.render→drainWindowFiltered→ bucle de dibujo.MessageRenderer.calculateHeightcacheada enheightBuf[].ChatOverlayRenderer.renderBackground(compatible 1.21.1) → fondo según tipo de mensaje / mención.MessageRenderer.render(...)→EmoteRenderer.render(texto + emotes con ajuste de línea sin indentar).
Subsistema de texturas
TextureCache (RAM, LRU 500 entradas)
↓ miss
TextureDiskCache (disco, TTL configurable)
↓ miss
HttpUtil.getBytes (HttpURLConnection — ver nota más abajo)
↓
TextureFetcher
├─ GIF → decodeGif() → atlas 2D (empaquetado en rejilla, respeta GL_MAX_TEXTURE_SIZE / 4096px)
└─ estático → NativeImage.read (PNG/JPEG) → fallback ImageIO (WebP) → fallback proxy wsrv.nl
Al desalojar del LRU se llama a textureManager.destroyTexture(id) en el
render thread para no filtrar memoria de GPU.
HTTP dentro del classloader de Minecraft
Minecraft usa un classloader personalizado que rompe ciertas librerías HTTP. Esto es importante si vas a tocar código de red:
| Librería | Estado |
|---|---|
HttpURLConnection |
✅ Segura — la usa el propio Minecraft. |
java.net.http.HttpClient |
⚠️ Falla con SSL en peticiones normales (el WebSocket sí funciona). |
| OkHttp | ❌ Crashea (NoClassDefFoundError: kotlin/jvm/internal/Intrinsics, falta el runtime de Kotlin en Fabric) — excepto para el WebSocket de Kick, donde se incluye vendorizado. |
Regla: usa siempre HttpURLConnection para peticiones REST.
java.net.http.HttpClient solo para WebSocket.
Contribuir
- Clona el repositorio del mod y compílalo con
./gradlew.bat compileJavapara validar cambios rápido. - Sigue las convenciones de hilos descritas arriba — cualquier código que
toque
TextureManageroSoundManagerfuera del render thread provoca crashes silenciosos o texturas corruptas. - Antes de abrir un PR, prueba manualmente con
/nexuschat simulate chat,sub,reward(o la pantalla Sesión de Prueba) y verifica que no rompes el hot-reload de configuración.