• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mobalazs / rotor-framework / 19980247662

06 Dec 2025 12:52AM UTC coverage: 85.611% (-0.6%) from 86.229%
19980247662

Pull #10

github

web-flow
Merge f2448421e into 2846dc2eb
Pull Request #10: Feat/url transfer support

2 of 15 new or added lines in 2 files covered. (13.33%)

2 existing lines in 2 files now uncovered.

1773 of 2071 relevant lines covered (85.61%)

1.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.68
/src/source/RotorFrameworkTask.bs
1
' =========================================================================
2
' ▗▄▄▖  ▗▄▖▗▄▄▄▖▗▄▖ ▗▄▄▖     ▗▄▄▄▖▗▄▄▖  ▗▄▖ ▗▖  ▗▖▗▄▄▄▖▗▖ ▗▖ ▗▄▖ ▗▄▄▖ ▗▖ ▗▖
3
' ▐▌ ▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌ ▐▌    ▐▌   ▐▌ ▐▌▐▌ ▐▌▐▛▚▞▜▌▐▌   ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌▗▞▘
4
' ▐▛▀▚▖▐▌ ▐▌ █ ▐▌ ▐▌▐▛▀▚▖    ▐▛▀▀▘▐▛▀▚▖▐▛▀▜▌▐▌  ▐▌▐▛▀▀▘▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▛▚▖
5
' ▐▌ ▐▌▝▚▄▞▘ █ ▝▚▄▞▘▐▌ ▐▌    ▐▌   ▐▌ ▐▌▐▌ ▐▌▐▌  ▐▌▐▙▄▄▖▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▌ ▐▌
6
' Rotor Framework™
7
' Version 0.4.3
8
' © 2025 Balázs Molnár — MIT License
9
' =========================================================================
10

11
' constants
12
import "engine/Constants.bs"
13

14
' engine
15
import "engine/providers/DispatcherProvider.bs"
16
import "engine/providers/Dispatcher.bs"
17

18
' base classes
19
import "base/DispatcherCreator.bs"
20
import "base/DispatcherExternal.bs"
21
import "base/BaseReducer.bs"
22
import "base/BaseModel.bs"
23
import "base/BaseStack.bs"
24

25
' utils
26
import "utils/GeneralUtils.bs"
27
import "utils/NodeUtils.bs"
28
import "utils/ArrayUtils.bs"
29

30
namespace Rotor
31
    ' =====================================================================
32
    ' FrameworkTask - Task thread version of Rotor Framework for MVI
33
    '
34
    ' Task thread version of the Rotor Framework that enables cross-thread MVI
35
    ' (Model-View-Intent) architecture. This class manages state and dispatchers
36
    ' on a separate task thread, allowing heavy computations and state management
37
    ' to run off the render thread for better performance.
38
    '
39
    ' Configuration:
40
    '   - tasks (array, optional): List of additional task node names to synchronize with.
41
    '                             Allows multiple task threads to communicate and share
42
    '                             dispatchers across different threads.
43
    '
44
    ' USAGE NOTES:
45
    ' The FrameworkTask must be instantiated in the task's init() function and the sync()
46
    ' method MUST be called at the end of your task function to establish the message loop.
47
    '
48
    ' IMPORTANT: The sync() method creates an infinite loop that handles cross-thread
49
    ' communication and dispatcher synchronization. This call should be the LAST statement
50
    ' in your task function, after all dispatcher initialization.
51
    '
52
    ' Example:
53
    '   File: MyTask.task.bs
54
    '   import "pkg:/source/RotorFrameworkTask.bs"
55
    '   import "pkg:/source/MyDispatcher.bs"
56
    '
57
    '   sub init()
58
    '       m.top.functionName = "task"
59
    '       m.appFw = new Rotor.FrameworkTask({
60
    '           tasks: ["AnotherTask"]
61
    '       })
62
    '   end sub
63
    '
64
    '   sub task()
65
    '       m.fooDispatcher = createFooDispatcher()
66
    '       m.barDispatcher = createBarDispatcher()
67
    '       m.appFw.sync()
68
    '   end sub
69
    ' =====================================================================
70
    class FrameworkTask
71

72
        name = "Rotor Framework"
73
        version = "0.4.3"
74

75
        config = {
76
            tasks: invalid, ' optional
77
            debug: {
78
            }
79
        }
80

81
        threadType = Rotor.Const.ThreadType.TASK
82

83
        keepAlive = true
84

85
        ' helper vars
86
        taskNode as object
87
        dispatcherProvider as object
88
        port as object
