PDA

View Full Version : Not so random, random crashes in my ruleset.



Brenn
November 17th, 2008, 05:48
I'm pulling my hair out here.

Background. There are basically 3 kinds of rolls in Reign-

Normal roll - Roll your pool of d10's, sort into sets and go.

Expert Die- Set the value of your expert die prior to rolling the rest of your pool.

Master Die- Set the value of your master die after seeing the results from the roll of the rest of your pool.

Ok, I have a dialog for both of the Master and Expert die rolls, the Expert comes up prior to throwDice and the Master afterwards (with the results shown at the bottom of the dialog).

What is happening, is purely at (seemingly?) random times, after making the Master Die selection FG crashes. It does not do it with any other types of rolls. I don't know what the heck is going on, but I'm going to post a ton of code and hope someone can clue me in on something I've missed. Here goes, the whole roll process starts with processOneRoll in OneRollEngine.lua.

OneRollEngine.lua



function getOneRoll()
local oneRoll = {};
oneRoll.tableType = "oneroll";
oneRoll.validate = true;
oneRoll.owner = "";
oneRoll.identity = "";
oneRoll.skill = "";
oneRoll.stat = "";
oneRoll.mastery = "basic";
oneRoll.mdVal = 0;
oneRoll.pool = 0;
oneRoll.poolMod = 0;
oneRoll.dH = 0;
oneRoll.dW = 0;
oneRoll.results = {};
oneRoll.sets = {};
return oneRoll;
end
comment = [[
function createRoll(owner, skill, stat, mastery, pool, poolmodifier, difficultyH, difficultyW)
local oneRoll = getOneRoll();
oneRoll.identity = owner;
oneRoll.skill = skill;
oneRoll.stat = stat;
oneRoll.mastery = mastery;
oneRoll.pool = pool;
oneRoll.poolMod = poolmodifier;
oneRoll.dH = difficultyH;
oneRoll.dW = difficultyW;
processOneRoll(oneRoll);
end
--]]

function processOneRoll(oneRoll)
oneRoll.owner = User.getUsername();
oneRoll.index = table.maxn(pendingRolls)+1;
table.insert(pendingRolls, oneRoll);
if string.lower(oneRoll.mastery) == "expert" then
getExpertDie(oneRoll);
else
rollOneRoll(oneRoll);
end
end

function rollOneRoll(oneRoll)
local tempDieList = {};
for i = 1, (oneRoll.pool + oneRoll.poolMod) do
tempDieList[i] = "d10";
end
ChatManager.control.throwDice("oneroll", tempDieList, 0, "", oneRoll);
--while not testBool do
--end
end

function getExpertDie(oneRoll)
needMasterDie = false;
needExpertDie = true;
bonusDieDialog.setVisible(true);
bonusDieDialog.getBonusDie(oneRoll);
end

function getMasterDie(oneRoll)
needExpertDie = false;
needMasterDie = true;
bonusDieDialog.setVisible(true);
bonusDieDialog.getBonusDie(oneRoll);
end

function dialogSelectionMade(oneRoll)
--There might be a crash problem in this area.
if oneRoll.mastery == "expert" then
rollOneRoll(oneRoll);
else
pushOneRoll(oneRoll);
end
end

function getSets(results, mdVal, mdType)
local sets = {};
local dieResults = results;
table.sort(dieResults, function(a,b) return a > b end);
local done = false;
local setindex = 1;
sets[setindex]={};
local lastheight = dieResults[1];
local widthcounter = 0;
for i = 1, 10 do
for j = 1, table.maxn(dieResults) do
if i == dieResults[j] then
widthcounter = widthcounter + 1;
end
end
if widthcounter > 0 then
sets[setindex]={};
sets[setindex]["width"] = widthcounter;
sets[setindex]["height"] = i;
if mdVal == i then
if mdType == "basic" then
sets[setindex]["hasMD"] = false;
sets[setindex]["hasED"] = false;
elseif mdType == "expert" then
sets[setindex]["hasMD"] = false;
sets[setindex]["hasED"] = true;
elseif mdType == "master" then
sets[setindex]["hasMD"] = true;
sets[setindex]["hasED"] = false;
end
else
sets[setindex]["hasMD"] = false;
sets[setindex]["hasED"] = false;
end
widthcounter = 0;
setindex = setindex + 1;
end
end
local setString = "Sets For This Roll: ";
for i, v in ipairs(sets) do
if v then
setString = setString .. v.width .. "x" .. v.height ..", ";
end
end
print(setString);
return sets;
end

