Jump to content

Fallout 4 Papyrus Programming Gems


Recommended Posts

View File

This topic is designed for Fallout 4 mod developers. It is not for mod users.

 

As I move along in my papyrus experience, I learned a great deal. The purpose of this thread is to collect "Gems" of knowledge that other developers might find useful.

 

I hope everyone with papyrus experience will share their knowledge. If you post something useful here, I will collect it and add it to the download document.

 

The only thing I ask is that you prove your assertion with example code.


 

Link to comment

I did the standard test of Game.getPlayer() compared to the PlayerRef property.

 

The code was:

 

int i =10000

while i

  i-=1

  Actor a = Game.getPlayer()

  bool t = a.isDisabled()

endWhile

 

And

 

Actor property PlayerRef Auto

 

int i =10000

while i

  i-=1

  Actor a = PlayerRef

  bool t = a.isDisabled()

endWhile

 
 
The second one is about twice as fast.
(I will post real numbers, but I did the test months ago.)
 
Link to comment

the reason for your #1 is actually the heart of the multithreading engine, and can be both a good and bad thing. every time you access an external function that unlocks the papyrus engine to process other threads, when it gets back to yours that external call happens and the result is returned back to your original thread. you actually /need/ to design your scripts with this in mind. things that are not mission critical you can actually help the game be more responsive by letting your lock go for a little bit when calling these external methods. the only time you should really local all the things is when timing is critical - but considering papyrus, even that is kind of a joke.

 

for example in soulgem oven the background loop that processes all the actors in queue references all external methods to the sgo main script. it is not mission critical that my queue processes as fast as it can, in fact, it should go as slow as it can and use the least amount of cpu time possible. by constantly unblocking my thread with these external calls other scripts trying to do more important things can do more important things more often. by doing that i am basically allowing my loop to be put on hold for other things at multiple points during any single iteration.

 

skyrim link, but its basically the same engine in fo4. http://www.creationkit.com/index.php?title=Threading_Notes_(Papyrus)

 

if you replace Math.pow with a multiplicative loop, there is a chance that 4.7ms function could be even faster because of the external call to Math.pow. depends how busy your engine is, and if the external call to a native function is slower than an all local execution scope of non native code.

Link to comment

...

 

Very good posts.

 

And, if may add, don't do something that is invariant many times inside a cycle.

If you just ask for the player once, then there is no real difference between the two ways.

But if you do it in a cycle, then don't use the heavy form.

(The same for every other call/computation.)

Link to comment

for anther example, because sexlab is soooooooooooooooooo fragmented, its actually theoretically, technically, and most probably, allowing more different things to access and do sexlab things as the chance for an unblock is so high. and that actually ends up being a good thing, things like sexlab aroused, etc, and any other mod ever using the sexlab api, can get in and out of sexlab script instances more often than if he coded it all flat. 

 

so at the end of it, while his execution time is slower, in whole, the game is going to be generally more responsive due to it.

Link to comment

...

 

That is not actually true.

 

SexLab is structured as 3 main types of scripts.

The interface, The data, Then runners.

 

Runners, once started, then should NOT be called or changed. (e.g. sslThreadController)

 

The interface is safe for multiple calls. (And is not really event based, all functions are NOT latent) (e.g. SexLabFramework)

 

The data does not matter, it is just data. (e.g. sslbaseAnim)

 

Writing all in a single script? not possible, and really limiting the parallelism, and consuming way too much memory.

Link to comment

but you can still ping the runners. Untamed actually pings and modifies running sexlab instances to make "infinite sex mode" work, and the core framework is still an instance. if all his work was done in the core, it would almost never unblock and all the other mods would get queued up against it for doing simple things like GetGender. because its fragmented the SexLabFramework instance almost never blocks.

Link to comment

This is not entirely true.

 

the reason for your #1 is actually the heart of the multithreading engine, and can be both a good and bad thing. every time you access an external function that unlocks the papyrus engine to process other threads, when it gets back to yours that external call happens and the result is returned back to your original thread. you actually /need/ to design your scripts with this in mind. things that are not mission critical you can actually help the game be more responsive by letting your lock go for a little bit when calling these external methods. the only time you should really local all the things is when timing is critical - but considering papyrus, even that is kind of a joke.

There is still a context switch when you call external functions, that in itself takes CPU cycles.

 

for example in soulgem oven the background loop that processes all the actors in queue references all external methods to the sgo main script. it is not mission critical that my queue processes as fast as it can, in fact, it should go as slow as it can and use the least amount of cpu time possible. by constantly unblocking my thread with these external calls other scripts trying to do more important things can do more important things more often. by doing that i am basically allowing my loop to be put on hold for other things at multiple points during any single iteration.

 

skyrim link, but its basically the same engine in fo4. http://www.creationkit.com/index.php?title=Threading_Notes_(Papyrus)

 

if you replace Math.pow with a multiplicative loop, there is a chance that 4.7ms function could be even faster because of the external call to Math.pow. depends how busy your engine is, and if the external call to a native function is slower than an all local execution scope of non native code.

 

I used Math.pow() because it is one of the few external calls  that do not do a lock. http://www.creationkit.com/fallout4/index.php?title=Category:Non-Delayed_Native_Functions

 

Quote from http://www.creationkit.com/fallout4/index.php?title=Performance_(Papyrus)
 

Because every time you call a function on another script that isn't you or a script you extend (i.e. the "self" variable would be different - even if it's attached to the same form) there is a cost in switching. This cost may be expensive if the object is used by a lot of scripts (i.e. the player). So if you find yourself calling multiple functions in a row on the same object, it might be better to group them into a function on the target object and call that instead. That way the switch cost is only paid once. This may not always be possible, however, especially if the destination script isn't one you own.

 

 

So, in my view, this is all a balancing act.  You want to minimize CPU cycles while still making FO4 responsive.

 

Link to comment

Additional testing for non-local calls.

 

Redid the test where a bit of code was run locally within class or externally in another class.  The results are similar

 

For a 100 count loop this time:

 

local   - 103.87msec

remote - 126.03

remote with return value - 138.43

 

That means about .26msecs per call, so if you subtract out the frame rate of .16, you get .1msec per context switch.

 

Not a big deal unless you are doing lots of it which I do in several cases.

 

 

 

This time, I got rid of the math.pow() function and just did this:

 


function testMath2Local(int count)

    int i = 0
    while i < count
        Float timeSinceUpdate = 4.00
        Float res = timeSinceUpdate * 25.6
        i = i + 1
    endwhile
    
endFunction

function testMath2Remote(int count)

    int i = 0
    while i < count
        testScript.mathTestRemoteNoExternal()
        i = i + 1
    endwhile
    
endFunction

Scriptname SLAR:slaTestScript extends Quest

function mathTestRemote()

    Float timeSinceUpdate = 4.00
    Float res = Math.pow(1.5, - timeSinceUpdate)

endFunction


function mathTestRemoteNoExternal()

    Float timeSinceUpdate = 4.00
    Float res = timeSinceUpdate * 25.6

endFunction

Float function mathTestRemoteNoExRet()

    Float timeSinceUpdate = 4.00
    Float res = timeSinceUpdate * 25.6

    return res
endFunction
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. For more information, see our Privacy Policy & Terms of Use