@adcops/autocore-react 3.1.1 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/additional-docs/react_performance_notes.md +94 -0
  2. package/dist/components/AutoCoreDevPanel.d.ts.map +1 -1
  3. package/dist/components/AutoCoreDevPanel.js +1 -1
  4. package/dist/components/FileList.d.ts.map +1 -1
  5. package/dist/components/FileList.js +1 -1
  6. package/dist/components/FileSelect.d.ts.map +1 -1
  7. package/dist/components/FileSelect.js +1 -1
  8. package/dist/core/AutoCoreTagContext.d.ts +59 -185
  9. package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
  10. package/dist/core/AutoCoreTagContext.js +1 -1
  11. package/dist/core/AutoCoreTagTypes.d.ts +127 -6
  12. package/dist/core/AutoCoreTagTypes.d.ts.map +1 -1
  13. package/dist/core/CoreStreamTypes.d.ts +345 -0
  14. package/dist/core/CoreStreamTypes.d.ts.map +1 -0
  15. package/dist/core/CoreStreamTypes.js +1 -0
  16. package/dist/core/EventEmitterContext.d.ts +91 -473
  17. package/dist/core/EventEmitterContext.d.ts.map +1 -1
  18. package/dist/core/EventEmitterContext.js +1 -1
  19. package/dist/hooks/adsHooks.d.ts.map +1 -1
  20. package/dist/hooks/adsHooks.js +1 -1
  21. package/dist/hooks/commandHooks.d.ts +3 -3
  22. package/dist/hooks/commandHooks.d.ts.map +1 -1
  23. package/dist/hooks/commandHooks.js +1 -1
  24. package/dist/hooks/useAutoCoreTag.js +1 -1
  25. package/dist/hub/CommandMessage.d.ts +18 -9
  26. package/dist/hub/CommandMessage.d.ts.map +1 -1
  27. package/dist/hub/CommandMessage.js +1 -1
  28. package/dist/hub/DebugPanel.d.ts +31 -0
  29. package/dist/hub/DebugPanel.d.ts.map +1 -0
  30. package/dist/hub/DebugPanel.js +1 -0
  31. package/dist/hub/HubBase.d.ts +83 -129
  32. package/dist/hub/HubBase.d.ts.map +1 -1
  33. package/dist/hub/HubBase.js +1 -1
  34. package/dist/hub/HubSimulate.d.ts +41 -8
  35. package/dist/hub/HubSimulate.d.ts.map +1 -1
  36. package/dist/hub/HubSimulate.js +1 -1
  37. package/dist/hub/HubTauri.d.ts +24 -60
  38. package/dist/hub/HubTauri.d.ts.map +1 -1
  39. package/dist/hub/HubTauri.js +1 -1
  40. package/dist/hub/HubWebSocket.d.ts +33 -17
  41. package/dist/hub/HubWebSocket.d.ts.map +1 -1
  42. package/dist/hub/HubWebSocket.js +1 -1
  43. package/dist/hub/debug.d.ts +23 -0
  44. package/dist/hub/debug.d.ts.map +1 -0
  45. package/dist/hub/debug.js +1 -0
  46. package/dist/hub/index.d.ts +19 -4
  47. package/dist/hub/index.d.ts.map +1 -1
  48. package/dist/hub/index.js +1 -1
  49. package/package.json +4 -4
  50. package/src/components/AutoCoreDevPanel.tsx +14 -11
  51. package/src/components/FileList.tsx +5 -4
  52. package/src/components/FileSelect.tsx +2 -1
  53. package/src/core/ActionMode.ts +1 -1
  54. package/src/core/AutoCoreTagContext.tsx +247 -330
  55. package/src/core/AutoCoreTagTypes.ts +236 -104
  56. package/src/core/CoreStreamTypes.ts +512 -0
  57. package/src/core/EventEmitterContext.tsx +182 -520
  58. package/src/core/IndicatorButtonState.ts +1 -1
  59. package/src/core/hoc.tsx +1 -1
  60. package/src/hooks/adsHooks.tsx +21 -22
  61. package/src/hooks/commandHooks.tsx +23 -19
  62. package/src/hooks/index.ts +1 -1
  63. package/src/hooks/useAutoCoreTag.ts +2 -2
  64. package/src/hooks/useScaledValue.tsx +1 -1
  65. package/src/hub/CommandMessage.ts +71 -19
  66. package/src/hub/DebugPanel.ts +280 -0
  67. package/src/hub/HubBase.ts +147 -223
  68. package/src/hub/HubSimulate.ts +93 -24
  69. package/src/hub/HubTauri.ts +87 -96
  70. package/src/hub/HubWebSocket.ts +118 -140
  71. package/src/hub/debug.ts +211 -0
  72. package/src/hub/index.ts +49 -39
  73. package/docs/.nojekyll +0 -1
  74. package/docs/assets/hierarchy.js +0 -1
  75. package/docs/assets/highlight.css +0 -134
  76. package/docs/assets/icons.js +0 -18
  77. package/docs/assets/icons.svg +0 -1
  78. package/docs/assets/main.js +0 -60
  79. package/docs/assets/navigation.js +0 -1
  80. package/docs/assets/search.js +0 -1
  81. package/docs/assets/style.css +0 -1633
  82. package/docs/classes/components_CodeEditor.CodeEditor.html +0 -135
  83. package/docs/classes/components_Indicator.Indicator.html +0 -122
  84. package/docs/classes/components_IndicatorRect.IndicatorRect.html +0 -121
  85. package/docs/classes/components_JogPanel.JogPanel.html +0 -136
  86. package/docs/classes/components_Lamp.Lamp.html +0 -122
  87. package/docs/classes/components_OskDialog.OskDialog.html +0 -125
  88. package/docs/classes/components_TextInput.TextInput.html +0 -125
  89. package/docs/classes/components_ValueDisplay.ValueDisplay.html +0 -148
  90. package/docs/classes/components_ValueIndicator.ValueIndicator.html +0 -126
  91. package/docs/classes/core_ValueSimulator.ValueSimulator.html +0 -51
  92. package/docs/classes/hub_HubBase.HubBase.html +0 -106
  93. package/docs/classes/hub_HubSimulate.HubSimulate.html +0 -75
  94. package/docs/classes/hub_HubTauri.HubTauri.html +0 -93
  95. package/docs/classes/hub_HubWebSocket.HubWebSocket.html +0 -112
  96. package/docs/documents/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -148
  97. package/docs/enums/components_JogPanel.JogDistanceAction.html +0 -5
  98. package/docs/enums/components_JogPanel.JogPanelAction.html +0 -18
  99. package/docs/enums/components_JogPanel.JogSpeedAction.html +0 -5
  100. package/docs/enums/core_ActionMode.ActionMode.html +0 -6
  101. package/docs/enums/core_IndicatorColor.IndicatorColor.html +0 -23
  102. package/docs/functions/assets.BlocklyLogo.html +0 -1
  103. package/docs/functions/assets.Distance.html +0 -1
  104. package/docs/functions/assets.JogLong.html +0 -1
  105. package/docs/functions/assets.JogMedium.html +0 -1
  106. package/docs/functions/assets.JogShort.html +0 -1
  107. package/docs/functions/assets.PythonLogo.html +0 -1
  108. package/docs/functions/assets.Rotation3D.html +0 -1
  109. package/docs/functions/assets.RotationCcw.html +0 -1
  110. package/docs/functions/assets.RotationCcwA.html +0 -1
  111. package/docs/functions/assets.RotationCcwB.html +0 -1
  112. package/docs/functions/assets.RotationCcwC.html +0 -1
  113. package/docs/functions/assets.RotationCw.html +0 -1
  114. package/docs/functions/assets.RotationCwA.html +0 -1
  115. package/docs/functions/assets.RotationCwB.html +0 -1
  116. package/docs/functions/assets.RotationCwC.html +0 -1
  117. package/docs/functions/assets.Run.html +0 -1
  118. package/docs/functions/assets.Speed.html +0 -1
  119. package/docs/functions/assets.SpeedFast.html +0 -1
  120. package/docs/functions/assets.SpeedMedium.html +0 -1
  121. package/docs/functions/assets.SpeedNone.html +0 -1
  122. package/docs/functions/assets.SpeedSlow.html +0 -1
  123. package/docs/functions/assets.Walk.html +0 -1
  124. package/docs/functions/components_BlocklyEditor.createCustomToolbox.html +0 -6
  125. package/docs/functions/core_UniqueId.UniqueId.html +0 -9
  126. package/docs/functions/core_hoc.hocAddSubscription.html +0 -6
  127. package/docs/functions/hooks_adsHooks.useAdsRegisterSymbols.html +0 -16
  128. package/docs/functions/hooks_adsHooks.useAdsTapValue.html +0 -8
  129. package/docs/functions/hooks_adsHooks.useAdsWriteScaledValue.html +0 -18
  130. package/docs/functions/hooks_adsHooks.useAdsWriteValue.html +0 -9
  131. package/docs/functions/hooks_commandHooks.useRegisterSymbols.html +0 -16
  132. package/docs/functions/hooks_commandHooks.useTapValue.html +0 -10
  133. package/docs/functions/hooks_commandHooks.useWriteScaledValue.html +0 -18
  134. package/docs/functions/hooks_commandHooks.useWriteValue.html +0 -11
  135. package/docs/functions/hooks_useAutoCoreTag.ts.makeAutoCoreTagHooks.html +0 -12
  136. package/docs/functions/hooks_useScaledValue.useScaledValue.html +0 -18
  137. package/docs/functions/hub.createHub.html +0 -3
  138. package/docs/hierarchy.html +0 -1
  139. package/docs/index.html +0 -148
  140. package/docs/interfaces/components_IndicatorButton.IndicatorButtonProps.html +0 -654
  141. package/docs/interfaces/components_IndicatorRect.IndicatorRectProps.html +0 -37
  142. package/docs/interfaces/components_JogPanel.JogPanelButtonDefinition.html +0 -5
  143. package/docs/interfaces/components_ToggleGroup.ToggleGroupProps.html +0 -644
  144. package/docs/interfaces/core_AutoCoreTagTypes.BaseContextValue.html +0 -12
  145. package/docs/interfaces/core_AutoCoreTagTypes.ScaleConfig.html +0 -13
  146. package/docs/interfaces/core_EventEmitterContext.Action.html +0 -8
  147. package/docs/interfaces/core_EventEmitterContext.EventEmitterContextType.html +0 -33
  148. package/docs/interfaces/core_EventEmitterContext.State.html +0 -8
  149. package/docs/interfaces/core_EventEmitterContext.Subscription.html +0 -6
  150. package/docs/interfaces/core_IndicatorButtonState.IndicatorButtonState.html +0 -10
  151. package/docs/interfaces/core_PositionContext.IPositionContext.html +0 -17
  152. package/docs/interfaces/hub_CommandMessage.CommandMessage.html +0 -6
  153. package/docs/interfaces/hub_CommandMessage.CommandMessageResult.html +0 -4
  154. package/docs/modules/assets.html +0 -1
  155. package/docs/modules/assets_BlocklyLogo.html +0 -1
  156. package/docs/modules/assets_Distance.html +0 -1
  157. package/docs/modules/assets_JogLong.html +0 -1
  158. package/docs/modules/assets_JogMedium.html +0 -1
  159. package/docs/modules/assets_JogShort.html +0 -1
  160. package/docs/modules/assets_PythonLogo.html +0 -1
  161. package/docs/modules/assets_Rotation3D.html +0 -1
  162. package/docs/modules/assets_RotationCcw.html +0 -1
  163. package/docs/modules/assets_RotationCcwA.html +0 -1
  164. package/docs/modules/assets_RotationCcwB.html +0 -1
  165. package/docs/modules/assets_RotationCcwC.html +0 -1
  166. package/docs/modules/assets_RotationCw.html +0 -1
  167. package/docs/modules/assets_RotationCwA.html +0 -1
  168. package/docs/modules/assets_RotationCwB.html +0 -1
  169. package/docs/modules/assets_RotationCwC.html +0 -1
  170. package/docs/modules/assets_Run.html +0 -1
  171. package/docs/modules/assets_Speed.html +0 -1
  172. package/docs/modules/assets_SpeedFast.html +0 -1
  173. package/docs/modules/assets_SpeedMedium.html +0 -1
  174. package/docs/modules/assets_SpeedNone.html +0 -1
  175. package/docs/modules/assets_SpeedSlow.html +0 -1
  176. package/docs/modules/assets_Walk.html +0 -1
  177. package/docs/modules/components_AutoCoreDevPanel.html +0 -20
  178. package/docs/modules/components_BlocklyEditor.html +0 -1
  179. package/docs/modules/components_CodeEditor.html +0 -1
  180. package/docs/modules/components_FileList.html +0 -1
  181. package/docs/modules/components_FileSelect.html +0 -1
  182. package/docs/modules/components_FitText.html +0 -1
  183. package/docs/modules/components_Indicator.html +0 -1
  184. package/docs/modules/components_IndicatorButton.html +0 -1
  185. package/docs/modules/components_IndicatorRect.html +0 -1
  186. package/docs/modules/components_JogPanel.html +0 -1
  187. package/docs/modules/components_Lamp.html +0 -1
  188. package/docs/modules/components_Osk.html +0 -1
  189. package/docs/modules/components_OskDialog.html +0 -1
  190. package/docs/modules/components_ProgressBarWithValue.html +0 -1
  191. package/docs/modules/components_TextInput.html +0 -1
  192. package/docs/modules/components_ToggleGroup.html +0 -1
  193. package/docs/modules/components_ValueDisplay.html +0 -1
  194. package/docs/modules/components_ValueIndicator.html +0 -1
  195. package/docs/modules/components_ValueInput.html +0 -1
  196. package/docs/modules/core_ActionMode.html +0 -1
  197. package/docs/modules/core_AutoCoreTagContext.html +0 -11
  198. package/docs/modules/core_AutoCoreTagTypes.html +0 -1
  199. package/docs/modules/core_EventEmitterContext.html +0 -53
  200. package/docs/modules/core_IndicatorButtonState.html +0 -1
  201. package/docs/modules/core_IndicatorColor.html +0 -1
  202. package/docs/modules/core_MaskPatterns.html +0 -1
  203. package/docs/modules/core_NumerableTypes.html +0 -1
  204. package/docs/modules/core_PositionContext.html +0 -1
  205. package/docs/modules/core_UniqueId.html +0 -1
  206. package/docs/modules/core_ValueSimulator.html +0 -1
  207. package/docs/modules/core_hoc.html +0 -1
  208. package/docs/modules/hooks.html +0 -1
  209. package/docs/modules/hooks_adsHooks.html +0 -1
  210. package/docs/modules/hooks_commandHooks.html +0 -1
  211. package/docs/modules/hooks_useAutoCoreTag.ts.html +0 -52
  212. package/docs/modules/hooks_useScaledValue.html +0 -1
  213. package/docs/modules/hub.html +0 -1
  214. package/docs/modules/hub_CommandMessage.html +0 -1
  215. package/docs/modules/hub_HubBase.html +0 -1
  216. package/docs/modules/hub_HubSimulate.html +0 -1
  217. package/docs/modules/hub_HubTauri.html +0 -1
  218. package/docs/modules/hub_HubWebSocket.html +0 -1
  219. package/docs/modules.html +0 -23
  220. package/docs/types/components_IndicatorButton.IndicatorButtonOptionsType.html +0 -1
  221. package/docs/types/core_AutoCoreTagTypes.ExtractByTag.html +0 -2
  222. package/docs/types/core_AutoCoreTagTypes.PrimitiveKind.html +0 -1
  223. package/docs/types/core_AutoCoreTagTypes.TagConfig.html +0 -16
  224. package/docs/types/core_AutoCoreTagTypes.TagValueMap.html +0 -1
  225. package/docs/types/core_AutoCoreTagTypes.TagValueOf.html +0 -1
  226. package/docs/types/core_EventEmitterContext.EmitterDispatchFunction.html +0 -3
  227. package/docs/types/core_EventEmitterContext.EmitterSubscribeFunction.html +0 -3
  228. package/docs/types/core_EventEmitterContext.EmitterUnsubscribeFunction.html +0 -3
  229. package/docs/types/core_NumerableTypes.NumerableFormatOptions.html +0 -4
  230. package/docs/types/core_hoc.HocAddSubscriptionProps.html +0 -6
  231. package/docs/variables/components_AutoCoreDevPanel.AutoCoreDevPanel.html +0 -43
  232. package/docs/variables/components_BlocklyEditor.BlocklyEditor.html +0 -13
  233. package/docs/variables/components_BlocklyEditor.StandardToolbox.html +0 -1
  234. package/docs/variables/components_FileList.FileList.html +0 -23
  235. package/docs/variables/components_FileSelect.FileSelect.html +0 -1
  236. package/docs/variables/components_FitText.FitText.html +0 -4
  237. package/docs/variables/components_IndicatorButton.IndicatorButton.html +0 -1
  238. package/docs/variables/components_JogPanel.DefaultLinearJogButtons.html +0 -2
  239. package/docs/variables/components_JogPanel.DefaultRotationJogButtons.html +0 -2
  240. package/docs/variables/components_Osk.Osk.html +0 -1
  241. package/docs/variables/components_ProgressBarWithValue.ProgressBarWithValue.html +0 -1
  242. package/docs/variables/components_ToggleGroup.ToggleGroup.html +0 -1
  243. package/docs/variables/components_ValueInput.ValueInput.html +0 -4
  244. package/docs/variables/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -1
  245. package/docs/variables/core_AutoCoreTagContext.AutoCoreTagProvider.html +0 -7
  246. package/docs/variables/core_EventEmitterContext.EventEmitterContext.html +0 -64
  247. package/docs/variables/core_EventEmitterContext.EventEmitterProvider.html +0 -10
  248. package/docs/variables/core_MaskPatterns.PrimeReactMaskPatterns.html +0 -14
  249. package/docs/variables/core_MaskPatterns.RegExMaskPatterns.html +0 -15
  250. package/docs/variables/core_PositionContext.DimensionsContext.html +0 -6
  251. package/docs/variables/hooks_useScaledValue.kMillimeters2Inches.html +0 -2
  252. package/docs/variables/hooks_useScaledValue.kNewtons2Pounds.html +0 -2
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2024-01-17 11:45:10
4
4
  * -----