function registerTrackerControl(control)
tracker = control;
if bonusDieDialog then
bonusDieDialog.setParent(tracker.onerolllist);
end
end

function registerDialogControl(control)
bonusDieDialog = control;
if tracker then
bonusDieDialog.setParent(tracker.onerolllist);
end
end

function pushOneRoll(oneRoll)
if oneRoll.mdVal > 0 then
table.insert(oneRoll.results, oneRoll.mdVal);
end
oneRoll.sets = getSets(oneRoll.results, oneRoll.mdVal, oneRoll.mastery);
tracker.onerolllist.setNewRoll(oneRoll);
if User.isHost() then
local identityList = User.getAllActiveIdentities();
local recipientList = {};
for i, v in ipairs(identityList) do
table.insert(recipientList, User.getIdentityOwner(v));
end
print("Host Table Deliver");
ChatManager.deliverTable(oneRoll, recipientList);
else
print("Client Table Deliver");
ChatManager.deliverTable(oneRoll, "");
end
end

function onOneRollReceived(oneRoll)
if User.getUsername() ~= oneRoll.owner then
tracker.onerolllist.setNewRoll(oneRoll);
end
if User.isHost() then
local identityList = User.getAllActiveIdentities();
local recipientList = {};
for i, v in ipairs(identityList) do
table.insert(recipientList, User.getIdentityOwner(v));
end
for i, v in ipairs(recipientList) do
if oneRoll.owner == v then
table.remove(recipientList, i);
end
end
ChatManager.deliverTable(oneRoll, recipientList);
end
end
function deliverSetSelection(selTable)
if selTable.owner == User.getIdentityLabel() then
if User.isHost() and not selTable.wasBroadcasted then
selTable.wasBroadcasted = true;
ChatManager.deliverTable(selTable);
else
ChatManager.deliverTable(selTable, "");
end
elseif User.isHost and not selTable.wasBroadcasted then
selTable.wasBroadcasted = true;
local identityList = User.getAllActiveIdentities();
local recipientList = {};
for i, v in ipairs(identityList) do
if (User.getIdentityLabel(v) ~= selTable.owner) or selTable.hostSelect then
table.insert(recipientList, User.getIdentityOwner(v));
end
end
if table.maxn(recipientList) > 0 then
ChatManager.deliverTable(selTable, recipientList);
end
end
end
function onExternalSelection(selTable)
if (User.getIdentityLabel() ~= selTable.owner) or selTable.hostSelect then
tracker.onerolllist.onExternalSelection(selTable);
end
end
function onTrackerCommand(cmdTable)
print("ORE: onTrackerCommand: order = " .. cmdTable.order);
if cmdTable.order == "sortByWidth" then
tracker.onerolllist.setSortByWidth(true);
elseif cmdTable.order == "sortByHeight" then
tracker.onerolllist.setSortByWidth(false);
end
end

function reportRollResults(draginfo)
local oneRoll = draginfo.getCustomData();
for i = 1, draginfo.getSlotCount() do
local dieList = draginfo.getDieList();
for k, v in ipairs(dieList) do
table.insert(oneRoll.results, v.result);
end
end
if string.lower(oneRoll.mastery) == "master" then
oneRoll.sets = getSets(oneRoll.results, 0, "");
oneRoll.index = table.maxn(pendingRolls)+1;
table.insert(pendingRolls, oneRoll);
getMasterDie(oneRoll);
else
pushOneRoll(oneRoll);
end
end

function onInit()
tracker = nil;
bonusDieDialog = nil;
needMasterDie = false;
needExpertDie = false;
pendingRolls = {};
end

Brenn
November 17th, 2008, 05:49
oneRollDialog.lua:



function getBonusDie(oneRoll)
setVisible(true);
bringToFront();
if oneRoll.mastery == "master" then
isMasterDie = true;
else
isMasterDie = false;
end
titletext = "Select the value of your " .. oneRoll.mastery;
titletext = titletext .. " die:";
currOneRoll = oneRoll;
titleWidget = addTextWidget("titlefont", titletext);
titleWidget.setPosition("top",0,15);
if isMasterDie then
displayResults();
end
createDieSelections();
end

