The Utility Library.
Description: The PLIB Utility Library is targeted towards hiding common operating system functions behind a thin layer that makes them portable and provides some useful helper classes and routines.
Keywords: UL, PLIB, OpenGL, utility, library, portable, Baker, Steve, ulDir, ulClock, ulDynamicLibrary, ulList, ulLinkedList, ulHashTable, ulPropertySet, ulSleep, ulMilliSecondSleep, ulFindFile, ulFileExists, ulIsAbsolutePathName, ulGetCWD, ulMakePath, ulSetErrorCallback, ulGetErrorCallback, ulGetError, ulClearError, ulStrEqual, ulStrNEqual

# The PLIB general Utility Library.

By Sebastian Ude

## Introduction

The 'UL' utility library is primarily targeted towards hiding common operating system functions behind a thin layer that makes them portable. Additionally, it provides some helper classes or routines that were usually written to support certain parts of the PLIB library, but at some point we felt that they could be useful for user applications as well.

UL is a part of PLIB.

## Contents:

- Misc. routines
- File handling
- Directory handling
- Data storage
- Error handling
- Misc. classes
- Endian handling

## Quick reference:

### Classes: | ### Non-class functions:
ulDir ulClock ulDynamicLibrary ulList ulLinkedList ulHashTable ulPropertySet | ulSleep, ulMilliSecondSleep ulFindFile ulFileExists ulIsAbsolutePathName ulGetCWD ulMakePath ulSetErrorCallback, ulGetErrorCallback | ulGetError, ulClearError ulStrEqual, ulStrNEqual

## Misc. routines

### ulSleep

      void ulSleep ( int seconds ) ;

Now comes a typical example of what the UL library does. If you need to 'sleep' for, say, 3 seconds, then under Linux/UNIX, you'd need to call:

      sleep ( 3 ) ;

But under MS-Windows, you have to say:

      Sleep ( 3000 ) ;

In order to avoid writing non-portable code, you can instead call:

      ulSleep ( 3 ) ;

...under either operating system.

### ulMilliSecondSleep

      void ulMilliSecondSleep ( int milliseconds ) ;

Same as ulSleep, except that it sleeps (as you may have guessed) a certain number of milliseconds instead of seconds.

## File handling

### ulFindFile

      void ulFindFile ( char *filenameOutput, const char *path, const char * tfnameInput, const char *sAPOM ) ;

Basically, this utility function adds tfnameInput to the path and puts this into the buffer filenameOutput.

It handles special chars in path:

- ";;" is replaced by ";"
- "$$" is replaced by "$"
- "$(APOM)" is replaced by sAPOM

If there are ";" in path, the path-variable is interpreted as several paths "segments", delimited by ";". The first file found by this function is returned. It looks from left to right. A segment may end in $(...). ulFindFile will then look in in this path and recursively in all the sub-paths.

Some examples:

To load *.MDl-models, it is very nice to set the texture path to "$(APOM);$(APOM)/texture;$(APOM)/../texture". This consists of three segments and tells ulFindFile to look in the path of the model, in a subpath texture and in a path texture "besides" the path of the model. Some *.mdl-models are shipped in a directory which contains a "texture"-directory, a "Model"-directory and others. In this case you find the texture in "$(APOM)/../texture".

Another example: You have all your textures in a directory-structure under /roomplan:

    textures --+-- Wallpapers
               |
    		   +-- Wood --+-- Oak
               |          |
    		   |          +-- pine
               ...

Then you should simply use the following texture path: "/roomplan/$(...)"

### ulFileExists

      bool ulFileExists ( const char *fileName ) ;

Returns "true" if a file with the name 'fileName' exists, or "false" if it does not.

### ulIsAbsolutePathName

      int ulIsAbsolutePathName ( const char *pathname ) ;

Returns '1' if 'pathname' is an absolute pathname or '0' if it is an relative one.

### ulGetCWD

      char * ulGetCWD ( char *result, int maxlength ) ;

Stores the current working directory in 'result', which has enough space for 'maxlength' characters, including the trailing '\0'. On success, 'result' is returned.

### ulMakePath

      char * ulMakePath ( char *path, const char *dir, const char *fname ) ;

Concatenates the strings 'dir' and 'fname', puts the system's slash character inbetween and stores stores the result in 'path'. Be sure that the buffer 'path' is large enough to store 'strlen ( dir ) + strlen ( fname ) + 2' (slash and trailing '\0') characters.

## Directory handling

### struct ulDir

This structure provides a portable way to read directories. To allocate and initialize a new ulDir structure, call:

      ulDir * ulOpenDir ( const char* dirname ) ;

This function returns a pointer to a newly allocated ulDir structure on success or a NULL pointer if the specified directory could not be read.

