CrystalSpace

Public API Reference

Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

array.h

Go to the documentation of this file.
00001 /*
00002   Crystal Space Generic Array Template
00003   Copyright (C) 2003 by Matze Braun
00004   Copyright (C) 2003 by Jorrit Tyberghein
00005   Copyright (C) 2003,2004 by Eric Sunshine
00006 
00007   This library is free software; you can redistribute it and/or
00008   modify it under the terms of the GNU Library General Public
00009   License as published by the Free Software Foundation; either
00010   version 2 of the License, or (at your option) any later version.
00011 
00012   This library is distributed in the hope that it will be useful,
00013   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015   Library General Public License for more details.
00016 
00017   You should have received a copy of the GNU Library General Public
00018   License along with this library; if not, write to the Free
00019   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00020 */
00021 #ifndef __CSUTIL_ARRAY_H__
00022 #define __CSUTIL_ARRAY_H__
00023 
00028 #include "comparator.h"
00029 
00030 // Hack: Work around problems caused by #defining 'new'.
00031 #if defined(CS_EXTENSIVE_MEMDEBUG) || defined(CS_MEMORY_TRACKER)
00032 # undef new
00033 #endif
00034 #include <new>
00035 
00036 #if defined(CS_MEMORY_TRACKER)
00037 #include "csutil/memdebug.h"
00038 #include "csutil/snprintf.h"
00039 #include <typeinfo>
00040 #endif
00041 
00045 // Define CSARRAY_INHIBIT_TYPED_KEYS if the compiler is too old or too buggy to
00046 // properly support templated functions within a templated class.  When this is
00047 // defined, rather than using a properly typed "key" argument, search methods
00048 // fall back to dealing with opaque void* for the "key" argument.  Note,
00049 // however, that this fact is completely hidden from the client; the client
00050 // simply creates csArrayCmp<> functors using correct types for the keys
00051 // regardless of whether the compiler actually supports this feature.  (The
00052 // MSVC6 compiler, for example, does support templated functions within a
00053 // template class but crashes and burns horribly when a function pointer or
00054 // functor is thrown into the mix; thus this should be defined for MSVC6.)
00055 #if !defined(CSARRAY_INHIBIT_TYPED_KEYS)
00056 
00066 template <class T, class K>
00067 class csArrayCmp
00068 {
00069 public:
00075   typedef int(*CF)(T const&, K const&);
00077   csArrayCmp(K const& k, CF c = DefaultCompare) : key(k), cmp(c) {}
00079   csArrayCmp(csArrayCmp const& o) : key(o.key), cmp(o.cmp) {}
00081   csArrayCmp& operator=(csArrayCmp const& o)
00082     { key = o.key; cmp = o.cmp; return *this; }
00091   int operator()(T const& r) const { return cmp(r, key); }
00093   operator CF() const { return cmp; }
00095   operator K const&() const { return key; }
00106   static int DefaultCompare(T const& r, K const& k)
00107     { return csComparator<T,K>::Compare(r,k); }
00108 private:
00109   K key;
00110   CF cmp;
00111 };
00112 
00113 #define csArrayTemplate(K) template <class K>
00114 #define csArrayCmpDecl(T1,T2) csArrayCmp<T1,T2>
00115 #define csArrayCmpInvoke(C,R) C(R)
00116 
00117 #else // CSARRAY_INHIBIT_TYPED_KEYS
00118 
00119 class csArrayCmpAbstract
00120 {
00121 public:
00122   typedef int(*CF)(void const*, void const*);
00123   virtual int operator()(void const*) const = 0;
00124   virtual operator CF() const = 0;
00125 };
00126 
00127 template <class T, class K>
00128 class csArrayCmp : public csArrayCmpAbstract
00129 {
00130 public:
00131   typedef int(*CFTyped)(T const&, K const&);
00132   csArrayCmp(K const& k, CFTyped c = DefaultCompare) : key(k), cmp(CF(c)) {}
00133   csArrayCmp(csArrayCmp const& o) : key(o.key), cmp(o.cmp) {}
00134   csArrayCmp& operator=(csArrayCmp const& o)
00135     { key = o.key; cmp = o.cmp; return *this; }
00136   virtual int operator()(void const* p) const { return cmp(p, &key); }
00137   virtual operator CF() const { return cmp; }
00138   operator K const&() const { return key; }
00139   static int DefaultCompare(T const& r, K const& k)
00140     { return csComparator<T,K>::Compare(r,k); }
00141 private:
00142   K key;
00143   CF cmp;
00144 };
00145 
00146 #define csArrayTemplate(K)
00147 #define csArrayCmpDecl(T1,T2) csArrayCmpAbstract const&
00148 #define csArrayCmpInvoke(C,R) C(&(R))
00149 
00150 #endif // CSARRAY_INHIBIT_TYPED_KEYS
00151 
00155 template <class T>
00156 class csArrayElementHandler
00157 {
00158 public:
00159   static void Construct (T* address)
00160   {
00161     new (CS_STATIC_CAST(void*,address)) T();
00162   }
00163 
00164   static void Construct (T* address, T const& src)
00165   {
00166     new (CS_STATIC_CAST(void*,address)) T(src);
00167   }
00168 
00169   static void Destroy (T* address)
00170   {
00171     address->~T();
00172   }
00173 
00174   static void InitRegion (T* address, size_t count)
00175   {
00176     for (size_t i = 0 ; i < count ; i++)
00177       Construct (address + i);
00178   }
00179 };
00180 
00184 template <class T>
00185 class csArrayMemoryAllocator
00186 {
00187 public:
00188   static T* Alloc (size_t count)
00189   {
00190     return (T*)malloc (count * sizeof(T));
00191   }
00192 
00193   static void Free (T* mem)
00194   {
00195     free (mem);
00196   }
00197 
00198   // The 'relevantcount' parameter should be the number of items
00199   // in the old array that are initialized.
00200   static T* Realloc (T* mem, size_t relevantcount, size_t oldcount,
00201     size_t newcount)
00202   {
00203     (void)relevantcount; (void)oldcount;
00204     return (T*)realloc (mem, newcount * sizeof(T));
00205   }
00206 
00207   // Move memory.
00208   static void MemMove (T* mem, size_t dest, size_t src, size_t count)
00209   {
00210     memmove (mem + dest, mem + src, count * sizeof(T));
00211   }
00212 };
00213 
00222 template <class T, class ElementHandler = csArrayElementHandler<T> >
00223 class csSafeCopyArrayMemoryAllocator
00224 {
00225 public:
00226   static T* Alloc (size_t count)
00227   {
00228     return (T*)malloc (count * sizeof(T));
00229   }
00230 
00231   static void Free (T* mem)
00232   {
00233     free (mem);
00234   }
00235 
00236   static T* Realloc (T* mem, size_t relevantcount, size_t oldcount,
00237     size_t newcount)
00238   {
00239     if (newcount <= oldcount)
00240     {
00241       // Realloc is safe.
00242       T* newmem = (T*)realloc (mem, newcount * sizeof (T));
00243       CS_ASSERT (newmem == mem);
00244       return newmem;
00245     }
00246 
00247     T* newmem = Alloc (newcount);
00248     size_t i;
00249     for (i = 0 ; i < relevantcount ; i++)
00250     {
00251       ElementHandler::Construct (newmem + i, mem[i]);
00252       ElementHandler::Destroy (mem + i);
00253     }
00254     Free (mem);
00255     return newmem;
00256   }
00257 
00258   static void MemMove (T* mem, size_t dest, size_t src, size_t count)
00259   {
00260     size_t i;
00261     if (dest < src)
00262     {
00263       for (i = 0 ; i < count ; i++)
00264       {
00265         ElementHandler::Construct (mem + dest + i, mem[src + i]);
00266         ElementHandler::Destroy (mem + src + i);
00267       }
00268     }
00269     else
00270     {
00271       i = count;
00272       while (i > 0)
00273       {
00274         i--;
00275         ElementHandler::Construct (mem + dest + i, mem[src + i]);
00276         ElementHandler::Destroy (mem + src + i);
00277       }
00278     }
00279   }
00280 };
00281 
00286 const size_t csArrayItemNotFound = (size_t)-1;
00287 
00296 template <class T,
00297         class ElementHandler = csArrayElementHandler<T>,
00298         class MemoryAllocator = csArrayMemoryAllocator<T> >
00299 class csArray
00300 {
00301 private:
00302   size_t count;
00303   size_t capacity;
00304   size_t threshold;
00305   T* root;
00306 #ifdef CS_MEMORY_TRACKER
00307   csMemTrackerInfo* mti;
00308   void UpdateMti (int dn, int curcapacity)
00309   {
00310     if (!mti)
00311     {
00312       if (!curcapacity) return;
00313       char buf[1024];
00314       cs_snprintf (buf, sizeof (buf), "csArray<%s>", typeid (T).name());
00315       mti = mtiRegisterAlloc (1 * sizeof (T), buf);
00316       if (!mti) return;
00317       curcapacity--;
00318       if (curcapacity)
00319         mtiUpdateAmount (mti, curcapacity, curcapacity * sizeof (T));
00320       return;
00321     }
00322     mtiUpdateAmount (mti, dn, dn * sizeof (T));
00323   }
00324 #endif
00325 
00326 protected:
00331   void InitRegion (size_t start, size_t count)
00332   {
00333     ElementHandler::InitRegion (root+start, count);
00334   }
00335 
00336 private:
00338   void CopyFrom (const csArray& source)
00339   {
00340     if (&source != this)
00341     {
00342       DeleteAll ();
00343       threshold = source.threshold;
00344       SetSizeUnsafe (source.Length ());
00345       for (size_t i=0 ; i<source.Length() ; i++)
00346         ElementHandler::Construct (root + i, source[i]);
00347     }
00348   }
00349 
00351   void InternalSetCapacity (size_t n)
00352   {
00353     if (root == 0)
00354     {
00355       root = MemoryAllocator::Alloc (n);
00356 #ifdef CS_MEMORY_TRACKER
00357       UpdateMti (n, n);
00358 #endif
00359     }
00360     else
00361     {
00362       root = MemoryAllocator::Realloc (root, count, capacity, n);
00363 #ifdef CS_MEMORY_TRACKER
00364       UpdateMti (n-capacity, n);
00365 #endif
00366     }
00367     capacity = n;
00368   }
00369 
00374   void AdjustCapacity (size_t n)
00375   {
00376     if (n > capacity || (capacity > threshold && n < capacity - threshold))
00377     {
00378       InternalSetCapacity (((n + threshold - 1) / threshold ) * threshold);
00379     }
00380   }
00381 
00388   void SetSizeUnsafe (size_t n)
00389   {
00390     if (n > capacity)
00391       AdjustCapacity (n);
00392     count = n;
00393   }
00394 
00395 public:
00407   static int DefaultCompare(T const& r1, T const& r2)
00408   {
00409     return csComparator<T,T>::Compare(r1,r2);
00410   }
00411 
00417   csArray (size_t in_capacity = 0, size_t in_threshold = 0)
00418   {
00419 #ifdef CS_MEMORY_TRACKER
00420     mti = 0;
00421 #endif
00422     count = 0;
00423     capacity = (in_capacity > 0 ? in_capacity : 0);
00424     threshold = (in_threshold > 0 ? in_threshold : 16);
00425     if (capacity != 0)
00426     {
00427       root = MemoryAllocator::Alloc (capacity);
00428 #ifdef CS_MEMORY_TRACKER
00429       UpdateMti (capacity, capacity);
00430 #endif
00431     }
00432     else
00433     {
00434       root = 0;
00435     }
00436   }
00437 
00439   ~csArray ()
00440   {
00441     DeleteAll ();
00442   }
00443 
00445   csArray (const csArray& source)
00446   {
00447 #ifdef CS_MEMORY_TRACKER
00448     mti = 0;
00449 #endif
00450     root = 0;
00451     capacity = 0;
00452     count = 0;
00453     CopyFrom (source);
00454   }
00455 
00457   csArray<T,ElementHandler>& operator= (const csArray& other)
00458   {
00459     CopyFrom (other);
00460     return *this;
00461   }
00462 
00464   size_t GetSize () const
00465   {
00466     return count;
00467   }
00468 
00473   size_t Length () const
00474   {
00475     return GetSize();
00476   }
00477 
00479   size_t Capacity () const
00480   {
00481     return capacity;
00482   }
00483 
00490   void TransferTo (csArray& destination)
00491   {
00492     if (&destination != this)
00493     {
00494       destination.DeleteAll ();
00495       destination.root = root;
00496       destination.count = count;
00497       destination.capacity = capacity;
00498       destination.threshold = threshold;
00499 #ifdef CS_MEMORY_TRACKER
00500       destination.mti = mti;
00501       mti = 0;
00502 #endif
00503       root = 0;
00504       capacity = count = 0;
00505     }
00506   }
00507 
00517   void SetSize (size_t n, T const& what)
00518   {
00519     if (n <= count)
00520     {
00521       Truncate (n);
00522     }
00523     else
00524     {
00525       size_t old_len = Length ();
00526       SetSizeUnsafe (n);
00527       for (size_t i = old_len ; i < n ; i++)
00528         ElementHandler::Construct (root + i, what);
00529     }
00530   }
00531 
00539   void SetSize (size_t n)
00540   {
00541     if (n <= count)
00542     {
00543       Truncate (n);
00544     }
00545     else
00546     {
00547       size_t old_len = Length ();
00548       SetSizeUnsafe (n);
00549       ElementHandler::InitRegion (root + old_len, n-old_len);
00550     }
00551   }
00552 
00558   void SetLength (size_t n, T const& what) { SetSize(n, what); }
00559   void SetLength (size_t n) { SetSize(n); }
00562 
00563   T& Get (size_t n)
00564   {
00565     CS_ASSERT (n < count);
00566     return root[n];
00567   }
00568 
00570   T const& Get (size_t n) const
00571   {
00572     CS_ASSERT (n < count);
00573     return root[n];
00574   }
00575 
00581   T& GetExtend (size_t n)
00582   {
00583     if (n >= count)
00584       SetSize (n+1);
00585     return root[n];
00586   }
00587 
00589   T& operator [] (size_t n)
00590   {
00591     return Get(n);
00592   }
00593 
00595   T const& operator [] (size_t n) const
00596   {
00597     return Get(n);
00598   }
00599 
00601   void Put (size_t n, T const& what)
00602   {
00603     if (n >= count)
00604       SetSize (n+1);
00605     ElementHandler::Destroy (root + n);
00606     ElementHandler::Construct (root + n, what);
00607   }
00608 
00616   csArrayTemplate(K)
00617   size_t FindKey (csArrayCmpDecl(T,K) comparekey) const
00618   {
00619     for (size_t i = 0 ; i < Length () ; i++)
00620       if (csArrayCmpInvoke(comparekey, root[i]) == 0)
00621         return i;
00622     return csArrayItemNotFound;
00623   }
00624 
00629   size_t Push (T const& what)
00630   {
00631     if (((&what >= root) && (&what < root + Length())) &&
00632       (capacity < count + 1))
00633     {
00634       /*
00635         Special case: An element from this very array is pushed, and a
00636         reallocation is needed. This could cause the passed ref to the
00637         element to be pushed to be read from deleted memory. Work
00638         around this.
00639        */
00640       size_t whatIndex = &what - root;
00641       SetSizeUnsafe (count + 1);
00642       ElementHandler::Construct (root + count - 1, root[whatIndex]);
00643     }
00644     else
00645     {
00646       SetSizeUnsafe (count + 1);
00647       ElementHandler::Construct (root + count - 1, what);
00648     }
00649     return count - 1;
00650   }
00651 
00656   size_t PushSmart (T const& what)
00657   {
00658     size_t const n = Find (what);
00659     return (n == csArrayItemNotFound) ? Push (what) : n;
00660   }
00661 
00663   T Pop ()
00664   {
00665     CS_ASSERT (count > 0);
00666     T ret(root [count - 1]);
00667     ElementHandler::Destroy (root + count - 1);
00668     SetSizeUnsafe (count - 1);
00669     return ret;
00670   }
00671 
00673   T const& Top () const
00674   {
00675     CS_ASSERT (count > 0);
00676     return root [count - 1];
00677   }
00678 
00680   T& Top ()
00681   {
00682     CS_ASSERT (count > 0);
00683     return root [count - 1];
00684   }
00685 
00687   bool Insert (size_t n, T const& item)
00688   {
00689     if (n <= count)
00690     {
00691       SetSizeUnsafe (count + 1); // Increments 'count' as a side-effect.
00692       size_t const nmove = (count - n - 1);
00693       if (nmove > 0)
00694         MemoryAllocator::MemMove (root, n+1, n, nmove);
00695       ElementHandler::Construct (root + n, item);
00696       return true;
00697     }
00698     else
00699       return false;
00700   }
00701 
00705   csArray<T> Section (size_t low, size_t high) const
00706   {
00707     CS_ASSERT (high < count && high >= low);
00708     csArray<T> sect (high - low + 1);
00709     for (size_t i = low; i <= high; i++) sect.Push (root[i]);
00710     return sect;
00711   }
00712 
00718   csArrayTemplate(K)
00719   size_t FindSortedKey (csArrayCmpDecl(T,K) comparekey,
00720                         size_t* candidate = 0) const
00721   {
00722     size_t m = 0, l = 0, r = Length ();
00723     while (l < r)
00724     {
00725       m = (l + r) / 2;
00726       int cmp = csArrayCmpInvoke(comparekey, root[m]);
00727       if (cmp == 0)
00728       {
00729         if (candidate) *candidate = csArrayItemNotFound;
00730         return m;
00731       }
00732       else if (cmp < 0)
00733         l = m + 1;
00734       else
00735         r = m;
00736     }
00737     if (candidate) *candidate = m;
00738     return csArrayItemNotFound;
00739   }
00740 
00747   size_t InsertSorted (const T& item,
00748     int (*compare)(T const&, T const&) = DefaultCompare,
00749     size_t* equal_index = 0)
00750   {
00751     size_t m = 0, l = 0, r = Length ();
00752     while (l < r)
00753     {
00754       m = (l + r) / 2;
00755       int cmp = compare (root [m], item);
00756 
00757       if (cmp == 0)
00758       {
00759         if (equal_index) *equal_index = m;
00760         Insert (++m, item);
00761         return m;
00762       }
00763       else if (cmp < 0)
00764         l = m + 1;
00765       else
00766         r = m;
00767     }
00768     if ((m + 1) == r)
00769       m++;
00770     if (equal_index) *equal_index = csArrayItemNotFound;
00771     Insert (m, item);
00772     return m;
00773   }
00774 
00781   size_t Find (T const& which) const
00782   {
00783     for (size_t i = 0 ; i < Length () ; i++)
00784       if (root[i] == which)
00785         return i;
00786     return csArrayItemNotFound;
00787   }
00788 
00790   size_t Contains(T const& which) const
00791   { return Find(which); }
00792 
00799   size_t GetIndex (const T* which) const
00800   {
00801     CS_ASSERT (which >= root);
00802     CS_ASSERT (which < (root + count));
00803     return which-root;
00804   }
00805 
00809   void Sort (int (*compare)(T const&, T const&) = DefaultCompare)
00810   {
00811     qsort (root, Length(), sizeof(T),
00812       (int (*)(void const*, void const*))compare);
00813   }
00814 
00818   void DeleteAll ()
00819   {
00820     if (root)
00821     {
00822       size_t i;
00823       for (i = 0 ; i < count ; i++)
00824         ElementHandler::Destroy (root + i);
00825       MemoryAllocator::Free (root);
00826 #     ifdef CS_MEMORY_TRACKER
00827       UpdateMti (-capacity, 0);
00828 #     endif
00829       root = 0;
00830       capacity = count = 0;
00831     }
00832   }
00833 
00845   void Truncate (size_t n)
00846   {
00847     CS_ASSERT(n <= count);
00848     if (n < count)
00849     {
00850       for (size_t i = n; i < count; i++)
00851         ElementHandler::Destroy (root + i);
00852       SetSizeUnsafe(n);
00853     }
00854   }
00855 
00861   void Empty ()
00862   {
00863     Truncate (0);
00864   }
00865 
00871   bool IsEmpty() const
00872   {
00873     return GetSize() == 0;
00874   }
00875 
00882   void SetCapacity (size_t n)
00883   {
00884     if (n > Length ())
00885       InternalSetCapacity (n);
00886   }
00887 
00893   void ShrinkBestFit ()
00894   {
00895     if (count == 0)
00896     {
00897       DeleteAll ();
00898     }
00899     else if (count != capacity)
00900     {
00901       root = MemoryAllocator::Realloc (root, count, capacity, count);
00902 #ifdef CS_MEMORY_TRACKER
00903       UpdateMti (count-capacity, count);
00904 #endif
00905       capacity = count;
00906     }
00907   }
00908 
00917   bool DeleteIndex (size_t n)
00918   {
00919     if (n < count)
00920     {
00921       size_t const ncount = count - 1;
00922       size_t const nmove = ncount - n;
00923       ElementHandler::Destroy (root + n);
00924       if (nmove > 0)
00925         MemoryAllocator::MemMove (root, n, n+1, nmove);
00926       SetSizeUnsafe (ncount);
00927       return true;
00928     }
00929     else
00930       return false;
00931   }
00932 
00942   bool DeleteIndexFast (size_t n)
00943   {
00944     if (n < count)
00945     {
00946       size_t const ncount = count - 1;
00947       size_t const nmove = ncount - n;
00948       ElementHandler::Destroy (root + n);
00949       if (nmove > 0)
00950         MemoryAllocator::MemMove (root, n, ncount, 1);
00951       SetSizeUnsafe (ncount);
00952       return true;
00953     }
00954     else
00955       return false;
00956   }
00957 
00962   void DeleteRange (size_t start, size_t end)
00963   {
00964     if (start >= count) return;
00965     // Treat 'csArrayItemNotFound' as invalid indices, do nothing.
00966     // @@@ Assert that?
00967     if (end == csArrayItemNotFound) return;
00968     if (start == csArrayItemNotFound) return;//start = 0;
00969     if (end >= count) end = count - 1;
00970     size_t i;
00971     for (i = start ; i <= end ; i++)
00972       ElementHandler::Destroy (root + i);
00973 
00974     size_t const range_size = end - start + 1;
00975     size_t const ncount = count - range_size;
00976     size_t const nmove = count - end - 1;
00977     if (nmove > 0)
00978       MemoryAllocator::MemMove (root, start, start + range_size, nmove);
00979     SetSizeUnsafe (ncount);
00980   }
00981 
00987   bool Delete (T const& item)
00988   {
00989     size_t const n = Find (item);
00990     if (n != csArrayItemNotFound)
00991       return DeleteIndex (n);
00992     return false;
00993   }
00994 
01008   bool DeleteFast (T const& item)
01009   {
01010     size_t const n = Find (item);
01011     if (n != csArrayItemNotFound)
01012       return DeleteIndexFast (n);
01013     return false;
01014   }
01015 
01017   class Iterator
01018   {
01019   public:
01021     Iterator(Iterator const& r) :
01022       currentelem(r.currentelem), array(r.array) {}
01023 
01025     Iterator& operator=(Iterator const& r)
01026     { currentelem = r.currentelem; array = r.array; return *this; }
01027 
01029     bool HasNext()
01030     { return currentelem < array.Length(); }
01031 
01033     const T& Next()
01034     { return array.Get(currentelem++); }
01035 
01037     void Reset()
01038     { currentelem = 0; }
01039 
01040   protected:
01041     Iterator(const csArray<T, ElementHandler>& newarray)
01042         : currentelem(0), array(newarray) {}
01043     friend class csArray<T, ElementHandler>;
01044 
01045   private:
01046     size_t currentelem;
01047     const csArray<T, ElementHandler>& array;
01048   };
01049 
01051   Iterator GetIterator() const
01052   { return Iterator(*this); }
01053 };
01054 
01060 template <class T>
01061 class csSafeCopyArray
01062         : public csArray<T,
01063                 csArrayElementHandler<T>,
01064                 csSafeCopyArrayMemoryAllocator<T> >
01065 {
01066 public:
01071   csSafeCopyArray (size_t limit = 0, size_t threshold = 0)
01072         : csArray<T, csArrayElementHandler<T>,
01073                      csSafeCopyArrayMemoryAllocator<T> > (limit, threshold)
01074   {
01075   }
01076 };
01077 
01078 #if defined(CS_EXTENSIVE_MEMDEBUG) || defined(CS_MEMORY_TRACKER)
01079 # define new CS_EXTENSIVE_MEMDEBUG_NEW
01080 #endif
01081 
01084 #endif

Generated for Crystal Space by doxygen 1.4.4