Man descriptors core

From LSWiki

(Difference between revisions)
Jump to: navigation, search

Current revision

Contents

Files

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

Description

This document describes the internal workings of the descriptor system for core systems developers who need to maintain existing descriptor types or create new ones. If you are not familiar with the user-level perspective on descriptors as described in 'man descriptors', you should probably review it before proceeding.

Introduction

Descriptors are array-based data structures that closely resemble MudOS classes or C structs in applicability, with some object-like functionality layered on through the availability of functions implemented in the descriptor definition object, somewhat like Perl's object support. Each descriptor type definition in /def/descriptor determines the characteristics of a class of descriptors, and each has a corresponding automatically-generated header file in /lib/descriptors. (These header files should not be manually edited; if they are, the changes will be lost the next time the descriptor definition loads.)

Adding a New Descriptor Type

Like other /def mechanisms, descriptors have a header file, in this case /lib/descriptors.h, and a broker daemon, /daemon/descriptor.c. When adding a new descriptor type, it is necessary to give it a macro code (as seen in the list at the top of descriptors.h). Follow these steps:

1) Create a line that #defines the value of the new descriptor type's macros as the current value of Descriptors_Count plus one.

2) Insert this line into the macro list according to alphabetical order.

3) Increment the values of Descriptors_Count and Descriptors_Array_Size by one.

It is important that you resist any temptation to renumber the existing descriptor types, because the integer tags that identify descriptors as of a particular type are determined from these codes, and those tags are, in some cases, saved and restored. Changing these codes would, therefore, cause a great deal of trouble. Be aware that the same kind of trouble can be caused by adding fields to or removing fields from descriptors; when doing so, you need to be aware of whether the descriptor type you're working with is saved or not.

Descriptor Definitions

The meat of the descriptor type, of course, is in the descriptor definition itself. These all inherit /std/def/descriptor and are configured using the following functions:

void set_descriptor(int code)

Sets the definition's descriptor code, using the appropriate macro from descriptors.h. (Required.)

void set_descriptor_name(string name)

Sets the string name of the descriptor. This should correspond to the macro and the name of the definition file; that is, if your name is "example", your macro should be Descriptor_Example and your filename should be example.c. If either the filename or the macro does not match, pervasive errors will make your descriptor type almost completely unusable. A warning will be issued if the filename does not match. (Required.)

void set_descriptor_public_field_names(string array names)

Sets the string names of the descriptor's public fields. The system will automatically generate the corresponding macro names from these.

void set_descriptor_internal_field_names(string array names)

Sets the string names of the descriptor's internal fields. The system will automatically generate the corresponding macro names from these. Be aware that an extra, standard internal field named "tag" is generated for all descriptors; because of this, the field name "tag" is reserved. The tag field is always defined so that it is the last non-virtual field in the descriptor.

void set_descriptor_virtual_field_names(string array names)

Sets the string names of the descriptor's virtual fields. The system will automatically generate the corresponding macro names from these.

void set_descriptor_preload_field_names(string array names)

Sets a list of fields that need to be "high-priority" loaded, before other fields are processed. An example is the shape descriptor's type field, which must be preloaded so it can control which virtual fields are available. These must be public fields. If you specify virtual or internal fields or unknown field names, an error will be raised.

void set_descriptor_field_aliases(mapping aliases)

Defines a set of alternate names which you wish one or more fields to be addressable by. The keys in the mapping are the string field names of the aliases and the values are the string field names of the target fields.

void set_descriptor_field_validate(closure proc)

Sets a procedure which can be used to control the values that can be assigned to fields. The closure given will receive arguments of 1) the descriptor data structure 2) the field 3) the value being assigned. Any value but True will cause an error to be raised; if the return value is a string, that will be used as the error message, otherwise one will be generated.

void set_descriptor_field_convert(closure proc)

Sets a procedure which can be used to alter the value being assigned to a field. The closure given will receive arguments of 1) the descriptor data structure 2) the field 3) the value being assigned. The return value will be used as the actual value of the field (or, in the case of virtual fields, will be passed to the virtual-field-set procedure instead of the original value).

void set_descriptor_query_convert(closure proc)

