Downcast application registered interface.

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

Hello Angelscript users,

I'm trying to downcast an object handle from an application registered interface to a script defined class. I'm wanting to do this as described in the 'Returning script classes' section on this page. But that page doesn't describe what you can/cannot do with the returned handle, and that's where it's going wrong for me.

My final goal is to be able to attach script instances to entities (as behaviours), but then i also want to get those script instances from inside other scripts. So for example i want the scene's main script be able to get a behaviour script that's attached to an entity in that scene. I've added relevant code below, i've provided quite alot to provide the entire context.

The application registers te interface i'm using to pass around the script instance like so:


engine->RegisterInterface( "Script" );

Then the behaviours that are attached to an entity are included by the script builder like so:


int IncludeCallback( const char* include, const char* from, CScriptBuilder* builder, void* userParam )
{
	...(other predefined includes)
	else if( std::string( include ).compare( "EntityBehaviour" ) == 0 )
	{
		static std::string entityBehaviourClass =
		"class EntityBehaviour : Script\n\
		{\n\
			Entity@ entity;\n\
			ComponentLight@ light;\n\
			ComponentModel@ model;\n\
			...(other components)
		}";
		return builder->AddSectionFromMemory( "EntityBehaviour", entityBehaviourClass.c_str() );
	}
	...(includes from files)
}

The final scripted behaviour includes and implements the EntityBehaviour.


#include "EntityBehaviour"
class TestAnimPlayer : EntityBehaviour
...(class implementation)

All of this is working fine by itself. But then when i try to add the functionality of getting the behaviours from an entity something is going wrong. I have added the function that should return the script class. This is registered outside the angelscript package so it's wrapped. The returned void* is actually an asIScriptObject*.


