Search Unity

  1. Unity 2019.1 beta is now available.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. We're looking for insight from anyone who has experience with game testing to help us better Unity. Take our survey here. If chosen to participate you'll be entered into a sweepstake to win an Amazon gift card.
    Dismiss Notice
  4. On February 28th the Feedback website will shut down and be redirected to the Unity forums. See the full post for more information.
    Dismiss Notice
  5. Want to provide direct feedback to the Unity team? Join the Unity Advisory Panel.
    Dismiss Notice
  6. Unity 2018.3 is now released.
    Dismiss Notice
  7. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice

MiniScript: lightweight scripting language for your game

Discussion in 'Works In Progress' started by JoeStrout, Dec 14, 2015.

  1. ifayad

    ifayad

    Joined:
    Jun 15, 2017
    Posts:
    20
    Yes. I agree with you 100% on this point. I was talking from my point of view and forgot who the end user is... I think that having fewer options is the best option in this case.
     
    JoeStrout likes this.
  2. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    I have found a bug that can crash the game. I have been able to reproduce it in one of the example scenes.
    To reproduce the error you have to create a script with a very large loop:

    for i in range(0, 100000000000000000)
    test = "a"
    end for
    print("end")


    it seems that somehow even if it exceeds the maximum time (0.01) it is still processed.
    It happens with a single call to RunUntilDone(). That is, without being in a loop or in the update function.

    Is there any way to prevent this?
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    I'll look into this today and get back to you!
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    OK. The problem has nothing to do with the loop; it's the (attempted) creation of a list with
    100 quadrillion elements in it (which is what the range() call does here). It fills up memory and goes boom. And unfortunately, takes the whole Mono environment — including the IDE — down with it. You might expect Mono to be a little smarter about memory management than that, but no.

    So I guess MiniScript needs to be smarter about it. Because ideally there shouldn't be any code you can type into a MiniScript context that brings down the host app. I will look for a solution.

    EDIT: OK, here's at least a first step. Open up MiniscriptInstrinsics.cpp, and find the code for the range() function (just search for
     Intrinsic.Create("range")
    ). You will find the for-loop that builds the requested list:

    Code (CSharp):
    1.         for (double v = fromVal; step > 0 ? (v <= toVal) : (v >= toVal); v += step) {
    2.             values.Add(TAC.Num(v));
    3.         }
    Replace that code with this:

    Code (CSharp):
    1.     try {
    2.         for (double v = fromVal; step > 0 ? (v <= toVal) : (v >= toVal); v += step) {
    3.             values.Add(TAC.Num(v));
    4.         }
    5.     } catch (SystemException e) {
    6.         // uh-oh... probably out-of-memory exception; clean up and bail out
    7.         values = null;
    8.         throw(new RuntimeException("range() error", e));
    9.     }
    Now when you try some shenanigans of this sort, you will get a range error (MiniScript exception) rather than crashing the app.

    Note that there will still be a substantial delay while MiniScript tries to create a list with 100 quadrillion elements. That's not ideal, but it's a pathological case, and certainly more acceptable than crashing.

    This change (or something similar) will be in the next MiniScript update. I'll also try to look for and button up any other ways a malicious user could cause such a problem. Thank you for bringing it to my attention.
     
    Last edited: Feb 1, 2019
  5. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    thanks for your quick response!

    It may not be ideal, but perhaps you could limit the maximum number of elements that can be on a loop? since players do not need to create such large loops.

    In my case, this issue it is a bit more problematic since I am using it in a multiplayer environment where the scripts are shared.

    In any case I have to say that I am very satisfied with miniscript and how extensible it is. You have done a great job!

    EDIT: wow! this has been fast! thank you.
     
  6. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    humm, I'm sorry, but it seems I had not tested it correctly before. I've tried it again in the example scene with the change you've made and it's still crashing me.
    I'm developing on a mac.
     
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    Ugh, no, I'm the one who didn't test it correctly. I have an isolated, pure C# test environment where I do most of my low-level MiniScript work. That showed the problem originally but handled it gracefully after the change. But the Unity environment is different — an older version of Mono, for one thing — and you're right, it still crashes there.

    All right, if you're willing to put a hard limit on what range() will do, then insert this code right before the new try block:
    Code (CSharp):
    1.         double count = (toVal - fromVal) / step;
    2.         if (count > 1E6) {    // bail out if requested list is too big
    3.             return new Intrinsic.Result(new ValList());
    4.         }
    This will make range() return an empty list for any set of parameters that would produce more than a million elements. In my testing (also on a Mac), this solves the problem. I don't love it as a long-term solution, but it should at least get you going for now.
     
  8. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    thanks! that worked. Since the limit is really high, it will not interfere with the players' scripts.
     
    JoeStrout likes this.
  9. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    I'm not sure but I think I've found another problem. It is not a serious error, but it causes a crash in miniscript.

    Here is the example code that I used to reproduce the error:
    val1 = null
    val2 = 0
    print("hello1")
    if not val1 or val2 then
    print("hello2")
    else
    print("hello3")
    end if
    print("END")


    This causes this crash.
    https://imgur.com/BBjw3a4

    It seems that there is a problem when checking if one of the values is null, and it does not enter the catch section in the interpreter.RunUntilDone() part.

    Is this behavior intentional? I have tested this code in the example scene.
     
  10. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    Yikes! Definitely not intentional. Great catch!

    I have a list of (mostly small) improvements to MiniScript I've been sitting on. This is the first serious bug (other than the over-trusting range() function a few posts up) that anyone has found in quite a while. So it's enough to spur me to wrap up my other changes, and post a MiniScript update. I'll try to get that out in the next week or two.
     
  11. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    great to hear that! I wait for the update. Thanks for the support.
     
    JoeStrout likes this.
  12. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,072
    @JoeStrout I was running into a situation where when I call

    Code (csharp):
    1.  
    2.    try
    3.    {
    4.        interpreter.Compile();
    5.    }
    6.    catch (MiniscriptException e)
    7.    {
    8.  
    9.    }
    10.  
    I can't seem to get an exception to throw on Compile().

    An example miniscript file that I was using, with purposefully blatant errors, is:
    Code (csharp):
    1.  
    2. gTitle="Hello world"
    3. akj)
    4. {
    5. OnGameBegin = function()
    6.  
    7. //end function
    8.  
    But for some reason I wasn't getting any exceptions thrown. Is there something I'm doing wrong?
     
    JoeStrout likes this.
  13. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    Maybe? They're certainly thrown for me, because that's how the demos display compile errors. So, somehow your project is different. Not sure exactly what that might be, though.

    If you can put together a small project demonstrating the problem, PM it to me and I'll have a look.
     
    aer0ace likes this.
  14. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,072
    So, to my understanding, MiniscriptInterpreter.cs has the following code block:

    Code (csharp):
    1.  
    2. public void Compile()
    3. {
    4.    ...
    5.    parser = new Parser();
    6.    try
    7.    {
    8.        parser.Parse(source);
    9.        ....
    10.    }
    11.    catch (MiniscriptException mse)
    12.    {
    13.       ReportError(mse);
    14.    }
    15. }
    16.  
    This tells me that if the compiler Parser throws an error, the MiniscriptException is caught, but it doesn't get rethrown, so my try/catch for my intepreter.Compile() will never get handled.

    This also tells me that Miniscript is intended to handle MiniscriptExceptions internally, and the way errors are reported are via the errorOutput TextOutputMethod. This is great for abstraction and all, but what if I want to know if it's specifically a Compiler error? Is it possible to have an ExceptionOutputMethod so that client code can dig into the exception? Or perhaps this is against your design philosophy? Thoughts?
     
    JoeStrout likes this.
  15. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    Ah yes, right you are. But the exception is passed to ReportError (which, as I recall, you're intended to override). So you can examine the error there and see what sort of exception it is.
     
  16. LoadingHome

    LoadingHome

    Joined:
    Jan 8, 2015
    Posts:
    32
    I think I've found another bug, tested in the example scene. It could also be called inconsistency.

    According to the documentation, the methods that do not accept parameters do not need to invoke them with ().
    But for example this does not work, it throws an error of "too many arguments":

    Code (CSharp):
    1. print("Spam".upper.indexOf("A"))
    But this works:
    Code (CSharp):
    1. print("Spam".upper().indexOf("A"))
    It seems that the () are not required if the child function has not parameters passed to it, but fails if the child function has parameters as in this case.

    Is there any solution to this or should I call the methods always with ()?
     
  17. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    190
    I am sorry if this was answered already, but I didn't have time to read through the whole thread.
    Anyway, is it possible to save the state of a MiniScript interpreter instance, and load that state in a later session - or is this actually not a feasible?
     
  18. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    Hmm, you're right. That is not intended. I've added it to my test suite and will get a solution ASAP (I have a few other things in the hopper to be released at the same time).

    Until then, yes, you'll have to use the empty parentheses.
     
  19. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    It's feasible, but you're the first to ever want it as far as I know. You would just need to walk the state of the interpreter object (in particular, its virtual machine) and save it out. Most of that data is in the form of MiniScript values, which are wrappers for basic types (string, double), or lists or dictionaries of same.

    That serializing/deserializing isn't something that MiniScript provides out of the block, but you could certainly add it with a bit of effort.
     
  20. purestrain

    purestrain

    Joined:
    Apr 7, 2015
    Posts:
    3
    Hi, how much is your asset integrated into unity? Could i possibly use it standalone for some server side scripts?
     
  21. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    190
    Thanks for the answer. My question is aiming towards saving an NPC's state in some kind of RPG or adventure, where the NPCs can have more complicated scripts, and where it's possible to save anytime during gameplay. Would there be a better way to do this?
     
  22. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    It is neatly encapsulated and works fine outside of Unity. I myself have a command-line Mono project I use to do most of the MiniScript development. So it will work fine server side.
     
  23. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    7,601
    I would recommend making the NPCs event-driven. So rather than have scripts designed to be running all the time, they should be set up to run small bits of code in response to something happening (possibly including a periodic tick of the clock), and return very quickly.

    These scripts could still have persistent state, which would appear to your Unity code as a ValMap, but you'd only have to save and restore that map — you wouldn't have to worry about trying to save the entire state of the virtual machine (including the call stack, program counter, etc.).
     
    ratking likes this.
  24. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    190
    Yeah, sounds sensible. My thinking was the modding-ability makes it hard to predict what the script will look like, so "anything is possible in a state".