Hello everyone,

Since I have been trying some scripting languages back and forth for scripting a small game and I accumulated some experience, I would like to share here my experience with the hope that someone could find it useful.

This article is focused on Scripting APIs that are reasonably easy to bind with C++, whether natively or with a helper library.

I tried and tested exposing C++APIs to foreign languages for the purpose of scripting a small cards game.

Why scripting?

For people unfamiliar with scripting: scripting is usually used to accelerate the development of your own programs.

It can very effective because, besides eliminating the (explicit) compile step, you can code the logic in them with a simpler-to-use language.

With this in mind, scripting can accelerate your edit-compile-debug iterations by a lot by eliminating the compile step. Think of any change you do to your code + recompiling but eliminating the compile step. Things become almost instantaneous.

The game

The game is a networked 4-player game, where each player takes the turn to play one of the cards in their hand and cards are played until the deck is fully consumed.

It is a game called Guiñote that is popular in some areas of Spain. It has many variations like Brisca, Tute and others, though this is not terribly important for what I am going to explain in the article.

The purpose of the article is to share my experience binding a C++ API and exposing it into scripting languages.

Disclaimer: keep in mind that I am not an expert in the binding APIs therefore there could be some knowledge that escapes to me.

Relevant points about the engine architecture (for binding purposes)

The game is composed of a controller that loads screens. Each screen is represented by a script that controls the screen logic.

The script uses game views and game models, besides some networking APIs (which were written via Capnproto, stay tuned if I find myself with the strength to write an article about it!)

The C++ side of the game goes into a screen via a controller, and, at that time, it loads the screen and gives control to the scripting engine.

A virtual machine is created just before a screen is loaded and from there on the scripting is responsible for the processing.

Once the script is finished, the script returns a couple of strings:

  1. the next screen to load
  2. the arguments for the next screen (equivalent to argv in main in C++)

Control is returned to the C++ side after returning. In order to make the scripts functional, some additional data is exposed via the environment as a global: basically the runtime configuration and logger, which are available to the script engine.

When a screen finishes, the virtual machine is destroyed. This way no state is kept alive between screen runs. This also makes easier my debugging, etc.

Basically I wanted to accelerate my code logic writing for screens.

From the scripting side of things this means:

  1. Being able to load scripts at run-time from a file (when developing)
  2. Being able to load scripts from memory (when embedding the code in the final binary)
  3. Being able to pass arguments to the scripting main function.
  4. C++ types must be constructed/destroyed on the scripting side, besides used
  5. Global, already-existing data in the C++ side must be exposed to the scripting environment.

Think of one of these scripts as a program by itself that can get input arguments and has some environment (log, config, for example). It is like main in C++, Java, C#, etc. It does something, and then it returns information to the controller so that the controller knows what screen to load next.

#c++ #scripting

C++ scripting alternatives: easy-to-bind-in-C++ scripting
1.25 GEEK