@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.
- package/additional-docs/react_performance_notes.md +94 -0
- package/dist/components/AutoCoreDevPanel.d.ts.map +1 -1
- package/dist/components/AutoCoreDevPanel.js +1 -1
- package/dist/components/FileList.d.ts.map +1 -1
- package/dist/components/FileList.js +1 -1
- package/dist/components/FileSelect.d.ts.map +1 -1
- package/dist/components/FileSelect.js +1 -1
- package/dist/core/AutoCoreTagContext.d.ts +59 -185
- package/dist/core/AutoCoreTagContext.d.ts.map +1 -1
- package/dist/core/AutoCoreTagContext.js +1 -1
- package/dist/core/AutoCoreTagTypes.d.ts +127 -6
- package/dist/core/AutoCoreTagTypes.d.ts.map +1 -1
- 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 +91 -473
- package/dist/core/EventEmitterContext.d.ts.map +1 -1
- package/dist/core/EventEmitterContext.js +1 -1
- package/dist/hooks/adsHooks.d.ts.map +1 -1
- package/dist/hooks/adsHooks.js +1 -1
- package/dist/hooks/commandHooks.d.ts +3 -3
- package/dist/hooks/commandHooks.d.ts.map +1 -1
- package/dist/hooks/commandHooks.js +1 -1
- package/dist/hooks/useAutoCoreTag.js +1 -1
- package/dist/hub/CommandMessage.d.ts +18 -9
- package/dist/hub/CommandMessage.d.ts.map +1 -1
- 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 +83 -129
- package/dist/hub/HubBase.d.ts.map +1 -1
- package/dist/hub/HubBase.js +1 -1
- package/dist/hub/HubSimulate.d.ts +41 -8
- package/dist/hub/HubSimulate.d.ts.map +1 -1
- package/dist/hub/HubSimulate.js +1 -1
- package/dist/hub/HubTauri.d.ts +24 -60
- package/dist/hub/HubTauri.d.ts.map +1 -1
- package/dist/hub/HubTauri.js +1 -1
- package/dist/hub/HubWebSocket.d.ts +33 -17
- package/dist/hub/HubWebSocket.d.ts.map +1 -1
- 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 +19 -4
- package/dist/hub/index.d.ts.map +1 -1
- package/dist/hub/index.js +1 -1
- package/package.json +4 -4
- package/src/components/AutoCoreDevPanel.tsx +14 -11
- package/src/components/FileList.tsx +5 -4
- package/src/components/FileSelect.tsx +2 -1
- package/src/core/ActionMode.ts +1 -1
- package/src/core/AutoCoreTagContext.tsx +247 -330
- package/src/core/AutoCoreTagTypes.ts +236 -104
- package/src/core/CoreStreamTypes.ts +512 -0
- package/src/core/EventEmitterContext.tsx +182 -520
- package/src/core/IndicatorButtonState.ts +1 -1
- 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 +2 -2
- 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 +118 -140
- package/src/hub/debug.ts +211 -0
- package/src/hub/index.ts +49 -39
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -134
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/components_CodeEditor.CodeEditor.html +0 -135
- package/docs/classes/components_Indicator.Indicator.html +0 -122
- package/docs/classes/components_IndicatorRect.IndicatorRect.html +0 -121
- package/docs/classes/components_JogPanel.JogPanel.html +0 -136
- package/docs/classes/components_Lamp.Lamp.html +0 -122
- package/docs/classes/components_OskDialog.OskDialog.html +0 -125
- package/docs/classes/components_TextInput.TextInput.html +0 -125
- package/docs/classes/components_ValueDisplay.ValueDisplay.html +0 -148
- package/docs/classes/components_ValueIndicator.ValueIndicator.html +0 -126
- package/docs/classes/core_ValueSimulator.ValueSimulator.html +0 -51
- package/docs/classes/hub_HubBase.HubBase.html +0 -106
- package/docs/classes/hub_HubSimulate.HubSimulate.html +0 -75
- package/docs/classes/hub_HubTauri.HubTauri.html +0 -93
- package/docs/classes/hub_HubWebSocket.HubWebSocket.html +0 -112
- package/docs/documents/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -148
- 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.html +0 -1
- package/docs/functions/assets.Distance.html +0 -1
- package/docs/functions/assets.JogLong.html +0 -1
- package/docs/functions/assets.JogMedium.html +0 -1
- package/docs/functions/assets.JogShort.html +0 -1
- package/docs/functions/assets.PythonLogo.html +0 -1
- package/docs/functions/assets.Rotation3D.html +0 -1
- package/docs/functions/assets.RotationCcw.html +0 -1
- package/docs/functions/assets.RotationCcwA.html +0 -1
- package/docs/functions/assets.RotationCcwB.html +0 -1
- package/docs/functions/assets.RotationCcwC.html +0 -1
- package/docs/functions/assets.RotationCw.html +0 -1
- package/docs/functions/assets.RotationCwA.html +0 -1
- package/docs/functions/assets.RotationCwB.html +0 -1
- package/docs/functions/assets.RotationCwC.html +0 -1
- package/docs/functions/assets.Run.html +0 -1
- package/docs/functions/assets.Speed.html +0 -1
- package/docs/functions/assets.SpeedFast.html +0 -1
- package/docs/functions/assets.SpeedMedium.html +0 -1
- package/docs/functions/assets.SpeedNone.html +0 -1
- package/docs/functions/assets.SpeedSlow.html +0 -1
- package/docs/functions/assets.Walk.html +0 -1
- package/docs/functions/components_BlocklyEditor.createCustomToolbox.html +0 -6
- package/docs/functions/core_UniqueId.UniqueId.html +0 -9
- package/docs/functions/core_hoc.hocAddSubscription.html +0 -6
- package/docs/functions/hooks_adsHooks.useAdsRegisterSymbols.html +0 -16
- package/docs/functions/hooks_adsHooks.useAdsTapValue.html +0 -8
- package/docs/functions/hooks_adsHooks.useAdsWriteScaledValue.html +0 -18
- package/docs/functions/hooks_adsHooks.useAdsWriteValue.html +0 -9
- package/docs/functions/hooks_commandHooks.useRegisterSymbols.html +0 -16
- package/docs/functions/hooks_commandHooks.useTapValue.html +0 -10
- package/docs/functions/hooks_commandHooks.useWriteScaledValue.html +0 -18
- package/docs/functions/hooks_commandHooks.useWriteValue.html +0 -11
- package/docs/functions/hooks_useAutoCoreTag.ts.makeAutoCoreTagHooks.html +0 -12
- package/docs/functions/hooks_useScaledValue.useScaledValue.html +0 -18
- package/docs/functions/hub.createHub.html +0 -3
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -148
- package/docs/interfaces/components_IndicatorButton.IndicatorButtonProps.html +0 -654
- package/docs/interfaces/components_IndicatorRect.IndicatorRectProps.html +0 -37
- package/docs/interfaces/components_JogPanel.JogPanelButtonDefinition.html +0 -5
- package/docs/interfaces/components_ToggleGroup.ToggleGroupProps.html +0 -644
- package/docs/interfaces/core_AutoCoreTagTypes.BaseContextValue.html +0 -12
- package/docs/interfaces/core_AutoCoreTagTypes.ScaleConfig.html +0 -13
- package/docs/interfaces/core_EventEmitterContext.Action.html +0 -8
- package/docs/interfaces/core_EventEmitterContext.EventEmitterContextType.html +0 -33
- package/docs/interfaces/core_EventEmitterContext.State.html +0 -8
- package/docs/interfaces/core_EventEmitterContext.Subscription.html +0 -6
- package/docs/interfaces/core_IndicatorButtonState.IndicatorButtonState.html +0 -10
- package/docs/interfaces/core_PositionContext.IPositionContext.html +0 -17
- package/docs/interfaces/hub_CommandMessage.CommandMessage.html +0 -6
- package/docs/interfaces/hub_CommandMessage.CommandMessageResult.html +0 -4
- package/docs/modules/assets.html +0 -1
- package/docs/modules/assets_BlocklyLogo.html +0 -1
- package/docs/modules/assets_Distance.html +0 -1
- package/docs/modules/assets_JogLong.html +0 -1
- package/docs/modules/assets_JogMedium.html +0 -1
- package/docs/modules/assets_JogShort.html +0 -1
- package/docs/modules/assets_PythonLogo.html +0 -1
- package/docs/modules/assets_Rotation3D.html +0 -1
- package/docs/modules/assets_RotationCcw.html +0 -1
- package/docs/modules/assets_RotationCcwA.html +0 -1
- package/docs/modules/assets_RotationCcwB.html +0 -1
- package/docs/modules/assets_RotationCcwC.html +0 -1
- package/docs/modules/assets_RotationCw.html +0 -1
- package/docs/modules/assets_RotationCwA.html +0 -1
- package/docs/modules/assets_RotationCwB.html +0 -1
- package/docs/modules/assets_RotationCwC.html +0 -1
- package/docs/modules/assets_Run.html +0 -1
- package/docs/modules/assets_Speed.html +0 -1
- package/docs/modules/assets_SpeedFast.html +0 -1
- package/docs/modules/assets_SpeedMedium.html +0 -1
- package/docs/modules/assets_SpeedNone.html +0 -1
- package/docs/modules/assets_SpeedSlow.html +0 -1
- package/docs/modules/assets_Walk.html +0 -1
- package/docs/modules/components_AutoCoreDevPanel.html +0 -20
- package/docs/modules/components_BlocklyEditor.html +0 -1
- package/docs/modules/components_CodeEditor.html +0 -1
- package/docs/modules/components_FileList.html +0 -1
- package/docs/modules/components_FileSelect.html +0 -1
- package/docs/modules/components_FitText.html +0 -1
- package/docs/modules/components_Indicator.html +0 -1
- package/docs/modules/components_IndicatorButton.html +0 -1
- package/docs/modules/components_IndicatorRect.html +0 -1
- package/docs/modules/components_JogPanel.html +0 -1
- package/docs/modules/components_Lamp.html +0 -1
- package/docs/modules/components_Osk.html +0 -1
- package/docs/modules/components_OskDialog.html +0 -1
- package/docs/modules/components_ProgressBarWithValue.html +0 -1
- package/docs/modules/components_TextInput.html +0 -1
- package/docs/modules/components_ToggleGroup.html +0 -1
- package/docs/modules/components_ValueDisplay.html +0 -1
- package/docs/modules/components_ValueIndicator.html +0 -1
- package/docs/modules/components_ValueInput.html +0 -1
- package/docs/modules/core_ActionMode.html +0 -1
- package/docs/modules/core_AutoCoreTagContext.html +0 -11
- package/docs/modules/core_AutoCoreTagTypes.html +0 -1
- package/docs/modules/core_EventEmitterContext.html +0 -53
- package/docs/modules/core_IndicatorButtonState.html +0 -1
- package/docs/modules/core_IndicatorColor.html +0 -1
- package/docs/modules/core_MaskPatterns.html +0 -1
- package/docs/modules/core_NumerableTypes.html +0 -1
- package/docs/modules/core_PositionContext.html +0 -1
- package/docs/modules/core_UniqueId.html +0 -1
- package/docs/modules/core_ValueSimulator.html +0 -1
- package/docs/modules/core_hoc.html +0 -1
- package/docs/modules/hooks.html +0 -1
- package/docs/modules/hooks_adsHooks.html +0 -1
- package/docs/modules/hooks_commandHooks.html +0 -1
- package/docs/modules/hooks_useAutoCoreTag.ts.html +0 -52
- package/docs/modules/hooks_useScaledValue.html +0 -1
- package/docs/modules/hub.html +0 -1
- package/docs/modules/hub_CommandMessage.html +0 -1
- package/docs/modules/hub_HubBase.html +0 -1
- package/docs/modules/hub_HubSimulate.html +0 -1
- package/docs/modules/hub_HubTauri.html +0 -1
- package/docs/modules/hub_HubWebSocket.html +0 -1
- package/docs/modules.html +0 -23
- package/docs/types/components_IndicatorButton.IndicatorButtonOptionsType.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.ExtractByTag.html +0 -2
- package/docs/types/core_AutoCoreTagTypes.PrimitiveKind.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.TagConfig.html +0 -16
- package/docs/types/core_AutoCoreTagTypes.TagValueMap.html +0 -1
- package/docs/types/core_AutoCoreTagTypes.TagValueOf.html +0 -1
- package/docs/types/core_EventEmitterContext.EmitterDispatchFunction.html +0 -3
- package/docs/types/core_EventEmitterContext.EmitterSubscribeFunction.html +0 -3
- package/docs/types/core_EventEmitterContext.EmitterUnsubscribeFunction.html +0 -3
- package/docs/types/core_NumerableTypes.NumerableFormatOptions.html +0 -4
- package/docs/types/core_hoc.HocAddSubscriptionProps.html +0 -6
- package/docs/variables/components_AutoCoreDevPanel.AutoCoreDevPanel.html +0 -43
- package/docs/variables/components_BlocklyEditor.BlocklyEditor.html +0 -13
- package/docs/variables/components_BlocklyEditor.StandardToolbox.html +0 -1
- package/docs/variables/components_FileList.FileList.html +0 -23
- package/docs/variables/components_FileSelect.FileSelect.html +0 -1
- package/docs/variables/components_FitText.FitText.html +0 -4
- package/docs/variables/components_IndicatorButton.IndicatorButton.html +0 -1
- package/docs/variables/components_JogPanel.DefaultLinearJogButtons.html +0 -2
- package/docs/variables/components_JogPanel.DefaultRotationJogButtons.html +0 -2
- package/docs/variables/components_Osk.Osk.html +0 -1
- package/docs/variables/components_ProgressBarWithValue.ProgressBarWithValue.html +0 -1
- package/docs/variables/components_ToggleGroup.ToggleGroup.html +0 -1
- package/docs/variables/components_ValueInput.ValueInput.html +0 -4
- package/docs/variables/core_AutoCoreTagContext.AutoCoreTagContext.html +0 -1
- package/docs/variables/core_AutoCoreTagContext.AutoCoreTagProvider.html +0 -7
- package/docs/variables/core_EventEmitterContext.EventEmitterContext.html +0 -64
- package/docs/variables/core_EventEmitterContext.EventEmitterProvider.html +0 -10
- package/docs/variables/core_MaskPatterns.PrimeReactMaskPatterns.html +0 -14
- package/docs/variables/core_MaskPatterns.RegExMaskPatterns.html +0 -15
- package/docs/variables/core_PositionContext.DimensionsContext.html +0 -6
- package/docs/variables/hooks_useScaledValue.kMillimeters2Inches.html +0 -2
- 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:
|
|
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
|
-
* - **
|
|
24
|
-
* - **
|
|
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
|
-
* ##
|
|
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
|
-
*
|
|
294
|
-
*
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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.
|
|
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
|
-
*
|
|
368
|
-
*
|
|
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
|
|
107
|
+
* The current state of the event emitter (mostly for debugging).
|
|
388
108
|
*/
|
|
389
109
|
state: State;
|
|
390
110
|
|
|
391
111
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* @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.
|
|
396
116
|
*/
|
|
397
|
-
dispatch:
|
|
117
|
+
dispatch: EmitterDispatchFunction;
|
|
398
118
|
|
|
399
119
|
/**
|
|
400
|
-
*
|
|
401
|
-
*
|
|
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
|
-
|
|
405
|
-
|
|
128
|
+
topic: string,
|
|
129
|
+
messageType: MessageType,
|
|
406
130
|
payload?: object
|
|
407
|
-
): Promise<
|
|
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
|
|
411
|
-
*
|
|
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
|
|
419
|
-
*
|
|
420
|
-
* @
|
|
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
|
|
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
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
674
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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.
|
|
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}>
|