home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1993 #3 / NN_1993_3.iso / spool / sci / virtual / 4559 < prev    next >
Encoding:
Internet Message Format  |  1993-01-24  |  10.8 KB

  1. Path: sparky!uunet!zaphod.mps.ohio-state.edu!uwm.edu!ogicse!news.u.washington.edu!stein.u.washington.edu!hlab
  2. From: jpc@tauon.ph.unimelb.edu.au (John Costella)
  3. Newsgroups: sci.virtual-worlds
  4. Subject: TECH: Test drivers sought for a C++ object
  5. Message-ID: <1jvoupINNmdr@shelley.u.washington.edu>
  6. Date: 23 Jan 93 02:33:56 GMT
  7. Article-I.D.: shelley.1jvoupINNmdr
  8. Organization: University of Washington
  9. Lines: 248
  10. Approved: cyberoid@milton.u.washington.edu
  11. NNTP-Posting-Host: stein.u.washington.edu
  12. Originator: hlab@stein.u.washington.edu
  13.  
  14.  
  15. We have a C++ object that needs to be `test driven' on a variety of
  16. platforms, and criticised wherever it either doesn't work or is 
  17. deemed unnecessarily inflexible.
  18.  
  19. The class essentially allows ``multi-dimensional arrays'' of arbitrary 
  20. objects to be dynamically allocated and manipulated with standard 
  21. [i][j][k][l]  type of notation, where there are no restrictions on the 
  22. dynamical specification of any of the indices, and any special memory
  23. handling routines (which can be completely arbitrary, and indeed can
  24. be different for each index of the ``array'') are encapsulated into 
  25. classes derived from this base class. 
  26.  
  27. If you are a C++ programmer, whether a CS undergrad or experienced
  28. veteran, and would like to give the above class a test drive, please
  29. send an e-mail to    jpc@tauon.ph.unimelb.edu.au   .
  30. The class can be used `as is' for the supplied example derived classes
  31. for bytes and bits, or classes can be derived for your own needs.
  32. Further technical details for those interested are given below. The 
  33. class is freely usable for any purpose, and should run with any flavour 
  34. of C++. Preliminary documentation will be available.
  35.  
  36. John Costella
  37. The University of Melbourne
  38.  
  39. ----------------------------------------------------------------------------
  40. John P. Costella              School of Physics, The University of Melbourne
  41. jpc@tauon.ph.unimelb.edu.au         Tel: +61 3 543-7795, Fax: +61 3 347-4783
  42. ----------------------------------------------------------------------------
  43.  
  44.  
  45. More details about the above class
  46. ==================================
  47. (NOTE: Most of the following discussion applies to C without need 
  48. for any knowledge of C++.)
  49.  
  50. The class focusses on the somewhat primitive treatment that plain
  51. C and C++ has for handling ``tensors'' of arbitrary objects.
  52. By a ``tensor'' we mean what would be called in BASIC a ``multi-
  53. dimensional array'' (we are borrowing the mathematical term 
  54. ``tensor'' as a good fit, which has a descriptive language ready-
  55. made for us).
  56.  
  57. The ``rank'' of a tensor of objects is essentially the number of 
  58. subscripts that are required to specify a single object. A rank 0
  59. tensor is a ``scalar'', a rank 1 tensor is a ``vector'', a rank 2
  60. tensor is a ``matrix'', a rank 3 tensor is like a 3-dimensional 
  61. rectangular prism of voxels, and so on.
  62.  
  63. A scalar of the object Object     =>   Object scalar
  64. A vector of objects Object        =>   Object vector[ i ]
  65. A matrix of objects Object        =>   Object matrix[ i ][ j ]
  66. A rank 3 tensor of objects Object =>   Object rank_3[ i ][ j ][ k ]
  67.  
  68. ...and so on.
  69.  
  70. The problem is that vanilla C++ is really only geared up to handle
  71. scalars and vectors of objects dynamically (and even the handling
  72. of vectors keeps changing as C++ shakes down). You can write
  73.  
  74.   int* scalar = new int;
  75.   *scalar = 7;
  76.   delete scalar;
  77.  
  78.   int* vector = new int[ num ];
  79.   vector[ 3 ] = 12;
  80.   delete [] vector;
  81.  
  82. (for C, substitute `malloc' and `free' for the `new' and `delete' 
  83. operators; for older versions of C++, read:   
  84.  
  85.   delete [ num ] vector;   
  86.  
  87. for the deletion of a vector), but you can't write
  88.  
  89.   int* matrix = new int[ numx ][ numy ];   // WRONG
  90.  
  91. or
  92.  
  93.   int** matrix = new int[ numx ][ numy ];  // ALSO WRONG
  94.  
  95. There are two standard ways to get around this problem. One is to
  96. essentially lay all the ``rows'' of the matrix end-to-end and 
  97. allocate a single large vector:
  98.  
  99.   int* matrixfudge = new int[ numx * numy ];
  100.  
  101. But then you need to perform a multiplication to access every 
  102. element:
  103.  
  104.   i = matrixfudge[ y * numx + x ];   // Fudge for matrix[ y ][ x ]
  105.  
  106. and so not only is the syntactical cleanliness lost, you are also
  107. invoking multiplications just to access an element. In general, for a 
  108. rank r tensor, you need to perform (r-1) obscure, processor-consuming
  109. and error-prone multiplications. Consider, as an example, a rank 4 
  110. tensor:
  111.  
  112.   temp1 = numw;
  113.   temp2 = numx * temp1;
  114.   temp3 = numy * temp2;
  115.  
  116.   int* rankfour = new int[ numz * temp3 ];
  117.  
  118.   // Fudge for i = rankfour[ z ][ y ][ x ][ w ] follows:
  119.   i = rankfour[ z * temp3 + y * temp2 + x * temp1 + w ]; 
  120.  
  121. This approach also requires that the whole memory for the tensor
  122. be allocated in one huge contiguous chunk, which might not be 
  123. either possible or desirable.
  124.  
  125. The other standard workaround (* see footnote), which avoids the 
  126. obscurity, the performance penalty and the contiguity problems of 
  127. the above, at the small expense of a smaller number of extra pointers, 
  128. is to allocate a vector of pointers, each pointing to another vector 
  129. of pointers, each pointing to another vector of pointers, ..., and so
  130. on until we finally have each of the last vector of pointers pointing 
  131. to a vector of objects. For example, a matrix of integers is 
  132. implemented via the code
  133.  
  134.   int** matrix = new int*[ numy ];
  135.  
  136.   for( int y = 0; y < numy; y++ )
  137.     matrix[ y ] = new int[ numx ];
  138.  
  139. Now we can validly use clean syntax:
  140.  
  141.   i = matrix[ y ][ x ]; // Now OK
  142.  
  143. There are no multiplications needed to access an element, only
  144. an additional pointer dereference per rank. And best of all, the
  145. memory is allocated as (numy) separate rows, each of length (numx)
  146. integers, rather than a single block of (numy*numx) integers.
  147. The only size penalty is the vector of pointers, which is in 
  148. general insignificant compared to the matrix as a whole.
  149.  
  150. This second workaround is clearly the better of the two, but
  151. it suffers from lack of code re-use. To implement a matrix of
  152. doubles, or a matrix of Windows, or a matrix of any other arbitrary
  153. object that we haven't even invented yet, we have to rewrite the
  154. above code replacing `int' by `double' or `Win' or whatever. (* see
  155. footnote for details) To then go to rank 3 objects we would have to 
  156. write another swag of routines to create a vector of matrices, and 
  157. again for rank 4, and so on. And then each of this large population 
  158. of routines would need to be modified if we needed a different 
  159. memory handling---say, __far or __huge pointers for 16-bit PC apps. 
  160. We'd end up with hundreds of different specialised routines just to 
  161. handle a handful of objects with a handful of ranks and a few memory 
  162. models.
  163.  
  164. Clearly, the desired solution is to create a generic Tensor class
  165. that will automatically recurse down into itself to allocate and
  166. free all the pointers needed for an arbitrary-rank tensor. But it
  167. is slightly complicated because the Tensor class doesn't know what
  168. sort of object it is that we want a tensor of (if we want to be
  169. able to use it for any arbitrary objects), it doesn't know how
  170. to allocate memory (if we want that to be changeable) and it 
  171. doesn't even know how large the pointers are that point to either
  172. the objects or even its own subtensors (if we need to use
  173. different types of pointers). In addition, virtual functions are
  174. *disabled* within constructors (because the derived classes don't
  175. exist at that point) so we can't use polymorphism to help us out.
  176.  
  177. Nevertheless, it is not impossible to write a Tensor class that 
  178. does this job. The tensors only recurse down to the vector level
  179. (not the scalar level) to ensure that the tensor structure does not
  180. take up more memory than the objects it houses. There is still a thin 
  181. layer of wrapping that needs to be performed at the vector level, 
  182. once for each memory model, and once for each different type of
  183. object that needs to be tensorised. However, once this is done,
  184. the job is complete. For example, if a BitTensor class wrapping is
  185. written (a page or two of code), then one can create bitmaps via
  186. the code
  187.  
  188. {
  189.   unsigned int rank = 2;  // dynamically specify rank
  190.   unsigned long dimensions[ rank ] = { numx, numy };  // dynamically
  191.                                                       // specify dims
  192.  
  193.   BitTensor bitmap( rank, dimensions );  // create the tensor: actually
  194.                                          // dynamically allocated
  195.  
  196.   bitmap[ 34 ][ 47 ] = 1;  // access a bit entry in the bitmap
  197.  
  198.   BitTensor bitmap2( bitmap );  // create a new tensor which is a
  199.                                 // copy of the first
  200. // bitmaps are dynamically freed at end of scope
  201.  
  202. There is not even any need to delete the bitmaps when finished: the
  203. objects `bitmap' and `bitmap2' are automatic variables (of size only 
  204. a few bytes) which contains pointers to its subtensors, and so on; 
  205. when the variables `bitmap' and `bitmap2' go out of scope, their 
  206. destructors automatically clean away the tensors.
  207.  
  208. Now imagine that you wanted to create simple 1-bit-deep voxmaps
  209. to represent three-dimensional volumes (say, for spatial-occupancy
  210. enumeration). But say that you need some number N of these voxmaps,
  211. one for the volume surrounding each of the N participants in your 
  212. virtual world. Without writing a single extra line of code, you can 
  213. simply say
  214.  
  215.   unsigned int rank = 4;  
  216.   unsigned long dimensions[ rank ] = { N, sizex, sizey, sizez };  
  217.  
  218.   BitTensor vector_of_voxmaps( rank, dimensions );  
  219.  
  220.   // set the voxel ( 445, 28, 112 ) for the third participant.
  221.   vector_of_voxmaps[ 3 ][ 445 ][ 28 ][ 112 ] = 1;
  222.  
  223.   // out-of-bounds automatically trapped and avoided for the following
  224.   vector_of_voxmaps[ N + 18 ][ 0 ][ 0 ][ 0 ] = 0;
  225.  
  226. By instead using a derived class which allows arbitrary floating
  227. point values as indices to the closest voxel, a test for occupancy
  228. could then be written
  229.  
  230.   if( vector_of_voxmaps[ 3 ][ -4.77 ][ 12.3 ][ 196.65 ] )
  231.     ; // do something meaningful
  232.  
  233. which is about as clean a syntax as one could ask for.
  234.  
  235. . . .
  236.  
  237. The tensor class described above is the one that requires test 
  238. drivers. It should be emphasised that the class is designed 
  239. for high performance and low overheads: using it for a tensor
  240. structure of any type of object is at least as fast as any
  241. other method, and the memory overheads are negligible. 
  242.  
  243. If you would like a copy of class (supplied via source code in
  244. plain ASCII), please follow the intructions at the start of this 
  245. post.
  246.  
  247. John Costella
  248.  
  249. ---------------------------------------------------------------------
  250. *Footnote: The `pointer to vector of pointers to vectors of pointers 
  251. to vectors of ...' trick is described in detail in
  252.   
  253.   W. H. Press, B. P. Flannery, S. A. Teukolsky and W. T. Vetterling,
  254.   _Numerical_Recipes_In_C_ (Cambridge University Press, Cambridge,
  255.   1988), section 1.2, `Some C Conventions for Scientific Computing'.
  256.  
  257. The explosion in code needed to even service a handful of C built-in
  258. types using this method, without the benefit of a Tensor class, is 
  259. exemplified by their library of utilities in Appendix D.
  260. ---------------------------------------------------------------------
  261.