After you have constructed an ulDir structure, the directory content can be read with subsequent calls to:

      struct ulDirEnt
      {
        char d_name [ UL_NAME_MAX+1 ] ;
        bool d_isdir ;
      } ;
      ulDirEnt * ulReadDir ( ulDir *dir ) ;

This function returns a pointer to a ulDirEnt structure which resides in the corresponding ulDir structure and which has the above form. The "d_isdir" flag indicates if a directory entry is another directory. If the end of the directory has been reached, the function returns NULL.

To free an ulDir object and to close the associated directory stream, please call:

      void ulCloseDir ( ulDir *dir ) ;

## Data storage

### class ulList

      ulList::ulList ( int init_max = 1 ) ;

This class stores a list of generic (void*) pointers using an automatically-growing array. Since the process of resizing the internal array is rather expensive, it is important that one picks a reasonable default array size (init_max) when constructing an ulList object if performance matters. Remember that a too large size means that some memory is wasted (but memory is cheap nowadays), while a too small one means that expensive array resize operations will be necessary later.

If you have absolutely no clue about how many entities will be stored, you should probably look at the linked list class below, which has it's own disadvantages, though.

Once you have constructed an ulList (hopefully with a good initial size), you can insert an element using one of the following methods:

      void ulList::addEntity ( void *entity ) ;
      void ulList::addBefore ( int n, void *entity ) ;

While the first one simply adds the new entity to the tail of the list, the second one lets you specify an exact position (0 is the first element). Note that due to it's array implementation, inserting an entity at the head or the middle of the ulList requires that the following entities are all shifted one array slot to the right, which can be a rather expensive operation.

These functions automatically check whether there is room for one more element and double the internal array's size if necessary. However, as said previously, resizing the array is a rather expensive process, so it is not recommended that you rely too much on this behavior. Instead, pick a good initial array size.

Note that ulList allows you to have multiple entities with the same data value in the list. Please also note that ulList does not make it's own copy of the data pointed to by 'entity'. It is up to you to take care of that the memory region that 'entity' points to holds something useful as long as the ulList exists. Be especially careful with addresses of variables that have a limited lifetime.

To retrieve an entity, call:

      void * ulList::getEntity ( unsigned int n ) ;

Where 'n' is the position of the entity in the list. If 'n' is not a valid index, NULL is returned. Once you have called this function, you can use subsequent calls to

      void * ulList::getNextEntity ( void ) ;

in order to retrieve the following entities in the list. If there are no more entities, this function will return NULL.

To remove an entity, call one of:

      void ulList::removeEntity ( unsigned int n ) ;
      void ulList::removeEntity ( void *entity ) ;

Where the second function removes the first entity with the specified data value in case there are multiple ones.

To replace the value of an entity, call one of:

      void ulList::replaceEntity ( unsigned int n, void *new_entity ) ;
      void ulList::replaceEntity ( void *old_entity, void *new_entity ) ;

Where the second function will replace the value of the first entity with the specified old data value in case there are multiple ones.

And finally, you can retrieve the number of entities stored in the list (that's not necessarily the internal array's size), remove all entities or retrieve the position of an entity by specifying it's data value:

      void ulList::getNumEntities ( void ) const ;
      void ulList::removeAllEntities () ;
      int  ulList::searchForEntity ( void *entity ) const ;

Where the latter returns a negative value if no entity with the specified value was found in the list, and otherwise the position of the first entity with the specified data value.

### class ulLinkedList

      ulLinkedList::ulLinkedList () ;

The ulLinkedList class stores generic (void*) pointers using a linked list of nodes where each node maintains a pointer to the next node.

This technique has some advantages compared to an array implementation of a list like ulList.