89
        asyncTransferRegistry = {} ' transferId -> { dispatcherId, context }
90

91
        ' ---------------------------------------------------------------------
92
        ' new - Initializes the FrameworkTask instance
93
        '
94
        ' Sets up the task thread dispatcher provider, message port, and
95
        ' global framework helper for cross-thread communication.
96
        '
97
        ' @param {object} config - Configuration object (see class documentation)
98
        '
99
        sub new(config = {} as object)
100

101
            Rotor.Utils.deepExtendAA(m.config, config)
1✔
102

103
            globalScope = GetGlobalAA()
1✔
104
            globalScope.rotor_framework_helper = { ' this give to dispatcher instance the possibility to self-register
1✔
105
                threadType: m.threadType,
106
                frameworkInstance: m
107
            }
108
            m.taskNode = globalScope.top
1✔
109

110
            m.dispatcherProvider = new Rotor.DispatcherProvider(m.threadType)
1✔
111

112
            m.taskNode.addField("rotorSync", "assocarray", true)
1✔
113
            m.port = CreateObject("roMessagePort")
1✔
114
            m.taskNode.observeFieldScoped("rotorSync", m.port)
1✔
115

116
        end sub
117

118
        ' =====================================================================
119
        ' PUBLIC API
120
        ' =====================================================================
121

122
        ' ---------------------------------------------------------------------
123
        ' getDispatcher - Gets dispatcher facade by ID
124
        '
125
        ' @param {string} dispatcherId - Dispatcher identifier
126
        ' @returns {object} Dispatcher facade instance
127
        '
128
        public function getDispatcher(dispatcherId as string) as object
129
            return m.dispatcherProvider.getFacade(dispatcherId, GetGlobalAA())
×
130
        end function
131

132
        ' ---------------------------------------------------------------------
133
        ' registerAsyncTransfer - Registers an async transfer with dispatcher context
134
        '
135
        ' This method associates a roUrlTransfer identity with a dispatcher ID and
136
        ' optional context data. When the transfer completes and sends a roUrlEvent,
137
        ' the framework will route it to the correct dispatcher's asyncReducerCallback.
138
        '
139
        ' @param {string} transferId - The transfer.GetIdentity().ToStr() value
140
        ' @param {string} dispatcherId - Dispatcher ID that initiated the transfer
141
        ' @param {object} context - Optional user data to pass back in callback
142
        '
143
        public sub registerAsyncTransfer(transferId as string, dispatcherId as string, context = invalid as dynamic)
NEW
144
            m.asyncTransferRegistry[transferId] = {
×
145
                dispatcherId: dispatcherId,
146
                context: context
147
            }
148
        end sub
149

150
        ' ---------------------------------------------------------------------
151
        ' sync - Starts the message loop for cross-thread communication
152
        '
153
        ' IMPORTANT: This method creates an infinite loop that handles:
154
        '   - Intent dispatching from render thread
155
        '   - External dispatcher registration
156
        '   - State change notifications
157
        '   - Async reducer callbacks
158
        '
159
        ' This method MUST be the last call in your task function, as it
160
        ' blocks execution until the framework is destroyed.
161
        '
162
        sub sync()
163
            m.notifySyncStatus(Rotor.Const.ThreadSyncType.TASK_SYNCING)
1✔
164

165
            keepAlive = true
1✔
166

167
            while true and keepAlive = true
1✔
168
                msg = wait(0, m.port)
1✔
169
                if msg <> invalid
3✔
170
                    msgType = type(msg)
1✔
171
                    if msgType = "roSGNodeEvent"
3✔
172
                        fieldId = msg.getField()
1✔
173

174
                        if fieldId = "rotorSync"
3✔
175

176
                            sync = msg.getData() ' @type:AA
1✔
177

178
                            if sync.type = Rotor.Const.ThreadSyncType.DISPATCH
2✔
179

180

181
                                dispatcherId = sync.payload.dispatcherId
1✔
182
                                intent = sync.payload.intent
1✔
183
                                dispatcherInstance = m.dispatcherProvider.stack.LookupCI(dispatcherId)
1✔
184

185
                                ' taskIntent = Rotor.Utils.deepCopy(intent)
186
                                dispatcherInstance.dispatch(intent)
1✔
187

188
                            else if sync.type = Rotor.Const.ThreadSyncType.REGISTER_EXTERNAL_DISPATCHER
2✔
189

190
                                for each item in sync.externalDispatcherList
×
191
                                    m.dispatcherProvider.registerExternalDispatchers(item.dispatcherId, item.externalTaskNode)
×
192
                                end for
193

