Jump to content

Avoiding race conditions from terminal fragments (screen displaying before called function completes)


Recommended Posts

EDIT: bah, can't edit the title. Not exactly Solved, but Workarounded:

  • A terminal menu item will not advance until its fragment is completed, so adding delays like Utility.WaitMenuMode(x) will give you some time before the text displays. This is useful when the display is using Text Replacement and you want to give the globals time to update.
  • HOWEVER, no matter what delay you give, the menu item will advance after a timeout of ~ six seconds. This seems to be hardcoded; I can't find it in Game Settings in the CK or anywhere else. Therefore, if you have anything that takes longer, it gets caught half-dressed, as it were. Sooo, any "x" you set in the function there > 6 still only gets you 6 seconds.
  • but HO! This timeout is just for that one menu item, that one fragment. So if you make the first option kick off your long-running process, and go the max wait, you will get to the submenu - where you can run another fragment / menu item, and another delay of up to 6 seconds. This works around the timeout by stacking them together. At the end of this chain, you can show your text replacement, knowing it will be filled.
  • If you have to go to more than one submenu and get a delay of > 12 seconds, then I have no idea what you're calculating but please share; it must be gargantuan.

 

---

Original post:

---

Hopefully I'm just missing something arcane that isn't in the wiki yet. I've a terminal whose menu items are a text display with text replacement. After many trials and tests:

  • Functions are working as expected
  • Text replacement is occurring as expected - eventually
  • I seem to be getting the correct calculated values

The issue is that the terminal shows the text *before* the function the fragment calls is completed, resulting in either a display full of zeroes, or partially-filled values. If i wait a bit, and select the same menu item again, it's finished finally and I get the full correct display of values.

 

I have tried some of the following; in the fragment itself:

 

RegisterForRemoteEvent(DespotQuest, "OnStageSet")
where the quest script is setting a stage as the last line of the function. Nope, the menu item advances before it's done. Also
RegisterForCustomEvent(DespotQuest, "InventoryDone")
where I'm sending that event the same way - compiles, but doesn't seem to care. Thought the menu item wouldn't advance until these events were received; but no, I'm guessing it just says "Okay, I'm listening for that now, yep" and carries on.
Is there any way to delay or pause the screen's display until the called function is done with its business?
I can't seem to figure out OnMenuItemRun() either, since it extends Terminal, which the fragment window is inherently, while a script attached to a terminal extends object reference; so I've no idea where this function calls home and just gave up and put everything in the bloody quest.
Which I'm accessing like this - full fragment example:
HomeDespotQuestScript DespotQuest = Game.GetFormFromFile(0x00000F9A, "TEST_HomeDespot-spacefiddle.bak3.esp") as HomeDespotQuestScript
DespotQuest.LocalInventory()
RegisterForRemoteEvent(DespotQuest, "OnStageSet")
RegisterForCustomEvent(DespotQuest, "InventoryDone")
DespotQuest.SetStage(20)

halp!

Link to comment
Share on other sites

Update: yeah, after I cleaned stuff up cluttering the logs, it's taking about 9 seconds to run my calculations. I remember some blurb about "will not advance until completes or an internal timeout is reached." I'm guessing I'm hitting that timeout. How can I disable or modify it?

Link to comment
Share on other sites

First, I have to ask: What sort of calculations are you doing that take 9 seconds to run?

 

Secondly, you will likely want to post all your script source - not just the terminal fragments - if you want someone to really be able to help you. That being said, the command you want to use to pause the terminal menu is Utility.WaitMenuMode(float seconds).

Edited by Reneer
Link to comment
Share on other sites

First, I have to ask: What sort of calculations are you doing that take 9 seconds to run?

 

Secondly, you will likely want to post all your script source - not just the terminal fragments - if you want someone to really be able to help you. That being said, the command you want to use to pause the terminal menu is Utility.WaitMenuMode(float seconds).

 

