rossvideo / Catena

Other
4 stars 0 forks source link

compiled -lite model #179

Open mejohnnaylor opened 4 weeks ago

mejohnnaylor commented 4 weeks ago

idea: process the JSON definition of a device model to create C++ data structure that matches the state of the model.

let's start with some simple examples

say we're authoring our device in a folder called example

"params": {
  "int_example": {
    "type": "INT32",
    "value": 1
  }
}

this would produce a C++ header example_state.h thus:

#pragma once

struct example_state {
  int32_t int_example;
}
``

which the service would use like this:

```cpp
#include <example_state.h>

device_state ds; // the device's state

business logic would then have trivial access to the state

// lock goes here
  do_something_useful(ds.int_example); // function defined elsewhere
// unlock goes here

but what about constraints, names etc?

One way to tackle this would be to have a ParamAccessor class (not too fussy about the name, there could be better ones)

template <PARAM_TYPE>
public:
class ParamAccessor : public IParamAccessor { // we'll probably need a base class
  setValue(const PARAM_TYPE& v); // applies sync lock and sets value
  PARAM_TYPE& getValue(); 

  deserialize (const catena::Value&); // sets the device state to the value sent from client

private:
  std::reference_wrapper<PARAM_TYPE> _value; // set in constructor 
  // not sure this is a good idea, why wouldn't this class just own the value?
};

More complex scenario

"params": {
  "type": "STRUCT",
  "location": {
    "params": {
      "longitude": {
        "type": float;
        "constraint": {
          "type": "FLOAT_RANGE",
          "min_value": -180;
          "max_value": 180
        },
      "latitude": {
        "type": float;
        "constraint": {
          "type": "FLOAT_RANGE",
          "min_value": -90;
          "max_value": 90
        }
      }
    }
  }
}

This introduces the need for a user defined type in the C++ code...

example.h again

#pragma once

namespace Example {

struct Location {
  float longitude;
  float latitude;
}

struct example_state {
  Location location{};
};

}

Some rules

Conversion mappings

Param Type Maps to
INT32 int32_t
FLOAT32 float
STRING std::string
INT32_ARRAY std::vector
etc.. for arrays std::vector
STRUCT struct definition with user defined type named for param name with initial cap
STRUCT_ARRAY std::vector
STRUCT_VARIANT std::variant
Other types tbd
mejohnnaylor commented 4 weeks ago

each type definition needs to have some bolt-on static methods to assist with serialization and deserialization thus:

struct FieldSpec {
  std::string name;
  std::ptr_diff_max_t offset
};

using FieldInfo = std::vector<FieldSpec>;

// this code is generated by cppgen
//
struct MyType {
  int a_number;
  float another_number;
  using Fieldtypes = catena::Typelist<int, float>;
  static FieldInfo field_info = {
    "a_number", std::offsetof (MyType, a_number),
    "another_number, std::offsetof (MyType, another_number)
  },

  static fromClient (void* dst, const catena::Value& v) {
    char* base = static_cast<char*>(dst); // so our pointer math works
    uint_t idx = 0;
    for (f : field_info) {
      if (v.has_field(f.name)) {  // can't remember the actual call to make but you get the idea
        *reinterpret_cast<Fieldtypes<idx>*>(base + f.offset) = v.field_value(f.name); //
      }
      idx++;
    }
  }

  static toClient (catena::Value& dst, const void* src) {
    const char* base = static_cast<const char*>(src); // so our pointer math works
    uint32_t idx = 0;
    for (f : field_info) {
        v.field_value(f.name) = *reinterpret_cast<const Fieldtypes<idx>*>(dst + f.offset);
        idx++;
    }
  }
};
mejohnnaylor commented 4 weeks ago

correction usage of Fieldtypes<idx> should be...

catena::meta::Typelist Types<int, float>;

*reinterpret_cast<catena::meta::NthElement<Types, idx>*>(base + f.offset) = v.field_value(f.name);
mejohnnaylor commented 4 weeks ago

we could also put this as a handy operator

template <typename T>
T& operator[](const std::string& fieldname) {
   return *(base + fieldinfo[fieldname]);
}

client code in business logic...

Location& loc = device_model.getRef("/location");

loc = {0,1};

float& lat = device_model.getRef("/location/latitude");
// above first calls device_model's getRef method, then the Location type's getRef method.
lat = 45.0;

obviously this presents thread safety issues so we still need setValue, getValue for the biz logic to use that asserts a lock.