🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Serializing C++ objects with references

Started by
4 comments, last by WitchLord 9 years, 6 months ago

Hello, I'm thinking about all problems I'll have to face when implemening hot-reloading of scripts.
Currently, I assume that I'll use the Serializer add-on.

I have difficulty to find a good solution against the following problem:


// In C++ (simplified)

class ScriptObject
{
    int addRef();
    int release();
    // etc...
};

class Foo : public ScriptObject
{
    string name;
};

class Bar : public ScriptObject
{
    Foo * foo; // A function is used to bind this field to AS
};

// In AS

//-----------
// Script before hot reload:
Foo@ foo; // Globals
Foo@ foo2; // Global too etc
Bar@ bar;
Bar@ bar2;

// Somewhere:
foo.name = "Yay";
bar.foo = foo;
bar2.foo = foo;

//-----------
// Script after hot reload:
// The user removed foo and foo2
Bar@ bar;
Bar@ bar2;

// Somewhere else:
print(bar.foo);
print(bar2.foo);
// Expected: printing "Yay" twice, coming from the same Foo instance.

First, how can we serialize Bar?

- Save foo as a full instance?
Despite "Yay" will be printed twice,
we'll end up with two Foo instances instead of a shared one...

- Just save the address of foo in it, not its value?
Foo is deleted in the process of recompiling, so the print will badly crash in C++.

- Call addRef on each C++ objects before recompiling, so they stay in memory when recompiling?

Then, how would we prevent objects from leaking?

I'm new to hot-reloading with AngelScript, so does someone has an idea how to handle this case?

Advertisement

The serializer logic must keep track of which object instances have already been serialized, so that when a reference to the same object is seen from somewhere else it just stores some identifier to that first serialized instance instead of serializing it again.

Using your example, you could serialize it to the following text buffer:

object 0001 type "Foo" value "Yay"

variable "foo" type "Foo@" value ref to 0001

variable "foo2" type "Foo@" value null

object 0002 type "Bar" value ref to 0001

variable "bar" type "Bar@" value ref to 0002

object 0003 type "Bar value ref to 0001

variable "bar2" type "Bar@" value ref to 0003

It's obviously invented syntax, but hopefully you can easily understand that there are 3 object instances (1 Foo, and 2 Bar). Each are identifier with a sequence number. The 4 variables from the original script are serialized by the name and refer to the serialized objects by the sequence number.

De-serializing this into the hot-loaded script would work as following:

1. Create an instance of "Foo" and store in a map with key 0001

2. Skip the variable "foo" since it is no longer present in the script module

3. Skip the variable "foo2" since it is no longer present in the script module

4. Create an instance of "Bar", and populate its member to refer to the instance of "Foo" found in the map with key 0001. The instance is stored in the map with key 0002

5. Set the variable "bar" to refer to the instance of "Bar" stored in the map with key 0002

6. Create an instance of "Bar", and populate its member to refer to the instance of "Foo" found in the map with key 0001. The instance is stored in the map with key 0003

7. Set the variable "bar2" to refer to the instance of "Bar" stored in the map with key 0003

The above shows how it might work if you serialize Foo by storing its content. If instead you only serialize the reference to Foo, then the serializer need to increment the refCount so the object is not destroyed too early, and then release it after you've hot-loaded the script.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

So you mean that I could serialize C++ objects by doing addRef(), storing their pointer as userdata, and then release() on Restore() or Cleanup() ?

(at least for reference-only types)

Yes. If you want to keep the same instance then that is the way to do it.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Ok, I just sketched a generic user type for my classes inheriting ScriptObject in C++, however, now I'm facing a new problem:


    // Generic object saving for simple reference types
    struct ScriptObjectType : public CUserType
    {
        void Store(CSerializedValue * val, void * ptr)
        {
            ASScriptObject * obj = (ASScriptObject*)ptr;
            obj->addRef();
            val->SetUserData(obj);
        }

        void Restore(CSerializedValue * val, void * ptr)
        {
            ASScriptObject * obj = (ASScriptObject*)(val->GetUserData());

            *(ASScriptObject*)ptr = *obj; // This does more than wanted, also requires a working copy constructor
            //ptr = obj; // Why can't we simply do this with ptr as a reference?
        }

        void CleanupUserData(CSerializedValue * val)
        {
            ASScriptObject * obj = (ASScriptObject*)(val->GetUserData());
            obj->release();
        }
    };

As you can see, it seems that despite my object is a reference, I'm forced to copy it by value for this to work. Just giving back the pointer should have done the job, but now there is two issues:

- All objects must have a copy constructor, even if they are reference types, which is not always available/feasible

- Big objects that were designed to be references will be copied, resulting in big memory shifts, in the case of assets for example.

Under the hood, the Serializer does


void *newPtr = m_engine->CreateUninitializedScriptObject( type );

And that's the ptr we get in Restore().

Does it means I can't keep the object at the same address, with the need to copy it? Or is there a detail I'm missing?

I must confess that I do not know the details of how the CSerializer works. I didn't write this add-on myself. It was a contribution from FDsagizi. It may not be a one-size-fits-all solution, though I believe it is a very good starting point to show how serialization of script modules can be done.

I welcome suggestions for how to make it even better.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement