IO Streams

Started by
9 comments, last by WitchLord 18 years, 8 months ago
I'm using the IO streams class addon submitted by Sam Cragg. But I have a few questions on the operation of the scripting engine. First, I've modified the code to allow output to a file, or to std::cout when no file is specified. I also had to make some other modifications to get around the MiniGW issues with the offsetof() macros on non-POD types. But essentially it is the same. So, I have the following script:

void main()
{
  ofstream test;

  test << "This is a test:  " << 55 << std.endl;
}
I put in a debug statement into each constructor of the ofstream to print when it is called. The constructors are simple:

asCOScriptStream::asCOScriptStream()
{
  std::cout << "Creating default ostream" << std::endl;
  _stream = &std::cout;
}

asCOScriptStream::asCOScriptStream(const std::string& fn)
{
  std::cout << "Creating ostream (filename:  " << fn << ")" << std::endl;
  _stream = new std::ofstream(fn.c_str());
}
When run, I get the following output: Creating default ostream Creating default ostream This is a test: Destroying ostream Creating default ostream 55Destroying ostream Creating default ostream Destroying ostream Destroying ostream I understand where the first default ostream comes from. The statement "ofstream test;" creates a default ostream. What gets me is the 3 other calls. I think they are temporaries from the insert operators being called. So, here's the problem. I wanted to derive a class from the ofstream class that dumps the insert operator output to the console as well as to a file. The idea is that it creates visible and file log output. I did the following: I registered a new global property of type ofstream pointing to an instance of the newly derived class, i.e.:

DerivedCOScriptStream* ptr = new DerivedCOScriptStream("test.log");
engine->RegisterGlobalProperty("ofstream cout", ptr);
I know that Angelscript will be calling member functions via a pointer to the base type, COScriptStream (via a call to a template inserter function), so all the write functions (and the destructor) are virtual. However, the creation of temporaries messes things up (of course). Is there a way to do what I'm trying? Or am I going to have to register a completely new class, and all the insertion operators?
Advertisement
How is the << operator registered?

Is the temporary objects really a problem? When the temporary object is created it uses the assignment operator to initialize it so that the temporary and original object ought to point to the same file.

Sometimes AngelScript makes copies of references to guarantee their life time. Some of these copies may be unnecessary, but AS has to look at the worst case. In a future version I may make AngelScript increase the reference to the object instead of making a copy (assuming the object supports object handles).




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

My favorite feature of Angelscript is the incredible support. That a question is answered so quickly, and with useful detail, is spectacular.
Quote:Original post by WitchLord
How is the << operator registered?

The << operator goes through a template function that takes a pointer to the base object, i.e.:
template<class T>asCOScriptStream* InsertOperator(asCOScriptStream* obj, T val){  obj->write(val);  return obj;}

And the write member function is written as:
class asCOScriptStream{  public:    template<class T>    void write(T val)    {      *_stream << val;    }  private:    std::ostream* _stream;};

(What isn't shown is the constructor, etc).
Quote:Original post by WitchLord
Is the temporary objects really a problem? When the temporary object is created it uses the assignment operator to initialize it so that the temporary and original object ought to point to the same file.

Ah..so an assignment operator must exist? I'll try that.

:)

Yes, you must register an assignment operator, otherwise AngelScript will just do a bitwise copy of the object, which in most cases is not a very good thing to do.

I'm thinking about how I can improve this, because too many forget to register these behaviours. I think I'll make them obligatory, and instead allow the application to register the behaviours with a default parameter if they really aren't necessary. This way the application writer will receive an error if he doesn't actively tell the library what to do. It will be better to receive an error like this, than having to track down bugs later on because resources are released multiple times.

Since you may have a lot of temporary objects created, you should try to make the default constructor and assignment operator as light as possible.

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

Ok, I'm getting frustrated, and things aren't working. And it is unclear to me why. I'm struggling with this, and the calls aren't making sense to me.

Using a basic I/O class (based on the version from Sam Cragg), I created the following class:
class asCOScriptStream{  public:    asCOScriptStream();    asCOScriptStream(const std::string&);    ~asCOScriptStream();    asCOScriptStream& operator=(const asCOScriptStream&);    template<class T>    void write(T);    void open(const std::string&);    void close(void);    bool good(void);    static bool RegisterCOScriptStream(asIScriptEngine*);      private:    bool isFile;    std::ostream * _stream;};

I register the constructors, destructor, and operators with:
bool asCOScriptStream::RegisterCOScriptStream(asIScriptEngine* engine){  int r = 0;  // Register the ofstream type  r = (r < 0) ? r : engine->RegisterObjectType("ofstream", sizeof(asCOScriptStream), asOBJ_CLASS_CDA);  // Register the constructors  r = (r < 0) ? r : engine->RegisterObjectBehaviour("ofstream", asBEHAVE_CONSTRUCT, "void f()", asFUNCTIONPR(ConstructOstream, (asCOScriptStream*), void), asCALL_CDECL_OBJFIRST);  r = (r < 0) ? r : engine->RegisterObjectBehaviour("ofstream", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTIONPR(ConstructOstream, (asCOScriptStream*, const std::string&), void), asCALL_CDECL_OBJFIRST);  // Register the destructor  r = (r < 0) ? r : engine->RegisterObjectBehaviour("ofstream", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructOstream), asCALL_CDECL_OBJFIRST);  // Register the assignment operator  r = (r < 0) ? r : engine->RegisterObjectBehaviour("ofstream", asBEHAVE_ASSIGNMENT, "ofstream &f(const ofstream &in)", asMETHODPR(asCOScriptStream, operator =, (const asCOScriptStream&), asCOScriptStream&), asCALL_THISCALL);  // Register the manipulators  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, ios_manip)", asFUNCTIONPR(InsertOperator<asCScriptStreamManip::ios_manip>, (asCOScriptStream*, asCScriptStreamManip::ios_manip), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, ostream_manip)", asFUNCTIONPR(InsertOperator<asCScriptStreamManip::ostream_manip>, (asCOScriptStream*, asCScriptStreamManip::ostream_manip), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, set_manip)", asFUNCTIONPR(InsertOperator<asCScriptStreamManip::set_manip>, (asCOScriptStream*, asCScriptStreamManip::set_manip), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, set_fill)", asFUNCTIONPR(InsertOperator<asCScriptStreamManip::set_fill>, (asCOScriptStream*, asCScriptStreamManip::set_fill), asCOScriptStream*), asCALL_CDECL);  // Register the inserters  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, bool)", asFUNCTIONPR(InsertOperator<bool>, (asCOScriptStream*, bool), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, uint)", asFUNCTIONPR(InsertOperator<unsigned long>, (asCOScriptStream*, unsigned long), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, uint16)", asFUNCTIONPR(InsertOperator<unsigned short>, (asCOScriptStream*, unsigned short), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, uint8)", asFUNCTIONPR(InsertOperator<unsigned char>, (asCOScriptStream*, unsigned char), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, int)", asFUNCTIONPR(InsertOperator<long>, (asCOScriptStream*, long), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, int16)", asFUNCTIONPR(InsertOperator<short>, (asCOScriptStream*, short), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, int8)", asFUNCTIONPR(InsertOperator<char>, (asCOScriptStream*, char), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, bits)", asFUNCTIONPR(InsertOperator<unsigned long>, (asCOScriptStream*, unsigned long), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, bits16)", asFUNCTIONPR(InsertOperator<unsigned short>, (asCOScriptStream*, unsigned short), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, bits8)", asFUNCTIONPR(InsertOperator<unsigned char>, (asCOScriptStream*, unsigned char), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, float)", asFUNCTIONPR(InsertOperator<float>, (asCOScriptStream*, float), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, double)", asFUNCTIONPR(InsertOperator<double>, (asCOScriptStream*, double), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, const string &in)", asFUNCTIONPR(InsertOperator<const std::string&>, (asCOScriptStream*, const std::string&), asCOScriptStream*), asCALL_CDECL);  r = (r < 0) ? r : engine->RegisterGlobalBehaviour(asBEHAVE_BIT_SLL, "ofstream & f(ofstream &in, string @+)", asFUNCTIONPR(InsertOperator<const std::string&>, (asCOScriptStream*, const std::string&), asCOScriptStream*), asCALL_CDECL);  if ( r < 0 )    return false;  return true;}

I have a template function for the insertion operator:
template<class T>staticasCOScriptStream* InsertOperator(asCOScriptStream* out, T value){   out->write(value);   return out;}

And I have my constructors, destructor, and assignment operator as:
asCOScriptStream::asCOScriptStream(){  std::cerr << "asCOScriptStream default constructor called" << std::endl;  _stream = &std::cout;  isFile  = false;}asCOScriptStream::asCOScriptStream(const std::string& fn) : isFile(true){  std::cerr << "asCOScriptStream constructor called" << std::endl;  _stream = new std::ofstream(fn.c_str());}asCOScriptStream::~asCOScriptStream(){  std::cerr << "asCOScriptStream destructor called" << std::endl;}asCOScriptStream& asCOScriptStream::operator=(const asCOScriptStream& other){  std::cerr << "asCOScriptStream assignment operator called" << std::endl;  _stream = other._stream;  isFile  = other.isFile;  return *this;}

And a template function to write the value to a stream:
template<class T>void asCOScriptStream::write(T val){  // Ignore writes to closed files  if ( isFile )  {    std::ofstream* tmp = dynamic_cast<std::ofstream*>(_stream);    if ( !tmp->good() )      return;  }   *_stream << val;}

Now, when I execute the following script, only the first argument is output to the file, and the remaining to the console:
void main(){  ofstream test("test.out");  test << "Test" << 0x55 << std.endl;}

I dumped out some debug showing the calls to the constructors, destructor, and assignment operators. It shows also the right hand pointer to _stream for the assignment operator.
Address of std::cout:  0x4b4570Stream created.  Pointer:  0x3da3d0asCOScriptStream::asCOScriptStream(const std::string&):    this:  0x3de768    this->_stream:  0x3dac80asCOScriptStream::asCOScriptStream():    this:  0x3de568    this->_stream:  0x4b4570asCOScriptStream::operator=:    this:  0x3de568    this->_stream:  0x4b4570    other:  0x3de768    other._stream:  0x3dac80asCOScriptStream::write:    this:  0x3de568    this->_stream:  0x3dac80asCOScriptStream::~asCOScriptStream    this:  0x3de568asCOScriptStream::asCOScriptStream():    this:  0x3de568    this->_stream:  0x4b4570asCOScriptStream::operator=:    this:  0x3de568    this->_stream:  0x4b4570    other:  0x3de568    other._stream:  0x4b4570asCOScriptStream::write:    this:  0x3de568    this->_stream:  0x4b4570asCOScriptStream::~asCOScriptStream    this:  0x3de568asCOScriptStream::asCOScriptStream():    this:  0x3de568    this->_stream:  0x4b4570asCOScriptStream::operator=:    this:  0x3de568    this->_stream:  0x4b4570    other:  0x3de568    other._stream:  0x4b4570asCOScriptStream::write:    this:  0x3de568    this->_stream:  0x4b4570asCOScriptStream::~asCOScriptStream    this:  0x3de568asCOScriptStream::~asCOScriptStream    this:  0x3de768Destructor called

Looking at this, I'm confused why the every call to the assignment operator after the first is assigning itself to itself (ala x=x;).

It seems to follow this progression:
1.   Create test.2.   Create a temporary (call it tmp).3.   Assign tmp = test.4.   Call tmp->write().5.   Destroy tmp.6.   Create a temporary (call it tmp2).7.   Assign tmp2 = tmp2.8.   Call tmp2->write().9.   Destroy tmp2.10.  Create a temporary (call it tmp3).11.  Assign tmp3 = tmp3.12.  Call tmp3->write().13.  Destroy tmp3.14.  Destry test.

The problem is on steps 7 and 11. Why is this done?

Have I setup something wrong? Or is the devil in the details, and I'm not getting the details?
It is possible that you've come upon a bug in the library. It would seem that the script compiler is incorrectly reusing temporary objects.

I'll have to take a closer look at this. I'll probably only have the time to do so on monday, though.

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 wrote a small test case for this in order to verify your problem, and it does indeed seem like there is a bug in the library. The output from my test was:

new (30FF30)new (309F20)(309F20) = (30FF30)(309F20) << "Hello "del (309F20)new (309D90)(309D90) = (309F20)(309D90) << "there!"  ... an exception occurred here since the stream isn't properly initialized


What I see here is that the temporary stream object that was created when calling the << operator is deleted immediately upon return. However, the library still tries to assign it to the next temporary object being created, thus leading to an exception.

This is probably what is happening in your case as well, except that it isn't as easy to see, since your memory manager is reusing the recently destroyed object.

This is a serious bug. I hope that I will be able to come up with a fix soon enough, but I cannot promise anything. The short term fix would be to delay the delete until the copy has been made, and the long term fix is to not create the temporary objects to begin with.

It's strange that this bug hasn't been found before.

I'll keep you updated on the progress.

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

I've been able to correct the bug. However, quite a few changes had to be made throughout the compiler, so I'll only be able to give you the fix with a new release.

I'll try to get version 2.3.0b with this correction out as soon as possible. Then I'll also verify if the same problem exists in version 2.1.0c, which is quite probable.

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 just uploaded version 2.3.0b that fixes this bug. Please give it a try and see if everything is working ok.

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

Quote:Original post by WitchLord
I just uploaded version 2.3.0b that fixes this bug. Please give it a try and see if everything is working ok.


It works beautifully. (Getting the boost::shared_ptr to compile using the mingw provided by Modelsim was another challenge... ;-) )

Once again I want to complement you on the outstanding support you provide. For a free-to-use library, it is spectacular. And with half the commercial products I use, I don't get even half the support you've provided.

Bravo!

Suudy

This topic is closed to new replies.

Advertisement