[Tutorial] States in Papyrus

Post » Thu Jun 21, 2012 5:38 pm

Hi everyone! Just a notification that I've put a new tutorial up in cipscis.com's Skyrim section, talking about how to use states. Hopefully some of you will find it useful.

Here's a link - http://www.cipscis.com/skyrim/tutorials/states.aspx

As always, feedback is appreciated.

Cipscis
User avatar
Angelina Mayo
 
Posts: 3427
Joined: Wed Jan 24, 2007 4:58 am

Post » Thu Jun 21, 2012 11:01 am

Great explanation as always. The only lack I can find (maybe I didn't search well enough), is not includding info on how to return to the empty state ( gotostate(none) ).
User avatar
Sammi Jones
 
Posts: 3407
Joined: Thu Nov 23, 2006 7:59 am

Post » Thu Jun 21, 2012 9:56 am

Great explanation as always. The only lack I can find (maybe I didn't search well enough), is not includding info on how to return to the empty state ( gotostate(none) ).
Arguably, if you're using states at all then at least one of them should be Auto, and you should only leave in the empty state those event handlers or functions that are common to all or several states. That way, your state machine is more easily readable, and your logic control is more explicit both to you and others.

Just a thought :smile:.

[edit]
Cipsis, maybe have a sub-section at the beginning ("For novices") which explains what states are for. That is, what sort of logical and coding problems thay can simplify or avoid. Then explain the ins and outs of using them.
User avatar
P PoLlo
 
Posts: 3408
Joined: Wed Oct 31, 2007 10:05 am

Post » Thu Jun 21, 2012 8:20 pm

The final section is very clear and it will be helpful to refer to in the future. I see a number of questions come up about dealing with concurrency. Local booleans are often used but I think states are much cleaner.

So great to see you posting again Cipscis. The wiki is a great reference but articles like this really help people new to Papyrus. :goodjob: As Amgepo already noted, only thing I didn't see was mention of GotoState("").
User avatar
Richus Dude
 
Posts: 3381
Joined: Fri Jun 16, 2006 1:17 am

Post » Thu Jun 21, 2012 10:21 pm

Arguably, if you're using states at all then at least one of them should be Auto, and you should only leave in the empty state those event handlers or functions that are common to all or several states. That way, your state machine is more easily readable, and your logic control is more explicit both to you and others.

Just a thought :smile:.
I can't agree. That symply deppends on the type of script you are doing. If you are doing a great script with a lot of funcions and events, with only one or few needing different states, using the main body in a states will only force you to update the empty state each time you add or remove a function and the duplicity of parts won't help the reading of the code preciselly. I'm working on a such script and must say that doing so would have been a nightmare.
User avatar
Ricky Meehan
 
Posts: 3364
Joined: Wed Jun 27, 2007 5:42 pm

Post » Thu Jun 21, 2012 3:02 pm

I can't agree. That symply deppends on the type of script you are doing. If you are doing a great script with a lot of funcions and events, with only one or few needing different states, using the main body in a states will only force you to update the empty state each time you add or remove a function and the duplicity of parts won't help the reading of the code preciselly. I'm working on a such script and must say that doing so would have been a nightmare.
Heh, well I did say 'arguably' :biggrin:.

I'm a little puzzled, though; why would you need to update the empty state each time you add a function? Or rather, where is the problem with doing so, and why would there be duplication? If the current state doesn't have a function or event handler, then the one in the empty state will be called, so you'd have your explicit "Default" state (which may consist only of an OnActivate event handler, or a DoMainThing function), and any function which is shared by more than one state (in your example, most of them) would be defined in the empty state.

My point is, the Auto state may be little more than an indication that there is a default state, but being explicit (rather than the empty state's implicit) will make things clearer and more readable. More of a documentation thing, I suppose, but not needing any duplication.

[edit]
Ok, looking at Cipsis's first example, I see where you're coming from. I think of states a bit differently, and (for example) would have written his first example as
ScriptName Statesixample extends ObjectReferenceFunction SharedStuff()     ;Do stuffEndFunctionAuto State Inactive   Event OnActivate(ObjectReference akActionRef)      GoToState("Active")   EndEvent   Function InactiveOnlyStuff ()      ;Do stuff   EndFunctionEndStateState Active   Event OnActivate(ObjectReference akActionRef)      GoToState("Inactive")   EndEvent   Function ActiveOnlyStuff ()      ;Do stuff   EndFunctionEndState
A difference of coding style :shrug:.
User avatar
Quick Draw III
 
Posts: 3372
Joined: Sat Oct 20, 2007 6:27 am

Post » Thu Jun 21, 2012 10:57 am

Could be called a difference of codding style or a difference of paradigm. Anyway the convenience of doing it one way or the other depends on to what amount your code works around states or simply use them.
User avatar
louise tagg
 
Posts: 3394
Joined: Sun Aug 06, 2006 8:32 am

Post » Thu Jun 21, 2012 9:39 am

Ive been confused about states for a long time now. This cleared everything up. Great tutorial!
User avatar
Ally Chimienti
 
Posts: 3409
Joined: Fri Jan 19, 2007 6:53 am

Post » Thu Jun 21, 2012 5:05 pm

There isn't really a way to send a script back into the empty state, but sending it into a state that hasn't been or can't be defined (like GoToState("")) will do essentially return the same thing.

I touch on this near the end of the tutorial, but perhaps I should make it more explicit elsewhere:
Once the body of LongFunction is complete, it is sent to the "Waiting" state. This script doesn't have a "Waiting" state defined, so for all intents and purposes it's being sent back into the empty state. I could have created a "Waiting" state to use, but that would still have required me to write a function definition for LongFunction in the empty state so I decided to just use that.
@andyw:
I was kind of hoping that the examples I use in the tutorial would demonstrate what states are useful for, but I can see how that sort of description can be useful. How does this sound to you?
States are particularly useful when you're writing a script that should act differently under different circumstances, like a light switch. By defining a state for each of these circumstances, you can define the behaviour of your script in such a way that it is always appropriate for the current scenario.

Cipscis
User avatar
lolly13
 
Posts: 3349
Joined: Tue Jul 25, 2006 11:36 am

Post » Thu Jun 21, 2012 10:50 am

Useful tutorial.

One point I'm hazy on - given that a script can have many instances, and every instance can have many threads, at what level is state defined?

My impression is that each instance can be in a different state, but a given instance can only be in one state at a time, so all threads running on that instance see the same state?
User avatar
phil walsh
 
Posts: 3317
Joined: Wed May 16, 2007 8:46 pm

Post » Thu Jun 21, 2012 8:52 am

Once the state is changed all future instances will use that state, but a change of state won't affet ongoing instances including the one changing the state.
User avatar
Amber Ably
 
Posts: 3372
Joined: Wed Aug 29, 2007 4:39 pm

Post » Thu Jun 21, 2012 11:44 pm

Awesome tutorial, Cipscis :) Thanks!

Local booleans are often used but I think states are much cleaner.

Kinda reminds me of Cipscis' http://www.gamesas.com/topic/987561-script-optimisation/page__view__findpost__p__14279570 threads for the GECKs. I set up a horse race of sorts with a light switch using two different setups (Pegasus vs. Fluke):
Spoiler

  • A boolean toggle (Pegasus)
    Event OnActivate(ObjectReference akActivator)	bLightsOn = !bLightsOn	If bLightsOn		If rLight			rLight.Enable()		Else			rLight = PlaceAtMe(LightBase)		EndIf	Else		rLight.Disable()	EndIfEndEvent
  • Two States (Fluke)
    Auto State LightsOff	Event OnActivate(ObjectReference akActivator)		If rLight			rLight.Enable()		Else			rLight = PlaceAtMe(LightBase)		EndIf		GoToState("LightsOn")	EndEventEndStateState LightsOn	Event OnActivate(ObjectReference akActivator)		rLight.Disable()		GoToState("LightsOff")	EndEventEndState
...then tested both with...
Spoiler
Function TestSpeed(Int aiIterations = 0, Float afBeginTime = 0.0, Float afEndTime = 0.0)	afBeginTime = Utility.GetCurrentRealTime()	Debug.Trace("Start: " + afBeginTime)	While (aiIterations < 1000)		aiIterations += 1		Activate(Game.GetPlayer())	EndWhile	afEndTime = Utility.GetCurrentRealTime()	Debug.Trace("Finish: " + afEndTime)	Debug.Trace("Time Elapsed: " + (afEndTime - afBeginTime))EndFunction

Results:
Spoiler
Pegasus wins by a nose.
  • Pegasus
    Spoiler
    [06/04/2012 - 05:04:38AM] Start: 37.200001[06/04/2012 - 05:05:04AM] Finish: 63.493000[06/04/2012 - 05:05:04AM] Time Elapsed: 26.292999
  • Fluke
    Spoiler
    [06/04/2012 - 05:06:44AM] Start: 40.375999[06/04/2012 - 05:07:10AM] Finish: 66.750000[06/04/2012 - 05:07:10AM] Time Elapsed: 26.374001
In game, both behaved identically.
User avatar
roxanna matoorah
 
Posts: 3368
Joined: Fri Oct 13, 2006 6:01 am

Post » Thu Jun 21, 2012 9:46 pm

@andyw:
I was kind of hoping that the examples I use in the tutorial would demonstrate what states are useful for, but I can see how that sort of description can be useful. How does this sound to you?

Cipscis
Perhaps add "This helps to structure your scripts, avoiding extremely complicated or nested 'If-Elseif' structures that can slow a script down. By making a script more structured and easier to read, States can also help you avoid mistakes in your logic and code."

Or something like that :smile:?

[edit]

Awesome tutorial, Cipscis :smile: Thanks!



Kinda reminds me of Cipscis' Script Optimisation threads for the GECKs. I set up a horse race of sorts with a light switch using two different setups (Pegasus vs. Fluke):
[snip]
I reckon that time difference is within random variation, yes? As a matter of interest, did you try putting the 'PlaceAtMe' into an OnInit event, and losing that logic from your State machine? Would that have much effect on the timing?
User avatar
Jinx Sykes
 
Posts: 3501
Joined: Sat Jan 20, 2007 11:12 pm

Post » Thu Jun 21, 2012 6:14 pm

'If-Elseif' structures that can slow a script down
The If-ElseIf slowed Pegasus down ~0.15 seconds.
I reckon that time difference is within random variation, yes? As a matter of interest, did you try putting the 'PlaceAtMe' into an OnInit event, and losing that logic from your State machine? Would that have much effect on the timing?
It does seem to vary, minutely.

I placed the light in an OnInit Event with 'rLight = PlaceAtMe(LightBase, 1, False, True)'.

Results:
Spoiler
  • Bool toggle (Time Elapsed: 26.160000)
    Event OnActivate(ObjectReference akActivator)	bLightsOn = !bLightsOn	If bLightsOn		rLight.Enable()	Else		rLight.Disable()	EndIfEndEvent
  • State toggle (Time Elapsed: 26.181995)
    Auto State LightsOff	Event OnActivate(ObjectReference akActivator)		rLight.Enable()		GoToState("LightsOn")	EndEventEndStateState LightsOn	Event OnActivate(ObjectReference akActivator)		rLight.Disable()		GoToState("LightsOff")	EndEventEndState
  • Function call (Time Elapsed: 26.301998)
    Event OnActivate(ObjectReference akActivator)	If rLight.IsDisabled()		rLight.Enable()	Else		rLight.Disable()	EndIfEndEvent

Edit: Swapping 'Game.GetPlayer()' with a PlayerREF property for the TestSpeed function speeds up the process considerably.

Bool toggle called with property (Time Elapsed: 12.858002)
Bool toggle with function (Time Elapsed: 26.160000)
User avatar
Samantha hulme
 
Posts: 3373
Joined: Wed Jun 21, 2006 4:22 pm

Post » Thu Jun 21, 2012 10:19 pm

There isn't really a way to send a script back into the empty state, but sending it into a state that hasn't been or can't be defined (like GoToState("")) will do essentially return the same thing.

"" is the empty state :). (Hence the name "empty" state) While putting your script into a state you don't use has the same effect, you run the risk of introducing unexpected behavior if you later define the state in that script, one it extends, or one that extends it.
User avatar
Jason White
 
Posts: 3531
Joined: Fri Jul 27, 2007 12:54 pm

Post » Thu Jun 21, 2012 8:20 pm

The If-ElseIf slowed Pegasus down ~0.15 seconds.
I did say "extremely complicated or nested" :). Not a criticism that could be levelled at that speed test.

