Advertisement

SGScript - the new and improved scripting engine

Started by July 11, 2014 08:49 AM
1 comment, last by snake5 10 years, 4 months ago

logo-red.png

The Story

Just about as long as I've done game programming, I remember interacting with some kind of a scripting engine. GML, Lua, AngelScript, Python. All of them have something to dislike, something that makes a developer pull his hair in an attempt to defy laws of gravity as even that seems easier than making the code behave just the way it's needed at the time. I'd been that developer for some time and I've had my share of those moments. Then, two years ago, near the beginning of the year 2012, I decided that I should try to address every problem I noticed in there, as well as integrate the successful decisions of various languages. There were tradeoffs to be made, not all good things go well together, though there is some place for being proud of what's accomplished. High portability, ease of integration, accessible API and debugging / profiling tools.

The Reasons and Solutions

  • Programmability - control over software should not be exclusive to the man with the compiler and all that other stuff. Sometimes it helps to be able to just change the things you want. In those moments, SGScript is there for you. As a dynamic language with a friendly interface, it allows to do a great deal of work with a few small scripts.
  • Portability - with other operating systems and cloud storage becoming more accessible, it is important that binaries as well as SDKs are small, and everything works largely the same way everywhere, with only a few things to swap when moving between different OS's, to make it easy to work on your projects anywhere, without setting up huge SDKs and build systems. Working in a cloud storage folder is a real possibility, and not only that - I've been doing it when I need to test how things work on different platforms.
  • Accessibility - it should be easy to pick up the language. SGScript syntax is mostly based on C / JavaScript. This saves time for most common users of scripting languages in that they know one or two similar languages and the syntactical differences can be picked up in a day. Behaviors are documented and simple.
  • Compatibility - with a few code files, everything's possible. Adding non-trivial makefiles, various dependencies on other libraries or even a C++-only API makes things worse. It's sometimes necessary or helpful but certainly not here. SGScript therefore can be fully compiled with just one step, using the most available tools: GCC, a C runtime library and GNU Make.
  • Performance - don't you just *love* it that when closing in on the release date, suddenly there's garbage collection hiccups everywhere and there's no good way of getting rid of them? Since memory management to some extent is always a necessity, it's easier to just start with that. There's not much to be done anyway and whatever there is, the included memory profiler will help with that. It shows allocations / frees / allocations - frees (memory block count change) / memory usage change in bytes per call stack frame so it's easy to see where your resources go and where they come back.

The Package

SGScript is the game scripting solution and a library that helps you add external programmability to your software. To find out more, you can visit the website at http://www.sgscript.org! You can even try the language at http://www.sgscript.org/try to see for yourself how easy it is to use.

The Extensions

I've built a few extension libraries for SGScript. All of those can be found at https://github.com/snake5?tab=repositories. sgs-sdl, sgs-box2d, sgs-audio and sgs-ui are some of the more finished ones, though they do lack proper documentation at the moment.

SGScript in the Wild

I've used these libraries for two published game demos. Both are top-down shooters due to their artistic simplicity.
Ludum Dare #29 game: http://www.ludumdare.com/compo/ludum-dare-29/?action=preview&uid=34947 (includes source code). Using pre-v0.9.5 libraries so swapping something might not work or require the full set of new libraries + some code changes.
TACStrike: http://sgscript.org/files/tacstrike.zip (screenshots: LBrsYGds.jpgv0ohEnEs.jpguPU7Lj7s.jpg). Source code might be released later. Using old (pre-v0.9.0) libraries.

Near Future

Currently the plan is to finish a UI toolkit, make a basic 2D game engine and an editor, finish other libraries and document all written code to make them available for everyone. As demonstrated, all those libraries are usable and stable, however the lack of documentation leaves only sample code and original source code as the only ways to learn the library.

UI library test video: http://screencast.com/t/yPZ7nV3zYKL

UI library / 2D editor screenshots: GhJdZyps.png | 1tpEsGus.png

sgs-sdl-3D screenshots: ox40MOKs.png | auHMPSgs.png | zbhoaZ0s.png

Conclusion

I hope you'll have just as much fun using the library as I do. If you have any questions about it, you can post them here or ask me directly. If you run into any bugs, you can either post them in GitHub (https://github.com/snake5/sgscript/issues) or send me an e-mail. A test case, in the form of one or several source files / code snippets, must be included.

