Sheep scripting language

Started by
6 comments, last by BradDaBug 14 years, 5 months ago
One of the things I've been working on in my spare time are some tools for Gabriel Knight 3. One of those tools is a viewer for viewing rooms and models and stuff. As part of that I started work on implementing the scripting language used by GK3, "Sheep." I think my Sheep implementation has finally reached a level of maturity that it is ready for some outside attention. It's still in an alpha stage, so I wouldn't use it for anything important, and I'm sure it's full of bugs and memory leaks. But my hope is that someday Sheep would be useful to people as a general purpose scripting language for games, not just for my little GK3 tools project. Here is a quick sample of what Sheep looks like:
symbols
{
   int foo$ = 5;
   float bar$ = 3;
}

code
{
   main$()
   {
      if (foo$ > bar$)
      {
         PrintString("Foo is bigger");
      }
      else
      {
         PrintString("Bar is bigger!");
      }
   }
}
Since it's probably Sheep's most important feature I should mention waiting. Basically each external function (like PrintString() in the example above) cannot block. So, for example, the StartDialogue() function from GK3 executes asynchronously. To pause the script while the dialogue plays you use the "wait" keyword, like this:
code
{
   main$()
   {
      wait PlayDialogue("woots");
   }
}
You can also wait on multiple functions at once:
wait
{
   PlayDialogue("lalala");
   PlayAnimation("dance");
}
Now both things happen at once, and the script waits for both to finish before continuing. Anyway, any feedback on anything at all would be appreciated. download clicky Edit: For some more documentation on Sheep you can look at intro_to_sheep.txt and using_sheep.txt inside the zip. Edit again: I just uploaded a version of intro_to_sheep.txt that can be read online: clicky [Edited by - BradDaBug on November 6, 2009 2:52:59 PM]
I like the DARK layout!
Advertisement
Interesting [smile] your "wait" operator basically acts as a join operator, and your external functions act as fork operators. Does the join happen on all child processes created by the wrapped code? That is, what happens here:

code{  frobnicate$()  {    PlayDialogue("woots");  }  main$()  {    wait frobnicate$()  }}


Does the "wait" here join the forked dialogue?

Similarly, how do you handle asynchronous operations that return data (such as, for instance, modal dialogs) ?

Do you allow explicit forking of user-defined functions?
Calling local functions must be done through the Call() function* so the syntax for that would look more like:
code{  frobnicate$()  {    PlayDialogue("woots");  }  main$()  {    wait Call("frobnicate$");  }}

But, anyway, I need to double-check, but IIRC waiting on Call() will wait until the called function returns. In this case since you aren't waiting on PlayDialogue() then frobnicate$() will return immediately, so the wait won't really do any waiting. But if you were to wait on PlayDialogue() inside frobnicate$() as well as the wait on Call() then waiting on Call() would wait until frobnicate$() finished, which would wait until PlayDialogue() was finished.

I'm pretty sure that's how GK3 handled it, and I think that's also working in my VM.

Quote:Similarly, how do you handle asynchronous operations that return data (such as, for instance, modal dialogs) ?

I haven't found any wait-able functions in GK3 that return a value to test, so I don't know what the "correct" behavior should be. And I'm not really sure how the VM would handle that at the moment. So... undefined?

Quote:Do you allow explicit forking of user-defined functions?

Not sure what you mean.

* If you've read using_sheep.txt you may see that all the external functions have to be defined and registered by whoever is using the VM library. Call() is the one exception. My VM treats Call() as a built-in function.
I like the DARK layout!
You know, I've been working on my scripting language for ages. I've got OOP, function overloading, etc. BUT, I've not got a function as simple, and as useful, as "wait"! You sir, have inspired me to rethink this a bit :p Good work!
The semantics of this language seem rather anti-productive, don't you think?

Having to use a Call("string-version-of-function-name") is redundant, as is having to declare variables outside your functions.

Only providing some critique but I don't think I would use this over something like Lua.
thre3dee,

Actually I agree with you, but every product has to have one thing that makes it stand out from the others, in this case, the 'wait' command.

Combine this with a language we're all used to, and this would be great.



BradDaBug,

I don't want to sound like I'm trying to plagiarize you're work, but I'm interested in just how this wait command works. Does it depend in the function you call from it?
That is an interesting idea, let me ask some questions:
1)How does the implementation of PlayDialogue look like? Do you have a loop inside all of your functions?
2)How do you call the a function asynchronously? Are you creating a thread for every function call?
3) Shouldn't a functions itself decide whether it's should block or not?
For example I have a TimeLine class with a function call_in(time_in_ms,fnc), where a time delay of 0 means next frame. With the help of this class I would implement functions like PlayDialogue this way:
TimeLine timeLine;void PlayDialogue(){     // render the dialog and do whatever needs to be done in one frame     ...     // if not done tell timeLine to call this function again     if(!done)         timeLine.call_in(0,&PlayDialogue);//use boost.bind to pass arguments if needed}// main loop{    ...    timeLine.advance(elapsed_time);    ...}
Here is my implementation of StartDialogue() (from the GK3 level viewer source, which happens to be C#, but the same idea should work for C/C++):

private static void sheep_StartDialogue(IntPtr vm){   int numLines = SheepMachine.PopIntOffStack(vm);   string licensePlate = SheepMachine.PopStringOffStack(vm);   bool waiting = SheepMachine.IsInWaitSection(vm);   WaitHandle handle = Game.DialogManager.PlayDialogue(licensePlate, numLines, waiting);   if (waiting && handle != null)      SheepMachine.AddWaitHandle(vm, SheepMachine.GetCurrentContext(vm), handle);}


The call to DialogManager.PlayDialogue() begins playing the dialog. It returns a "WaitHandle" which is just a class that has a Finished boolean property that returns whether whatever the WaitHandle is waiting on has finished (in this case the playing dialog). Then if the script is inside a wait section it associates the wait handle and the current context together and keeps track of it.

Just so the code doesn't confused anyone, SheepMachine.AddWaitHandle() is just a method in my Sheep VM wrapper class that doesn't actually interact with the Sheep VM library. All the other SheepMachine methods here, like PopStringOffStack(), GetCurrentContext(), etc are just wrapped versions of SHP_*() functions.

Anyway, the next bit of code is my "end wait callback" that gets called whenever the script tries to leave a wait section:
private static void endWaitCallback(IntPtr vm, IntPtr context){   if (_waitHandles.ContainsKey(context))   {      foreach (var wait in _waitHandles[context])      {         if (wait.Finished == false)         {            // still waiting on stuff to finish, so suspend the VM            SHP_Suspend(vm);            return;         }      }      // everything is done!      _waitHandles[context].Clear();   }}


At this point all that's left is to periodically check all the WaitHandles to see if any are done (I do it once per frame), and if any suspended context has no more unfinished WaitHandles then resume that context with SHP_Resume().

The Sheep library just provides the low-level waiting stuff, like SHP_Suspend(), SHP_Resume(), etc. The code you see here is my high-level implementation of waiting on top of the Sheep library inside the GK3 level viewer. It doesn't necessarily have to work this way. But there are no threads or spinlocks or anything like that.
I like the DARK layout!

This topic is closed to new replies.

Advertisement