@adcops/autocore-react 3.0.40 → 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.
- package/additional-docs/AutoCoreTagContext.md +441 -0
- package/additional-docs/react_performance_notes.md +94 -0
- package/dist/assets/BlocklyLogo.d.ts +1 -0
- package/dist/assets/BlocklyLogo.d.ts.map +1 -0
- package/dist/assets/Distance.d.ts +1 -0
- package/dist/assets/Distance.d.ts.map +1 -0
- package/dist/assets/JogLong.d.ts +1 -0
- package/dist/assets/JogLong.d.ts.map +1 -0
- package/dist/assets/JogMedium.d.ts +1 -0
- package/dist/assets/JogMedium.d.ts.map +1 -0
- package/dist/assets/JogShort.d.ts +1 -0
- package/dist/assets/JogShort.d.ts.map +1 -0
- package/dist/assets/PythonLogo.d.ts +1 -0
- package/dist/assets/PythonLogo.d.ts.map +1 -0
- package/dist/assets/Rotation3D.d.ts +1 -0
- package/dist/assets/Rotation3D.d.ts.map +1 -0
- package/dist/assets/RotationCcw.d.ts +1 -0
- package/dist/assets/RotationCcw.d.ts.map +1 -0
- package/dist/assets/RotationCcwA.d.ts +1 -0
- package/dist/assets/RotationCcwA.d.ts.map +1 -0
- package/dist/assets/RotationCcwB.d.ts +1 -0
- package/dist/assets/RotationCcwB.d.ts.map +1 -0
- package/dist/assets/RotationCcwC.d.ts +1 -0
- package/dist/assets/RotationCcwC.d.ts.map +1 -0
- package/dist/assets/RotationCw.d.ts +1 -0
- package/dist/assets/RotationCw.d.ts.map +1 -0
- package/dist/assets/RotationCwA.d.ts +1 -0
- package/dist/assets/RotationCwA.d.ts.map +1 -0
- package/dist/assets/RotationCwB.d.ts +1 -0
- package/dist/assets/RotationCwB.d.ts.map +1 -0
- package/dist/assets/RotationCwC.d.ts +1 -0
- package/dist/assets/RotationCwC.d.ts.map +1 -0
- package/dist/assets/Run.d.ts +1 -0
- package/dist/assets/Run.d.ts.map +1 -0
- package/dist/assets/Speed.d.ts +1 -0
- package/dist/assets/Speed.d.ts.map +1 -0
- package/dist/assets/SpeedFast.d.ts +1 -0
- package/dist/assets/SpeedFast.d.ts.map +1 -0
- package/dist/assets/SpeedMedium.d.ts +1 -0
- package/dist/assets/SpeedMedium.d.ts.map +1 -0
- package/dist/assets/SpeedNone.d.ts +1 -0
- package/dist/assets/SpeedNone.d.ts.map +1 -0
- package/dist/assets/SpeedSlow.d.ts +1 -0
- package/dist/assets/SpeedSlow.d.ts.map +1 -0
- package/dist/assets/Walk.d.ts +1 -0
- package/dist/assets/Walk.d.ts.map +1 -0
- package/dist/assets/index.d.ts +1 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/components/AutoCoreDevPanel.d.ts +144 -0
- package/dist/components/AutoCoreDevPanel.d.ts.map +1 -0
- package/dist/components/AutoCoreDevPanel.js +1 -0
- package/dist/components/BlocklyEditor.d.ts +1 -0
- package/dist/components/BlocklyEditor.d.ts.map +1 -0
- package/dist/components/BlocklyEditor.js +1 -1
- package/dist/components/CodeEditor.d.ts +2 -1
- package/dist/components/CodeEditor.d.ts.map +1 -0
- package/dist/components/CodeEditor.js +1 -1
- package/dist/components/FileList.d.ts +1 -0
- package/dist/components/FileList.d.ts.map +1 -0
- package/dist/components/FileList.js +1 -1
- package/dist/components/FileSelect.d.ts +1 -0
- package/dist/components/FileSelect.d.ts.map +1 -0
- package/dist/components/FileSelect.js +1 -1
- package/dist/components/FitText.d.ts +1 -0
- package/dist/components/FitText.d.ts.map +1 -0
- package/dist/components/FitText.js +1 -1
- package/dist/components/Indicator.d.ts +2 -1
- package/dist/components/Indicator.d.ts.map +1 -0
- package/dist/components/Indicator.js +1 -1
- package/dist/components/IndicatorButton.d.ts +2 -1
- package/dist/components/IndicatorButton.d.ts.map +1 -0
- package/dist/components/IndicatorButton.js +1 -1
- package/dist/components/IndicatorRect.d.ts +2 -1
- package/dist/components/IndicatorRect.d.ts.map +1 -0
- package/dist/components/JogPanel.d.ts +1 -0
- package/dist/components/JogPanel.d.ts.map +1 -0
- package/dist/components/Lamp.d.ts +2 -1
- package/dist/components/Lamp.d.ts.map +1 -0
- package/dist/components/Lamp.js +1 -1
- package/dist/components/Osk.d.ts +1 -0
- package/dist/components/Osk.d.ts.map +1 -0
- package/dist/components/Osk.js +1 -1
- package/dist/components/OskDialog.d.ts +1 -0
- package/dist/components/OskDialog.d.ts.map +1 -0
- package/dist/components/ProgressBarWithValue.d.ts +1 -0
- package/dist/components/ProgressBarWithValue.d.ts.map +1 -0
- package/dist/components/ProgressBarWithValue.js +1 -1
- package/dist/components/TextInput.d.ts +62 -103
- package/dist/components/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput.js +1 -1
- package/dist/components/ToggleGroup.d.ts +2 -1
- package/dist/components/ToggleGroup.d.ts.map +1 -0
- package/dist/components/ToggleGroup.js +1 -1
- package/dist/components/ValueDisplay.d.ts +3 -2
- package/dist/components/ValueDisplay.d.ts.map +1 -0
- package/dist/components/ValueDisplay.js +1 -1
- package/dist/components/ValueIndicator.d.ts +2 -1
- package/dist/components/ValueIndicator.d.ts.map +1 -0
- package/dist/components/ValueIndicator.js +1 -1
- package/dist/components/ValueInput.d.ts +2 -1
- package/dist/components/ValueInput.d.ts.map +1 -0
- package/dist/components/ValueInput.js +1 -1
- package/dist/core/ActionMode.d.ts +1 -0
- package/dist/core/ActionMode.d.ts.map +1 -0
- package/dist/core/AutoCoreTagContext.d.ts +98 -0
- package/dist/core/AutoCoreTagContext.d.ts.map +1 -0
- package/dist/core/AutoCoreTagContext.js +1 -0
- package/dist/core/AutoCoreTagTypes.d.ts +283 -0
- package/dist/core/AutoCoreTagTypes.d.ts.map +1 -0
- package/dist/core/AutoCoreTagTypes.js +1 -0
- package/dist/core/CoreStreamTypes.d.ts +345 -0
- package/dist/core/CoreStreamTypes.d.ts.map +1 -0
- package/dist/core/CoreStreamTypes.js +1 -0
- package/dist/core/EventEmitterContext.d.ts +113 -202
- package/dist/core/EventEmitterContext.d.ts.map +1 -0
- package/dist/core/EventEmitterContext.js +1 -1
- package/dist/core/IndicatorButtonState.d.ts +1 -0
- package/dist/core/IndicatorButtonState.d.ts.map +1 -0
- package/dist/core/IndicatorColor.d.ts +1 -0
- package/dist/core/IndicatorColor.d.ts.map +1 -0
- package/dist/core/MaskPatterns.d.ts +1 -0
- package/dist/core/MaskPatterns.d.ts.map +1 -0
- package/dist/core/NumerableTypes.d.ts +1 -0
- package/dist/core/NumerableTypes.d.ts.map +1 -0
- package/dist/core/NumerableTypes.js +1 -1
- package/dist/core/PositionContext.d.ts +1 -1
- package/dist/core/PositionContext.d.ts.map +1 -0
- package/dist/core/UniqueId.d.ts +1 -0
- package/dist/core/UniqueId.d.ts.map +1 -0
- package/dist/core/ValueSimulator.d.ts +2 -1
- package/dist/core/ValueSimulator.d.ts.map +1 -0
- package/dist/core/ValueSimulator.js +1 -1
- package/dist/core/hoc.d.ts +1 -0
- package/dist/core/hoc.d.ts.map +1 -0
- package/dist/core/hoc.js +1 -1
- package/dist/hooks/adsHooks.d.ts +1 -0
- package/dist/hooks/adsHooks.d.ts.map +1 -0
- package/dist/hooks/adsHooks.js +1 -1
- package/dist/hooks/commandHooks.d.ts +4 -3
- package/dist/hooks/commandHooks.d.ts.map +1 -0
- package/dist/hooks/commandHooks.js +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useAutoCoreTag.d.ts +26 -0
- package/dist/hooks/useAutoCoreTag.d.ts.map +1 -0
- package/dist/hooks/useAutoCoreTag.js +1 -0
- package/dist/hooks/useScaledValue.d.ts +1 -0
- package/dist/hooks/useScaledValue.d.ts.map +1 -0
- package/dist/hooks/useScaledValue.js +1 -1
- package/dist/hub/CommandMessage.d.ts +19 -9
- package/dist/hub/CommandMessage.d.ts.map +1 -0
- package/dist/hub/CommandMessage.js +1 -1
- package/dist/hub/DebugPanel.d.ts +31 -0
- package/dist/hub/DebugPanel.d.ts.map +1 -0
- package/dist/hub/DebugPanel.js +1 -0
- package/dist/hub/HubBase.d.ts +85 -130
- package/dist/hub/HubBase.d.ts.map +1 -0
- package/dist/hub/HubBase.js +1 -1
- package/dist/hub/HubSimulate.d.ts +42 -8
- package/dist/hub/HubSimulate.d.ts.map +1 -0
- package/dist/hub/HubSimulate.js +1 -1
- package/dist/hub/HubTauri.d.ts +25 -60
- package/dist/hub/HubTauri.d.ts.map +1 -0
- package/dist/hub/HubTauri.js +1 -1
- package/dist/hub/HubWebSocket.d.ts +34 -17
- package/dist/hub/HubWebSocket.d.ts.map +1 -0
- package/dist/hub/HubWebSocket.js +1 -1
- package/dist/hub/debug.d.ts +23 -0
- package/dist/hub/debug.d.ts.map +1 -0
- package/dist/hub/debug.js +1 -0
- package/dist/hub/index.d.ts +20 -4
- package/dist/hub/index.d.ts.map +1 -0
- package/dist/hub/index.js +1 -1
- package/package.json +32 -27
- package/readme.md +193 -22
- package/src/components/AutoCoreDevPanel.tsx +414 -0
- package/src/components/CodeEditor.tsx +2 -2
- package/src/components/FileList.tsx +7 -6
- package/src/components/FileSelect.tsx +2 -1
- package/src/components/Indicator.tsx +2 -2
- package/src/components/IndicatorButton.tsx +2 -2
- package/src/components/IndicatorRect.tsx +2 -2
- package/src/components/Lamp.tsx +3 -3
- package/src/components/TextInput.tsx +159 -240
- package/src/components/ToggleGroup.tsx +3 -3
- package/src/components/ValueDisplay.tsx +4 -4
- package/src/components/ValueIndicator.tsx +2 -2
- package/src/components/ValueInput.tsx +2 -2
- package/src/core/ActionMode.ts +1 -1
- package/src/core/AutoCoreTagContext.tsx +615 -0
- package/src/core/AutoCoreTagTypes.ts +334 -0
- package/src/core/CoreStreamTypes.ts +512 -0
- package/src/core/EventEmitterContext.tsx +257 -281
- package/src/core/IndicatorButtonState.ts +1 -1
- package/src/core/ValueSimulator.ts +2 -2
- package/src/core/hoc.tsx +1 -1
- package/src/hooks/adsHooks.tsx +21 -22
- package/src/hooks/commandHooks.tsx +23 -19
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useAutoCoreTag.ts +103 -0
- package/src/hooks/useScaledValue.tsx +1 -1
- package/src/hub/CommandMessage.ts +71 -19
- package/src/hub/DebugPanel.ts +280 -0
- package/src/hub/HubBase.ts +147 -223
- package/src/hub/HubSimulate.ts +93 -24
- package/src/hub/HubTauri.ts +87 -96
- package/src/hub/HubWebSocket.ts +133 -158
- package/src/hub/debug.ts +211 -0
- package/src/hub/index.ts +49 -39
- package/tsconfig.json +43 -28
- package/docs/classes/components_BlocklyEditor.BlocklyEditor.html +0 -124
- package/docs/classes/components_CodeEditor.CodeEditor.html +0 -128
- package/docs/classes/components_JogPanel.JogPanel.html +0 -138
- package/docs/classes/components_Lamp.Lamp.html +0 -105
- package/docs/classes/components_TextInput.TextInput.html +0 -115
- package/docs/classes/components_ValueIndicator.ValueIndicator.html +0 -119
- package/docs/classes/components_ValueInput.ValueInput.html +0 -113
- package/docs/classes/hub_HubWebSocket.HubWebSocket.html +0 -106
- package/docs/enums/components_JogPanel.JogDistanceAction.html +0 -5
- package/docs/enums/components_JogPanel.JogPanelAction.html +0 -18
- package/docs/enums/components_JogPanel.JogSpeedAction.html +0 -5
- package/docs/enums/core_ActionMode.ActionMode.html +0 -6
- package/docs/enums/core_IndicatorColor.IndicatorColor.html +0 -23
- package/docs/functions/assets_BlocklyLogo.default.html +0 -1
- package/docs/functions/assets_Distance.default.html +0 -1
- package/docs/functions/assets_JogLong.default.html +0 -1
- package/docs/functions/assets_JogMedium.default.html +0 -1
- package/docs/functions/assets_JogShort.default.html +0 -1
- package/docs/functions/assets_PythonLogo.default.html +0 -1
- package/docs/functions/assets_Rotation3D.default.html +0 -1
- package/docs/functions/assets_RotationCcw.default.html +0 -1
- package/docs/functions/assets_RotationCcwA.default.html +0 -1
- package/docs/functions/assets_RotationCcwB.default.html +0 -1
- package/docs/functions/assets_RotationCcwC.default.html +0 -1
- package/docs/functions/assets_RotationCw.default.html +0 -1
- package/docs/functions/assets_RotationCwA.default.html +0 -1
- package/docs/functions/assets_RotationCwB.default.html +0 -1
- package/docs/functions/assets_RotationCwC.default.html +0 -1
- package/docs/functions/assets_Run.default.html +0 -1
- package/docs/functions/assets_Speed.default.html +0 -1
- package/docs/functions/assets_SpeedFast.default.html +0 -1
- package/docs/functions/assets_SpeedMedium.default.html +0 -1
- package/docs/functions/assets_SpeedNone.default.html +0 -1
- package/docs/functions/assets_SpeedSlow.default.html +0 -1
- package/docs/functions/assets_Walk.default.html +0 -1
- package/docs/functions/components_BlocklyEditor.createCustomToolbox.html +0 -6
- package/docs/functions/components_FileList.FileList.html +0 -21
- package/docs/functions/components_FitText.FitText.html +0 -8
- package/docs/functions/components_ToggleGroup.ToggleGroup.html +0 -5
- package/docs/interfaces/components_JogPanel.JogPanelButtonDefinition.html +0 -5
- package/docs/interfaces/components_ToggleGroup.ToggleGroupProps.html +0 -618
- package/docs/interfaces/core_IndicatorButtonState.IndicatorButtonState.html +0 -10
- package/docs/interfaces/hub_CommandMessage.CommandMessage.html +0 -6
- package/docs/interfaces/hub_CommandMessage.CommandMessageResult.html +0 -4
- package/docs/modules/assets.html +0 -23
- package/docs/modules/assets_BlocklyLogo.html +0 -2
- package/docs/modules/assets_Distance.html +0 -2
- package/docs/modules/assets_JogLong.html +0 -2
- package/docs/modules/assets_JogMedium.html +0 -2
- package/docs/modules/assets_JogShort.html +0 -2
- package/docs/modules/assets_PythonLogo.html +0 -2
- package/docs/modules/assets_Rotation3D.html +0 -2
- package/docs/modules/assets_RotationCcw.html +0 -2
- package/docs/modules/assets_RotationCcwA.html +0 -2
- package/docs/modules/assets_RotationCcwB.html +0 -2
- package/docs/modules/assets_RotationCcwC.html +0 -2
- package/docs/modules/assets_RotationCw.html +0 -2
- package/docs/modules/assets_RotationCwA.html +0 -2
- package/docs/modules/assets_RotationCwB.html +0 -2
- package/docs/modules/assets_RotationCwC.html +0 -2
- package/docs/modules/assets_Run.html +0 -2
- package/docs/modules/assets_Speed.html +0 -2
- package/docs/modules/assets_SpeedFast.html +0 -2
- package/docs/modules/assets_SpeedMedium.html +0 -2
- package/docs/modules/assets_SpeedNone.html +0 -2
- package/docs/modules/assets_SpeedSlow.html +0 -2
- package/docs/modules/assets_Walk.html +0 -2
- package/docs/modules/components_BlocklyEditor.html +0 -5
- package/docs/modules/components_CodeEditor.html +0 -3
- package/docs/modules/components_FileList.html +0 -3
- package/docs/modules/components_FitText.html +0 -3
- package/docs/modules/components_JogPanel.html +0 -9
- package/docs/modules/components_Lamp.html +0 -4
- package/docs/modules/components_TextInput.html +0 -2
- package/docs/modules/components_ToggleGroup.html +0 -6
- package/docs/modules/components_ValueIndicator.html +0 -4
- package/docs/modules/components_ValueInput.html +0 -2
- package/docs/modules/core_ActionMode.html +0 -2
- package/docs/modules/core_IndicatorButtonState.html +0 -2
- package/docs/modules/core_IndicatorColor.html +0 -2
- package/docs/modules/hub_CommandMessage.html +0 -3
- package/docs/modules/hub_HubWebSocket.html +0 -2
- package/docs/types/components_IndicatorButton.IndicatorButtonOptionsType.html +0 -1
- package/docs/variables/components_BlocklyEditor.StandardToolbox.html +0 -1
- package/docs/variables/components_JogPanel.DefaultLinearJogButtons.html +0 -2
- package/docs/variables/components_JogPanel.DefaultRotationJogButtons.html +0 -2
|
@@ -2,45 +2,84 @@
|
|
|
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:
|
|
5
|
+
* Last Modified: 2026-01-29 09:32:29
|
|
6
6
|
* Modified By: ADC
|
|
7
7
|
* -----
|
|
8
8
|
*
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @fileoverview EventEmitterContext - Global Event Bus for AutoCore React Applications
|
|
13
|
+
*
|
|
14
|
+
* The EventEmitterContext provides a comprehensive event-driven communication system for React applications,
|
|
15
|
+
* enabling seamless interaction between components and backend services. It serves as the foundational layer
|
|
16
|
+
* for AutoCore's real-time data flow and component communication architecture.
|
|
17
|
+
*
|
|
18
|
+
* ## Core Features
|
|
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
|
+
* - **Performance Optimized**: Uses stable context values and direct callbacks to avoid global re-renders.
|
|
24
|
+
* - **Connection Management**: Automatic reconnection and state synchronization.
|
|
25
|
+
*
|
|
26
|
+
* ## Architecture
|
|
27
|
+
*
|
|
28
|
+
* The system consists of three main components:
|
|
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
|
|
34
|
+
*
|
|
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.
|
|
40
|
+
*
|
|
41
|
+
* @module core/EventEmitterContext
|
|
42
|
+
* @version 3.0.42
|
|
43
|
+
* @author Automated Design Corp.
|
|
44
|
+
*/
|
|
45
|
+
|
|
11
46
|
import React, {
|
|
12
47
|
createContext,
|
|
13
|
-
ReactNode,
|
|
14
48
|
useState,
|
|
15
49
|
useMemo,
|
|
16
50
|
useCallback,
|
|
17
51
|
useRef,
|
|
18
52
|
useEffect,
|
|
19
53
|
} from "react";
|
|
54
|
+
|
|
55
|
+
import type {ReactNode} from "react";
|
|
20
56
|
import { createHub, Hub } from "../hub";
|
|
21
|
-
import {
|
|
57
|
+
import type { CommandMessage } from "../hub";
|
|
58
|
+
import { MessageType } from "../hub/CommandMessage";
|
|
22
59
|
|
|
23
60
|
export { Hub };
|
|
24
61
|
|
|
25
62
|
/**
|
|
26
|
-
* Represents an event
|
|
63
|
+
* Represents an active event subscription.
|
|
27
64
|
*/
|
|
28
65
|
export interface Subscription {
|
|
29
|
-
/** ID of the subscription used for unsubscription. */
|
|
66
|
+
/** Unique ID of the subscription used for unsubscription. */
|
|
30
67
|
id: number;
|
|
31
|
-
/** Callback function. */
|
|
68
|
+
/** Callback function to execute when the event fires. */
|
|
32
69
|
callback: React.Dispatch<any>;
|
|
33
70
|
}
|
|
34
71
|
|
|
35
72
|
/**
|
|
36
|
-
* Represents the
|
|
73
|
+
* Represents the internal state of the EventEmitter.
|
|
74
|
+
* Mostly used for debugging and dev tools inspection.
|
|
37
75
|
*/
|
|
38
76
|
export interface State {
|
|
39
77
|
/**
|
|
40
|
-
* 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.
|
|
41
80
|
*/
|
|
42
81
|
eventData?: any;
|
|
43
|
-
/**
|
|
82
|
+
/** Active subscriptions grouped by topic. */
|
|
44
83
|
subscriptions: Record<string, Subscription[]>;
|
|
45
84
|
|
|
46
85
|
/** Tracks the next subscription ID that will be assigned. */
|
|
@@ -48,261 +87,152 @@ export interface State {
|
|
|
48
87
|
}
|
|
49
88
|
|
|
50
89
|
/**
|
|
51
|
-
* An action event published by the EventEmitterContext.
|
|
52
|
-
* the topic identifying the event and the optional payload, which is
|
|
53
|
-
* a value of any type.
|
|
90
|
+
* An action event published by the EventEmitterContext.
|
|
54
91
|
*/
|
|
55
92
|
export interface Action {
|
|
56
|
-
/**
|
|
57
|
-
* The topic or identifier of the event.
|
|
58
|
-
*/
|
|
93
|
+
/** The topic or identifier of the event. */
|
|
59
94
|
topic: string;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The optional data payload associated with the event.
|
|
63
|
-
*/
|
|
95
|
+
/** The optional data payload associated with the event. */
|
|
64
96
|
payload?: any;
|
|
65
97
|
}
|
|
66
98
|
|
|
67
|
-
/**
|
|
68
|
-
* Type declaration for the EventEmitter dispatch function, which
|
|
69
|
-
* publishes an Action globally throughout the EventEmitterContext.
|
|
70
|
-
*/
|
|
71
99
|
export type EmitterDispatchFunction = (action: Action) => void;
|
|
72
100
|
|
|
73
101
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*/
|
|
77
|
-
export type EmitterSubscribeFunction = (action: Action) => void;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Type declaration for the EventEmitter unsubscribe function, which
|
|
81
|
-
* receives an Action on a specific topic broadcast through the EventEmitterContext.
|
|
82
|
-
*/
|
|
83
|
-
export type EmitterUnsubscribeFunction = (action: Action) => void;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Defines the context for an event emitter used throughout a front-end application to manage and dispatch events.
|
|
87
|
-
* This interface includes methods for managing the application's state, handling global actions, communicating with the back end,
|
|
88
|
-
* subscribing to and unsubscribing from events, and accessing a global hub for event publishing and subscription management.
|
|
89
|
-
* It serves as a central point for event-driven interactions within the application, facilitating communication between
|
|
90
|
-
* 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.
|
|
91
104
|
*/
|
|
92
105
|
export interface EventEmitterContextType {
|
|
93
106
|
/**
|
|
94
|
-
* The current state of the event emitter
|
|
107
|
+
* The current state of the event emitter (mostly for debugging).
|
|
95
108
|
*/
|
|
96
109
|
state: State;
|
|
97
110
|
|
|
98
111
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param action The action to dispatch
|
|
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.
|
|
103
116
|
*/
|
|
104
|
-
dispatch:
|
|
117
|
+
dispatch: EmitterDispatchFunction;
|
|
105
118
|
|
|
106
119
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
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
|
|
109
126
|
*/
|
|
110
127
|
invoke(
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
topic: string,
|
|
129
|
+
messageType: MessageType,
|
|
113
130
|
payload?: object
|
|
114
|
-
): Promise<
|
|
131
|
+
): Promise<CommandMessage>;
|
|
115
132
|
|
|
116
133
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
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>;
|
|
173
|
+
|
|
174
|
+
/**
|
|
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).
|
|
119
182
|
* @param callback The callback to signal.
|
|
120
183
|
* @returns number Subscription ID used to unsubscribe later.
|
|
121
184
|
*/
|
|
122
185
|
subscribe: (topic: string, callback: React.Dispatch<any>) => number;
|
|
123
186
|
|
|
124
187
|
/**
|
|
125
|
-
* Unsubscribe
|
|
126
|
-
*
|
|
127
|
-
* @
|
|
188
|
+
* Unsubscribe from **local** frontend events.
|
|
189
|
+
*
|
|
190
|
+
* @param subscriptionId The id returned by the subscribe method.
|
|
128
191
|
*/
|
|
129
192
|
unsubscribe: (subscriptionId: number) => void;
|
|
130
193
|
|
|
131
194
|
/**
|
|
132
|
-
* Global hub for
|
|
133
|
-
* data with the backend.
|
|
195
|
+
* Global hub instance for backend communication.
|
|
134
196
|
*/
|
|
135
197
|
hub: Hub | null;
|
|
136
198
|
|
|
137
199
|
/**
|
|
138
200
|
* Retrieves the current subscriptions. Used for debugging purposes.
|
|
139
|
-
* @param topic Optional. The topic to retrieve subscriptions for. If omitted, returns all subscriptions.
|
|
140
|
-
* @returns An object containing the current subscriptions, optionally filtered by topic.
|
|
141
201
|
*/
|
|
142
202
|
getSubscriptions: (
|
|
143
203
|
topic?: string
|
|
144
204
|
) => Record<string, Subscription[]> | Subscription[];
|
|
145
205
|
|
|
146
206
|
/**
|
|
147
|
-
* Returns true if the Hub
|
|
148
|
-
* @returns boolean
|
|
207
|
+
* Returns true if the Hub is connected to the backend.
|
|
149
208
|
*/
|
|
150
209
|
isConnected: () => boolean;
|
|
151
210
|
}
|
|
152
211
|
|
|
153
|
-
|
|
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
|
+
};
|
|
154
222
|
|
|
155
|
-
/**
|
|
156
|
-
* A global context for managing event emission and subscription.
|
|
157
|
-
*
|
|
158
|
-
* Creates a React context for the EventEmitter system, providing a structured way to manage events and data flow
|
|
159
|
-
* in a React application. It serves as a global event bus that components can subscribe to or emit events, allowing for
|
|
160
|
-
* a loosely coupled architecture. Additionally, it provides a mechanism for invoking backend functions and managing
|
|
161
|
-
* subscriptions, making it easier to integrate React components with backend services.
|
|
162
|
-
*
|
|
163
|
-
* The context includes several key functionalities:
|
|
164
|
-
* - `state`: Maintains the current state of subscriptions and their identifiers.
|
|
165
|
-
* - `dispatch`: Allows components to emit events with specific topics and payloads, which can be listened to by other components.
|
|
166
|
-
* - `subscribe`: Enables components to listen to specific topics and react to those events by providing a callback function.
|
|
167
|
-
* - `unsubscribe`: Provides a way for components to stop listening to events, helping prevent memory leaks and unnecessary updates.
|
|
168
|
-
* - `invoke`: Facilitates calling backend functions with specific arguments and handling their responses asynchronously.
|
|
169
|
-
* - `getSubscriptions`: Offers insight into current active subscriptions, useful for debugging purposes.
|
|
170
|
-
*
|
|
171
|
-
* This context is essential for applications that require a high degree of inter-component communication or need to
|
|
172
|
-
* interact with a backend efficiently.
|
|
173
|
-
*
|
|
174
|
-
* For more information, see [Additional Documentation](../additional-docs/GlobalEventEmitter.md).
|
|
175
|
-
*
|
|
176
|
-
* ## Usage
|
|
177
|
-
*
|
|
178
|
-
* The entire application should be wrapped in the EventEmitterProvider.
|
|
179
|
-
*
|
|
180
|
-
* App.tsx
|
|
181
|
-
* ```
|
|
182
|
-
* import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
|
|
183
|
-
* function App() {
|
|
184
|
-
*
|
|
185
|
-
* return(
|
|
186
|
-
* <EventEmitterProvider>
|
|
187
|
-
* <PrimeReactProvider>
|
|
188
|
-
* <main>
|
|
189
|
-
* <section>
|
|
190
|
-
* <ContentView />
|
|
191
|
-
* </section>
|
|
192
|
-
* </main>
|
|
193
|
-
* </PrimeReactProvider>
|
|
194
|
-
* </EventEmitterProvider>
|
|
195
|
-
* );
|
|
196
|
-
*
|
|
197
|
-
* }
|
|
198
|
-
*
|
|
199
|
-
* ```
|
|
200
|
-
*
|
|
201
|
-
* ### Catching and receiving events
|
|
202
|
-
* The EventEmitterContext creates an appropriate instance of the hub, which is derived from HubBase.
|
|
203
|
-
* That hub can be used to publish and subscribe to
|
|
204
|
-
* topics globally in the front-end, regardless of being connected to any backend.
|
|
205
|
-
* Usage within a component is simple.
|
|
206
|
-
*
|
|
207
|
-
* ```
|
|
208
|
-
* const {dispatch, subscribe, unsubscribe} = useContext(EventEmitterContext);
|
|
209
|
-
* const [controlPower, setControlPower] = useState(false);
|
|
210
|
-
* useEffect(() => {
|
|
211
|
-
* const unsubscribeControlPower = subscribe('value-simulator-bBit1', (value) => {
|
|
212
|
-
* setControlPower(value);
|
|
213
|
-
* });
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* return () => {
|
|
217
|
-
* unsubscribe(unsubscribeControlPower);
|
|
218
|
-
* }
|
|
219
|
-
* }, [] );
|
|
220
|
-
*
|
|
221
|
-
* const onPbPressed = () => {
|
|
222
|
-
* let count = 1;
|
|
223
|
-
* dispatch({
|
|
224
|
-
* topic: "my-awesome-topic",
|
|
225
|
-
* payload: count
|
|
226
|
-
* });
|
|
227
|
-
* }
|
|
228
|
-
*
|
|
229
|
-
* ```
|
|
230
|
-
* The hub should also be used for invoking events in the backend.
|
|
231
|
-
* This example will call the function "update_count" in the backend, passing
|
|
232
|
-
* the expected argument "count". Details of the interaction between the Hub and
|
|
233
|
-
* the backend will be handled by the appropriate HubBase sub-class, and should
|
|
234
|
-
* be transparent to the front end.
|
|
235
|
-
*
|
|
236
|
-
* ```
|
|
237
|
-
* const {invoke} = useContext(EventEmitterContext);
|
|
238
|
-
* const incrementCount = () => {
|
|
239
|
-
* count += 1;
|
|
240
|
-
* invoke('update_count', {"count": count});
|
|
241
|
-
* };
|
|
242
|
-
*
|
|
243
|
-
* Subscribing to a topic is simple. The type of value received is specific
|
|
244
|
-
* to the topic.
|
|
245
|
-
*
|
|
246
|
-
* Example: Listen to an event 'xarm-position':
|
|
247
|
-
* ```
|
|
248
|
-
* const {subscribe, unsubscribe} = useContext(EventEmitterContext);
|
|
249
|
-
* useEffect(() => {
|
|
250
|
-
* const unsubscripeMp = subscribe('xarm-position', (value) => {
|
|
251
|
-
* // The publisher sent a JSON object of 3D position values.
|
|
252
|
-
* setX(value.x);
|
|
253
|
-
* setY(value.y);
|
|
254
|
-
* setZ(value.z);
|
|
255
|
-
* setA(value.roll);
|
|
256
|
-
* setB(value.yaw);
|
|
257
|
-
* setC(value.pitch);
|
|
258
|
-
* });
|
|
259
|
-
*
|
|
260
|
-
* return () => {
|
|
261
|
-
* unsubscribe(unsubscripeMp);
|
|
262
|
-
* }
|
|
263
|
-
*
|
|
264
|
-
* }, [] );
|
|
265
|
-
*
|
|
266
|
-
* ```
|
|
267
|
-
*
|
|
268
|
-
* For applications that need to access the instance of the hub, get the current instance
|
|
269
|
-
* from the EventEmitterContext:
|
|
270
|
-
*
|
|
271
|
-
* ```
|
|
272
|
-
* const {hub} = useContext(EventEmitterContext);
|
|
273
|
-
* * ```
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*/
|
|
277
223
|
export const EventEmitterContext = createContext<EventEmitterContextType>({
|
|
278
224
|
state: { subscriptions: {}, nextSubscriptionId: 1 },
|
|
279
225
|
dispatch: () => {},
|
|
280
|
-
subscribe: () =>
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
let ret: CommandMessageResult = {
|
|
288
|
-
data: {},
|
|
289
|
-
success: false,
|
|
290
|
-
error_message: "",
|
|
291
|
-
};
|
|
292
|
-
// Placeholder for invoke logic
|
|
293
|
-
// Implement the logic to send a message to the backend and return a promise
|
|
294
|
-
return Promise.resolve(ret); // Example placeholder, replace with actual implementation
|
|
295
|
-
},
|
|
296
|
-
unsubscribe: (subscriptionId: number) => {
|
|
297
|
-
subscriptionId;
|
|
298
|
-
}, // 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,
|
|
299
233
|
hub: null,
|
|
300
|
-
getSubscriptions: () =>
|
|
301
|
-
|
|
302
|
-
},
|
|
303
|
-
isConnected: () => {
|
|
304
|
-
return false;
|
|
305
|
-
},
|
|
234
|
+
getSubscriptions: () => [],
|
|
235
|
+
isConnected: () => false,
|
|
306
236
|
});
|
|
307
237
|
|
|
308
238
|
/**
|
|
@@ -312,56 +242,53 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
|
|
|
312
242
|
* with the event emitter.
|
|
313
243
|
*
|
|
314
244
|
* @param children The child components to be wrapped in the context.
|
|
315
|
-
*
|
|
316
|
-
* ## Usage
|
|
317
|
-
*
|
|
318
|
-
* The entire application should be wrapped in the EventEmitterProvider.
|
|
319
|
-
*
|
|
320
|
-
* App.tsx
|
|
321
|
-
* ```
|
|
322
|
-
* import { EventEmitterProvider } from "@adcops/autocore-react/core/EventEmitterContext.js";
|
|
323
|
-
* function App() {
|
|
324
|
-
*
|
|
325
|
-
* return(
|
|
326
|
-
* <EventEmitterProvider>
|
|
327
|
-
* <PrimeReactProvider>
|
|
328
|
-
* <main>
|
|
329
|
-
* <section>
|
|
330
|
-
* <ContentView />
|
|
331
|
-
* </section>
|
|
332
|
-
* </main>
|
|
333
|
-
* </PrimeReactProvider>
|
|
334
|
-
* </EventEmitterProvider>
|
|
335
|
-
* );
|
|
336
|
-
*
|
|
337
|
-
* }
|
|
338
|
-
*
|
|
339
|
-
* ```
|
|
340
245
|
*/
|
|
341
246
|
export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
|
|
342
247
|
children,
|
|
343
248
|
}) => {
|
|
249
|
+
console.log("[EventEmitterProvider] Rendering...");
|
|
250
|
+
|
|
344
251
|
const [state, setState] = useState<State>({
|
|
345
252
|
subscriptions: {},
|
|
346
253
|
nextSubscriptionId: 1,
|
|
347
254
|
});
|
|
348
255
|
|
|
349
|
-
// Memoize the hub instance so it's only created once
|
|
350
|
-
|
|
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
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
// Use ref for subscription ID management (avoids global state issues)
|
|
264
|
+
const nextIdRef = useRef(1);
|
|
351
265
|
|
|
352
|
-
//
|
|
266
|
+
// Subscription storage - this is the single source of truth for local listeners
|
|
353
267
|
const subsRef = useRef<Record<string, Subscription[]>>({});
|
|
354
268
|
|
|
355
|
-
//
|
|
269
|
+
// Sync state with ref for debugging/inspection purposes only (on mount)
|
|
356
270
|
useEffect(() => {
|
|
357
|
-
|
|
358
|
-
|
|
271
|
+
setState(prev => ({
|
|
272
|
+
...prev,
|
|
273
|
+
subscriptions: { ...subsRef.current },
|
|
274
|
+
nextSubscriptionId: nextIdRef.current
|
|
275
|
+
}));
|
|
276
|
+
}, []);
|
|
359
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
|
+
*/
|
|
360
285
|
const dispatch = useCallback((action: Action) => {
|
|
361
286
|
const { topic, payload } = action;
|
|
362
287
|
|
|
363
|
-
// Read
|
|
288
|
+
// Read from ref (single source of truth) and create a defensive copy
|
|
364
289
|
const listeners = subsRef.current[topic]?.slice() ?? [];
|
|
290
|
+
|
|
291
|
+
// Execute callbacks synchronously to avoid timing issues
|
|
365
292
|
for (const sub of listeners) {
|
|
366
293
|
try {
|
|
367
294
|
sub.callback(payload);
|
|
@@ -370,24 +297,32 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
|
|
|
370
297
|
}
|
|
371
298
|
}
|
|
372
299
|
|
|
373
|
-
//
|
|
374
|
-
|
|
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 }));
|
|
375
303
|
}, []);
|
|
376
304
|
|
|
377
305
|
const subscribe = useCallback(
|
|
378
306
|
(topic: string, callback: React.Dispatch<any>): number => {
|
|
379
|
-
const id =
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
307
|
+
const id = nextIdRef.current++;
|
|
308
|
+
|
|
309
|
+
// Create new subscription entry
|
|
310
|
+
const newSub: Subscription = { id, callback };
|
|
311
|
+
|
|
312
|
+
// Atomically update the ref with a complete new structure
|
|
313
|
+
const currentSubs = subsRef.current[topic] ?? [];
|
|
314
|
+
const newSubs = [...currentSubs, newSub];
|
|
315
|
+
|
|
316
|
+
subsRef.current = {
|
|
317
|
+
...subsRef.current,
|
|
318
|
+
[topic]: newSubs
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Update state for debugging/inspection (less frequent than dispatch, so acceptable)
|
|
322
|
+
setState(prevState => ({
|
|
388
323
|
...prevState,
|
|
389
|
-
subscriptions: { ...
|
|
390
|
-
nextSubscriptionId:
|
|
324
|
+
subscriptions: { ...subsRef.current },
|
|
325
|
+
nextSubscriptionId: nextIdRef.current,
|
|
391
326
|
}));
|
|
392
327
|
|
|
393
328
|
return id;
|
|
@@ -396,57 +331,98 @@ export const EventEmitterProvider: React.FC<{ children: ReactNode }> = ({
|
|
|
396
331
|
);
|
|
397
332
|
|
|
398
333
|
const unsubscribe = useCallback((subscriptionId: number) => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
334
|
+
// Create a completely new structure to avoid mutation issues
|
|
335
|
+
const newSubscriptions: Record<string, Subscription[]> = {};
|
|
336
|
+
|
|
337
|
+
for (const [topic, subs] of Object.entries(subsRef.current)) {
|
|
338
|
+
const filteredSubs = subs.filter(s => s.id !== subscriptionId);
|
|
339
|
+
if (filteredSubs.length > 0) {
|
|
340
|
+
newSubscriptions[topic] = filteredSubs;
|
|
341
|
+
}
|
|
404
342
|
}
|
|
343
|
+
|
|
344
|
+
// Atomically replace the entire structure
|
|
345
|
+
subsRef.current = newSubscriptions;
|
|
346
|
+
|
|
347
|
+
// Update state for debugging/inspection
|
|
348
|
+
setState(prev => ({
|
|
349
|
+
...prev,
|
|
350
|
+
subscriptions: { ...newSubscriptions }
|
|
351
|
+
}));
|
|
352
|
+
}, []);
|
|
405
353
|
|
|
406
|
-
|
|
354
|
+
// Clean up on unmount
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
return () => {
|
|
357
|
+
subsRef.current = {};
|
|
358
|
+
nextIdRef.current = 1;
|
|
359
|
+
};
|
|
407
360
|
}, []);
|
|
408
361
|
|
|
409
|
-
//
|
|
362
|
+
// Handle HMR so listeners don't leak across hot updates
|
|
410
363
|
if (import.meta && (import.meta as any).hot) {
|
|
411
364
|
(import.meta as any).hot.dispose(() => {
|
|
412
365
|
subsRef.current = {};
|
|
366
|
+
nextIdRef.current = 1;
|
|
413
367
|
});
|
|
414
368
|
}
|
|
415
369
|
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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.
|
|
430
398
|
const contextValue = useMemo(
|
|
431
399
|
() => ({
|
|
432
400
|
state,
|
|
433
401
|
dispatch,
|
|
434
402
|
subscribe,
|
|
435
403
|
unsubscribe,
|
|
436
|
-
invoke
|
|
404
|
+
invoke,
|
|
405
|
+
read,
|
|
406
|
+
write,
|
|
407
|
+
serverSubscribe,
|
|
408
|
+
serverUnsubscribe,
|
|
437
409
|
hub,
|
|
438
410
|
getSubscriptions: (topic?: string) =>
|
|
439
411
|
topic ? subsRef.current[topic] ?? [] : subsRef.current,
|
|
440
|
-
isConnected
|
|
412
|
+
isConnected,
|
|
441
413
|
}),
|
|
442
|
-
[state, hub, dispatch, subscribe, unsubscribe]
|
|
414
|
+
[state, hub, dispatch, subscribe, unsubscribe, invoke, read, write, serverSubscribe, serverUnsubscribe, isConnected]
|
|
443
415
|
);
|
|
444
416
|
|
|
445
|
-
hub.
|
|
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]);
|
|
446
422
|
|
|
447
423
|
return (
|
|
448
424
|
<EventEmitterContext.Provider value={contextValue}>
|
|
449
425
|
{children}
|
|
450
426
|
</EventEmitterContext.Provider>
|
|
451
427
|
);
|
|
452
|
-
};
|
|
428
|
+
};
|