Contents

Files

Description

The descriptor system provides a way of easily creating and managing data
structures that have some object-like functionality without the overhead, high
persistence, and potential clunkiness of LPC objects.

This document consists of an overview of the practical application of
descriptors from a user point of view. If you are mainly concerned with how
to use descriptors effectively in your projects, this document should meet
your needs. If you are a core systems developer who needs to maintain existing
descriptor definitions or create new ones, you should review this document,
then proceed to 'man descriptors_core'.

Introduction

All descriptors are implemented as mixed-type arrays that are specially
tagged so they can be identified; variables holding them will generally be
declared 'descriptor', which is an alias for 'mixed', and a common shorthand
name for them is 'dxr'. (Though they are most properly 'mixed array' values,
'mixed' is used so that one can say 'descriptor array'.) There are many kinds
of descriptors, and each type has its own header file for working with it.
These are kept in /lib/descriptors; you can see the different descriptor types
currently implemented by looking through that directory. I will primarily be
using the belonging descriptors used by /std/autonomon's belongings system
(see 'man set_belongings') as examples, since they're fairly simple, yet they
illustrate the workings of the system well.

Descriptor Fields

Each descriptor is made up of a number of named fields; for example, some
of the fields of a belonging descriptors are Belonging_File, Belonging_Count,
and Belonging_Usage. You can see the descriptor's field definitions at the
top of its header file (in this case, /lib/descriptors/belonging.h). Fields
should, of course, always be referred to using their macros. (They can be
referred to with their string names in some circumstances, but this is intended
for compatibility purposes where descriptors are reimplementing old mechanisms
that used mapping specifications, and should not be used in new code. Fields
should NEVER be referred to using the literal integer values that their macros
resolve to.) There are three types of field: public, internal, and virtual.

Public fields are the main fields intended for user consumption; when
specifying descriptors, you will mostly be specifying public fields.

Internal fields, on the other hand, are mainly intended for use by the
core systems that support the descriptor. Every descriptor has at least one
internal field: the tag that identifies it as a descriptor of its type. Though
nothing in particular prevents you from messing with internal fields, nothing
good will come of your doing so, except insofar as the documentation for a
given system tells you you should.

The final type is the virtual field; these are fields that do not actually
have dedicated space allocated for them in the descriptor's data structure,
but are nonetheless useful to refer to for various purposes. They are much
like public fields in that they are generally intended for your use.

Specifying Descriptors

Specifying descriptors is intended to be flexible and easy, and to never
require you to provide more information than necessary. You can always
generate a descriptor using its specific construction macro; for example, the
construction macro for belongings is Belonging(). The information for the
descriptor can be given in any of several forms:

The mapping form lets you specify whichever fields you want, in whatever
order you want, and if often the clearest and most maintainable form because
the purpose of each value is clearly labeled by its field name. This is
frequently the best form to use.

Array

[ Example : ] Belonging(({ LS_Weapon("dagger"), 2 }))

The array form is something of a quick shorthand that lets you specify a
descriptor without the verbosity of the mapping form. The elements of the
array are each interpreted as the corresponding field in the descriptor's field
order; in the above example, because the first field in a belonging descriptor
is Belonging_File and the second is Belonging_Count, the first element in the
array is interpreted as a Belonging_File and the second as a Belonging_Count.
This sometimes becomes more complex when dealing with virtual fields; each
descriptor can determine which virtual fields appear in its field order for
this purpose. For example, shape descriptors' virtual fields are the geometry
parameters from the shape descriptor's shape type. On the other hand, other
descriptors simply append their virtual field list to their public field list.

Single element

[ Example : ] Belonging(LS_Weapon("dagger"))

The single element form is an even shorter shorthand; the value given is
simply interpreted as the first of the descriptor's fields, in this case a
Belonging_File. Some unusual descriptors may also interpret arrays as single
element form rather than array form; if so, this will be noted in their
documentation. Note that if you call the constructor macro with no arguments,
e.g. Belonging(), the system will generally act as if you had called it with
a single argument of 0. (The exception to this is if the descriptor defines
no public or virtual fields.)

Multiple element

[ Example : ] Belonging(LS_Weapon("dagger"), 2)

Behaves much like array form, but without the extra notation. The example
here accomplishes exactly the same thing as the example given for array form.

Beyond the forms above, you often do not need to use the constructor macro;
many descriptor-based mechanisms automatically pass their arguments through the
constructor macro. When you take advantage of this, you are using implicit
form. For example, set_belongings() takes an array as argument, each element
of which is automatically interpreted as a belonging specification, as shown
above. You can generally use all of the specification forms above with this
type of mechanism, except for multiple element form; there is usually no way to
specify the extended argument list when using implicit form.

Standard Macros

In addition to the constructor macro, all descriptors provide various other
standard macros for working with them. These should be used in preference to
directly manipulating the descriptor data structure whenever possible.

It is also important to note that many descriptor types implement mechanics
where part or all of the descriptor data structure may be shared globally in
order to save memory. Directly manipulating the contents of descriptors of
these types may be disastrous, resulting in unwanted changes to every case
where the descriptor is used rather than simply the one you wanted. Refer to
the documentation or implementation of the descriptor type to determine the
proper way to work with it.

Definition Macro

X_Def

Returns the definition object for the descriptor type.

[ Example : ] object def = Belonging_Def;

Identity Macro

Is_X(value)

Evaluates whether a given value is a descriptor of the relevant type. Returns True or False as appropriate.

Sets a field in a descriptor. If the descriptor provides field
validation or conversion faculties, these will be invoked. (I use
a dialog descriptor from the input system as an example here because
there are no circumstances where it is recommended to modify an
existing belonging descriptor.) Returns the final value assigned to
the field (in the case of virtual fields, this means the final value
passed to the virtual-field-set procedure).

Further Information

In addition to the descriptor header files, you may find useful information
in the descriptor definitions, which live in /def/descriptor.

This concludes what can be said about descriptors in general; the meaning
of particular fields, their use in application, and so forth is left to the
documentation of individual descriptor types. That documentation is kept in
/txt/doc/descriptors; please review the files on any types you are interested
in using.