C++ Computational Steering Library:
===================================

Download URL: http://www.anirudh.net/phd/steering.tgz
	OR    http://bart.ihpca.psu.edu/phd/steering.tgz

This distribution contains libraries for HP-UX (using aCC), SUN (using g++),
Windows NT/2000 (using CYGWIN g++), and Linux (using g++).

There is a client/server demo app in the demo/ directory. Just run the
appropriate Makefile using "make -f Makefile.PLATFORM" in each of
the "client" and "server" directories to get the respective binaries.

-Anirudh Modi- <anirudh@anirudh.net>
10/12/2001-Fri

Registration functions:
=======================

1. Only "global" data can be registered to be served by the server.

2. Include the header "dataserver.h" in the C++ code containing main().

3. All registration has to be specified within the function to
   be defined by the user:

	REGISTER_DATA_BLOCK()
	{
	/* All REGISTER_WHATEVER commands come here */
	}

4. Just invoke the Data Server by issuing an initialization to the
   DataServer class in the main() function.
   e.g., DataServer Server;	// Run on default port 4096

   Optionally, the port number for the server can be specified.
   e.g., DataServer Server(5000);	// Run on port 5000

   Another way to run the invoke the server is:
   DataServer *Server = new DataServer;
   if (Server->Start(5000) != CSL_SUCCESS)
	{
	 /* Server did not start! Your Error Message Here */
	}
   This is recommended as you can get the status of the connection.

5. Here are the functions/macros provided to register various data
   types:

a. To register single variable (any type provided by the system, including
   int, short, long, float, double, char):

   REGISTER_VARIABLE(key, "rw", variable);

   e.g. REGISTER_VARIABLE("time", "ro", dt); // dt is defined as double

   This function is independent of byte ordering (endian-ness) which may
   possibly be different from machine to machine (server/client).

   The second parameter specifies the permission for the variable
   registered. "rw" means that the variable is in READ-WRITE mode and
   can be modified by the client. "ro" is READ-ONLY mode in which the
   client can only view the variable and not modify it. This is true
   for all the REGISTER macros.

b. To register static arrays of any system type (int, double, ...), use:

   REGISTER_1D_ARRAY(key, "rw", array1D);
   REGISTER_2D_ARRAY(key, "rw", array2D);
   REGISTER_3D_ARRAY(key, "rw", array3D);
   REGISTER_4D_ARRAY(key, "rw", array4D);

   NOTE: The arrays should all be static. i.e, "double mat[3][4]" can be 
   registered, but not "double **mat" which may possibly be allocated the
   same size dynamically using malloc/new.

   e.g. REGISTER_2D_ARRAY("matrixA", "rw", A); // A is defined as float A[2][9];

   This function is independent of byte ordering (endian-ness) which may
   possibly be different from machine to machine (server/client).

c. To register dynamic arrays of any system type (int, double, ...), use:

   REGISTER_DYNAMIC_1D_ARRAY(key, "rw", array1D, dim1);
   REGISTER_DYNAMIC_2D_ARRAY(key, "rw", array2D, dim1, dim2);
   REGISTER_DYNAMIC_3D_ARRAY(key, "rw", array3D, dim1, dim2, dim3);
   REGISTER_DYNAMIC_4D_ARRAY(key, "rw", array4D, dim1, dim2, dim3, dim4);

   NOTE: The arrays should all be dynamic. i.e, "double **mat" can be 
   registered, but not "double mat[3][5]" which may possibly be allocated the
   same size. dim1, dim2, ... denote the number of elements in each dimension.
   They may be variable or constant, but have to be defined as global 
   variables. The following registration is illegal:

   i.e, REGISTER_DYNAMIC_3D_ARRAY("dyn3D", "ro", dyn3D, n1, 7, n3); // Illegal

   Also "double **mat; // ALLOC2D(&mat, n1, n2)" is a 2D dynamic array
   whereas "double (*mat)[5]; // ALLOC1D(&mat, n1)" is just a 1D dynamic
   array (as only one dimension is dynamic, the other is static).

   REGISTER_DYNAMIC_1D_ARRAY("dyn1D", "rw", dyn2D, n1);
   REGISTER_DYNAMIC_4D_ARRAY("dyn4D", "rw", dyn4D, n1, n2, n3, n4);

   This function is independent of byte ordering (endian-ness) which may
   possibly be different from machine to machine (server/client).

d. To register a user defined structure, use:

   REGISTER_STRUCTURE(key, "ro", mystruct);	// myStruct mystruct;

   Also, along with this, two additional functions, packStruck/unpackStruct
   has to be defined by the user:

   --------
   int packStruct(myStruct *S, unsigned char **dataptr, int &totsize);
   --------

   packStruct allocates "totsize" bytes of data to the address pointed
   to by "dataptr" and packs the individual members of the structure
   into this contiguous memory. It returns the location of the block
   where the last data was written to.