E-mail: snake5creator+sgscript [at] gmail [dot] com
Twitter: https://twitter.com/snake5creator

Very cool. I actually have a project I have been working on for some time as well that is remarkably similar. Rather than my own scripting language I am instead using a C# compliant parser/compiler, but the way we both process the c/c++ files to generate the binding is remarkably similar. Even down to the GCMark method and macro names, just replace SG_ with NOZ_ and I have the same macros.. crazy. :)

It has been a lot of fun developing this stuff actually, hope you enjoyed it as well.

Advertisement
Thanks. The similarities you mentioned are rather interesting. I couldn't find much about your project on the Internet, though.


Anyway, the main reason why I'm posting now is this - it seems that every time I ask for opinions (at various online chatrooms), I hear it's not explained why, from a purely objective viewpoint, this engine/language should be used instead of so many others. So I think I'll do that thoroughly right now.

Native serialization
[+] SGScript, Python
[-] JavaScript, Lua, Squirrel

Without any extra work, most of the data can be converted to a byte buffer so that it could be stored in a file or sent over a network and later converted back again. Languages that don't have it will require extra effort to get it and make sure it works with all kinds of custom objects. How much extra effort? Well, try googling for "lua serialize userdata". I didn't find a single way. SGScript - easy: https://github.com/snake5/sgscript/blob/6e9349a5a1ef5210ee440301b889f9afd78291be/ext/sgsxgmath.c#L248

Reference counted memory management support
[+] SGScript, Python, Squirrel
[-] JavaScript, Lua

This is a preferred method to garbage collection since it releases resources as soon as possible, avoiding random stalls throughout the game and thus providing a smooth gameplay experience. There are algorithms that reduce these stalls (incremental/generational garbage collection) but, given enough objects, they will be noticeable again. Source: http://sealedabstract.com/rants/why-mobile-web-apps-are-slow/

Custom native objects with complex links
[+] SGScript, Python, JavaScript (partial support)
[-] Lua, Squirrel

These are objects that can be created in C/C++, with special interfaces that support operator overloading, serialization, debug printing, conversions, cloning, type name and iterator retrieval, index and property retrieval. If not used, this feature has no cost, however it helps greatly with defining fast and accessible interfaces by encapsulating native resource management and access. To see what SGScript objects are all about, check the previous GitHub link, there's quite a lot of them.

Native arrays
[+] SGScript, Python, Squirrel, JavaScript (partial support)
[-] Lua

Native arrays (not to be confused with arrays that contain native data types, that is a subset of these) offer increased performance and memory-friendly storage over arrays made from hash tables. Array stores size as uint32, capacity as uint32 and values (16 bytes + extended data in SGScript) x size. A table would store all the same + keys (16 bytes + extended data) + hash array (size may differ but it's generally another array with size, capacity and a list of hash and index values). When arrays are considered, less (memory usage) is more (capacity).

Map support (all non-string/number keys)
[+] SGScript, Python, JavaScript (requires the support of an extension)
[-] Lua, Squirrel (some types are not supported in both)

The ability to map any variable to any other variable provides extended metadata storage possibilities - it can be stored without modifying the original variable.
m = map();
m[ sprite_obj ] = { high = 5 };

Game math library
[+] SGScript, Python, Lua, JavaScript
[-] Squirrel

A library with vector/matrix objects and functions for games. Not having to rewrite at least the bindings for it saves a lot of time.

Native debugging/profiling facilities
[+] SGScript, Python, JavaScript (support differs between JS engines)
[-] Lua, Squirrel

Introspective debugging and time/memory usage profiling can help resolve various issues found. SGScript supports call stack time, instruction time and call stack memory usage profilers out-of-the-box. At any point, all data can be dumped via the built-in output facilities that can be rerouted to any file or parser. They are written in C to ensure a practically minimal performance impact while profiling. Significantly less than if the profiler was written in Lua, which is the main solution there.

There's also access to some stats in SGScript so it is easy to see, for example, how many new allocations were done each frame.

Advanced native function argument parsing facilities.
[+] SGScript, Python
[-] Lua, Squirrel, JavaScript

Every modern scripting engine should have a function that parses and validates function arguments according to a specification and puts the data in the specified locations. With bigger functions it saves you from writing a lot of boilerplate code.

SGScript:
SGSFN( "fmt_string_parser" );
if( !sgs_LoadArgs( C, "?m|ii", &off, &bufsize ) ) // in case of type mismatch, emits a warning
    return 0; // ... and returns here to continue execution

Lua: (source: http://forums.tigsource.com/index.php?topic=36737.0)
float x  =luaL_checknumber(L,1); // in case of type mismatch, emits a fatal error, cannot continue script execution after this function call
float y  =luaL_checknumber(L,2); // same here
const char* str=luaL_checkstring(L,3); // same here

Non-fatal error messaging facilities without exceptions
[+] SGScript
[-] Python, Lua, Squirrel, JavaScript

This feature allows you to try and continue execution after a failed function call or intercept the error for debugging with the option of continuing later anyway. This is useful when code is published and there's a necessity to avoid going back to bug fixing immediately, before gathering more information about the state of the program.

Why exceptions don't fit the criteria: they force the code to break out of the original execution path, thus severely reducing the usefulness of error suppression with logging.
name = string_cut( other.name ); // warning: missing argument 2; after call, name = null
// name gets printed somewhere as 'null' or is invisible due to some other function not accepting null for a string
// everything else works

Built-in introspective pretty-printing (variable dumps)
[+] SGScript, Python, JavaScript
[-] Lua, Squirrel

This is a very useful feature to have when you need to debug data (i.e. always). Simply passing a variable to some function (for example, printvar) prints some useful information about it - the type, contents, linked resources.

Warning suppression on missing property access
[+] SGScript, Python (requires exception handling code)
[-] Lua, Squirrel, JavaScript (no warnings about it at all)

This feature allows to specify, per-read, whether the property is expected to be there or not.
a = obj.prop; // expected, emits a warning if not found
b = @obj.prop; // might not be there, no error
This also works for many other actions, like function calls and assignments.

Custom native iterators
[+] SGScript, Python
[-] JavaScript, Lua, Squirrel

An extension for custom native objects, it allows to create objects that can be foreach'ed through.
foreach( entry : io_dir( "." ) ) println( entry ); // prints the contents of current directory

Dual access dictionaries
[+] SGScript, Lua, JavaScript, Squirrel
[-] Python

The ability to access simple dictionaries just like any other object visually and syntactically reduces code complexity.
a.b = x; // simple
a["b"] = x; // not so simple

Explicit closures
[+] SGScript
[-] Lua, JavaScript, Squirrel, Python

Explicit closures (specifying which local variables to pass over to the newly defined function) make it easier to read code using closures and prevents closure-related accidents, like having a variable changed unexpectedly.

Multi-index/property-set operation without temporary tables
[+] SGScript
[-] Lua, JavaScript, Squirrel, Python

Simplifying code further without the introduction of sub-optimal memory access patterns.

SGScript:
obj.{ // object name written only once, accessed only once
    a = 1, // property write
    b = 2, // property write
    c = 3, // property write
    d = 4, // property write
};

Lua, method 1:
obj.a = 1 // property write
obj.b = 2 // property write
obj.c = 3 // property write
obj.d = 4 // property write
// object name written four times, accessed possibly four times (depending on compiler)

Lua, method 2:
for k, v in pairs({ a = 1, b = 2, c = 3, d = 4 }) do // create table, property write x4, function call, create closure
    obj[ k ] = v // object access x4, property write x4
end

C-like syntax
[+] SGScript, JavaScript, Squirrel
[-] Lua, Python

With the overwhelming majority of code being written in C-like languages (http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html), having a compatible syntax helps when you need to copy code - there's simply less changes to perform.

Indices start at 0
[+] SGScript, JavaScript, Squirrel, Python
[-] Lua

Even though it is argued that it may help someone understand code better (which is actually hard to prove), I have a few arguments to make against it:
  • Array index is generally the distance (or as programmers would rather say, offset) from first element. 1 - 1 = 0. Not a distance from 0th element that doesn't even exist. Not the first element itself because elements have no keys in arrays.
  • Programming languages don't exist in a vacuum. Almost every other programming language treats indices as distances (offsets). Going against the grain makes it hard to interpret both languages at once for comparison or interface design. Whoever has to write that binding, has to keep in mind this difference for every array access made in the binding area. This is brain power not spent well.
  • Similarly to previous argument, this difference makes porting code from other languages harder.

This topic is closed to new replies.

Advertisement