GDL - Generic linkeD list Loading/saving
========================================

Updated in the last three years (!):
* changed macros a bit to use a type* instead of a type; post-2.7 gcc chokes
  on using a structure there for some reason. Mostly untested.

Version 2, Jan 14 1999
[See file License inside package for copyright and licensing information]


I was about to write yet another set of functions for saving/loading a
linked list with a number of fields (a list of bounties on players,
including player name, ID, amount, name of person who placed the bounty
etc).

The routines would load a linked list at boot and save when changes happen.
Keywords would be used, like in the player files, so that fields could be
added without breaking the old files). I've done this a number of times
before, so this time I decided to try to make my job easier.

After a while of hacking, I have come up with this module for saving/loading
linked lists. It makes heavy use of the preprocessor, and contains some
rather interesting casts: but it works, and is a real time saver, if you
like me have many linked lists for storing various information which must
save over reboots.

Using a set of macros, you define a table that for each field to be saved
contains the type of the field, offset of the field from the beginning of
the structure and name of the field. The load/save routines are passed a
pointer to the head of your list: they run through the list, assuming that
the first field of the structure is the pointer to the next element in the
list.

For each field in the table, the function finds the place in memory where
data resides then casts it to the appropriate type before reading it or
writing to it.

USING GDL
=========

Let us say you have this structure you want saved and loaded:

typedef struct bounty_data
{
    struct bounty_data *next;   /* next: it's vital this is the first field! */

    char *name;         /* Name of the player */
    int id;             /* ID of the player */

    int amount;         /* Amount of money */

} BOUNTY;

You would include the "gdl.h" file and somewhere in your source code (at top
level, not inside a function) you would have the following construct:

BEGIN_EL_LIST(BOUNTY)

STR(name)
INT(id)
INT(amount)

END_EL_LIST

Start your list with the BEGIN_EL_LIST(typedef-name). Note that is important
that the type is a single word, therefore you must use typedef, as for
example above. There is no semicolon at the end.

Then, list the fields, in any order. There are to basic types, integer and
string fields. An integer field is declared with INT(name), a string one
with STR(name).

Finally, end the declaration with END_EL_LIST.

Let us say the head of your list of bounties is called bounty_list:

BOUNTY *bounty_list;

And you have defined a fitting file name for the bounty file:

#define BOUNTY_FILE "../data/bounty"

To load the list (only once, at start up) you'd use the LOAD_LIST macro:

LOAD_LIST(bounty_list,BOUNTY,BOUNTY_FILE);

The macro takes three parameters: the list head, the type of the structure
the list contains and the name of the file to load from. It proceeds to call
the generic_load_list function with the right parameters.

To save the list, when changes have been made, just:

SAVE_LIST(bounty_list,BOUNTY,BOUNTY_FILE);

The parameters are the same as above. Saving will produce a file with
contents like these:

name Drylock~
id 424242
amount 160000
End

[the above record repeats any number of times]

EOF


FLAGS
=====

If you are using OLC, you are probably familar with the flags in bit.c: a
table contains a flag value and string. Saving flags as strings rather than
just numbers makes the files readable by humans, and it also makes it
possible to change the values of the flags or perhaps even convert into
another flag system altogether.

You can define a flag field using FLAG(variable_name, flag_table_name), e.g:

FLAG(affected_by, affect_flags)

This would result in something like that in the file:

affected_by invis blind~


CUSTOM FUNCTIONS
================

Typing INT(something) in the list expands into an element of the array
that contains the string "something", offset of something from the beginning
of the structure and a pointer to the RWInteger function. Finally, there's a
field called "extra" which can contain anything: the data in that fiels is
passed to the function - this is for example used in the RWFlag function to
pass which flag table the flags should be looked up in.

The RWInteger is one of the 4 Read/Write functions in this version of GDL:
a R/W function is responsible for reading/writing data to/from a file. It is
passed a pointer to the data to be read/written, a file pointer, an flag
telling whether it should read or write the data and finally the extra
information from the table.

To add your own custom function you should write it, add it to gdl.c, add
the header to gdl.h and finally add a macro to the gdl.h so it can be used
easily. Let us say that you want a way of saving short ints:

void    RWShortInt (gdl_action action, void *struct_ptr, void *member_ptr, 
                    const void *extra, FILE * fp)
{
	short int *p = (short int*) member_ptr;
	
    if (action == action_read)
        *p = fread_number (fp);
    else if (action == action_write)
        fprintf (fp, "%d\n", *p);
}

If action is action_read, you'd use fread_number to read a number from the
file fp, then assign it to member_ptr. Note the special cast: You have a
member_ptr which is of type void * : you cast it to short int *, and then
derefer it to assign the number to the contents of that memory location. I
chose to use a local variable here, so that the cast happens only once: this
makes thing a bit more readable.

Except action_read and action_write there is a third action type,
action_postinit. Ater a whole record has been read in, all the functions are
called with action as this parameter on each field: this allows something
like setting string fields to str_dup("") rather than just NULL.

Note that memory for records is allocated using calloc_mem, which sets all
of it to 0: therefore you need to handle action_postinit only on
pointer-type fields.

The struct_ptr points to the beginning of the structure. It is not normally
used, but I included it in the case where one would want to look at the
other fields of the structure.

The extra pointer is usually NULL. You would use it when you want to give
the function an extra argument, like the RWFlag function takes a flag table
it should look the flags up in.

When you have defined a custom function you can use the CUSTOM macro to add
it to the table: the macro takes as first parameter the field to save, the
function as second and the extra field as the last. It's probably easiest
though to define a new macro like INT(x) - see the gdl.h file.


MORE DETAILS
============

The BEGIN_EL_LIST macro declares a function named get_<structure name>_GDL:
this function declares the structure as a local variable (that structure is
used for finding offsets for the fields) and a static table containing the
GDL fields. I have put the table into a function so that the INT/STR macros
do not have to have the structure type passed as parameter: rather, they
calculate offset from the beginning of a structure that has a set name: if
this structure was outside the function, only one table could be used per
module.

If you want to see what the table that the macros define looks like, use gcc
-E file.c | less to just preprocess the file. One look should make you
respect the macro preprocessor much more <G>


PERFORMANCE
===========

I am considering adding optional hashing to increase performance when
dealing with large files. Personally though I don't think it will pay off,
unless the files are very large or they are read very often.


FUNCTIONS FROM OTHER PACKAGES
=============================

bugf: like printf, but prints a bug. From:
 ftp://pip.dknet.dk/pub/pip1773/short-1.tgz

calloc_mem: alloc_mem that zeroes memory after allocating it:

void * calloc_mem(int size)
{
    void *p = alloc_mem(size);
    memset(p, 0, size);

    return p;
}

do_abort: Calling abort() directly seems to hose the current stack frame,
therefore this function that just calls abort:

void do_abort() { abort(); }

ADRESSES
========

EMAIL: erwin@andreasen.org
WWW:   http://www.andreasen.org/

Copyright 1997 Erwin S. Andreasen
