Using streams without inheritance

Started by
17 comments, last by Kylotan 22 years, 3 months ago
I need the formatting of the stringstream in cases like this:
// nicely formatted tablefor (allCharacters)myCharacter << setw(10) << bigInt << setw(5) << littleInt << endl;end for 

Apologies for the pseudocode. I might also find the fill manipulator, the left/right formatting ones, and precision useful.

Storing and transmitting the data is not the problem. For all intents and purposes, it is stored as plain text and sent as plain text. The problem is getting a clean interface to write to a character that allows as much formatting as I would like. Currently I have to do it this way :
myCharacter << ToString(bigInt, 10) << ToString(littleInt, 5) << "\n";

It would be nice to be able to use the standard streams method instead of having to write a load of functions that convert values to strings, especially since all the functionality I need is already there and working.

This isn''t a "I can''t get my code working" problem. This is just a "is there any way I can do it this way" issue. I prefer not to replicate standard library functionality if at all possible, so I thought I would ask to see if anyone had any ideas.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]
Advertisement
Does this help?


ostringstream oss;

for each characer
{
oss.str(""); // reset oss

// do formatting using oss;
char << oss.str();
}

Let me know if this helps:

  // dependenciestemplate <T> class pool;class socket;class socket::multi;using std::stringstream;using std::istream;using std::ostream;using std::setw; socket::multi socketmultibuffer; class isocketstream{  string&      buffer;  stringstream stream; public:   socketstream () : buffer (socketmultibuffer.get_input ()) {}  ~socketstream () { socketmultibuffer.release_input (&buffer); }   operator istream& () { return stream; }}; class osocketstream{  string&      buffer;  stringstream stream; public:   osocketstream () : buffer (socketmultibuffer.get_output ()) {}  ~osocketstream () { socketmultibuffer.release_output (&buffer); }   operator ostream& () { return stream; }}; typedef pool <osocketstream> output_stream_pool;typedef pool <isocketstream>  input_stream_pool; output_stream_pool output_streams (20); input_stream_pool  input_streams (20); template <class T, pool <T>& P > class pooled_object{  T& t; public:   pooled_object () : t (P.create ()) {}  ~pooled_object () { P.destroy (t); }   operator T& () { return t; }}; typedef pooled_object <osocketstream, output_streams> pooled_ostream;typedef pooled_object <isocketstream,  input_streams> pooled_istream; class character{  int big, small; public:  void send    () { serialize (pooled_istream ()); }  void receive () { serialize (pooled_ostream ()); } protected:  virtual void serialize (ostream& out)  {    out << setw(10) << big << setw(5) << small;  }   virtual void serialize (istream& in)  {    in >> big >> small;  }}; class player{  int x, y; protected:  virtual void serialize (ostream& out)  {    character::serialize(out);    out << x << y;  }   virtual void serialize (ostream& in)  {    character::serialize(in);    in >> x >> y;  }};  


The pool template manages a pool of type T and allows you to reserve an element for your own use and release it back to the system when you are done. socket is a class that encapsulates socket operations. socketmultibuffer is an instance of a class that adapts the simple socket class to add support for multiple input/output buffers, then encapsulates the combining of the multiple input/output streams them into a single input/output stream for send/receive using the socket class interface. socketmultibuffer manages multiple buffers, which are exposed by the socketmultibuffer.get_input/get_output and socketmultibuffer.release_input/release_output functions.

The isocketstream and osocketstream abstract socketmulti's support for multiple buffers and provide a standard istream/ostream interface by containing a stringstream object and exposing its istream/ostream interface. The stringstream object gets its buffer from the socketmultibuffer class, and leaves the details of combining the multiple streams in socketmultibuffer's implementation. The isocketstream/osocketstream classes should be separate, because the socketmultibuffer class allows a buffer to be either input or output, but not both.

Creating a pool of socket streams is easy now, just use the typedef and instantiate the pool with the initial capacity. I used two pools, one for input and one for output. To encapsulate the fact that the streams are in a pool, I placed them into a pooled_object template class which simply creates a pool object of type T from pool instance P, and then created typedefs so that the template parameters were hidden behind type names. The pooled_object's destructor takes care of destroying the object.

Retrieving one of these pooled streams is simplicity itself; you just instantiate a pooled_istream or pooled_ostream, which goes through the chain of adaptation code, eventually reserving a buffer, presenting a standard stream interface, etc. The character send/receive functions are used to serialize data across a socket, so they call the overloaded serialize virtual member functions to perform input and output. Remember to call any immediate base classes versions of serialize from within a derived class's serialize functions, and everything should go smoothly.