Thanks - I was using WaitMenuMode but it seems like it was not affecting the screen draw. I had it set for 10 seconds, and it was maybe 6 before the screen drew. Logs show when it's called and when it ends, and it was indeed 10 seconds apart. As for my code.... heh... thanks for the offer, I'm going to have to spend some time cleaning it up first :D

Link to comment
Share on other sites

First, I have to ask: What sort of calculations are you doing that take 9 seconds to run?

 

Alright, ladies and gents: welcome to The Home Despot. Get a Grip on Your Junk!

 

This is working, actually, except for the race condition. If I choose the local inventory, it's instant and fine. When I choose Linked Inventory (showing components available to build here with, by looking at component values - not junk items - across all provisioner-linked workshops to here), if i back out, give it a few seconds, and look again - the globals have had time to update.

 

I've only spot-checked the component totals but they seem correct so far. And now, my sloppy monolith of a quest script:

 

Scriptname HomeDespotQuestScript extends Quest Hidden
{Quest script for Home Despot mod. Updates workshop inventories when accessed.}


import debug


import CommonArrayFunctions
import WorkshopDataScript


WorkshopParentScript Property WorkshopParent Auto const mandatory


Group WorkshopData
WorkshopScript[] Property Workshops Auto
{ Array of all workshops
 Index is the "workshopID" of that workshop }
WorkshopScript[] Property PlayerOwnedList Auto
{ A list of player-owned settlements. }
WorkshopScript Property OwnedSettlement Auto
{ Contains an owned settlement to be added to PlayerOwnedList. }
ReferenceAlias Property CurrentWorkshop Auto
{ Where we are now, since this is based around a Workshop-constructed terminal }
Location[] Property WorkshopLocations Auto
{ Fairly obvious }
EndGroup