Sets a procedure which can be used to alter the value being queried from a field. The closure given will receive arguments of 1) the descriptor data structure 2) the field 3) the value being returned. The return value will be used as the actual return value of the query.

void set_descriptor_field_interpret(closure proc)

Sets a procedure which can provide debugging interpretation information on descriptor content. When a debug dump of a given field is being performed, the closure given will be called with arguments of 1) the descriptor data structure 2) the field 3) the value for the field. If the return value is a string, this will be interpreted as an explanation of the value in the field, and will be displayed after the value. If the return value is an array or mapping, this will be interpreted as a set of labels for an array or mapping value, and the labels will be displayed before the corresponding elements of the value. To see this mechanism in action, together with the debugging support provided by the descriptor mechanism, type 'eval me->query_shape()'.

void set_descriptor_set_virtual(closure proc)

Sets the procedure used to set the descriptor's virtual fields. When a virtual field is to be set, the closure given will be called with arguments of 1) the descriptor data structure 2) the field 3) the value specified. Return values are ignored. If the descriptor has virtual fields and this procedure is not specified, errors will be raised when and if code attempts to set the virtual fields.

void set_descriptor_query_virtual(closure proc)

Sets the procedure used to query the descriptor's virtual fields. When a virtual field is queried, the closure given will be called with arguments of 1) the descriptor data structure 2) the field. The return value will be passed back as the result of the query. If the descriptor has virtual fields and this procedure is not specified, errors will be raised when and if code attempts to query the virtual fields.

void set_descriptor_virtual_list(closure proc)

Sets a procedure which can be used to determine which virtual fields are considered part of the descriptor's field order for purposes of a descriptor specification in array form, single element form, or multiple element form. The closure given will be passed the descriptor data structure as argument, and its return value is expected to be an array of field codes or 0. If nonzero, the return value will be added to the descriptor's public field list to construct the final field order. When this procedure is not specified, the entire virtual field list is simply added to the public field list in creating the field order. Note that this procedure does not come into play when dealing with mapping-form descriptor specifications, and for that reason virtual-set and virtual-query procedures cannot assume they will only be dealing with the fields returned by the virtual-list procedure.

void set_descriptor_preconfigure(closure proc)

Sets a procedure to be called when a descriptor is first instantiated, after preload fields have been loaded but before any other operations. The closure given will receive the descriptor data structure as argument. Return values are ignored.

void set_descriptor_initialize(closure proc)

Sets a procedure to be called when a descriptor has just been constructed, after fully loading its fields. The closure given will receive the descriptor data structure as argument. Return values are ignored.

void set_descriptor_convert(closure proc)

Sets a procedure that can be used to replace an entire descriptor with another one as it is generated. The closure given is called after the initialization procedure, if any, and just before returning the descriptor to the code requesting it. The descriptor data structure is passed as argument. If the return value is non-zero, the descriptor is replaced with that value, which is expected to be another valid descriptor of the same type. This is generally used to implement descriptor caching/sharing schemes.

void set_descriptor_specification_convert(closure proc)

Sets a procedure that can be used to preprocess the specification of a to-be-created descriptor. The closure is called at the beginning of the process of descriptor assembly, and is passed the descriptor specification as its argument. This specification may be any data type, including an already-assembled descriptor (which normally would be returned unchanged). This mechanism rarely needs to be or should be used.

void set_descriptor_definitions(mapping spec)

Defines fields in the descriptor as referring to definitions. The keys in the mapping passed are the names of fields, and the values are definition type identifiers (generally macros from /lib/definition_types.h). The values for the indicated fields will be required to be either zero or a valid definition specification for the type specified, will have its stored value coerced into the appropriate identifier for the definition type (generally an integer code for definitions that have them, a string key otherwise), and the descriptor will automatically be able to interpret these fields as being a definition of the relevant type.

void set_descriptor_definition_groups(mapping spec)

Defines fields in the descriptor as referring to arrays of definitions. The keys in the mapping passed are the names of fields, and the values are definition type identifiers. The values for the indicated fields will be required to be either zero, a definition specification for the type specified, or an array of these, and will have its stored value coerced into an array of the appropriate identifiers for the definition type. The descriptor will automatically be able to interpret these fields as being arrays of definitions of the relevant type.

void set_descriptor_descriptors(mapping spec)

Defines fields in the descriptor as referring to descriptors. The keys in the mapping passed are the names of fields, and the values are descriptor type codes (macros from /lib/descriptors.h). The values for the indicated fields will, if not zero, be coerced into an instance of the descriptor type.

void set_descriptor_descriptor_groups(mapping spec)

Defines fields in the descriptor as referring to arrays of descriptors. The keys in the mapping passed are the names of fields, and the values are descriptor type codes (macros from /lib/descriptors.h). The values for the indicated fields will, if not zero, be coerced into an array of instances of the descriptor type.

void set_descriptor_enumerations(mapping spec)

Defines fields in the descriptor as having enumerated values. The keys in the mapping passed are the names of fields, and the values are arrays of strings which provide the names of the possible values for that field. Macros will be automatically constructed for these and integer values assigned to them. The names will look like "Descriptor_Field_Value", where "Descriptor" is the name of the descriptor type, "Field" is the name of the field, and "Value" is the string value name provided. The descriptor will automatically be able to interpret these fields back to their names, and will normally restrict the field to being set to one of the enumerated values. If you embed an integer in the value name list, the integer values for subsequent items will be indexed from that number.

void set_descriptor_bitmasks(mapping spec)

Defines fields in the descriptor which are used for bitmask flags. The keys in the mapping passed are the names of fields, and the values are arrays of strings which provide the names of the bitmask values for that field. Macros will be automatically constructed for these and bit values assigned to them. The names will look like "Descriptor_Field_Value", where "Descriptor" is the name of the descriptor type, "Field" is the name of the field (converted to a singular if it appears to be a plural), and "Value" is the string bitmask name provide. The descriptor will automatically be able to interpret these fields, and will restrict the field to being set to valid combinations of the bitmasks defined. A given bitmask field cannot have more than 32 possible bitmask values, since we have 32-bit integers. The names "all" and "except" may not be used for bitmasks values, since they are used for standard bitmask manipulation macros.

void set_descriptor_optionality(mapping spec)

Affects definition, definition group, descriptor, descriptor group, and enumeration fields. For the first four, fields mapping to a True value in the specification can have a zero value to be specified for the field without causing an error. For enumeration fields, allows fields to have non-integer values, which will be simply stored in the field; integer values will still be checked against the enumerated list.

void set_descriptor_additional_macros(mixed array list)

Defines additional macros to be included in the descriptor's header file. Each element of the list may be one of the following:

   A two-element string array: Results in a macro definition where the
   first element of the array, with the descriptor name prepended, is used
   as the macro name, and the second element is used as the macro value
   (and will be aligned horizontally as generally appropriate to the
   header file).  For example, the list element ({ "Usage_None", "1" }) in
   /def/descriptor/belonging.c results in a line resembling "#define
   Belonging_Usage_None 1" in belonging.h.
   Zero: Results in a blank line in the header file.
   A string: Results in the string being included in the header file
   exactly as is, except for having a newline appended.  Try to use the
   two-element string array form instead of the string form whenever
   possible.

void set_description_additional_includes(string array list)

Defines additional includes which should be placed in the generated header file for the descriptor type. These should be the bare filenames to be used.

Support Functions

Beyond these configuration functions, the descriptor definition may also contain arbitrary functions for use with its descriptors. Any function defined in the definition object which is not private or static, and whose name begins with the descriptor name or its plural, spaces replaced by underscores, and an underscore, will automatically have a corresponding macro defined for its use by the header file. For example, if /def/descriptor/belonging.c had a function 'void belonging_check(mixed array dxr)', this would cause a macro 'Belonging_Check(x)' to appear in belonging.h, defined so as to call the function in the descriptor definition. These functions are automatically examined for the number of arguments they take and whether they are defined varargs, so as to create a macro that is fully appropriate to the function.

Further Information

It is highly recommended to familiarize yourself with existing descriptor implementations to better understand how they operate. Belonging descriptors are a good place to start; they are relatively simple and straightforward, yet use many of the more advanced features of the system to some extent. Shape descriptors are an example of heavy use of virtual fields and the close linking of a definition class (geometries) to a descriptor type. Many of the other descriptors are very simple in their definition, though their usage may be complex. Look around.

Personal tools