5
- * Last Modified: 2025-09-06 14:08:30
5
+ * Last Modified: 2026-01-29 09:32:29
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -17,287 +17,30 @@
17
17
  *
18
18
  * ## Core Features
19
19
  *
20
- * - **Global Event Bus**: Publish and subscribe to events across all components
21
- * - **Backend Integration**: Direct communication with AutoCore server via Hub abstraction
22
- * - **Type-Safe Subscriptions**: Strongly-typed event handling with automatic cleanup
23
- * - **Connection Management**: Automatic reconnection and state synchronization
24
- * - **Development Tools**: Built-in debugging and subscription introspection
20
+ * - **Global Event Bus**: Publish and subscribe to events across all components.
21
+ * - **Backend Integration**: Direct communication with AutoCore server via Hub abstraction.
22
+ * - **Type-Safe Subscriptions**: Strongly-typed event handling with automatic cleanup.
23
+ * - **Performance Optimized**: Uses stable context values and direct callbacks to avoid global re-renders.
24
+ * - **Connection Management**: Automatic reconnection and state synchronization.
25
25
  *
26
26
  * ## Architecture
27
27
  *
28
28
  * The system consists of three main components:
29
- * 1. **EventEmitterProvider**: React context provider that manages global state
30
- * 2. **Hub**: Abstraction layer for backend communication (WebSocket, HTTP, etc.)
31
- * 3. **Subscription Manager**: Handles event routing and lifecycle management
32
- *
33
- * ## Basic Usage
34
- *
35
- * ### 1. Application Setup
36
- *
37
- * Wrap your entire application with the EventEmitterProvider:
38
- *
39
- * ```tsx
40
- * // App.tsx
41
- * import React from 'react';
42
- * import { EventEmitterProvider } from '@adcops/autocore-react/core/EventEmitterContext';
43
- * import { PrimeReactProvider } from 'primereact/api';
44
- * import { MainView } from './MainView';
45
- *
46
- * export default function App() {
47
- * return (
48
- * <EventEmitterProvider>
49
- * <PrimeReactProvider>
50
- * <MainView />
51
- * </PrimeReactProvider>
52
- * </EventEmitterProvider>
53
- * );
54
- * }
55
- * ```
56
- *
57
- * ### 2. Component Communication
58
- *
59
- * Use the context for inter-component communication:
60
- *
61
- * ```tsx
62
- * // PublisherComponent.tsx
63
- * import React, { useContext } from 'react';
64
- * import { EventEmitterContext } from '@adcops/autocore-react/core/EventEmitterContext';
65
- * import { Button } from 'primereact/button';
66
- *
67
- * export const PublisherComponent: React.FC = () => {
68
- * const { dispatch } = useContext(EventEmitterContext);
69
- *
70
- * const handleButtonClick = () => {
71
- * dispatch({
72
- * topic: 'user-action/button-clicked',
73
- * payload: { timestamp: Date.now(), buttonId: 'main-action' }
74
- * });
75
- * };
76
- *
77
- * return <Button label="Trigger Event" onClick={handleButtonClick} />;
78
- * };
79
- *
80
- * // SubscriberComponent.tsx
81
- * import React, { useContext, useEffect, useState } from 'react';
82
- * import { EventEmitterContext } from '@adcops/autocore-react/core/EventEmitterContext';
83
- *
84
- * export const SubscriberComponent: React.FC = () => {
85
- * const { subscribe, unsubscribe } = useContext(EventEmitterContext);
86
- * const [lastAction, setLastAction] = useState<any>(null);
87
- *
88
- * useEffect(() => {
89
- * const subscriptionId = subscribe('user-action/button-clicked', (payload) => {
90
- * setLastAction(payload);
91
- * console.log('Button clicked at:', payload.timestamp);
92
- * });
93
- *
94
- * return () => unsubscribe(subscriptionId);
95
- * }, [subscribe, unsubscribe]);
96
- *
97
- * return (
98
- * <div>
99
- * {lastAction && (
100
- * <p>Last action: {new Date(lastAction.timestamp).toLocaleTimeString()}</p>
101
- * )}
102
- * </div>
103
- * );
104
- * };
105
- * ```
106
- *
107
- * ### 3. Backend Integration
108
- *
109
- * Communicate with AutoCore server through the integrated Hub:
110
- *
111
- * ```tsx
112
- * // ControlPanel.tsx
113
- * import React, { useContext, useEffect, useState } from 'react';
114
- * import { EventEmitterContext } from '@adcops/autocore-react/core/EventEmitterContext';
115
- * import { Button } from 'primereact/button';
116
- * import { InputNumber } from 'primereact/inputnumber';
117
- *
118
- * export const ControlPanel: React.FC = () => {
119
- * const { invoke, subscribe, unsubscribe, isConnected } = useContext(EventEmitterContext);
120
- * const [motorSpeed, setMotorSpeed] = useState<number>(0);
121
- * const [targetSpeed, setTargetSpeed] = useState<number>(100);
122
- * const [connected, setConnected] = useState<boolean>(false);
123
- *
124
- * // Monitor connection status
125
- * useEffect(() => {
126
- * setConnected(isConnected());
127
- *
128
- * const connectionSub = subscribe('HUB/connected', () => setConnected(true));
129
- * const disconnectionSub = subscribe('HUB/disconnected', () => setConnected(false));
130
- *
131
- * return () => {
132
- * unsubscribe(connectionSub);
133
- * unsubscribe(disconnectionSub);
134
- * };
135
- * }, [subscribe, unsubscribe, isConnected]);
136
- *
137
- * // Subscribe to real-time motor speed updates
138
- * useEffect(() => {
139
- * const speedSub = subscribe('ADS/MAIN.motor.speed', (data) => {
140
- * setMotorSpeed(data.value);
141
- * });
142
- *
143
- * return () => unsubscribe(speedSub);
144
- * }, [subscribe, unsubscribe]);
145
- *
146
- * // Send commands to backend
147
- * const handleSpeedChange = async () => {
148
- * try {
149
- * const result = await invoke('ADS', 'write_value', {
150
- * symbol_name: 'MAIN.motor.setpoint',
151
- * value: targetSpeed
152
- * });
153
- *
154
- * if (!result.success) {
155
- * console.error('Failed to set motor speed:', result.error_message);
156
- * }
157
- * } catch (error) {
158
- * console.error('Communication error:', error);
159
- * }
160
- * };
161
- *
162
- * const handleEmergencyStop = async () => {
163
- * try {
164
- * await invoke('ADS', 'emergency_stop', {});
165
- * } catch (error) {
166
- * console.error('Emergency stop failed:', error);
167
- * }
168
- * };
169
- *
170
- * return (
171
- * <div className="control-panel">
172
- * <div className="status">
173
- * Connection: {connected ? '🟢 Connected' : '🔴 Disconnected'}
174
- * </div>
175
- *
176
- * <div className="motor-control">
177
- * <p>Current Speed: {motorSpeed} RPM</p>
178
- *
179
- * <InputNumber
180
- * value={targetSpeed}
181
- * onValueChange={(e) => setTargetSpeed(e.value || 0)}
182
- * min={0}
183
- * max={1000}
184
- * suffix=" RPM"
185
- * />
186
- *
187
- * <Button
188
- * label="Set Speed"
189
- * onClick={handleSpeedChange}
190
- * disabled={!connected}
191
- * />
192
- *
193
- * <Button
194
- * label="Emergency Stop"
195
- * onClick={handleEmergencyStop}
196
- * severity="danger"
197
- * disabled={!connected}
198
- * />
199
- * </div>
200
- * </div>
201
- * );
202
- * };
203
- * ```
204
- *
205
- * ### 4. Advanced Pattern: State Management Hook
206
- *
207
- * Create reusable hooks for common patterns:
208
- *
209
- * ```tsx
210
- * // hooks/useRealtimeValue.ts
211
- * import { useContext, useEffect, useState } from 'react';
212
- * import { EventEmitterContext } from '@adcops/autocore-react/core/EventEmitterContext';
213
- *
214
- * export function useRealtimeValue<T>(topic: string, initialValue?: T) {
215
- * const { subscribe, unsubscribe } = useContext(EventEmitterContext);
216
- * const [value, setValue] = useState<T | undefined>(initialValue);
217
- * const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
218
- *
219
- * useEffect(() => {
220
- * const subscriptionId = subscribe(topic, (data) => {
221
- * setValue(data.value);
222
- * setLastUpdate(new Date());
223
- * });
224
- *
225
- * return () => unsubscribe(subscriptionId);
226
- * }, [topic, subscribe, unsubscribe]);
227
- *
228
- * return { value, lastUpdate };
229
- * }
230
- *
231
- * // Usage in component
232
- * export const SensorDisplay: React.FC = () => {
233
- * const { value: temperature } = useRealtimeValue<number>('ADS/sensors.temperature');
234
- * const { value: pressure } = useRealtimeValue<number>('ADS/sensors.pressure');
235
- *
236
- * return (
237
- * <div>
238
- * <p>Temperature: {temperature?.toFixed(1)}°C</p>
239
- * <p>Pressure: {pressure?.toFixed(2)} bar</p>
240
- * </div>
241
- * );
242
- * };
243
- * ```
244
- *
245
- * ## Integration with AutoCore Systems
246
- *
247
- * The EventEmitterContext is designed to work seamlessly with other AutoCore React components:
248
- *
249
- * ```tsx
250
- * // Complete AutoCore application setup
251
- * import React from 'react';
252
- * import { EventEmitterProvider } from '@adcops/autocore-react/core/EventEmitterContext';
253
- * import { AutoCoreTagProvider } from '@adcops/autocore-react/core/AutoCoreTagContext';
254
- * import { PrimeReactProvider } from 'primereact/api';
255
- * import { tagSpec } from './config/tags';
256
- * import { MainInterface } from './components/MainInterface';
257
- *
258
- * export default function App() {
259
- * return (
260
- * <EventEmitterProvider>
261
- * <AutoCoreTagProvider tags={tagSpec} eagerRead>
262
- * <PrimeReactProvider>
263
- * <MainInterface />
264
- * </PrimeReactProvider>
265
- * </AutoCoreTagProvider>
266
- * </EventEmitterProvider>
267
- * );
268
- * }
269
- * ```
270
- *
271
- * ## Best Practices
272
- *
273
- * 1. **Always Cleanup Subscriptions**: Use the cleanup function returned by useEffect
274
- * 2. **Use Meaningful Topic Names**: Follow a hierarchical naming convention (e.g., 'domain/category/specific')
275
- * 3. **Handle Connection States**: Always check connection status before invoking backend functions
276
- * 4. **Error Handling**: Wrap invoke calls in try-catch blocks
277
- * 5. **Type Safety**: Use TypeScript interfaces for payload structures
278
- * 6. **Performance**: Avoid subscribing to high-frequency events in render-heavy components
279
- *
280
- * ## Debugging
281
- *
282
- * Use the built-in debugging features:
283
- *
284
- * ```tsx
285
- * const { getSubscriptions, state } = useContext(EventEmitterContext);
286
- *
287
- * // View all subscriptions
288
- * console.log('All subscriptions:', getSubscriptions());
289
- *
290
- * // View subscriptions for specific topic
291
- * console.log('Motor subscriptions:', getSubscriptions('ADS/MAIN.motor.speed'));
29
+ * 1. **EventEmitterProvider**: React context provider that manages global state and event routing.
30
+ * 2. **Hub**: Abstraction layer for backend communication (WebSocket, HTTP, etc.).
31
+ * 3. **Subscription Manager**: Handles event routing and lifecycle management.
32
+ *
33
+ * ## Performance Notes
292
34
  *
293
- * // View current state
294
- * console.log('EventEmitter state:', state);
295
- * ```
35
+ * To ensure high performance with high-frequency data (e.g., 50Hz sensor updates):
36
+ * 1. **No State Updates on Dispatch**: The `dispatch` function does NOT update React state. It calls listeners directly.
37
+ * This prevents the entire component tree from re-rendering on every message.
38
+ * 2. **Stable Context Value**: The `contextValue` object is memoized and rarely changes.
39
+ * 3. **Direct Subscriptions**: Components subscribe via `useEffect` and receive updates via callbacks, not props.
296
40
  *
