rotor-framework 0.7.1 → 0.7.3

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/README.md CHANGED
@@ -96,7 +96,7 @@ You can find [🌱](./docs/ai/readme.opt.yaml) symbols in all documentation page
96
96
 
97
97
  ## 📚 Learn More
98
98
 
99
- ![Version](https://img.shields.io/badge/version-v0.7.1-blue?label=Documents%20TAG)
99
+ ![Version](https://img.shields.io/badge/version-v0.7.3-blue?label=Documents%20TAG)
100
100
 
101
101
  ### Framework Core
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rotor-framework",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Roku toolkit library providing a ViewBuilder, full UI lifecycle with focus handling and many core features, plus MVI-based state management.",
5
5
  "author": "Balázs Molnár",
6
6
  "license": "Apache-2.0",
@@ -4,7 +4,7 @@
4
4
  ' ▐▛▀▚▖▐▌ ▐▌ █ ▐▌ ▐▌▐▛▀▚▖ ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌▐▌ ▐▌▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▛▚▖
5
5
  ' ▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌
6
6
  ' Rotor Framework™
7
- ' Version 0.7.1
7
+ ' Version 0.7.3
8
8
  ' © 2025 Balázs Molnár — Apache License 2.0
9
9
  ' =========================================================================
10
10
 
@@ -21,8 +21,8 @@ import "engine/animator/Animator.bs"
21
21
 
22
22
  ' base classes
23
23
  import "base/BaseWidget.bs"
24
- import "base/DispatcherCreator.bs"
25
- import "base/DispatcherExternal.bs"
24
+ import "base/DispatcherOriginal.bs"
25
+ import "base/DispatcherCrossThread.bs"
26
26
  import "base/BaseReducer.bs"
27
27
  import "base/BaseModel.bs"
28
28
  import "base/BaseStack.bs"
@@ -86,7 +86,7 @@ namespace Rotor
86
86
  class Framework
87
87
 
88
88
  name = "Rotor Framework"
89
- version = "0.7.1"
89
+ version = "0.7.3"
90
90
 
91
91
  config = {
92
92
  tasks: invalid, ' @array (optional)
@@ -118,23 +118,14 @@ namespace Rotor
118
118
  ttsService as object
119
119
  dispatcherProvider as object
120
120
  animatorProvider as object
121
- info = {
122
- device: {}
123
- }
124
-
125
121
  ' plugin adapter workspace
126
122
  plugins = {}
127
123
 
128
- ' sync
129
- taskOperationalFlag = {}
130
- taskSyncReadyFlag = {}
131
- taskNodes = {}
124
+ ' sync - task tracking with phase: pending → operational → synced
125
+ tasks = {} ' taskId -> { node, phase, dispatchers[] }
132
126
 
133
127
  enableRendering = true
134
128
 
135
- ' helper vars
136
- deviceInfo as object
137
-
138
129
  '----------------------------------------------------------------------
139
130
  ' new - Initializes the Rotor Framework instance
140
131
  '
@@ -161,8 +152,6 @@ namespace Rotor
161
152
 
162
153
  m.builder.init(m)
163
154
 
164
- m.initInfo()
165
-
166
155
  ' set root node
167
156
  if m.config.rootNode = invalid
168
157
  rootNode = globalScope.top
@@ -325,15 +314,6 @@ namespace Rotor
325
314
  return m.animatorProvider.getFactory(animatorId, m)
326
315
  end function
327
316
 
328
- ' ---------------------------------------------------------------------
329
- ' getInfo - Gets framework and device information
330
- '
331
- ' @returns {object} Info object containing device details
332
- '
333
- public function getInfo() as object
334
- return m.info
335
- end function
336
-
337
317
  ' ---------------------------------------------------------------------
338
318
  ' getNodePoolInfo - Gets node pool statistics
339
319
  '
@@ -366,32 +346,6 @@ namespace Rotor
366
346
  m.builder.pluginAdapter.registerPlugins(plugins)
367
347
  end sub
368
348
 
369
- ' ---------------------------------------------------------------------
370
- ' initInfo - Initializes device and framework information
371
- '
372
- ' Collects device information (graphics platform, model, OS version, locale)
373
- ' and stores it in m.info for later access.
374
- '
375
- sub initInfo()
376
- ' Device info
377
- di = CreateObject("roDeviceInfo")
378
- ' Make it available on framework instance
379
- m.deviceInfo = di
380
- ' Generate app info
381
- m.info.device.append({
382
- graphicsPlatform: di.GetGraphicsPlatform(), ' ["opengl"|"directfb"]
383
- modelDisplayName: di.GetModelDisplayName(),
384
- OSVersion: di.GetOSVersion(),
385
- currentLocale: di.GetCurrentLocale(), ' Example: "en_US"
386
- countryCode: di.GetCountryCode() ' A value that indicates the Streaming Store associated with a user's Roku account.
387
- })
388
- #if debug
389
- print `[DEVICEINFO] Graphics Platform: ${m.info.device.graphicsPlatform}`
390
- print `[DEVICEINFO] modelDisplayName: ${m.info.device.modelDisplayName}`
391
- print `[DEVICEINFO] OSVersion: ${m.info.device.OSVersion.major}.${m.info.device.OSVersion.minor}.${m.info.device.OSVersion.revision}.${m.info.device.OSVersion.build}`
392
- #end if
393
- end sub
394
-
395
349
  ' ---------------------------------------------------------------------
396
350
  ' setupTaskForSyncing - Prepares a task node for cross-thread syncing
397
351
  '
@@ -404,15 +358,17 @@ namespace Rotor
404
358
  taskNode = rootNode.createChild(taskName)
405
359
  taskId = Rotor.Utils.getUUIDHex(16)
406
360
 
407
- m.taskNodes[taskId] = taskNode ' collection for later usage
408
- m.taskOperationalFlag[taskId] = false ' collection for later usage
361
+ m.tasks[taskId] = {
362
+ node: taskNode,
363
+ phase: "pending",
364
+ dispatchers: []
365
+ }
409
366
 
410
367
  Rotor.Utils.setCustomFields(taskNode, {
411
368
  taskId: taskId,
412
369
  rootNode: rootNode
413
370
  })
414
371
 
415
- ' taskNode.observeFieldScoped("rotorSync", "Rotor_syncCallback")
416
372
  taskNode.control = "RUN"
417
373
  end sub
418
374
 
@@ -422,136 +378,108 @@ namespace Rotor
422
378
  ' @param {object} taskList - Array of task names to set up
423
379
  '
424
380
  sub setupAdditionalTasks(taskList as object)
425
- ' check additional tasks
426
381
  if taskList = invalid then return
427
-
428
- taskNames = Rotor.Utils.ensureArray(taskList)
429
- for each taskName in taskNames
382
+ for each taskName in Rotor.Utils.ensureArray(taskList)
430
383
  m.setupTaskForSyncing(taskName)
431
384
  end for
432
385
  end sub
433
386
 
434
387
  ' ---------------------------------------------------------------------
435
- ' areAllTasksOperational - Checks if all task threads are operational
388
+ ' allTasksInPhase - Checks if all tasks are in given phase
436
389
  '
437
- ' @returns {boolean} True if all tasks have signaled operational status
390
+ ' @param {string} phase - Phase to check ("pending", "operational", "synced")
391
+ ' @returns {boolean} True if all tasks are in the given phase
438
392
  '
439
- function areAllTasksOperational() as boolean
440
- ' Check if all nodes ready (very basic logic (< future improvement)
441
- if m.taskOperationalFlag.Count() = 0 then return false
442
-
443
- for each flag in m.taskOperationalFlag.Items()
444
- if flag.value <> true then return false
393
+ function allTasksInPhase(phase as string) as boolean
394
+ if m.tasks.Count() = 0 then return false
395
+ for each taskId in m.tasks
396
+ if m.tasks[taskId].phase <> phase then return false
445
397
  end for
446
-
447
398
  return true
448
399
  end function
449
400
 
450
401
  ' ---------------------------------------------------------------------
451
- ' collectExternalDispatcherRegistrations - Collects external dispatcher info for tasks
452
- '
453
- ' Creates a map of which external dispatchers need to be registered in which tasks.
454
- '
455
- ' @returns {object} AA mapping taskId to array of dispatcher registration configs
456
- '
457
- function collectExternalDispatcherRegistrations() as object
458
- registrations = {}
459
- dispatcherIds = m.dispatcherProvider.getAll().keys()
460
-
461
- for each taskEntry in m.taskNodes.Items()
462
- taskNode = taskEntry.value
463
-
464
- for each dispatcherId in dispatcherIds
465
- dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
466
- externalTaskNode = dispatcherInstance.taskNode
467
-
468
- if not taskNode.isSameNode(externalTaskNode)
469
- m.taskSyncReadyFlag[taskNode.taskId] = false ' collection for later usage
470
- if registrations[taskNode.taskId] = invalid then registrations[taskNode.taskId] = []
471
- registrations[taskNode.taskId].push({
472
- dispatcherId: dispatcherId,
473
- externalTaskNode: externalTaskNode
474
- })
475
- end if
476
- end for
402
+ ' getDispatchersNotOnTask - Gets dispatchers that are NOT on given task
403
+ '
404
+ ' @param {object} taskNode - Task node to exclude
405
+ ' @returns {object} Array of { dispatcherId, stateNode } for foreign dispatchers
406
+ '
407
+ function getDispatchersNotOnTask(taskNode as object) as object
408
+ result = []
409
+ for each dispatcherId in m.dispatcherProvider.getAll().keys()
410
+ stateNode = m.dispatcherProvider.get(dispatcherId).stateNode
411
+ if not taskNode.isSameNode(stateNode)
412
+ result.push({ dispatcherId: dispatcherId, stateNode: stateNode })
413
+ end if
477
414
  end for
478
-
479
- return registrations
415
+ return result
480
416
  end function
481
417
 
482
418
  ' ---------------------------------------------------------------------
483
- ' dispatchExternalDispatcherRegistrations - Sends dispatcher registration to tasks
484
- '
485
- ' @param {object} registrations - Map of taskId to dispatcher registration configs
486
- '
487
- sub dispatchExternalDispatcherRegistrations(registrations as object)
488
- for each taskId in registrations
489
- taskNode = m.taskNodes[taskId]
490
- taskNode.setField("rotorSync", {
491
- type: Rotor.Const.ThreadSyncType.REGISTER_EXTERNAL_DISPATCHER,
492
- externalDispatcherList: registrations[taskId]
493
- })
494
- end for
495
- end sub
496
-
497
- ' ---------------------------------------------------------------------
498
- ' haveAllTasksSynced - Checks if all tasks have completed syncing
499
- '
500
- ' @returns {boolean} True if all tasks have synced successfully
501
- '
502
- function haveAllTasksSynced() as boolean
503
- ' Check if all nodes ready (very basic logic (< future improvement)
504
- if m.taskSyncReadyFlag.Count() = 0 then return false
505
-
506
- for each flag in m.taskSyncReadyFlag.Items()
507
- if flag.value <> true then return false
419
+ ' broadcastDispatchersToTasks - Sends foreign dispatchers to each task
420
+ '
421
+ ' For each task, sends list of dispatchers that exist on other threads.
422
+ '
423
+ sub broadcastDispatchersToTasks()
424
+ for each taskId in m.tasks
425
+ task = m.tasks[taskId]
426
+ foreignDispatchers = m.getDispatchersNotOnTask(task.node)
427
+
428
+ if foreignDispatchers.Count() > 0
429
+ task.node.setField("rotorSync", {
430
+ type: Rotor.Const.ThreadSyncType.REGISTER_CROSS_THREAD_DISPATCHER,
431
+ crossThreadDispatcherList: foreignDispatchers
432
+ })
433
+ else
434
+ task.phase = "synced"
435
+ end if
508
436
  end for
509
437
 
510
- return true
511
- end function
438
+ ' If no tasks needed dispatchers, we're done
439
+ if m.allTasksInPhase("synced")
440
+ m.isReady()
441
+ end if
442
+ end sub
512
443
 
513
444
  ' ---------------------------------------------------------------------
514
445
  ' handleTaskSyncing - Handles task syncing phase
515
446
  '
516
- ' Registers external dispatchers and sets up cross-thread communication.
447
+ ' Called when a task reports its dispatchers. Registers cross-thread
448
+ ' dispatchers and broadcasts dispatcher map when all tasks are ready.
517
449
  '
518
- ' @param {object} sync - Sync payload from task thread
450
+ ' @param {object} sync - Sync payload { taskNode, dispatcherIds, tasks }
519
451
  '
520
452
  sub handleTaskSyncing(sync as object)
521
453
  taskNode = sync.taskNode
454
+ taskId = taskNode.taskId
522
455
 
456
+ ' Setup any additional tasks this task declared
523
457
  m.setupAdditionalTasks(sync.tasks)
524
458
 
525
- ' register incoming dispatchers
526
- dispatcherIds = sync.dispatcherIds
527
- if dispatcherIds <> invalid
528
- m.dispatcherProvider.registerExternalDispatchers(dispatcherIds, taskNode)
529
- end if
459
+ ' Store dispatcher IDs for this task
460
+ m.tasks[taskId].dispatchers = sync.dispatcherIds ?? []
461
+ m.tasks[taskId].phase = "operational"
530
462
 
531
- ' update task status
532
- m.taskOperationalFlag[taskNode.taskId] = true
463
+ ' Register cross-thread dispatchers on render thread
464
+ if sync.dispatcherIds <> invalid
465
+ m.dispatcherProvider.registerCrossThreadDispatchers(sync.dispatcherIds, taskNode)
466
+ end if
533
467
 
534
- if not m.areAllTasksOperational() then return
468
+ ' Wait for all tasks to be operational
469
+ if not m.allTasksInPhase("operational") then return
535
470
 
536
- ' if allTasksRunning then create external dispatchers in all tasks
537
- registrations = m.collectExternalDispatcherRegistrations()
538
- if registrations.Count() > 0
539
- m.dispatchExternalDispatcherRegistrations(registrations)
540
- else
541
- m.isReady()
542
- end if
471
+ ' All tasks ready - broadcast dispatcher map
472
+ m.broadcastDispatchersToTasks()
543
473
  end sub
544
474
 
545
475
  ' ---------------------------------------------------------------------
546
476
  ' handleTaskSynced - Handles task synced confirmation
547
477
  '
548
- ' Marks task as synced and checks if all tasks are ready.
549
- '
550
478
  ' @param {string} taskId - ID of the task that has synced
551
479
  '
552
480
  sub handleTaskSynced(taskId as string)
553
- m.taskSyncReadyFlag[taskId] = true
554
- if m.haveAllTasksSynced()
481
+ m.tasks[taskId].phase = "synced"
482
+ if m.allTasksInPhase("synced")
555
483
  m.isReady()
556
484
  end if
557
485
  end sub
@@ -593,15 +521,15 @@ namespace Rotor
593
521
  public sub destroy()
594
522
 
595
523
  rootNode = m.getRootNode()
596
- for each taskId in m.taskNodes
597
- taskNode = m.taskNodes[taskId]
598
- taskNode.setField("rotorSync", {
524
+ for each taskId in m.tasks
525
+ task = m.tasks[taskId]
526
+ task.node.setField("rotorSync", {
599
527
  type: Rotor.Const.ThreadSyncType.DESTROY
600
528
  })
601
- rootNode.removeChild(taskNode)
529
+ rootNode.removeChild(task.node)
602
530
  end for
603
531
 
604
- m.taskNodes.Clear()
532
+ m.tasks.Clear()
605
533
 
606
534
  rootNode.unobserveFieldScoped("rotorSync")
607
535
 
@@ -619,9 +547,6 @@ namespace Rotor
619
547
  }
620
548
  m.config.rootNode = invalid
621
549
 
622
- m.info.Clear()
623
- m.deviceInfo = invalid
624
-
625
550
  end sub
626
551
 
627
552
  end class
@@ -4,7 +4,7 @@
4
4
  ' ▐▛▀▚▖▐▌ ▐▌ █ ▐▌ ▐▌▐▛▀▚▖ ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌▐▌ ▐▌▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▛▚▖
5
5
  ' ▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌
6
6
  ' Rotor Framework™
7
- ' Version 0.7.1
7
+ ' Version 0.7.3
8
8
  ' © 2025 Balázs Molnár — Apache License 2.0
9
9
  ' =========================================================================
10
10
 
@@ -16,8 +16,8 @@ import "engine/providers/DispatcherProvider.bs"
16
16
  import "engine/providers/Dispatcher.bs"
17
17
 
18
18
  ' base classes
19
- import "base/DispatcherCreator.bs"
20
- import "base/DispatcherExternal.bs"
19
+ import "base/DispatcherOriginal.bs"
20
+ import "base/DispatcherCrossThread.bs"
21
21
  import "base/BaseReducer.bs"
22
22
  import "base/BaseModel.bs"
23
23
  import "base/BaseStack.bs"
@@ -70,7 +70,7 @@ namespace Rotor
70
70
  class FrameworkTask
71
71
 
72
72
  name = "Rotor Framework"
73
- version = "0.7.1"
73
+ version = "0.7.3"
74
74
 
75
75
  config = {
76
76
  tasks: invalid, ' optional
@@ -87,6 +87,7 @@ namespace Rotor
87
87
  dispatcherProvider as object
88
88
  port as object
89
89
  asyncTransferRegistry = {} ' transferId -> { dispatcherId, context }
90
+ deviceInfoRegistry = {} ' deviceInfoId -> { dispatcherId, context, deviceInfo }
90
91
  onTick as function
91
92
 
92
93
  ' ---------------------------------------------------------------------
@@ -148,6 +149,27 @@ namespace Rotor
148
149
  }
149
150
  end sub
150
151
 
152
+ ' ---------------------------------------------------------------------
153
+ ' registerDeviceInfo - Registers a roDeviceInfo for event listening
154
+ '
155
+ ' This method associates a roDeviceInfo object with a dispatcher ID.
156
+ ' When device info events occur (linkStatus, etc.), the framework will
157
+ ' route them to the correct dispatcher's asyncReducerCallback.
158
+ '
159
+ ' @param {string} deviceInfoId - Unique identifier for this registration
160
+ ' @param {string} dispatcherId - Dispatcher ID that will handle events
161
+ ' @param {object} deviceInfo - The roDeviceInfo object (must have SetMessagePort called)
162
+ ' @param {object} context - Optional user data to pass back in callback
163
+ '
164
+ public sub registerDeviceInfo(deviceInfoId as string, dispatcherId as string, deviceInfo as object, context = invalid as dynamic)
165
+ deviceInfo.SetMessagePort(m.port)
166
+ m.deviceInfoRegistry[deviceInfoId] = {
167
+ dispatcherId: dispatcherId,
168
+ deviceInfo: deviceInfo,
169
+ context: context
170
+ }
171
+ end sub
172
+
151
173
  ' ---------------------------------------------------------------------
152
174
  ' sync - Starts the message loop for cross-thread communication
153
175
  '
@@ -205,10 +227,10 @@ namespace Rotor
205
227
  ' taskIntent = Rotor.Utils.deepCopy(intent)
206
228
  dispatcherInstance.dispatch(intent)
207
229
 
208
- else if sync.type = Rotor.Const.ThreadSyncType.REGISTER_EXTERNAL_DISPATCHER
230
+ else if sync.type = Rotor.Const.ThreadSyncType.REGISTER_CROSS_THREAD_DISPATCHER
209
231
 
210
- for each item in sync.externalDispatcherList
211
- m.dispatcherProvider.registerExternalDispatchers(item.dispatcherId, item.externalTaskNode)
232
+ for each item in sync.crossThreadDispatcherList
233
+ m.dispatcherProvider.registerCrossThreadDispatchers(item.dispatcherId, item.stateNode)
212
234
  end for
213
235
 
214
236
  m.notifySyncStatus(Rotor.Const.ThreadSyncType.TASK_SYNCED)
@@ -251,6 +273,22 @@ namespace Rotor
251
273
  end if
252
274
 
253
275
  end if
276
+ else if msgType = "roDeviceInfoEvent"
277
+ ' Handle device info events (ignore appFocused, etc.)
278
+ if m.deviceInfoRegistry.count() > 0
279
+ eventInfo = msg.GetInfo()
280
+ ' Only process linkStatus events, ignore appFocused and other events
281
+ if eventInfo?.linkStatus <> invalid
282
+ ' Route to all registered device info dispatchers
283
+ for each deviceInfoId in m.deviceInfoRegistry
284
+ registryEntry = m.deviceInfoRegistry[deviceInfoId]
285
+ dispatcherInstance = m.dispatcherProvider.get(registryEntry.dispatcherId)
286
+ if dispatcherInstance <> invalid
287
+ dispatcherInstance.asyncReducerCallback(eventInfo, registryEntry?.context as dynamic)
288
+ end if
289
+ end for
290
+ end if
291
+ end if
254
292
  end if
255
293
  end if
256
294
  end while
@@ -317,6 +355,7 @@ namespace Rotor
317
355
  '
318
356
  public sub destroy()
319
357
  m.asyncTransferRegistry.clear()
358
+ m.deviceInfoRegistry.clear()
320
359
  m.dispatcherProvider.destroy()
321
360
 
322
361
  globalScope = GetGlobalAA()
@@ -87,22 +87,24 @@ namespace Rotor
87
87
  ' These are optionally injected by plugins
88
88
 
89
89
  ' Focus Plugin Methods (from FocusPlugin.bs)
90
- optional enableFocusNavigation as function ' Enable focus navigation
90
+ optional enableFocusNavigation as function ' Enable focus navigation
91
91
  optional isFocusNavigationEnabled as function ' Check if focus enabled
92
- optional setFocus as function ' Set focus on widget
93
- optional getFocusedWidget as function ' Get currently focused widget
94
- optional proceedLongPress as function ' Trigger long press
95
- optional isLongPressActive as function ' Check if long press active
92
+ optional setFocus as function ' Set focus on widget
93
+ optional getFocusedWidget as function ' Get currently focused widget
94
+ optional proceedLongPress as function ' Trigger long press
95
+ optional isLongPressActive as function ' Check if long press active
96
+ optional triggerKeyPress as function ' Trigger key press event
97
+ optional setGroupLastFocusedId as function ' Set the last focused widget ID within the current focus group (used to restore focus when re-entering the group)
96
98
 
97
99
  ' =============================================================
98
100
  ' PRIVATE PROPERTIES (Engine Use Only)
99
101
  ' =============================================================
100
102
 
101
- private HID as string ' Hierarchical ID (e.g., "0.header.logo")
102
- private vmHID as string ' Owning ViewModel's HID
103
- private isRootChild as boolean ' True if direct child of root
103
+ private HID as string ' Hierarchical ID (e.g., "0.header.logo")
104
+ private vmHID as string ' Owning ViewModel's HID
105
+ private isRootChild as boolean ' True if direct child of root
104
106
  private childrenHIDhash as object ' Hash of child HIDs for fast lookup
105
- private parentHID as string ' Parent's HID
107
+ private parentHID as string ' Parent's HID
106
108
 
107
109
  end class
108
110
 
@@ -16,10 +16,15 @@ end interface
16
16
  namespace Rotor
17
17
 
18
18
  ' =====================================================================
19
- ' ListenerForDispatchers - Base class for dispatcher state change listener management
19
+ ' DispatcherCore - Core base class for dispatcher implementations
20
20
  '
21
- ' Provides listener registration, notification, and lifecycle management
22
- ' for both internal and external dispatchers.
21
+ ' Provides shared properties and listener management for both
22
+ ' DispatcherOriginal and DispatcherCrossThread.
23
+ '
24
+ ' Shared Properties:
25
+ ' - dispatcherId: Unique identifier for this dispatcher
26
+ ' - stateNode: SceneGraph node for state exposure
27
+ ' - listeners: Array of registered state change listeners
23
28
  '
24
29
  ' Listener Features:
25
30
  ' - State mapping: mapStateToProps for updating widget props
@@ -33,12 +38,15 @@ namespace Rotor
33
38
  ' 3. Invoke callback functions
34
39
  ' 4. Remove 'once' listeners
35
40
  ' =====================================================================
36
- class ListenerForDispatchers
41
+ class DispatcherCore
37
42
 
38
43
  ' =============================================================
39
44
  ' MEMBER VARIABLES
40
45
  ' =============================================================
41
46
 
47
+ dispatcherId as string ' Dispatcher identifier
48
+ stateNode as object ' Node for cross-thread state exposure
49
+
42
50
  listeners = [] ' Array of listener configurations
43
51
 
44
52
  ' =============================================================