Advertisement

Inheriting from application registered class

Started by April 22, 2024 10:26 AM
12 comments, last by WitchLord 6 months, 1 week ago

Hello!

I'm developing feature to inherit from C++ registered classes by tutorial described at https://www.angelcode.com/angelscript/sdk/docs/manual/doc_adv_inheritappclass.html

My example script:

FooScripted s;
s.CallMe();

Started debugging I've encountered strange behavior: execution of FooScripted::CallMe() is looping for 100 times after ctx->Execute() due to m_isDead->Get() returning false:

  void CallMe()
  {
    // If the script side is still alive, then call the scripted function
    if( !m_isDead->Get() )
    {
      asIScriptEngine *engine = m_obj->GetEngine();
      asIScriptContext *ctx = engine->RequestContext();
 
      // GetMethodByDecl returns the virtual function on the script class
      // thus when calling it, the VM will execute the derived method
      ctx->Prepare(m_obj->GetObjectType()->GetMethodByDecl("void CallMe()"));
      ctx->SetObject(m_obj);
      ctx->Execute();
 
      engine->ReturnContext(ctx);
    }
  }

Let the power of C++ be with you.

I agree that looping a 100 times is indeed strange, but I would need to see more of your implementation if you want me to help on something.

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

Advertisement

Thank you for answer. While I'm building minimal example for reproduction, what's the reason may set m_isDead→Get() to true?

Let the power of C++ be with you.

Here is my example:

class FooScripted
{
public:
   // Public interface that we want the script to be able to override  
   void CallMe()
   {
       // If the script side is still alive, then call the scripted function
       if (!m_isDead->Get())
       {
           asIScriptEngine* engine = m_obj->GetEngine();
           asIScriptContext* ctx = engine->RequestContext();

           // GetMethodByDecl returns the virtual function on the script class
           // thus when calling it, the VM will execute the derived method
           ctx->Prepare(m_obj->GetObjectType()->GetMethodByDecl("void CallMe()"));
           ctx->SetObject(m_obj);
           ctx->Execute();

           engine->ReturnContext(ctx);
       }
   }

   int m_value;

   // A factory function that can be used by the script side to create
   static FooScripted* Factory()
   {
       asIScriptContext* ctx = asGetActiveContext();

       // Get the function that is calling the factory so we can be certain it is the FooScript script class
       asIScriptFunction* func = ctx->GetFunction(0);
       if (func->GetObjectType() == 0 || std::string(func->GetObjectType()->GetName()) != "FooScripted")
       {
           ctx->SetException("Invalid attempt to manually instantiate FooScript_t");
           return 0;
       }

       // Get the this pointer from the calling function so the FooScript C++
       // class can be linked with the FooScript script class
       asIScriptObject* obj = reinterpret_cast<asIScriptObject*>(ctx->GetThisPointer(0));

       return new FooScripted(obj);
   }

   // Reference counting
   void AddRef()
   {
       m_refCount++;

       // Increment also the reference counter to the script side so
       // it isn't accidentally destroyed before the C++ side
       if (!m_isDead->Get())
           m_obj->AddRef();
   }
   void Release()
   {
       // Release the script instance too
       if (!m_isDead->Get())
           m_obj->Release();

       if (--m_refCount == 0) delete this;
   }

   // Assignment operator
   FooScripted& operator=(const FooScripted& o)
   {
       // Copy only the content, not the script proxy class
       m_value = o.m_value;
       return *this;
   }

protected:
   // The constructor and destructor are indirectly called
   FooScripted(asIScriptObject* obj) : m_obj(0), m_isDead(0), m_value(0), m_refCount(1)
   {
       // Get the weak ref flag for the script object to
       // avoid holding a strong reference to the script class
       m_isDead = obj->GetWeakRefFlag();
       m_isDead->AddRef();

       m_obj = obj;
   }

   ~FooScripted()
   {
       // Release the weak ref flag
       m_isDead->Release();
   }

   // Reference count
   int m_refCount;

   // The C++ side holds a weak link to the script side to
   // avoid a circular reference between the C++ side and
   // script side
   asILockableSharedBool* m_isDead;
   asIScriptObject* m_obj;
};

void RegisterFooScripted(asIScriptEngine* engine)
{
   engine->RegisterObjectType("FooScripted_t", 0, asOBJ_REF);
   engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_FACTORY, "FooScripted_t @f()", asFUNCTION(FooScripted::Factory), asCALL_CDECL);
   engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_ADDREF, "void f()", asMETHOD(FooScripted, AddRef), asCALL_THISCALL);
   engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_RELEASE, "void f()", asMETHOD(FooScripted, Release), asCALL_THISCALL);
   engine->RegisterObjectMethod("FooScripted_t", "FooScripted_t &opAssign(const FooScripted_t &in)", asMETHOD(FooScripted, operator=), asCALL_THISCALL);
   engine->RegisterObjectMethod("FooScripted_t", "void CallMe()", asMETHOD(FooScripted, CallMe), asCALL_THISCALL);
   engine->RegisterObjectProperty("FooScripted_t", "int m_value", asOFFSET(FooScripted, m_value));
}

void MessageCallback(const asSMessageInfo* msg, void* param)
{
   stringstream ss;
   ss << msg->section << " (" << msg->row << ", " << msg->col << ") :" << msg->message << ". ";
   cout << ss.str() << endl;
}

bool DoTestAS001()
{
   asIScriptEngine* pEngine = nullptr;
   asIScriptModule* pModule = nullptr;
   asIScriptContext* pContext = nullptr;
   asIScriptFunction* pScriptFunction = nullptr;

   pEngine = asCreateScriptEngine();

   int res = pEngine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL);

   pModule = pEngine->GetModule("module", asGM_CREATE_IF_NOT_EXISTS);

   string strScript;
   std::ifstream ifs("tests/test-as001.as");

   if (ifs.is_open() == true)
   {
       std::string strLine;
       while (std::getline(ifs, strLine))
       {
           strScript += (strLine + "\r\n");
       }
       ifs.close();
   }
   else // Error opening file
   {
   }

   RegisterFooScripted(pEngine);

   res = pModule->AddScriptSection("script", strScript.c_str(), strScript.length());

   if (res < 0)
   {
       cout << "AddScriptSection() failed" << endl;
   }

   res = pModule->Build();

   if (res < 0)
   {
       cout << "Build() failed" << endl;
   }

   pContext = pEngine->CreateContext();

   if (pContext != nullptr)
   {
       if (pScriptFunction == nullptr)
       {
           pScriptFunction = pModule->GetFunctionByDecl("void ExecuteDerived()");
       }

       res = pContext->Prepare(pScriptFunction);

       if (res < 0)
       {
       }

       res = pContext->Execute();

       if (res != asEXECUTION_FINISHED)
       {
           // The execution didn't finish as we had planned. Determine why.
           if (res == asEXECUTION_ABORTED)
           {
           }
           else if (res == asEXECUTION_EXCEPTION)
           {
               string strError;

               // Write some information about the script exception
               asIScriptFunction* func = pContext->GetExceptionFunction();

               strError += util::string_format("fn: %s, ", func->GetDeclaration());
               strError += util::string_format("line: %i, ", pContext->GetExceptionLineNumber());
               strError += util::string_format("reason: %s.", pContext->GetExceptionString());

               cout << strError << endl;
           }
           else
           {
           }
       }
       else
           // Ok
       {
           cout << "Executed successfully" << endl;
       }
   }

   return bReturn;
}

And script "tests/test-as001.as":

  // On the script side
 shared /*abstract*/ class FooScripted
 {
   // Allow scripts to create instances
   FooScripted()
   {
     // Create the C++ side of the proxy
     @m_obj = FooScripted_t();  
   }

   // The copy constructor performs a deep copy
   FooScripted(const FooScripted &o)
   {
     // Create a new C++ instance and copy content
     @m_obj = FooScripted_t();
     m_obj = o.m_obj;
   }

   // Do a deep a copy of the C++ object
   FooScripted &opAssign(const FooScripted &o)
   {
     // copy content of C++ instance
     m_obj = o.m_obj;
     return this;
   }

   // The script side forwards the call to the C++ side
   void CallMe() { m_obj.CallMe(); }

   // The C++ side property is exposed to the script through accessors
   int m_value  
   {
     get { return m_obj.m_value; }
     set { m_obj.m_value = value; }
   }

   // The script class can be implicitly cast to the C++ type through the opImplCast method
   FooScripted_t @opImplCast() { return m_obj; }

   // Hold a reference to the C++ side of the proxy
   private FooScripted_t @m_obj;
 }

 // Implement a script class that derives from the application class
 class FooDerived : FooScripted
 {
   void CallMe()
   {
      m_value += 1;
   }
 }

 void ExecuteScripted()
 {
   FooScripted s;
   s.CallMe();
 }

 void ExecuteDerived()
 {
   // When newly created the m_value is 0
   FooDerived d;

   // When calling the method the m_value is incremented with 1
   d.CallMe();
 }

The results are:

Calling void ExecuteScripted() loops inside of void FooScripted::CallMe() at C++ side for more than 100 times, I didnt counted exactly.

Calling void ExecuteDerived() not enters void FooScripted::CallMe() at C++ side.

This was tested on version 2.36.1.

Thanks for answer in advance.

Let the power of C++ be with you.

C++ FooScripted::CallMe is forwarding the call to the script FooScripted::CallMe which is in turn forwarding the call back to the C++ FooScripted::CallMe 🙂

You're having an infinite recursive loop.

m_isDead is used to hold a weak ref to the script class, so you don't have a hard circular reference that wouldn't allow the pair of objects to be freed when they are no longer needed. When m_isDead is true it means the script class has already been destroyed and freed, thus the C++ class should no longer forward any calls to 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

WitchLord said:

C++ FooScripted::CallMe is forwarding the call to the script FooScripted::CallMe which is in turn forwarding the call back to the C++ FooScripted::CallMe 🙂

You're having an infinite recursive loop.

I don't think this behavior was intended when the guide posted at https://www.angelcode.com/angelscript/sdk/docs/manual/doc_adv_inheritappclass.html​ was made.

My example for 99.9% based on that guide.

By the way, there is not infinite loop, looping breaks at 100th or near iteration.

Let the power of C++ be with you.

Advertisement

Indeed, I'll update the sample. It was just to give an idea on how it can be done, but I'll update it to avoid misunderstanding.

Yes, it won't loop infinitely, the program will run out of stack memory before that.

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

Excuse me for possibly dumb questions, but I'm not so familiar with internal architecture of Angelscript, and so now I can rely only on guides.

WitchLord said:
Yes, it won't loop infinitely, the program will run out of stack memory before that.

Really there is no stack overflow. Looping breaks after dozens of cycles and execution finishes sucessfully. Try my example.

Let the power of C++ be with you.

Well, if it is not stack overflow then it can only be running out of memory.

There are no other loops in the code, so these are the only possible explanations for the code not running forever.

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 would like to know when the sample will be updated?

Let the power of C++ be with you.

This topic is closed to new replies.

Advertisement