297
41
  * @module core/EventEmitterContext
298
- * @version 3.0.41
42
+ * @version 3.0.42
299
43
  * @author Automated Design Corp.
300
- * @since 1.0.0
301
44
  */
302
45
 
303
46
  import React, {
@@ -311,29 +54,32 @@ import React, {
311
54
 
312
55
  import type {ReactNode} from "react";
313
56
  import { createHub, Hub } from "../hub";
314
- import type { CommandMessageResult } from "../hub/CommandMessage";
57
+ import type { CommandMessage } from "../hub";
58
+ import { MessageType } from "../hub/CommandMessage";
315
59
 
316
60
  export { Hub };
317
61
 
318
62
  /**
319
- * Represents an event subsription.
63
+ * Represents an active event subscription.
320
64
  */
321
65
  export interface Subscription {
322
- /** ID of the subscription used for unsubscription. */
66
+ /** Unique ID of the subscription used for unsubscription. */
323
67
  id: number;
324
- /** Callback function. */
68
+ /** Callback function to execute when the event fires. */
325
69
  callback: React.Dispatch<any>;
326
70
  }
327
71
 
328
72
  /**
329
- * Represents the payload data associated with an event.
73
+ * Represents the internal state of the EventEmitter.
74
+ * Mostly used for debugging and dev tools inspection.
330
75
  */
331
76
  export interface State {
332
77
  /**
333
- * The optional data payload of the event.
78
+ * The optional data payload of the last event (DEBUGGING ONLY).
79
+ * Note: This is no longer updated in production for performance reasons.
334
80
  */
335
81
  eventData?: any;
336
- /** Callback subscription. A list of callback subscriptions matched to a topic. */
82
+ /** Active subscriptions grouped by topic. */
337
83
  subscriptions: Record<string, Subscription[]>;
338
84
 
339
85
  /** Tracks the next subscription ID that will be assigned. */
@@ -341,259 +87,152 @@ export interface State {
341
87
  }
342
88
 
343
89
  /**
344
- * An action event published by the EventEmitterContext. Contains
345
- * the topic identifying the event and the optional payload, which is
346
- * a value of any type.
90
+ * An action event published by the EventEmitterContext.
347
91
  */
348
92
  export interface Action {
349
- /**
350
- * The topic or identifier of the event.
351
- */
93
+ /** The topic or identifier of the event. */
352
94
  topic: string;
353
-
354
- /**
355
- * The optional data payload associated with the event.
356
- */
95
+ /** The optional data payload associated with the event. */
357
96
  payload?: any;
358
97
  }
359
98
 
360
- /**
361
- * Type declaration for the EventEmitter dispatch function, which
362
- * publishes an Action globally throughout the EventEmitterContext.
363
- */
364
99
  export type EmitterDispatchFunction = (action: Action) => void;
365
100
 
366
101
  /**
367
- * Type declaration for the EventEmitter dispatch function, which
368
- * receives an Action on a specific topic broadcast through the EventEmitterContext.
369
- */
370
- export type EmitterSubscribeFunction = (action: Action) => void;
371
-
372
- /**
373
- * Type declaration for the EventEmitter unsubscribe function, which
374
- * receives an Action on a specific topic broadcast through the EventEmitterContext.
375
- */
376
- export type EmitterUnsubscribeFunction = (action: Action) => void;
377
-
378
- /**
379
- * Defines the context for an event emitter used throughout a front-end application to manage and dispatch events.
380
- * This interface includes methods for managing the application's state, handling global actions, communicating with the back end,
381
- * subscribing to and unsubscribing from events, and accessing a global hub for event publishing and subscription management.
382
- * It serves as a central point for event-driven interactions within the application, facilitating communication between
383
- * components and the back end, as well as among components themselves.
102
+ * Defines the context for the global event emitter.
103
+ * Provides methods for dispatching events, subscribing to topics, and communicating with the backend.
384
104
  */
385
105
  export interface EventEmitterContextType {
386
106
  /**
387
- * The current state of the event emitter, containing the latest event data.
107
+ * The current state of the event emitter (mostly for debugging).
388
108
  */
389
109
  state: State;
390
110
 
391
111
  /**
392
- * A function to dispatch actions globally throughout the front-end,
393
- * triggering state updates and events.
394
- *
395
- * @param action The action to dispatch, containing topic and optional payload.
112
+ * Dispatch an action globally throughout the front-end.
113
+ * Triggers callbacks for all subscribers of the topic.
114
+ *
115
+ * @param action The action to dispatch.
396
116
  */
397
- dispatch: (action: Action) => void;
117
+ dispatch: EmitterDispatchFunction;
398
118
 
399
119
  /**
400
- * Invoke/send a message to the back end.
401
- * This does NOT get published to the front end.
120
+ * Send a message to the backend (Low-level).
121
+ *
122
+ * @param topic The FQDN of the resource (e.g., "ads.plc1.gio.bControlPowerOk")
123
+ * @param messageType The action to perform (Read, Write, Subscribe, etc.)
124
+ * @param payload Optional data payload
125
+ * @returns Promise resolving to the CommandMessage response
402
126
  */
403
127
  invoke(
404
- domain: string,
405
- fname: string,
128
+ topic: string,
129
+ messageType: MessageType,
406
130
  payload?: object
407
- ): Promise<CommandMessageResult>;
131
+ ): Promise<CommandMessage>;
132
+
133
+ /**
134
+ * Read a value from the backend.
135
+ *
136
+ * @param topic The FQDN of the resource to read
137
+ * @param payload Optional additional parameters
138
+ * @returns Promise resolving to the CommandMessage response with the value in `data`
139
+ */
140
+ read(topic: string, payload?: object): Promise<CommandMessage>;
141
+
142
+ /**
143
+ * Write a value to the backend.
144
+ *
145
+ * @param topic The FQDN of the resource to write
146
+ * @param value The value to write
147
+ * @returns Promise resolving to the CommandMessage response
148
+ */
149
+ write(topic: string, value: any): Promise<CommandMessage>;
150
+
151
+ /**
152
+ * Subscribe to value changes on the backend.
153
+ *
154
+ * Tells the server to start broadcasting updates for a specific topic.
155
+ * Also registers a mapping between the local `tagName` and the remote `topic` (FQDN)
156
+ * in the Hub, so that incoming broadcasts are routed correctly.
157
+ *
158
+ * @param tagName The local tag name to map the topic to
159
+ * @param topic The FQDN of the resource to watch
160
+ * @param payload Optional subscription parameters (e.g., update rate)
161
+ * @returns Promise resolving to the CommandMessage response
162
+ */
163
+ serverSubscribe(tagName: string, topic: string, payload?: object): Promise<CommandMessage>;
164
+
165
+ /**
166
+ * Unsubscribe from value changes on the backend.
167
+ *
168
+ * @param tagName The local tag name to stop watching
169
+ * @param payload Optional parameters
170
+ * @returns Promise resolving to the CommandMessage response
171
+ */
172
+ serverUnsubscribe(tagName: string, payload?: object): Promise<CommandMessage>;
408
173
 
409
174
  /**
410
- * Subscribe to events identified by the topic.
411
- * @param topic The subscription topic.
175
+ * Subscribe to **local** frontend events.
176
+ *
177
+ * This registers a callback to listen for events dispatched on the local event bus.
178
+ * This includes both locally dispatched events AND broadcast messages received
179
+ * from the server (which the Hub dispatches to the local bus).
180
+ *
181
+ * @param topic The subscription topic (or tagName).
412
182
  * @param callback The callback to signal.
413
183
  * @returns number Subscription ID used to unsubscribe later.
414
184
  */
415
185
  subscribe: (topic: string, callback: React.Dispatch<any>) => number;
416
186
 
417
187
  /**
418
- * Unsubscribe to events.
419
- * @param subscriptionId The id of the subscription returned by the subscribe method.
420
- * @returns
188
+ * Unsubscribe from **local** frontend events.
189
+ *
190
+ * @param subscriptionId The id returned by the subscribe method.
421
191
  */
422
192
  unsubscribe: (subscriptionId: number) => void;
423
193
 
424
194
  /**
425
- * Global hub for publishing and receiving events throughout the interface, and for exchanging
426
- * data with the backend.
195
+ * Global hub instance for backend communication.
427
196
  */
428
197
  hub: Hub | null;
429
198
 
430
199
  /**
431
200
  * Retrieves the current subscriptions. Used for debugging purposes.
432
- * @param topic Optional. The topic to retrieve subscriptions for. If omitted, returns all subscriptions.
433
- * @returns An object containing the current subscriptions, optionally filtered by topic.
434
201
  */
435
202
  getSubscriptions: (
436
203
  topic?: string
437
204
  ) => Record<string, Subscription[]> | Subscription[];
438
205
 
439
206
  /**
440
- * Returns true if the Hub in use is connected to its source.
441
- * @returns boolean
207
+ * Returns true if the Hub is connected to the backend.
442
208
  */
443
209
  isConnected: () => boolean;
444
210
  }
445
211
 
446
- /**
447
- * A global context for managing event emission and subscription.
448
- *
449
- * Creates a React context for the EventEmitter system, providing a structured way to manage events and data flow
450
- * in a React application. It serves as a global event bus that components can subscribe to or emit events, allowing for
451
- * a loosely coupled architecture. Additionally, it provides a mechanism for invoking backend functions and managing
452
- * subscriptions, making it easier to integrate React components with backend services.
453
- *
454
- * The context includes several key functionalities:
455
- * - `state`: Maintains the current state of subscriptions and their identifiers.
456
- * - `dispatch`: Allows components to emit events with specific topics and payloads, which can be listened to by other components.
457
- * - `subscribe`: Enables components to listen to specific topics and react to those events by providing a callback function.
458
- * - `unsubscribe`: Provides a way for components to stop listening to events, helping prevent memory leaks and unnecessary updates.
459
- * - `invoke`: Facilitates calling backend functions with specific arguments and handling their responses asynchronously.
460
- * - `getSubscriptions`: Offers insight into current active subscriptions, useful for debugging purposes.
461
- *
462
- * This context is essential for applications that require a high degree of inter-component communication or need to
463
- * interact with a backend efficiently.
464
- *
465
- * For more information, see [Additional Documentation](../additional-docs/GlobalEventEmitter.md).
466
- *
467
- * ## Usage
468
- *
469
- * The entire application should be wrapped in the EventEmitterProvider.
470
- *
471
- * App.tsx
472
- * ```
473
- * import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
474
- * function App() {
475
- *
476
- * return(
477
- * <EventEmitterProvider>
478
- * <PrimeReactProvider>
479
- * <main>
480
- * <section>
481
- * <ContentView />
482
- * </section>
483
- * </main>
484
- * </PrimeReactProvider>
485
- * </EventEmitterProvider>
486
- * );
487
- *
488
- * }
489
- *
490
- * ```
491
- *
492
- * ### Catching and receiving events
493
- * The EventEmitterContext creates an appropriate instance of the hub, which is derived from HubBase.
494
- * That hub can be used to publish and subscribe to
495
- * topics globally in the front-end, regardless of being connected to any backend.
496
- * Usage within a component is simple.
497
- *
498
- * ```
499
- * const {dispatch, subscribe, unsubscribe} = useContext(EventEmitterContext);
500
- * const [controlPower, setControlPower] = useState(false);
501
- * useEffect(() => {
502
- * const unsubscribeControlPower = subscribe('value-simulator-bBit1', (value) => {
503
- * setControlPower(value);
504
- * });
505
- *
506
- *
507
- * return () => {
508
- * unsubscribe(unsubscribeControlPower);
509
- * }
510
- * }, [] );
511
- *
512
- * const onPbPressed = () => {
513
- * let count = 1;
514
- * dispatch({
515
- * topic: "my-awesome-topic",
516
- * payload: count
517
- * });
518
- * }
519
- *
520
- * ```
521
- * The hub should also be used for invoking events in the backend.
522
- * This example will call the function "update_count" in the backend, passing
523
- * the expected argument "count". Details of the interaction between the Hub and
524
- * the backend will be handled by the appropriate HubBase sub-class, and should
525
- * be transparent to the front end.
526
- *
527
- * ```
528
- * const {invoke} = useContext(EventEmitterContext);
529
- * const incrementCount = () => {
530
- * count += 1;
531
- * invoke('update_count', {"count": count});
532
- * };
533
- *
534
- * Subscribing to a topic is simple. The type of value received is specific
535
- * to the topic.
536
- *
537
- * Example: Listen to an event 'xarm-position':
538
- * ```
539
- * const {subscribe, unsubscribe} = useContext(EventEmitterContext);
540
- * useEffect(() => {
541
- * const unsubscripeMp = subscribe('xarm-position', (value) => {
542
- * // The publisher sent a JSON object of 3D position values.
543
- * setX(value.x);
544
- * setY(value.y);
545
- * setZ(value.z);
546
- * setA(value.roll);
547
- * setB(value.yaw);
548
- * setC(value.pitch);
549
- * });
550
- *
551
- * return () => {
552
- * unsubscribe(unsubscripeMp);
553
- * }
554
- *
555
- * }, [] );
556
- *
557
- * ```
558
- *
559
- * For applications that need to access the instance of the hub, get the current instance
560
- * from the EventEmitterContext:
561
- *
562
- * ```
563
- * const {hub} = useContext(EventEmitterContext);
564
- * * ```
565
- *
566
- *
567
- */
212
+ // Default placeholder CommandMessage for context initialization
213
+ const placeholderResponse: CommandMessage = {
214
+ data: {},
215
+ success: false,
216
+ error_message: "Context not initialized",
217
+ transaction_id: 0,
218
+ timecode: 0,
219
+ topic: "",
220
+ message_type: MessageType.NoOp
221
+ };
222
+
568
223
  export const EventEmitterContext = createContext<EventEmitterContextType>({
569
224
  state: { subscriptions: {}, nextSubscriptionId: 1 },
570
225
  dispatch: () => {},
571
- subscribe: () => {
572
- return 0;
573
- }, // Placeholder for subscription logic
574
- invoke: async (domain: string, fname: string, payload?: object) => {
575
- domain;
576
- fname;
577
- payload;
578
- let ret: CommandMessageResult = {
579
- data: {},
580
- success: false,
581
- error_message: "",
582
- };
583
- // Placeholder for invoke logic
584
- // Implement the logic to send a message to the backend and return a promise
585
- return Promise.resolve(ret); // Example placeholder, replace with actual implementation
586
- },
587
- unsubscribe: (subscriptionId: number) => {
588
- subscriptionId;
589
- }, // Placeholder for unsubscription logic
226
+ subscribe: () => 0,
227
+ unsubscribe: () => {},
228
+ invoke: async () => placeholderResponse,
229
+ read: async () => placeholderResponse,
230
+ write: async () => placeholderResponse,
231
+ serverSubscribe: async (_tagName: string, _topic: string, _payload?: object) => placeholderResponse,
232
+ serverUnsubscribe: async (_tagName: string, _payload?: object) => placeholderResponse,
590
233
  hub: null,
591
- getSubscriptions: () => {
592
- return [];
593
- },
594
- isConnected: () => {
595
- return false;
596
- },
234
+ getSubscriptions: () => [],
235
+ isConnected: () => false,
597
236
  });
598
237
 
599
238
  /**
@@ -603,50 +242,31 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
603
242
  * with the event emitter.
604
243
  *
605
244
  * @param children The child components to be wrapped in the context.
606
- *
607
- * ## Usage
608
- *
609
- * The entire application should be wrapped in the EventEmitterProvider.
610
- *
611
- * App.tsx
612
- * ```
613
- * import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
614
- * function App() {
615
- *
616
- * return(
617
- * <EventEmitterProvider>
618
- * <PrimeReactProvider>
619
- * <main>
620
- * <section>
621
- * <ContentView />
622
- * </section>
623
- * </main>
624
- * </PrimeReactProvider>
625
- * </EventEmitterProvider>
626
- * );
627
- *
628
- * }
629
- *
630
- * ```
631
245
  */
632
246
  export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
633
247
  children,
634
248
  }) => {
249
+ console.log("[EventEmitterProvider] Rendering...");
250
+
635
251
  const [state, setState] = useState<State>({
636
252
  subscriptions: {},
637
253
  nextSubscriptionId: 1,
638
254
  });
639
255
 
640
- // Memoize the hub instance so it's only created once
641
- const hub = useMemo(() => createHub(), []);
256
+ // PERFORMANCE: Memoize the hub instance so it's only created once.
257
+ // This ensures the WebSocket connection persists across re-renders.
258
+ const hub = useMemo(() => {
259
+ console.log("[EventEmitterProvider] Creating Hub instance");
260
+ return createHub();
261
+ }, []);
642
262
 
643
263
  // Use ref for subscription ID management (avoids global state issues)
644
264
  const nextIdRef = useRef(1);
645
265
 
646
- // Subscription storage - this is the single source of truth
266
+ // Subscription storage - this is the single source of truth for local listeners
647
267
  const subsRef = useRef<Record<string, Subscription[]>>({});
648
268
 
649
- // Sync state with ref for debugging/inspection purposes only
269
+ // Sync state with ref for debugging/inspection purposes only (on mount)
650
270
  useEffect(() => {
651
271
  setState(prev => ({
652
272
  ...prev,
@@ -655,6 +275,13 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
655
275
  }));
656
276
  }, []);
657
277
 
278
+ /**
279
+ * Dispatches an event to all listeners.
280
+ *
281
+ * PERFORMANCE CRITICAL: This function iterates through listeners and calls them directly.
282
+ * It intentionally does NOT update React state (setState) to avoid triggering a
283
+ * full app re-render on every high-frequency event.
284
+ */
658
285
  const dispatch = useCallback((action: Action) => {
659
286
  const { topic, payload } = action;
660
287
 
@@ -670,8 +297,9 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
670
297
  }
671
298
  }
672
299
 
673
- // Update state for debugging/inspection (optional)
674
- setState((prev) => ({ ...prev, eventData: payload }));
300
+ // NOTE: State update removed for performance.
301
+ // Uncommenting this will cause the entire Provider to re-render on every event!
302
+ // setState((prev) => ({ ...prev, eventData: payload }));
675
303
  }, []);
