It's been almost exactly 4 months since the last release. This is longest period I've gone without making a new release since this project started back in 2003. This does not mean that development has slowed down though, I merely restrained myself from releasing the code so I could fit more improvements into it. If you take a peek at the change log you'll see that this is by far the largest update too.
As always, when I change the middle version number there are some changes in the interface. The changes are not dramatic, but might require some minor code changes. The most dramatic change in the interface is the removal of the behaviours asBEHAVE_REF_CAST and asBEHAVE_VALUE_CAST (+implicit versions). These behaviours should now be registered as object methods with the names opCast and opConv, respectively. The implementation of the functions in the application doesn't change, just how they are registered with the engine.
I've improved the internal memory management so that less objects will be placed in the garbage collector. This translates into improved performance as less CPU will be required to clean up the dead objects in the GC. The change will perhaps not be too noticeable at runtime, but during recompilation of scripts and engine shutdown you should definitely see a significant improvement.
The script language has gotten some improvements too. The ternary condition operator can now be used as lvalue (as long as both options are lvalues of the same type). Script classes can implement the opCast and opConv operator overloads. Compound assignments can now be used with virtual properties too. Class members can be declared as protected, with the same meaning as in other languages.
There are of course enhancements in the add-ons too, but I'll leave the discovery of those to the reader.
If you need to know which of the members were inherited from a base class, then you need to check if the same member is declared in the parent class too. You can get the parent class with the method asIObjectType::GetBaseType.
If you want to know if the member was declared as public you'll need to check the indicator isPrivate returned from asIObjectType::GetProperty. (Note, in 2.30.0 WIP I've added support for protected members too, so a second indicator isProtected is also available. If neither is true, then the member is public).
You may also want to look into using meta data in the script classes to for example allow the script to inform the editor what values are allowed. The CScriptBuilder add-on shows one way of how meta data can be implemented and collected from scripts, if you wish to use that.
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.
Polymorphic value types is tricky in a "safe" language like AngelScript. In order to be able to send a value type by reference to a function AngelScript sometimes has to make a copy of the value type in order to guarantee that the reference will stay safe throughout the function call. When this is done on the base type, the data from the derived type is lost in the copy, which is probably why you see the object as empty.
With reference types AngelScript does not have to make the copy of the object, since the lifetime is guaranteed by the existing references, so here it is perfectly OK to use polymorphic types.
asOBJ_REF | asOBJ_NOCOUNT with no factory behaviour is a valid combination. asOBJ_NOCOUNT tells AngelScript that it is not necessary to count the references to the object, since the lifetime of the object is managed by the application. no factory behaviour tells AngelScript that no instances of the type can be created by the scripts, any instances that should be created has to be created by the application.
I've added the engine property asEP_DISALLOW_EMPTY_LIST_ELEMENTS in revision 2050. Set it to true to tell the compiler to give an error on empty elements (but ignore the last comma in lists with variable length).
If your declaration is "Schedule @slIdleStand", i.e. you want the script to be able to reassign the handle to point to a different object, then the second argument must be a pointer to a pointer to a Schedule object, e.g.
static Schedule *g_slIdleStandHandle = &slidleStand; // Declared globally so the address of the variable is valid while the engine is valid
pEngine->RegisterGlobalProperty( "Schedule@ slIdleStand", &g_slIdleStandHandle );
Unfortunately there is no way for AngelScript to see the difference between one pointer to another, otherwise I would make AngelScript return an error if the property is registered incorrectly.
I'll look into this. Perhaps only warning about empty sections if all are empty might be better as you suggested.
The warning was added because there were a couple of developers that accidentally tried to compile empty sections and obviously the result wasn't what they expected. So rather than giving an error when the application couldn't find the expected function or class, which may not be too clear about why the function or class is missing, I felt it better to give a warning when the section was empty.