diff --git a/direct/src/doc/dcPacker.txt b/direct/src/doc/dcPacker.txt new file mode 100755 index 0000000000..6918b1ff9e --- /dev/null +++ b/direct/src/doc/dcPacker.txt @@ -0,0 +1,248 @@ +This document introduces the basic use of the DCPacker class, which is +available to C++ and Python programs for high-level packing and +unpacking of messages into bytestreams for shipping over the network, +especially via Panda's DistributedObject system. See also the +comments in direct/src/dcparser/dcPacker.h and related source files. + + +OVERVIEW + +The DCPacker has four modes of operation: pack (sequential write), +unpack (sequential read), unpack (random read), and repack (random +write). + +To enter one of these four modes, call begin_pack(), begin_unpack(), +or begin_repack(). (begin_unpack() is used for both kinds of unpack +modes.) Once you have called begin, you can call a series of +pack_this() or unpack_that() methods, and then you finish up by +calling end_pack(), end_unpack(), or end_repack(). + +The return value of the end method will be true to indicate that no +errors have occurred during the packing/unpacking process, or false if +something went wrong (in which case you should probably disregard the +output). + +In general, when packing or unpacking a series of values, you call +pack_int(), pack_uint(), pack_double(), or pack_string() (or the +corresponding unpack methods) according to what kind of data type you +have for each value; it will be coerced into the appropriate data size +as indicated by the DC file and written to the output buffer. + +To pack an array or an embedded class, or any element which itself is +made up of sub-elements, you must bracket the packs for the +sub-elements between calls to push() and pop(). This also applies to +the individual elements of a DCField; so to pack all the elements of a +field, you would call push(), followed by the appropriate pack() for +each element, then pop(). + + +PACK MODE (sequential write) + +Pack mode is used to build up a network message from scratch. Call +begin_pack() and pass it the pointer to a DCField object. You must +immediately call push() to indicate that you will be packing the +individual elements of the field, then make a series of pack calls, +one for each element on the field in order, followed by a call to +pop(), and finally end_pack(). + +You must pack all of the elements of the field, from beginning to +end--it is an error to leave out any elements, including the elements +on the end. + +If end_pack() returns false, there was an error (see ADDITIONAL NOTES, +below). Otherwise, you may call get_data() to get a pointer to the +packed data record, and get_length() to get the number of bytes in the +record. + + DCField *field = dclass->get_field_by_name("setChat"); + + DCPacker packer; + packer.begin_pack(field); + packer.push(); + packer.pack_string(chatString); + packer.pack_int(0); + packer.pop(); + if (!packer.end_pack()) { + cerr << "error occurred while packing.\n"; + return; + } + + memcpy(result, packer.get_data(), packer.get_length()); + + + +UNPACK MODE (sequential read) + +You can also unpack all the elements of a field, from beginning to +end. This is very similar to pack mode, above. Start with a call to +begin_unpack() and pass in the existing data record for the field, and +the pointer to the DCField itself. Then call push(), followed by the +appropriate number and type of unpack calls, followed by pop() and +end_unpack(). + +As above, you must unpack all fields; it is an error not to unpack the +fields on the end. However, it is not an error if there are +additional bytes in the data buffer; the assumption is the data buffer +may be part of a larger buffer. After end_unpack(), you can call +get_num_unpacked_bytes() to determine how many bytes of the buffer +were consumed. + + DCField *field = dclass->get_field_by_name("setChat"); + + DCPacker packer; + packer.begin_unpack(source_buffer, source_size, field); + packer.push(); + string chat = packer.unpack_string(); + int chatFlags = packer.unpack_int(); + packer.pop(); + if (!packer.end_unpack()) { + cerr << "error occurred while unpacking.\n"; + return; + } + + +UNPACK MODE (random read) + +You can also unpack just the particular elements that you care about +by name, in no particular order. To do this, call seek() for each +element you wish to unpack, specifying the name of the element. You +can only do this for elements that have been given names in the DC +file. + +In this case, it is not necessary to bracket the outer unpack calls +with push() and pop() (since you are not walking through all the +elements of the field). However, you still need to use push() and +pop() to unpack the nested elements of an array that you seek to. + + DCField *field = dclass->get_field_by_name("setChat"); + + DCPacker packer; + packer.begin_unpack(source_buffer, source_size, field); + packer.seek("chat"); + string chat = packer.unpack_string(); + if (!packer.end_unpack()) { + cerr << "error occurred while unpacking.\n"; + return; + } + + +REPACK MODE (random write) + +Repack mode allows you to modify some elements of a previously-packed +field, without disturbing the elements you don't specify. +begin_repack() takes the same parameters as begin_unpack(), then call +seek() for each field you want to modify followed by the appropriate +pack call. + +After end_repack() returns true, you can retrieve the newly-repacked +field with get_data() and get_length(), just as in pack mode. + + DCField *field = dclass->get_field_by_name("setChat"); + + DCPacker packer; + packer.begin_repack(source_buffer, source_size, field); + packer.seek("chat"); + packer.pack_string(chatString); + if (!packer.end_repack()) { + cerr << "error occurred while repacking.\n"; + return; + } + + memcpy(result, packer.get_data(), packer.get_length()); + + + +ADDITIONAL NOTES + +It is acceptable to call pack_int() for a uint type element and +vice-versa; the data type will be range-checked and converted to the +appropriate signedness. In general, all of the numeric types are +interchangeable--just call the appropriate one according to the data +type you already have; don't worry about matching to the data type +defined in the DC file. However, if you are trying to write a general +algorithm and you need a hint, you can call get_pack_type() to return +a suggested type for the next pack call; this will return one of +PT_int, PT_uint, PT_double, PT_string, etc. + +The same is true when unpacking: unpack_int() or unpack_uint() may be +used interchangeably on signed or unsigned data (but if you call +unpack_uint() and the data in the record happens to be negative, you +will trigger a pack error). As above, get_pack_type() may be called +to return the suggested type for the next unpack call. + + +If end_pack() or end_repack() returns false, there are two possible +causes. (1) You tried to pack some value that exceeded the range +specified in the DC file (or the limits of the datatype). In this +case, had_range_error() will return true. (2) There was some other, +more serious error while packing the data, such as a mismatched type +(e.g. pack_string() where a uint16 was expected), or you did not pack +the right number of elements. In this case, had_pack_error() will +return true. It might be the case that both error flags are +triggered. + +If end_unpack() returns false, there are two similar causes. (1) +There was an invalid value in the record that exceeded the limits +specified in the DC file. This will be indicated by +had_range_error(). (2) Some mismatched data type (unpack_string() for +a uint16) or the wrong number of elements. This is indicated by +had_pack_error(). Note that specifying a too-small return value +(e.g. unpack_uint() to retrieve a signed value, or unpack_int() to +retrieve a float64 or int64 value greater than 2^32) is considered a +pack error, not a range error. + + +You may call pack_literal_value() for any element for which you want +to supply a pre-packed data value (for instance, a default value +returned by DCAtomicField::get_element_default()). This will be +accepted without further validation. Similarly, +unpack_literal_value() will return a string corresponding to the +pre-packed value of the current element. Both of these work for +composite elements as well as for single-component elements (that is, +you may call unpack_literal_value() instead of calling push() +.. unpack .. pop() to retrieve an entire pre-packed array in one +string). + +Python programmers may be especially interested in pack_object() and +unpack_object(). pack_object() will accept any Python object and call +the appropriate pack function for it. Python tuple or list will +implicitly call push(), followed by pack_object() for all the elements +in the list, followed by pop(), so pack_object() can pack deeply +nested structures with a single call, and with no need to call push() +and pop() explicitly. Conversely, unpack_object() will unpack a +deeply nested structure and return an appropriate Python tuple or list +or other object. You may also consider DCField::pack_args() and +DCField::unpack_args(), which automatically invokes the DCPacker for +you. + +You may also find parse_and_pack() and unpack_and_format() useful for +presenting data to (and accepting data from) a human user. +parse_and_pack() accepts a string formatted in the DC file syntax +(that is, with the same syntax accepted for a DC file default value), +and packs that value for the current element. It may be a single +value or a deeply nested value, with brackets and braces embedded as +appropriate. Similarly, unpack_and_format() will unpack a single +value or a deeply nested value into the same formatted string. As +with pack_object() and unpack_object(), these methods are also +implemented on the DCField class for convenience, as parse_string() +and format_data(). + + +RANGE VALIDATION + +The DCPacker automatically verifies that all data passing through its +fundamental pack or unpack methods fits within the ranges (if any) +specified in the DC file for each data type. Violating a range +restriction triggers a range error, which is indicated by a false +return value from end_pack() / end_unpack() / end_repack() and by a +true return value from had_range_error(). + +If you just want to verify that a message contains legal values +without otherwise inspecting the values, you can use unpack_validate() +for this purpose. Since unpack_validate() will work on deeply nested +structures, you can just call it once in lieu of the entire push() +.. pack .. pop() loop. Furthermore, as in unpack_object() and +unpack_and_format(), above, there is a convenience function for this +on DCField; just call DCField::validate_ranges() to ensure that the +data in the record for the given field fits within its specified +limits.