rotor-framework 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +120 -0
  3. package/package.json +59 -0
  4. package/src/source/RotorFramework.bs +654 -0
  5. package/src/source/RotorFrameworkTask.bs +278 -0
  6. package/src/source/base/BaseModel.bs +52 -0
  7. package/src/source/base/BasePlugin.bs +48 -0
  8. package/src/source/base/BaseReducer.bs +184 -0
  9. package/src/source/base/BaseStack.bs +92 -0
  10. package/src/source/base/BaseViewModel.bs +124 -0
  11. package/src/source/base/BaseWidget.bs +104 -0
  12. package/src/source/base/DispatcherCreator.bs +193 -0
  13. package/src/source/base/DispatcherExternal.bs +260 -0
  14. package/src/source/base/ListenerForDispatchers.bs +246 -0
  15. package/src/source/engine/Constants.bs +74 -0
  16. package/src/source/engine/animator/Animator.bs +334 -0
  17. package/src/source/engine/builder/Builder.bs +213 -0
  18. package/src/source/engine/builder/NodePool.bs +236 -0
  19. package/src/source/engine/builder/PluginAdapter.bs +139 -0
  20. package/src/source/engine/builder/PostProcessor.bs +331 -0
  21. package/src/source/engine/builder/Processor.bs +156 -0
  22. package/src/source/engine/builder/Tree.bs +278 -0
  23. package/src/source/engine/builder/TreeBase.bs +313 -0
  24. package/src/source/engine/builder/WidgetCreate.bs +322 -0
  25. package/src/source/engine/builder/WidgetRemove.bs +72 -0
  26. package/src/source/engine/builder/WidgetUpdate.bs +113 -0
  27. package/src/source/engine/providers/Dispatcher.bs +72 -0
  28. package/src/source/engine/providers/DispatcherProvider.bs +95 -0
  29. package/src/source/engine/services/I18n.bs +169 -0
  30. package/src/source/libs/animate/Animate.bs +753 -0
  31. package/src/source/libs/animate/LICENSE.txt +21 -0
  32. package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
  33. package/src/source/plugins/FieldsPlugin.bs +180 -0
  34. package/src/source/plugins/FocusPlugin.bs +1522 -0
  35. package/src/source/plugins/FontStylePlugin.bs +159 -0
  36. package/src/source/plugins/ObserverPlugin.bs +548 -0
  37. package/src/source/utils/ArrayUtils.bs +495 -0
  38. package/src/source/utils/GeneralUtils.bs +181 -0
  39. package/src/source/utils/NodeUtils.bs +180 -0