function createDieSelections()
local myW, myH = myParent.getSize();
for i = 1,10 do
selectionItems[i] = window.createControl("dialogselectionitem", "dsi" .. i);
selectionItems[i].setParent(self);
selectionItems[i].setRadialPosition(math.floor(myW/2)+20, math.floor(myH/2), 100, i*10);
selectionItems[i].setValue(i);
end
end

function displayResults()
resultTitleWidget = addTextWidget("titlefont", "Your current roll results: ");
resultTitleWidget.setPosition("bottom",0,-60);

local myW, myH = myParent.getSize();
--myW = myW - 38;
--myH = myH - 78;
local totalWidth = 0;
for i = 1, table.maxn(currOneRoll.sets) do
dieResults[i] = window.createControl("ore_diecontrol", "dc" .. i);
dieResults[i].addSet(currOneRoll.sets[i]);
local drW, drH = dieResults[i].getCurrentSize();
totalWidth = totalWidth + drW;
if i == 1 then
dieResults[i].setAnchor("left",self.getName(),"left");
dieResults[i].resetAnchor("top");
dieResults[i].setAnchor("bottom",self.getName(),"bottom","absolute",-16);
else
dieResults[i].setAnchor("left",dieResults[i-1].getName(),"right");
dieResults[i].resetAnchor("top");
dieResults[i].setAnchor("bottom",dieResults[i-1].getName(),"bottom");
end
end
print("my width: "..myW)
local xoffset = math.floor((myW/2)-(totalWidth/2));
if dieResults[1] then
dieResults[1].setAnchor("left",self.getName(),"left","absolute",xoffset);
end
end

function onSelectionMade(selControl)
local selDie = selControl.getValue();
currOneRoll.mdVal = tonumber(selDie);
clearWidgets();
setVisible(false);
OneRollEngine.dialogSelectionMade(currOneRoll);
end

function setParent(win)
myParent = win;
end

--function setTracker(tracker)
-- myTracker = tracker;
--end

--function resize()
-- myParent.setPosition(myTracker.getPosition());
-- myParent.setSize(myTracker.getSize());
--end

function clearWidgets()
for i, v in ipairs(selectionItems) do
v.destroy();
selectionItems[i] = nil;
end
selectionItems = {};
for i, v in ipairs(dieResults) do
v.destroy();
dieResults[i] = nil;
end
dieResults = {};
if titleWidget then
titleWidget.destroy();
titleWidget = nil;
end
if resultTitleWidget then
resultTitleWidget.destroy();
resultTitleWidget = nil;
end
end

function onInit()
currOneRoll = {};
titleWidget = nil;
selectionItems = {};
resultTitleWidget = nil;
dieResults = {};
myParent = nil;
myTracker = nil;
isMasterDie = false;
setVisible(false);
OneRollEngine.registerDialogControl(self);
end

Brenn
November 17th, 2008, 05:52
ChatManager.lua(partial):



.
.
.
-- Generic message delivery
function deliverMessage(msg, recipients)
if control then
print("Just before control.deliverMessage");
control.deliverMessage(msg, recipients);
print("Just after control.deliverMessage");
end
end
.
.
.
--Table processing
function processReceivedTable(aTable)
print("processReceivedTable");
if aTable.tableType == "oneroll" then --process the table.
OneRollEngine.onOneRollReceived(aTable);
elseif aTable.tableType == "setSelection" then
OneRollEngine.onExternalSelection(aTable);
elseif aTable.tableType == "trackerCommand" then
OneRollEngine.onTrackerCommand(aTable);
else
print("ChatManager: Unknown table type delivered.");
end
end

function deliverTable(aTable, recipientList)
if type(aTable) == "table" then
print("pre table String");
local tableStr = constructTableString("", aTable);
print("post table String");
local msg = {};
msg.font = "systemfont";
msg.text = tableStr;
msg.dice = {};
msg.diemodifier = 0;
msg.dicesecret = false;
if User.isHost() then
msg.sender = GmIdentityManager.getCurrent();
else
msg.sender = User.getIdentityLabel();
end
print("preDeliver");
if recipientList ~= nil then
print("Deliver to Recs");
deliverMessage(msg, recipientList);
else
print("Deliver "..aTable.tableType);
deliverMessage(msg);
end
end
end

