In this post we are going to be talking about object and component management systems inside of lua. This post is best read after my previous post involving initializing and setting up your lua interface.
At some point when you are initializing your lua interface you are going to need to create your object and component management system. Thanks to lua this is actually pretty straight forward however it can sometimes be a bit confusing. What we will be discussing in a moment is in regards to a game engine which supports both C++ and lua components, or what I call "hybrid game objects." You may be making a game engine which supports ONLY lua components in which case you would do this slightly different.
The very first step is to make a table which will hold ALL of your game objects, or if you wish to separate your game objects into different spaces (or game spaces) then a table what holds all of your game spaces, which are tables which hold all of your game objects.
If you are taking the multiple game space approach then make sure to make functions for creating and removing game spaces that are called from C++ whenever a game space is created or removed.
Now that we have a place for storing game objects inside of lua we need a place for storing lua components. The obvious answer is to store them inside of the game object inside of lua. The game object inside of lua can simply be a table with object identification data and a list of all attached components as well as some other important information like a handle to the corresponding C++ game object, and a reference back to the game space table which the object is stored inside of.
Now we should talk about what we want to store inside of a lua component.
Beyond state data for the lua component itself there's quite a lot of information you're going to need inside of it. First off is a handle or pointer to the owning C++ game object (for speed and convenience sake), a reference back to the lua game object table, a reference back to the lua game space table (once gain for speed and convenience), a handle or pointer to the C++ component corresponding to this lua component (more on this later), and lastly the name of lua component itself (or an identifier) so we know what type of component it is.
You may have noticed that some of the data stored inside of the component could be accessed from the game object however this is one of those things where just storing that data inside of the component itself is much simpler when working with the component since it means that you have to type less to get something like the owning game space or the handle to the owning C++ game object.
Now in regards to the "Handle/Pointer to the C++ component corresponding to the lua component" ("Handle/Pointer to the C++ lua component" on the diagram.)
So far we have only setup a way for lua to know about what lua components are attached onto a game object and we've left C++ blind to this. We could make a function that queries the component list and returns it back to C++ however that would be cumbersome to call every time we need that information, instead I purpose that for each lua component a game object has we have a corresponding instance of a "lua component proxy" component inside of C++. I apologize for how wordy this is going to get.
The "lua component proxy" component in it's simplest form is just a string with the name of the real lua component attached to the game object. You could actually make this a vector of strings inside of your game object if you really wanted to however there may be some additional functionality you'll want to add (like lua component serialization) so that might not be the best of ideas. It should also act just like any C++ components that you have (an exception here is if you have a limit of one component per type on each object then you're going to want to store the "lua component proxy" components differently.)
Beyond simply storing the name of the lua component, the "lua component proxy" component should also call the proper lua functions to instantiate and attach the lua component onto the game object, as well as call the proper lua function to remove the lua component when the "lua component proxy" component is removed from the game object (or the game object is being deleted.) Most importantly however is all of the "lua component proxy" components should be serialized so that when a saved game object is loaded it will have all the proper lua component attached to it.
Keep in mind that the lua component itself is stored inside of what we covered previously.
So you may be wanting to know what are the actual functions you are going to need for all this so i'll list them here.
--[[ Registers a game space inside of lua. name is the name of the gamespace and space is a pointer to the game space. We will want to store the pointer inside of the lua table. ]]--
AddGameSpace(name, space)
--[[ Removes a game space which will delete all objects and lua components inside of it ]]--
RemoveGameSpace(name)
--[[ Attaches a lua component onto a game object, if this is the first lua component attached to the game object then we will make a new game object table inside of the game space. space is the name of the game space,owner is the handle or identifier to the owning game object, compHandle is the handle or identifier to the "lua component" component, compName is the name of the component ]]--
AttachComponentToObject(space, owner, compHandle, compName)
--[[ Removes a lua component from a game object. space is the name of the game space, objHandle is the handle of the owning object, and compHandle is the handle to the "lua component" component. ]]--
RemoveComponentFromObject(space, objHandle, compHandle)
In my implementation the Attach/Remove component functions are only ever called by my "lua component proxy" component.
In a previous blog post I talked about using an event hooking system which covers this which I highly recommend you read. Update logic should be handled through an event system which is inside of lua but called from C++, this way it takes one lua function call to update all of your lua components.
Beyond simply having the name of which lua component should be added to the object, "lua component proxies" can be used to store data used for serializing lua components. I'm going to put some example code here and then talk about how it works.
Since lua is an interpreted language we can do crazy stuff like have lua write a script and then run that script, which is why this works. What we are doing is creating a function which takes a variable named self which will be the component table we pass in later when we want to set it's values. We then iterate through every single value inside of the component we are serializing and write code that will set the variable on self to equal what it is now. The resulting code then is saved inside of a string field inside of our "lua component proxy."
We introduce some arbitrary limits with this code snippet, first off we never serialize a variable if it's a table or userdata. By writing a recursive function it's actually very simple to save tables as well so you can implement that yourself if you please. The next limitation is that no variables with a leading underscore "_" are serialized. This is chosen so that I can easily hide variables from being serialized, completely optional mind you.
This is a shotgun approach to serializing lua components and is purely a proof of concept so I wouldn't recommend implementing this verbosely but instead a modified version that better suits your needs. Below is the very complicated script for Deserializing a lua component.