Search Unity

MiniScript: lightweight scripting language for your game

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

  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I finally got around to trying that Ace editor + WebGL MiniScript combo that @mgear suggested a while back. It is serious fun!


    Try it out here, and please let me know what you think!
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    MiniScript version 1.1 has just been submitted to the App Store.

    This is a bug-fix release. We discovered a small bug where, contrary to the claims in the docs, maps were always being treated as "false" (0) even when they were nonempty. This new version fixes that bug. It's a recommended update for all MiniScript users.

    While we were at it, we also finally updated our web demo to use WebGL instead of the old Unity Player. So there's that, too.
     
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Hey all, I'm currently wrestling with a bit of a thorny design issue related to using MiniScript to control NPC behaviors... see the thread about it here.
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Hey guys, I had a thought and I'd love to get your reaction.

    As background, I've been playing around a fair bit lately with Scratch with my kids. Actually we're thinking about writing (another) book about it, as we're not completely satisfied with any of the game-programming-with-Scratch books currently available.

    But I keep running into really annoying limitations that make it much harder to do things than it should be. For example, there are no string operations other than getting an indexed character of a string, and joining two strings. Granted, with these and piles and piles of code, you could make other string functions... except that Scratch also has no way of making a function that returns a value (you can only make subroutines, and oh yeah, they can't have any local variables). And this is just an example — you bump into limitations at every turn.

    On the other hand, the basic structure is really nice; a background image, a transparent drawing layer, and then a big pile of sprites. With that you can do a lot.

    So. Now I'm thinking about making a sort of Scratch-like game programming environment for kids, but built on MiniScript.

    Just like Scratch, you'd have the Stage (background) and zero or more Sprites. And each one would have a MiniScript associated with it, that would be driven by event handlers (onStart, etc.). I would set up the coordinate system and such to match what Scratch does, and make sure we have functions equivalent to Scratch functions as much as possible (including gems like "is color A of my sprite touching color B of any other sprite or the background?"). But then you'd obviously have access to all the other MiniScript goodness, including strings, lists, maps, and so on.

    The idea is that this would be a stepping stone for kids (or adults, for that matter) who have been using Scratch, but are getting annoyed by its limitations. They could go to this environment and learn to type (or copy/paste!) code, rather than snapping blocks together. The range of things they could do would be substantially increased, while still feeling familiar. And then when they go to all the awesome stuff you guys are making with MiniScript, they'll have a leg up because they already know the language.

    What do you think? Genius or insanity?
     
  5. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    25,477
    I started learning to program between the age of 5 and 10, and I think the problem is less to do with syntax complexity (kids are way smarter than us, just inexperienced) and more to do with interest. Back then, everything was 2D and so it didn't take much for things to be quite exciting.

    These days, that's a lot of code and unlimited possibilities, so the same connection between cause and effect is lost. You want to effect a thrilling next-gen console title but the cause is dishearteningly long (that's a lot of code!).

    What is the goal, to intrigue kids in programming or just teach them. Is there actually a difference with this question?
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I don't see a difference. The basic goal is just to get kids hooked on programming, if possible; or if not, to at least expose them to enough to get some of the benefits (learning to think logically, problem decomposition, etc.).

    But Scratch already does a good job of that for the complete newbies. It's about as easy as I can imagine programming being, short of an AI that just does it for you based on a rough natural language description (which I believe is coming, but at that point it's not really programming at all anymore; it's the AI doing your thinking for you).

    Trouble is, kids get started on Scratch, hit its limitations, and then — they're stuck. It's a big leap to anything else. Stencyl ought to be the logical next step, but it rather sucks. And to go to Minecraft modding (which I tried for a couple weeks over the holidays, and man that's brutal), or even Unity, is too big a leap for some kids, so they just give up.

    And as I've been working with Scratch, and coming up with projects for a book, I keep hitting things that either can't be done at all, or can only be done through extreme gyrations, that would be so easy in MiniScript.

    So that's the idea: some basic structure (stage/sprites), same basic functions (move 10, wait, turn left 30 degrees, etc.), but with lots of stupid limitations removed, and (for the first time, for most kids) typing code instead of dragging blocks around. Seems like a nice next step to me.

    (I have ideas for much deeper & more sophisticated uses of MiniScript too... but best to start small!)
     
  7. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    Hey @JoeStrout, I've started to explore some ideas for introducing Tutorials, scripted story events, and mod support for my game. Naturally, I explored the ideas of using Python, Lua, or rolling my own. Then, I stumbled upon Miniscript. I'm still researching it, but it looks like this is something that I might use, especially if you continue supporting it, as it looks like you are, and especially since I recognize you as a prominent contributor to the Unity community. I'm hoping this'll be the solution for me! And if not, I think the $50 you're charging is well worth it even to just toy around with.
     
    JoeStrout likes this.
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Yep, that sounds like exactly the sort of thing MiniScript was designed for! I look forward to hearing how it works in your project, and if you run into any trouble... I'm here for you. :)
     
  9. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    OK, I have a question for you MiniScript users (and potential users).

    Should we define true and false as built-in keywords?

    Currently they are not; MiniScript uses numbers for truth values (in a way that fully supports fuzzy logic, if that's your thing!). And of course the design goal was to keep the language "mini," i.e., to not have more built-in stuff than really necessary. So we just use 1 for true, and 0 for false.

    But even as the language creator, when I actually go to use it, I find myself typing stuff like "while true" to make an infinite loop, and only noticing my mistake when I get an error. Apparently these "true" and "false" keywords have gotten so ingrained in my brain from other languages, I expect them to be there. (And they really are nearly universal; everything from C to Java to REALbasic to C# uses them.)

    Of course you could just put "true = 1; false=0" at the top of your script. But that seems lame.

    So what do you think? Is it worth bloating the language a teeny bit to add these two additional keywords, that simply evaluate to 1 and 0?
     
  10. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    4,287
    I would follow C in this (I really like the C boolean expression). Keep it numeric, let's say 1 == true any other value is false [edited] . And you can define false = 0 and true = 1 constant or whatever after that. End of story.
     
    Last edited: Feb 23, 2018
  11. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Sorry, I'm not clear on what you're saying... are you in favor of the new keywords, or against them?
     
  12. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    4,287
    Disclaimer: Please, keep in mind that I'm not your primary customer and not someone who will be any time soon. I'm not planning to use anything like this in any of my projects. So I'm just expressing my preference without any ties. I am in favor of C's define macros, so yes, it would be helpful having true and false keywords in terms of readability.
     
  13. Fortitude3D

    Fortitude3D

    Joined:
    Sep 7, 2017
    Posts:
    155
    Is it based off another language? looks impressive, good work!
     
    JoeStrout likes this.
  14. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Oops, somehow I missed your question last month, @Fortitude3D! No, it's not directly based off of any other language. Its design is mostly a reaction to Lua, which looks nice at first, but the deeper I got into it, the more I despised it. :) But it takes inspiration from a number of sources, including Python, C#, and even MOO, which was one of the first truly high-level languages I used as a yoot, and a real eye-opener ("I didn't know programming could be like this!")

    So with every design decision, my #1 goal was to keep it simple, consistent, and easy to learn and use. There were a few compromises necessary (like the @ syntax for referring to a function without invoking it), but on the whole I'm very happy with how it came out.
     
  15. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Hey, here's a neat sighting of MiniScript in the wild: Creating Unity in Unity, wherein "Pixelpizza" makes a nifty programming environment for use on a tablet.

    He even includes a visual block-based editor; you can switch back and forth between that and a straight-up MiniScript code editor:



    Pretty neat!
     
  16. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    Just bought Miniscript in the Madness Sale. I'm excited!
     
    JoeStrout likes this.
  17. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Awesome! Thanks for your support, and be sure to share (if you're willing) what cool things you do with it!
     
  18. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    So, integration was pretty painless given the fantastic documentation. I'm running into a case which I'm not sure if MiniScript can handle, but I think it actually should as it is, so I may be doing something wrong.

    I would like to register a callback with a specific name in MiniScript, such that the backing C# will later on call that function name. I tried to do this, but for some reason, my function is not registered as a GlobalValue when I think it should be. Should this be working? I think it should. Here's sort of an example:

    Code (MiniScript):
    1. funcName = "playScriptedEvent"
    2. RegisterCallback(funcName)
    3.  
    4. playScriptedEvent = function()
    5.     foo
    6. end function
    7.  
    The idea is that the C# script will at some future time run an event that will invoke the call to "playScriptedEvent".

    I've already appended the "_events" loop code to this script as a preprocess before compiling it, and I also have an Invoke function, which is exactly like the examples:

    Code (CSharp):
    1.     private void Invoke(string funcName)
    2.     {
    3.         Value handler = interpreter.GetGlobalValue(funcName);
    4.         if (handler == null)
    5.             return;
    6.  
    7.         var eventQueue = interpreter.GetGlobalValue("_events") as ValList;
    8.         if (eventQueue == null)
    9.         {
    10.             eventQueue = new Miniscript.ValList();
    11.             interpreter.SetGlobalValue("_events", eventQueue);
    12.         }
    13.  
    14.         eventQueue.values.Add(handler);
    15.     }
    But when the invoke is called, interpreter.GetGlobalValue(funcName) returns null. Shouldn't the name of the function be available by the time the mini script is compiled?

    EDIT:
    So, I seemed to have "fixed" my issue by moving the function RegisterCallback(funcName) after the function declaration.

    Code (MiniScript):
    1. funcName = "playScriptedEvent"
    2. playScriptedEvent = function()
    3.     foo
    4. end function
    5. RegisterCallback(funcName)
    The reason I say "fixed" is because it works as it was, but I was putting the Invoke() test code in the body of that RegisterCallback intrinsic function the C# side. But, while executing the script, C# did not yet have the "playScriptedEvent" function entered as a global value with the interpreter. This is why placing RegisterCallback(funcName) after all declarations worked, but I doubt it was necessary, because the Invoke() function shouldn't really be called in the same body as the function registration anyway.

    Regardless, I'm going to leave it as a "best practice", unless there truly was some sort of bug here.
     
    Last edited: May 6, 2018
    JoeStrout likes this.
  19. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    From a marketing perspective, within the context of the giants available to walk upon, some questions to consider:

    how is this superior (in order of pertinence) to:

    - Boo
    - Lua
    - Logo
    - Lisp

    how does it provide better ways to create and shape interactive experiences than visual scripting?

    when contrasted with (more literal) visual coding (like Bolt and BluePrints), how does it hold up (good and bad)?

    how is it superior to the block based (Scratch etc) paradigms of programming?

    in what ways is it inferior to all of the above and each of the above?

    how does it compare to the means of discovering and using a coding language demonstrated in the tiering of Smalltalk?

    in the context of grammar and readability, how does it holdup to livecode?

    When viewed from the perspective of a minimal take on verbosity but being an all conquering, general, modern and highly capable language (eg Swift or Kotlin) how does it hold up in terms of blending/compromising readability, accessibility and expressibility with power/performance, flexibility and capability?

    I don't envy anyone designing a method of coding for computers.
     
  20. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I think it holds up very well on all those points. I used Lisp professionally for years, but would not wish it on anyone. Logo was cute in the 1970s but hasn't aged well. I actually started writing a book for kids based on Lua, but gave it up in disgust after a couple of chapters — that's a language that looks great on the surface but is just nasty as soon as you get more than skin deep. I haven't used Boo, but I believe it's based on Python, which I also used for years — but is hardly a lightweight language.

    Indeed, of all of those, the only one actually well-suited to be an embedded scripting language is Lua. MiniScript was basically written as a reaction to that. It is designed to be simple, easy to learn, and hold up well even to deeper use. And on top of its merits as a language, MiniScript is also especially well integrated with C#, since that's what it's written in and designed for.

    But judge for yourself! If you're familiar with languages in general, you can see everything you need to know about MiniScript in the one-page quick reference.
     
  21. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I think you've got it. But I'm dashing out the door right now and don't have time to dig into this properly; I'll try to do so later this evening.
     
  22. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    Is there an example of this somewhere? If not, could you provide some guidance on implementing this? I kind of wanted to save a couple of hours of trial and error to get this working.

    EDIT:
    Actually, if I'm understanding the quote a little better... Does this mean, that in order to support a variable number of parameters passed to a function, you'd have to assemble the parameters into a single map and pass that single map as the sole parameter? It seems a little awkward, but workable.
     
    Last edited: May 8, 2018
  23. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I think you've got the idea. This is pretty similar to event handlers in other environments, which receive an "event" object (like this) which contains any information the handler might need about what happened, where, and when.

    So the event loop in MiniScript would look something like:

    Code (CSharp):
    1. _events = []
    2. while 1
    3.   if _events.len > 0 then
    4.     _nextEvent = _events.pull
    5.     _func = _nextEvent["handler"]
    6.     _func(_nextEvent)
    7.   end if
    8. end while
    ...and all your handlers would now expect a map as a parameter. And then in the C# side, instead of pushing just a function pointer, you would push a map containing the function pointer and additional data, something like this:
    Code (CSharp):
    1.   // Add a reference to the handler function onto the event queue,
    2.   // wrapped in a map with any other data needed.
    3.   // The main event loop will pick this up and invoke it ASAP.
    4.   ValMap m = new ValMap();
    5.   m["handler"] = handler;
    6.   m["x"] = x;
    7.   m["y"] = y;   // (...whatever you need...)
    8.   m["frame"] = Time.frameCount;
    9.   eventQ.values.Add(m);
     
    Last edited: Feb 5, 2019
    aer0ace likes this.
  24. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    Is there any way to reference other MiniScript files? I'm thinking "using", "import", "extern", "include", etc.... My script files may get potentially lengthy. If not, is this on the roadmap, or if not on the roadmap, what are the motivations for not supporting it?
     
  25. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    It's not on the roadmap because MiniScript doesn't deal in files. You hand it a string (or a list of strings, one per line). Where and how you assemble that string (or list) is up to you.

    So if you have a routine somewhere that loads MiniScript code from files, then that routine is the one that should scan for "#include" (or whatever you want to call it), and at that point load another file.

    I've done this in one of my projects somewhere, and it works fine. The only caveat is that of course you have to be careful to watch out for recursive includes throwing your code into an infinite loop.
     
    aer0ace likes this.
  26. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    787
    Hi, a few questions:

    1) How exactly this handle interop with other functions, can you call any c# function on any object? Is there a way to sandbox it?

    2) The "wait" statement, does that use enumerators/async await or other allocating stuff?

    3) How much reflection is used? Does it use reflection to call things everytime a methodcall is done?

    4) How much allocations can we expect? MethodInfo invoke will box value types for parameters and return values, `yield return` allocates an enumerator, and so on... Were you able to somehow get around that?
     
  27. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    It is completely sandboxed; you can't call arbitrary C# functions with it. You can only call "intrinsic" functions explicitly added to the MiniScript environment.

    No. Any intrinsic function can either return results immediately, or return a little struct that means "not done yet" along with some partial results if needed. The main loop will then call the intrinsic again, passing back the partial results, until it reports that it's done. With the standard MiniScript driver, that main loop happens on the next Update, so this amounts to calling again on the next frame.

    So, wait is just a trivial intrinsic that does nothing except report it's not done, until the specified amount of time has passed.

    But note that the execution of MiniScript code does involve allocations; there are often temporary values needed while evaluating expressions, etc. In practice I haven't seen this cause a problem.

    It does not use any reflection.

    Nope. I could probably reduce allocations through extensive use of recycling pools, but I don't know that I could eliminate them entirely. String values, for example, are stored internally as plain old C# strings, so if your MiniScript code is creating strings, I really can't avoid creating C# strings.
     
    dadude123 likes this.
  28. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Hi all! I'm working on a MiniScript update with a handful of small language improvements, including the addition of true and false keywords, allowing return to be used without a return value, and a new map.values method that returns all the values as a list (similar to how .indexes returns all the keys).

    So, if you have any gripes/confusion/suggestions about the MiniScript language, now's the time to post them. I may be reluctant to add entirely new stuff — I want to keep the "mini" in MiniScript as much as possible — but if there is anything not working correctly, or ways to make the existing stuff work in more intuitive ways, I will be very receptive.

    I will also drop the first public hint that I'm working on something that will help MiniScript get even wider adoption. MiniScript is first and foremost a language designed for embedding in other software; but it would also make a great language for other scripty purposes (shell scripting, etc.). And the more popular the language becomes, the happier your users will be to see it in your games, so this will be a win/win!
     
    zyzyx likes this.
  29. krisu

    krisu

    Joined:
    Jul 10, 2013
    Posts:
    32
    do this thing, have yielding/coroutines system? which can be use in custom c# function which are binded in this miniscript?

    like:

    public void my_function()
    {
    miniscript.YieldMe();
    };

    ....

    miniscript.Continue(thread);
     
  30. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I'm not entirely sure I understand what you're asking, but I think the answer is "yes." :) In your C# code, you typically run your MiniScript code with a time limit, like this:

    Code (CSharp):
    1. interpreter.RunUntilDone(0.01);
    That will run the MiniScript for 0.01 seconds, and then return. (See chapter 1 of the MiniScript Integration Guide.)

    In the MiniScript code, the built-in wait method lets you pause your script for some amount of time (and in the next release, this will also cause the RunUntilDone to return early, essentially yielding until the next frame).

    So yes, you can easily have lots of MiniScripts running at once, just like coroutines in C#.
     
  31. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    Is there any syntax highlighting markup somewhere for Notepad++ for MiniScript? If not, I'm sort of happy with the Lua syntax highlighting, and if I ever get bored, I'll make one for MiniScript.
     
  32. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Please do. I don't use Notepad++, so I can't help you there. (But hmm... maybe I should make a Miniscript mode for BBEdit!)

    And please share it if you do. I've got (currently a bit vague) plans to create a MiniScript web site, which would be a good place to host goodies like that.
     
  33. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    OK guys, I need your input.

    The current release version of MiniScript doesn't compare lists or maps; a == b, where a and b are either of these types, just returns null. For version 1.1 I've fixed that, so you can now use == or != to test whether two lists or maps are equal. It does this via a deep comparison (i.e., comparing each contained item), when there is no obvious shortcut (i.e., if the lengths are unequal, then obviously the data can't be the same).

    All works great, except in one case: a list or map that contains itself. In this case, the deep comparison never terminates. For example:

    Code (miniscript):
    1. > a = [1,2,3]
    2. > a[1] = a
    3. > a
    4. [1, [1, [1, [...], 3], 3], 3]
    5. > hash(a)
    6. 390885601
    7. > b = [1,2,3]
    8. > b[1] = b
    9. > b
    10. [1, [1, [1, [...], 3], 3], 3]
    11. > a == b
    12. Stack overflow in unmanaged: IP: 0xa737ab83, fault addr: 0xbf780b4c
    So my question for you all: how should we handle this? I see two options:
    1. Make the == and != operators detect and properly handle the recursive data. Or,
    2. Check for recursion upon element assignment, and throw an error at that point.
    For reference, Python faces the same issue — if you try the above there, you get "RuntimeError: maximum recursion depth exceeded in cmp" when you do the comparison. But then Python has exception handling; MiniScript doesn't. So we'd have to either throw the exception up to the host app (on comparison or assignment), or do something else. I guess since MiniScript supports partial truth values, we could give up in this case and return 0.5 (the computer equivalent of a shrug).

    So I'm not sure. What do you guys think?
     
    Last edited: Jun 21, 2018
  34. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    I guess I vote for #2, but I'm actually here to post a question.

    I'm exploring the usage of Intrinsic.Result false. I'd like to block my script until the user has input something to unblock the script, and I think this is the correct purpose.

    However, I also have my Miniscript event handler, which, when my game receives events throughout the game, and attempts to place those events in the Miniscript event queue, it doesn't actually happen. It just gets dropped on the ground. This is what I'd expect from the blocking call, but would a satisfactory way to solve be to:

    a. Maintain behavior as is. If a game message gets sent, Miniscript just never receives it because the event loop is not collecting them.
    b. Create a game-side queue that queues up all the events while Miniscript is blocked, and then add to the queue once MiniScript resumes?
    c. Some other options?

    Thoughts?
     
  35. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Hmm, I wouldn't expect events to get dropped on the ground. In your C# code, you're adding them to a ValList, right? That's something you can do at any time, quite independent of what the script is doing or whether it is running at all.

    So, something fishy is going on there. Maybe post some of the relevant code, and we'll see if we can spot the problem?
     
  36. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    610
    Just out of curiosity how C# dependent is MiniScript? In your opinion would it be difficult for an experienced programmer to port the interpreter to C/C++?

    As for your comparison question, in the example given I see you using hash(a) - does hash(b) return the same result? If so I'd say use that for the fallback in the recursive case (and maybe even if not, if two things have a different hash they probably shouldn't count as equal.)
     
  37. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    It's not that difficult. Please PM me if that's something you would be interested in.

    Yes, in fact it should always be true that a == b implies hash(a) == hash(b) (although the reverse is not necessarily true).

    Today I added an implementation that bails out with a value of 0.5 if we hit the recursion limit (16 levels) without finding any differences. I'm pretty satisfied with this. If you use a truth value of 0.5 in an if or while condition, it's treated as true, and in practical use that's what you'd want most of the time. But if you really need to detect this case for some reason, you can do so.
     
  38. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    So I'll try to set up the scenario as best I can.
    I'll start off with my event loop and invoke function
    Code (Miniscript):
    1.  
    2.             // Main event loop
    3.             _events = []
    4.             while 1
    5.                 if _events.len > 0 then
    6.                     _nextEvent = _events.pull
    7.                     _func = _nextEvent["handler"]
    8.                     _func(_nextEvent)
    9.                 end if
    10.                
    11.             end while
    12.  
    Code (csharp):
    1.  
    2.         public void Invoke(Interpreter interpreter, ValMap functionAndParams)
    3.         {
    4.             string funcName = functionAndParams["handler"].ToString();
    5.             Value handler = interpreter.GetGlobalValue(funcName);
    6.             if (handler == null)
    7.                 return;
    8.             // Swap the string value for the function handler value
    9.             functionAndParams["handler"] = handler;
    10.             var eventQueue = interpreter.GetGlobalValue("_events") as ValList;
    11.             if (eventQueue == null)
    12.             {
    13.                 eventQueue = new ValList();
    14.                 interpreter.SetGlobalValue("_events", eventQueue);
    15.             }
    16.             eventQueue.values.Add(functionAndParams);
    17.         }
    18.  
    Here's the Minisript:
    Code (Miniscript):
    1.  
    2. OnUnitSelected = function(params)
    3.     print("unit selected test")
    4.     print("unit id: " + params["unitId"])
    5. end function
    6. OnTurnBegin = function(params)
    7.     print("turn: " + params["turnNumber"])
    8.     print("faction: " + params["factionId"])
    9. end function
    10. Say("Here's some text from the game world!")
    11.  
    And here's my "Say" intrinsic (wrapped in my factory code)
    Code (Csharp):
    1.  
    2.     public class FuncSay : GameFunction
    3.     {
    4.         public FuncSay(IGame game) : base("Say", game) { }
    5.         protected override void InitParams(Intrinsic intrinsic)
    6.         {
    7.             intrinsic.AddParam("speech");
    8.         }
    9.         protected override Intrinsic.Result Execute(TAC.Context context, Intrinsic.Result partialResult)
    10.         {
    11.             if (!mGame.CanSpeechContinue)
    12.                 return new Intrinsic.Result(null, false);
    13.             string speech = context.GetVar("speech").ToString();
    14.            
    15.             Console.WriteLine("Say: " + speech);
    16.             return new Intrinsic.Result(null);
    17.         }
    18.     }
    19.  
    And here's the driver program that mocks the game's behavior
    Code (Csharp):
    1.  
    2.             int count = 0;
    3.             while(true)
    4.             {
    5.                 sys.interpreter.RunUntilDone(0.1f);
    6.                 count++;
    7.                 if (count == 30)
    8.                 {
    9.                     UnitSelectedMsg msg = new UnitSelectedMsg();
    10.                     msg.UnitId = 1;
    11.                     gi.Handle(msg);
    12.                     TurnBeginMsg turnMsg = new TurnBeginMsg();
    13.                     turnMsg.TurnNumber = 3;
    14.                     turnMsg.FactionId = 1;
    15.                     gi.Handle(turnMsg);
    16.                 }
    17.                 if (count == 10)
    18.                 {
    19.                     gi.CanSpeechContinue = true;
    20.                 }
    21.             }
    22.  
    And the above mock code works. This is the output
    Code (output):
    1.  
    2. Say: Here's some text from the game world!
    3. unit selected test
    4. unit id: 1
    5. turn: 3
    6. faction: 1
    7.  
    And now the problem. If I switch the execution order in the mock gameloop (swap 10 and 30)...
    Code (Csharp):
    1.  
    2.             int count = 0;
    3.             while(true)
    4.             {
    5.                 sys.interpreter.RunUntilDone(0.1f);
    6.                 count++;
    7.                 if (count == 10)
    8.                 {
    9.                     UnitSelectedMsg msg = new UnitSelectedMsg();
    10.                     msg.UnitId = 1;
    11.                     gi.Handle(msg);
    12.                     TurnBeginMsg turnMsg = new TurnBeginMsg();
    13.                     turnMsg.TurnNumber = 3;
    14.                     turnMsg.FactionId = 1;
    15.                     gi.Handle(turnMsg);
    16.                 }
    17.                 if (count == 30)
    18.                 {
    19.                     gi.CanSpeechContinue = true;
    20.                 }
    21.             }
    22.  
    And the output
    Code (output):
    1.  
    2. Say: Here's some text from the game world!
    3.  
    So in this case, Invoke is called on the UnitSelectedMsg and TurnBeginMsg first, while the Say() intrinsic is blocking, because CanSpeechContinue is false. Then, 20 frames later, CanSpeechContinue is set to true, which fires the rest f the Say() intrinsic, but at this point, what happens to the events that were invoked? I would have thought the MiniScript event loop is frozen while the Say() intrinsic is blocking, and once it is unblocked, the event queue would fire off the 2 handlers, but I never get any print() output from the two handlers.
     
  39. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    That looks like it should work. My expectation is the same as yours. I tried to simulate it all in my head and debug it by inspection, but failed to spot the problem that way.

    So, I'd suggest digging a little deeper to where you're enqueuing the events, either by stepping through it with a debugger or adding more Debug.Logs. Are we failing to find the handler? Is the eventQueue somehow empty? I'm really scratching my head on this one.

    If you want to zip up your test project (looks like it's a plain vanilla C# project? That's OK, I often work on MiniScript the same way) and PM me a Dropbox link or similar, I'll be happy to take a look myself.
     
  40. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    @JoeStrout

    Okay, I found a workaround that I'm happy with.

    I moved the "_events =[]" declaration to the very top of my script instead of immediately before the processing loop. What I was noticing was that after the intrinsic function returned Result true, the line "_events=[]" was getting called, wiping out the list. I tried debugging, but couldn't follow it completely.
     
  41. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Of course! You found it exactly.

    In the non-working case, you had the script blocked on the Say call in the global context. While it's blocked, your event-enqueuing code runs, doesn't find an _events member in the global space, and so it creates it (and adds your events to it). Then the Say call finally completes, and the script continues to the boilerplate code you added to the end, which includes _events = [], wiping out the events you had enqueued at that point.

    When I wrote that, I hadn't anticipated code in the global context blocking long enough for this to matter!

    If you want to guard against that sort of thing (while still allowing you to only append to the user code), you could change that _events = [] line to something like

    Code (miniscript):
    1. if not globals.hasIndex("_events") then; _events = []; end if
    That will avoid wiping out the _events list if it's created by the C# code. Or, I suppose, you could just always stuff _events into the global space in the C# code, right when you initialize the script (but before you run it), and then take that _events = [] line out of the hidden code entirely.
     
  42. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    That makes complete sense. Derpaderp. I was just so happy to get it working that I couldn't really work through why it was doing what it was. Thanks. I'm going to stick with my solution of prepending the _events list to the script and appending its usage in C#.
     
    JoeStrout likes this.
  43. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    610
    A couple of trivial things after more carefully reading the manual.

    There's a typo in both the map examples on page 15 where the comments don't match the code.

    What's the thinking behind the .sum functions on list and map? They don't hurt anything, but I can't imagine they'd ever be used and the language is so streamlined everywhere else.
     
  44. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Well spotted — those typos (and several others) will be fixed in the 1.2 release (coming soon).

    I've actually used a sum over a list fairly often. It's the first step in computing an average, for example (and is also used in most other statistical measures).

    Sum over a map is harder to justify, but the thing is, once we have a function in the namespace at all, there is no additional cost to making it work on as many data types as possible. Internally we have to check the argument type and do something about it anyway, so we follow the principle of "if somebody tries this, let's make it do what they would reasonably expect it to do."

    If I could think of any reasonable definition of sum over a string, I'd make it work for strings too. :)
     
    Last edited: Jun 24, 2018
  45. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    Version 1.2 has been submitted to the App Store. This includes a half-dozen small enhancements to the language, and a similar number of fixes/additions to the documentation.

    I'll post here again when the update is approved.
     
  46. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    MiniScript v1.2 is now available! This release enhances the language in several ways, and also includes one change to the Interpreter class that makes it perform better in typical cases:

    • Added a .values intrinsic to map; returns all values in the map as a list.
    • The 'return' statement can now be used without a value, implicitly returning null.
    • Fixed a precedence issue between . and [] which caused (for example) a.b[42] to fail (where 'a' is a map containing a list or map 'b').
    • Added 'true' and 'false' keywords, which evaluate immediately to 1 and 0 respectively.
    • Added short-form 'if' statements, of the form:
      if <condition> then <result1> [else <result2>]
      where <condition> is any expression, and <result1> and <result2> are single statements.
    • Fixed equality testing (both == and !=) on lists and maps, which previously always returned null. It now returns 1 or 0 by doing a deep comparison of the elements, unless the equality cannot be determined (for example due to a self-referencing map or list causing an infinite recursion), in which case it returns 0.5.
    • Changed the behavior of Interpreter.RunUntilDone, so that it (by default) bails out early if it reaches any intrinsic method call that does not immediately complete. Specify false for the second parameter to get the old behavior.
    • Updated the MiniScript User Manual, Integration Guide, and Quick Reference.

    This is a recommended (free) update for all users.
     
    tosiabunio likes this.
  47. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    First, make sure you're running the latest version of MiniScript. It changed the default behavior of RunUntilDone slightly so that, if the interpreter hits some intrinsic function that doesn't finish its results right away — like wait for example — then RunUntilDone bails out right away, instead of calling that intrinsic over and over until the 0.01 seconds are up (or it finally finishes its work).

    So, if your script is like most scripts, where it's doing a bit of work and then calling wait(0.01) or whatever, then this will help a lot.

    Second, you might want to consider adding a waitFrame intrinsic, that waits for the next update:

    Code (CSharp):
    1.         f = Intrinsic.Create("waitFrame");
    2.         f.code = (context, partialResult) => {
    3.             if (partialResult == null) {
    4.                 // Just starting our wait; return a partial result (value doesn't matter)
    5.                 return new Intrinsic.Result(null, false);
    6.             } else {
    7.                 // When we're called again, then it should be the next frame already.
    8.                 return Intrinsic.Result.Null;
    9.             }
    10.         };
    11.  
    This depends on the change to RunUntilDone above, where an intrinsic that doesn't finish won't get called again until the next frame. Of course with that change you could get the same effect by calling wait(0.001). But waitFrame is cleaner.

    Chances are this will be enough, but if you have a lot of scripts running at once, you might want to crank the runtime down from 0.01 to 0.001 seconds. That's still (somewhat surprisingly) enough time to keep scripts running reasonably fast, but they can't suck all your CPU time even if they sit in a tight loop.
     
  48. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    @JoeStrout I would like to print out the name of a function in Miniscript. How would I do that?
     
    Last edited: Aug 2, 2018
  49. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    8,138
    I'm afraid you can't — functions don't have names. They have parameter lists, which you can see with something like
    print(@f)
    (where f refers to a function), but they don't have names — f in this example is just a variable to which a function has been assigned. Its name is the name of the variable, not of the function.

    Depending on what you're trying to do, maybe there's some other solution? For example, you could set up a map that maps functions (referenced with @, as above) to names.
     
  50. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,155
    I did find another solution yesterday, but I still wanted to know the answer regardless :).

    Basically, in my event handler I was doing something like this:

    Code (Miniscript):
    1. _func = _event["handler"]
    where I was assigning _event["handler"] as a VarFunction in C#, after doing a substitution from the string to the actual function. Instead, I kept _event["handler"] as a VarString. This allowed me to do the following in Miniscript:

    Code (Miniscript):
    1. _funcName = _event["handler"]
    2. _func = globals[_funcName]
    And this works great.
     
    JoeStrout likes this.