Welcome to Keen Software House Forums! Log in or Sign up to interact with the KSH community.
  1. You are currently browsing our forum as a guest. Create your own forum account to access all forum functionality.

Space Engineers New! PB Scripting Guide: How To Use Self-Updating

Discussion in 'Programming (In-game)' started by rexxar, Nov 17, 2017.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. rexxar Senior Engineer

    Messages:
    1,532
    Greetings engineers! As you all have (hopefully) read in last week's update post, the PB now has some fancy new self-updating features which means you no longer need timer blocks!

    This self-update system is relatively straightforward, but does require a bit of explanation, so let's get into it!


    Update Frequency:
    The self-update system is controlled through the Runtime.UpdateFrequency property. Those of you who are familiar with GameLogic components should recognize this system, because it's actually the same! At any time in your script (including the constructor!) you can set Runtime.UpdateFrequency, which will automatically register your script for automatic updates at the times you specify. UpdateFrequency is a Flags enum, which means you can combine multiple values at the same time. For instance:
    Code:
    public Program()
    {
      Runtime.UpdateFrequency = UpdateFrequency.Update1 | UpdateFrequency.Update10;
    }
    When your script is compiled, this will schedule updates every tick and every 10th tick. Again, this can be changed at any time during your script's execution.
    Code:
    Runtime.UpdateFrequency |= UpdateFrequency.Update100;
    Runtime.UpdateFrequency &= ~UpdateFrequency.Update10;
    This code will add the Update100 flag, and remove the Update10 flag.

    To use this system to its full potential, you should be comfortable with bitwise operations and how they relate to enums. I'll touch on it briefly, but you should ask your search engine of choice for more information if you aren't already familiar with bitwise operations.

    Bitwise operations are defined as follows:
    Code:
    //bitwise OR
      1010
    | 0110
    ------
      1110
    
    //bitwise AND
      1010
    & 0110
    ------
      0010
    
    //bitwise NOT (also called twiddle)
    ~1010
    -----
    0101
    
    //bitwise XOR (exclusive OR)
      1010
    ^ 0110
    ------
      1100
    
    With that in mind, let's see how these functions apply to our UpdateFreqency property:
    Code:
    //Runtime.UpdateFrequency = UpdateFrequency.Update1 | UpdateFrequency.Update10;
      0001 //UpdateFrequency.Update1
    | 0010 //UpdateFrequency.Update10
    ------
      0011 //Runtime.UpdateFrequency
    
    //Runtime.UpdateFrequency |= UpdateFrequency.Update100
      0011 //Runtime.UpdateFrequency
    | 0100 //UpdateFrequency.Update100
    ------
      0111 //Runtime.UpdateFrequency
    
    //Runtime.UpdateFrequency &= ~UpdateFrequency.Update10
      0111 //Runtime.UpdateFrequency
    & 1101 //~UpdateFrequency.Update10
    ------
      0101 //Runtime.UpdateFrequency
    
    That should cover the basics of adding and removing flags from an enum. Of course, you can also do straight assignment as in the example constructor, if you want to completely overwrite the value, for instance if you want to stop all updating, you can assign Runtime.UpdateFrequency = UpdateFrequency.None; which will remove all flags, and stop any future updating.

    Also of note is the UpdateFrequency.Once value. This is a special flag which schedules your script for one bonus update on the next game tick. After this update, the Once flag is removed. This is useful when you want a one-shot update on the next tick, like initialization code or a function that should only run when triggered by an outside event.

    UpdateType:
    There's now a new argument on the Main method. The updateSource argument will tell you *how* the script is being run. Take a look at the definition:
    Code:
    /// <summary>
    /// Enum describes what source triggered the script to run.
    /// </summary>
    [Flags]
    public enum UpdateType
    {
      None = 0,
      /// <summary>
      /// Script run by user in the terminal.
      /// </summary>
      Terminal = 1 << 0,
      /// <summary>
      /// Script run by a block such as timer, sensor.
      /// </summary>
      Trigger = 1 << 1,
      /// <summary>
      /// Script run by antenna receiving a message.
      /// </summary>
      Antenna = 1 << 2,
      /// <summary>
      /// Script run by a mod.
      /// </summary>
      Mod = 1 << 3,
      /// <summary>
      /// Script run by another programmable block.
      /// </summary>
      Script = 1 << 4,
      /// <summary>
      /// Script is updating every tick.
      /// </summary>
      Update1 = 1 << 5,
      /// <summary>
      /// Script is updating every 10th tick.
      /// </summary>
      Update10 = 1 << 6,
      /// <summary>
      /// Script is updating every 100th tick.
      /// </summary>
      Update100 = 1 << 7,
      /// <summary>
      /// Script is updating once before the tick.
      /// </summary>
      Once = 1 << 8,
    }
    
    Like the string argument, this is optional and can be omitted (however, you cannot omit the string argument and have only the updateSource argument). Note that UpdateType is a Flags enum, meaning it can have multiple flags in one value. This is because all the self-update sources are batched. That is, if your script is set to update on 1, 10, and 100, there's a chance all three can run at the same time. In this case, the update is batched, and updateSource will have all three flags in it, and the script will be run only once, instead of three times.

    Since this enum can have multiple values, you can't do simple comparison with ==. You need to use bitwise operations again.
    Code:
    if((updateSource & UpdateType.Update100) != 0)
       //script run by Update100
    if((updateSource & (UpdateType.Update1 | UpdateType.Update10)) != 0)
       //script run from Update1 OR Update10
    
    You can also use updateSource.HasFlag(UpdateType.Update1), but this function is several orders of magnitude slower than using bitwise operations, so it's best to avoid it in your scripts.


    Important information!!!
    The 10 and 100 tick updates are not guaranteed to be exactly 10 or 100 ticks! The update system includes a load distribution element, which attempts to spread updates out over the selected timespan. For example, imagine there are 12 programmable blocks scheduled to update every 10th tick. Your script is number 11 on the list, so the update looks like this:
    Tick1: PB 1, 11
    Tick2: PB 2, 12
    Tick3: PB 3
    etc
    After this update, PB number 10 removes itself from the update queue, bumping you up to #10, so the next update span looks like this:
    Tick11: PB 1, 12
    Tick12: PB 2
    ...
    Tick 19: PB 9
    Tick 20: PB 11

    This means there will be 19 ticks between the first and second run of your script!

    Additionally, setting the update frequency brings with it some internal overhead associated with the load balancer (I'm working on resolving this!). With these issues in mind, please be careful about how often you modify your update frequency.



    That should cover everything you need to know about the new update system! I'm super excited to see what you guys can do with it!
     
    • Like Like x 4
  2. Rdav Apprentice Engineer

    Messages:
    117
    Great stuff!
    Again a big thanks for this one, the ability to make scripts entirely self-initializing without timers, means we can put codes on the workshop opening up the world of scripting to those people who normally wouldn't be able to! (and save some space with a timer) Also the ability to change the time between ticks is going to be a godsend for performance saving,
    Updating my codes in preparation already!
     
    Last edited: Nov 17, 2017
    • Like Like x 2
  3. theCalcaholic Trainee Engineer

    Messages:
    38
    @rexxar Did noone think about all the timers loosing their jobs? They have families! You cruel, heartless person...

    Joking aside, that's a really nice update. Well done!
     
    • Funny Funny x 4
  4. UbioZur Trainee Engineer

    Messages:
    28
    Stupid question, but...
    How many ticks per second? (on a 1 sim speed of course).
     
  5. Malware Master Engineer

    Messages:
    9,861
    As far as the game is concerned, there's always 60 ticks per second. That is why simspeed differs and works as a time warp. Because if the game can't update 60 times per second, it "slows down time" to compensate.
     
    • Agree Agree x 1
  6. UbioZur Trainee Engineer

    Messages:
    28
    Thanks, that's what I thought but couldn't find the info, so it is now a bit more work to get a script running every second (outside of re-employ those poor timer blocks we just fired!).
     
  7. Malware Master Engineer

    Messages:
    9,861
    Well this auto update thing isn't really designed for timing at all.
     
    • Agree Agree x 1
  8. UbioZur Trainee Engineer

    Messages:
    28
    Just trying to understand/fix some statements, and in the hope that for future players joining into the in-game script life, they will have easy information on what that new feature does and doesn't do.


    We still need timer blocks for simpler timing scripts (instead of counting the number of ticks, which would need to be a 1 tick update to be accurate etc.......

    Many no time/ticks sensitive can be switched to the new method. and the tick sensitive (Easy Automation and all other complicated scripts), will highly benefit that feature.
     
  9. Malware Master Engineer

    Messages:
    9,861
    To be nitpicking, no, you don't need timer blocks. You can do all that timer quite accurately without timer blocks, by using Runtime.TimeSinceLastRun. Yeah, I'd like a slightly easier way to deal with that, this is true. But it can be done.
     
  10. UbioZur Trainee Engineer

    Messages:
    28
    I wouldn't call it nitpicking but good information ;)
     
  11. Malware Master Engineer

    Messages:
    9,861
    I call it nitpicking because it's a bit more troublesome than it needs to be, so it may still be easier to use a timer. But you don't need a timer :p
     
  12. Wicorel Senior Engineer

    Messages:
    1,258
    I have a script that allows you to play with the UpdateFrequency (and shows UpdateType).

    Just place this into a PB and compile.
    Then Run it with one of the arguments (or something else like XXX).
    You can also set up a Timer if you want.

    Code:
    long lCount = 0;
    void Main(string argument, UpdateType ut)
    {
        Echo(ut.ToString());
        Echo(argument);
        if ((ut & (UpdateType.Update1 | UpdateType.Update10 | UpdateType.Update100)) > 0)
            lCount++;
        Echo(lCount.ToString());
        Echo("Before=" + Runtime.UpdateFrequency.ToString());
        if ((ut & (UpdateType.Terminal | UpdateType.Trigger)) > 0)
        {
                    if (argument == "1")
                    {
                        Runtime.UpdateFrequency |= UpdateFrequency.Update1;
                        Echo("Turn On Update1");
                    }
                    else if (argument == "10")
                    { 
                        Runtime.UpdateFrequency |= UpdateFrequency.Update10;
                        Echo("Turn On Update10");
                    }
                    else if (argument == "100")
                    { 
                        Runtime.UpdateFrequency |= UpdateFrequency.Update100;
                        Echo("Turn On Update100");
                    }
                    else if (argument == "once")
                    { 
                        Runtime.UpdateFrequency |= UpdateFrequency.Once;
                        Echo("Turn On Once");
                    }
                    else if (argument == "none")
                    { 
                        Runtime.UpdateFrequency = UpdateFrequency.None;
                        Echo("Set None");
                    }
    
                    else if (argument == "!1")
                    { 
                        Runtime.UpdateFrequency &= ~UpdateFrequency.Update1;
                        Echo("Turn Off Update1");
                    }
                    else if (argument == "!10")
                    { 
                        Runtime.UpdateFrequency &= ~UpdateFrequency.Update10;
                        Echo("Turn Off Update10");
                    }
                    else if (argument == "!100")
                    { 
                        Runtime.UpdateFrequency &= ~UpdateFrequency.Update100;
                        Echo("Turn Off Update100");
                    }
                    else if (argument == "!once")
                    { 
                        Runtime.UpdateFrequency &= ~UpdateFrequency.Once;
                        Echo("Turn Off Once");
                    }
    
            Echo("After=" + Runtime.UpdateFrequency.ToString());
        }
    
    }
    
    
     
    • Like Like x 2
  13. UbioZur Trainee Engineer

    Messages:
    28
    Nice

    Also when it comes to false statement! It's not RuntimeInfo but Runtime!

    [​IMG]
     
    • Agree Agree x 1
  14. Ronin1973 Master Engineer

    Messages:
    4,946

    I don't need a battery to get my motorcycle started... but it sure is a whole hell of a lot easier. I wouldn't call that nitpicking. So I'm nitpicking your post about nitpicking.
     
  15. Malware Master Engineer

    Messages:
    9,861
    If you insist, allow me to continue: That's hardly comparable. All you need to do to deal with manual timing in the PB is to add up a variable. It's not difficult, but it doesn't need to be as difficult as it is.
     
  16. Ronin1973 Master Engineer

    Messages:
    4,946
    The programmable block is where scripters' work meets those who are using it as a product. We're talking end-user experience since many scripters release their work for others to use.
     
  17. Malware Master Engineer

    Messages:
    9,861
    --- Automerge ---
    Aaaand that was a mess-up and I can't edit the post...
     
  18. Ronin1973 Master Engineer

    Messages:
    4,946

    No worries. You've done a lot for the game and contributed greatly. Your opinion is greatly respected. I just think the application of the self-looping block should be refined to be a little easier to manipulate considering it's where the programmer hands off control to the user.
     
  19. rexxar Senior Engineer

    Messages:
    1,532
    Yeah, but the thing is, sometimes you don't want the user to have control. Your script can manage itself far more efficiently and reliably than the user can. And users are terrible, you can't trust them to press buttons in the right order :p
     
    • Agree Agree x 3
  20. Malware Master Engineer

    Messages:
    9,861
    Exactly. None of my scripts are designed to leave any timing at the users's control. Not even when timers were required.
     
  21. Lightwolf Trainee Engineer

    Messages:
    8
    First Thanks to Wicorel, because your example, i understand much more how to use it,....
    I have a ProgBlock thats powered by a Timer, so all is works fine after reloading the Gamefile, now i tried to use the self updating system.
    i trying different ways, but cant bring it to work,... is there a way to let the script run automaticly, after loading the gamefile?
    To get closer to my Problem:
    i use Runtime.UpdateFrequency = UpdateFrequency.Update100; in my script, compile, run it, script works, and the self Updating thing works.
    Now i Save the Gamefile, and close SpaceEngineers back to Main, and load my Gamefile again, and my script no longer self Updating, and stopped.
    So i have to use again a Timer to intialize the Scriptcode ?
    Is there away without a timer to fix that? Examples please.

    by the way for a C Sharp noop like me, this is the simplest way to use it:

    public void Main()
    {
    Runtime.UpdateFrequency = UpdateFrequency.Update100; // or Update10 or Update1

    //Your script here
    }

    Compile + run, to stop, press recompile.
     
  22. Wicorel Senior Engineer

    Messages:
    1,258
    Put the initial setting of update into the constructor

    Code:
    Program()
    {
    	Runtime.UpdateFrequency = UpdateFrequency.Update100;	// or Update10	or Update1
    }
    
    
    
     
  23. Lightwolf Trainee Engineer

    Messages:
    8
    Thanks,... i tried that earlier but i forgotten to add "string argument, UpdateType updateSource" to public void Main(),... so now it works perfekt
     
  24. Pauli Rikula Trainee Engineer

    Messages:
    69
    Are exceptions now officially supported on PB code or do try-catches just work by accident sometimes?
     
  25. Phoera Senior Engineer

    Messages:
    1,713
    whole C#6 supported now.
    (Except finallizers, they are forbiden)
     
  26. Pauli Rikula Trainee Engineer

    Messages:
    69
    Cool. The timer thing looks good btw. What a nice update. Thanks :)

    I have found it useful to introduce basic functionality on interfaces/classes and to extend it via extension methods to form a fluent API.
    I would like to have a bunch of extension methods for all kinds of lists so i would not need to write so many lines.
    Now i have to pass the lists as a parameter to every function and I cant code with autocomplete in Visual Studio ;)

    I know you are not fans of LINQ, but nothing stops yout from returning the same object casted as a different interface so that no new garbage is generated and your game runs smoothly.
    If you have not studied fluent API making, please google for your own sake. Fluent API's are really interesting to program.
    ( see https://en.wikipedia.org/wiki/Fluent_interface )
     
  27. Malware Master Engineer

    Messages:
    9,861
    Ehrm... Linq is already available... ;)

    Just use it carefully. There is a reason the API is the way it is.
     
  28. Pauli Rikula Trainee Engineer

    Messages:
    69
    I mean for example this function:
    Code:
    void GetActions(List<ITerminalAction> resultList, Func<ITerminalAction, bool> collect = null);
    
    can be written as an extension method for 'List<ITerminalAction>' so that it returns itself.

    There is a lot of functions like this where you pass the result variable into the function and they all could be written as extension methods also. To reduce the confusion of when a new object is created and when the modifications are made for the object could be reduced by naming the in place medhods for example with '_i' - suffix.

    Writing code by using autocomplete is much easier than going through lists of functions manually.
     
    Last edited: Dec 10, 2017
  29. Malware Master Engineer

    Messages:
    9,861
    You're suggesting that we switch it around, so you start retrieval on the list itself like this?
    Code:
    List<IMyTerminalAction> _actions;
    IMyTerminalBlock _block;
    
    public void Main()
    {
    	_actions.GetActions(_block);
    }
    
    That would be hugely confusing to beginners. And also counterintuitive. No.
     
  30. Pauli Rikula Trainee Engineer

    Messages:
    69
    Code:
    _actions.GetActions_i(_block);
    Yes. that was what I meant. You would not need to take it away from the terminal block though.

    I found the VRageMath function lists annoying to browse. There is plenty of classes and functions and many places where to search the right function. Maybe I am not a newbie when it comes to c#, but with VRageMath yes.

    This is just my personal feeling and I don't know about the rest.
     
    Last edited: Dec 10, 2017
Thread Status:
This last post in this thread was made more than 31 days old.