Jump to content

Question on Events and threads (Papyrus scripting)


PJMail

Recommended Posts

I have a number of unanswered questions concerning Papyrus and how it handles Events (from a script point of view) so I am wondering if anyone has a definitive answer to the following -

 

1) Are Animation events handled (internally) via a FIFO like queue or immediately dispatched? The first would mean the OnEvent procedure threads would run in the order the events came in, whereas the 2nd method would mean they could be out of order (as it's then up to the thread scheduler which thread runs first). It seems impossible to tell this from trace in a script because logging messages appear to be buffered and can appear out of order once written (even when I know they were generated in a certain order).

 

I have worked on the assumption that animation 'OnEvents' from an actor are executed in the order they are generated, but am wondering if that is not 100% true... Like a 'weaponsheath' event occurring before the 'weapondraw'...

 

2) Does it matter how long the script attached to an OnEvent procedure takes to execute? By this I mean is anything 'blocked' until the code in the OnEvent procedure completes?

 

I put all the code into the OnEvent procedures (which can sometimes be large), but I see some Mod authors decouple that code from the Event by just triggering a timer and immediately returning. That timer event then does the work they could have put into the OnEvent procedure itself. This makes me question am I doing it wrongly?

 

3) Is the 'TimerID' on StartTimer just a parameter passed to the resulting OnTimer Event, or uniquely defines a timer?

By this I mean does

StartTimer(10.0, 2000)

StartTimer(30.0,2000)

generate 2 events - one in 10s and one in 30s, OR just one in 30s (implying there is only one timer called '2000')?

 

I have worked on the assumption that it generates 2 events, but that means I have added lots of 'multiple trigger prevention' code that is quite messy...

 

 

Hope some of you experienced Papyrus coders can answer...

 

Thanks

Edited by PJMail
Link to comment
Share on other sites

  • Replies 55
  • Created
  • Last Reply

Top Posters In This Topic

I dont know about the event pipeline as script execution priority seems totally arbitrary. When testing via UI pops or Log entries their acknowledgment of (even well sequenced) events from the queue/buffer/bus/UART/whatever can be unpredictable. Which is why synch activity should be kept in the same script to ensure linear flow.

 

A script is a single thread so it can only process one function at a time, if a script is handling a large number of real time events then triggering CallfFunctionNoWait on another worker script (aka "thread") to release processing and grab unfair share of papyris procesing time is a method I have used a lot. But those worker scripts must not depend on any other event states else you will create sequencing issues.

 

I do know that each concurrent timer needs a unique ID as your example would reset timer ID 2000 from 10 seconds to 30 seconds. For maintainability I always give each timer a unique veriablename so I know what it does in the OnTimer... event handler.

Int   iValidateSpawnTimer = 10
Int   iCleanupSpawnTimer  = 20
Int   iWarningMessageTimer  = 30
Int   iCrackerStillExplosionTimer = 40
Int   iSKKShineKnockOutTimer = 50 
Link to comment
Share on other sites

Thanks SKK. Wow - 2 things I got wrong!

So, are you saying everything that happens in a script instance is a single thread? So every event handler in that script needs to wait until there is break in something else running in that script (say a latent call) before it can run? When I look at the log it seems multiple event procedures are running simultaneously (the log entries are mixed together) - and I know there are no latent or external calls in them (or is trace such a call?)...

 

And I have been puting a 'canceltimer' before the second starttimer call because I assumed it would generate 2 events - you are 100% positive the second call just resets the timer? Not how I would have implemented it if I was Bethesda... (and I do use int Const for my timer id's - just not in my example).

Link to comment
Share on other sites

SKK is correct re the timers.

The documentation even states this.

I can also verify this works as expected from successful use of StartTimer() to reset the period on an already running timer.

 

The timer implementation is probably a table containing the calling script, the timer Id and the time the StartTimer() call is meant to expire, while the engine checks the table each frame/papyrus timeslice or whatever period if something's expired. Any new StartTimer() with the same id from the same script will overwrite any already existing entry in the table. What would your implementation have looked like?

 

Also yes, scripts are single threaded and will block. You can verify this with CallFunctionNoWait() and two test scripts.

If you want multithreading, you can use a proxy object with the worker script attached. You can then spawn a number of instances (PlaceAtMe, PlaceAtNode) which can run simultaneously.

 

As you already mentioned, order of messages in the log can not be trusted to be indicative of script execution order.

Link to comment
Share on other sites

Thinking further on this, I am sure you are correct concerning the single threaded nature of procedures invoked in a script - I have seen this stated elsewhere - however that concerns one script calling another.

 

I am almost 100% sure that within my scripts, procedures invoked "by the game itself" (events) are multithreaded. In fact I have had to write threadsafe procedures to handle the fact that multiple animation events can be running in the same script simultaneously (without latent calls confusing things).

 

Seems like I need to sit down and write some tests, as well as closely scrutinize my procedures for 'missed' latent calls that would give the allusion of multithreaded behavior...

 

Edit: niston and I posted simultaneously so I will review what I said above.

Edited by PJMail
Link to comment
Share on other sites

Thanks niston - I missed that reference. Clearly a failure to RTFM... Though I am still lacking a 'nice' way to prevent double timer events if the timer has already fired and the OnTimerEvent is in the thread queue waiting to be unblocked (no way to detect this that I know of). Maybe a 'canceltimer' in the timer event script.

 

I am currently modding on a 'potato' (laptop) so scripts run very slowly which shows up all sorts of timing issues.

Edited by PJMail
Link to comment
Share on other sites

As there is no "IsTimerRunning" query I use bool or int flag variables to track their state say -1 never used, 0 idle, 1 running if I need to track "is the spawn timer running ?" state.

 

Some script functions are sync/blocking (effectivley using CallFunction) and some dispatch asych (effectively using CallFunctionNoWait) which is probably why you can see multiple simultanious animations using PlayAnimation(), but not PlayAnimationAndWait().

 

This is not a full list of latent/blocking functions, but gives an idea: https://www.creationkit.com/fallout4/index.php?title=Latent_Functions

 

Its on individual functions, so if your using latent functions in a high event volume context, calling them in a worker script thread via CallFunctionNoWait helps to cheese the system. Of course robbing other scripts of their latent execution time slices as there are no free lunches in compute time allocation :wink:

 

Also be aware that most scipt functions are "native" which means they take one render frame to execute tying scroipt execution to the game frame rate.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...