Difference between revisions of "Creating Multithreaded Skyrim Mods Part 3 - Callbacks"
Creating Multithreaded Skyrim Mods Part 3 - Callbacks (edit)
Revision as of 17:11, 22 January 2015
, 17:11, 22 January 2015User script updates.
imported>Chesko m |
imported>Chesko (User script updates.) |
||
Line 274: | Line 274: | ||
== Tying it All Together == | == Tying it All Together == | ||
Now that we've created our Threads | Now that we've created our Threads and our Thread Manager, we can start to put them to work. Since we aren't calling the functions we want to execute directly, we need to change how we do things slightly. | ||
The previous execution flow was: | The previous execution flow was: | ||
Line 282: | Line 282: | ||
The flow using threads now is: | The flow using threads now is: | ||
# Call an Async function on our Thread Manager | # Call an Async function on our Thread Manager. | ||
# | # Handle return events as they are raised and store our results. | ||
In our original ActiveMagicEffect script, we did all of our MoveGuardMarkerNearPlayer() and PlaceAtMe() calls in a row, getting a series of Actor references for our guards in return. We're going to modify that slightly to use our shiny new threaded placement system. | |||
In our original ActiveMagicEffect script, we did all of our MoveGuardMarkerNearPlayer() and PlaceAtMe() calls in a row, getting a series of Actor references for our guards in return. We're going to modify that slightly to use our shiny new threaded placement system | |||
<source lang="papyrus"> | <source lang="papyrus"> | ||
scriptname SummonArmy extends ActiveMagicEffect | scriptname SummonArmy extends ActiveMagicEffect | ||
Quest property GuardPlacementQuest auto | Quest property GuardPlacementQuest auto | ||
{We need a reference to our quest with the threads and Thread Manager defined.} | {We need a reference to our quest with the threads and Thread Manager defined.} | ||
Line 298: | Line 297: | ||
ObjectReference Guard1 | ObjectReference Guard1 | ||
ObjectReference Guard2 | ObjectReference Guard2 | ||
... | ;...and so on | ||
ObjectReference | ObjectReference Guard9 | ||
ObjectReference Guard10 | |||
Event OnEffectStart(Actor akTarget, Actor akCaster) | Event OnEffectStart(Actor akTarget, Actor akCaster) | ||
if akCaster == Game.GetPlayer() | if akCaster == Game.GetPlayer() | ||
;Cast the Quest as our Thread Manager and store it | ;Cast the Quest as our Thread Manager and store it | ||
GuardPlacementThreadManager threadmgr = GuardPlacementQuest as GuardPlacementThreadManager | GuardPlacementThreadManager threadmgr = GuardPlacementQuest as GuardPlacementThreadManager | ||
;Register for the callback event | |||
RegisterForModEvent("MyMod_GuardPlacementCallback", "GuardPlacementCallback") | |||
;Call PlaceConjuredGuardAsync for each Guard and store the returned Future | ;Call PlaceConjuredGuardAsync for each Guard and store the returned Future | ||
threadmgr.PlaceConjuredGuardAsync(Guard) | |||
threadmgr.PlaceConjuredGuardAsync(Guard) | |||
;...and so on | ;...and so on | ||
threadmgr.PlaceConjuredGuardAsync(Guard) | |||
threadmgr.PlaceConjuredGuardAsync(Guard) | |||
threadmgr.wait_all() | |||
endif | endif | ||
endEvent | endEvent | ||
Event OnEffectFinish(Actor akTarget, Actor akCaster) | Event OnEffectFinish(Actor akTarget, Actor akCaster) | ||
if akCaster == Game.GetPlayer() | if akCaster == Game.GetPlayer() | ||
Guard1 | DisableAndDelete(Guard1) | ||
DisableAndDelete(Guard2) | |||
;...and so on | |||
DisableAndDelete(Guard9) | |||
DisableAndDelete(Guard10) | |||
endif | endif | ||
endEvent | endEvent | ||
bool locked = false | |||
Event GuardPlacementCallback(Form akGuard) | |||
;A spin lock is required here to prevent us from writing two guards to the same variable | |||
while locked | |||
Utility.wait(0.1) | |||
endWhile | |||
locked = true | |||
ObjectReference myGuard = akGuard as ObjectReference | |||
debug.trace("[Callback] Assigning " + myGuard + "...") | |||
if !Guard1 | |||
Guard1 = myGuard | |||
elseif !Guard2 | |||
Guard2 = myGuard | |||
;...and so on | |||
elseif !Guard9 | |||
Guard9 = myGuard | |||
elseif !Guard10 | |||
Guard10 = myGuard | |||
endif | |||
locked = false | |||
endEvent | |||
function DisableAndDelete(ObjectReference akReference) | |||
akReference.Disable() | |||
akReference.Delete() | |||
endFunction | |||
</source> | |||
Here, instead of doing the work in our script, registered for a callback Mod Event and delegated the work to the Thread Manager. We then called the Thread Manager's <code>wait_all() function to make sure every thread has completed before continuing. Our return values are handed to us when the GuardPlacementCallback() event is raised. | |||
You'll notice that our callback event employs a spin lock. This is very important, since it is possible for two callback events to accidentally write to the same variable using this pattern. | |||
== Notes on Callbacks == | |||
* You can create as many threads as you want, but I wouldn't recommend more than 10 or so. It depends on your needs, the strain each thread places on the Papyrus VM, and how quickly you need your results. | * You can create as many threads as you want, but I wouldn't recommend more than 10 or so. It depends on your needs, the strain each thread places on the Papyrus VM, and how quickly you need your results. |