PDA

View Full Version : 3.5 damage pathways. (drag/drop)



Ken L
March 16th, 2018, 23:33
There's this annoying bug I have in one of my extensions that I haven't been able isolate. It's an FG crashing bug so 'crash-n-fix' has put a large dent in my time allocation to fixing it.

It has to do with the damage pathways for drag-n-drop damage actions.

What's the main difference between drag-dropping a chat damage roll to a CT entry vs a CT connected map token?

I have an extension that given a certain amount of damage, a token will be replaced with another with the CT's token reference properly changing as well. The issue is that if the damage is dropped on the token, I suspect this old reference is being accessed somehow so FG just up and crashes rather than throwing an error.

Compared to drag-dropping this damage on the CT where everything performs as normal when the damage threshold for changing the token is reached.

Does anyone know the difference between these two pathways?

Moon Wizard
March 17th, 2018, 22:25
The drag and drop to the token goes through TokenManager.onDrop -> CombatManager.onDrop
The drag and drop on the combat tracker goes through ct.lua:onDrop -> CombatManager.onDrop

Scripts can cause crashes if they delete an object that is the calling object on an event. I try really hard to move all script calls to absolutely last in the code chain for events, but I've had to track these down as they come up.

Also, in this case, it also may be a case of multiple token event handler registrations, where the ordering of script registrations may matter. Currently, the client processes all registered handlers, regardless of what happens in the scripts. It's best to hook into the existing event handlers, if possible.

How are you triggering your damage threshold check when the onDrop event happens? Basically, I'd have to review your code call chain, and most likely suggest workarounds. I may be able to adjust the client code to help a bit, but tracking deletions mid-stream during multiple event handlers is non-trivial and not something I have priority to add at this point.

Regards,
JPG

Ken L
April 6th, 2018, 10:17
Sorry I wasn't able to respond sooner, there's been a ton on my plate lately. For my time reserved for RPGs (and their related tools) I've been tied up with campaign prep as my group is in the 'grand finale' stage. This current bug is for my PF one-shot group so it's lower on my priority docket.

I've done some poking and I've figured out it's not drag damage from an action source, ie: PC damage action button drags as well as attack damage text from both PC and NPC work fine if dropped directly on the token. Targeted/multi-targeted damage always works so that's not a point of issue.

The issue seems to arise from damage from the chat window dragged atop the token.

I've been doing a bit of reverse tracing when I can, basically just a logical trace without creating an extension to 'watermark' log messages along the execution path. I may have to eventually do that on a vanilla 3.5 ruleset and then layering in this extension; issue is if FG crashes, which it does for this particular instance, all log messages are essentially pointless. I think I may have a few hours this weekend to perform the 'watermark' technique to at least trace the function paths for a vanilla implementation so that should put some wind in the sail.

From what I've dug up, it has to do with the draginfo from the chat vs what comes from the attack text or damage action button, and the timing of the token switch that you mentioned above.

The token switch occurs during the health update DB hook that occurs in the 3.5 token_manager2 which already changes the wound text and colors in the CT, so no additional hooks are used. Prior to passing the token reference to those vanilla functions, I check and swap the token so the vanilla operations all use the same new reference so there's no race condition that exists. If it did, it would occur in the other two damage operations (attack text, action button) sporadically as opposed to specifically from damage from any source, but dragged from the chat to the token.

I'm guessing I need to look at some kind of 'chat entry' like object and view the onDrag there.

From the action buttons on the PC sheet, it originates from onDamageAction(draginfo) from campaign/scripts/char_weapon.lua which rolls on to invoke ActionDamage.performRoll(draginfo, rActor, rDamage) in scripts/manager_action_damage.lua.

At this point it gets interesting as it calls into CoreRPG's ActionManager (scripts/manager_actions.lua) where it follows through with performAction -> performMultiAction and forks either into a 'direct action' if there's no drag info (the output only on chat case) and the case if the draginfo is non-null in which it encodes actor information for delivery.

For our chat-only output, we follow actionDirect (with no specified targets) -> actionRoll -> applyModifiersandRoll -> roll

At this point the trace ends with:



local rThrow = buildThrow(rSource, vTargets, rRoll, bMultiTarget);
Comm.throwDice(rThrow);


Which is outputted to the chat window.

From the desktop/scripts/chat_window.lua, on drag invokes 'onChatDragStart(draginfo)' from scripts/manager_actions, which eventually ends at the onDrop call for the specified token.



function onDrop(tokenCT, draginfo)
local nodeCT = CombatManager.getCTFromToken(tokenCT);
if nodeCT then
CombatManager.onDrop("ct", nodeCT.getNodeName(), draginfo);
else
if draginfo.getType() == "targeting" then
ChatManager.SystemMessage(Interface.getString("ct_error_targetingunlinkedtoken"));
end
end
end


And it follows through to manager_combat.lua 's onDrop:


function onDrop(nodetype, nodename, draginfo)
local rSource = ActionsManager.decodeActors(draginfo);
local rTarget = ActorManager.getActor(nodetype, nodename);
if rTarget then
local sDragType = draginfo.getType();