194
                                m.notifySyncStatus(Rotor.Const.ThreadSyncType.TASK_SYNCED)
×
195

196
                            else if sync.type = Rotor.Const.ThreadSyncType.DESTROY
3✔
197

198
                                keepAlive = false
1✔
199

200
                            end if
201
                        else
202

×
203
                            data = msg.getData()
×
204
                            extraInfo = msg.GetInfo() ' Info AA passed during observeFieldScoped
×
205

206
                            if extraInfo?.dispatcherId <> invalid and m.dispatcherProvider.get(extraInfo?.dispatcherId) <> invalid
×
207
                                ' Catch by dispatcherId
208
                                m.dispatcherProvider.get(extraInfo?.dispatcherId).asyncReducerCallback(msg)
×
209
                            else
×
210
                                dispatcherId = fieldId
×
211
                                dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
×
212
                                dispatcherInstance.notifyListeners(data)
×
213
                            end if
214

215
                        end if
NEW
216
                    else if msgType = "roUrlEvent"
×
217
                        ' Handle async transfer responses
NEW
218
                        transferId = msg.GetSourceIdentity().ToStr()
×
219

NEW
220
                        if m.asyncTransferRegistry.DoesExist(transferId)
×
NEW
221
                            transferData = m.asyncTransferRegistry[transferId]
×
NEW
222
                            dispatcherId = transferData.dispatcherId
×
223

NEW
224
                            dispatcherInstance = m.dispatcherProvider.get(dispatcherId)
×
NEW
225
                            if dispatcherInstance <> invalid
×
226
                                ' Route to dispatcher with wrapped message
NEW
227
                                dispatcherInstance.asyncReducerCallback({
×
228
                                    type: "roUrlEvent",
229
                                    event: msg,
230
                                    context: transferData.context
231
                                })
232
                            end if
233

234
                            ' Cleanup registry entry
NEW
235
                            m.asyncTransferRegistry.delete(transferId)
×
236
                        end if
237
                    end if
238
                end if
239
            end while
240
            m.destroy()
1✔
241
        end sub
242

243
        ' =====================================================================
244
        ' INTERNAL METHODS
245
        ' =====================================================================
246

247
        ' ---------------------------------------------------------------------
248
        ' notifySyncStatus - Notifies render thread of sync status
249
        '
250
        ' Sends sync status message to render thread via rotorSync field.
251
        '
252
        ' @param {string} status - Sync status type (TASK_SYNCING or TASK_SYNCED)
253
        '
254
        sub notifySyncStatus(status as string)
255

256
            payload = {
1✔
257
                type: status,
258
                taskNode: m.taskNode
259
            }
260

261
            if status = Rotor.Const.ThreadSyncType.TASK_SYNCING
3✔
262
                payload.append({
1✔
263
                    dispatcherIds: m.dispatcherProvider.stack.Keys(),
264
                    tasks: m.config.tasks
265
                })
266
            end if
267

268
            m.taskNode.rootNode.setField("rotorSync", payload)
1✔
269

270
        end sub
271

272
        ' ---------------------------------------------------------------------
273
        ' addObserver - Adds field observer to task thread message port
274
        '
275
        ' @param {string} fieldId - Field name to observe
276
        ' @param {object} node - SceneGraph node to observe
277
        '
278
        sub addObserver(fieldId as string, node)
279
            node.observeFieldScoped(fieldId, m.port)
×
280
        end sub
281

282
        ' ---------------------------------------------------------------------
283
        ' removeObserver - Removes field observer from node
284
        '
285
        ' @param {string} fieldId - Field name to stop observing
286
        ' @param {object} node - SceneGraph node to unobserve
287
        '
288
        sub removeObserver(fieldId as string, node)
289
            node.unobserveFieldScoped(fieldId)
×
290
        end sub
291

292
        ' =====================================================================
293
        ' CLEANUP
294
        ' =====================================================================
295

296
        ' ---------------------------------------------------------------------
297
        ' destroy - Cleans up task thread resources
298
        '
299
        ' Destroys dispatcher provider and clears global framework helper.
300
        '
301
        public sub destroy()
302
            m.asyncTransferRegistry.clear()
1✔
303
            m.dispatcherProvider.destroy()
1✔
304

305
            globalScope = GetGlobalAA()
1✔
306
            globalScope.rotor_framework_helper = {
1✔
307
                frameworkInstance: invalid
308
            }
309

310
            m.taskNode.rootNode = invalid
1✔
311
            m.taskNode = invalid
1✔
312
        end sub
313

314
    end class
315

316
end namespace
317

318

319

320

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc