Casting value objects.

Started by
7 comments, last by WitchLord 9 years, 3 months ago

Hi Andreas,

I have a quick question about polymorphic types.

I'm trying to register a series of functions that accepts a const reference to an abstract class and I'd love to find a way to make it work like in c++, being able to implicitly cast a Derived class to a const reference of the base class. Something like this :

class Base { // abstract with no public constructors
};
class DerivedA : public Base {
};
class DerivedB : public Base {
};
class DerivedC : public Base {
};
class DerivedD : public Base {
};
class DerivedE : public Base {
};
void someFunction( const DerivedA &obj );

My objects are registered as Values making it difficult I guess to find a solution to my problem. The first issue of course is that the Base class usually doesn't have a public default constructor, which trigger an error message when trying to register the type. I managed to make it run by registering the base type as POD (this is probably totally wrong). Secondly I added to the Derived classes an opImplConv operator. Everything compiles fine without any error messages, but somehow when converted and then passed to the function the object seems to be empty and usually make the application crash.

I don't know if I've been really clear but if you have any advices other than re-implementing everything as Ref types I'd be really grateful.

Thanks in advance,

Simon.

Advertisement

Alright, so I did some more tests and it seems that I managed to find a solution that works, but I was curious to still hear your opinion;

Would registering the base type as a asOBJ_REF | asOBJ_NOCOUNT with no factory or behaviors be something wrong?

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.

Regards,

Andreas

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

Great, thanks a lot for the explanation!

Last question though, does it means that giving a "const RefBase& opImplConv( )" to a Value type can potentially solve the whole "safe/copy" issue you are explaining above?

opConv and opImplConv are intended to return a new value not a reference to the same value, so you wouldn't be using it for its intended purpose if you do this. (though feel free to experiment)

It wouldn't solve your problem anyway, since the returned reference is to an object of unknown lifetime (at least as far as the compiler knows) and it will still need to make a copy of the object before giving it as a reference to the function.

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

Thanks Andreas.

opConv and opImplConv are intended to return a new value not a reference to the same value, so you wouldn't be using it for its intended purpose if you do this.

I thought opConv and opImplConv were a replacement for the depreciated value cast behavior. If we take the aswrapper test.cpp as an example, how would you replace the conversion code that starts at line 140? Would opConv and opImplConv be the way to go?

(though feel free to experiment)

It wouldn't solve your problem anyway, since the returned reference is to an object of unknown lifetime (at least as far as the compiler knows) and it will still need to make a copy of the object before giving it as a reference to the function.

Ok, I think I'm getting slowly the idea :)

So I did experiment a bit more with this. I tried to register an addRef and release behavior to my base class. Here's what I think is happening when the conversion happen. So basically I pass a Derived object to a function as a &in reference. When this happen and my opImplConv is executed, the addRef of the base class is called and the refcounting starts. Directly after the engine call the release function where I check if the pointer is correct and if the refcount is at zero I try to delete the object. I think at that point that the address is actually pointing to the actual Derived object and not a copy, which would explain why it crash if I delete the object when it's released... So if I'm getting it correctly, in this case it's not exactly a copy of the object but a reference. Is that right?

Would that help if I post some code?

Thanks a lot anyway and sorry for being slow at understanding how this work!

opConv and opImplConv are a direct replacement of the deprecated asBEHAVE_VALUE_CAST and asBEHAVE_IMPLICIT_VALUE_CAST behaviours. The only change is that you now use RegisterObjectMethod instead of RegisterObjectBehaviour.

if (use_generic) {
    r = engine->RegisterObjectMethod("Base", "Derived opConv() const", WRAP_OBJ_FIRST((std::dynamic_pointer_cast<Derived, Base>)), asCALL_GENERIC); assert(r >= 0);
    r = engine->RegisterObjectMethod("Derived", "Base opImplConv() const", WRAP_OBJ_FIRST((std::static_pointer_cast<Base, Derived>)), asCALL_GENERIC); assert(r >= 0);
  } else {
    r = engine->RegisterObjectMethod("Base", "Derived opConv() const", asFUNCTION((std::dynamic_pointer_cast<Derived, Base>)), asCALL_CDECL_OBJFIRST); assert(r >= 0);
    r = engine->RegisterObjectMethod("Derived", "Base opImplConv() const", asFUNCTION((std::static_pointer_cast<Base, Derived>)), asCALL_CDECL_OBJFIRST); assert(r >= 0);
  }

You must not delete value objects (even if you have used opConv to return a reference to the base class), as they may be allocated locally on the stack.

If you can post some code to show what you're doing it would definitely help make sure we have a common understanding. :)

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 here it is!

I have the same issue in several part of my code but this one is a good example because the base class is abstract and for this reason unregistrable as a value (because of not being instanciable).

// Source AddRef and Release Behaviors
std::map<geom::Source*, uint32_t> sSourceRefs;
void sourceAddRef( geom::Source* source )
{
if( !sSourceRefs.count( source ) ){
sSourceRefs[source] = 1;
}
else sSourceRefs[source]++;
}
void sourceRelease( geom::Source* source )
{
if( sSourceRefs.count( source ) ){
sSourceRefs[source]--;
if( sSourceRefs[source] == 0 ){
sSourceRefs.erase( sSourceRefs.find( source ) );
//source->~Source();
//delete source;
}
}
}
// Conv to geom::Source
const geom::Source& sourceConv( const geom::Source& t ){ return t; }
void registerGeom( asIScriptEngine* engine )
{
engine->SetDefaultNamespace( "geom" );
// Source Type (the base class)
engine->RegisterObjectType( "Source", sizeof(geom::Source), asOBJ_REF );
// Source behaviors
engine->RegisterObjectBehaviour( "Source", asBEHAVE_ADDREF, "void f()", asFUNCTION(sourceAddRef), asCALL_CDECL_OBJFIRST );
engine->RegisterObjectBehaviour( "Source", asBEHAVE_RELEASE, "void f()", asFUNCTION(sourceRelease), asCALL_CDECL_OBJFIRST );
// Cube Type (one of the derived classes)
engine->RegisterObjectType( "Cube", sizeof(geom::Source), asOBJ_VALUE | asOBJ_APP_CLASS_CDAK );
// Cube behaviors
Script::registerConstructor<geom::Cube>( engine, "Cube" );
Script::registerDestructor<geom::Cube>( engine, "Cube" );
Script::registerCopyConstructor<geom::Cube>( engine, "Cube" );
Script::registerCopyAssignment<geom::Cube>( engine, "Cube" );
engine->RegisterObjectMethod( "Cube", "const geom::Source& opImplConv() const", asFUNCTION( sourceConv ), asCALL_CDECL_OBJLAST );
engine->SetDefaultNamespace( "" );
}

So basically there is a series of geometric objects like geom::Cube, geom::Sphere, geom::Plane, etc... and all of them inherit from the base class geom::Source. The only reason to expose geom::Source is that a lot of other functions takes a const Source& as inputs. I'd love to be able to rely on some sort of polymorphism and not have to register the different functions several time for each type of geom classes.

1. Do the derived classes, e.g. Cube, really have to be registered as value types? You seem to be needing them to behave more like reference types than value types. What was your reason to chose to register them as value types in the first place?

2. You will need to register the inherited functions/prototypes for all the derived types too. AngelScript doesn't really know that the Cube is derived from Source. The article on class hierarchies in the manual shows how to minimize the work needed to register methods for class hierarchies.

http://www.angelcode.com/angelscript/sdk/docs/manual/doc_adv_class_hierarchy.html

3. I don't recommend mixing value types and reference types like you're doing. While it may work, you are entering uncharted territory, and you'll have to rely in trial-and-error iterations to get it to work as you want. Value types are simply not designed to be used for polymorphing (for the reason mentioned above).

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