Skip to content

Object Data

The data classes are used by both hall2D and hall3D for rendering. The library of objects is stored in PostGreSQL, while floorplans (containing instances of the objects) are stored in MongoDB.

The base class, DaObjectBase, contains parsing and serialization logic that leverage the Serialized decorator. When adding a field or extending a class, you only need to add a decorator to your new field. There is no need to override parseJson or serializeJson.

By adding the @ObjectData decorator to a class, you mark it as a data class, with a given ClassName (the argument to the decorator). ObjectDataLoader will use this to create instances of the class when loading data from the database.

The Serialized decorator

When a field is marked with the Serialized decorator, it will be serialized and deserialized automatically in the parseJson and serializeJson methods. The decorator has one required argument, the name of the serialized field, and an options argument:

Options

typescript
/**
 * Options for the Serialized decorator.
 * @type SerializedDecoratorOptions
 * @property {string[]} [alternateFieldNames] - An array of alternate field names that can be used to parse the field from JSON. Will still be serialized with the original field name.
 * @property {SerializableType} [type] - if this has a value, the field will be serialized as an object of this type.
 * @property {boolean} [isArray] - if true, the field will be serialized as an array of the type specified in the 'type' field.
 * @property {boolean} [excludeFromHash] - if true, this field will be serialized as part of the layoutObject, and not as part of libraryObject which is used for hashing.
 * @property {boolean} [excludeFromObserve] - if true, this field will not be observed. Should not be used generally
 * @property {boolean} [excludeFromClone] - if true, this field will not be cloned.
 * @property {ObservableGroup} [observableGroup] - Fields in the same observable group can be observed using a getter.
 * @property {(iValue: any) => ISerializable} [customParser] - A custom parser function that will be called instead of ISerializable.parseJSON (e.g. to parse the correct type of DaObjectBase using ObjectDataLoader)
 * @property {boolean} [allowMinusOne] - In the olden days of ops, -1 was used interchangeably with undefined. If true, the field will be serialized even if it is -1.
 */

Serializing in layout

To save on storage and bandwith, we serialize the layoutObject and libraryObject separately. layoutObject contains fields which only pertain to the instance of the object (position, rotation, displayOrder, etc), while libraryObject contains fields which are shared between all instances of the object.

This way, if there are 100 identical tables in a floorplan, we only need to store the libraryObject once, and a smaller layoutObject for each instance containing their positions and such.

The layoutObject is connected to the libraryObject by the hash field, which is a hash of the libraryObject.

The excludeFromHash option tells the serializer to serialize the field in the layoutObject and not in the libraryObject.

Mobx

documentation

The data classes heavily leverage Mobx. All @Serialized fields are observables, getters are computed, and setters are actions. Functions are not actions by default. You can override getObservableAnnotations to specify them as such.

Observable Groups

One of the options for the Serialized decorator is observableGroup. This is used to group fields together for observation. You can use one of the following getters to listen to all fields in a certain group: graphicProperties, transformProperties

typescript
/**
 * Fields in the same observable group can be observed using a getter.
 * @readonly
 * @enum {number}
 * @property {number} Graphic - Fields that are related to the graphic representation of the object. Default value.
 * @property {number} Transform - Fields that are related to the transform of the object, such as position and rotation
 * @property {number} Logic - Fields that are related to the logic of the object, such as objectID and orderID. Usually you would listen to individual fields here, not the whole group.
 *  */

UID

The UID is a unique identifier for each object. It is composed of a 4-character nanoID, and then a base36 representation of the userID. This ensures there are no collisions between objects created by different users simultaneously.

In the database, this represents the key of the object in the "layout" field of the floorplan document.

You may encounter number strings serving as UIDs. These are legacy IDs from vops, and are kept that way for simplicity as only one vops user can edit an event at a time.

Known Issues!!!

  • If you have a property that has a type (and therefore implements ISerializable), you must call this.makeObservable in its constructor for it to behave correctly.

  • When listening to a group of properties, you access the field itself and not its getter. For complex objects, this means you will only observe the reference changes and not the nested values changing. To work around this, you can listen to the individual fields in the group, as we do for PresetIDs currently. You can also use deepObservable to observe the nested values, but this is not recommended as it can lead to performance issues.