diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 1f8b1acf..632ceea3 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -234,11 +234,12 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid command format"); } else { Introspectable property = root_obj.get_child(name, sizeof(name)); - if (!property.is_valid()) { + const StringConvertibleTypeInfo* type_info = dynamic_cast(property.get_type_info()); + if (!type_info) { respond(response_channel, use_checksum, "invalid property"); } else { char response[10]; - bool success = property.get_string(response, sizeof(response)); + bool success = type_info->get_string(property, response, sizeof(response)); if (!success) respond(response_channel, use_checksum, "not implemented"); else @@ -254,10 +255,11 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid command format"); } else { Introspectable property = root_obj.get_child(name, sizeof(name)); - if (!property.is_valid()) { + const StringConvertibleTypeInfo* type_info = dynamic_cast(property.get_type_info()); + if (!type_info) { respond(response_channel, use_checksum, "invalid property"); } else { - bool success = property.set_string(value, sizeof(value)); + bool success = type_info->set_string(property, value, sizeof(value)); if (!success) respond(response_channel, use_checksum, "not implemented"); } diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 8e2bc9e1..0d880997 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -15,6 +15,9 @@ #include +#pragma GCC push_options +#pragma GCC optimize ("s") + namespace fibre { const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]]; @@ -22,16 +25,36 @@ const size_t embedded_json_length = sizeof(embedded_json) - 1; const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); +Introspectable get_property(size_t idx) { + switch (idx) { +[%- for endpoint in endpoints %] +[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %] + case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); +[%- endif %] +[%- endfor %] + default: return {}; + } +} + + // Note: with -Og this function reserves a huge amount of stack space because it // reserves separate space for the stack frame of each of the inlined functions. // The minimum known set of flags to prevent this is `-O1 -fipa-sra`. // `-O2` is a superset of this so that's what we use here. -bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); +//bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { + //Introspectable property = get_property(idx); + //if property.is_valid() + switch (idx) { [%- for endpoint in endpoints %] +[%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %] + //case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; +[%- else %] + case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; +[%- endif %] [%- endfor %] default: return false; } @@ -50,17 +73,6 @@ bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { } } -Introspectable get_property(size_t idx) { - switch (idx) { -[%- for endpoint in endpoints %] -[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %][# //case [[endpoint.id]]: *(decltype([[endpoint.in_bindings['obj']]])*)buf = [[endpoint.in_bindings['obj']]]; break;#] - case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); -[%- endif %] -[%- endfor %] - default: return {}; - } -} - bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { if (endpoint_ref.json_crc != json_crc_) { return false; @@ -73,4 +85,6 @@ bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { } +#pragma GCC pop_options + #endif // __FIBRE_INTERFACES_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp index 6792d9be..d490ff71 100644 --- a/Firmware/fibre/cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -5,12 +5,15 @@ #include #include +#pragma GCC push_options +#pragma GCC optimize ("s") + class TypeInfo; class Introspectable; +using introspectable_storage_t = std::aligned_storage<16, 4>::type; struct PropertyInfo { const char * name; - void(*getter)(Introspectable&); const TypeInfo* type_info; }; @@ -29,24 +32,16 @@ public: TypeInfo(const PropertyInfo* property_table, size_t property_table_length) : property_table_(property_table), property_table_length_(property_table_length) {} - const PropertyInfo* get_property_info(const char * name, size_t length) const { - for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) { - if (!strncmp(name, prop->name, length)) { - return prop; - } - } - return nullptr; - } + virtual introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const = 0; + Introspectable get_child(const Introspectable& obj, const char * name, size_t length) const; protected: + template static T& as(Introspectable& obj); template static const T& as(const Introspectable& obj); template static Introspectable make_introspectable(T obj, const TypeInfo* type_info); private: - virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } - virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } - const PropertyInfo* property_table_; size_t property_table_length_; }; @@ -83,13 +78,7 @@ public: while ((begin < end) && current.type_info_) { const char * end_of_token = std::find(begin, end, '.'); - const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin); - if (prop_info) { - (*prop_info->getter)(current); - current.type_info_ = prop_info->type_info; - } else { - current.type_info_ = nullptr; - } + current = current.get_direct_child(begin, end_of_token - begin); begin = std::min(end, end_of_token + 1); } @@ -100,44 +89,38 @@ public: return type_info_; } - /** - * @brief Returns the underlying value as a string. This will only succeed - * if this Introspectable contains a Property<...> object. - */ - bool get_string(char* buffer, size_t length) { - return type_info_ && type_info_->get_string(*this, buffer, length); - } - - /** - * @brief Sets the underlying value from a string. This will only succeed - * if this Introspectable contains a Property<...> object. - */ - bool set_string(char* buffer, size_t length) { - return type_info_ && type_info_->set_string(*this, buffer, length); - } - const TypeInfo* get_type_info() { return type_info_; } private: + Introspectable get_direct_child(const char * name, size_t length) const { + for (size_t i = 0; i < type_info_->property_table_length_; ++i) { + if (!strncmp(name, type_info_->property_table_[i].name, length)) { + Introspectable result; + result.storage_ = type_info_->get_child(storage_, i); + result.type_info_ = type_info_->property_table_[i].type_info; + return result; + } + } + return {}; + } + // We use this storage to hold generic small objects. Usually that's a pointer // but sometimes it's an on-demand constructed Property<...>. // Caution: only put objects in here which are trivially copyable, movable // and destructible as any custom operation wouldn't be called. - unsigned char storage_[12]; + introspectable_storage_t storage_; const TypeInfo* type_info_ = nullptr; }; - - template T& TypeInfo::as(Introspectable& obj) { static_assert(sizeof(T) <= sizeof(obj.storage_)); - return *(T*)obj.storage_; + return *(T*)&obj.storage_; } template const T& TypeInfo::as(const Introspectable& obj) { static_assert(sizeof(T) <= sizeof(obj.storage_)); - return *(const T*)obj.storage_; + return *(const T*)&obj.storage_; } template Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) { Introspectable introspectable; @@ -154,8 +137,13 @@ template struct maybe_underlying_type { typedef T type; }; template using maybe_underlying_type_t = typename maybe_underlying_type::type; +struct StringConvertibleTypeInfo { + virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } + virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } +}; struct FloatSettableTypeInfo { + //virtual bool get_float(const Introspectable& obj, float* val) const { return false; } virtual bool set_float(const Introspectable& obj, float val) const { return false; } }; @@ -166,11 +154,15 @@ struct FibrePropertyTypeInfo; // readonly property template -struct FibrePropertyTypeInfo> : TypeInfo { +struct FibrePropertyTypeInfo> : StringConvertibleTypeInfo, TypeInfo { using TypeInfo::TypeInfo; static const PropertyInfo property_table[]; static const FibrePropertyTypeInfo> singleton; + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + return {}; + } + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { return to_string(static_cast>(as>(obj).read()), buffer, length, 0); } @@ -183,12 +175,16 @@ const FibrePropertyTypeInfo> FibrePropertyTypeInfo -struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, TypeInfo { +struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, StringConvertibleTypeInfo, TypeInfo { using TypeInfo::TypeInfo; static const PropertyInfo property_table[]; static const FibrePropertyTypeInfo> singleton; static const Introspectable make_introspectable(Property obj) { return TypeInfo::make_introspectable(obj, &singleton); } + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + return {}; + } + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { return to_string(static_cast>(as>(obj).read()), buffer, length, 0); } @@ -217,4 +213,6 @@ const PropertyInfo FibrePropertyTypeInfo>::property_table[] = {}; template const FibrePropertyTypeInfo> FibrePropertyTypeInfo>::singleton{FibrePropertyTypeInfo>::property_table, sizeof(FibrePropertyTypeInfo>::property_table) / sizeof(FibrePropertyTypeInfo>::property_table[0])}; +#pragma GCC pop_options + #endif // __FIBRE_INTROSPECTION_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 7f9733aa..a03a596e 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -566,6 +566,8 @@ template struct Property { Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T)) : ctx_(ctx), getter_(getter), setter_(setter) {} + Property(T* ctx) + : ctx_(ctx), getter_([](void* ctx){ return *(T*)ctx; }), setter_([](void* ctx, T val){ *(T*)ctx = val; }) {} Property& operator*() { return *this; } Property* operator->() { return this; } @@ -590,6 +592,8 @@ template struct Property { Property(void* ctx, T(*getter)(void*)) : ctx_(ctx), getter_(getter) {} + Property(const T* ctx) + : ctx_(const_cast(ctx)), getter_([](void* ctx){ return *(const T*)ctx; }) {} Property& operator*() { return *this; } Property* operator->() { return this; } diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index 44a8293d..b8feeafa 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -10,6 +10,9 @@ * */ +#pragma GCC push_options +#pragma GCC optimize ("s") + [%- macro rettype(func) %] [%- if not func.out -%] void @@ -36,13 +39,15 @@ public: [%- for property in intf.attributes.values() %] [%- if property.type.fullname.startswith("fibre.Property") %] -[%- if not property.c_setter %] - template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } +[%- if not property.c_getter and not property.c_setter %] + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{&obj->[[property.c_name]]}; } +[%- elif not property.c_setter %] + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } [%- else %] - template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } [%- endif %] [%- else %] - template static auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } + template static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } [%- endif %] [%- endfor %] @@ -80,3 +85,5 @@ inline [[enum.c_type]] operator ~ ([[enum.c_type]] a) { return static_cast<[[enu [%- endfor %] + +#pragma GCC pop_options diff --git a/Firmware/fibre/cpp/type_info_template.j2 b/Firmware/fibre/cpp/type_info_template.j2 index eb2a0b2c..70cfae2a 100644 --- a/Firmware/fibre/cpp/type_info_template.j2 +++ b/Firmware/fibre/cpp/type_info_template.j2 @@ -11,6 +11,9 @@ #include +#pragma GCC push_options +#pragma GCC optimize ("s") + [% for intf in interfaces.values() %][% if not intf.builtin %] template struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { @@ -18,6 +21,17 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { static const PropertyInfo property_table[]; static const [[intf.fullname | to_pascal_case]]TypeInfo singleton; static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); } + + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + T* ptr = *(T**)&obj; + introspectable_storage_t res; + switch (idx) { +[%- for property in intf.attributes.values() %] + case [[loop.index0]]: *(decltype([[intf.c_type]]::get_[[property.name]](std::declval()))*)(&res) = [[intf.c_type]]::get_[[property.name]](ptr); break; +[%- endfor %] + } + return res; + } }; [% endif %][% endfor %] @@ -25,10 +39,12 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { template const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo::property_table[] = { [%- for property in intf.attributes.values() %] - {"[[property.name]]", [](Introspectable& obj){ as()))>>(obj) = [[intf.c_type]]::get_[[property.name]](as(obj)); }, &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, + {"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, [%- endfor %] }; template const [[intf.fullname | to_pascal_case]]TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; [% endif %][% endfor %] + +#pragma GCC pop_options diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 7e84afff..369a4a1c 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -252,19 +252,20 @@ def regularize_attribute(path, name, elem, c_is_class): elem['fullname'] = join_name(path, name) elem['typeargs'] = elem.get('typeargs', {}) elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) - elem['c_getter'] = elem.get('c_getter', elem['c_name']) - elem['c_setter'] = elem.get('c_setter', elem['c_name'] + ' = ') + if ('c_getter' in elem) or ('c_setter' in elem): + elem['c_getter'] = elem.get('c_getter', elem['c_name']) + elem['c_setter'] = elem.get('c_setter', elem['c_name'] + ' = ') if isinstance(elem['type'], str) and elem['type'].startswith('readonly '): elem['typeargs']['fibre.Property.mode'] = 'readonly' elem['typeargs']['fibre.Property.type'] = elem['type'][len('readonly '):] elem['type'] = 'fibre.Property' - if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') + if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') elif ('flags' in elem['type']) or ('values' in elem['type']): elem['typeargs']['fibre.Property.mode'] = elem['typeargs'].get('fibre.Property.mode', None) or 'readwrite' elem['typeargs']['fibre.Property.type'] = regularize_valuetype(path, to_pascal_case(name), elem['type']) elem['type'] = 'fibre.Property' - if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') + if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') else: elem['type'] = regularize_interface(path, to_pascal_case(name), elem['type']) return elem