imported>Chesko |
imported>Chesko |
Line 101: |
Line 101: |
| ;Called from Event OnGuardPlacement | | ;Called from Event OnGuardPlacement |
| function MoveGuardMarkerNearPlayer(ObjectReference akMarker) | | function MoveGuardMarkerNearPlayer(ObjectReference akMarker) |
| ;Move the marker away from the player a random distance and direction in 75.0 game unit increments | | ;Some difficult calculations, etc |
| Actor player = Game.GetPlayer()
| |
| Float A = player.GetAngleZ() + (Utility.RandomInt(1, 24) * 15.0)
| |
| Float YDist = math.Sin(A)
| |
| Float XDist = math.Cos(A)
| |
| XDist *= (Utility.RandomInt(1, 5) * 75.0)
| |
| YDist *= (Utility.RandomInt(1, 5) * 75.0)
| |
| akMarker.MoveTo(player, XDist, YDist)
| |
| EndFunction | | EndFunction |
| | | |
Line 187: |
Line 180: |
| <source lang="papyrus"> | | <source lang="papyrus"> |
| scriptname GuardPlacementThreadManager extends Quest | | scriptname GuardPlacementThreadManager extends Quest |
| | | |
| Quest property GuardPlacementQuest auto | | Quest property GuardPlacementQuest auto |
| {The name of the thread management quest.} | | {The name of the thread management quest.} |
| | | |
| Activator property GuardPlacementFutureActivator auto
| |
| {Our Future object.}
| |
| | |
| ObjectReference property GuardPlacementFutureAnchor auto
| |
| {Our Future Anchor object reference.}
| |
| | |
| Static property XMarker auto | | Static property XMarker auto |
| {Something a thread needs; our threads don't declare their own properties.} | | {Tedious to define properties in the threads and hook up in CK over and over, so define things we need here. MoveGuardMarkerNearPlayer() needs XMarkers.} |
|
| |
|
| GuardPlacementThread01 thread01 | | GuardPlacementThread01 thread01 |
Line 205: |
Line 192: |
| GuardPlacementThread09 thread09 | | GuardPlacementThread09 thread09 |
| GuardPlacementThread10 thread10 | | GuardPlacementThread10 thread10 |
| | | |
| Event OnInit() | | Event OnInit() |
| ;Register for the event that will start all threads | | ;Register for the event that will start all threads |
Line 218: |
Line 205: |
| thread10 = GuardPlacementQuest as GuardPlacementThread10 | | thread10 = GuardPlacementQuest as GuardPlacementThread10 |
| EndEvent | | EndEvent |
| | | |
| ;The 'public-facing' function that our MagicEffect script will interact with. | | ;The 'public-facing' function that our MagicEffect script will interact with. |
| ObjectReference function PlaceConjuredGuardAsync(ActorBase akGuard)
| | function PlaceConjuredGuardAsync(ActorBase akGuard) |
| int i = 0
| | if !thread01.queued() |
| ObjectReference future
| | debug.trace("[Callback] Selected thread01") |
| while !future
| | thread01.get_async(akGuard, XMarker) |
| if !thread01.queued()
| | elseif !thread02.queued() |
| future = thread01.get_async(GuardPlacementFutureActivator, GuardPlacementFutureAnchor, akGuard, XMarker)
| | debug.trace("[Callback] Selected thread02") |
| elseif !thread02.queued()
| | thread02.get_async(akGuard, XMarker) |
| future = thread02.get_async(GuardPlacementFutureActivator, GuardPlacementFutureAnchor, akGuard, XMarker)
| | ;...and so on |
| ...
| | elseif !thread09.queued() |
| elseif !thread09.queued()
| | debug.trace("[Callback] Selected thread09") |
| future = thread09.get_async(GuardPlacementFutureActivator, GuardPlacementFutureAnchor, akGuard, XMarker)
| | thread09.get_async(akGuard, XMarker) |
| elseif !thread10.queued()
| | elseif !thread10.queued() |
| future = thread10.get_async(GuardPlacementFutureActivator, GuardPlacementFutureAnchor, akGuard, XMarker)
| | debug.trace("[Callback] Selected thread10") |
| else
| | thread10.get_async(akGuard, XMarker) |
| ;All threads are queued; start all threads, wait, and try again.
| | else |
| wait_all()
| | ;All threads are queued; start all threads, wait, and try again. |
| endif
| | wait_all() |
| endWhile | | PlaceConjuredGuardAsync(akGuard) |
| | | endif |
| return future
| |
| endFunction | | endFunction |
| | | |
| function wait_all() | | function wait_all() |
| RaiseEvent_OnGuardPlacement() | | RaiseEvent_OnGuardPlacement() |
Line 267: |
Line 253: |
| endFunction | | endFunction |
|
| |
|
| ;A helper function that can avert permanent thread failure if something goes wrong
| | ;Create the ModEvent that will start this thread |
| function TryToUnlockThread(ObjectReference akFuture)
| |
| bool success = false
| |
| if thread01.has_future(akFuture)
| |
| success = thread01.force_unlock()
| |
| elseif thread02.has_future(akFuture)
| |
| success = thread02.force_unlock()
| |
| ;...and so on
| |
| elseif thread09.has_future(akFuture)
| |
| success = thread09.force_unlock()
| |
| elseif thread10.has_future(akFuture)
| |
| success = thread10.force_unlock()
| |
| endif
| |
|
| |
| if !success
| |
| debug.trace("Error: A thread has encountered an error and has become unresponsive.")
| |
| else
| |
| debug.trace("Warning: An unresponsive thread was successfully unlocked.")
| |
| endif
| |
| endFunction
| |
| | |
| ;Create the ModEvent that will start all threads | |
| function RaiseEvent_OnGuardPlacement() | | function RaiseEvent_OnGuardPlacement() |
| int handle = ModEvent.Create("MyMod_OnGuardPlacement") | | int handle = ModEvent.Create("MyMod_OnGuardPlacement") |
Line 300: |
Line 265: |
|
| |
|
|
| |
|
| The PlaceConjuredGuardAsync() function handles making sure that our work gets delegated to an available thread. The function then returns a <code>Future</code> once an available thread is found. | | The PlaceConjuredGuardAsync() function handles making sure that our work gets delegated to an available thread. |
|
| |
|
|
| |
|
Line 311: |
Line 276: |
|
| |
|
| image here | | image here |
|
| |
|
| |
| == Back to the Future ==
| |
|
| |
| '''Futures''' are [https://cloud.google.com/appengine/docs/python/ndb/futureclass a concept from parallel processing]. It can be thought of as a placeholder in lieu of your result until your result has arrived. Like the Google App Engine version that this was inspired by, when the Future is created, it will probably not have any results yet. Your script can store a <code>Future</code> and later call the <code>Future</code> object's <code>get_result()</code> function, which should return your results immediately.
| |
|
| |
|
| |
| {{InDepth|A few notes about Futures:
| |
| * Futures '''contain the result''' of a thread that has finished.
| |
| * Futures are lightweight Activator ObjectReferences placed in an unloaded cell.
| |
| * A <code>Future</code> is '''temporary''', and exists until the result is read, after which, the <code>Future</code> is destroyed. Make sure to save your results to your own variable if you will need them later, since the <code>Future</code> will no longer exist after calling <code>get_result()</code>. This keeps the number of ObjectReferences created under control, and helps prevent save game size bloat.
| |
| * <code>get_result()</code> is technically ''blocking'', meaning it waits until results are received from the thread, and then returns. However, since <code>wait_all()</code> waits for all threads to complete, there should be no reason you should have to wait on your results when calling this function, unless something went wrong.
| |
| * The result of a <code>Future</code> is the same as the result of any other function in Papyrus, and can return None, false, etc if an error is encountered. Code the result of a <code>Future</code> like you would the result of anything else and anticipate errors accordingly.
| |
| * Futures will attempt to unlock threads that have become unresponsive.}}
| |
|
| |
|
| |
| Let's create our Future:
| |
|
| |
|
| |
| <source lang="papyrus">
| |
| scriptname GuardPlacementFuture extends ObjectReference
| |
|
| |
| Quest property GuardPlacementQuest auto
| |
|
| |
| ObjectReference r
| |
| ObjectReference property result hidden
| |
| function set(ObjectReference akResult)
| |
| done = true
| |
| r = akResult
| |
| endFunction
| |
| endProperty
| |
|
| |
| bool done = false
| |
| bool function done()
| |
| return done
| |
| endFunction
| |
|
| |
| ObjectReference function get_result()
| |
| ;Terminate the request after 10 seconds, or as soon as we have a result
| |
| int i = 0
| |
| while !done && i < 100
| |
| i += 1
| |
| utility.wait(0.1)
| |
| endWhile
| |
| RegisterForSingleUpdate(0.1)
| |
|
| |
| if i >= 100
| |
| ;Our thread probably encountered an error and is locked up; we need to unlock it.
| |
| (GuardPlacementQuest as GuardPlacementThreadManager).TryToUnlockThread(self as ObjectReference)
| |
| endif
| |
| return r
| |
| endFunction
| |
|
| |
| Event OnUpdate()
| |
| self.Disable()
| |
| self.Delete()
| |
| endEvent
| |
| </source>
| |
|
| |
|
| |
| This script should be '''compiled and attached to the Future Activator''' object we created earlier. After you've attached it, make sure to '''fill the properties.'''
| |
|
| |
|
| |
| {{ProTip|Note the Type of the result; this could be changed to any data type you need to return.}}
| |
|
| |
|
| |
| {{ProTip|As a best practice, only interface with the <code>Future</code> using its member functions, <code>done()</code> and <code>get_result()</code>.}}
| |
|
| |
|
| |
| === Quick Detour: Revisiting the Thread Script ===
| |
|
| |
|
| |
| There was a line from our thread script that was commented out, because our Future script didn't exist yet:
| |
|
| |
| <source lang="papyrus">
| |
| ;(future as GuardPlacementFuture).result = result
| |
| </source>
| |
|
| |
| Go back and uncomment this line and recompile the parent thread script. You don't need to recompile all of the children.
| |
|
| |
|
| |
| {{WarningBox|This is important! If you don't uncomment this line, your thread will never return results to the Future!}}
| |
|
| |
|
|
| |
|