Difference between revisions of "Threading Notes (Papyrus)"

1,204 bytes added ,  13:37, 21 January 2018
Tried to add some readability
imported>Qazaaq
(line break)
imported>Qazaaq
(Tried to add some readability)
Line 1: Line 1:
Papyrus is a '''''threaded''''' scripting language. In essence, this means that the game can run multiple scripts in parallel. This allows the Creation Engine to manage script processing more effectively. Papyrus scripters should have at least a cursory understanding of the ramifications this creates for them.  
Papyrus is a '''''threaded''''' scripting language.
In essence, this means that the game can run multiple scripts in parallel.
This allows the Creation Engine to manage script processing more effectively.
Papyrus scripters should have at least a cursory understanding of the ramifications this creates for them.


This article attempts to explain threads in layman's terms, in the context of scripting for Papyrus.
This article attempts to explain threads in layman's terms, in the context of scripting for Papyrus.


==Threading==
 
== Threading ==
The basic gist is this:<br>
The basic gist is this:<br>
Only one thing at a time can be doing anything with a script.
Only one thing at a time can be doing anything with a script.
If multiple things (threads) try to manipulate the script at the same time, a "queue" forms of all the threads which essentially wait in line for the script to stop doing something, then it lets the next thread in line in.
If the script calls another function in an external script, or calls a "latent" function (a function that does something on it's own before returning), it lets in the next thing from the cue while the other thread is off doing that other thing.
Then while the new thread is occupying the script, when the previous thread that was off doing it's thing wants to come back and continue processing, now it has to wait in line until the new thread is done with the script (or it calls a latent function or function on another script)
=== Example ===
ObjectA has script with a function called <code>DoSomething()</code>.


If multiple things (threads) try to manipulate the script at the same time, a "queue" forms of all the threads which essentially wait in line for the script to stop doing something, then it lets the next thread in line in.
ObjectB calls <code>ObjectA.DoSomething()</code> at the exact same time ObjectC calls <code>ObjectA.DoSomething()</code>


If the script calls another function in an external script, or calls a "latent" function (a function that does something on it's own before returning), it lets in the next thing from the cue while the other thread is off doing that other thing. Then while the new thread is occupying the script, when the previous thread that was off doing it's thing wants to come back and continue processing, now it has to wait in line until the new thread is done with the script (or it calls a latent function or function on another script)
First ObjectB finishes running ObjectA's <code>DoSomething()</code> and then ObjectC runs it.


Example:
Then first ObjectB calls <code>ObjectA.DoSomething()</code>, and while ObjectA is busy calling <code>Something1()</code> for ObjectB, ObjectC waits in line, not yet processing ObjectA's <code>DoSomething()</code>.
ObjectA has script with a function: DoSomething()


ObjectB calls ObjectA.DoSomething()at the exact same time ObjectC calls ObjectA.DoSomething()
But as soon as ObjectB's thread calls <code>ObjectD.DoSomethingElse()</code> because it has called a function on a different script/object, ObjectA waves in ObjectC who was waiting patiently to call <code>DoSomething()</code>.


First ObjectB finishes running ObjectA's DoSomething() and then ObjectC runs it.
Now while ObjectC's  thread is processing <code>SomeThing1()</code>, ObjectB's thread is done with <code>DoSomethingElse()</code> on ObjectD, but before it runs <code>Something2()</code> it now must wait in line until ObjectC's done with <code>Something1()</code> and itself moves out to ObjectD's <code>DoSomethingElse()</code>. As soon as ObjectC's thread heads out to <code>DoSomethingElse()</code> in ObjectD, ObjectA waves in ObjectB's thread who is returning from ObjectD's <code>DoSomethingElse()</code> and will now be allowed to continue on with <code>Something2()</code>... and so on...


But if DoSomething() in a script on ObjecttA looks like this:
<source lang="Papyrus">
<source lang="Papyrus">
Function DoSomething()
Scriptname ScriptA extends ObjectReference
{Attached to object A}
 
ScriptD Property ObjectD Auto Const Mandatory
 
Function DoSomething()
     SomeThing1() ;another function in ObjectA
     SomeThing1() ;another function in ObjectA
     ObjectD.DoSomethingElse() ;a function in a script on ObjectD  
     ObjectD.DoSomethingElse() ;a function in a script on ObjectD
     Something2() ;Another function in ObjectA
     Something2() ;Another function in ObjectA
EndFunction
EndFunction
 
Function SomeThing1()
    ; some things
EndFunction
 
Function SomeThing2()
    ; some things
EndFunction
</source>
<source lang="Papyrus">
Scriptname ScriptB extends ObjectReference
{Attached to object B}
 
ScriptA Property ObjectA Auto Const Mandatory
 
Event OnActivate(ObjectReference akActionRef)
    ObjectA.DoSomething()
EndEvent
</source>
</source>
Then first ObjectB calls ObjectA.DoSomething(), and while ObjectA is busy calling Something1() for ObjectB, ObjectC waits in line, not yet processing ObjectA's DoSomething().
<source lang="Papyrus">
Scriptname ScriptC extends ObjectReference
{Attached to object C}


But as soon as ObjectB's thread calls ObjectD.DoSomethingElse() because it has called a function on a different script/object, ObjectA waves in ObjectC who was waiting patiently to call DoSomething().
ScriptA Property ObjectA Auto Const Mandatory


Now while ObjectC's  thread is processing SomeThing1(), ObjectB's thread is done with DoSomethingElse on ObjectD, but before it runs Something2() it now must wait in line until ObjectC's done with Something1() and itself moves out to ObjectD's DoSomethingElse(). As soon as ObjectC's thread heads out to DoSomethingElse() in ObjectD, ObjectA waves in ObjectB's thread who is returning from ObjectD's DoSomethingElse() and will now be allowed to continue on with Something2()... and so on...
Event OnActivate(ObjectReference akActionRef)
    ObjectA.DoSomething()
EndEvent
</source>
<source lang="Papyrus">
Scriptname ScriptD extends ObjectReference
{Attached to object D}


==Threading: A Basic Example==
ScriptA Property ObjectA Auto Const Mandatory
 
Event OnActivate(ObjectReference akActionRef)
    ObjectA.DoSomething()
EndEvent
 
Function DoSomethingElse()
    ; something else
EndFunction
</source>
 
 
=== A Basic Example ===
In the image below, the player is activating a lever.  Because this a nice and cooperative player, he's only activated it once.  The game sends Papyrus a notification of the activate event.  Because our script contains an '''onActivate()''' Event, the game creates a '''"Thread"''', which you can think of as a set of instructions copied from our script. The game queues this thread up, and will process it momentarily.
In the image below, the player is activating a lever.  Because this a nice and cooperative player, he's only activated it once.  The game sends Papyrus a notification of the activate event.  Because our script contains an '''onActivate()''' Event, the game creates a '''"Thread"''', which you can think of as a set of instructions copied from our script. The game queues this thread up, and will process it momentarily.


Line 74: Line 126:
</source>
</source>


===Note about tracing "GoToState()"===
==== Note about tracing "GoToState()" ====
In the previous example, take note of where goToState() is called.  Why? Because setValue() is in an external script, it creates an opportunity for another thread to start processing your script before you go to the state you expect it to be in.  Therefore, go to your desired state first.  Your thread will complete the instructions below the gotoState() call just fine, and future threads will treat the script as being in the state - "XYZ" in the example below - when they encounter it.
In the previous example, take note of where goToState() is called.  Why? Because setValue() is in an external script, it creates an opportunity for another thread to start processing your script before you go to the state you expect it to be in.  Therefore, go to your desired state first.  Your thread will complete the instructions below the gotoState() call just fine, and future threads will treat the script as being in the state - "XYZ" in the example below - when they encounter it.


Line 88: Line 140:
</source>
</source>


==Another example==
=== Another example ===
From an email to Jeff asking if calling IncrementMyProperty from an external script multiple times in a row was "thread safe."
From an email to [[User:JBurgess|Jeff]] asking if calling IncrementMyProperty from an external script multiple times in a row was "thread safe."




Line 134: Line 186:
'''If myProperty is NOT in your local script, then “myProperty += 1” may not increment.''' This is because it resolves to “myProperty.set(myProperty.get() + 1)”, and myProperty’s value may change between the get and set calls since it isn’t on your own script. [Editor's note: Which is why you would write a IncrementMyProperty() function in that other script -- JPD ]
'''If myProperty is NOT in your local script, then “myProperty += 1” may not increment.''' This is because it resolves to “myProperty.set(myProperty.get() + 1)”, and myProperty’s value may change between the get and set calls since it isn’t on your own script. [Editor's note: Which is why you would write a IncrementMyProperty() function in that other script -- JPD ]


-Jeff
-[[User:JBurgess|Jeff]]
 
== See Also ==
*[[:Category:Latent_Functions|Latent Functions Category]]


==See Also==
*[[:Category:Latent_Functions]]


[[Category: Papyrus]]
[[Category: Papyrus]]
Anonymous user