@@ -0,0 +1,124 @@
1
+ namespace Rotor
2
+
3
+ ' =====================================================================
4
+ ' ViewModel (BaseViewModel) - Base class for ViewModels that extends Widget to provide view lifecycle management
5
+ '
6
+ ' ViewModels act as controllers for views, managing:
7
+ ' - props: Immutable properties passed from parent (read-only)
8
+ ' - viewModelState: Mutable local state (read-write)
9
+ ' - template: Declarative UI structure
10
+ ' - Lifecycle hooks: onCreateView, onMountView, onUpdateView, onDestroyView
11
+ '
12
+ ' Key Features:
13
+ ' - Scoped state: props and viewModelState shared across all child widgets
14
+ ' - Template system: template() returns widget tree configuration
15
+ ' - Lifecycle management: Hooks for view creation, mounting, updates, destruction
16
+ ' - No prop drilling: Children access parent ViewModel state directly
17
+ ' =====================================================================
18
+ class ViewModel extends Rotor.Widget
19
+
20
+ ' =============================================================
21
+ ' MEMBER VARIABLES
22
+ ' =============================================================
23
+
24
+ isViewModel = true ' Identifies this as a ViewModel
25
+
26
+ viewModelState = {} ' Mutable state shared across ViewModel's widgets
27
+ props = {} ' Immutable props shared across ViewModel's widgets
28
+
29
+ ' =============================================================
30
+ ' PROPS MANAGEMENT
31
+ ' =============================================================
32
+
33
+ ' ---------------------------------------------------------------------
34
+ ' setProps - Updates props and triggers view update
35
+ '
36
+ ' Merges new props into existing props and calls onUpdateView lifecycle hook.
37
+ '
38
+ ' @param {object} newProps - New properties to merge
39
+ '
40
+ sub setProps(newProps as object)
41
+ m.props.append(newProps)
42
+ m.onUpdateView()
43
+ end sub
44
+
45
+ ' =============================================================
46
+ ' LIFECYCLE HOOKS
47
+ ' =============================================================
48
+
49
+ ' ---------------------------------------------------------------------
50
+ ' onCreateView - Called when view is being created
51
+ '
52
+ ' Override to perform initialization tasks before template is generated.
53
+ ' Use this to set up initial viewModelState or props.
54
+ '
55
+ sub onCreateView()
56
+ end sub
57
+
58
+ ' ---------------------------------------------------------------------
59
+ ' onTemplateCreated - Called after template has been created
60
+ '
61
+ ' Override to access and modify the created template structure.
62
+ ' Useful for dynamic template manipulation.
63
+ '
64
+ ' @param {object} template - The created template object
65
+ '
66
+ sub onTemplateCreated(template as object)
67
+ end sub
68
+
69
+ ' ---------------------------------------------------------------------
70
+ ' onMountView - Called when view is mounted to SceneGraph
71
+ '
72
+ ' Override to perform post-mount operations like starting animations,
73
+ ' fetching data, or setting up observers.
74
+ '
75
+ sub onMountView()
76
+ end sub
77
+
78
+ ' ---------------------------------------------------------------------
79
+ ' onUpdateView - Called when view needs to update
80
+ '
81
+ ' Override to handle state or props changes.
82
+ ' Called automatically when setProps() is invoked.
83
+ '
84
+ sub onUpdateView()
85
+ end sub
86
+
87
+ ' ---------------------------------------------------------------------
88
+ ' onDestroyView - Called when view is being destroyed
89
+ '
90
+ ' Override to perform cleanup tasks like removing observers,
91
+ ' stopping timers, or clearing resources.
92
+ '
93
+ sub onDestroyView()
94
+ end sub
95
+
96
+ ' =============================================================
97
+ ' TEMPLATE
98
+ ' =============================================================
99
+
100
+ ' ---------------------------------------------------------------------
101
+ ' template - Returns view template structure
102
+ '
103
+ ' Override to define custom view structure using declarative widget config.
104
+ ' Return an object with widget configuration including children.
105
+ '
106
+ ' @returns {object} Widget tree configuration
107
+ '
108
+ ' Example:
109
+ ' function template() as object
110
+ ' return {
111
+ ' nodeType: "Group",
112
+ ' children: [
113
+ ' { id: "label", nodeType: "Label", fields: { text: m.props.title } }
114
+ ' ]
115
+ ' }
116
+ ' end function
117
+ '
118
+ function template() as object
119
+ return {}
120
+ end function
121
+
122
+ end class
123
+
124
+ end namespace
@@ -0,0 +1,104 @@
1
+ namespace Rotor
2
+
3
+ ' =====================================================================
4
+ ' Widget (BaseWidget) - Base class for UI components in the Rotor framework
5
+ '
6
+ ' Widgets are the fundamental building blocks of the UI tree, providing:
7
+ ' - Lifecycle management (mount, update, destroy)
8
+ ' - Tree navigation methods
9
+ ' - State access (viewModelState, props)
10
+ ' - Rendering and updates
11
+ ' - Plugin integration
12
+ '
13
+ ' IMPORTANT: Many properties and methods are injected/decorated by the
14
+ ' framework builder and plugins during widget instantiation.
15
+ '
16
+ ' Widget Creation Flow:
17
+ ' 1. builder.WidgetTreeBase.add() - Registers widget in tree
18
+ ' 2. builder.processor.createWidget() - Decorates with methods
19
+ ' =====================================================================
20
+ class Widget
21
+
22
+ ' =============================================================
23
+ ' CORE PROPERTIES
24
+ ' =============================================================
25
+
26
+ id as string ' Widget identifier
27
+ children as object ' Child widgets collection
28
+
29
+ ' =============================================================
30
+ ' RUNTIME-INJECTED PROPERTIES
31
+ ' =============================================================
32
+ ' These are populated by the builder during widget creation
33
+
34
+ node as object ' SceneGraph node reference
35
+ parent as object ' Parent widget reference
36
+ plugins as object ' Plugin methods and state
37
+
38
+ viewModelState as object ' Mutable state shared across ViewModel's widgets
39
+ props as object ' Immutable props shared across ViewModel's widgets
40
+ nodeType as string ' SceneGraph node type (e.g., "Group", "Label")
41
+ zIndex as integer ' Z-order for rendering
42
+
43
+ ' =============================================================
44
+ ' LIFECYCLE CALLBACKS
45
+ ' =============================================================
46
+
47
+ onMountWidget as Function ' Called after widget is mounted
48
+ onUpdateWidget as Function ' Called before widget updates
49
+ onDestroyWidget as Function ' Called before widget is destroyed
50
+
51
+ ' =============================================================
52
+ ' DECORATED METHODS
53
+ ' =============================================================
54
+ ' These methods are injected by WidgetCreate.bs
55
+
56
+ ' Tree Navigation
57
+ getWidget as Function ' Find widget by ID or path (any depth)
58
+ findWidgets as Function ' Find widgets by glob pattern
59
+ getChildrenWidgets as Function ' Get direct children (optionally filtered)
60
+ getSiblingWidget as Function ' Get sibling widget by ID
61
+ getSubtreeClone as Function ' Clone widget subtree
62
+
63
+ ' ViewModel Access
64
+ getParentViewModel as Function ' Get parent ViewModel
65
+ getViewModel as Function ' Get owning ViewModel
66
+ getRootWidget as Function ' Get root widget (HID "0")
67
+
68
+ ' Rendering
69
+ render as Function ' Render widget updates
70
+ refresh as Function ' Refresh specific properties by key paths
71
+ erase as Function ' Remove widget(s) from tree
72
+
73
+ ' Framework Integration
74
+ getFrameworkInstance as Function ' Get framework instance
75
+ getDispatcher as Function ' Get dispatcher facade by ID
76
+ createDispatcher as Function ' Create dispatcher
77
+ animator as Function ' Get animator factory
78
+
79
+ ' =============================================================
80
+ ' PLUGIN METHODS
81
+ ' =============================================================
82
+ ' These are optionally injected by plugins
83
+
84
+ ' Focus Plugin Methods (from FocusPlugin.bs)
85
+ optional enableFocusNavigation as function ' Enable focus navigation
86
+ optional isFocusNavigationEnabled as function ' Check if focus enabled
87
+ optional setFocus as function ' Set focus on widget
88
+ optional getFocusedWidget as function ' Get currently focused widget
89
+ optional proceedLongPress as function ' Trigger long press
90
+ optional isLongPressActive as function ' Check if long press active
91
+
92
+ ' =============================================================
93
+ ' PRIVATE PROPERTIES (Engine Use Only)
94
+ ' =============================================================
95
+
96
+ private HID as string ' Hierarchical ID (e.g., "0.header.logo")
97
+ private vmHID as string ' Owning ViewModel's HID
98
+ private isRootChild as boolean ' True if direct child of root
99
+ private childrenHIDhash as object ' Hash of child HIDs for fast lookup
100
+ private parentHID as string ' Parent's HID
101
+
102
+ end class
103
+
104
+ end namespace
@@ -0,0 +1,193 @@
1
+ import "ListenerForDispatchers.bs"
2
+
3
+ namespace Rotor
4
+
5
+ ' =====================================================================
6
+ ' createDispatcher - Factory function for creating dispatchers
7
+ '
8
+ ' Creates a dispatcher instance and returns its facade for external use.
9
+ '
10
+ ' @param {string} customDispatcherId - Dispatcher identifier
11
+ ' @param {Model} modelInstance - Model holding state
12
+ ' @param {Reducer} reducerInstance - Reducer for state transitions
13
+ ' @returns {Dispatcher} Dispatcher facade instance
14
+ ' =====================================================================
15
+ sub createDispatcher(customDispatcherId as string, modelInstance as Model, reducerInstance as Reducer)
16
+ m.dispatcherInstance = new Rotor.DispatcherCreator(customDispatcherId, modelInstance, reducerInstance)
17
+ end sub
18
+
19
+ ' =====================================================================
20
+ ' DispatcherCreator - Internal dispatcher implementation for MVI pattern state management
21
+ '
22
+ ' Responsibilities:
23
+ ' - Dispatches intents to reducer
24
+ ' - Manages state updates via Model
25
+ ' - Notifies listeners of state changes
26
+ ' - Handles dispatch queue to prevent reentrancy
27
+ ' - Exposes state to render thread via task node field
28
+ '
29
+ ' MVI Flow:
30
+ ' dispatch(intent) → reducer.reduce() → update state → notify listeners
31
+ ' =====================================================================
32
+ class DispatcherCreator extends ListenerForDispatchers
33
+
34
+ ' =============================================================
35
+ ' MEMBER VARIABLES
36
+ ' =============================================================
37
+
38
+ middlewares = [] ' Middleware array (unused, handled by Reducer)
39
+ isExternal = false ' False for internal dispatchers
40
+ isDispatchingInProgress = false ' Prevents reentrant dispatch
41
+ dispatchQueue = [] ' Queue for intents during active dispatch
42
+ dispatcherId as string ' Dispatcher identifier
43
+ taskNode as object ' Task node for cross-thread state exposure
44
+ reducerInstance as object ' Reducer instance
45
+ modelInstance as object ' Model instance holding state
46
+ listeners = [] ' State change listeners
47
+
48
+ ' =============================================================
49
+ ' CONSTRUCTOR
50
+ ' =============================================================
51
+
52
+ ' ---------------------------------------------------------------------
53
+ ' Constructor - Initializes dispatcher and registers with provider
54
+ '
55
+ ' @param {string} dispatcherId - Dispatcher identifier (auto-generated if empty)
56
+ ' @param {object} modelInstance - Model instance
57
+ ' @param {object} reducerInstance - Reducer instance
58
+ '
59
+ sub new(dispatcherId = "" as string, modelInstance = invalid as object, reducerInstance = invalid as object)
60
+ super()
61
+ if dispatcherId = "" then dispatcherId = Rotor.Utils.getUUIDHex()
62
+ m.dispatcherId = dispatcherId
63
+
64
+ globalScope = GetGlobalAA()
65
+ m.taskNode = globalScope.top
66
+
67
+ ' Link reducer to this dispatcher
68
+ m.reducerInstance = reducerInstance
69
+ m.reducerInstance.ownerDispatcher = m
70
+ m.reducerInstance.ownerDispatcherId = dispatcherId
71
+ m.modelInstance = modelInstance
72
+
73
+ ' Create field on task node for render thread communication
74
+ m.taskNode.addField(m.dispatcherId, "node", true)
75
+
76
+ ' Expose initial state
77
+ m.exposeState()
78
+
79
+ ' Register with dispatcher provider
80
+ dispatcherProvider = globalScope.rotor_framework_helper.frameworkInstance.dispatcherProvider
81
+ dispatcherProvider.registerDispatcher(m, dispatcherId)
82
+ end sub
83
+
84
+ ' =============================================================
85
+ ' STATE MANAGEMENT
86
+ ' =============================================================
87
+
88
+ ' ---------------------------------------------------------------------
89
+ ' exposeState - Exposes current state to render thread via task node field
90
+ '
91
+ ' Creates a ContentNode with state fields and sets it on task node.
92
+ ' This allows render thread to observe state changes.
93
+ '
94
+ sub exposeState()
95
+ newState = CreateObject("roSGNode", "ContentNode")
96
+ newState.addFields(m.modelInstance.state)
97
+ m.taskNode.setField(m.dispatcherId, newState)
98
+ end sub
99
+
100
+ ' ---------------------------------------------------------------------
101
+ ' getState - Returns current state (optionally mapped)
102
+ '
103
+ ' @param {dynamic} mapStateToProps - Optional mapping function
104
+ ' @param {object} callerScope - Caller scope for mapping function
105
+ ' @returns {object} Current state
106
+ '
107
+ function getState(mapStateToProps = invalid as Dynamic, callerScope = invalid as object) as object
108
+ state = m.modelInstance.state
109
+ m.runMapStateToProps(state, mapStateToProps, callerScope)
110
+ return state
111
+ end function
112
+
113
+ ' =============================================================
114
+ ' DISPATCH
115
+ ' =============================================================
116
+
117
+ ' ---------------------------------------------------------------------
118
+ ' dispatch - Dispatches intent to reducer and updates state
119
+ '
120
+ ' Process flow:
121
+ ' 1. Validate intent payload
122
+ ' 2. If not dispatching, execute reducer and notify listeners
123
+ ' 3. If dispatching, queue intent for later processing
124
+ ' 4. Process queued intents after completion
125
+ '
126
+ ' @param {Intent} intent - Intent object to dispatch
127
+ '
128
+ sub dispatch(intent as Intent)
129
+ if intent?.payload <> invalid and intent.payload.Count() > 1 and intent.payload = invalid
130
+ throw "[WARNING] Intent payload is invalid."
131
+ end if
132
+
133
+ if m.isDispatchingInProgress = false
134
+ ' Execute reducer
135
+ currentState = m.modelInstance.state
136
+ newState = m.reducerInstance.reduce(currentState, intent)
137
+
138
+ ' Middleware cancelled intent
139
+ if newState = invalid then return
140
+
141
+ ' Update state and notify
142
+ m.exposeState()
143
+ m.notifyListeners(newState)
144
+
145
+ ' Process queued intents
146
+ if m.dispatchQueue.Count() > 0
147
+ intent = m.dispatchQueue.shift()
148
+ m.dispatch(intent)
149
+ end if
150
+ else
151
+ ' Queue intent to prevent reentrancy
152
+ m.dispatchQueue.push(intent)
153
+ end if
154
+ end sub
155
+
156
+ ' =============================================================
157
+ ' ASYNC CALLBACK
158
+ ' =============================================================
159
+
160
+ ' ---------------------------------------------------------------------
161
+ ' asyncReducerCallback - Routes async callback to reducer
162
+ '
163
+ ' @param {object} msg - Message from async operation
164
+ '
165
+ sub asyncReducerCallback(msg)
166
+ m.reducerInstance.asyncReducerCallback(msg)
167
+ end sub
168
+
169
+ ' =============================================================
170
+ ' CLEANUP
171
+ ' =============================================================
172
+
173
+ ' ---------------------------------------------------------------------
174
+ ' destroy - Cleans up dispatcher resources
175
+ '
176
+ ' Deregisters from provider and clears all references.
177
+ '
178
+ override sub destroy()
179
+ super.destroy()
180
+
181
+ m.dispatchQueue.clear()
182
+
183
+ frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
184
+ frameworkInstance.dispatcherProvider.deregisterDispatcher(m.dispatcherId)
185
+
186
+ m.taskNode = invalid
187
+ m.modelInstance = invalid
188
+ m.reducerInstance = invalid
189
+ end sub
190
+
191
+ end class
192
+
193
+ end namespace
@@ -0,0 +1,260 @@
1
+ import "ListenerForDispatchers.bs"
2
+
3
+ namespace Rotor
4
+
5
+ ' =====================================================================
6
+ ' DispatcherExternal - External dispatcher for cross-thread MVI state management
7
+ '
8
+ ' This dispatcher is used when accessing dispatchers from a different thread
9
+ ' than where they were created (render thread accessing task thread dispatcher
10
+ ' or vice versa).
11
+ '
12
+ ' Responsibilities:
13
+ ' - Dispatches intents across thread boundaries via rotorSync field
14
+ ' - Observes state changes on task node field
15
+ ' - Manages cross-thread listener notifications
16
+ ' - Lazy observer setup (only when listeners are added)
17
+ ' - Auto cleanup when last listener is removed
18
+ '
19
+ ' MVI Cross-Thread Flow:
20
+ ' Render Thread: dispatch(intent) → rotorSync field → Task Thread
21
+ ' Task Thread: reduce state → task node field → Render Thread observer callback
22
+ ' =====================================================================
23
+ class DispatcherExternal extends ListenerForDispatchers
24
+
25
+ ' =============================================================
26
+ ' MEMBER VARIABLES
27
+ ' =============================================================
28
+
29
+ dispatcherId as string ' Dispatcher identifier
30
+ taskNode as object ' Task node for cross-thread communication
31
+ threadType as string ' Current thread type (RENDER or TASK)
32
+ isExternal = true ' True for external dispatchers
33
+ isStateObserved = false ' Tracks if observer is active
34
+
35
+ ' =============================================================
36
+ ' CONSTRUCTOR
37
+ ' =============================================================
38
+
39
+ ' ---------------------------------------------------------------------
40
+ ' Constructor - Initializes external dispatcher
41
+ '
42
+ ' @param {string} dispatcherId - Dispatcher identifier
43
+ ' @param {object} taskNode - Task node for cross-thread communication
44
+ ' @param {string} threadType - Current thread type (RENDER or TASK)
45
+ '
46
+ sub new(dispatcherId as string, taskNode as object, threadType as string)
47
+ super()
48
+
49
+ ' Store dispatcher configuration
50
+ m.dispatcherId = dispatcherId
51
+ m.taskNode = taskNode
52
+ m.threadType = threadType
53
+ end sub
54
+
55
+ ' =============================================================
56
+ ' DISPATCH
57
+ ' =============================================================
58
+
59
+ ' ---------------------------------------------------------------------
60
+ ' dispatch - Sends intent to internal dispatcher via cross-thread sync
61
+ '
62
+ ' Process flow:
63
+ ' 1. Validate intent payload
64
+ ' 2. Package intent with dispatcher ID
65
+ ' 3. Set rotorSync field to trigger task thread processing
66
+ '
67
+ ' @param {Intent} intent - Intent object to dispatch
68
+ '
69
+ sub dispatch(intent as Intent)
70
+ ' Validate intent payload
71
+ if intent?.payload <> invalid and intent.payload.Count() > 1 and intent.payload = invalid
72
+ throw "[WARNING] Intent payload is invalid."
73
+ end if
74
+
75
+ ' Send intent to task thread via rotorSync field
76
+ m.taskNode.setField("rotorSync", {
77
+ type: Rotor.Const.ThreadSyncType.DISPATCH,
78
+ payload: {
79
+ dispatcherId: m.dispatcherId,
80
+ intent: intent
81
+ }
82
+ })
83
+ end sub
84
+
85
+ ' =============================================================
86
+ ' STATE ACCESS
87
+ ' =============================================================
88
+
89
+ ' ---------------------------------------------------------------------
90
+ ' getState - Returns current state from task node field (optionally mapped)
91
+ '
92
+ ' Reads state from task node field set by internal dispatcher.
93
+ '
94
+ ' @param {dynamic} mapStateToProps - Optional mapping function
95
+ ' @param {object} callerScope - Caller scope for mapping function
96
+ ' @returns {object} Current state
97
+ '
98
+ function getState(mapStateToProps = invalid as Dynamic, callerScope = invalid as object) as object
99
+ ' Read state from task node field
100
+ state = m.taskNode.getField(m.dispatcherId)
101
+
102
+ ' Apply optional state mapping
103
+ m.runMapStateToProps(state, mapStateToProps, callerScope)
104
+
105
+ return state
106
+ end function
107
+
108
+ ' =============================================================
109
+ ' LISTENER MANAGEMENT
110
+ ' =============================================================
111
+
112
+ ' ---------------------------------------------------------------------
113
+ ' addListener - Adds state change listener with lazy observer setup
114
+ '
115
+ ' Sets up observer on first listener registration (lazy initialization).
116
+ '
117
+ ' @param {object} listenerConfig - Listener configuration
118
+ ' @param {string} listenerId - Listener identifier
119
+ ' @param {object} listenerScope - Listener scope for callback
120
+ '
121
+ override sub addListener(listenerConfig as object, listenerId as string, listenerScope as object)
122
+ ' Setup observer on first listener (lazy initialization)
123
+ if m.isStateObserved = false
124
+ m.setupObserver()
125
+ end if
126
+
127
+ ' Register listener with parent class
128
+ super.addListener(listenerConfig, listenerId, listenerScope)
129
+ end sub
130
+
131
+ ' ---------------------------------------------------------------------
132
+ ' removeAllListenersByListenerId - Removes all listeners with given ID
133
+ '
134
+ ' Automatically removes observer when last listener is removed.
135
+ '
136
+ ' @param {string} listenerId - Listener identifier to remove
137
+ '
138
+ override sub removeAllListenersByListenerId(listenerId as string)
139
+ ' Remove listeners via parent class
140
+ super.removeAllListenersByListenerId(listenerId)
141
+
142
+ ' Auto cleanup: remove observer when no listeners remain
143
+ if m.isStateObserved = true and m.listeners.Count() = 0
144
+ m.removeObserver()
145
+ end if
146
+ end sub
147
+
148
+ ' =============================================================
149
+ ' OBSERVER MANAGEMENT
150
+ ' =============================================================
151
+
152
+ ' ---------------------------------------------------------------------
153
+ ' setupObserver - Sets up observer for state changes on task node field
154
+ '
155
+ ' Thread-specific observer setup:
156
+ ' - RENDER thread: Uses observeFieldScoped with callback name
157
+ ' - TASK thread: Uses framework's observer system
158
+ '
159
+ sub setupObserver()
160
+ if m.threadType = Rotor.Const.ThreadType.RENDER
161
+ ' Render thread: use native SceneGraph observer
162
+ m.taskNode.observeFieldScoped(m.dispatcherId, "Rotor_dispatcherStateCallback")
163
+ else
164
+ ' Task thread: use framework observer system
165
+ fieldId = m.dispatcherId
166
+ frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
167
+ frameworkInstance.addObserver(fieldId, m.taskNode)
168
+ end if
169
+
170
+ m.isStateObserved = true
171
+ end sub
172
+
173
+ ' ---------------------------------------------------------------------
174
+ ' removeObserver - Removes observer for state changes
175
+ '
176
+ ' Thread-specific observer cleanup matching setupObserver.
177
+ '
178
+ sub removeObserver()
179
+ if m.threadType = Rotor.Const.ThreadType.RENDER
180
+ ' Render thread: unobserve SceneGraph field
181
+ m.taskNode.unobserveFieldScoped(m.dispatcherId)
182
+ else
183
+ ' Task thread: remove framework observer
184
+ fieldId = m.dispatcherId
185
+ frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
186
+ frameworkInstance.removeObserver(fieldId, m.taskNode)
187
+ end if
188
+
189
+ m.isStateObserved = false
190
+ end sub
191
+
192
+ ' =============================================================
193
+ ' CLEANUP
194
+ ' =============================================================
195
+
196
+ ' ---------------------------------------------------------------------
197
+ ' destroy - Cleans up external dispatcher resources
198
+ '
199
+ ' Cleanup steps:
200
+ ' 1. Call parent cleanup
201
+ ' 2. Remove observer if active
202
+ ' 3. Clear listeners
203
+ ' 4. Deregister from dispatcher provider
204
+ '
205
+ override sub destroy()
206
+ super.destroy()
207
+
208
+ frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
209
+
210
+ ' Remove observer if active
211
+ if m.isStateObserved = true
212
+ if m.threadType = Rotor.Const.ThreadType.RENDER
213
+ m.taskNode.unobserveFieldScoped(m.dispatcherId)
214
+ else
215
+ fieldId = m.dispatcherId
216
+ frameworkInstance.removeObserver(fieldId, m.taskNode)
217
+ end if
218
+ end if
219
+
220
+ ' Clear all references
221
+ m.listeners.Clear()
222
+ m.taskNode = invalid
223
+
224
+ ' Deregister from provider
225
+ frameworkInstance.dispatcherProvider.deregisterDispatcher(m.dispatcherId)
226
+ end sub
227
+
228
+ end class
229
+
230
+ end namespace
231
+
232
+ namespace Rotor
233
+
234
+ ' =====================================================================
235
+ ' dispatcherStateCallback - Global callback function for dispatcher state changes on render thread
236
+ '
237
+ ' This function is called by SceneGraph's observeFieldScoped when a dispatcher's
238
+ ' state field changes on the task node. It routes the state change to the
239
+ ' appropriate dispatcher instance for listener notification.
240
+ '
241
+ ' Process flow:
242
+ ' 1. Extract dispatcher ID from message field
243
+ ' 2. Extract new state from message data
244
+ ' 3. Lookup dispatcher instance from provider
245
+ ' 4. Notify all registered listeners with new state
246
+ '
247
+ ' @param {roSGNodeEvent} msg - SceneGraph observer message
248
+ ' =====================================================================
249
+ sub dispatcherStateCallback(msg)
250
+ ' Extract dispatcher ID and state from message
251
+ dispatcherId = msg.getField()
252
+ state = msg.getData()
253
+
254
+ ' Lookup dispatcher instance and notify listeners
255
+ frameworkInstance = GetGlobalAA().rotor_framework_helper.frameworkInstance
256
+ dispatcherInstance = frameworkInstance.dispatcherProvider.get(dispatcherId)
257
+ dispatcherInstance.notifyListeners(state)
258
+ end sub
259
+
260
+ end namespace