.. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. .. include:: ../../common.defs .. highlight:: cpp .. default-domain:: cpp .. |Extendible| replace:: :class:`Extendible` .. |FieldId| replace:: :class:`template FieldId` .. |ExtFieldContext| replace:: :type:`ExtFieldContext` .. |FieldDesc| replace:: :class:`FieldDesc` .. |Schema| replace:: :class:`Schema` .. _Extendible: Extendible ********** Synopsis ++++++++ .. code-block:: cpp #include "tscore/Extendible.h" |Extendible| allows Plugins to append additional storage to Core data structures and interface like a map or dictionary. Each additional field is declared during init, so that a custom allocator can malloc one block for the Datatype and its extended fields. In C++, the |FieldId| are strongly typed field handles, which allows you to use Extendible in multiple inheritance, and at many levels of inheritance hierarchy, with compile time type safety. Use Case: TSCore Defines class ``Host`` as |Extendible| TSPlugin ``HealthStatus`` Extend the ``Host`` datatype with field `` down reason code``. API returns a handle. Use :arg:`Data` and :arg:`handle` to read & write fields. Description +++++++++++ A data class that inherits from Extendible, uses a CRTP (Curiously Recurring Template Pattern) so that its static |Schema| instance is unique among other |Extendible| types. Thus all instances of the type implicitly know memory layout of the fields. .. code-block:: cpp class ExtendibleExample : public ext::Extendible { int real_member = 0; } The documentation and code refers to the `Derived` type as the class that is inheriting from an |Extendible|. During system init, code and plugins add fields to the |Extendible|'s schema. This will update the `Memory Layout`_ of the schema, and the memory offsets of all fields. The schema does not know the field's type, but it stores the byte size and creates std::functions of the type's constructor, destructor, and serializer. And to avoid corruption, the code asserts that no instances are in use when adding fields. .. code-block:: cpp ext::FieldId fld_my_int; void PluginInit() { fld_my_int = ext::fieldAdd(fld_my_int, "my_plugin_int"); } When an derived class is instantiated, :func:`template<> create()` will allocate a block of memory for the derived class and all added fields. The only memory overhead per instance is an uint16 used as a offset to the start of the extendible block. Then the constructor of the class is called, followed by the constructors of each extendible field. .. code-block:: cpp ExtendibleExample* alloc_example() { return ext::create(); } Memory Layout ------------- One block of memory is allocated per |Extendible|, which include all member variables and extended fields. Within the block, memory is arranged in the following order: #. Derived members (+padding align next field) #. Fields (largest to smallest) #. Packed Bits When using inheritance, all base cases arranged from most super to most derived, then all |Extendible| blocks are arranged from most super to most derived. If the fields are aligned, padding will be inserted where needed. Strongly Typed Fields --------------------- :class:`template FieldId` |FieldId| is a templated ``Field_t`` reference. One benefit is that all type casting is internal to the |Extendible|, which simplifies the code using it. Also this provides compile errors for common misuses and type mismatches. .. code-block:: cpp // Core code class Food : public ext::Extendible {}; class Car : public ext::Extendible {}; // Example Plugin ext::FieldId fld_food_weight; ext::FieldId fld_expr_date; ext::FieldId fld_max_speed; ext::FieldId fld_car_weight; PluginInit() { ext::addField(fld_food_weight, "weight"); ext::addField(fld_expr_date,"expire date"); ext::addField(fld_max_speed,"max_speed"); ext::addField(fld_car_weight,"weight"); // 'weight' is unique within 'Car' } PluginFunc() { Food *banana = ext::create(); Car *camry = ext::create(); // Common user errors float expire_date = ext::get(banana,fld_expr_date); //^^^ // Compile error: cannot convert time_t to float float speed = ext::get(banana,fld_max_speed); // ^^^^^^^^^^^^^ // Compile error: Cannot convert banana to type Extendible float weight = ext::get(camry,fld_food_weight); // ^^^^^^^^^^^^^^^ // Compile error: Cannot convert camry to type Extendible, even though Car and Food each have a 'weight' field, the FieldId is strongly typed. } // Inheritance Example class Fruit : Food, Extendible { using super_type = Food; }; ext::FieldId fld_has_seeds; Fruit.schema.addField(fld_has_seeds, "has_seeds"); Fruit mango = ext::create(); ext::set(mango, fld_has_seeds) = true; // converts mango to Extendible ext::set(mango, fld_food_weight) = 2; // converts mango to Extendible ext::set(mango, fld_max_speed) = 9; // ^^^^^^^^^^^^^ // Compile error: Cannot convert mango to type Extendible Inheritance ----------- Unfortunately it is non-trivial handle multiple |Extendible| super types in the same inheritance tree. :func:`template<> create()` handles allocation and initialization of the entire `Derived` class, but it is dependent on each class defining :code:`using super_type = *some_super_class*;` so that it recurse through the classes. .. code-block:: cpp struct A : public Extendible { uint16_t a = {1}; }; struct B : public A { using super_type = A; uint16_t b = {2}; }; struct C : public B, public Extendible { using super_type = B; uint16_t c = {3}; }; ext::FieldId> ext_a_1; ext::FieldId ext_c_1; C &x = *(ext::create()); ext::viewFormat(x); :func:`viewFormat` prints a diagram of the position and size of bytes used within the allocated memory. .. code-block:: text 1A | EXT | 2b | ##________##__ | 1A | BASE | 2b | __##__________ | 1B | BASE | 2b | ____##________ | 1C | EXT | 2b | ______##____## | 1C | BASE | 2b | ________##____ | See :ts:git:`src/tscore/unit_tests/test_Extendible.cc` for more examples. Reference +++++++++ Namespace `ext` .. class:: template FieldId The handle used to access a field. These are templated to prevent human error, and branching logic. :tparam Derived_t: The class that you want to extend at runtime. :tparam Field_t: The type of the field. .. type:: const void* ExtFieldContext The handle used to access a field through C API. Human error not allowed by convention. .. class:: template Extendible Allocates block of memory, uses |FieldId| and |Schema| to access slices of memory. :tparam Derived_t: The class that you want to extend at runtime. .. member:: static Schema schema one schema instance per |Extendible| to define contained |FieldDesc| .. function:: template Extendible* create() To be used in place of `new Derived_t()`. Allocate a block of memory. Construct the base data. Recursively construct and initialize `Derived_t::super_type` and its |Extendible| classes. :tparam Derived_t: The Derived class to allocate. .. function:: template \ bool fieldAdd(FieldId & field_id, std::string const & field_name) Declare a new |FieldId| for Derived_t. :tparam Derived_t: The class that uses this field. :tparam Field_t: The type of the field. .. function:: template \ bool fieldFind(FieldId & field_id, std::string const & field_name) Find an existing |FieldId| for Derived_t. :tparam Derived_t: The class that uses this field. :tparam Field_t: The type of the field. .. function:: template \ auto const get(T const &, FieldId) Returns T const& value from the field stored in the |Extendible| allocation. :tparam T: The class passed in. :tparam Derived_t: The class that uses this field. :tparam Field_t: The type of the field. .. function:: template \ T & set(T &, FieldId) Returns T & value from the field stored in |Extendible| allocation. :tparam T: The class passed in. :tparam Derived_t: The class that uses this field. :tparam Field_t: The type of the field. .. function:: template size_t sizeOf() Recurse through super classes and sum memory needed for allocation. Depends on usage of `super_type` in each class. :tparam Derived_t: The class to measure. .. function:: template void viewFormat() Recurse through super classes and prints chart of bytes used within the allocation. Depends on usage of `super_type` in each class. :tparam Derived_t: The class to analyze. .. function:: template std::string toString(T const &t) Convert all extendible fields to std::strings (in a YAML-like format) using the :func:`serializeField()` :tparam Derived_t: The class to convert to string. .. function:: template void serializeField(std::ostream &os, Field_t const &f) Converts a single field into a std::string (in a YAML-like format). Specialize this template or overload the `operator<<` for your field to convert properly. This is very useful when debugging. :tparam Derived_t: The field data type. Namespace `ext::details` .. class:: FieldDesc Defines a span of memory within the allocation, and holds the constructor, destructor and serializer as std::functions. Effectively the type-erased version of |FieldId|. .. class:: Schema Manages a memory layout through a map of |FieldDesc|.