Why SGScript?
Native serialization
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
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/
Coroutines
True cooperative multitasking allows the direction of timed events to be greatly simplified. SGScript goes one step further and also provides helper constructs (thread, subthread, sync, race) to make things as simple as possible for the user.
Custom native objects with complex links
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
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)
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
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
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.
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
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)
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
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
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
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
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
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
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
Even though it is argued that 1-based indexing 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.