Man descriptors

From LSWiki

(Difference between revisions)
Jump to: navigation, search

Current revision

Contents

Files

   /daemon/descriptor.c
   /std/def/descriptor.c
   /def/descriptor/

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:

Mapping

   [ Example : ] Belonging(([
                     Belonging_File     : LS_Weapon("dagger"),
                     Belonging_Count    : 2,
                     Belonging_Priority : Belonging_Priority_Low,
                 ]))

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.

Implicit

   [ Example : ] set_belongings(({
                     LS_Weapon("longbow"),
                     ({ LS_Weapon("sheaf_arrow", 10 }),
                     ([
                         Belonging_File     : LS_Potion("healing"),
                         Belonging_Count    : 3,
                         Belonging_Priority : Belonging_Priority_Low,
                     ]),
                 }));
   [ Example : ] add_belonging(LS_Weapon("broadsword"))

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.

       [ Example : ] if(Is_Belonging(val))
                         who->display("Value is a belonging descriptor.");

Field Name Query Macro

X_Field_Name(field)

Returns the string field name of a given descriptor field.

       [ Example : ] who->display(Belonging_Field_Name(Belonging_File));

Field Set Macro

X_Set(dxr, field, value)

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).

       [ Example : ] Dialog_Set(dialog, Dialog_Next, other_dialog)

Field Query Macro

X_Query(dxr, field)

Returns the value of the specified field in the descriptor.

       [ Example : ] if(Belonging_Query(dxr, Belonging_Count) >= 5)
                         who->display("Belonging is at least five items.");

Field Add Macro

X_Add(dxr, field, value);

Adds a value to the specified field, much like the += operator.

       [ Example : ] Dialog_Add(dialog, Dialog_Extra, ({ obj }));

Field Subtract Macro

X_Subtract(dxr, field, value);

Subtracts a value from the specified field, much like the -= operator.

       [ Example : ] Dialog_Subtract(dialog, Dialog_Extra, ({ obj }));

Field Multiply Macro

X_Multiply(dxr, field, value);

Multiplies a field by the specified value, much like the *= operator.

       [ Example : ] Dialog_Multiply(dialog, Dialog_Extra, 3);

Field Divide Macro

X_Divide(dxr, field, value);

Divides a field by the specified value, much like the /= operator.

       [ Example : ] Dialog_Divide(dialog, Dialog_Extra, 3);

Field Bitwise Or Macro

X_Bitwise_Or(dxr, field, value);

Bitwise-ors the specified field by the specified value, much like the |= operator.

       [ Example : ] Dialog_Bitwise_Or(dialog, Dialog_Flags,
                         Dialog_Flag_Hidden);

Field Bitwise And Macro

X_Bitwise_And(dxr, field, value);

Bitwise-ands the specified field by the specified value, much like the &= operator.

       [ Example : ] Dialog_Bitwise_And(dialog, Dialog_Flags,
                         ~Dialog_Flag_Hidden);

Field Logical Or Macro

X_Logical_Or(dxr, field, value);

Logical-ors the specified field with the specified value, much like the ||= operator.

       [ Example : ] Dialog_Logical_Or(dialog, Dialog_Extra, who->query_id());

Field Logical And Macro

X_Logical_And(dxr, field, value);

Logical-ands the specified field with the specified value, much like the &&= operator.

       [ Example : ] Dialog_Logical_And(dialog, Dialog_Extra, who->query_id());

Field Bitmask Check Macro

X_Y_Check(dxr, bitmask)

A version of this macro exists for each bitmask field in the descriptor. Returns True if the specified mask is set in the field, False otherwise.

       [ Example : ] if(Dialog_Flag_Check(dxr, Dialog_Flag_Default))
                         who->display("Dialog will default.");

Field Bitmask On Macro

X_Y_On(dxr, bitmask)

A version of this macro exists for each bitmask field in the descriptor. Sets as true the specified bitmask value in the bitmask field.

       [ Example : ] Dialog_Flag_On(dxr, Dialog_Flag_Default);

Field Bitmask Off Macro

X_Y_Off(dxr, bitmask)

A version of this macro exists for each bitmask field in the descriptor. Sets as false the specified bitmask value in the bitmask field.

       [ Example : ] Dialog_Flag_Off(dxr, Dialog_Flag_Default);

Field Bitmask All-Values Macro

X_Ys_All

A version of this macro exists for each bitmask field in the descriptor. Has the value of all the defined bitmasks for the field turned on.

       [ Example : ] Dialog_Set(dxr, Dialog_Flags, Dialog_Flags_All);

Field Bitmask All-Values-Except Macro

X_Ys_All_Except(x)

A version of this macro exists for each bitmask field in the descriptor. Has the value of all the defined bitmasks for the field turned on except those specified in the macro.

       [ Example : ] Dialog_Set(dxr, Dialog_Flags,
                         Dialog_Flags_All_Except(Dialog_Flag_Default)
                     );

Field Map-Through-Function Macro

X_Map(dxr, field, closure)

Sets the value of the field to the return value of the closure specified, as call with its current value as argument.

       [ Example : ] Dialog_Map(dxr, Dialog_Prompt, #'modify_prompt);

Field Cross-Equivalency Macro

X_Same(field, dxr1, dxr2)

Checks whether the specified field is the same in both passed descriptors.

       [ Example : ] if(Dialog_Same(Dialog_Type, dxr1, dxr2))
                         who->display("Dialogs are of the same type.");

Field All-Equivalency Macro

X_All_Same(field, dxrs...)

Checks whether the specified field is the same in all passed descriptors.

       [ Example : ] if(Dialog_Same(Dialog_Type, dxr1, dxr2, dxr3))
                         who->display("Dialogs are of the same type.");

Support Functions

Descriptors can also define functions that manipulate them for particular purposes and additional macros to flesh out their capabilities; these will be particular to the individual descriptor type.

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.

Personal tools