src/Network/Buffer.hh
changeset 399 c7295b72731a
parent 380 d193dd1d8a7e
child 400 d64bf28c4340
--- a/src/Network/Buffer.hh	Fri Jan 16 01:05:34 2009 +0200
+++ b/src/Network/Buffer.hh	Fri Jan 16 21:24:45 2009 +0200
@@ -1,6 +1,12 @@
 #ifndef NETWORK_BUFFER_HH
 #define NETWORK_BUFFER_HH
 
+/**
+ * @file
+ *
+ * Buffering of network streams
+ */
+
 #include "Socket.hh"
 #include "../Error.hh"
 
@@ -20,7 +26,12 @@
 };
 
 /**
- * Base buffer-manipulation operations for buffered socket send/recv
+ * Base buffer-manipulation operations for buffered socket send/recv.
+ *
+ * This implements a simple linear in-memory buffer which can contain data that must be buffered for send/recv.
+ * New data can be written to the end of the buffer, and data can be read/discarded from the beginning.
+ *
+ * A buffer is associated with a NetworkSocket, and takes care of all send/recv activity.
  */
 class NetworkBufferBase {
     protected:
@@ -30,7 +41,7 @@
         /** The buffer itself */
         char *buf;
 
-        /** Buffer size and current read/write offset */
+        /** Buffer size and current write offset */
         size_t size, offset;
     
     public:
@@ -54,7 +65,11 @@
     protected:
         /**
          * Resize the buffer, allocating enough new space to hold <item_size> bytes at the end, and leaving any
-         * existing data at the beginning in-place
+         * existing data at the beginning in-place.
+         *
+         * This is done by doubling the buffer size until item_size fits, and then reallocing (if needed).
+         * Currently, the buffer is never shrunk, although it probably should be to adapt better to peak load (e.g.
+         * when a player joins and the map data is transmitted...).
          *
          * @param item_size the number of bytes that must fit at the end of the buffer
          */
@@ -63,6 +78,9 @@
         /**
          * Trim the buffer, discarding <prefix_size> bytes at the beginning. Updates offset to match.
          *
+         * Currently, this is implemented as a simple memmove() of all the remaining data in the buffer, which may be
+         * slow under some load patterns.
+         *
          * @param prefix_size the number of bytes to discard
          */
         void trim (size_t prefix_size);
@@ -70,6 +88,10 @@
 
 /**
  * Buffered prefix-len socket input
+ *
+ * This handles calling recv() on the socket, and is specialized to handle a stream of messages prefixed with a length
+ * header (uint16_t or uint32_t). Use the peek_data() method to receive these messages when NetworkSocket::sig_read() 
+ * indicates that more data is available.
  */
 class NetworkBufferInput : public NetworkBufferBase {
     public:
@@ -110,77 +132,46 @@
         // @} 
         
         /**
-         * This attempts to read a length-prefix of the given type (using peek_prefix), and then the associated data.
-         * If succesful, this sets prefix to the length of the data, and buf_ref to point at the data inside our buffer
-         * and returns true, else false.
+         * This attempts to locate a full message in the buffer (calling recv as nessecary), prefixed with a length
+         * prefix of the given type (using peek_prefix). If succesfull (we have a full message in the buffer), 
+         * \a prefix will be updated to the length of the message (not including the prefix), and \a buf_ref will be
+         * updated to point at the message of \a prefix bytes (not including the prefix) in our internal buffer memory.
+         * If a full message could not be received (recv would block), this will return false.
          *
-         * This will try and consume data from the buffer, or recv if needed.
+         * Once you have processed the message, call flush_data() to remove the unused message from the buffer.
          *
-         * @param prefix stores the data length here
-         * @param buf_ref stores a pointer to the data here
-         * @return bool true if we have the full data, false if we need to wait for more data on the socket
+         * @param prefix updated to the message length
+         * @param buf_ref updated to point at the message data
+         * @return bool true if we have a full message, false if we need to wait for more data on the socket
          *
          * @see peek_prefix
          * @see flush_data
          */
-        template <typename PrefixType> bool peek_data (PrefixType &prefix, char *&buf_ref) {
-            size_t missing = 0;
-            
-            do {    
-                // do we have the prefix?
-                if (peek_prefix(prefix)) {
-                    // do we already have the payload?
-                    if (offset >= sizeof(PrefixType) + prefix) {
-                        break;
-
-                    } else {
-                        missing = (sizeof(PrefixType) + prefix) - offset;
-                    }
-
-                } else {
-                    missing = sizeof(PrefixType);
-                }
-
-                // sanity-check
-                // XXX: a zero-prefix will trigger this
-                assert(missing);
-                
-                // try and read the missing data
-                if (try_read(missing) == false) {
-                    // if unable to read what we need, return zero.
-                    return false;
-                }
-                
-                // assess the situation again
-            } while (true);
-            
-            // update the buf_ref to point past the prefix-length
-            buf_ref = buf + sizeof(PrefixType);
-
-            // return
-            return true;
-        }
+        template <typename PrefixType> bool peek_data (PrefixType &prefix, char *&buf_ref);
           
         /**
-         * This flushes a prefix-length worth of data from the buffer, i.e. it first reads the prefix, and then trims
-         * the prefix and the data away. Don't call this unless you *know* that the buffer contains enough data.
+         * This flushes the current message from the buffer, as returned when peek_data returns true. It is a bug to
+         * call flush_data when there is no full message present, the behaviour is unspecified (most likely an
+         * assert()).
          *
          * @see peek_data
          */
-        template <typename PrefixType> void flush_data (void) {
-            PrefixType prefix;
-            
-            // we *must* have a valid prefix
-            if (!peek_prefix(prefix))
-                assert(false);
-
-            // trim the bytes out
-            trim(sizeof(PrefixType) + prefix);
-        }
+        template <typename PrefixType> void flush_data (void);
 };
 
 /**
- * Buffered prefix-len socket output
+ * Buffered prefix-len socket output.
+ *
+ * This handles calling send() on the socket, and is specialized to handle a stream of message prefixed with a length
+ * header (uint16_t or uint32_t). You can write messages using write_prefix(), and they will be buffered if needed.
+ * If write_prefix() returns true (socket buffer full), then you must register the socket for
+ * NetworkSocket::set_poll_write(), and call flush_write() once NetworkSocket::sig_write() is triggered. The socket's
+ * poll_write should be unregistered once flush_write returns false (no more buffered data remaining to be sent), or
+ * an many processor cycles will be wasted, for the socket will remain ready for write until its buffer fills up again.
+ *
+ * Data is not buffered needlessly; if the socket's buffer has room, write_prefix will not have to touch our internal
+ * buffer. In fact, write_prefix will rarely return false except under heavy network congestion with high levels of
+ * traffic on the socket.
  */
 class NetworkBufferOutput : public NetworkBufferBase {
     public:
@@ -191,7 +182,7 @@
 
     private:
         /**
-         * Write the given data to the socket, either now of later. 
+         * Write the given data to the socket, either now or later. 
          *
          * If our buffer is empty, fast-path the given buf_ptr directly to send(), then copy the remaining portion to
          * our buffer for later use with flush_write.
@@ -203,15 +194,22 @@
    
     public:    
         /**
-         * If we have data in our buffer, flush it out using send().
+         * If we have data in our buffer, flush it out using send(). This should be called once the socket indicates it
+         * is ready for write again after a call to write_prefix that returned false. Once this returns false, the
+         * NetworkSocket::set_poll_write() flag should be unset again to avoid needless calls to this.
          *
-         * @return true if there's still buffered data left to write, false otherwise
+         * @return true if there's still buffered data left to write, false otherwise (buffer is empty)
          */
         bool flush_write (void);
         
         // @{
         /**
-         * Write out the given data, writing first the prefix, and then the data itself, using push_write.
+         * Write out the given message, writing first the prefix, and then the data itself, using push_write.
+         *
+         * Returns true if the data was passed on to the socket API directly, false if a portion of it had to be
+         * buffered (if there is already data buffered, all subsequent write_prefix's will buffer the full message
+         * until our buffer is empty again). If this method returns false, you must register the socket for
+         * poll_write and call flush_write later.
          * 
          * @param buf the data to write
          * @param prefix the amount of data
@@ -222,4 +220,59 @@
         // @}
 };
 
+
+/*
+ * NetworkBufferInput template method implementation
+ */
+template <typename PrefixType> bool NetworkBufferOutput::peek_data (PrefixType &prefix, char *&buf_ref) {
+    size_t missing = 0;
+    
+    do {    
+        // do we have the prefix?
+        if (peek_prefix(prefix)) {
+            // do we already have the payload?
+            if (offset >= sizeof(PrefixType) + prefix) {
+                break;
+
+            } else {
+                missing = (sizeof(PrefixType) + prefix) - offset;
+            }
+
+        } else {
+            missing = sizeof(PrefixType);
+        }
+
+        // sanity-check (above >= and - should never happen like this)
+        assert(missing);
+        
+        // try and read the missing data
+        if (try_read(missing) == false) {
+            // if unable to read what we need, return zero.
+            return false;
+        }
+        
+        // assess the situation again
+    } while (true);
+    
+    // update the buf_ref to point past the prefix-length
+    buf_ref = buf + sizeof(PrefixType);
+
+    // return message
+    return true;
+}
+
+template <typename PrefixType> void NetworkBufferOutput::flush_data (void) {
+    PrefixType prefix;
+    
+    // we *must* have a valid prefix
+    if (!peek_prefix(prefix))
+        assert(false);
+
+    // ensure that we have the data to trim...
+    assert(offset >= sizeof(PrefixType) + prefix);
+
+    // trim the bytes out
+    trim(sizeof(PrefixType) + prefix);
+}
+
 #endif