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.
- package/LICENSE.md +21 -0
- package/README.md +120 -0
- package/package.json +59 -0
- package/src/source/RotorFramework.bs +654 -0
- package/src/source/RotorFrameworkTask.bs +278 -0
- package/src/source/base/BaseModel.bs +52 -0
- package/src/source/base/BasePlugin.bs +48 -0
- package/src/source/base/BaseReducer.bs +184 -0
- package/src/source/base/BaseStack.bs +92 -0
- package/src/source/base/BaseViewModel.bs +124 -0
- package/src/source/base/BaseWidget.bs +104 -0
- package/src/source/base/DispatcherCreator.bs +193 -0
- package/src/source/base/DispatcherExternal.bs +260 -0
- package/src/source/base/ListenerForDispatchers.bs +246 -0
- package/src/source/engine/Constants.bs +74 -0
- package/src/source/engine/animator/Animator.bs +334 -0
- package/src/source/engine/builder/Builder.bs +213 -0
- package/src/source/engine/builder/NodePool.bs +236 -0
- package/src/source/engine/builder/PluginAdapter.bs +139 -0
- package/src/source/engine/builder/PostProcessor.bs +331 -0
- package/src/source/engine/builder/Processor.bs +156 -0
- package/src/source/engine/builder/Tree.bs +278 -0
- package/src/source/engine/builder/TreeBase.bs +313 -0
- package/src/source/engine/builder/WidgetCreate.bs +322 -0
- package/src/source/engine/builder/WidgetRemove.bs +72 -0
- package/src/source/engine/builder/WidgetUpdate.bs +113 -0
- package/src/source/engine/providers/Dispatcher.bs +72 -0
- package/src/source/engine/providers/DispatcherProvider.bs +95 -0
- package/src/source/engine/services/I18n.bs +169 -0
- package/src/source/libs/animate/Animate.bs +753 -0
- package/src/source/libs/animate/LICENSE.txt +21 -0
- package/src/source/plugins/DispatcherProviderPlugin.bs +127 -0
- package/src/source/plugins/FieldsPlugin.bs +180 -0
- package/src/source/plugins/FocusPlugin.bs +1522 -0
- package/src/source/plugins/FontStylePlugin.bs +159 -0
- package/src/source/plugins/ObserverPlugin.bs +548 -0
- package/src/source/utils/ArrayUtils.bs +495 -0
- package/src/source/utils/GeneralUtils.bs +181 -0
- 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
|