Since the pooled_istream and pooled_ostream classes provide a conversion operator to std::istream and std::ostream, you can design the serialize functions to use std::istream and std::ostream, while passing a pooled_istream/pooled_ostream object, in which case the conversion allowed by the operator istream&/ostream& will give you the stringstream interface to the multiple i/o socket buffers.

Simple, eh? The serialize member functions are borrowed from MFC's CArchive class, the adaptors from The C++ Programming Language, Special Edition, and the pool data structure from Magmai's post.

EDIT: Changed behavior of pool slightly to contain capacity for streams rather than actual streams, and also changed the pooled_object typedef so that it works.

Edited by - null_pointer on January 15, 2002 9:39:26 AM
Void: Not really. I can do it like that on occasion, but it is precisely that which I am trying to avoid.

null_pointer: uhh... I''m confused I should never have mentioned the sockets, as they are pretty much totally irrelevant to what my problem is. They were just mentioned to justify the fact that I need to buffer the data after formatting it. All the formatting takes place as the data is passed to the Character and is completely done before it is even considered for sending to the socket. Note that this is done for all Characters in the game: all the buffers are filled, then all the buffers are emptied.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]
I implemented a system of shared buffers with a pool of shared stream interfaces to those buffers, and hid all that code into two function calls.

When you call character::send (), here is what happens. First, a temporary pooled_ostream object is constructed, which retrieves a osocketstream from the global pool of i/o streams, which in turn retrieves exclusive access to a buffer from socketmultibuffer. (The purpose of socketmultibuffer is to manage multiple shared buffers and inserts/extracts them into/out of the socket''s single buffer at the proper time. Each time you get an input buffer, its contents are extracted from the socket. Each time you release an output buffer, its contents are inserted into the socket class''s single output buffer.)

After the temporary pooled_istream object is constructed, the compiler tries to convert it to an ostream& (in order to pass it to character::serialize (ostream&)) and finds the conversion path pooled_ostream->osocketstream&->ostream&, via the chain of conversion operators. So in effect, character::serialize gets a temporary, standard output stream interface to one of a pool of output buffers (to which it has exclusive access), and all through one constructor call. Note how elegant this is; no details of the shared buffer/stream implementation leak into the character classes - for all they know they are all writing into the same buffer.

After the call to character::serialize has been completed, the temporary object is destroyed, destroying the temporary output stream interface and releasing the buffer for use by another instance of a character-derived class. The chicanery of socket::multi/Xsocketstream/Xput_streams/pooled_Xstream class really just adds transparent support for a socket class using multiple buffers, and then abstracts the fact that it does. Unfortunately, due to the way this code was written, unless you are using multiple threads to write to the socket there is no use for it; no calls to character::send will simultaneously, and thus only one stream/buffer pair will ever be used. Massive overkill.

Anyway, it does demonstrate how you can abstract the storage of buffers and streams as well as abstracting the relation between a stream buffer and a stream interface, while still maintaining the standard stream interfaces and all the functionality they bring with them. However, it does not really overcome the problem you wanted to fix...


quote:Original post by Kylotan

They were just mentioned to justify the fact that I need to buffer the data after formatting it. All the formatting takes place as the data is passed to the Character and is completely done before it is even considered for sending to the socket. Note that this is done for all Characters in the game: all the buffers are filled, then all the buffers are emptied.


Must all buffers really be in use at the same time, or could you find some scheme for sharing buffers among instances of character?

If in your program each instance of character must have its own buffer, then I am afraid that the ideal solution to your problem does not exist. You must: 1) pay the memory overhead of having a stream for each character, or 2) pay the execution time overhead of constructing a stream for each buffer, or 3) some tradeoff between the two. Magmai''s suggestion of pooled stream interfaces does not actually solve anything if a stream must be attached to a buffer for each character; if you can find some way of sharing the buffers then you may be able to reduce this cost.

The only possibility that I can see for a tradeoff would be if you could process the characters in a different order each time; then you could keep a pool of stringstreams that can remain attached to some character buffers between two calls of the loop code. With this method you could have a tradeoff between the memory overhead and speed overhead by choosing the total number of stringstreams. This is not a very good solution, but it is still an option...I''m getting tired.
Now I''m getting confused.