function constructTableString(currStr, aTable)
local msg = currStr;
local idxType = "";
if msg == "" then
msg = controlChars["tableEsc"] .. controlChars["tableStartKey"];
end
for k, v in pairs(aTable) do
if type(k) == "string" then
idxType = controlChars["stringKey"];
elseif type(k) == "number" then
idxType = controlChars["numberKey"];
elseif type(k) == "boolean" then
idxType = controlChars["booleanKey"];
end
msg = msg .. controlChars["indexKey"] .. idxType .. k;
if type(v) == "table" then
msg = msg .. controlChars["tableStartKey"];
msg = constructTableString(msg, v);
elseif type(v) == "string" then
msg = msg .. controlChars["stringKey"] .. v;
elseif type(v) == "number" then
msg = msg .. controlChars["numberKey"] .. tostring(v);
elseif type(v) == "boolean" then
msg = msg .. controlChars["booleanKey"] .. tostring(v);
end
end
msg = msg .. controlChars["tableEndKey"];
return msg;
end

function onTableReceived(tableStr)
print("onTableReceived");
local buildTable = {};
local finalTable = {};
local function builder(cont, val) table.insert(buildTable, {control = cont, value = val}) end;
builder(string.gsub(tableStr, "("..controlPatternSet..")".."("..notControlPatternSet.."*)", builder));
-- remove final null record. It appends the length of the string at the end... Commented out for now.
--table.remove(buildTable,table.maxn(buildTable));
if controlDecode[buildTable[1].control] == "tableStartKey" then
--strip the initial table start key for the initial loop of reconstructTable.
table.remove(buildTable, 1);
print("pre table reconstruct");
finalTable = reconstructTable(buildTable);
processReceivedTable(finalTable);
else
print("ChatManager: Invalid table construction string delivered.");
end
end

function reconstructTable(buildTable)
local reconTable = {};
local subBuildTable = {};
local subTable = {};
local haveSubTable = false;
local currIndex = "";
local currIndexType = "";
local haveIndex = false;
local currVal = "";
local currValType = "";
local haveVal = false;
local outerDone = false;
local i = 1;
while not outerDone do
if controlDecode[buildTable[i].control] == "tableStartKey" then
local j = i + 1;
local k = 1;
local done = false;
local tableStartKeyCount = 0; --to track nested tables.
while not done do
subBuildTable[k] = table.remove(buildTable, j);
if controlDecode[subBuildTable[k].control] == "tableStartKey" then
tableStartKeyCount = tableStartKeyCount + 1;
elseif controlDecode[subBuildTable[k].control] == "tableEndKey" then
if tableStartKeyCount == 0 then
--no nested tables.
done = true;
else
--nested tables present.
tableStartKeyCount = tableStartKeyCount - 1;
end;
end
if j > table.maxn(buildTable) then
done = true;
end;
k = k + 1;
end
subTable = reconstructTable(subBuildTable);
haveSubTable = true;
subBuildTable = {};
k = 1;
elseif controlDecode[buildTable[i].control] == "tableEndKey" then
return reconTable;
elseif controlDecode[buildTable[i].control] == "indexKey" then
i = i + 1; --the index key will immediately be followed by a key to indicate the type of index.
currIndexType = controlDecode[buildTable[i].control];
if currIndexType == "stringKey" then
currIndex = buildTable[i].value;
elseif currIndexType == "numberKey" then
currIndex = tonumber(buildTable[i].value);
elseif currIndexType == "booleanKey" then
if buildTable[i].value == "true" then
currIndex = true;
else
currIndex = false;
end
end
haveIndex = true;
elseif controlDecode[buildTable[i].control] == "stringKey" then
currVal = buildTable[i].value;
currValType = "string";
haveVal = true;
elseif controlDecode[buildTable[i].control] == "numberKey" then
currVal = buildTable[i].value;
currValType = "number";
haveVal = true;
elseif controlDecode[buildTable[i].control] == "booleanKey" then
currVal = buildTable[i].value;
currValType = "boolean";
haveVal = true;
else
print("ChatManager: Invalid control character during table reconstruction.");
end
if haveIndex then
if haveSubTable then
reconTable[currIndex] = subTable;
haveSubTable = false;
haveIndex = false;
elseif haveVal then
if currValType == "string" then
reconTable[currIndex] = currVal;
elseif currValType == "number" then
reconTable[currIndex] = tonumber(currVal);
elseif currValType == "boolean" then
if currVal == "true" then
reconTable[currIndex] = true;
else
reconTable[currIndex] = false;
end
end
haveVal = false;
haveIndex = false;
end
end
i = i + 1;
if i > table.maxn(buildTable) then
outerDone = true;
end
end
end
.
.
.
function processOre(params)
local custORE = OneRollEngine.getOneRoll();
custORE.pool = string.match(params, "%s*(%d+)");
custORE.stat = string.match(params, "%s+(%a+)");
if User.isHost() then
custORE.identity = GmIdentityManager.getCurrent();
else
custORE.identity = User.getIdentityLabel();
end
local bme = string.match(params, "%s*(%a+)");
if bme == "e" then
custORE.mastery = "expert";
elseif bme == "m" then
custORE.mastery = "master";
end
OneRollEngine.processOneRoll(custORE);
end

