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.

Mod Approved! Programmable block: Update 1.082

Discussion in 'Programming (In-game)' started by Lynnux, May 14, 2015.

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

    Messages:
    881
    The PB was updated a bit, an argument which can be passed to it has been added:

    Sandbox.ModAPI.Ingame.IMyProgrammableBlock.TerminalRunArgument
     
  2. plaYer2k Master Engineer

    Messages:
    3,160
    The programmable block also has a few new internal informations:

    Code:
    IMyProgrammableBlock Me;
    Action<string> Echo;
    TimeSpan ElapsedTime;
    Me and ElapsedTime are straigtforward.
    Calling Echo(string) however allows you to display text at the terminal output and thus the DetailedInfo. Thus it is an easy and direct way to display informations for debugging now.
     
  3. MisterSwift Apprentice Engineer

    Messages:
    367
    Oh, that's really nice. The old work arounds were so clunky
     
  4. Wicorel Senior Engineer

    Messages:
    1,243
    What time spam is ElapsedTime measuring? Since last script run? Since game start?
     
  5. Malware Master Engineer

    Messages:
    9,631
    @Wicorel The timespan is measuring the in-game time since the last time the script was run. In-game as taking simspeed into account. I hope - I wasn't able to test on an actual multiplayer server, I just simulated it.
     
    Last edited: May 15, 2015
  6. Mauzen Trainee Engineer

    Messages:
    74
    Best thing is that you can set the parameter in the hotbar. So finally I wont need a full computerroom just for setting parameters for other computers.
     
  7. Malware Master Engineer

    Messages:
    9,631
    @Mauzen Yep. I've wanted that since PB first came out. I just wish I could have made it easier to use when calling from another PB, but allocations are a nono so it had to be this way. Sorry about that :/

    All of these additions were made possible due to Keen giving source code access. If they hadn't, it's likely we would never have gotten this, or at the very least not until after planets/netcode.
     
  8. Noy Apprentice Engineer

    Messages:
    112
    YES!!!!!!!!!!!!!!!!!!!!!!!!!! +1
     
  9. Malware Master Engineer

    Messages:
    9,631
    Oh and by the way, if you specify an argument in the terminal, it will only be applied when you click the Run button, which makes it possible to make a console like interface. I'm still investigating whether it's possible to "Run" when you press enter in the text field.
     
  10. Sirhan Blixt Apprentice Engineer

    Messages:
    459
    I've been waiting for the Me parameter for a long, long time. This is all good.
     
  11. hellokeith Apprentice Engineer

    Messages:
    335
    How is that?
     
  12. THEphil Trainee Engineer

    Messages:
    48
    Neato!

    I wrote some junky test code to figure out how to use TerminalRunArgument, Me, Echo and Elapsed time. Hopefully it will be useful to someone.
    Code:
    /*
    validate Existance of Argument, if it doesn't exist Echo to detailedinfo.
    Use argument to find LCD display with SearchBlocksOfName. Display situational
    information to detailed info. E.g. Not found, found too many, type not LCD/TextPanel.
    */
    
    void Main()
    {
       Echo ("Configuration Log:"); // reset DetailedInfo error on success
       //========================================================================//
       // Kill Program if Argument not found. Echo information to use detailedinfo.
       //========================================================================//
       if (Me.TerminalRunArgument == null || Me.TerminalRunArgument == "") // should only need "" I don't believe argument can be null, but I could be wrong.
       {
         Echo("Please create an Argument, this argument should be in your LCD or TextPanel Block name so we can find a display to use.");
         return;
       }
    
       //========================================================================//
       // Use Me,ElapsedTime, TerminalRunArgument....
       //========================================================================//
       string strData = "Programmable Block Test\n\n";
       strData += Me.GetType().ToString()+"\n"; // returns Sandbox.Game.Entities.Blocks.MyProgramm....
       strData += "Me: "+Me+"\n"; // returns MyProgrammableBlock {222AAD.....
       strData += "MyNameis: "+Me.CustomName+"\n"; // shows the name of THIS program block
       strData += "My Argument: "+Me.TerminalRunArgument+"\n"; // displays argument if available
       strData += "Elapsed time of this program: "+ElapsedTime;
    
       //========================================================================//
       // display to LCD as defined by argument. Blat Errors to DetailedInfo via Echo
       //========================================================================//
       string DEFINEDDISPLAYCONTAINS = Me.TerminalRunArgument;
       var ldisplayBlocks = new List<IMyTerminalBlock>();
       GridTerminalSystem.SearchBlocksOfName(DEFINEDDISPLAYCONTAINS, ldisplayBlocks);
       if (ldisplayBlocks.Count == 1) // found display
       {
         if (ldisplayBlocks[0].BlockDefinition.ToString().Contains("MyObjectBuilder_TextPanel"))
         {
           ((IMyTextPanel)ldisplayBlocks[0]).WritePublicText(strData);
           ((IMyTextPanel)ldisplayBlocks[0]).ShowTextureOnScreen();
           ((IMyTextPanel)ldisplayBlocks[0]).ShowPublicTextOnScreen();
         }
         else
           Echo ("Defined display block '"+ldisplayBlocks[0].CustomName+"' is not an LCD or TextPanel. Please remove '"+DEFINEDDISPLAYCONTAINS+"' from the aforementioned block and add it to your prefered LCD or TextPanel block.");
       }
       else if(ldisplayBlocks.Count > 1)
       {
         Echo ("The supplied Argument was found in the name on multiple blocks, please create a more unique argument and add it to your prefered LCD or TextPanel block.");
       }
       else
       {
         Echo ("Could not find a block with '"+DEFINEDDISPLAYCONTAINS+"' in it's name, please add '"+DEFINEDDISPLAYCONTAINS+"' to your prefered LCD or TextPanel block.");
       }
    }
    
     
  13. Malware Master Engineer

    Messages:
    9,631
    Because that's how I was able to add them :)
     
    • Like Like x 2
  14. Malware Master Engineer

    Messages:
    9,631
    @THEphil that argument property in the interface only gives you the argument as specified in the terminal. To use the argument as given by an action, add a string argument to your Main method. In general you don't need to check the property.

    Code:
    public void Main(string argument)
    {
        Echo(argument);
    }
     
  15. spacecadet Trainee Engineer

    Messages:
    89
    I just encountered this message when closing the PublicText popup of an LCD, I've not seen it before (mind you I haven't thrown so much at an LCD before) so checking if it's a new thing:

    Text is too long. Do you want to truncate it?

    It seems LCD public (and I guess private) texts have a shallowly-enforced limit of 4200 characters. A PB can write more than that to it. Once you read the text via Control Panel you are forced to either:
    - edit it manually
    - let SE truncate it
    - logout to avoid either of those (I've not tested that)
     
  16. Malware Master Engineer

    Messages:
    9,631
    @spacecadet I've seen that one many times, it's not new.
     
    • Like Like x 1
  17. THEphil Trainee Engineer

    Messages:
    48
    @LordDevious That sounds logical, cool. Thanks for the reply and example! The examples are extremely useful to folks like me.

    *makes a mental note, can pass terminal argument in main function and don't have to do Me.TerminalRunArgument.*
     
  18. Brenner Junior Engineer

    Messages:
    609
    I had that one figured out already, and already put it to good use.
    I made a script that can rotate a rotor by a specific amount of degrees - and the great part is that the new argument allows users to configure which rotor they want to turn, by what amount of degrees.
    One programmable block can handle a unlimited number of rotors and turn them in different amount of degrees. All it takes is a tiny amount of string parsing.

    Another use of the argument string would be setting variables in one of those "execute every second" programs. No more need to use those stupid "boolean interior lights" as workaround.
    The argument string is such a small change, but its incredibly useful for scripting.

    One warning though: arguments are currently not passed to the progammable block when you add them inside of a timer block.
    Arguments in cockpit actions and butons work, but not in timer blocks. Already made a thread in the bug forum.
     
  19. Malware Master Engineer

    Messages:
    9,631
    @Brenner I just responded to that bug thread :)
     
  20. Brenner Junior Engineer

    Messages:
    609
    @LordDevious Thanks, very appreciated!


    I have lots of new script ideas that are only possible thanks to the new command argument.
    For example, I could expand my rotor script to pistons and grav gens too, setting the exact height respectivly the grav acceleration.

    Or write a color changer script that can set a light or group of lights to a specific color, intensity, blink interval etc. by clicking a button. And to another color by clicking another button.
    This might require a more complicated argument because lights have a lot of settings.
    Using the command lines of Linux and Dos as an example, the command argument of that script could maybe look a bit like this:

    "My Light Group" -Color 255,0,0 -Intensity 0.4 -Radius 3.5 -Interval 0.5

    Where every parameter would be optional, so if one only wants to change the Light Color it would be "My Light Group" -Color 255,0,0
     
  21. Malware Master Engineer

    Messages:
    9,631
    @Brenner I'm glad you find it useful :)
     
  22. Noy Apprentice Engineer

    Messages:
    112
    has anyone tested to see if it accepts String[] ? That's typically the standard parameter for a main function, in various if not all languages.
     
  23. Mauzen Trainee Engineer

    Messages:
    74
    How exactly is "this way" btw? Could you give an example?
    I couldnt figure out how run a PB with a parameter from another PB's script.
     
    Last edited: May 17, 2015
  24. Malware Master Engineer

    Messages:
    9,631
    No it won't take an array. Like I said, allocations are a nono for something that can run in the game loop, especially something that can run for every tick in a game loop, so some sacrifices and simplifications are required. Also, the golden 80/20 rule applies here. More likely than not, no more than 20% of all scripts will have need of complicated argument systems - so one should not waste time and resources (including computer resources) implementing support for it. People who do need such support are most likely more than skilled enough to roll one on their own. Remember this is not really a main function for a program, where such overhead would not matter, but simply the entry function in what was originally intended to be a simple script. To put it short, I'm not allowed to be more complicated about it.

    If you need anything more complicated is your own responsibility - and blame if your memory use goes crazy :p

    @Mauzen hang on, I'll get you that sample.
     
    Last edited: May 17, 2015
  25. Malware Master Engineer

    Messages:
    9,631
    @Mauzen,

    Code:
    List<TerminalActionParameter> args = new List<TerminalActionParameter>();
    
    public void Main()
    {
        var otherPb = GridTerminalSystem.GetBlockWithName("Programmable block 2");
    
        Echo(Me.CustomName);
        args.Clear();
        args.Add(TerminalActionParameter.Get("Hello"));
        otherPb.ApplyAction("Run", args);
    }
    
    Note how I attempt to avoid unnecessary allocations by reusing a list allocated only once. The TerminalActionParameter is a struct and is "safe".

    This is not intuitive and not really how I'd like it to be. The TerminalActionParameter is something generic, which is why it's a list. It's not specific to the programmable block. I'd like to add the Run function to the interface directly, with a single string parameter as that is what is needed, but this is not really how it's done currently. I don't know. I think I'll probably try it and see if Keen will merge it.
     
    Last edited: May 17, 2015
  26. Mauzen Trainee Engineer

    Messages:
    74
    Thanks a lot for that!
    I already tried something very similar to that before, but somehow it didnt work. Now with your example it works well, no idea what I did wrong wrong.

    Having a simple Run function would be nice indeed. But at least parameters are working in general, good enough to realize some previously impossible ideas now ;)
     
  27. THEphil Trainee Engineer

    Messages:
    48
    @LordDevious
    About the allocations.... So something like this, where there is an IMyTerminalBlock stub above Main() stopping a new list to be "allocated" on every run in the findDisplay() would save allocations? I think I answered my own question.... I hope that makes sense because I'm confusing myself hahhaha.

    Code:
    int intCounter=1;
    IMyTerminalBlock ldisplayBlock=null;
    
    void Main(string ARGUMENT)
    {
       /********/
       IMyTerminalBlock TestTheD = ldisplayBlock as IMyTerminalBlock;
       if(TestTheD == null) if(!findDisplay(ARGUMENT))return;
       /********/
       //if(!findDisplay(ARGUMENT))return;
       /********/
       string strData = "We have pushed the button "+ intCounter + " time(s)\nsince our last recompile\n";
       writeDisplay(strData);
       Echo ("I've run this many times: "+intCounter.ToString());
       intCounter++;
       Echo (ElapsedTime.ToString());
    }
    //========================================================================//
    // findDisplay
    //========================================================================//
    bool findDisplay(string DISPLAYNAME)
    {
       bool boolFoundDisplay = false;
       var ldisplayBlocks = new List<IMyTerminalBlock>();
       GridTerminalSystem.SearchBlocksOfName(DISPLAYNAME, ldisplayBlocks);
       if (ldisplayBlocks.Count == 1) // found display
       {
         if (ldisplayBlocks[0].BlockDefinition.ToString().Contains("MyObjectBuilder_TextPanel"))
         {
           ldisplayBlock = ldisplayBlocks[0];
           boolFoundDisplay = true;
         }
       }
       return boolFoundDisplay;
    }
    //========================================================================//
    // writeDisplay *should have run findDisplay() before this.
    //========================================================================//
    void writeDisplay(string strData)
    {
       ((IMyTextPanel)ldisplayBlock).WritePublicText(strData);
       ((IMyTextPanel)ldisplayBlock).ShowTextureOnScreen();
       ((IMyTextPanel)ldisplayBlock).ShowPublicTextOnScreen();
    }
    
    Thanks!!
     
  28. Malware Master Engineer

    Messages:
    9,631
    Yes :). Your code can be optimized a lot though, but I don't have access to the game right now. Also this program will only work once as it stands.

    Another note, just FYI, using all-caps for arguments are generally considered bad form. Also the general standard for methods in C# is Capitalized. Not very important though :)

    [Edit] Written by memory, probably has some errors:
    Code:
    int intCounter=1;
    IMyTextPanel ldisplayBlock;
    
    void Main(string ARGUMENT)
    {
       /********/
        if (ldisplayBlock == null || ARGUMENT != ldisplayBlock.CustomName)
        {
            ldisplayBlock = GridTerminalSystem.GetBlockOfName(ARGUMENT) as IMyTextPanel;
            if (ldisplayBlock == null)
            {
                Echo("Couldn't find a text panel named " + ARGUMENT);
                return;
            }
        }
        /********/
       string strData = "We have pushed the button "+ intCounter + " time(s)\nsince our last recompile\n";
       writeDisplay(strData);
       Echo ("I've run this many times: "+intCounter.ToString());
       intCounter++;
       Echo (ElapsedTime.ToString());
    }
    
    
    Now there are no allocations at all, and the program will work every time even if ARGUMENT changes.
     
    Last edited: May 18, 2015
  29. THEphil Trainee Engineer

    Messages:
    48
    @LordDevious
    Thanks! PB is a great deal of fun in the game.

    Wouldn't have thought about the argument check.
    With the "IMyTextPanel ldisplay;" instead of "IMyTextPanel ldisplay=null;" it didn't need that workaround for a null test, that's super cool.

    Looks like its PascalCase instead of camelCase for most things thanks. All tips are appreciated!
    Naming Guides
    Capitalization Styles

    I'll have to play with some of the PB scripts that give my system the jitters.

    BTW Nice job from memory I think you only mixed up GetBlockOfName vs GetBlockWithName.

    Code:
    int intCounter=1;
    IMyTextPanel ldisplayBlock;
    void Main(string argument)
    {
      /********/
      if (ldisplayBlock == null || argument != ldisplayBlock.CustomName)
      {
      ldisplayBlock = GridTerminalSystem.GetBlockWithName(argument) as IMyTextPanel;
      if (ldisplayBlock == null)
      {
      Echo("Couldn't find a text panel named " + argument);
      return;
      }
      }
      /********/
      string strData = "We have pushed the button "+ intCounter + " time(s)\nsince our last recompile\n";
      WriteDisplay(strData);
      Echo ("I've run this many times: "+intCounter.ToString());
      intCounter++;
      Echo (ElapsedTime.ToString());
    }
    //========================================================================//
    // writeDisplay
    //========================================================================//
    void WriteDisplay(string strData)
    {
       ((IMyTextPanel)ldisplayBlock).WritePublicText(strData);
       ((IMyTextPanel)ldisplayBlock).ShowTextureOnScreen();
       ((IMyTextPanel)ldisplayBlock).ShowPublicTextOnScreen();
    }
    
     
  30. Hauger Trainee Engineer

    Messages:
    4
    I understand the concept of reusing variables to minimize the performance impact. I'm having trouble swallowing the allocations are a no-no concept though. Is there any way that could be expanded on a bit more?
     
Thread Status:
This last post in this thread was made more than 31 days old.