Advertisement

How *do* you Hot Reload Application-made Script Classes, anyway?

Started by July 09, 2022 02:00 PM
6 comments, last by Varrok 2 years, 4 months ago

Hi there ?

I'm trying to implement a run-time script editing feature into my project. However, having a (simplified example) script of:

class Main
{
    float test = 10.1;

    void Update(float dt)
    {
        test += dt;
        print(formatFloat(test));
    }
};

Whenever I rebuild the module, test returns to 10.1, which I consider unwanted in my case. Please note, however, that using a script like:

float test = 10.1;
class Main
{
    void Update(float dt)
    {
        test += dt;
        print(formatFloat(test));
    }
};

Issue never occurs, and after reloading the script module test contains previously incremented value.One instance of Main exists at the time (at least that's the idea), and is created via application in the exact same way described in: https://www.angelcode.com/angelscript/sdk/docs/manual/doc_use_script_class.html#doc_use_script_class_1

In order to hot-reload, I'm using the Serializer addon ( https://www.angelcode.com/angelscript/sdk/docs/manual/doc_addon_serializer.html )

  1. I store the module, and the main class object using the factor handle serializer.AddExtraObjectToStore(pMainObj),
  2. I discard module, and try to remove all traces of it (although GC seems not to clear everything instantly),
  3. Build the module again from the modified file (for the sake of example let's say I just saved it over itself without changes, still triggers the reload),
  4. serializer.Restore(module) (module variable points to the new module at this point.)

Sadly, every time I do it, test returns back to the initial example value of 10.1f.

I've spent noticeable time trying to debug this, and the serializer *does* seem to attempt restoring the test var, but even though some test value seems to be changed to the incremented one inside serializer.Restore(), in the app it still resets somehow. It's really confusing.

A thing of note is that the value of module->GetTypeInfoByDecl("Main")->GetTypeId() changes pre and past reload - type id gets incremented by 1. I'm afraid I have no interface to force match those.

(Update() is called from the application)

In your first script, the test is a member of the class. The fact that it gets reset to 10.1 tells me that the class' constructor is being called to initialize the members, Verify how you are creating the new instance of the script class. You should probably be using asIScriptEngine::CreateUninitializedScriptObject so the constructor is not called, and then manually initialize the members based on the serialized data. If you use the ordinary asIScriptEngine::CreateScriptObject it will automatically call the constructor, and it may invoke some script logic you don't want to re-execute as part of the hot reloading.g

I assume you've read the article on hot reloading in the manual. It's not very detailed, but it gives some general hints and tips on how to accomplish what you want,

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

Advertisement

Thank you very much for the quick response,

It is true that the constructor is called to initialize the members (at the step 3 of my previous post, for clarity), however this happens right before I call serializer.Restore(module) which I believe would replace the default float value of test (through restoring all members of stored Main object), with no regards to whether it was previously initialized or not.

I've just tried to replace the second making of the Main object using asIScriptEngine::CreateUninitializedScriptObject, though sadly the test value stays at 0 before and after the serializer.Restore(module) call.

I assume you've read the article on hot reloading in the manual. It's not very detailed, but it gives some general hints and tips on how to accomplish what you want,

I've read that article, without it I would not get as far as I am now ? Kudos to people involved in writing the large documentation

It sounds like you're not restoring the instance of the Main class correctly. As you said, it should overwrite the content of the members.

Can you show the code you've implemented so I can take a look at it?

Varrok said:

? Kudos to people involved in writing the large documentation

Thanks. That would be mainly me ?

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

Here's a quickly drafted minimal project to show the issue in action:

https://www.sendspace.com/file/cckt2g

To build:
1. git init && git submodule update --init
2. CMake the project

Hope it's useful to help you help me

Ah, you missed the step to retrieve the serialized instance of the script object from the serializer. Here's a sample code showing how it's done:

		// Reload the script while keeping the object states
		{
			CSerializer modStore;
			modStore.AddUserType(new CStringType(), "string");
			modStore.AddUserType(new CArrayType(), "array");

			modStore.AddExtraObjectToStore(scriptObj);

			r = modStore.Store(mod);
			if( r < 0 )
				TEST_FAILED;

			engine->DiscardModule(0);

			mod = engine->GetModule("2", asGM_ALWAYS_CREATE);
			mod->AddScriptSection("script", script);
			r = mod->Build();
			if( r < 0 )
				TEST_FAILED;

			r = modStore.Restore(mod);
			if( r < 0 )
				TEST_FAILED;

			// Restore the extra object
			asIScriptObject *obj2 = (asIScriptObject*)modStore.GetPointerToRestoredObject(scriptObj);
			scriptObj->Release();
			scriptObj = obj2;
			scriptObj->AddRef();
		}

This is from my regression test suite, so it doesn't match your code, but I think you'll easily be able to adjust it. Basically, when you hot reload the script you shouldn't create the new instance of Main yourself, the serializer will do it. You just need to retrieve the object from the serializer. In the above code scriptObj would be your mainObj.

I'll complement the documentation for the next release to better show how this should be done.

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

Advertisement

I've just applied it to that code I posted - seems to work perfectly ? Thank you a bunch for the help! Didn't even know GetPointerToRestoredObject() function existed, was looking in all the wrong places apparently.

I'll complement the documentation for the next release to better show how this should be done.

I think this is going to prevent a lot of people from having similar issues to mine. Plus I think dynamic reloading is one of the key things people pick languages like that for (at least I did)

This topic is closed to new replies.

Advertisement