Jump to content
  • Advertisement
Sign in to follow this  
Suudy

IO Streams

This topic is 4847 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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).




Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
:)

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.

Share this post


Link to post
Share on other sites
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>
static
asCOScriptStream* 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: 0x4b4570
Stream created. Pointer: 0x3da3d0
asCOScriptStream::asCOScriptStream(const std::string&):
this: 0x3de768
this->_stream: 0x3dac80
asCOScriptStream::asCOScriptStream():
this: 0x3de568
this->_stream: 0x4b4570
asCOScriptStream::operator=:
this: 0x3de568
this->_stream: 0x4b4570
other: 0x3de768
other._stream: 0x3dac80
asCOScriptStream::write:
this: 0x3de568
this->_stream: 0x3dac80
asCOScriptStream::~asCOScriptStream
this: 0x3de568
asCOScriptStream::asCOScriptStream():
this: 0x3de568
this->_stream: 0x4b4570
asCOScriptStream::operator=:
this: 0x3de568
this->_stream: 0x4b4570
other: 0x3de568
other._stream: 0x4b4570
asCOScriptStream::write:
this: 0x3de568
this->_stream: 0x4b4570
asCOScriptStream::~asCOScriptStream
this: 0x3de568
asCOScriptStream::asCOScriptStream():
this: 0x3de568
this->_stream: 0x4b4570
asCOScriptStream::operator=:
this: 0x3de568
this->_stream: 0x4b4570
other: 0x3de568
other._stream: 0x4b4570
asCOScriptStream::write:
this: 0x3de568
this->_stream: 0x4b4570
asCOScriptStream::~asCOScriptStream
this: 0x3de568
asCOScriptStream::~asCOScriptStream
this: 0x3de768
Destructor 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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!