-- Faction changes
if sDragType == "combattrackerff" then
if User.isHost() then
DB.setValue(ActorManager.getCTNode(rTarget), "friendfoe", "string", draginfo.getStringData());
return true;
end
-- Actor targeting
elseif sDragType == "targeting" then
if User.isHost() then
local _,sNodeSourceCT = draginfo.getShortcutData();
TargetingManager.addCTTarget(DB.findNode(sNodeSour ceCT), ActorManager.getCTNode(rTarget));
return true;
end
-- Effect targeting
elseif sDragType == "effect_targeting" then
local _,sEffectNode = draginfo.getShortcutData();
EffectManager.addEffectTarget(sEffectNode, ActorManager.getCTNodeName(rTarget));
return true;
end
end

-- Actions
local sDragType = draginfo.getType();
if StringManager.contains(GameSystem.targetactions, sDragType) then
ActionsManager.actionDrop(draginfo, rTarget);
return true;
end

return onDropEvent(rSource, rTarget, draginfo);
end


The only difference between damage dragged from the damage button vs from an existing chat entry. The Token.onDrop will perform the same and isn't worth delving into.

So what is the difference here from someone more intimate in the workings? I suspect something with the dropinfo is different, but the 'token swap' occurs only on drop so whatever this preliminary "pre-drop" information is, it points to something that does not update for the untargeted, direct to chat scenario compared to dropped on token from action source (damage button etc..). As is expected, even if you dropped the damage from the damage action button, if you wanted to double the damage by dragging the produced damage text from the chat damage entry to the token (which then say, would be enough to cross that threshold), it'll crash too; so it's a drag-source issue.

Moon Wizard
April 6th, 2018, 17:31
If the extension is stand alone, you can send my way to look at. ([email protected]) The smaller the code I need to look through, the better.

I still believe my hypothesis above is most likely cause, but it is strange that it's only triggered on chat drag.

Regards,
JPG

Ken L
April 7th, 2018, 07:08
It's interwoven with a number of other custom scripts given how they need to replace and substitute into the global scripts. I'll have to manually extract it and remove all the other options to the base token switching one. I think I have a hunch for what to do so I'll ping back come monday or sooner.

Ken L
April 22nd, 2018, 17:54
It's taken me awhile to debug this, but it boils down to tokeninstance's delete() method.

For some unknown reason, delete() will crash FG in the drag-from-chat -> token as opposed to the action-button/other-source -> token.

I fetch the windowcontrol for the CT entry, and invoke the tokenfield's 'replace(newTokenInstance)' in ct/scripts/ct_token.lua to perform the book-keeping of swapping targets and references during the change of graphics.

For reference:


function replace(newTokenInstance)
local oldTokenInstance = CombatManager.getTokenFromCT(window.getDatabaseNod e());
if oldTokenInstance and oldTokenInstance ~= newTokenInstance then
if not newTokenInstance then
local nodeContainerOld = oldTokenInstance.getContainerNode();
if nodeContainerOld then
local x,y = oldTokenInstance.getPosition();
TokenManager.setDragTokenUnits(DB.getValue(window. getDatabaseNode(), "space"));
newTokenInstance = Token.addToken(nodeContainerOld.getNodeName(), getPrototype(), x, y);
TokenManager.endDragTokenWithUnits();
end
end
oldTokenInstance.delete();
end

TokenManager.linkToken(window.getDatabaseNode(), newTokenInstance);
TokenManager.updateVisibility(window.getDatabaseNo de());

TargetingManager.updateTargetsFromCT(window.getDat abaseNode(), newTokenInstance);
end


I've found that by removing oldTokenInstance.delete(), there is no crash, however I do get duplicate tokens as the old graphic remains as expected. However the CT linkages and references all point to the new token which is also expected.




Scripts can cause crashes if they delete an object that is the calling object on an event.

In this event, dragging and dropping atop the token has that token instance invoke the chain of events that leads to the damage OOB message that is sent, eventually cascading to the DB.addHandler onUpdate to fire when the health changes. From this point, I have logic that swaps the token. This statement makes sense in that the token's onDrop handler eventually invokes logic that delete's the 'caller' per say.

Following the OOB message however tells me that that the onDrop merely ends in the firing of the OOB message, and a separate set of logic, the OOB handler picks it up. This is akin to a signal protocol or rudimentary IPC.

The only thing I can think of is if the OOB receiver is processed so quickly that the process life of the OOB sender is still active when the logic within the receiver (the DB.addHandler) attempts to delete it, in other words, the 'calling' object. The major problem with this is that this only occurs when drag-dropping damage from the chat and not other sources which also drop on the token.

Compounding with this is that FG is single threaded so hypothetically the object executing the call could finish whatever logic was done after the OOB message was sent before the logic of the OOB receiver is invoked.