Either u want

- Each character to be formatted differently. It means each character must store formatting states.

That means each character stores a ostringstream (which u mentioned is unacceptable) or the state to set (via manipulators or something else). But you say you don''t want to set the state of the stream each time for each character object.

As null_pointer pointed - How is that possible?

- Each character is to be formatted according to the current state of the stream passed.

But you might need multithreading, so the global stream has to be locked/unlocked and not acceptable.


You say it''s for a MUD project, why are you sending text for non players anyway (by streaming in your base class)? It''s seems to be more of a design issue really.
quote:Original post by Void
Either u want

- Each character to be formatted differently. It means each character must store formatting states.

This would be nice. Some players might turn off ANSI colour codes, for example (meaning that the user-defined manipulators for each colour would do nothing). Currently the players can store this state ok. It''s things like setting numerical precision that aren''t handled currently, hence my interest in moving to real streams.
quote:
That means each character stores a ostringstream (which u mentioned is unacceptable) or the state to set (via manipulators or something else). But you say you don''t want to set the state of the stream each time for each character object.

I don''t want to do anything when I output data except output data. I don''t want to have to explicitly acquire a singleton instance, I don''t want to have to explicitly create ostringstreams all over the place, and I don''t want to keep having to clear a global variable. If what I want can''t be done without something like that, then it''s not worth doing.
quote:
As null_pointer pointed - How is that possible?

Sometimes you elite coders find ways to do things that I would never have thought of.

quote:Each character is to be formatted according to the current state of the stream passed.

Not exactly: mainly yes, they have to be formatted according to the current formatting specifications, but it would be useful to store per-character state as well (such as which currency symbols to use: done with locales in normal iostreams).

quote:But you might need multithreading, so the global stream has to be locked/unlocked and not acceptable.

The main problem with using a global stream is that I''d have to do some explicit acquiring and releasing, which is a syntactic no-no given how often I''d have to do it.

quote:You say it''s for a MUD project, why are you sending text for non players anyway (by streaming in your base class)? It''s seems to be more of a design issue really.

Non players see output just as players do, and act upon it. Even if they didn''t, it would be a mess to put in "if (person.IsNPC())" checks before every output operation.

I am not claiming that what I want is possible: just saying what I want and wondering how close I can get to it.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]
quote:Original post by Kylotan
I don''t want to do anything when I output data except output data.


I think it''s unavoidable that you have to set the stream state for each player as they may use different formatting states.

quote:
Non players see output just as players do, and act upon it. Even if they didn''t, it would be a mess to put in "if (person.IsNPC())" checks before every output operation.


I haven''t done a MUD before but I would think sending string messages to each game object would be inefficient (?).

In MUD, the only string messages that needs to get formatted are the ones that gets sent to the client machine( each player ). Shouldn''t the server store everything as game state data instead (as per a normal game)

i.e.
Kylotan[Hit point 100/100] : You cause INTERNAL BLEEDING on monster:
Kylotan[Hit point 80/100] : Monster hits you 20 points
Kylotan[Hit point 100/100] : You mutter a healing spell.

The string is only formatted when sending to the player (after retrieving relevant data from the game objects ).

For differentiating PCs and NPCs, perhaps you need to store two lists of objects in the game server, 1 for PC, 1 for NPC. I can imagine situations that you need perform situations for PCs only like broadcasting a player''s message.
quote:Original post by Void
I haven''t done a MUD before but I would think sending string messages to each game object would be inefficient (?).

Of course, doing anything that you don''t have to is inefficient. But nonplayer characters need to be able to react to what they see in the same way that player characters do. Many NPC actions are scripted with the trigger being an output pattern.
quote:
i.e.
Kylotan[Hit point 100/100] : You cause INTERNAL BLEEDING on monster:
Kylotan[Hit point 80/100] : Monster hits you 20 points
Kylotan[Hit point 100/100] : You mutter a healing spell.

The string is only formatted when sending to the player (after retrieving relevant data from the game objects ).

The problem is that there is often more than one person you''re sending it to:
for (all characters in room)    current_character << attacker << " hits " << defender        " for 20 points" << endl;end for 

In that case, I could instead instantiate/clear a stringstream once per loop iteration, but that gets ugly when repeated a thousand times through the code. Which is why, at the moment, I have operations that make writing to a Character look like writing to a stream, but sadly without many of the useful formatting benefits.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost ]

This topic is closed to new replies.

Advertisement