Group DespotQuest_TerminalUpdates
Quest property DespotQuest auto const mandatory
{ If we don't have this quest, what are we doing? }
ReferenceAlias property HomeDespotTerminalConsole auto
{ The 3D console we're accessing }
GlobalVariable property DespotTotalSettlements auto
{ Global where the total number of player-owned settlements is kept }
GlobalVariable[] property Globals auto
{ The members are defined in the CK under script Properties, in the terminal itself }
EndGroup


Group Components
Component[] property Components auto
{ Again, members defined in CK as above }
Int Property AllComps = 0 auto
{ All components in the game. }
Int[] property ComponentInv auto
{ Our bucket to keep a running total of components as we count }
EndGroup


Group Keywords ; These are to find the linked settlements
Keyword Property WorkshopCaravanKeyword const auto mandatory
Keyword Property WorkshopLinkContainer const auto mandatory
EndGroup


; Set the stage to 20 so that the script knows that the plugin has been successfully loaded.
CustomEvent SetupDone


; Attempt to make the damn thing wait until we are done counting ffs which failed
; CustomEvent InventoryDone


HomeDespotQuestScript Function GetScript() Global
Return Game.GetFormFromFile(0x00000F9A, "TEST_HomeDespot-spacefiddle.bak.3.esp") as HomeDespotQuestScript
EndFunction


Event OnStageSet(int stageID, int auiItemID)
debug.trace(" HD> stage= " + stageID )
if stageID == 10
Initialize()
; register for event to mark initialization complete.
RegisterForCustomEvent(self, "SetupDone")
SendCustomEvent("SetupDone")
elseif stageID == 20
; Our ComponentInv[] sums wont work if everything is NONE.
int currentElement = 0
while (currentElement < ComponentInv.Length)
ComponentInv[currentElement] = 0
currentElement += 1
endWhile
debug.trace( " HD> Stage 20 set - ComponentInv should be zeroed below ")
debug.trace( " HD> " + ComponentInv )
endif
endEvent


Event HomeDespotQuestScript.SetupDone(HomeDespotQuestScript inputSender, Var[] inputArgs)
debug.trace(" HD> SetupDone ")
WorkshopLocations = new Location[Workshops.Length]
int index = 0
while index < Workshops.Length
WorkshopScript workshopRef = Workshops[index] as WorkshopScript
workshopRef.InitWorkshopID(index)
WorkshopLocations[index] = workshopRef.GetCurrentLocation()
;WorkshopLocations.Add(Workshops[index].GetCurrentLocation())
workshopRef.myLocation = WorkshopLocations[index]
index += 1
endWhile
debug.trace( " HD> " + WorkshopLocations )
setStage(20) ; Initialization is complete.
EndEvent


; Initializes the plugin.
function Initialize()
debug.trace(" HD> Init() ")
; Populate Workshops with a list of settlements.
SetWorkshops()
PlayerOwnedList = new WorkshopScript[0]
endFunction


; Populate Workshops.
function SetWorkshops()
debug.trace(" HD> SetWorkshops() ")
Workshops = WorkshopParent.Workshops as WorkshopScript[]
endFunction


; Find player-owned workshops.
function CheckPlayerOwned()
debug.trace(" HD> CheckPlayerOwned()" )
PlayerOwnedList.Clear()
int index = 0
while (index < GetWorkshops().length)
if PlayerOwnedSettlements(Workshops[index])
ProcessSettlement(Workshops[index])
endif
index += 1
endWhile
int numOwned = PlayerOwnedList.Length
debug.trace(" HD> Found " + numowned + " owned. Setting global and text.")
DespotTotalSettlements.SetValue(numOwned)
DespotQuest.UpdateCurrentInstanceGlobal(DespotTotalSettlements)
ObjectReference term
term = (HomeDespotTerminalConsole as ReferenceAlias).getReference()
debug.notification(" HD> quest term " + term )
term.AddTextReplacementData("DespotTotalSettlements", DespotTotalSettlements)
endFunction


bool function PlayerOwnedSettlements(WorkshopScript inputSettlement)
;debug.trace(" HD> PlayerOwnedSettlements ")
if inputSettlement.OwnedByPlayer
return true
else
return false
endif
endFunction


function ProcessSettlement(WorkshopScript inputSettlement)
RecordPlayerOwned(inputSettlement)
endFunction


function RecordPlayerOwned(WorkshopScript OwnedToRecord)
OwnedSettlement = OwnedToRecord as Workshopscript
PlayerOwnedList.Add(OwnedSettlement)
OwnedSettlement = NONE
endFunction


WorkshopScript[] function GetWorkshops()
debug.trace(" HD> GetWorkshops ")
if Workshops.length <= 0
SetWorkshops()
endif
return Workshops
endFunction


; poorly-named function i lifted from Parent that I should make more distinct from the above
WorkshopScript function GetWorkshop(int workshopID)
return Workshops[workshopID]
endFunction


; Terminal menu item: Local inventory. This workshop container only.
Function LocalInventory()
debug.trace( " HD> LocalInventory called " )
AllComps = Components.Length
Int i = 0
While i < AllComps
ComponentInv[i] = CurrentWorkshop.GetReference().GetComponentCount( Components[i] )
debug.trace( " HD> Local Count " + Components[i] + " is " + ComponentInv[i] )
UpdateGlobals(Globals[i], ComponentInv[i])
i += 1
EndWhile
SetStage(40)
;SendCustomEvent("InventoryDone")
EndFunction


; The bigun - using Caravan keyword to "traverse" all workshops linked to this one, and add each one
; inventory to the array of ComponentInv[] as a running total.
Function LinkedInventory()
debug.trace( " HD> LinkedInventory called " )
location here = CurrentWorkshop.GetReference().GetCurrentLocation()
debug.trace( " HD> Here is " + here )
WorkshopScript this = CurrentWorkshop.GetReference() as WorkshopScript
Location[] linkedLocations = this.myLocation.GetAllLinkedLocations(WorkshopCaravanKeyword)
debug.trace( " HD> LinkedLocs are " + linkedLocations )
AllComps = Components.Length
int index = 0
while (index < linkedLocations.Length)
; get linked workshop from location
int linkedWorkshopID = WorkshopLocations.Find(linkedLocations[index])
if linkedWorkshopID > 0
; get the linked workshop container
WorkshopScript linkedWorkshop = GetWorkshop(linkedWorkshopID)
debug.trace( " > " + linkedWorkshopID + " is " + linkedWorkshop )
ObjectReference linkedContainer = linkedWorkshop.GetContainer()
if linkedContainer
Int i = 0
While i < AllComps
ComponentInv[i] += linkedContainer.GetComponentCount( Components[i] )
debug.trace( " HD> " + Components[i] + " totals " + ComponentInv[i] )
i += 1
EndWhile
else
debug.trace(" HD> Nothing linked! " )
endif
endif
index += 1
endwhile
int g = 0
while g < Globals.Length
; UpdateGlobals follows this, and also does text replacement
UpdateGlobals(Globals[g], ComponentInv[g])
debug.trace( " HD> Global " + Globals[g] + " sending " + ComponentInv[g] )
g += 1
endWhile
SetStage(50)
;SendCustomEvent("InventoryDone")
EndFunction


; Once totals are present after looping through Linked, called from loop to update each global . text replacement
Function UpdateGlobals(globalvariable globComponent, int numComponents)
globalvariable globval = globComponent
int total = numComponents
debug.trace(" HD> Update Received " + total + " of " + globval )
globval.SetValue(total)
DespotQuest.UpdateCurrentInstanceGlobal(globval)
ObjectReference term
term = (HomeDespotTerminalConsole as ReferenceAlias).getReference()
term.AddTextReplacementData(globval, globval)
debug.trace(" HD> Current globval is " + globval )
EndFunction


; Another attempt to avoid race condition:
; We call this in the fragment for Linked Inventory, but it was pretty much ignored
Function DoNothing()
debug.trace( " HD> DoNothing() called" )
Utility.WaitMenuMode(10)
debug.trace( " HD> DoNothing() ended" )
EndFunction

 

On a side note - is there ANY way to trick some leading zeroes into a Global variable? I've just surrendered to the notion that I'll never get the damn thing to line up correctly unless the totals happen to cause it.

 

http://i.imgur.com/IOcf6Iul.png

 

Edit - I've tried the utility.waitmenumode(10) call both from the fragment itself, right after the LinkedInventory function call, as well as calling the DoNothing() function that contains the wait. No dice. After 5-6 seconds the screen draws no matter what I do (and usually has all zeroes, since the running-total loop hasn't completed so the globals aren't updated yet). As an annoying side effect, if I've done Local first, it shows those values, so I guess I need to zero the globals at function start too -_-

Link to comment
Share on other sites

ha HAH! Never mind, thought of a workaround.

 

The wait utility is working, but getting truncated. It's extremely consistent: delays under 6 seconds behave as expected, anything long is not honored. The internal timeout mentioned on the wiki for fragment processing seems to be about 6 seconds.

 

Per fragment. Per menu item.

 

So, I have the whole process kick off from the first menu item's fragment, with a delay of 5 seconds - which leads to a submenu. Text display of the submenu does some "Reticulating Splines..."-type silliness, leading to a single menu item of "Get Results." There is no function or fragment here except another delay of 5 seconds, which response covers with "Formatting output" or something. Finally, that item leads to the real text display with replacement; and the loop's had time to complete while we were reading the distract- uh, text output, and navigating the menu.

 

All told, it's just two clicks to hit "Linked Inventory" and then "Get Results," and the delay doesn't feel too horrible. If there's another way to process the above, traversing the linked workshops, I'd love to hear it; but if not, this consistently has time to finish no matter how fast I spam the buttons. The minimum 10 second delay enforced by Wait will in reality always be a couple seconds more, as the player takes action (even really quick action).

 

I hope to do a couple more sanity checks on the thing and get it up on the Nexus tonight, if all goes well. Thanks for offering anyway.

 

Edit: oh yeah. If ANYONE knows a trick to add leading zeroes or otherwise control the text output better (so I can line everything up as nice as it is in my screenshot), I'd love to hear it.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...