Sesion s32 · 2026-04-11 · Revision completa post-ClipBridgeService
La plataforma LRU tiene 6 productores de datos y 8 consumidores, todos convergiendo en el DataPlane como bus central. Pero la convergencia no es total: hay caminos laterales que crean complejidad innecesaria.
El DataPlane es la fuente de verdad universal para datos numericos. Sin embargo, el camino DataPlane → clips (Adobe Animate) sigue siendo el mas complejo y costoso porque requiere tres transformaciones innecesarias: escala real→ADC, serializacion JSON, y transporte via DOM events.
Actualmente coexisten 7 mecanismos de comunicacion diferentes dentro de F1:
| # | Mecanismo | Usado por | Overhead | Justificado? |
|---|---|---|---|---|
| 1 | DataPlane.subscribe() |
ClipBridgeService, SignalEngine, paneles React (via hooks) | Bajo dirty-flags + throttle 20fps | Si — bus central optimizado |
| 2 | CustomEvent DOM |
ClipBridgeService → IB_03 (hwObjId_online) | Medio DOM dispatch + bubble + JSON | Parcial — IB_03 legacy lo requiere |
| 3 | React Context |
ORC (hwObjs/swObjs state), Sim1Context, Datos01Context | Alto re-renders en cascada | Parcial — necesario para UI, excesivo para datos RT |
| 4 | DebugBus (EventEmitter) |
Debug1, 11 canales | Bajo | Si — solo debug |
| 5 | RouterService.subscribe() |
RouterModeChip, ControlBus | Bajo | Si — pocos suscriptores, baja frecuencia |
| 6 | window.__sim1_event__() |
Eventos viewer-search, sim1-pip | Bajo | Si — comandos esporadicos |
| 7 | MQTT via RabbitMQ (F3) |
CommsManager → ORC → DataPlane | Alto red + JSON + dispatch | Si — unica via para hardware real |
Los mecanismos 1, 4, 5, 6 y 7 estan bien justificados. Los problematicos son el #2 (CustomEvent DOM) y el #3 (React Context para datos RT). Ambos introducen overhead innecesario en el hot path.
P1. Doble serializacion: JSON.stringify en ClipBridgeService + JSON.parse en IB_03. Esto existe solo porque CustomEvent.detail transporta strings. Si IB_03 leyera directamente del DataPlane, ambas operaciones desaparecen.
P2. Escala real→ADC: La conversion realToAdc() existe porque IB_03 espera valores ADC 0-4095. Pero los clips originales de Adobe Animate mapean internamente frame = f(adcValue). Si los clips supieran interpretar valores reales directamente (altitud en pies, temperatura en °C), la conversion ADC desaparece.
P3. CustomEvent como transporte: El DOM event system esta disenado para interaccion de usuario, no para streams de datos a 20fps. Cada dispatch implica: crear objeto Event, buscar listeners en el DOM tree, invocar callbacks. Un callback directo es 10-50× mas rapido.
P4. Agrupacion por hwObjId: ClipBridgeService agrupa canales dirty por hwObjId para enviar un solo evento por hwObj. Esto es una optimizacion sobre el modelo anterior, pero sigue siendo un paso innecesario si IB_03 pull del DataPlane.
| Operacion | Coste unitario | × 3 hwObjs visibles | × 20fps | Total/s |
|---|---|---|---|---|
| realToAdc() × ~8 sensores | ~0.5μs | 1.5μs | 30μs | ~30μs/s |
| JSON.stringify(adcMap) | ~3μs | 9μs | 180μs | ~180μs/s |
| CustomEvent dispatch | ~2μs | 6μs | 120μs | ~120μs/s |
| JSON.parse en IB_03 | ~3μs | 9μs | 180μs | ~180μs/s |
| change1() × ~8 ports | ~1μs | 3μs | 60μs | ~60μs/s |
| Total | ~570μs/s | |||
570μs/s no es critico en un desktop, pero en un tablet o movil con 6+ hwObjs visibles puede subir a ~1.2ms/s, y compite con el presupuesto del RAF frame (16.6ms).
El tramo F3 (ESP32 → RabbitMQ → WebSocket → MqttAdapter) es irreducible: necesitamos la red. Pero una vez los datos llegan a MqttAdapter.onMessage(), la cadena tiene 2× JSON.parse (una en MqttAdapter, otra en IB_03) y 1× JSON.stringify (en ClipBridgeService). El parse de MqttAdapter es inevitable (viene de la red como texto). Pero el stringify+parse del tramo ClipBridge→IB_03 es eliminable.
Bien disenado Este pipeline es limpio: Float64 directo, sin serializacion, throttled a 20fps. Los hooks useDataPlaneValue y useDataPlaneValues solo causan re-render cuando el valor cambia (dirty-flag). Es el modelo a seguir.
Bien disenado SignalEngine es un singleton vanilla TS que lee y escribe directamente en el DataPlane sin intermediarios. Zero-copy para floats. Los 45 nodos de procesamiento operan en el mismo espacio de memoria. Sin serializacion.
Optimo Escritura directa. Sin intermediarios.
Cuando se reactive, EscenariosContext necesita evitar el path React Context → ORC dispatch. Los datos de escenarios (valores nominales por sensor, fallos) deberian escribirse directamente al DataPlane y SimService, sin pasar por el ORC.
| # | Anti-patron | Donde | Impacto | Severidad |
|---|---|---|---|---|
| A1 | Serializacion innecesaria: JSON.stringify + JSON.parse entre dos modulos in-memory (F1) | ClipBridgeService → IB_03 | ~360μs/s (3 hwObjs × 20fps) | Alta |
| A2 | DOM como bus de datos: CustomEvents para stream de datos a 20fps | ClipBridgeService → IB_03 | ~120μs/s + GC pressure (objetos Event) | Alta |
| A3 | Escala ADC redundante: conversion real→ADC que solo existe porque IB_03 espera ADC | ClipBridgeService.realToAdc() | ~30μs/s (bajo, pero complejidad innecesaria) | Media |
| A4 | IB_03 monolitico: 2033 lineas de JS legacy, mezcla responsabilidades (deteccion de clip, binding, update, fallback DOM) | public/InstrumentBridge_03.js | Deuda tecnica, dificil de optimizar | Media |
| A5 | ORC como intermediario MQTT: ORC recibe MQTT events, los procesa en React Context, y escribe al DataPlane. El hot path pasa por React. | ObjectRegistryContext.handleMqttEvent() | React re-renders + dispatch overhead en cada mensaje MQTT | Media |
| A6 | Dos mundos de suscripcion: DataPlane.subscribe (vanilla) vs React hooks (useDataPlaneValue). No es un bug, pero implica dos patrones mentales. | Toda la app | Complejidad cognitiva | Baja |
| A7 | channelMap indirecto: ORC mantiene channelMap (hwObjId→sensorId→channelIdx) que duplica info del DataPlane registry. | ORC + DataPlane | Estado duplicado, posible desincronizacion | Baja |
Estimaciones basadas en el analisis de s31 y benchmarks tipicos de V8:
| Pipeline | Frecuencia | Coste/frame | Coste/s | Eliminable? |
|---|---|---|---|---|
| SimService → DataPlane | 60fps | ~5μs | ~300μs | No (core loop) |
| DataPlane → ClipBridge → IB_03 | 20fps | ~28μs (3 hwObjs) | ~570μs | Si: -300μs con IB_04 |
| DataPlane → React hooks | 20fps | ~2μs | ~40μs | No (ya optimo) |
| SignalEngine.tick() | 60fps | ~10μs (5 nodos activos) | ~600μs | No (DSP necesario) |
| MQTT → ORC → DataPlane | ~10-50fps variable | ~15μs | ~300μs | Parcial: ORC extraible |
Crear InstrumentBridge_04.js que lea directamente del DataPlane en vez de escuchar CustomEvents. Esto elimina de un golpe los anti-patrones A1 (JSON), A2 (DOM events) y A3 (escala ADC).
| Aspecto | IB_03 actual | IB_04 propuesto |
|---|---|---|
| Fuente de datos | CustomEvent (push, JSON string) | DataPlane.read() (pull, Float64) |
| Serializacion | JSON.stringify + JSON.parse | Ninguna (Float64 directo) |
| Escala | real → ADC → frame (2 pasos) | real → frame (1 paso) |
| Timing | Push 20fps desde ClipBridgeService | Pull synced con RAF del clip (autoregulado) |
| Backpressure | IntersectionObserver externo | Nativo: no lee si clip no visible |
| Dependencia ClipBridgeService | Necesario | Eliminable (IB_04 es autonomo) |
IB_04 puede coexistir con IB_03. Cada clip declara que bridge usa (data-bridge="ib04"). Migracion clip a clip sin romper nada. ClipBridgeService se mantiene para clips IB_03 legacy, y se elimina cuando todos migren.
realToFrame()// Convierte valor real directo a frame del clip
// Elimina el paso intermedio ADC 0-4095
function realToFrame(value, sensorDef) {
const { min, max, totalFrames } = sensorDef;
const normalized = (value - min) / (max - min); // 0..1
const clamped = Math.max(0, Math.min(1, normalized));
return Math.round(clamped * (totalFrames - 1)) + 1;
}
Crear MqttBridgeService — singleton vanilla TS que reciba los mensajes MQTT del CommsManager y escriba directamente al DataPlane, sin pasar por el ORC (React Context). El ORC solo se actualiza para cambios de estado de dispositivos (online/offline, firmware version), no para datos de sensores en tiempo real.
Separa las dos responsabilidades del ORC: gestion de estado de dispositivos (baja frecuencia, necesita React) vs relay de datos de sensores (alta frecuencia, no necesita React). Elimina re-renders innecesarios del tree React en cada mensaje MQTT (anti-patron A5).
Eliminar el channelMap duplicado en el ORC. El DataPlane ya tiene un registry de canales (sensorId → channelIdx). El ORC puede consultar el DataPlane en vez de mantener su propia copia. Elimina anti-patron A7.
Cuando se reactive EscenariosContext, los presets de escenario escriben directamente al SimService (que a su vez escribe al DataPlane). Sin pasar por ORC ni React Context para los datos de simulacion. EscenariosContext solo gestiona la UI de seleccion de escenario.
No es un cambio de codigo sino una convencion: documentar que DataPlane.subscribe() es el patron canonico para singletons vanilla TS, y useDataPlaneValue() para componentes React. Eliminar cualquier uso residual de CustomEvents o React Context para datos de sensores en tiempo real. Anti-patron A6 no se elimina (los dos mundos son necesarios) pero se clarifica.
| # | Mecanismo | Uso | Estado |
|---|---|---|---|
| 1 | DataPlane (subscribe + read) | Todo dato de sensores | Principal — unico bus RT |
| 2 | React Context | Solo estado UI (dispositivos, presets, layout) | Correctamente scoped |
| 3 | Service.subscribe() | RouterService, ControlBus (baja frecuencia) | Sin cambios |
| 4 | DebugBus | Debug solamente | Sin cambios |
| 5 | MQTT (F3) | Hardware real | Irreducible |
| 6 | CustomEvent DOM | clips | Eliminado con IB_04 |
CustomEvents eliminado. React Context sacado del hot path. DataPlane como unico bus de datos en tiempo real, con dos APIs: .subscribe() para singletons, hooks para React.
| Prioridad | Propuesta | Impacto | Esfuerzo | Dependencias |
|---|---|---|---|---|
| 1 | P1: IB_04 — Bridge directo DataPlane | Alto — elimina JSON + DOM + ADC | Medio — ~300 lineas nuevo JS, coexiste con IB_03 | Ninguna |
| 2 | P2: MqttBridgeService | Medio — saca React del hot path MQTT | Medio — ~200 lineas, refactor ORC | Ninguna |
| 3 | P4: Escenarios → SimService | Medio — prepara reactivacion limpia | Bajo — routing directo | Cuando se reactive EscenariosContext |
| 4 | P3: channelMap unificar | Bajo — elimina estado duplicado | Bajo — refactor simple | P2 (menos riesgo si ORC ya simplificado) |
| 5 | P5: Documentar convencion | Bajo — claridad cognitiva | Minimo — solo documentacion | Ninguna |
El DataPlane ya es el bus central correcto. Las propuestas no cambian la arquitectura — la completan. El objetivo final es que todo dato de sensor fluya exclusivamente por el DataPlane, con lectores pull-based (IB_04, SignalEngine) o push-based throttled (React hooks), sin intermediarios DOM ni React Context en el hot path.
Con IB_04 + MqttBridgeService, la plataforma queda con un unico patron mental: "si es dato de sensor en tiempo real, pasa por el DataPlane. Punto."