So here is what i am thinking about - writing a utility function that could be used to handle asynchronous calls in RSG, specifically because workhorses like roUrlTransfer, parseXml(), parseJson(), readAsciiFile(), roAppInfo, roChannelStore, roRegistrySection, roStreamSocket/roDatagramSocket are bared from use in the render thread. The prescription so far has been "ad hoc subclass Task node for each custom functionality, then tickle its .control to RUN and observe a field if it's ready" (and tough luck if >1 task instances are started concurrently, so try not to get confused what's listening on).
Instead - i am thinking - we could get ourselves a somewhat more generic solution as a wrapper around a Task node that takes care of the complications and does not require a new subclassing every time a new long-lasting operation is needed. To be clear, this is not something i imply RokuCo should - rather, we developers could do in our code.
Here is conceptually what i have in mind:
sub init() ... async_call("some_fn_name", params, callback_fn, token) ... end sub
function some_fn_name(params) ... return result end function
function callback_fn(result, token) 'handle the result, possibly guided by the token (e.g. what state we are in)' ... end function
So async_call() when called would take care of allocating a Task subclass, run "some_fn_name" into it and listen for completion, then call the callback_fn with the result and the supplementary token. The app developer's part to this is to write two functions - the long-lasting "some_fn_name" worker, whose protocol only includes to get input from `input` field and store result in `output` field. The callback_fn protocol in turn will be called when the result is ready - and it can use the optional token as guidance where in the app flow we are.
Here is sample use:
sub init() ... async_call("regRead", "config", my_callback, "reg_config") end sub
sub my_callback(result, token) if token = "reg_config": m.config = result async_call("fetch_URL", m.config.URL + "/getCategories", my_callback, "fetch_categories") elseif token = "fetch_categories": async_call("parse_JSON", result, my_callback, "parse_categories") elseif token = "parse_categories": m.categories = result ... else: ? "PANIC: unknown state", token STOP end if end sub
In this regRead is utility function that reads from registry, fetch_URL gets a URL, and so on - all of them are simpel to write by using synchronous calls but that won't be an issue since they will be ran in a task thread. Such utility functions can be written once and kept part of developer's toolbelt. The only custom thing here is the my_callback function (which here is written to chain them together in a sequence - but potentially it could be more brached tree with multiple callback handlers).
Makes sense? Any thoughts? Is there a deadly flaw in my concept?
PS. There is one thing that bothers me - yet it's not with my idea per se but rather with the Task behavior, see /sdkdoc/Scene Graph Threads#Task Node Changes In Firmware Version 7.2 - so basically every time an async operation is to be spawn, the "mother thread" (here the render thread) is getting plucked/robbed from any and all non-RSG-type local variables (ports, functions - everything that's not on the blessed list), is that so? That rings to me like - to borrow Einstein's expression - a "spooky action at a distance". Say some method/event handler relies on a roMessagePort from `m` - in the meantime another handler calls async/task functionality and - poof!, the 1st one is royally unscrewed. "How is this even legal?!"
An interesting idea you have here. I did try using a variable to pass along in the callback for an observer and it fails - I would think this ought to work but it doesn't. ie -
m.b1=m.searchscreen.getChild(1) 'reference to a button that has focus m.b1.id="search" m.b1.text="Search" m.b1.showFocusFootprint=FALSE m.b1.translation=[164,670] m.b1.observeField("buttonSelected","ButtonHandler(m.b1.id)") m.b1.SetFocus(TRUE)
Function ButtonHandler(Name As String) m.b1.unobserveField("buttonSelected") Print Name End Function
m.b1=m.searchscreen.getChild(1) 'reference to a button that has focus m.b1.id="search" m.b1.text="Search" m.b1.showFocusFootprint=FALSE m.b1.translation=[164,670] m.b1.observeField("buttonSelected","ButtonHandler") m.b1.SetFocus(TRUE)
Function ButtonHandler() m.b1.unobserveField("buttonSelected") Print "Button Selected" End Function
Then the callback works. But still, it would be nice to be able to minimize extra code, enhance program flow, and use one callback function for anything needing to be done IMO.
"destruk" wrote: I did try using a variable to pass along in the callback for an observer and it fails - I would think this ought to work but it doesn't. ... m.b1.observeField("buttonSelected","ButtonHandler(m.b1.id)")
Yeah... but no. Kudos for the thinking and trying it - but that's not how observeField works, it does not take code to `eval()` as the second argument, just a name of function to late-bind by name.
I's an interesting idea though, say allow to pass there say anonymous function instead of string...