celestian
June 28th, 2019, 00:22
I've been trying to figure out the best way to improve load times for creatures placed into the Combat Tracker in the AD&D ruleset. It's kinda sluggish and I'd like to make it better. To that end I did some review of the code and the nodes that I might best "trim" out to reduce. As you can guess it's mostly Attacks, Powers and Inventory items.
My first experiment was to remove the inventory and "abilitynotes" nodes and all their children to npcs placed into the CT and replace those nested nodes with a single string of "node.getPath();node.getPath()" paths back to their source. The idea was I would use createControl for the view of those items showing them from the original source NPC.
It worked but the reduced feature set (can't edit really) really bothers me... but inventory and ability notes are not that big of a deal. Attacks are.
Attacks need a little more work to support. I started with the idea I did for inventory/ability notes and realized it wasn't really a good way to handle it. Someone had posted code that showed someone else using JSON strings in FG and it got me thinking, hell... I can store just about anything in a JSON string. I found a library that was opensource and began tinkering with it. After a bit I was able to get the weaponlist nested node and all the components stuffed into a single string entry on a npc in the CT and be able to "edit" the fields there.
Once I had it in JSON I began working on a UI window for it to display to tinker with it, see how it felt and test the load times.
The UI ended up being a intense learning experience and it game me a lot of ideas on how to to come work on the CT ui in general (I want to compact the npc views) but in the end it really didn't seem to make all that much difference in load times. I mean, it DOES make it load slightly faster but I think the added overhead of all the createControl() handling ate about 1/4 of the improvement or more.
I did notice some odd behavior in FG in general unrelated to these tweaks. I could close FG and start it and the first encounter I dropped into the CT was fairly spunky. If I cleared them and re-did it... each time it seemed to get slower (about 3-5th time it seemed to reach it's max slowness). I tested with my ruleset unmodified and modified and both seemed to experience this issue. Even tried the 5E one and seemed to be the same there. My guess is that it has something to do with memory management in the 32 bit app? Tho that's just speculation.
I'm going to keep tinkering with this a bit more to see what I can do but ... right not im mostly disappointed in the results. I don't think it's really worth the effect of losing the nodes (ease of manipulation) for the "slight" CT load times. Inventory items however I will probably continue to work with regardless so that people can use the Inventory tab and not dramatically affect CT load times. The benefits of inventory on ct npcs is worth it I think... it might end up being a "read only" mode. Meaning you'd need to have the npc's inventory setup the way you want before to drop them in the CT (which for me is 99.9% of the time anyway).
I say all that to solicit other's insight into this, perhaps someone has a method they use to help with these types of situations.
Here is the way I took the nodeNPC.weaponlist.* and converted it to a table then converted that to a JSON string.
-- pass a nodeNPC node and return the "weaponlist" children as a json string to be stored into a node as one single entry.
-- Once we have it in json style text we can use it like:
-- local aWeaponList = JSON.decode(DB.getValue(nodeCT,'weaponlist_json',"");
-- DB.setValue(nodeCT,'weaponlist_json',"string",JSON.encode(aWeaponList));
function getWeaponListAsJSONText(node)
local aWeaponsList = {};
for sID, nodeWeapon in pairs(DB.getChildren(node,"weaponlist")) do
local aWeapon = {};
aWeapon.sSource = nodeWeapon.getPath();
aWeapon.sID = sID;
aWeapon.sName = DB.getValue(nodeWeapon,"name","");
aWeapon.nAttackCurrent = DB.getValue(nodeWeapon,"attackview_weapon",0);
aWeapon.sAttackStat = DB.getValue(nodeWeapon,"attackstat","");
aWeapon.nAttackBonus = DB.getValue(nodeWeapon,"attackbonus",0);
aWeapon.nSpeedFactor = DB.getValue(nodeWeapon,"speedfactor",0);
aWeapon.nType = DB.getValue(nodeWeapon,"type",0);
aWeapon.nCarried = DB.getValue(nodeWeapon,"carried",0);
aWeapon.nMaxAmmo = DB.getValue(nodeWeapon,"maxammo",0);
aWeapon.nAmmo = DB.getValue(nodeWeapon,"ammo",0);
aWeapon.nLocked = DB.getValue(nodeWeapon,"locked",1);
-- aWeapon.sShortcutClass, aWeapon.sShortcutRecord = DB.getValue(nodeWeapon,"shortcut","","");
aWeapon.ItemNoteLocked = DB.getValue(nodeWeapon,"itemnote.locked",1);
aWeapon.ItemNoteName = DB.getValue(nodeWeapon,"itemnote.name","");
aWeapon.ItemNoteText = DB.getValue(nodeWeapon,"itemnote.text","");
aWeapon.aDamageList = {};
for sDMGid, nodeDamage in pairs(DB.getChildren(nodeWeapon,"damagelist")) do
local aDamage = {};
aDamage.sID = sDMGid;
aDamage.sDamageAsString = DB.getValue(nodeDamage,"damageasstring","");
aDamage.nBonus = DB.getValue(nodeDamage,"bonus",0);
aDamage.dDice = DB.getValue(nodeDamage,"dice","");
aDamage.sStat = DB.getValue(nodeDamage,"stat","");
aDamage.sType = DB.getValue(nodeDamage,"type","");
table.insert(aWeapon.aDamageList,aDamage);
end
-- sort damage by id so they appear as they do in weapons tab right now
local sort_byID = function( a,b ) return a.sID < b.sID end
table.sort(aWeapon.aDamageList, sort_byID);
-- add this weapon to weaponslist
table.insert(aWeaponsList,aWeapon);
-- Sort the weapons by name like they appear in list currently
local sort_byName = function( a,b ) return a.sName < b.sName end
table.sort(aWeaponsList, sort_byName);
end
local sJson = JSON.encode(aWeaponsList);
return sJson;
end
This is an example of how it turned out in the CT. I picked one of the most "nodey" npcs I had.
https://i.imgur.com/lWdbxQ8.png
The simple JSON library I used was this. I made some minor updates to it but it worked almost out of the gate.
https://gist.github.com/tylerneylon/59f4bcf316be525b30ab
My first experiment was to remove the inventory and "abilitynotes" nodes and all their children to npcs placed into the CT and replace those nested nodes with a single string of "node.getPath();node.getPath()" paths back to their source. The idea was I would use createControl for the view of those items showing them from the original source NPC.
It worked but the reduced feature set (can't edit really) really bothers me... but inventory and ability notes are not that big of a deal. Attacks are.
Attacks need a little more work to support. I started with the idea I did for inventory/ability notes and realized it wasn't really a good way to handle it. Someone had posted code that showed someone else using JSON strings in FG and it got me thinking, hell... I can store just about anything in a JSON string. I found a library that was opensource and began tinkering with it. After a bit I was able to get the weaponlist nested node and all the components stuffed into a single string entry on a npc in the CT and be able to "edit" the fields there.
Once I had it in JSON I began working on a UI window for it to display to tinker with it, see how it felt and test the load times.
The UI ended up being a intense learning experience and it game me a lot of ideas on how to to come work on the CT ui in general (I want to compact the npc views) but in the end it really didn't seem to make all that much difference in load times. I mean, it DOES make it load slightly faster but I think the added overhead of all the createControl() handling ate about 1/4 of the improvement or more.
I did notice some odd behavior in FG in general unrelated to these tweaks. I could close FG and start it and the first encounter I dropped into the CT was fairly spunky. If I cleared them and re-did it... each time it seemed to get slower (about 3-5th time it seemed to reach it's max slowness). I tested with my ruleset unmodified and modified and both seemed to experience this issue. Even tried the 5E one and seemed to be the same there. My guess is that it has something to do with memory management in the 32 bit app? Tho that's just speculation.
I'm going to keep tinkering with this a bit more to see what I can do but ... right not im mostly disappointed in the results. I don't think it's really worth the effect of losing the nodes (ease of manipulation) for the "slight" CT load times. Inventory items however I will probably continue to work with regardless so that people can use the Inventory tab and not dramatically affect CT load times. The benefits of inventory on ct npcs is worth it I think... it might end up being a "read only" mode. Meaning you'd need to have the npc's inventory setup the way you want before to drop them in the CT (which for me is 99.9% of the time anyway).
I say all that to solicit other's insight into this, perhaps someone has a method they use to help with these types of situations.
Here is the way I took the nodeNPC.weaponlist.* and converted it to a table then converted that to a JSON string.
-- pass a nodeNPC node and return the "weaponlist" children as a json string to be stored into a node as one single entry.
-- Once we have it in json style text we can use it like:
-- local aWeaponList = JSON.decode(DB.getValue(nodeCT,'weaponlist_json',"");
-- DB.setValue(nodeCT,'weaponlist_json',"string",JSON.encode(aWeaponList));
function getWeaponListAsJSONText(node)
local aWeaponsList = {};
for sID, nodeWeapon in pairs(DB.getChildren(node,"weaponlist")) do
local aWeapon = {};
aWeapon.sSource = nodeWeapon.getPath();
aWeapon.sID = sID;
aWeapon.sName = DB.getValue(nodeWeapon,"name","");
aWeapon.nAttackCurrent = DB.getValue(nodeWeapon,"attackview_weapon",0);
aWeapon.sAttackStat = DB.getValue(nodeWeapon,"attackstat","");
aWeapon.nAttackBonus = DB.getValue(nodeWeapon,"attackbonus",0);
aWeapon.nSpeedFactor = DB.getValue(nodeWeapon,"speedfactor",0);
aWeapon.nType = DB.getValue(nodeWeapon,"type",0);
aWeapon.nCarried = DB.getValue(nodeWeapon,"carried",0);
aWeapon.nMaxAmmo = DB.getValue(nodeWeapon,"maxammo",0);
aWeapon.nAmmo = DB.getValue(nodeWeapon,"ammo",0);
aWeapon.nLocked = DB.getValue(nodeWeapon,"locked",1);
-- aWeapon.sShortcutClass, aWeapon.sShortcutRecord = DB.getValue(nodeWeapon,"shortcut","","");
aWeapon.ItemNoteLocked = DB.getValue(nodeWeapon,"itemnote.locked",1);
aWeapon.ItemNoteName = DB.getValue(nodeWeapon,"itemnote.name","");
aWeapon.ItemNoteText = DB.getValue(nodeWeapon,"itemnote.text","");
aWeapon.aDamageList = {};
for sDMGid, nodeDamage in pairs(DB.getChildren(nodeWeapon,"damagelist")) do
local aDamage = {};
aDamage.sID = sDMGid;
aDamage.sDamageAsString = DB.getValue(nodeDamage,"damageasstring","");
aDamage.nBonus = DB.getValue(nodeDamage,"bonus",0);
aDamage.dDice = DB.getValue(nodeDamage,"dice","");
aDamage.sStat = DB.getValue(nodeDamage,"stat","");
aDamage.sType = DB.getValue(nodeDamage,"type","");
table.insert(aWeapon.aDamageList,aDamage);
end
-- sort damage by id so they appear as they do in weapons tab right now
local sort_byID = function( a,b ) return a.sID < b.sID end
table.sort(aWeapon.aDamageList, sort_byID);
-- add this weapon to weaponslist
table.insert(aWeaponsList,aWeapon);
-- Sort the weapons by name like they appear in list currently
local sort_byName = function( a,b ) return a.sName < b.sName end
table.sort(aWeaponsList, sort_byName);
end
local sJson = JSON.encode(aWeaponsList);
return sJson;
end
This is an example of how it turned out in the CT. I picked one of the most "nodey" npcs I had.
https://i.imgur.com/lWdbxQ8.png
The simple JSON library I used was this. I made some minor updates to it but it worked almost out of the gate.
https://gist.github.com/tylerneylon/59f4bcf316be525b30ab