What's interesting is this causes a host-side crash, my players have not reported crashes on their end; only a disconnect as the host crashes. I haven't asked if the token graphic updates on their end, and it would be interesting to know for the following reasons:

1. If the token did update, then the client processed the OOB without issue while the host did not, and I have no logic as far as the changing graphic is concerned to separate host vs client execution. In fact most of the logic is host side, OOBs are only sent out for visible DB updates.
2. If the token did not update then the crash occured before the OOB could be even broadcasted, but at the very least the host OOB was processed given the nature of OOB broadcasts including the host.

I'll post a small extension later today that isolates this single issue.

Ken L
April 23rd, 2018, 16:45
Was to post this last night but got caught up.

https://i.imgur.com/Ni4iYWi.gif

https://i.imgur.com/SfBgmFL.png
https://i.imgur.com/c2zoED3.png


extension + green_circle.png and red_circle.png

The token images are to be placed in .../tokens/host as they're directly referenced by the example extension. They need to be loaded by FG to work so if it doesn't appear in the token box then the script will drop some debug indicating as such.

Under normal circumstances, the token prototype is in fact the same as the 'old' token but placed on a different image control so it's the same concept in terms of 'token swapping'. I've reduced the extension to the test case and it's quite repeatable.

Note that the tokens need to be 'healthy' with the green_circle image initially for this extension to trigger as it checks this condition from the token prototype.

Moon Wizard
April 24th, 2018, 17:49
Thanks, Ken. That will make it very quick to track down. I'm installing the bits now, and firing up my debugger.

JPG

Moon Wizard
April 24th, 2018, 18:24
Ok, figured out what is happening.

When something is dropped on a token, the handlers for the token are called first (individual, then global), then the default handling code for token drops. If any of the handlers return a true value, then processing is stopped after that group of handlers is complete.

In this case, the Token.onDrop registered handler in CoreRPG:manager_token.lua does not return anything, which means that the default token drop handling is called. (which in this case doesn't exist anymore)

The fix is to update the TokenManager script onDrop function to this:


function onDrop(tokenCT, draginfo)
local nodeCT = CombatManager.getCTFromToken(tokenCT);
if nodeCT then
return CombatManager.onDrop("ct", nodeCT.getNodeName(), draginfo);
else
if draginfo.getType() == "targeting" then
ChatManager.SystemMessage(Interface.getString("ct_error_targetingunlinkedtoken"));
return true;
end
end
end


This will prevent the crash issue from occurring, by correctly notifying the client to stop processing after the handler called.

I'll put this change in the queue for the next version, but that might be a couple months out. For now, you can override the TokenManager script to address.

Cheers,
JPG

Ken L
April 24th, 2018, 23:04
This will prevent the crash issue from occurring, by correctly notifying the client to stop processing after the handler called.


Eurika! This works swimmingly.

It does open another question though about how this only occurred during a drop-from-chat as opposed from an action which also invokes the drop call. Perhaps it's timing related? Not that it matters greatly as this solves the underlying problem.

I have an issue where if I dragged a token that was already 'dead' from the tracker to the map throw a newTokenInstance error during linkToken in manager_token. I assume this is due to the speed at which the attributeHelper function whick links the token when initially dropped is superseded by the linking of the token that immediately replaces it (due to the health threshold). This can be replicated with the existing extension.

Moon Wizard
April 24th, 2018, 23:24
The reason why the chat drag triggers whereas the dice drag does not is that dice rolls complete asynchronously (i.e. wait for dice to settle), so the token doesn't get changed during the tokenisntance.onDrop event but in the chatwindow.onDiceLanded event. When you drag from the chat window, it's fully resolves in the tokeninstance.onDrop event, because there are no dice to roll.

I'll have to take a look at the other one to see what is happening, but I'll need some time. Trying to compartmentalize support time vs. FGU time. Guessing it's similar issue.

I actually just dropped by to mention that it looks like I'll be pushing hot fix for CoreRPG next Tuesday, so the previous onDrop fix I mentioned will be included.

Regards,
JPG

Ken L
April 25th, 2018, 00:06
Ah, so there's is a little bit of async. I'm guessing there's a wait() on the delete. Regarding the dice, my next plan of attack was spoofing a dice roll by adding a d4 to the roll parameters on the draginfo prior to processing, glad that's not needed.

I'll look in to it as well as this time it doesn't crash but where it crashed was interesting and only made sense if the instance was desroyed (linkToken in manager_token). The support / development split is always an issue so I'll ping back in a few days if you don't beat me to it.

Moon Wizard
April 25th, 2018, 03:15
The only async is waiting for the dice physics simulation to complete. There's no inherent wait. If there's no dice, then there's no dice physics and the action resolves immediately.

The crash was caused by the fact that the tokeninstance was removed from the image (i.e. deleted) after receiving a drop; and then crashed when the default handling continued after calling the event handlers which deleted the token.

Whenever you delete an object in a script, you have to be very careful that you let the API know. In the case of event handlers, the scripts must return true, in order to indicate that the event has been completely handled, and further execution should be skipped.

Regards,
JPG