PDA

View Full Version : Waiting for a client side database node to appear



phantomwhale
August 26th, 2012, 09:00
I hit an odd problem today, which I can't see solved in other rulesets - although I'm not sure equally why I've not seen it before.

I am adding to the SavageWorlds minitracker (for the new chase tool) - so this is a client-shared windowlist.

Each instance (window) of this windowlist has a custom control which creates it's own database node, and then watches for any children being added to it - it's being used for storing a hand of cards.

So in my onInit() method I was trying to do the following :



dbNode = window.getDatabaseNode().createChild(getName())
dbNode.onChildUpdate = update


This is a pattern I have used elsewhere, and it usually works. Only the "host" session actually succeeds to create a child database node - the "client" version of the control just discovers the database node already exists (which is good, as they don't have access to create nodes anyway) and hooks into the onChildUpdate event too.

But for this particular control, I'm getting an error, saying that dbNode is nil. This doesn't happen when I share the minitracker (e.g. the window containing the windowlist), but it DOES happen when I add a new element to the windowlist. So it seems the client is running the onInit() method of the control in the newly created window of the windowlist before the host has had time to copy the new database node over to the client.

Thinking about it - none of this should surprise me - I think the timeline looks like this:


A new database node for the windowlist is created on the host (via a GUI command)
A "windowlist window" instance is created on the host
The new database node is copied client side
A new windowlist window is created client side.
As the new host window is loaded, it creates one of the custom controls
The custom control on the host runs onInit() and creates it's own database node
As the new client window is loaded, it creates one of the custom controls
The custom control on the client runs onInit() and can't find a database node
The database node for the custom control is copied over - too late for the onInit() method to hook onto it


Or, in other words, we have the creation of a database node (the windowlist window) triggering the creation of another database node (the custom control) within the onInit() method. This means, unlike the built in database backed controls (stringfield, numberfield etc...), this second database node is not in place when the client looks for it.

So - onto the (messy) workaround ! I've ended up with the following code within the onInit block, which I might add works :



if User.isHost() then
dbNode = window.getDatabaseNode().createChild(getName())
dbNode.onChildUpdate = update
else
local f = function()
dbNode = window.getDatabaseNode().getChild(getName())
if dbNode then
dbNode.onChildUpdate = update
end
end
f()
if not dbNode then
window.getDatabaseNode().onChildAdded = f
end
end


This deals with both the scenario above (where the dbnode replicates onto the client too late) and the situation where the dbnode has already arrived.

On top of this, I've had to adapt the code to deal with the dbNode variable maybe not being set. Not too bothered about this, and see that this coding style is often used in the 4E ruleset, although I get the impression that there it's saying "if the dbNode isn't set, it's never going to be" which is different to this.

So have I made a mountain out of a molehill ? Or is this just one of those things from having custom DB node controls within windowlists ?! Either way, it's a level of complexity I've not needed before, which makes me suspicious I've done something stupid...

-PW-

Zeus
August 26th, 2012, 09:41
Yep, I have hit this issue in the past, FGII's asynchronous database update approach means you can't guarantee exactly when the database nodes are going to be visible to clients.

It would be nice to get a DB.commit() method; or a means of checkpointing the database between hosts + clients moving forward. In the meantime, the approach your using looks correct to me, your trapping host and client access separately and not relying on createChild() for the the clients; I have seen and used the approach before as in 4E (see 4E CT, PartySheet and Fortune Cards).

Alternatively, in situations like your example above where you have dynamic runtime controls, in the past I have also successfully used subwindow controls to hold the dynamic controls (enabling access to the controls and their database nodes) just after initialisation has occurred but before the user can do anything i.e. onSubwindowInstantiated(), since all controls must be rendered and bound when initialisation finishes, this allows you to apply a second level of trapping for the existence of the database nodes and manage accordingly.

phantomwhale
August 26th, 2012, 12:05
Many many thanks Dr. Z - needed a good sanity check on that one.

Have tidied it up into my NodeManager script, as turns out I need it in a couple more places after all. And in the interest of sharing, that script is:



function loadChild(nodeSource, sField, sFieldType, fCallback)
if not nodeSource or not sField then
return nil;
end

fCallback = fCallback or function(node) return node; end

if not isReadOnly(nodeSource) then
if sFieldType then
fCallback(nodeSource.createChild(sField, sFieldType));
else
fCallback(nodeSource.createChild(sField));
end
else
local f = function()
if nodeSource.getChild(sField) then
fCallback(nodeSource.getChild(sField))
end
end
if not f() then
nodeSource.onChildAdded = f
end
end
end


This then turns my main class onInit script into a much neater:



local dbNode = nil

function onInit()
--[[ other stuff ... ]]

NodeManager.loadChild(window.getDatabaseNode(), getName(), nil, onNodeCreate)
end

-- Store the database node reference away for later use in the dbNode variable.
-- Register onChildUpdate hook for database node.
function onNodeCreate(node)
dbNode = node
dbNode.onChildUpdate = update
end


Which I'm quite happy with for now :)

Moon Wizard
September 2nd, 2012, 20:16
Yeah, this is an issue I've run into a few times. If you look at the code for the stringcycler and iconcycler custom templates in the 3.5E/4E ruleset, you will see similar code.

The challenge is that FG database updates occur in series from the top-down. So, when a new record is created, the top level DB update is placed in the comm queue. Then, as the children DB nodes are created, they are placed in the queue behind the top level update. Finally, any script created nodes in onInit are after that.

My initial thought is to make all database events (creation, update, deletion) be stored separately; and sent as part of a single XML-style update to the host/client instead of individually. This would allow all initialization (window/control/script) to occur before any database updates are sent. Of course, the flip side is that I would also need a way to apply all the updates at once, before triggering any database/control events based on database updates. I thought about changing the behavior in v2.9, but this requires a total rewrite of the FG database synchronization and communication code. Also, I have no idea how it would impact assumptions in all the various rulesets already created. I could do the work and not be able to release, due to backward compatibility issues. It's such a drastic change to the data flow and a big chunk of work to boot, that I decided against it in the short term.

Regards,
JPG