[in continuation of my previous musing at
viewtopic.php?f=34&t=98511]
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?!"