e.g.:

   int packStruct(myStruct *S, unsigned char **dataptr, int &totsize)
   {
   totsize = /*...(user specified size for the structure)...*/;
   unsigned char *data = new unsigned char[*totsize];
   *dataptr = data;
   int ptr = 0;
 
   /* pack data from S starting at &data[ptr] and update ptr accordingly */
   PACK_VARIABLE(S->member1, data, &ptr);
   PACK_2D_ARRAY(S->member2, data, &ptr);
   .
   .
   .

   return ptr;
   }

   NOTE: "ptr" should always be equal to "*totsize", else a runtime error
   would be generated. This just serves as a simple check to ensure that
   the user indeed packs in the amount of data he claims to pack when
   defining "*totsize".

   --------
   int unpackStruct(myStruct *S, unsigned char *data, int size);
   --------

   unpackStruct unpacks "size" bytes of data from contiguous memory
   block "data" into the structure S (essentially filling the
   structure). It returns the location of the block from where the data
   was last read from.

   int unpackStruct(myStruct *S, unsigned char *data, int size)
   {
   int ptr = 0;
 
   /* pack data from S starting at &data[ptr] and update ptr accordingly */
   UNPACK_VARIABLE(&(S->member1), data, &ptr);
   UNPACK_2D_ARRAY(S->member2, data, &ptr);
   .
   .
   .

   return ptr;
   }

   NOTE: "ptr" should always be equal to "size", else a runtime error
   would be generated. This just serves as a simple check to ensure that
   the user indeed unpacks in the amount of data he claims to pack in
   the function packStruct();

Packing/Unpacking functions:
============================

All packing/unpacking functions are independent of byte-ordering.
All the routines are independent of variable type (int, double, ...).
All routines have the same last 2 arguments of the form:

PACK_WHATEVER(variable or array, unsigned char *data, int *ptr);

which means that pack "variable/array" starting location &data[*ptr] and update
*ptr accordingly by incrementing it with the size of the "variable/array".

Here are all the routines:

PACK_VARIABLE(var, data, &ptr);
PACK_1D_ARRAY(array1D, data, &ptr);
PACK_2D_ARRAY(array2D, data, &ptr);
PACK_3D_ARRAY(array3D, data, &ptr);
PACK_4D_ARRAY(array4D, data, &ptr);
PACK_DYNAMIC_1D_ARRAY(array1D, num_elems, data, &ptr);
PACK_DYNAMIC_2D_ARRAY(array2D, num_elems, data, &ptr);
PACK_DYNAMIC_3D_ARRAY(array3D, num_elems, data, &ptr);
PACK_DYNAMIC_4D_ARRAY(array4D, num_elems, data, &ptr);

UNPACK_VARIABLE(&var, data, &ptr);	// Note the presence of "&"
UNPACK_1D_ARRAY(array1D, data, &ptr);
UNPACK_2D_ARRAY(array2D, data, &ptr);
UNPACK_3D_ARRAY(array3D, data, &ptr);
UNPACK_4D_ARRAY(array4D, data, &ptr);
UNPACK_DYNAMIC_1D_ARRAY(array1D, num_elems, data, &ptr);
UNPACK_DYNAMIC_2D_ARRAY(array2D, num_elems, data, &ptr);
UNPACK_DYNAMIC_3D_ARRAY(array3D, num_elems, data, &ptr);
UNPACK_DYNAMIC_4D_ARRAY(array4D, num_elems, data, &ptr);

Client-Side calling routines:
=============================

Client is invoked by invoking the Class DataClient.

e.g., DataClient Client(name);	// Connect to server "name" on port 4096

Optionally the port on which the server is running can be specified.

e.g., DataClient Client(name, 5000);  // Connect to server "name" on port 5000

Here are the client-side routines:

a. RecvVariable(key, &variable);
   OR variable = RecvVariable<type>(key);

   This is used for receiving any type of variable registered on the server
   side using the keyword/handle "key".

   e.g., double dt;
         client.RecvVariable("time", &dt);
    OR	 dt = client.RecvVariable<double>("time");

   Corresponding Send: client.SendVariable("time", dt);
		    OR client.SendVariable<double>("time", 0.1);

b. RecvArray(key, array);

   This is used for receiving any type of static array (1D/2D/3D/4D) 
   registered on the server side using the keyword/handle "key".

   e.g., float A[20][30];
	 client.RecvArray("matrix", A);

   Corresponding Send: client.SendArray("matrix", A);

c. RecvArray1D(key, arr1d);
   RecvArray2D(key, arr2d);
   RecvArray3D(key, arr3d);
   RecvArray4D(key, arr4d);
   dimN = getArrayDim(key, N);

   These are used for receiving any 1D/2D/3D/4D dynamic array
   registered on the server side using the keyword/handle "key".
   getArrayDim() gets the values of the corresponding dimensions for
   the dynamic array. N = 1, 2, ..,number of dimensions.

   e.g., double **A;
	 int n1 = client.getArrayDim("matrix", 1);
	 int n2 = client.getArrayDim("matrix", 2);
	 ALLOC2D(&A, n1, n2);
	 client.RecvArray2D("matrix", A);

   Corresponding Send: client.SendArray2D("matrix", A);

   Optionally, address of the array can also be passed if the programmer
   wants the library to autmatically allocate the correct dimensions
   to the dynamic array.

   e.g., double **A;
	 int n1 = client.getArrayDim("matrix", 1);
	 int n2 = client.getArrayDim("matrix", 2);
	 client.RecvArray2D("matrix", &A);

   Note, there is no ALLOC2D call needed for allocation in the above example,
   as RecvArray2D will automatically allocate the right amount of memory for A.

d. RecvStruct(key, &mystruct)

   This is used for receiving any custom structure which has been 
   registered on the server side using REGISTER_STRUCTURE command.
   packStruct()/unpackStruct() need to be defined for this structure.

   Corresponding Send: client.SendStruct("matrix", &A);