-- Initialization
function onInit()
controlChars = {["tableEsc"] = string.char(5),
["tableStartKey"] = string.char(15),
["tableEndKey"] = string.char(14),
["indexKey"] = string.char(6),
["stringKey"] = string.char(17),
["numberKey"] = string.char(18),
["booleanKey"] = string.char(19)};
controlPatternSet = "";
controlDecode = {};
for k, v in pairs(controlChars) do
controlPatternSet = controlPatternSet .. "%" .. v;
controlDecode[v] = k;
end
notControlPatternSet = "[^" .. controlPatternSet;
notControlPatternSet = notControlPatternSet .. "]";
controlPatternSet = "[" .. controlPatternSet;
controlPatternSet = controlPatternSet .. "]";

registerSlashHandler("/whisper", processWhisper);
registerSlashHandler("/die", processDie);
registerSlashHandler("/ore", processOre);
end

Brenn
November 17th, 2008, 05:53
I really haven't been able to isolate this down to a specific spot. Everytime I think I have it nailed and remove one bit or another, it still exhibits the same behavior. Awfully frustrating when in testing you think you have it 19 rolls in and it fails on the 20th.

I don't know if I'm not creating/destroying controls quite properly or what.

I've been chasing this for about 10 hours now and am at my wits end.

Any help or ideas would be appreciated.

Brenn
November 17th, 2008, 13:46
I mistakenly said in my first post that the whole thing started with rollOneRoll, which isn't correct. It starts with processOneRoll

Brenn
November 17th, 2008, 15:25
I'm pretty sure I fixed this. Yet another post that really shouldn't be here.

I'm not sure what was happening really, but I moved stuff around so that the dialog isn't 'closed' (hidden really and it's associated controls and widgets destroyed) until after the bonus die stuff is processed. If this is the cure (which about 50 master die rolls leads me to believe), I don't understand why I was not seeing the same problem with expert die rolls. The external code to the dialog has no link to the controls within except through the dialog control itself, so it wouldn't be using references that have been destroyed.

Oh well. This whole fiasco has made me somewhat wary of creating and destroying stuff at runtime because I believe some of that process is obfuscated. I also noted in this process that the onClose event for the diecontrols that I use to display roll results is not being called when those controls are destroyed, which is contrary to what is said in the online documentation.

Foen
November 17th, 2008, 20:38
Windows which close themselves triggered an erratic crash in FG until the latest release (FG 2.3.5) so maybe that has helped.

Foen

Brenn
November 17th, 2008, 20:48
This problem was happening in 2.3.5

I'm pretty sure it was the order in which I was doing stuff. Troubleshooting things is just difficult though without being able to step through stuff, set breakpoints and the other stuff I'm used to from the past. I'm not complaining, those things go way beyond the scope of what lua for FG is and I wouldn't expect to see them here. I'm pretty happy with what I have to work with- this is way better than what it used to be, as I recall.