Desarrolladores
9 TOP-LEVEL ITEMSRed
Croparia IF no construye un framework de red pesado por su cuenta. En su lugar, añade una capa unificadora más ligera sobre NetworkManager de Architectury:
NetworkHandlerNetworkHandlerType
El objetivo de esa capa es bastante directo:
- permitir que un paquete lleve en un solo sitio su tipo, codec, dirección y lógica de manejo
- simplificar el registro multiplataforma
- mantener el código de envío lo más uniforme posible
Si solo quieres una imagen rápida del flujo de red del mod, quédate con esto:
NetworkHandlersignifica “un paquete que puede enviarse y procesarse”NetworkHandlerTypesignifica “el registro de ese paquete”
Modelo mental
La capa de red de Croparia IF puede entenderse en tres niveles:
NetworkHandlerNetworkHandlerTypeNetworkHandlers
Sus responsabilidades son:
NetworkHandler- el objeto de paquete concreto
- también implementa
CustomPacketPayload - proporciona su propio tipo e implementa
handle(...)
NetworkHandlerType- describe si este paquete es
C2SoS2C - mantiene el
CustomPacketPayload.Type - mantiene el
StreamCodec - puede llevar opcionalmente un
PacketTransformer
- describe si este paquete es
NetworkHandlers- el punto de entrada central del registro
- conecta cada tipo de paquete al sistema de envío y recepción de Architectury
La idea de esta estructura no es la complejidad, sino que todos los paquetes acaben teniendo la misma forma:
- un record u objeto como carga útil
- una constante
TYPEcomo metadato de registro - un método
handle(...)como lógica de recepción
NetworkHandler
NetworkHandler es la abstracción mínima de Croparia IF para un paquete.
Principalmente ofrece tres cosas:
type()- por defecto obtiene el tipo real del payload desde
handlerType()
- por defecto obtiene el tipo real del payload desde
send()- decide automáticamente si debe enviarse al servidor o difundirse a clientes según el lado del paquete
handle(NetworkManager.PacketContext context)- punto de entrada de la lógica de negocio una vez recibido
El detalle más importante aquí es que el comportamiento del envío ya está condicionado por la dirección declarada.
- Si
handlerType().side()esC2Ssend()intentará enviarlo al servidor
- Si
handlerType().side()esS2Csend()lo difundirá, o usarásend(ServerPlayer)para un jugador concreto
Eso significa que quien llama normalmente no necesita una rama extra para decidir “si esto es un paquete de cliente”. El propio tipo del paquete ya lo ha declarado.
NetworkHandlerType
NetworkHandlerType es el descriptor de registro de un paquete.
Empaqueta:
- el
Identifierdel paquete - la dirección de envío y recepción
NetworkManager.Side - el
StreamCodec - un
PacketTransformeropcional
Los helpers de creación más comunes en el código fuente son:
NetworkHandlerType.ofC2S(...)NetworkHandlerType.ofS2C(...)
La forma más fácil de entenderlo es como la ficha de registro del paquete:
NetworkHandleres la carga útil realNetworkHandlerTypele dice al sistema cómo identificarlo, cómo codificarlo y decodificarlo, y a qué lado pertenece
ofS2C(...) también admite un PacketTransformer, algo importante en sincronizaciones con mucha carga. El flujo de sincronización de recetas es el ejemplo más claro.
Flujo de registro
El punto de entrada unificado del registro es NetworkHandlers.
Su trabajo es:
- llamar una vez a
register(...)por cadaNetworkHandlerType - elegir la ruta de registro correcta de Architectury según el lado del paquete
- en
S2C, cubrir tanto el registro de recepción en cliente como la declaración del payload en el servidor
La ventaja principal para desarrolladores es:
- los paquetes de negocio no necesitan lógica de registro separada para Fabric / Forge / NeoForge
- la mayor parte del tiempo solo tienes que preocuparte por la constante
TYPEy porhandle(...)
Flujo C2S típico: CropTransmuter
El ejemplo C2S más claro del código actual es la pantalla del menú CropTransmuter.
Aquí intervienen dos paquetes:
CropTransmuterSelectPacketCropTransmuterRedstoneModePacket
Ambos son enviados por interacciones de la interfaz cliente en CropTransmuterScreen, y después el servidor los procesa para actualizar la block entity correspondiente.
Selección de salida
CropTransmuterSelectPacket envía al servidor “qué salida candidata seleccionó el jugador en la interfaz”.
Su payload solo contiene:
BlockPos posint selectedIndex
En el servidor, el manejo pasa por una cadena de comprobaciones:
- ¿El emisor es un
ServerPlayer? - ¿El menú abierto actualmente es realmente un
CropTransmuterMenu? - ¿La posición del menú coincide con la posición del paquete?
- ¿La block entity en esa posición es realmente un
CropTransmuterBlockEntity? - ¿Existe material de entrada válido en ese momento?
- ¿
selectedIndexestá dentro del rango válido?
Solo entonces se llama a transmuter.setSelectedIndex(...).
Este flujo es una referencia excelente porque resume muy bien la actitud normal de Croparia IF ante C2S:
- el cliente solo envía el estado mínimo necesario
- el servidor siempre vuelve a validar el contexto real
- el estado de interfaz que viene del cliente no se considera fiable por sí solo
Cambio del modo de redstone
CropTransmuterRedstoneModePacket es más simple y solo transporta la posición del bloque objetivo.
En el servidor:
- comprueba que el menú actual y la posición sigan coincidiendo
- encuentra la
CropTransmuterBlockEntitycorrespondiente - llama a
toggleRedstoneMode() - después usa
menu.broadcastChanges()para que el estado del menú vuelva al cliente
Eso hace que la responsabilidad del paquete sea muy estrecha:
- solo significa “el usuario pidió cambiar el modo”
- el cambio real de estado sigue realizándose en el servidor
Flujo S2C típico: sincronización de recetas
El otro flujo que merece la pena estudiar es la sincronización de recetas S2C en tres etapas usada por SyncedRecipeCache:
S2CSyncRecipeStartS2CSyncRecipeChunkS2CSyncRecipeEnd
Juntos sirven para un único objetivo:
- enviar al cliente una instantánea de los tipos de receta marcados como “requieren sincronización del lado cliente”
Por qué son tres etapas
La sincronización no se hace en un único paquete enorme. Se divide en tres fases:
Start
- informa al cliente del
syncIdde esta ronda y de qué tipos de receta incluye
Chunk
- envía los datos reales de receta por tipo y por fragmentos
End
- indica al cliente que la ronda terminó y que ya puede comprometer la nueva instantánea
Las ventajas son:
- el cliente puede distinguir con claridad una ronda completa de sincronización
- grandes conjuntos de recetas no tienen que caber en un único paquete enorme
- el servidor puede enviar los datos por fragmentos, agrupados por tipo y tamaño
SplitPacketTransformer
Cuando S2CSyncRecipeChunk registra su TYPE, también lleva un SplitPacketTransformer.
Eso significa que la sincronización de recetas no solo se fragmenta a nivel lógico. La capa de red también declara explícitamente que las cargas grandes necesitan división a nivel de transporte. Para desarrolladores, este patrón es muy útil:
- si un payload
S2Cpuede crecer mucho, no dependas solo de “enviar un poco menos” - puedes seguir este patrón y adjuntar un transformer en
NetworkHandlerType
Cómo lo aplica el cliente
El punto de aterrizaje del lado cliente es SyncedRecipeCache:
beginClientSync(...)- crea el estado de la ronda actual de sincronización
acceptChunk(...)- guarda temporalmente cada fragmento recibido
endClientSync(...)- fusiona todos los fragmentos en la nueva instantánea activa
- y después dispara
CompatRecipeRefresh.onRecipesUpdated(...)
La idea importante aquí no es “procesar cada paquete inmediatamente”, sino:
- primero ensamblar una instantánea completa en el cliente
- después refrescar la visibilidad de recetas en un solo paso
Dos hábitos comunes dentro de handle(...)
Estos paquetes comparten dos hábitos de implementación muy estables.
context.queue(...)
La lógica de negocio suele envolverse dentro de context.queue(...).
Eso significa que Croparia IF prefiere devolver los cambios reales de estado al contexto de hilo correcto, en vez de mutar directamente el mundo o las cachés del cliente dentro del hilo bruto del callback de red.
Si añades un paquete nuevo, normalmente deberías seguir el mismo patrón.
Validar primero, mutar después
Tanto en CropTransmuter como en la sincronización de recetas, la lógica no consiste simplemente en “recibir y cambiar estado”.
Las comprobaciones habituales incluyen:
- si el jugador actual existe
- si el menú abierto sigue coincidiendo
- si la posición del bloque sigue coincidiendo
- si el tipo de block entity sigue coincidiendo
- si índices, materiales o números de fragmento siguen siendo válidos
Esta es una de las lecciones más reutilizables de la capa de red:
- mantén los paquetes pequeños
- deja la confianza en el lado receptor
Cuándo seguir este patrón
El diseño actual de red encaja bien cuando:
- estás añadiendo una interacción pequeña de GUI que necesita enviar clics de botones o selecciones al servidor
- necesitas sincronizar una instantánea de solo lectura al cliente y esperas que la carga crezca bastante
- ya estás usando
StreamCodecy quieres que la definición y el registro de paquetes mantengan un estilo uniforme - quieres una capa sobre Architectury que siga sintiéndose cercana a la lógica del mod
Si tu caso es extremadamente local y puntual, no hace falta abstraerlo todo en más capas. Pero en cuanto entre en la superficie compartida de API de Croparia IF, seguir NetworkHandler / NetworkHandlerType suele ser la opción más segura.
Recomendaciones
- Para paquetes nuevos, prioriza el patrón “un objeto de payload + una constante
TYPE+ unhandle(...)”, porque encaja mejor con el estilo de código existente. - Los paquetes
C2Sdeberían enviar solo la información mínima necesaria. No confíes en grandes cantidades de estado proporcionado por el cliente. - Cuando un paquete toca menús o block entities, vuelve a validar siempre la posición, la vinculación del menú y el tipo de entidad en el servidor.
- Si un payload
S2Cpuede crecer mucho, prioriza una sincronización por fases y fragmentos como la de recetas. - Si un sistema termina funcionando alrededor de una instantánea de cliente, prefiere “acumular primero y confirmar una vez” en lugar de refrescar el estado visible con cada paquete recibido.