It seems to show that a state machine (unsurprisingly) has a small execution overhead, so isn't appropriate for the simplest of cases that would be implemented more efficiently with booleans. But with a very large and complicated script handling a variety of states, I'd expect implementing it as a state machine will increasingly rival conditional statements for efficiency. But, I agree, given the comparatively small impact of conditional statements over function calls (especially latent ones) perhaps speed improvements aren't worth mentioning.

Readability and ease of maintenance are, I think, big factors.
User avatar
Anthony Santillan
 
Posts: 3461
Joined: Sun Jul 01, 2007 6:42 am

Post » Thu Jun 21, 2012 11:12 pm

As I understood it, the optimization benefits of states kick in when you can declare completely empty events, which the compiler can toss out altogether, rather than having to check a boolean before exiting.
User avatar
Hazel Sian ogden
 
Posts: 3425
Joined: Tue Jul 04, 2006 7:10 am

Post » Thu Jun 21, 2012 2:01 pm

As I understood it, the optimization benefits of states kick in when you can declare completely empty events, which the compiler can toss out altogether, rather than having to check a boolean before exiting.
Exactly. You can find other beneficts depending on how you preffer to organice your scripts, but the conditional use of an event where you can use it fully or not use it at all is undeniably an advantage. You could also do it with aliases, but this way you can do it only with scripting (besides the obvious gain in speed in the switch).
User avatar
Lizbeth Ruiz
 
Posts: 3358
Joined: Fri Aug 24, 2007 1:35 pm


Return to V - Skyrim