- No wasted memory. When storing data using arrays (as ulList does), it is a common practice to choose an array size that seems "large enough". Most of the time the number of entities stored will be smaller than the available array slots, so some memory space is wasted. With a linked list, we allocate single nodes. No memory is wasted (except a few bytes for each node's pointer to the next node).
- Resizing an array is an expensive operation, since usually the whole array content has to be copied from the old to the new memory location. We do not have this problem with linked lists.
- In contrast to arrays, it is a cheap operation to insert or remove entities to / from the head or middle of a linked list. While with an array, the following entities must all be shifted one slot to the left or right, we simply need to (re)connect a few pointers with a linked list.

There are some disadvantages, though:

- In contrast to an array, there is no cheap way to locate the n-th entity in a linked list. With an array, you simply have to add the desired position multiplied with the size of an element to the array's base address and look at the content of the memory at this location - a very simple operation. With a linked list, however, we need to iterate over all nodes 'n' times, so locating the 1000th node is a rather expensive operation.
- One may argue that insertion operations are more expensive with linked lists than with arrays, since we allocate single nodes from the heap in contrast to an array, where the whole list is one huge allocated block of memory. Although allocation from the heap is indeed rather expensive, this is not true when it comes to insertions to the middle of a list, these are usually way more expensive with arrays. Furthermore, a list implemented using an array such as ulList may make resizes of the array necessary on insertions, which are again rather expensive.

Decide yourself if a list implemented as an array such as ulList or a linked list implementation like this one fits your needs better.

Once you have constructed a ulLinkedList object, you can insert a node using one of:

      void ulLinkedList::appendNode ( void *data ) ;
      void ulLinkedList::prependNode ( void *data ) ;
      void ulLinkedList::insertNode ( void *data, int pos ) ;

While "appendNode" adds the new node at the tail of the list, "prependNode" will place the new node at the head of the list, and "insertNode" allows you to specify the desired position ('0' is the first node) yourself; 'pos' must be either '0' or a number between '0' and the number of nodes minus one. "prependNode" is equal to calling "insertNode" with pos == 0.

Note that ulLinkedList allows you to have two nodes with the same data in the list. Also note that just as ulList, ulLinkedList does not make it's own copy of the memory pointed to by 'data', so make sure that the corresponding memory location holds something useful as long as the list exists.

To retrieve the number of nodes in the list, call:

      int ulLinkedList::getNumNodes ( void ) const ;

To retrieve the data of the node at a certain position in the list, call:

      void * ulLinkedList::getNodeData ( int pos ) const ;

Where 'pos' must be a number between '0' and the number of nodes minus one, again.

If you need to retrieve the position of a node in the list by specifying it's data value, call:

      int ulLinkedList::getNodePosition ( void *data ) const ;

If there is more than one node whose data value is 'data' in the list, this function will return the position of the first one. If there is no node with the specified data value in the list, this function will return a negative number to indicate failure.

Checking if the return value of "getNodePosition" is non-negative is also the recommended way to determine whether there exists at least one node with a certain data value in a list.

To remove a node from the list, call one of:

      bool ulLinkedList::removeNode ( void *data ) ;
      void * ulLinkedList::removeNode ( int pos ) ;

Where the first function returns 'true' if the node was sucessfully removed or 'false' if it could not find a node whose value is 'data'. In case there is more than one node whose data value is 'data', it will remove the first one. With the second function, 'pos' has to be a number between '0' and the number of nodes minus one. It's return value is the removed node's data value.

To iterate over the list (starting from the head) and to have a custom function being called for each node's data pointer, call:

      typedef bool (*ulIterateFunc)( void *data, void *user_data ) ;
      void * ulLinkedList::forEach ( ulIterateFunc fn, void *user_data = NULL ) const ;

The iteration process will stop if your ulIterateFunc returns 'false', in which case "forEach" returns the data value of the node at which the iteration stopped, or if the tail of the list has been reached, in which case "forEach" returns NULL. The user_data pointer will be passed to your ulIterateFunc as the second argument.

ulLinkedList allows you to maintain a sorted list as an option. To set up a sorted list, simply be sure to insert all nodes using the sorted insertion function as soon as there is at least one node in the list. It's prototype is:

      typedef int (*ulCompareFunc)( const void *data1, const void *data2 ) ;
      int ulList::insertSorted ( void *data, ulCompareFunc comparefn ) ;

Where 'comparefn' is your custom comparison function that takes two data pointers, compares them and returns a memcmp / strcmp-like result:

- A number less than zero if "data1" is "smaller" than "data2", that means if "data1" had to be inserted before "data2" in the list.
- A number equal to zero if "data1" is equal to "data2", that means if "data" had to be inserted immediately before or after "data2" in the list.
- A number greater than zero if "data1" is "greater" than "data2", that means if "data" had to be inserted after "data2" in the list.

The return value of "insertSorted" is the position of the new node in the list on success, or a negative value if you tried to do a sorted insertion on a non-sorted list, that is a list which contains more than one node of which at least one was not inserted using the sorted insertion function. In the latter case, the new node would not have been inserted to the list.

To determine whether a list is sorted or not, call the following function:

      bool ulLinkedList::isSorted ( void ) const ;

Note that with a linked list, you must not modify the criteria of a node's data that is used for sorting without removing the node from the list and re-inserting it.

Finally, you can empty a list (remove all nodes) by calling:

      typedef bool (*ulIterateFunc)( const void *data ) ;
      void ulList::empty ( ulIterateFunc destroyfn = NULL, void *user_data = NULL ) ;

Where "destroyfn" is an optionally specified function that will be called with each destroyed node's data pointer as the first and with user_data as the second argument. This is for example useful if the list entries are pointers to dynamically allocated objects that have to be freed on destruction of the list. The return value of the specified function is ignored.

### ulHashTable

Not yet.

## Error handling

### ulSetErrorCallback

      typedef void (*ulErrorCallback) ( enum ulSeverity severity, char* msg ) ;
      void ulSetErrorCallback ( ulErrorCallback cb ) ;

PLIB has an internal error handling system that the subsystems use to report debug, warning or error messages. An application can set up an error callback that PLIB will call whenever such a message occurs in addition to printing the message on the user's terminal.

ulSeverety indicates the importance of an message and is currently defined the following way:

      enum ulSeverity
      {
        UL_DEBUG,
        UL_WARNING,
        UL_FATAL
      } ;

Where

- UL_DEBUG are unimportant debug messages that can usually safely be ignored
- UL_WARNING are important warning messages that should not be ignored
- UL_FATAL are fatal errors that PLIB can not recover from

### ulGetErrorCallback

      typedef void (*ulErrorCallback) ( enum ulSeverity severity, char* msg ) ;
      ulErrorCallback ulGetErrorCallback ( void ) ;

Returns the current error callback (if any, else NULL is returned).

### ulGetError

      char * ulGetError ( void ) ;

Returns a pointer to the error buffer, that is, the last error message or an empty string if there were not any error messages or if the error buffer has just been cleared.

### ulClearError

      void ulClearError ( void ) ;

Clears the error buffer.

## Misc. classes

### class ulDynamicLibrary

      ulDynamicLibrary::ulDynamicLibrary ( const char *libname ) ;

This class provides a portable way to load a dynamic library and to retrieve the memory address of a specific function afterwards. When constructing an ulDynamicLibrary object, you have to specify the name of the dynamic library you want to operate on without the platform-specific file extension for dynamic libraries.

Afterwards, you can retrieve the memory address where a function / symbol of the library has been loaded by calling:

      void * ulDynamicLibrary::getFuncAddress ( const char *funcname ) :

This function returns NULL if the specified symbol was not found.

### ulClock

      ulClock::ulClock () ;

No further documentation yet.

### ulPropertySet

Not yet.

## String handling

### ulStrEqual

      int ulStrEqual ( const char *s1, const char *s2 ) ;

This function provides a portable way to compare two strings while ignoring the case of the characters. We need it since half of the machines on the planet provide strcasecmp and the other half stricmp for this purpose.

In contrast to the libc string comparison routines, this routine returns '1' if the strings are equal and '0' if they are not.

### ulStrNEqual

      int ulStrNEqual ( const char *s1, const char *s2, int len ) ;

Same as ulStrEqual, except that it only compares the first 'len' characters of the strings.

## Endian handling

      bool ulIsLittleEndian ( void ) ;
      bool ulIsBigEndian ( void ) ;

      unsigned short ulEndianLittle16 ( unsigned short x ) ;
      unsigned int ulEndianLittle32 ( unsigned int x ) ;
      float ulEndianLittleFloat ( float x ) ;

      unsigned short ulEndianBig16 ( unsigned short x ) ;
      unsigned int ulEndianBig32 ( unsigned int x ) ;
      float ulEndianBigFloat ( float x ) ;

      void ulEndianLittleArray16 ( unsigned short *x, int length ) ;
      void ulEndianLittleArray32 ( unsigned int *x, int length ) ;
      void ulEndianLittleArrayFloat ( float *x, int length ) ;

      void ulEndianBigArray16 ( unsigned short *x, int length ) ;
      void ulEndianBigArray32 ( unsigned int *x, int length ) ;
      void ulEndianBigArrayFloat ( float *x, int length ) ;

      unsigned short ulEndianReadLittle16 ( FILE *f ) ;
      unsigned int ulEndianReadLittle32 ( FILE *f ) ;
      float ulEndianReadLittleFloat ( FILE *f ) ;

      unsigned short ulEndianReadBig16 ( FILE *f ) ;
      unsigned int ulEndianReadBig32 ( FILE *f ) ;
      float ulEndianReadBigFloat ( FILE *f ) ;

      size_t ulEndianWriteLittle16 ( FILE *f, unsigned short x ) ;
      size_t ulEndianWriteLittle32 ( FILE *f, unsigned int x ) ;
      size_t ulEndianWriteLittleFloat ( FILE *f, float x ) ;

      size_t ulEndianWriteBig16 ( FILE *f, unsigned short x ) ;
      size_t ulEndianWriteBig32 ( FILE *f, unsigned int x ) ;
      size_t ulEndianWriteBigFloat ( FILE *f, float x ) ;

No further documentation yet.