Game engine vs Papyrus efficiency Questions

Post » Wed Mar 11, 2015 5:21 am

I would like to preface by saying that while this post is rather long, please do not be deterred. I have organized it by importance and broadness at the top, and it gets more specific toward the end, so you can bail anytime you feel you've lost interest, but give it a shot. If you think I am a more experienced modder than you, read on the first part, and you may learn something from me, but I am just beginning my computer science major, and my to begin, I am requesting anyone to correct me where I am wrong:

If it can be done without papyrus, do it without papyrus.


I asked a few questions about this to Egocarib, who essentially taught me the above philosophy, and I've gathered some ideas: the engine was build to optimize these condition checks based on relevance, and thus could blow through them rather quickly; meanwhile papyrus scripts are assembled in the broadest most universally compatible ways possible. At the same time, papyrus appears to be hastily designed to be as accessible to modders as possible, and bethesda assumes that a modder prioritizes their own scripts functions highest.

In short, I theorize that papyrus calls to the engine essentially come in bluntly screaming to top priority, devoid of the fundamental agility that similar engine calls have, bogging down all performance.

                                                                          Example: Adding a spell to the playerWith papyrus, you can just use the following function in one of your scripts: game.getPlayer().addspell(exampleSpell)                                                          - OR -In the creation kit, you could make a quest with a referenceAlias for the player to which you include the spell.The latter is much easier to throw together, whereas the quest is an extra object to add, as well as consideration like how/when to begin the quest, but it's more efficient.

So okay, knowing that the CK is better for doing the exact same things you try to do with papyrus is great, but there's a very important consideration of how much better, for which I am essentially begging understanding/education. Papyrus is really tempting because it can more directly get at the things you want to do and is way less tedious. Sometimes the gap between how much more is involved to accomplish the same things with CK assets begs the question of "is it better to just go at this with papyrus?"

                                   Example: responding to the player dropping low in stamina, by checking its percent value every second. With papyrus:Event onUpdate()  if game.getPlayer().getActorValuePercentage("Magicka") <= 0.1   myImportantFunction()  endif  RegisterForSingleUpdate(1.0)EndEvent or again, more efficiently in the Creation KitCreate an ability spell and attach a custom magic effect with the conditions getCombatState != 0 run on Subject and getActorValuePercent Stamina <= 9 run on Subject, and then attach the following script to the magic effect:Event onInit()  myImportantFunction()EndEvent

So now I get to the things I don't know for certain. Say I want to monitor the %health of an enemy in combat. I could do the following

while enemyActor.getCombatState()  currentHealth = enemyActor.getActorValuePercentage("health")  utility.wait(1.0)endwhile

That can give me precise amount every second, but I get the impression it's rather taxing on the system. I can track it just as precisely by making 100 unique magic effects, with 100 unique corresponding scripts like:

_permanantScript property otherScript auto hidden event onInit()  otherScript.currentHealth = 9  endevent

But not only wow is that a lot of work to put together, that makes for the game checking 100 conditions every second in that spell. Is that still more efficient than the single papyrus function? If it's a spectrum, then it could be brought down to relevant ranges of health, so maybe only 10 effects, but I'm still want to know:

What is this spectrum?!

So, I'll give just one more example of the rising complexity I'm dealing with my mod, though the following can be answered with a comprehensive understanding of the above question. Assuming that CK value checking is the way to go, I'd still strive to optimize that design for further efficiency, because if I'm trying to track not just health of a single enemy, but all sorts of information about multiple enemies, I wouldn't want to bog down the system checking on information that isn't going to be used. I can imagine limiting these condition checks in two ways: after identifying the enemy and therefore what information is relevant, I can change the values of globalvariables that are the first "AND" condition checks of each effect, and I presume should it fail, the system gives up checking the values of all other conditions for that effect. Secondly, I could have multiple spells with different loadouts of magiceffects, and swap to the appropriate one. So instead of having a single universal monitor spell checking over a 100 conditions every second every enemy, I could have different spells for each enemy, each checking roughly 10 conditions every second. Both of these options still involve papyrus, though. For the former option, I need to change the values of global variables, and for the latter, I need to be removing and adding spells. So, now I need to know how taxing that is, and how much, to know how much of each of these options I want to be utilizing (as they are not mutually exclusive).