void* GetScriptBehaviour( Entity& entity, const std::string& typeName )
{
...(looks up the behaviour by it's name and returns an asIScriptObject*)
}
scriptManager.RegisterObjectMethod( "Entity", "Script@ GetScriptBehaviour( const string& in )", asFUNCTION( GetScriptBehaviour ), EECore::ScriptManager::CALL_CDECL_OBJFIRST )

And here's the final script where it's going wrong:


Entity@ lb = scene.GetEntity( "LB animated" ); //This handle is valid.
Script@ playerScript = lb.GetScriptBehaviour( "TestAnimPlayer" ); //This handle is valid.
//DebugScriptType( playerScript );
if( playerScript is null )
	Print( "no player script found" );
else
	Print( "Player script was found" ); //This is printed (since the andle is valid)
EntityBehaviour@ behaviour = cast< EntityBehaviour >( playerScript ); //Will be null
if( behaviour is null )
	Print( "cast failed" ); //Prints this
else
	Print( "cast succeeded" );
TestAnimPlayer@ animPlayer = cast< TestAnimPlayer >( playerScript ); //Will be null
if( animPlayer is null )
	Print( "cast failed" ); //Prints this
else
	Print( "cast succeeded" );
//animPlayer.animationClip = "dance";

The page on inheritance describes the '@handle_to_B = cast<B>(handle_to_A);' behaviour, which is what i'm wanting to do. Though i'm not sure, shouldn't it cast to B@?

For debugging purposes i've hadded the DebugScriptType function, if i enable this it calls this method:


void DebugScriptType( asIScriptObject* obj )
{
	obj->Release();
}
engine->RegisterGlobalFunction( "void DebugScriptType( Script@ obj )", asFUNCTION( DebugScriptType ), asCALL_CDECL );

When inspecting the obj using the debugger it's filled in like this:

obj

-objType

-name

-dynamic: TestAnimPlayer

-interfaces

-[0].name

-local: Script

-derived from

-name

-dynamic: EntityBehaviour

-interfaces

-[0].name

-local: Script

This type hierarchy looks correct to me.

I hope somebody can help me out a bit with this, or would it be better to use the script handle add-on?

Thanks in advance.

Advertisement

Everything appears to be correct in how you've set things up, though there is one point that you left out that may be causing trouble:

Is the implementation of GetScriptBehaviour() increasing the ref counter on the returned script class? If it isn't then it is possible that the script object is destroyed too early which may be why the casts return null. Though I would imagine you would eventually experience an application crash if this was the case.

Assuming GetScriptBehaviour() is in fact increasing the ref counter, then the problem may be a bug in AngelScript. Can you set a break point in as_context.cpp on the asBC_Cast bytecode instruction and see why the VM is not able to successfully cast the object handle?

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

The script object returned by GetScriptBehaviour has it's reference increased prior to returning it, so i've been looking into the cast bytecode for the first cast (Script->EntityBehaviour).

It looks like i've got multiple versions of the EntityBehaviour type. the 'to' variable is 0x090d5c58 while the 'objType' derives from 0x04321fe8. The DerivesFrom function checks on pointer so the types indeed dont match.

My gues is that it has something to do with the contexts/modules. Every derivative of an EntityBehaviour uses a different context, and so does the scene main script (the one that's doing the cast). They also all are placed inside their own module.

I probably need to do something with the shared script entities. I dont think i can place all the scripts inside a single module, as the user should be able to add a new script even after the module has been compiled. If i make all the behaviours/derivatives 'shared' this should work right? The 'Shared script entities' page talks about order of module compilation, i gues this is no problem if i just include the files that contain the classes and not use forward declarations?(if they exist in as).

I've tried making the EntityBehaviour and TestAnimPlayer shared, this makes the casts work and i can call a function on the object. Is this a stable solution or am i misusing something? I dont fully understand modules/contexts yet, do i need to solve anything there or is this okay?

Indeed. The solution here is to use shared entities as you already discovered. This is exactly what shared entities are there for.

As all your modules include the same in-memory script code for the EntityBehaviour you just need to declare this EntityBehaviour as 'shared' and the compiler will re-use the first version of the EntityBehaviour compiled in the first module.

Without declaring the the class as 'shared' each module will have its own unique instance of the type even if the originating script code is the same.

Do you really need to share the TestAnimPlayer class across modules? Shouldn't the scene's main script interact with the object through the EntityBehaviour, i.e. the parent class? TestAnimPlayer could still override the behaviour since class methods are all virtual, so the scene's main script doesn't really need to know that the EntityBehaviour really is a TestAnimPlayer instance.

Of course, if you really need to share the TestAnimPlayer too, then that is OK, you just need to understand the drawback. The drawback with shared classes is that they can only use other shared entities, and I really don't think you plan on sharing everything, since then you might as well put everything in a single module. Currently, everything except global variables can be shared.

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

I've been trying to use the sharing but this doesn't work as good as i hoped. Sure enough i can access other classes, but with sharing i cant seem to reload scripts anymore. If i reload the dependant class first (scene main script) i get a message that it uses a type with a different layout than in it's original module. That's the 'order of compilation' thing i gues, so then i've tried recompiling the dependency first (test anim player), but that doesn't work either.

My reloading code works like this:

  1. Build a new module from the new source file with a new module name.
  2. Rename the new module to the old object type's name.
  3. Destroy instances of the old object type.
  4. Release old object type
  5. Start using the new object type from the new module.

There's a few missing steps like old module cleanup and recreating / synchronizing new object instances, but this is how i was supporting rapid prototyping on isolated entity behaviours.

My question now is: Is it actually possible to replace a shared object type?

I've stayed with 'sharing everything' instead of a single module because i dont see a way to add/remove/replace types on the fly when i'm using a single module.

Making all the inter-module communication happen through an abstract class (EntityBehaviour) is suboptimal as i would then have to add annother abstract class for the scene's main script (so that behaviours may talk back as well). I then also would have to create an elaborate messaging system. Since the EntityBehaviour is defined by the engine while all it's concrete implementations are defined by the game. I cant possibly put all the functions/properties of all the derivatives into the main abstract class.

I'd like to keep this messaging system as a last resort because i'd rather make direct function calls to other classes than send it some abstract message.

It is possible to replace shared objects too, however it is considerably more complicated. The problem is that the previous shared code will only be removed once there are no more references to it. References in this case is any code that uses the shared code, or any live objects of the type.

You'll need to discard all modules that may possible have any direct or indirect references to the shared object you wish to recompile, then you need to destroy all objects (after backing up necessary data, of course), by releasing them and running a full cycle of the garbage collector. Unless you have very tight control over what is actually coded in each script you'll likely end up having to recompile everything (much as if you had compiled everything in a single module anyway).

Because of this I really don't recommend using shared objects for implementations that may is expected to be modified at run-time. Minimize the use of shared objects to where it is absolutely necessary.

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