PrevUpHomeNext

Intro

Let's suppose that you are designing a C++ application which employs a scripting language in order to customize its behavior. For instance, maybe it embeds Python or lua.

Most likely, there is some dedicated object in your application that represents an instance of a virtual machine, and acts as an RAII object wrapping over functionality provided by a C implementation of your scripting language of choice.

In modern C++, how would we expect that the interface to such an object might look, and what are some basic functionalities that we might hope for or expect?

Here's a sketch, using the PIMPL idiom:

// Just an example "message" structure, specific to your application.
struct message {
  std::string command;
  std::string parameters;
};

// Represents a virtual machine
class VM {
  struct impl;

  std::unique_ptr<impl> impl_;
public:

  // Special member functions
  VM();
  ~VM();

  VM(VM &&);
  VM(const VM &);

  VM & operator = (VM &&);
  VM & operator = (const VM &);

  // Initialize from a script
  void load_and_execute_script(const std::string & script);

  // Send message from the application logic to the script inside the VM.
  //   (Means, call a function which was earlier constructed and registered by
  //   the script, to be the message handler).
  // May throw exceptions in case of an error (?)
  void send_message(message m);

  // Get output messages from the script
  //   (e.g., which were emitted using an api function "send_message")
  bool has_output_messages() const;
  const message & top_message() const;
  void pop_message();

  // Serialization & Deserialization
  std::string serialize();
  static VM deserialize(std::string);
};

Given this kind of an interface, the details of the VM are quite well encapsulated.

From the point of view of the rest of the application, the VM is just a "movable, copyable, serializable black box that can send and receive messages." Nothing about the particular scripting language is even visible at all.

Quite possibly, one would want a richer interface, allowing the VM to interact more directly with other components of the application rather than having to make requests via message objects. But for purposes of exposition, this should be sufficient for now.

Most of these member functions are straightforward to implement when using a language like Python or Lua, but three of them, perhaps surprisingly, are not:

Indeed, it's relatively uncommon for a scripting language implementation to provide built-in facilities to deep copy a VM, or to provide built-in serialization facilities.

There are several common reasons for this:

However, in some applications, it's very important to be able to do these things.

For instance, in a game engine, a scripting language VM might be used to represent the game logic and the state of the game. Then, you really want to be able to deep copy the VM, if one wants to create "simulation-based" AIs which can experiment with a position without messing up the "actual" position. You really want to be able to serialize and deserialize the state faithfully, to create saved games, and replays. You really want these things to work correctly with a minimum of hassle, as it can be very frustrating to users when it doesn't work right. These problems are often hard to reproduce and very finicky to solve. Even popular AAA game engines sometimes have lingering problems with saving and reloading the game.

Another popular use of scripting languages is in Webservers. In web frameworks like Ruby-on-rails and OpenResty, web applications are written in a scripting language like Ruby or lua. Serializing and deserializing a VM state has an obvious potential use in load balancing -- if a server gets bogged down, some of its threads could pause and their VM states written to disk. Then they could be moved and booted back up again on a different machine seamlessly.

Primer is a C++ library that makes it easy to expose complex APIs to lua while also preserving its serializability, using a lua technology called eris. eris is a very powerful serialization facility for lua 5.2 and 5.3, but many of the strategies traditionally used to expose C++ APIs to lua don't work very well together with serializability or with eris.

In this quick-start, you'll see


PrevUpHomeNext