I think I've written enough for a single post. I am going to elaborate more on what I'm doing specifically in the second post.

User avatar
Jeremy Kenney
 
Posts: 3293
Joined: Sun Aug 05, 2007 5:36 pm

Post » Wed Mar 11, 2015 2:41 am

things i've learned:

  • the most efficient way to collect information is events
  • update loops svck beyond svck - avoid
  • papyrus condition checks have the same impact on performance as ck derived conditions effects, but the latter get higher priority
User avatar
OnlyDumazzapplyhere
 
Posts: 3445
Joined: Wed Jan 24, 2007 12:43 am

Post » Wed Mar 11, 2015 8:10 am

As far as I have seen, whenever a Papyrus script seems to take a long time to do something, it turns out that 90% of that latency can be attributed to not actually *doing* anything, but simply waiting.

Specifically, waiting for delayed native functions.

Every single time you call any native function that is not non-delayed (http://www.creationkit.com/Category:Non-delayed_Native_Function), your script will stop and wait for the next frame draw. If you have a lot of those calls in a row, then the time between each frame draw will be spent doing literally nothing at all: the first call will make your script wait for a frame, then your script will resume, make one more native call, and go back to waiting for the next frame draw, and so on.

Chesko put together a whole tutorial on multi-threaded design which mitigates this issue somewhat, but in the end, optimizing the performance of a Papyrus script ends up being mostly about making as few native calls as possible just to cut down on all that waiting.

User avatar
Vincent Joe
 
Posts: 3370
Joined: Wed Sep 26, 2007 1:13 pm

Post » Wed Mar 11, 2015 4:22 am

I see... that actually explains quite a lot.

User avatar
Tiffany Carter
 
Posts: 3454
Joined: Wed Jul 19, 2006 4:05 am

Post » Wed Mar 11, 2015 12:18 pm

While condition checks you set up in the CK run faster they still affect system performance so you wouldn't want to replace one Papyrus call with 100 condition checks. I don't know where the exact performance line is drawn, but that certainly crossed over. Condition checks for most things (like spells) are made every second.

In previous TES/FO games the scripting system automatically ran scripts every frame. Modders got used to that and initially brought that style of coding over to Skyrim by setting up giant OnUpdate functions registered to run as frequently as possible. The performance hit was noticed almost immediately and after lots of discussion the consensus became that you needed to register for the longest period of time that still gave acceptable performance for your mod. But that style of coding is fundamentally wrong given the design of the Papyrus language and the way it interacts with the rest of the game.

The Papyrus scripting engine is designed to be used in an event-driven fashion. If you find yourself using either a loop with Utility.Wait or repeated OnUpdate calls for anything other than very specific activities for a very limited time, then you're probably not doing things in the most efficient way. If you can find events that let you accomplish your goal, they you should use them. If your task is something that is not time-critical but needs continual checksing, then OnUpdate with a reasonably long registration time is good. If you do need to monitor a value constantly and can do it with conditions, then you should. And only if there is no other option should you use the dreaded OnUpdate-RegisterForSingleUpdate(0.1) sequence.

Here's an example using SKSE functions. The first SKSE functions for dealing with keyboard input were based on the same logic used in previous games, so to use them you would write:
Event OnUpdate()	if Input.IsKeyPressed(ItemUpKey)		MoveItemUp()	elseif Input.IsKeyPressed(ItemDownKey)		MoveItemDown()	elseif Input.IsKeyPressed(ItemLeftKey)		MoveItemLeft()	elseif Input.IsKeyPressed(ItemRightKey)		MoveItemRight()	endif	RegisterForSingleUpdate(UpdateCheckTime)EndEvent
In this code I'm checking to see if any one of 4 different keys is pressed. If I set UpdateCheckTime to a small value then the code is more responsive to key presses, but I put a greater strain on the system. Most of the time none of the keys will be pressed, so the function will run for 4 frames (because of the 4 IsKeyPressed calls) and then re-register itself. That's five function calls that don't actually accomplish any real task and lots of overhead as the Papyrus engine manages the script activity.

Here's the new event-based SKSE input scheme.
Event OnKeyDown(int KeyCode)	if KeyCode == ItemUpKey		MoveItemUp()	elseif KeyCode == ItemDownKey		MoveItemDown()	elseif KeyCode == ItemLeftKey		MoveItemLeft()	elseif KeyCode == ItemRightKey		MoveItemRight()	endifEndEvent
This code is more efficient and better in so many ways. First, the OnKeyDown event is only called when a key has been pressed so no extra Papyrus code gets run during the vast majority of the time when no keys are being pressed. Since you register for specific keys not all of them at once, when the event does get called you'll know that at least one of the four keys has been pressed. The overhead for filtering out unwanted keystrokes is handled by the much faster and efficient SKSE code. Each call to this OnKeyDown event will use only a single frame to pick the right function because comparing two integers is a simple activity compared to making a function call. There will even be time left in the same frame for the chosen MoveItem... function to start. The final advantage of this code is that it will be extremely responsive to the actual key press without the arbitrary UpdateCheckTime wait imposed in the other one.

In summary,

1. Find the best tool for the task. Conditions are good, but careful use of Papyrus events might be better.
2. Avoid polling strategies whenever possible and use OnUpdate only as a last resort.
User avatar
Alexandra Louise Taylor
 
Posts: 3449
Joined: Mon Aug 07, 2006 1:48 pm

Post » Wed Mar 11, 2015 10:41 am

This.

I'd like to add myself, that shared objects (like quests) may also cause performance drop when properties, functions are accessed simultaneously (like when being accessing by multiple magic effects and etc)

User avatar
Emmi Coolahan
 
Posts: 3335
Joined: Wed Jan 24, 2007 9:14 pm

Post » Wed Mar 11, 2015 9:59 am

*Looks at PlayerREF*

User avatar
Lil Miss
 
Posts: 3373
Joined: Thu Nov 23, 2006 12:57 pm

Post » Wed Mar 11, 2015 5:28 am

Lovely responses. Greatly appreciated. I will incorporate events whenever possible.

User avatar
Emma louise Wendelk
 
Posts: 3385
Joined: Sat Dec 09, 2006 9:31 pm

Post » Wed Mar 11, 2015 12:19 pm

So I have been under the impression that using a property pointing at the PlayerRef is a lot better than using Game.GetPlayer(). Now it sounds like player properties are not that great after all?

User avatar
Jay Baby
 
Posts: 3369
Joined: Sat Sep 15, 2007 12:43 pm

Post » Wed Mar 11, 2015 7:53 am

No, you didn't got it. GetPlayer() approach may make the code even more slower. Read http://www.creationkit.com/Threading_Notes_%28Papyrus%29

User avatar
Leanne Molloy
 
Posts: 3342
Joined: Sat Sep 02, 2006 1:09 am

Post » Wed Mar 11, 2015 11:24 am

No I was making cheeky reference to the amount of use the PlayerREF gets, if you think about just how many times in an average game it is shoved into an alias and most likely have yet another script loaded on top of it. How many times is it checked in conditions etc. even from just the vanilla game?

I might also add even though I don't think nifs were the intended target of this thread, that it might also be a good idea for those people intending things like translate function calls from scripts to consider animated nifs instead which would almost completely operate separate to Papyrus leaving it free for other jobs.

User avatar
MatthewJontully
 
Posts: 3517
Joined: Thu Mar 08, 2007 9:33 am

Post » Wed Mar 11, 2015 2:23 am

https://docs.google.com/document/d/1VJVjd1NmXZZuWaS2Ic0CVcpUhLfciiqOGGatvP2GXAM/edit, but I'd very much appreciate some feedback where I can improve the general design. I'm not offended or surprised by areas I might be way off in concept from appropriate.

User avatar
Rebecca Clare Smith
 
Posts: 3508
Joined: Fri Aug 04, 2006 4:13 pm

Post » Tue Mar 10, 2015 11:53 pm

Would using an event in a script like oncombatstate changed to add and remove a spell be more efficient than having the spell permanently attached with a combatstate condition?

User avatar
Kat Ives
 
Posts: 3408
Joined: Tue Aug 28, 2007 2:11 pm

Post » Wed Mar 11, 2015 2:02 am


And now we've got the power of SKSE's http://www.creationkit.com/ModEvent_Script the possibilities are huge, including mods talking to each other. Would love Frostfall or Wet & Cold to include a "weather ping" which other mods could listen out for

- Hypno
User avatar
Georgine Lee
 
Posts: 3353
Joined: Wed Oct 04, 2006 11:50 am

Post » Wed Mar 11, 2015 4:56 am

Potentially it would if it worked. Unfortunately OnCombatStateChanged doesn't work for the player.

I'll address your other PM question here too since other people might be interested.

OnHit can get called many times in a very short span of time. Some attacks actually make two or three calls instead of just one. So whatever processing you need to do should be kept to an absolute minimum. How you minimize the drain depends on what exactly you are trying to accomplish. The old rule from the days of Morrowind scripting applies: check the most likely outcome first (especially if that outcome requires little or no processing) so you can return from the function early. I just wrote a little code that does something like this:

bool SkipHitCheckEvent OnHit(ObjectReference aggressor, Form src, Projectile pr, bool pa, bool sa, bool ba, bool hb)	; block redundant, overlapping events and only run when attacked by an actor that is hostile	if !SkipHitCheck  && aggressor as Actor && (aggressor as Actor).IsHostileToActor(GetReference() as Actor)		SkipHitCheck = true		GetOwningQuest().OnAnimationEvent(aggressor, "OnHit")		SkipHitCheck = false	endifEndEvent
My only goal here was to run a (fairly long) list of checks (in the OnAnimationEvent function) triggered by a successful attack. It doesn't matter if I don't see the second or third attack of a sequence that happens in a short period of time. (This is basically my replacement for OnCombatStateChanged to detect the beginning of combat and requires that the character actually get hit, not just have a enemy intending to attack.)

In theory the code could have been optimized more by using states instead of a SkipHitCheck variable, but I didn't think about that until you asked. The state version is optimal because the game tracks which events are empty and doesn't even bother calling them. That version would have been:

State SkipHitCheckEvent OnHit(ObjectReference aggressor, Form src, Projectile pr, bool pa, bool sa, bool ba, bool hb)EndEventEndStateEvent OnHit(ObjectReference aggressor, Form src, Projectile pr, bool pa, bool sa, bool ba, bool hb)	; block redundant, overlapping events and only run when attacked by an actor that is hostile	if aggressor as Actor && (aggressor as Actor).IsHostileToActor(GetReference() as Actor)		GoToState("SkipHitCheck")		GetOwningQuest().OnAnimationEvent(aggressor, "OnHit")		GoToState("")	endifEndEvent
The only problem with the state-based version is if you use states for other things in the same script.
User avatar
Jon O
 
Posts: 3270
Joined: Wed Nov 28, 2007 9:48 pm

Post » Wed Mar 11, 2015 12:39 pm

Good idea for a thread. Optimization is important stuff, especially as mods get more sophisticated, and users are installing 100-200 at once.


This is good to know. I wonder sometimes whether to bother setting up states. It sounds like the answer is yes.

Regarding delayed native calls:

A single script instance is allowed one delayed function call per frame. In my testing, this correlates with framerate down to 1/1000th of a second. So, at 75fps, one DNC takes 0.013 sec. At 30fps, it will take 0.033 sec.

The following is from a post I made on Chesko's multithreading tutorial thread. Multithreading takes some time to set up, but the speed difference is huge: Iterating a script with 15 delayed function calls on 50 objects can be as fast as 0.001 sec per function call. (13x faster)

Chesko's strategy allows you to queue up scripts with multiple delayed calls and run them simultaneously. This 'cheats' the system by executing multiple function calls per frame. As someone mentioned on the thread, multithreading in this case may more accurately be called 'multi-waiting'.

I did some controlled testing. These are the results.

User avatar
Kelvin Diaz
 
Posts: 3214
Joined: Mon May 14, 2007 5:16 pm

Post » Wed Mar 11, 2015 2:01 am

lucky me, it's getting called on a follower and about adding spells to the follower.

User avatar
kyle pinchen
 
Posts: 3475
Joined: Thu May 17, 2007 9:01 pm

Post » Wed Mar 11, 2015 8:31 am

The statement above is why people trying the stuff below makes me nervous.

That's micro-optimization for the benefit of a single script/mod and the result is more system overhead. At the moment most systems have enough spare resources for it to work, but if enough mods start using that technique for more than occasional tasks ...
User avatar
RaeAnne
 
Posts: 3427
Joined: Sat Jun 24, 2006 6:40 pm


Return to V - Skyrim