676
304
 
677
305
  const subscribe = useCallback(
@@ -690,7 +318,7 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
690
318
  [topic]: newSubs
691
319
  };
692
320
 
693
- // Update state for debugging/inspection
321
+ // Update state for debugging/inspection (less frequent than dispatch, so acceptable)
694
322
  setState(prevState => ({
695
323
  ...prevState,
696
324
  subscriptions: { ...subsRef.current },
@@ -711,7 +339,6 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
711
339
  if (filteredSubs.length > 0) {
712
340
  newSubscriptions[topic] = filteredSubs;
713
341
  }
714
- // If no subscriptions left for this topic, don't include it
715
342
  }
716
343
 
717
344
  // Atomically replace the entire structure
@@ -724,7 +351,7 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
724
351
  }));
725
352
  }, []);
726
353
 
727
- // Clean up on unmount and handle HMR
354
+ // Clean up on unmount
728
355
  useEffect(() => {
729
356
  return () => {
730
357
  subsRef.current = {};
@@ -740,23 +367,58 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
740
367
  });
741
368
  }
742
369
 
743
- // Provide the memoized hub instance in the context value
370
+ // Memoize hub methods separately so they don't change when state changes.
371
+ // This prevents downstream useEffects from re-running due to function reference changes.
372
+ const invoke = useCallback(
373
+ (topic: string, messageType: MessageType, payload?: object) => hub.invoke(topic, messageType, payload),
374
+ [hub]
375
+ );
376
+ const read = useCallback(
377
+ (topic: string, payload?: object) => hub.read(topic, payload),
378
+ [hub]
379
+ );
380
+ const write = useCallback(
381
+ (topic: string, value: any) => hub.write(topic, value),
382
+ [hub]
383
+ );
384
+ const serverSubscribe = useCallback(
385
+ (tagName: string, topic: string, payload?: object) => hub.subscribe(tagName, topic, payload),
386
+ [hub]
387
+ );
388
+ const serverUnsubscribe = useCallback(
389
+ (tagName: string, payload?: object) => hub.unsubscribe(tagName, payload),
390
+ [hub]
391
+ );
392
+ const isConnected = useCallback(() => hub.isConnected(), [hub]);
393
+
394
+ // Provide the memoized context value
395
+ // PERFORMANCE: This object is memoized and its dependencies (dispatch, subscribe, etc.)
396
+ // are also memoized/stable. This means 'contextValue' reference rarely changes,
397
+ // preventing consumers (like App) from re-rendering unless necessary.
744
398
  const contextValue = useMemo(
745
399
  () => ({
746
400
  state,
747
401
  dispatch,
748
402
  subscribe,
749
403
  unsubscribe,
750
- invoke: hub.invoke,
404
+ invoke,
405
+ read,
406
+ write,
407
+ serverSubscribe,
408
+ serverUnsubscribe,
751
409
  hub,
752
410
  getSubscriptions: (topic?: string) =>
753
411
  topic ? subsRef.current[topic] ?? [] : subsRef.current,
754
- isConnected: hub.isConnected,
412
+ isConnected,
755
413
  }),
756
- [state, hub, dispatch, subscribe, unsubscribe]
414
+ [state, hub, dispatch, subscribe, unsubscribe, invoke, read, write, serverSubscribe, serverUnsubscribe, isConnected]
757
415
  );
758
416
 
759
- hub.setContext(contextValue);
417
+ // Set the context on the hub instance so it can dispatch back to us.
418
+ // Must be in useEffect to avoid side-effects during render phase.
419
+ useEffect(() => {
420
+ hub.setContext(contextValue);
421
+ }, [hub, contextValue]);
760
422
 
761
423
  return (
762
424
  <EventEmitterContext.Provider value={contextValue}>