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,654 @@
1
+ '''''''''
2
+ ' ▗▄▄▖ ▗▄▖▗▄▄▄▖▗▄▖ ▗▄▄▖ ▗▄▄▄▖▗▄▄▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄▖▗▖ ▗▖ ▗▄▖ ▗▄▄▖ ▗▖ ▗▖
3
+ ' ▐▌ ▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▛▚▞▜▌▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌▗▞▘
4
+ ' ▐▛▀▚▖▐▌ ▐▌ █ ▐▌ ▐▌▐▛▀▚▖ ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌▐▌ ▐▌▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▛▚▖
5
+ ' ▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▐▌ ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▙▄▄▖▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌
6
+ ' Rotor Framework™ © 2025 Balázs Molnár. All rights reserved.
7
+ ' Version 0.3.2
8
+ '''''''''
9
+
10
+ ' constants
11
+ import "engine/Constants.bs"
12
+
13
+ ' engine
14
+ import "engine/builder/Builder.bs"
15
+ import "engine/services/I18n.bs"
16
+ import "engine/providers/DispatcherProvider.bs"
17
+ import "engine/providers/Dispatcher.bs"
18
+ import "engine/animator/Animator.bs"
19
+
20
+ ' base classes
21
+ import "base/BaseWidget.bs"
22
+ import "base/DispatcherCreator.bs"
23
+ import "base/DispatcherExternal.bs"
24
+ import "base/BaseReducer.bs"
25
+ import "base/BaseModel.bs"
26
+ import "base/BaseStack.bs"
27
+ import "base/BaseViewModel.bs"
28
+
29
+ ' utils
30
+ import "utils/GeneralUtils.bs"
31
+ import "utils/NodeUtils.bs"
32
+ import "utils/ArrayUtils.bs"
33
+
34
+ ' plugins
35
+ import "plugins/DispatcherProviderPlugin.bs"
36
+ import "plugins/FieldsPlugin.bs"
37
+ import "plugins/FontStylePlugin.bs"
38
+ import "plugins/ObserverPlugin.bs"
39
+ import "plugins/FocusPlugin.bs"
40
+
41
+ namespace Rotor
42
+
43
+ '==========================================================================
44
+ '** Framework
45
+ '**
46
+ '** Main Rotor Framework class that orchestrates the entire framework ecosystem.
47
+ '** Provides a reactive, component-based architecture for building Roku applications
48
+ '** with unidirectional data flow, cross-thread state management, and plugin system.
49
+ '**
50
+ '** @param {object} config - Configuration object with the following properties:
51
+ '** - tasks (array, optional): List of task node names to synchronize with the render thread.
52
+ '** When specified, the framework waits for all tasks to initialize
53
+ '** before enabling rendering and calling onReady.
54
+ '** - onReady (function, optional): Callback function invoked when the framework is fully
55
+ '** initialized and all tasks are synced. Called in global scope.
56
+ '** - rootNode (roSGNode, optional): The root SceneGraph node for the framework. Defaults to
57
+ '** the global 'top' node if not specified.
58
+ '** - readyFieldId (string, optional): Field name to add to the root node that will be set to
59
+ '** true when the framework is ready. Useful for triggering
60
+ '** observers when initialization completes.
61
+ '** - nodePool (array, optional): Array of node pool configurations for pre-instantiating
62
+ '** SceneGraph nodes to improve rendering performance.
63
+ '** Supported types: Group, Rectangle, Poster, Label
64
+ '** Format: [{ nodeType: "Label", count: 10 }, ...]
65
+ '** - debug (object): Debug configuration options
66
+ '** - autoSetNodeId (boolean): Automatically set node IDs for debugging (default: false)
67
+ '** - plugins (array): List of plugin instances to register with the framework.
68
+ '** Default plugins include: FieldsPlugin, FontStylePlugin, FocusPlugin,
69
+ '** DispatcherProviderPlugin, ObserverPlugin
70
+ '**
71
+ '** @example
72
+ '** framework = new Rotor.Framework({
73
+ '** tasks: ["DataTask", "NetworkTask"],
74
+ '** rootNode: m.top,
75
+ '** readyFieldId: "appReady",
76
+ '** onReady: sub()
77
+ '** print "Framework ready!"
78
+ '** end sub,
79
+ '** nodePool: [
80
+ '** { nodeType: "Label", count: 20 },
81
+ '** { nodeType: "Rectangle", count: 10 }
82
+ '** ]
83
+ '** })
84
+ '==========================================================================
85
+ class Framework
86
+
87
+ name = "Rotor Framework"
88
+ version = "0.3.2"
89
+
90
+ config = {
91
+ tasks: invalid, ' @array (optional)
92
+ onReady: invalid, ' @function (optional)
93
+ rootNode: invalid, ' @sgNode (optional)
94
+ readyFieldId: invalid, ' @string (optional)
95
+ nodePool: invalid, ' @array (optional)
96
+
97
+
98
+ debug: {
99
+ autoSetNodeId: false
100
+ },
101
+
102
+ plugins: [
103
+ new Rotor.FieldsPlugin("fields"),
104
+ new Rotor.FontStylePlugin("fontStyle"),
105
+ new Rotor.FocusPlugin("focus")
106
+ new Rotor.DispatcherProviderPlugin("dispatcher"),
107
+ new Rotor.ObserverPlugin("observer")
108
+ ]
109
+ }
110
+
111
+ threadType = Rotor.Const.ThreadType.RENDER
112
+
113
+ ' subsystems
114
+ builder as object
115
+ i18nService as object
116
+ dispatcherProvider as object
117
+ animatorProvider as object
118
+ info = {
119
+ device: {}
120
+ }
121
+
122
+ ' plugin adapter workspace
123
+ plugins = {}
124
+
125
+ ' sync
126
+ taskOperationalFlag = {}
127
+ taskSyncReadyFlag = {}
128
+ taskNodes = {}
129
+
130
+ enableRendering = true
131
+
132
+ ' helper vars
133
+ deviceInfo as object
134
+
135
+ '----------------------------------------------------------------------
136
+ ' new - Initializes the Rotor Framework instance
137
+ '
138
+ ' Sets up the framework subsystems (Builder, I18n, Animator, DispatcherProvider),
139
+ ' initializes device info, registers plugins, and synchronizes with task threads.
140
+ '
141
+ ' @param {object} config - Configuration object (see class documentation for details)
142
+ '
143
+ sub new(config = {})
144
+
145
+ Rotor.Utils.deepExtendAA(m.config, config)
146
+
147
+ m.builder = new Rotor.ViewBuilder.Builder()
148
+ m.i18nService = new Rotor.ViewBuilder.I18nService()
149
+ m.animatorProvider = new Rotor.Animator()
150
+ m.dispatcherProvider = new Rotor.DispatcherProvider(m.threadType)
151
+
152
+ globalScope = GetGlobalAA()
153
+ globalScope.rotor_framework_helper = {
154
+ threadType: m.threadType,
155
+ frameworkInstance: m
156
+ }
157
+
158
+ m.initInfo()
159
+
160
+ m.builder.init(m)
161
+ m.i18nService.init(m)
162
+
163
+ ' set root node
164
+ if m.config.rootNode = invalid
165
+ rootNode = globalScope.top
166
+ m.config.rootNode = rootNode
167
+ else
168
+ rootNode = m.config.rootNode
169
+ end if
170
+ m.builder.widgetTree.setRootNode(rootNode)
171
+
172
+ ' Configure Node Pool
173
+ if Rotor.Utils.isArray(m.config.nodePool)
174
+ m.presetNodePool({ nodePool: m.config.nodePool })
175
+ end if
176
+
177
+ ' Register plugins
178
+ m.registerPlugins(m.config.plugins)
179
+
180
+ ' Prepare render for syncing with tasks
181
+ rootNode.addField("rotorSync", "assocarray", true)
182
+ rootNode.observeFieldScoped("rotorSync", "Rotor_syncCallback")
183
+
184
+ ' Add Field to notify when all tasks have been synced.
185
+ if m.config.readyFieldId <> invalid
186
+ rootNode.addField(m.config.readyFieldId, "boolean", false)
187
+ rootNode.setField(m.config.readyFieldId, false)
188
+ end if
189
+
190
+ ' prepare tasks for syncing
191
+ if config.tasks <> invalid and Rotor.Utils.isArray(config.tasks) and config.tasks.Count() > 0
192
+ m.enableRendering = false ' disable rendering until task sync ready, then call renderQueueFlush()
193
+ taskNames = Rotor.Utils.ensureArray(config.tasks)
194
+ for each taskName in taskNames
195
+ m.setupTaskForSyncing(taskName)
196
+ end for
197
+ else
198
+ m.isReady()
199
+ end if
200
+
201
+
202
+ end sub
203
+
204
+ ' =====================================================================
205
+ ' PUBLIC API - VIEW BUILDER METHODS
206
+ ' =====================================================================
207
+
208
+ ' ---------------------------------------------------------------------
209
+ ' render - Renders widget tree from declarative configuration
210
+ '
211
+ ' @param {object} payload - Widget tree configuration object
212
+ ' @param {object} params - Optional render parameters (callback, callbackScope, etc.)
213
+ '
214
+ public sub render(payload as object, params = {} as object)
215
+ if Rotor.Utils.isValid(params.callback) then params.callbackScope = m
216
+ m.builder.render(payload, params)
217
+ end sub
218
+
219
+ ' ---------------------------------------------------------------------
220
+ ' erase - Removes widget(s) from the tree
221
+ '
222
+ ' @param {dynamic} payload - Widget ID, HID, or array of IDs to remove
223
+ ' @param {boolean} shouldSkipNodePool - If true, removed nodes won't be returned to pool
224
+ ' @param {string} HID - Parent HID context for widget lookup
225
+ '
226
+ public sub erase(payload as dynamic, shouldSkipNodePool = false as boolean, HID = "0" as string)
227
+ m.builder.erase(payload, shouldSkipNodePool, HID)
228
+ end sub
229
+
230
+ ' ---------------------------------------------------------------------
231
+ ' findWidgets - Finds all widgets matching glob pattern
232
+ '
233
+ ' @param {string} searchPattern - Glob pattern (e.g., "header.*", "*.button")
234
+ ' @param {string} HID - Parent HID context for search (default: "0" = root)
235
+ ' @returns {object} Array of matching widget instances
236
+ '
237
+ public function findWidgets(searchPattern as string, HID = "0" as string) as object
238
+ return m.builder.widgetTree.find(searchPattern, HID)
239
+ end function
240
+
241
+ ' ---------------------------------------------------------------------
242
+ ' getWidget - Gets first widget matching search pattern
243
+ '
244
+ ' @param {string} searchPattern - Widget ID or path (e.g., "header", "header.logo")
245
+ ' @param {string} HID - Parent HID context for search (default: "0" = root)
246
+ ' @returns {object} Widget instance or invalid if not found
247
+ '
248
+ public function getWidget(searchPattern as string, HID = "0" as string) as object
249
+ return m.builder.widgetTree.get(searchPattern, HID)
250
+ end function
251
+
252
+ ' ---------------------------------------------------------------------
253
+ ' getTopWidgets - Gets all direct children of root widget
254
+ '
255
+ ' @param {string} matchingPattern - Optional glob pattern to filter results
256
+ ' @returns {object} Array of top-level widget instances
257
+ '
258
+ public function getTopWidgets(matchingPattern = "" as string) as object
259
+ return m.builder.widgetTree.getChildrenWidgets(m.builder.widgetTree.tree, matchingPattern)
260
+ end function
261
+
262
+ ' ---------------------------------------------------------------------
263
+ ' getWidgetByHID - Gets widget by Hierarchical ID
264
+ '
265
+ ' @param {string} HID - Hierarchical ID (e.g., "0.header.logo")
266
+ ' @returns {object} Widget instance or invalid if not found
267
+ '
268
+ public function getWidgetByHID(HID = "0" as string) as object
269
+ return m.builder.widgetTree.getByHID(HID)
270
+ end function
271
+
272
+ ' ---------------------------------------------------------------------
273
+ ' getRootWidget - Gets the root widget (HID "0")
274
+ '
275
+ ' @returns {object} Root widget instance
276
+ '
277
+ public function getRootWidget() as object
278
+ return m.builder.widgetTree.getByHID("0")
279
+ end function
280
+
281
+ ' ---------------------------------------------------------------------
282
+ ' getSubtreeClone - Creates deep clone of widget subtree
283
+ '
284
+ ' @param {string} searchPattern - Widget ID or path to clone
285
+ ' @param {object} keyPathList - Array of key paths to include in clone
286
+ ' @param {string} parentHID - Parent HID context for search
287
+ ' @returns {object} Cloned widget subtree
288
+ '
289
+ public function getSubtreeClone(searchPattern as string, keyPathList = [] as object, parentHID = "0" as string) as object
290
+ return m.builder.widgetTree.getSubtreeClone(searchPattern, keyPathList, parentHID)
291
+ end function
292
+
293
+ ' ---------------------------------------------------------------------
294
+ ' getRootNode - Gets the root SceneGraph node
295
+ '
296
+ ' @returns {object} Root SceneGraph node (roSGNode)
297
+ '
298
+ public function getRootNode() as object
299
+ return m.builder.widgetTree.getRootNode()
300
+ end function
301
+
302
+ ' ---------------------------------------------------------------------
303
+ ' getDispatcher - Gets dispatcher facade by ID
304
+ '
305
+ ' @param {string} dispatcherId - Dispatcher identifier
306
+ ' @returns {object} Dispatcher facade instance
307
+ '
308
+ public function getDispatcher(dispatcherId as string) as object
309
+ return m.dispatcherProvider.getFacade(dispatcherId, GetGlobalAA())
310
+ end function
311
+
312
+ ' ---------------------------------------------------------------------
313
+ ' animator - Gets animator factory for creating animations
314
+ '
315
+ ' @param {dynamic} animatorId - Animator identifier
316
+ ' @returns {object} Animator factory instance
317
+ '
318
+ public function animator(animatorId) as object
319
+ return m.animatorProvider.getFactory(animatorId, m)
320
+ end function
321
+
322
+ ' ---------------------------------------------------------------------
323
+ ' getInfo - Gets framework and device information
324
+ '
325
+ ' @returns {object} Info object containing device details
326
+ '
327
+ public function getInfo() as object
328
+ return m.info
329
+ end function
330
+
331
+ ' ---------------------------------------------------------------------
332
+ ' getNodePoolInfo - Gets node pool statistics
333
+ '
334
+ ' @returns {object} Node pool information (available nodes, usage, etc.)
335
+ '
336
+ public function getNodePoolInfo() as object
337
+ return m.builder.nodePool.getNodePoolInfo()
338
+ end function
339
+
340
+ ' ---------------------------------------------------------------------
341
+ ' presetNodePool - Pre-instantiates nodes for node pool
342
+ '
343
+ ' @param {object} config - Node pool configuration
344
+ ' @returns {object} Node pool instance
345
+ '
346
+ public function presetNodePool(config) as object
347
+ return m.builder.nodePool.presetNodePool(config)
348
+ end function
349
+
350
+ ' =====================================================================
351
+ ' INTERNAL METHODS - INITIALIZATION AND TASK SYNCING
352
+ ' =====================================================================
353
+
354
+ ' ---------------------------------------------------------------------
355
+ ' registerPlugins - Registers plugin instances with the framework
356
+ '
357
+ ' @param {object} plugins - Array of plugin instances to register
358
+ '
359
+ sub registerPlugins(plugins as object)
360
+ m.builder.pluginAdapter.registerPlugins(plugins)
361
+ end sub
362
+
363
+ ' ---------------------------------------------------------------------
364
+ ' initInfo - Initializes device and framework information
365
+ '
366
+ ' Collects device information (graphics platform, model, OS version, locale)
367
+ ' and stores it in m.info for later access.
368
+ '
369
+ sub initInfo()
370
+ ' Device info
371
+ di = CreateObject("roDeviceInfo")
372
+ ' Make it available on framework instance
373
+ m.deviceInfo = di
374
+ ' Generate app info
375
+ m.info.device.append({
376
+ graphicsPlatform: di.GetGraphicsPlatform(), ' ["opengl"|"directfb"]
377
+ modelDisplayName: di.GetModelDisplayName(),
378
+ OSVersion: di.GetOSVersion(),
379
+ currentLocale: di.GetCurrentLocale(), ' Example: "en_US"
380
+ countryCode: di.GetCountryCode() ' A value that indicates the Streaming Store associated with a user's Roku account.
381
+ })
382
+ #if debug
383
+ print `[DEVICEINFO] Graphics Platform: ${m.info.device.graphicsPlatform}`
384
+ print `[DEVICEINFO] modelDisplayName: ${m.info.device.modelDisplayName}`
385
+ print `[DEVICEINFO] OSVersion: ${m.info.device.OSVersion}`
386
+ #end if
387
+ end sub
388
+
389
+ ' ---------------------------------------------------------------------
390
+ ' setupTaskForSyncing - Prepares a task node for cross-thread syncing
391
+ '
392
+ ' Creates and configures a task node with unique ID and root node reference.
393
+ '
394
+ ' @param {string} taskName - Name of the task component to create
395
+ '
396
+ sub setupTaskForSyncing(taskName as string)
397
+ rootNode = m.getRootNode()
398
+ taskNode = rootNode.createChild(taskName)
399
+ taskId = Rotor.Utils.getUUIDHex(16)
400
+
401
+ m.taskNodes[taskId] = taskNode ' collection for later usage
402
+ m.taskOperationalFlag[taskId] = false ' collection for later usage
403
+
404
+ Rotor.Utils.setCustomFields(taskNode, {
405
+ taskId: taskId,
406
+ rootNode: rootNode
407
+ })
408
+
409
+ ' taskNode.observeFieldScoped("rotorSync", "Rotor_syncCallback")
410
+ taskNode.control = "RUN"
411
+ end sub
412
+
413
+ ' ---------------------------------------------------------------------
414
+ ' setupAdditionalTasks - Sets up additional task nodes dynamically
415
+ '
416
+ ' @param {object} taskList - Array of task names to set up
417
+ '
418
+ sub setupAdditionalTasks(taskList as object)
419
+ ' check additional tasks
420
+ if taskList = invalid then return
421
+
422
+ taskNames = Rotor.Utils.ensureArray(taskList)
423
+ for each taskName in taskNames
424
+ m.setupTaskForSyncing(taskName)
425
+ end for
426
+ end sub
427
+
428
+ ' ---------------------------------------------------------------------
429
+ ' areAllTasksOperational - Checks if all task threads are operational
430
+ '
431
+ ' @returns {boolean} True if all tasks have signaled operational status
432
+ '
433
+ function areAllTasksOperational() as boolean
434
+ ' Check if all nodes ready (very basic logic (< future improvement)
435
+ if m.taskOperationalFlag.Count() = 0 then return false
436
+
437
+ for each flag in m.taskOperationalFlag.Items()
438
+ if flag.value <> true then return false
439
+ end for
440
+
441
+ return true
442
+ end function
443
+
444
+ ' ---------------------------------------------------------------------
445
+ ' collectExternalDispatcherRegistrations - Collects external dispatcher info for tasks
446
+ '
447
+ ' Creates a map of which external dispatchers need to be registered in which tasks.
448
+ '
449
+ ' @returns {object} AA mapping taskId to array of dispatcher registration configs
450
+ '
451
+ function collectExternalDispatcherRegistrations() as object
452
+ registrations = {}
453
+ dispatcherIds = m.dispatcherProvider.getAll().keys()
454
+
455
+ for each taskEntry in m.taskNodes.Items()
456
+ taskNode = taskEntry.value
457
+
458
+ for each dispatcherId in dispatcherIds
459
+ dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
460
+ externalTaskNode = dispatcherInstance.taskNode
461
+
462
+ if not taskNode.isSameNode(externalTaskNode)
463
+ m.taskSyncReadyFlag[taskNode.taskId] = false ' collection for later usage
464
+ if registrations[taskNode.taskId] = invalid then registrations[taskNode.taskId] = []
465
+ registrations[taskNode.taskId].push({
466
+ dispatcherId: dispatcherId,
467
+ externalTaskNode: externalTaskNode
468
+ })
469
+ end if
470
+ end for
471
+ end for
472
+
473
+ return registrations
474
+ end function
475
+
476
+ ' ---------------------------------------------------------------------
477
+ ' dispatchExternalDispatcherRegistrations - Sends dispatcher registration to tasks
478
+ '
479
+ ' @param {object} registrations - Map of taskId to dispatcher registration configs
480
+ '
481
+ sub dispatchExternalDispatcherRegistrations(registrations as object)
482
+ for each taskId in registrations
483
+ taskNode = m.taskNodes[taskId]
484
+ taskNode.setField("rotorSync", {
485
+ type: Rotor.Const.ThreadSyncType.REGISTER_EXTERNAL_DISPATCHER,
486
+ externalDispatcherList: registrations[taskId]
487
+ })
488
+ end for
489
+ end sub
490
+
491
+ ' ---------------------------------------------------------------------
492
+ ' haveAllTasksSynced - Checks if all tasks have completed syncing
493
+ '
494
+ ' @returns {boolean} True if all tasks have synced successfully
495
+ '
496
+ function haveAllTasksSynced() as boolean
497
+ ' Check if all nodes ready (very basic logic (< future improvement)
498
+ if m.taskSyncReadyFlag.Count() = 0 then return false
499
+
500
+ for each flag in m.taskSyncReadyFlag.Items()
501
+ if flag.value <> true then return false
502
+ end for
503
+
504
+ return true
505
+ end function
506
+
507
+ ' ---------------------------------------------------------------------
508
+ ' handleTaskSyncing - Handles task syncing phase
509
+ '
510
+ ' Registers external dispatchers and sets up cross-thread communication.
511
+ '
512
+ ' @param {object} sync - Sync payload from task thread
513
+ '
514
+ sub handleTaskSyncing(sync as object)
515
+ taskNode = sync.taskNode
516
+
517
+ m.setupAdditionalTasks(sync.tasks)
518
+
519
+ ' register incoming dispatchers
520
+ dispatcherIds = sync.dispatcherIds
521
+ if dispatcherIds <> invalid
522
+ m.dispatcherProvider.registerExternalDispatchers(dispatcherIds, taskNode)
523
+ end if
524
+
525
+ ' update task status
526
+ m.taskOperationalFlag[taskNode.taskId] = true
527
+
528
+ if not m.areAllTasksOperational() then return
529
+
530
+ ' if allTasksRunning then create external dispatchers in all tasks
531
+ registrations = m.collectExternalDispatcherRegistrations()
532
+ if registrations.Count() > 0
533
+ m.dispatchExternalDispatcherRegistrations(registrations)
534
+ else
535
+ m.isReady()
536
+ end if
537
+ end sub
538
+
539
+ ' ---------------------------------------------------------------------
540
+ ' handleTaskSynced - Handles task synced confirmation
541
+ '
542
+ ' Marks task as synced and checks if all tasks are ready.
543
+ '
544
+ ' @param {string} taskId - ID of the task that has synced
545
+ '
546
+ sub handleTaskSynced(taskId as string)
547
+ m.taskSyncReadyFlag[taskId] = true
548
+ if m.haveAllTasksSynced()
549
+ m.isReady()
550
+ end if
551
+ end sub
552
+
553
+ ' ---------------------------------------------------------------------
554
+ ' isReady - Notifies framework ready state via multiple channels
555
+ '
556
+ ' Flushes render queue, triggers onReady callback, and sets ready field.
557
+ '
558
+ sub isReady()
559
+ m.enableRendering = true
560
+ m.builder.renderQueueFlush()
561
+
562
+ ' Notify ready state on rotorSync field
563
+ m.getRootNode().setField("rotorSync", {
564
+ type: Rotor.Const.ThreadSyncType.SYNC_COMPLETED
565
+ })
566
+
567
+ ' Notify ready state by calling onReady fn if configured
568
+ if Rotor.Utils.isFunction(m.config.onReady)
569
+ Rotor.Utils.callbackScoped(m.config.onReady, GetGlobalAA())
570
+ end if
571
+
572
+ ' Notify ready state on rotorSync field
573
+ if m.config.readyFieldId <> invalid
574
+ m.getRootNode().setField(m.config.readyFieldId, true)
575
+ end if
576
+ end sub
577
+
578
+ ' =====================================================================
579
+ ' CLEANUP
580
+ ' =====================================================================
581
+
582
+ ' ---------------------------------------------------------------------
583
+ ' destroy - Cleans up framework resources
584
+ '
585
+ ' Destroys all task nodes, unregisters observers, and cleans up subsystems.
586
+ '
587
+ public sub destroy()
588
+
589
+ rootNode = m.getRootNode()
590
+ for each taskId in m.taskNodes
591
+ taskNode = m.taskNodes[taskId]
592
+ taskNode.setField("rotorSync", {
593
+ type: Rotor.Const.ThreadSyncType.DESTROY
594
+ })
595
+ rootNode.removeChild(taskNode)
596
+ end for
597
+
598
+ m.taskNodes.Clear()
599
+
600
+ rootNode.unobserveFieldScoped("rotorSync")
601
+
602
+ ' destroy subsystems
603
+ m.builder.destroy()
604
+ m.dispatcherProvider.destroy()
605
+ m.i18nService.destroy()
606
+
607
+ ' remove references
608
+ m.animatorProvider = invalid
609
+ globalScope = GetGlobalAA()
610
+ globalScope.rotor_framework_helper = {
611
+ frameworkInstance: invalid
612
+ }
613
+ m.config.rootNode = invalid
614
+
615
+ m.info.Clear()
616
+ m.deviceInfo = invalid
617
+
618
+ end sub
619
+
620
+ end class
621
+
622
+ ' =====================================================================
623
+ ' syncCallback - Global callback for cross-thread synchronization
624
+ '
625
+ ' Global callback function for cross-thread synchronization messages.
626
+ '
627
+ ' This function is called when the rotorSync field changes on the root node.
628
+ ' It routes sync messages to the appropriate framework handler based on type.
629
+ '
630
+ ' @param {roSGNodeEvent} msg - SceneGraph observer message containing sync data
631
+ ' =====================================================================
632
+ sub syncCallback(msg)
633
+ ' extraInfo = msg.GetInfo()
634
+ ' fieldId = msg.getField()
635
+ sync = msg.getData() ' @type:AA
636
+
637
+ framework = GetGlobalAA().rotor_framework_helper.frameworkInstance
638
+ if framework = invalid then return
639
+
640
+ taskNode = sync.taskNode
641
+
642
+ if sync.type = Rotor.Const.ThreadSyncType.TASK_SYNCING
643
+ framework.handleTaskSyncing(sync)
644
+ else if sync.type = Rotor.Const.ThreadSyncType.TASK_SYNCED
645
+ framework.handleTaskSynced(taskNode.taskId)
646
+ else if sync.type = Rotor.Const.ThreadSyncType.SYNC_COMPLETED
647
+ ' Do nothing
648
+ end if
649
+
650
+ end sub
651
+
652
+
653
+
654
+ end namespace