From 7f3bb3cce08ccb29d03cc4f09ab5f377caf2a669 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 24 Mar 2021 21:02:54 +0100 Subject: [PATCH] New autogen architecture This rework prepares Fibre servers for the upcoming rework of the low level protocol (that will add remote coroutine call support) and the rework of the object model (that add support for dynamically adding/removing objects). It also decouples the user's server application from autogenerated Fibre interfaces such that user classes no longer need to inherit from autogenerated classes. This is expected to reduce compile time and improve user code organization. --- .vscode/launch.json | 2 +- cpp/codecs.hpp | 121 +++++ cpp/endpoints_template.j2 | 7 +- cpp/fibre.cpp | 18 +- cpp/include/fibre/callback.hpp | 5 + cpp/include/fibre/cpp_utils.hpp | 23 + cpp/include/fibre/fibre.hpp | 23 + cpp/include/fibre/object_server.hpp | 149 +++++++ cpp/include/fibre/status.hpp | 3 +- cpp/interfaces_template.j2 | 114 +++-- cpp/legacy_endpoints_template.j2 | 30 ++ cpp/legacy_object_server.cpp | 248 +++++++++++ cpp/legacy_object_server.hpp | 43 ++ cpp/legacy_protocol.cpp | 33 +- cpp/legacy_protocol.hpp | 22 +- cpp/package.lua | 3 + cpp/platform_support/posix_socket.cpp | 21 +- cpp/property.hpp | 49 ++ cpp/protocol.hpp | 131 ------ cpp/static_exports.hpp | 22 + cpp/static_exports_template.j2 | 39 ++ python/fibre/libfibre.py | 4 +- test/Tupfile.lua | 9 +- test/test-interface.yaml | 9 + test/test_client.py | 19 +- test/test_server.cpp | 38 +- tools/interface-definition-file.md | 60 +++ tools/interface_generator.py | 617 +++++++++++++++++--------- 28 files changed, 1409 insertions(+), 453 deletions(-) create mode 100644 cpp/codecs.hpp create mode 100644 cpp/include/fibre/object_server.hpp create mode 100644 cpp/legacy_endpoints_template.j2 create mode 100644 cpp/legacy_object_server.cpp create mode 100644 cpp/legacy_object_server.hpp create mode 100644 cpp/property.hpp create mode 100644 cpp/static_exports.hpp create mode 100644 cpp/static_exports_template.j2 create mode 100644 tools/interface-definition-file.md diff --git a/.vscode/launch.json b/.vscode/launch.json index c6938f0..7859a7f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -54,7 +54,7 @@ "name": "C++ Test Server", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/test/test_server.elf", + "program": "${workspaceFolder}/test/build/test_server.elf", "stopAtEntry": false, "cwd": "${workspaceFolder}/test", "environment": [{"name": "FIBRE_LOG", "value": "5"}], diff --git a/cpp/codecs.hpp b/cpp/codecs.hpp new file mode 100644 index 0000000..d5a741a --- /dev/null +++ b/cpp/codecs.hpp @@ -0,0 +1,121 @@ +#ifndef __FIBRE_CODECS_HPP +#define __FIBRE_CODECS_HPP + +#include "logging.hpp" +#include "static_exports.hpp" +#include +#include +#include +#include "property.hpp" + +DEFINE_LOG_TOPIC(CODEC); +#define current_log_topic LOG_TOPIC_CODEC + +namespace fibre { + +template +struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { + printf("unknown decoder for %s\n", typeid(T).name()); + return std::nullopt; } +}; + +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return (buffer->begin() == buffer->end()) ? std::nullopt : std::make_optional((bool)*(buffer->begin()++)); } + static bool encode(bool value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { + std::optional int_val = Codec::decode(domain, buffer); + return int_val.has_value() ? std::optional(*reinterpret_cast(&*int_val)) : std::nullopt; + } + static bool encode(float value, bufptr_t* buffer) { + void* ptr = &value; + return Codec::encode(*reinterpret_cast(ptr), buffer); + } +}; +template +struct Codec::value>> { + using int_type = std::underlying_type_t; + static std::optional decode(Domain* domain, cbufptr_t* buffer) { + std::optional int_val = SimpleSerializer::read(&(buffer->begin()), buffer->end()); + return int_val.has_value() ? std::make_optional(static_cast(*int_val)) : std::nullopt; + } + static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; + +//template<> struct Codec { +// static std::optional decode(Domain* domain, cbufptr_t* buffer) { +// std::optional val0 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); +// std::optional val1 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); +// return (val0.has_value() && val1.has_value()) ? std::make_optional(endpoint_ref_t{*val1, *val0}) : std::nullopt; +// } +// static bool encode(endpoint_ref_t value, bufptr_t* buffer) { +// return SimpleSerializer::write(value.endpoint_id, &(buffer->begin()), buffer->end()) +// && SimpleSerializer::write(value.json_crc, &(buffer->begin()), buffer->end()); +// } +//}; + + +//, std::enable_if_t<(get_interface_id(), true) +template struct Codec { + static std::optional decode(Domain* domain, cbufptr_t* buffer) { + uint8_t idx = (*buffer)[0]; // TODO: define actual decoder + + // Check object type + ServerObjectDefinition* obj_entry = domain->get_server_object(idx); + + if (!obj_entry) { + FIBRE_LOG(W) << "index out of range"; + return std::nullopt; + } + + if (obj_entry->interface != get_interface_id()) { + FIBRE_LOG(W) << "incompatile interface: expected " << (int)obj_entry->interface << " but got " << (int)get_interface_id(); + return std::nullopt; + } + + *buffer = buffer->skip(1); + return (T*)obj_entry->ptr; + } + + static bool encode(bool value, bufptr_t* buffer) { return false; } +}; + +} + +#undef current_log_topic + +#endif // __FIBRE_CODECS_HPP diff --git a/cpp/endpoints_template.j2 b/cpp/endpoints_template.j2 index 459a2ca..edae7e3 100644 --- a/cpp/endpoints_template.j2 +++ b/cpp/endpoints_template.j2 @@ -13,8 +13,9 @@ #ifndef __FIBRE_ENDPOINTS_HPP #define __FIBRE_ENDPOINTS_HPP -#include +//#include #include +#include #include // Note: with -Og the functions with large switch statements reserves a huge amount @@ -32,7 +33,7 @@ const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_str 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); - +/* static void get_property(Introspectable& result, size_t idx) { switch (idx) { [%- for endpoint in endpoints %] @@ -84,7 +85,7 @@ bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { const FloatSettableTypeInfo* type_info = dynamic_cast(property.get_type_info()); return type_info && type_info->set_float(property, value); } - +*/ } #pragma GCC pop_options diff --git a/cpp/fibre.cpp b/cpp/fibre.cpp index d85fa88..67c37db 100644 --- a/cpp/fibre.cpp +++ b/cpp/fibre.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "static_exports.hpp" #if FIBRE_ALLOW_HEAP #include @@ -240,7 +241,7 @@ void Domain::add_channels(ChannelDiscoveryResult result) { #if FIBRE_ENABLE_CLIENT || FIBRE_ENABLE_SERVER // Deleted during on_stopped() - auto protocol = new fibre::LegacyProtocolPacketBased(result.rx_channel, result.tx_channel, result.mtu); + auto protocol = new fibre::LegacyProtocolPacketBased(this, result.rx_channel, result.tx_channel, result.mtu); #if FIBRE_ENABLE_CLIENT protocol->start(MEMBER_CB(this, on_found_root_object), MEMBER_CB(this, on_lost_root_object), MEMBER_CB(this, on_stopped)); #else @@ -268,3 +269,18 @@ void Domain::on_lost_root_object(LegacyObjectClient* obj_client, std::shared_ptr void Domain::on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status) { delete protocol; } + +#if FIBRE_ENABLE_SERVER +ServerFunctionDefinition* Domain::get_server_function(ServerFunctionId id) { + if (id < n_static_server_functions) { + return &static_server_function_table[id]; + } + return nullptr; +} +ServerObjectDefinition* Domain::get_server_object(ServerObjectId id) { + if (id < n_static_server_objects) { + return &static_server_object_table[id]; + } + return nullptr; +} +#endif diff --git a/cpp/include/fibre/callback.hpp b/cpp/include/fibre/callback.hpp index 52aaa76..15f6817 100644 --- a/cpp/include/fibre/callback.hpp +++ b/cpp/include/fibre/callback.hpp @@ -95,6 +95,11 @@ function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_ return {}; } +template +function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...) const) { + return {}; +} + template struct MemberCallback; diff --git a/cpp/include/fibre/cpp_utils.hpp b/cpp/include/fibre/cpp_utils.hpp index 262598d..b8e4bc6 100644 --- a/cpp/include/fibre/cpp_utils.hpp +++ b/cpp/include/fibre/cpp_utils.hpp @@ -639,6 +639,8 @@ class function_traits { * For an empty TypeList, the return type is void. For a list with * one type, the return type is equal to that type. For a list with * more than one items, the return type is a tuple. +* +* TODO: this is redundant with as_tuple */ template struct return_type; @@ -1041,6 +1043,11 @@ struct result_of { using type = TRet; }; +template +struct result_of { + using type = TRet; +}; + template struct result_of { using type = TRet; @@ -1079,16 +1086,32 @@ using tuple_cat_t = decltype(std::tuple_cat(std::declval(). template struct as_tuple { using type = std::tuple; + + template + static std::tuple wrap_result(TFunc func) { + return {func()}; + } }; template<> struct as_tuple { using type = std::tuple<>; + + template + static std::tuple<> wrap_result(TFunc func) { + func(); + return {}; + } }; template struct as_tuple> { using type = std::tuple; + + template + static std::tuple wrap_result(TFunc func) { + return func(); + } }; template diff --git a/cpp/include/fibre/fibre.hpp b/cpp/include/fibre/fibre.hpp index 78b5f68..5b88faf 100644 --- a/cpp/include/fibre/fibre.hpp +++ b/cpp/include/fibre/fibre.hpp @@ -90,6 +90,24 @@ struct LegacyProtocolPacketBased; class LegacyObjectClient; struct LegacyObject; +#if FIBRE_ENABLE_SERVER +typedef uint8_t ServerFunctionId; +typedef uint8_t ServerInterfaceId; + +// TODO: Use pointer instead? The codec that decodes the object still needs a table +// to prevent arbitrary memory access. +typedef uint8_t ServerObjectId; + +struct ServerFunctionDefinition { + Callback, Domain*, bool, bufptr_t, CallBuffers, Callback, CallBufferRelease>> impl; +}; + +struct ServerObjectDefinition { + void* ptr; + ServerInterfaceId interface; // TODO: use pointer instead of index? Faster but needs more memory +}; +#endif + class Domain { friend struct Context; public: @@ -102,6 +120,11 @@ class Domain { void add_channels(ChannelDiscoveryResult result); +#if FIBRE_ENABLE_SERVER + ServerFunctionDefinition* get_server_function(ServerFunctionId id); + ServerObjectDefinition* get_server_object(ServerObjectId id); +#endif + Context* ctx; private: #if FIBRE_ENABLE_CLIENT diff --git a/cpp/include/fibre/object_server.hpp b/cpp/include/fibre/object_server.hpp new file mode 100644 index 0000000..b32ae84 --- /dev/null +++ b/cpp/include/fibre/object_server.hpp @@ -0,0 +1,149 @@ +#ifndef __FIBRE_OBJECT_SERVER_HPP +#define __FIBRE_OBJECT_SERVER_HPP + +#include "fibre.hpp" +#include "../../static_exports.hpp" +#include "../../codecs.hpp" +#include + +namespace fibre { + +template +constexpr ServerObjectDefinition make_obj(T* obj) { + return {obj, get_interface_id()}; +} + +template +constexpr ServerObjectDefinition make_obj(const T* obj) { + return {const_cast(obj), get_interface_id()}; +} + +template +T decode(Domain* domain, cbufptr_t* inbuf, bool* success) { + std::optional result = fibre::Codec::decode(domain, inbuf); + if (result.has_value()) { + return *result; + } else { + *success = false; + return T{}; + } +} + +struct SyncWrapper { + Callback sync_entrypoint; + + // The call context is guaranteed to always have the same size for an + // ongoing call + // TODO: what about alignment guarantees? + std::optional entrypoint(Domain* domain, bool start, bufptr_t call_context, CallBuffers call_buffers, Callback, CallBufferRelease> continuation); +}; + +std::optional SyncWrapper::entrypoint(Domain* domain, bool start, bufptr_t call_context, CallBuffers call_buffers, Callback, CallBufferRelease> continuation) { + struct CallState { + bool tx_phase = false; + size_t offset = 0; + size_t tx_len = 0; // only valid in TX phase + }; + + if (call_context.size() < sizeof(CallState)) { + return CallBufferRelease{kFibreOutOfMemory, call_buffers.tx_buf.begin(), call_buffers.rx_buf.begin()}; + } + + CallState& state = *(CallState*)call_context.begin(); + bufptr_t arg_memory = call_context.skip(sizeof(CallState)); + + if (start) { + state = CallState{}; // initialize call state + } + + if (!state.tx_phase) { + // Copy caller's input buffer into call's state buffer + size_t n_copy = std::min(call_buffers.tx_buf.size(), arg_memory.skip(state.offset).size()); + std::copy_n(call_buffers.tx_buf.begin(), n_copy, arg_memory.skip(state.offset).begin()); + state.offset += n_copy; + call_buffers.tx_buf.skip(n_copy); + + bufptr_t outbuf = arg_memory; + Status call_status = sync_entrypoint.invoke(domain, arg_memory.take(state.offset), &outbuf); + if (call_status != kFibreClosed && call_status != kFibreInsufficientData) { + // Synchronous call failed + return CallBufferRelease{call_status, call_buffers.tx_buf.begin(), call_buffers.rx_buf.begin()}; + } + if (call_status == kFibreInsufficientData && state.offset == arg_memory.size()) { + // Context buffer too small to hold the input args for this function + return CallBufferRelease{kFibreOutOfMemory, call_buffers.tx_buf.begin(), call_buffers.rx_buf.begin()}; + } + if (call_status != kFibreInsufficientData) { + // Synchronous call succeeded - change over to TX phase + state.tx_len = outbuf.begin() - arg_memory.begin(); + state.tx_phase = true; + state.offset = 0; + } + } + + if (state.tx_phase) { + // Copy call's state buffer into caller's output buffer + size_t n_copy = std::min(call_buffers.rx_buf.size(), state.tx_len - state.offset); + std::copy_n(arg_memory.skip(state.offset).begin(), n_copy, call_buffers.rx_buf.begin()); + state.offset += n_copy; + call_buffers.rx_buf.skip(n_copy); + } + + Status status = state.offset == state.tx_len ? kFibreClosed : kFibreOk; + return CallBufferRelease{status, call_buffers.tx_buf.begin(), call_buffers.rx_buf.begin()}; +} + + +template +struct Wrappers; + +template +struct Wrappers, std::tuple> { + /** + * Note: this template function should be kept simple as it's instantiated for + * every call signature. Functions of the same call signature share the same + * instantiation of this template. + * + * @param ptr: A function that takes sizeof...(TIn) input arguments and + * returns a tuple with sizeof...(TOut) output arguments. + */ + template + static Status sync_func_wrapper_impl(void* ptr, Domain* domain, cbufptr_t inbuf, bufptr_t* outbuf, std::index_sequence, std::index_sequence) { + bool success = true; + std::tuple inputs = { + decode(domain, &inbuf, &success) ... + }; + if (!success) { + return kFibreInsufficientData; // TODO: decoders could fail for other reasons than insufficient data + } + auto func = (std::tuple (*)(TIn...))ptr; + std::tuple outputs = std::apply(*func, inputs); + std::array ok = {fibre::Codec::encode(std::get(outputs), outbuf) ...}; + return std::all_of(ok.begin(), ok.end(), [](bool val) { return val; }) ? kFibreClosed : kFibreOutOfMemory; + }; + + static Status sync_func_wrapper(void* ptr, Domain* domain, cbufptr_t inbuf, bufptr_t* outbuf) { + return sync_func_wrapper_impl(ptr, domain, inbuf, outbuf, std::make_index_sequence(), std::make_index_sequence()); + }; + + template + static Callback make_sync_member_func_wrapper() { + std::tuple (*free_func)(TObj*, TIn...) = [](TObj* obj, TIn ... args) { + return as_tuple::type>::wrap_result( + [obj, &args...]() { return (obj->*func)(args...); } + ); + }; + return { + Wrappers, std::tuple>::sync_func_wrapper, + (void*)free_func + }; + } +}; + +template SyncWrapper sync_member_func_wrapper = { + .sync_entrypoint = Wrappers::type>::template make_sync_member_func_wrapper() +}; + +} + +#endif // __FIBRE_OBJECT_SERVER_HPP diff --git a/cpp/include/fibre/status.hpp b/cpp/include/fibre/status.hpp index c6a61bd..1282d1f 100644 --- a/cpp/include/fibre/status.hpp +++ b/cpp/include/fibre/status.hpp @@ -12,7 +12,8 @@ enum Status { kFibreInternalError, //!< Bug in the local fibre implementation kFibreProtocolError, //!< A remote peer is misbehaving (indicates bug in the remote peer) kFibreHostUnreachable, //!< The remote peer can no longer be reached - //kFibreInsufficientData, // maybe we will introduce this to tell the caller that the granularity of the data is too small + kFibreOutOfMemory, //!< There are not enough local resources to complete the request (this error can also pertain to statically sized buffers, not only heap memory) + kFibreInsufficientData, // TODO: review if we need this status code }; } diff --git a/cpp/interfaces_template.j2 b/cpp/interfaces_template.j2 index 46cb2b3..0b81c56 100644 --- a/cpp/interfaces_template.j2 +++ b/cpp/interfaces_template.j2 @@ -12,29 +12,51 @@ #ifndef __FIBRE_INTERFACES_HPP #define __FIBRE_INTERFACES_HPP +#include +#include +#include + [[userdata.c_preamble]] -#include +[%- macro intf_name(intf) %] +[%- if intf.builtin %]fibre::Property<[['const ' if intf.mode == 'readonly']][[type_name(intf.value_type)]]> +[%- else %] +[%- for elem in intf.name %][[elem | to_pascal_case]][['_' if not loop.last]][% endfor %]Intf +[%- endif %] +[%- endmacro %] -#pragma GCC push_options -#pragma GCC optimize ("s") +[%- macro wrapper_name(intf) %] +[%- if intf.builtin %]fibre::Property<[['const ' if intf.mode == 'readonly']][[type_name(intf.value_type)]]> +[%- else %] +[%- for elem in intf.name %][[elem | to_pascal_case]][['_' if not loop.last]][% endfor %]Wrapper +[%- endif %] +[%- endmacro %] + +[%- macro type_name(type) %] +[%- if type.c_name %][[type.c_name]][% else %]unknown name for [[type]][% endif %] +[%- endmacro %] [%- macro rettype(func) %] [%- if not func.out -%] void [%- elif func.out | length == 1 -%] -[[(func.out.values() | first).type.c_name]] +[[type_name((func.out.values() | first).type)]] [%- else -%] -std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> +std::tuple<[% for arg in func.out.values() %][[type_name(arg.type)]][[', ' if not loop.last]][% endfor %]> [%- endif -%] [%- endmacro %] -[%- macro render_interface(intf) %] -class [[intf.name | to_pascal_case]]Intf[% if intf.implements %] :[%- for base_intf in intf.implements %] public [[base_intf.c_name]][% endfor %][% endif %] { -public: -[%- for intf in intf.interfaces -%] -[[render_interface(intf) | indent(4)]] +[% for intf in interfaces.values() %] +[%- if not intf.builtin %] +class [[intf_name(intf)]]; +template class [[wrapper_name(intf)]]; +[%- endif %] [%- endfor %] + +[% for intf in interfaces.values() %] +[%- if not intf.builtin %] +class [[intf_name(intf)]][% if intf.implements %] :[%- for base_intf in intf.implements %] public [[base_intf.c_name]][% endfor %][% endif %] { +public: [%- for enum in intf.enums %] enum [[enum.name | to_pascal_case]] { [%- for k, value in enum['values'].items() %] @@ -43,43 +65,53 @@ public: }; [%- endfor %] -[%- for property in intf.attributes.values() %] -[%- if property.type.fullname.startswith("fibre.Property") %] -[%- if not property.c_getter and not property.c_setter %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{&obj->[[property.c_name]]}; } - template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{&obj->[[property.c_name]]}; }[# these are for the set_endpoint_from_float function. This is unmaintainable and should go away #] -[%- elif not property.c_setter %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; } - template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; } -[%- else %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } - template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } -[%- endif %] -[%- else %] - template static inline auto get_[[property.name]](T* obj) { return &obj->[%- if property.c_getter %][[property.c_getter]][% else %][[property.c_name]][% endif %]; } -[%- endif %] +[%- for func in intf.functions.values() %] + virtual [[rettype(func)]] [[func.name[-1]]]([% for name, in in func.in.items() %][% if loop.index0 %][[type_name(in.type)]] [[name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; [%- endfor %] -[%- for func in intf.functions.values() %] - virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_name]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; +[%- for name, attr in intf.attributes.items() %] + virtual [[intf_name(attr.type)]]* get_[[name]]() = 0; [%- endfor %] -[%- for func in intf.functions.values() %] -[%- for k, arg in func.in.items() | skip_first %] - [[arg.type.c_name]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } - template static void get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } +}; +[%- endif %] +[% endfor %] + + +[% for intf in interfaces.values() %] +[%- if not intf.builtin %] +template +struct [[wrapper_name(intf)]] : [[intf_name(intf)]] { +[%- for name, attr in intf.attributes.items() %] + template().[[name]]_)> static constexpr bool Has[[name | to_pascal_case]](int) { return true; } + template static constexpr bool Has[[name | to_pascal_case]](...) { return false; } [%- endfor %] -[%- for k, arg in func.out.items() %] - [[arg.type.c_name]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } - template static void get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } +[%- for name, func in intf.functions.items() %] + template().[[name]]([% for name, in in func.in.items() %][% if loop.index0 %]std::declval<[[type_name(in.type)]]>()[[', ' if not loop.last]][% endif %][% endfor %]))> static constexpr bool Has[[name | to_pascal_case]](int) { return true; } + template static constexpr bool Has[[name | to_pascal_case]](...) { return false; } [%- endfor %] + + [[wrapper_name(intf)]](T& impl) : impl_(&impl) {} +[% for name, attr in intf.attributes.items() %] + virtual [[intf_name(attr.type)]]* get_[[name]]() final { + static_assert(Has[[name | to_pascal_case]](0), "T cannot be cast to interface [[intf.name.get_fibre_name()]]: No member [[name]]_. See details below."); + return &[[name]]_; + } +[% endfor %][% for name, func in intf.functions.items() %] + [[rettype(func)]] [[name]]([% for name, in in func.in.items() %][% if loop.index0 %][[type_name(in.type)]] [[name]][[', ' if not loop.last]][% endif %][% endfor %]) final { + static_assert(Has[[name | to_pascal_case]](0), "T cannot be cast to interface [[intf.name.get_fibre_name()]]: No matching overload for [[name]](). See details below."); + return impl_->[[name]]([% for name in func.in.keys() %][% if loop.index0 %][[name]][[', ' if not loop.last]][% endif %][% endfor %]); + } +[% endfor %] + T* impl_; +[%- for name, attr in intf.attributes.items() %] +[%- if attr.type.builtin %] + [[wrapper_name(attr.type)]] [[name]]_{&impl_->[[name]]_}; +[%- else %] + [[wrapper_name(attr.type)]][[name]]_)> [[name]]_{impl_->[[name]]_}; +[%- endif %] [%- endfor %] }; -[%- endmacro %] - -[% for intf in toplevel_interfaces %] -[[render_interface(intf)]] +[%- endif %] [% endfor %] [%- for _, enum in value_types.items() %] @@ -97,6 +129,4 @@ inline [[enum.c_name]] operator ~ ([[enum.c_name]] a) { return static_cast<[[enu -#pragma GCC pop_options - #endif // __FIBRE_INTERFACES_HPP \ No newline at end of file diff --git a/cpp/legacy_endpoints_template.j2 b/cpp/legacy_endpoints_template.j2 new file mode 100644 index 0000000..aa8678a --- /dev/null +++ b/cpp/legacy_endpoints_template.j2 @@ -0,0 +1,30 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains the toplevel handler for Fibre v0.1 endpoint operations. + * + * This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of + * a function-oriented approach and a more powerful object model. + * + */ + +#include +#include +#include + +namespace fibre { + +const unsigned char embedded_json[] = [[endpoint_descr | to_c_string]]; +const size_t embedded_json_length = sizeof(embedded_json) - 1; + +static EndpointDefinition endpoint_table[] = { +[%- for ep in endpoint_table %] + [[ep]], +[%- endfor %] +}; + + +} diff --git a/cpp/legacy_object_server.cpp b/cpp/legacy_object_server.cpp new file mode 100644 index 0000000..9c9a7d4 --- /dev/null +++ b/cpp/legacy_object_server.cpp @@ -0,0 +1,248 @@ + +#include "legacy_object_server.hpp" +#include "codecs.hpp" +#include +#include "logging.hpp" +#include + +DEFINE_LOG_TOPIC(LEGACY_OBJ); +USE_LOG_TOPIC(LEGACY_OBJ); + +namespace fibre { + +enum class EndpointType { + kFunctionTrigger, + kFunctionInput, + kFunctionOutput, + kRoProperty, + kRwProperty, +}; + +struct EndpointDefinition { + EndpointType type; + union { + struct { + ServerFunctionId function_id; + ServerObjectId object_id; + } function_trigger; + struct { + unsigned size; + } function_input; + struct { + unsigned size; + } function_output; + struct { + ServerFunctionId read_function_id; + ServerObjectId object_id; + } ro_property; + struct { + ServerFunctionId read_function_id; + ServerFunctionId exchange_function_id; + ServerObjectId object_id; + } rw_property; + }; +}; + +} + +// TODO: don't hardcode path +#include "../test/autogen/endpoints.hpp" + +namespace fibre { + +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); +static constexpr size_t n_endpoints = sizeof(endpoint_table) / sizeof(endpoint_table[0]); + + + +// Returns part of the JSON interface definition. +bool endpoint0_handler(cbufptr_t* input_buffer, bufptr_t* output_buffer) { + // The request must contain a 32 bit integer to specify an offset + std::optional offset = read_le(input_buffer); + + if (!offset.has_value()) { + // Didn't receive any offset + return false; + } else if (*offset == 0xffffffff) { + // If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead + return write_le(json_version_id_, output_buffer); + } else if (*offset >= embedded_json_length) { + // Attempt to read beyond the buffer end - return empty response + return true; + } else { + // Return part of the json file + size_t n_copy = std::min(output_buffer->size(), embedded_json_length - (size_t)*offset); + memcpy(output_buffer->begin(), embedded_json + *offset, n_copy); + *output_buffer = output_buffer->skip(n_copy); + return true; + } +} + +bool LegacyObjectServer::endpoint_handler(Domain* domain, int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { + if (idx < 0 || idx >= n_endpoints) { + return false; + } + + if (idx == 0) { + return endpoint0_handler(input_buffer, output_buffer); + } + + EndpointDefinition& ep = endpoint_table[idx]; + + if (ep.type == EndpointType::kRoProperty || ep.type == EndpointType::kRwProperty) { + if (ep.type == EndpointType::kRoProperty && input_buffer->size() != 0) { + return false; // size mismatch + } + + ServerObjectId obj = ep.type == EndpointType::kRoProperty ? ep.ro_property.object_id : ep.rw_property.object_id; + ServerFunctionId fn = ep.type == EndpointType::kRoProperty ? ep.ro_property.read_function_id : + input_buffer->size() ? ep.rw_property.exchange_function_id : ep.rw_property.read_function_id; + + // Wrate object ID into RX buf as first argument + bufptr_t outbuf{rx_buf_}; + // TODO: this codec doesn't work like that + fibre::Codec::encode(obj, &outbuf); + rx_pos_ = outbuf.begin() - rx_buf_; + + // Write argument into RX buf as second argument + size_t n_copy = std::min(input_buffer->size(), sizeof(rx_buf_) - rx_pos_); + std::copy_n(input_buffer->begin(), n_copy, rx_buf_ + rx_pos_); + rx_pos_ += n_copy; + *input_buffer = input_buffer->skip(n_copy); + + // invoke function + auto* func = domain->get_server_function(fn); + if (!func) { + return false; + } + + uint8_t call_frame[256]; + std::optional call_buffer_release = func->impl.invoke( + domain, + true, // start + call_frame, + {kFibreClosed, {rx_buf_, rx_pos_}, *output_buffer}, // call buffers + {} // continuation + ); + + if (!call_buffer_release.has_value()) { + FIBRE_LOG(W) << "legacy protocol used to call function that did not return synchronously"; + return false; + } + + *output_buffer = output_buffer->skip(call_buffer_release->tx_end - output_buffer->begin()); + + if (call_buffer_release->status != kFibreClosed) { + FIBRE_LOG(W) << "legacy protocol return error " << call_buffer_release->status << " but legacy protocol does not support error reporting"; + return false; + } + + return true; + + } else { + // This endpoint access is part of a function call + + if (idx != expected_ep_) { + reset(); // reset function call state + + // Find the end of the function and determine its arg sizes + for (size_t i = idx + 1; i < n_endpoints; ++i) { + if (endpoint_table[i].type == EndpointType::kFunctionInput) { + n_inputs_++; + } else if (endpoint_table[i].type == EndpointType::kFunctionOutput) { + output_size_ += endpoint_table[i].function_output.size; + n_outputs_++; + } else { + break; + } + } + + bool correct_first_ep_access = + (ep.type == EndpointType::kFunctionTrigger && n_inputs_ == 0) // functions with no in args + || (ep.type == EndpointType::kFunctionInput && endpoint_table[idx - 1].type == EndpointType::kFunctionTrigger); // functions with some in args + + if (!correct_first_ep_access) { + return false; + } + + trigger_ep_ = ep.type == EndpointType::kFunctionTrigger ? idx : (idx - 1); + + if (ep.type != EndpointType::kFunctionTrigger) { + n_inputs_++; + } + + // Wrate object ID into RX buf as first argument + bufptr_t outbuf{rx_buf_}; + // TODO: this codec doesn't work like that + fibre::Codec::encode(endpoint_table[trigger_ep_].function_trigger.object_id, &outbuf); + rx_pos_ = outbuf.begin() - rx_buf_; + } + + + if (ep.type == EndpointType::kFunctionInput) { + if (input_buffer->size() != ep.function_input.size || output_buffer->size() != 0) { + return false; // size mismatch + } + + // Copy input buffer into scratch buffer + std::copy_n(input_buffer->begin(), input_buffer->size(), rx_buf_ + rx_pos_); + rx_pos_ += input_buffer->size(); + *input_buffer = input_buffer->skip(input_buffer->size()); + + // advance progress (to next input or trigger ep) + expected_ep_ = (idx + 1 - trigger_ep_) % (n_inputs_ + 1) + trigger_ep_; + + } else if (ep.type == EndpointType::kFunctionTrigger) { + if (input_buffer->size() != 0 || output_buffer->size() != 0) { + return false; // size mismatch + } + + // invoke function + auto* func = domain->get_server_function(ep.function_trigger.function_id); + if (!func) { + return false; + } + + uint8_t call_frame[256]; + std::optional call_buffer_release = func->impl.invoke( + domain, + true, // start + call_frame, + {kFibreClosed, {rx_buf_, rx_pos_}, {tx_buf_, output_size_}}, // call buffers + {} // continuation + ); + + if (!call_buffer_release.has_value()) { + FIBRE_LOG(W) << "legacy protocol used to call function that did not return synchronously"; + return false; + } + + if (call_buffer_release->status != kFibreClosed) { + FIBRE_LOG(W) << "legacy protocol return error " << call_buffer_release->status << " but legacy protocol does not support error reporting"; + return false; + } + + // advance progress (to first output ep or to 0 if there are no outputs) + expected_ep_ = (trigger_ep_ + n_inputs_ + 1) % (trigger_ep_ + n_inputs_ + 1 + n_outputs_); + + } else if (ep.type == EndpointType::kFunctionOutput) { + // copy to output buffer + if (input_buffer->size() != 0 || output_buffer->size() != ep.function_output.size) { + return false; // size mismatch + } + + // Copy scratch buffer into output buffer + std::copy_n(tx_buf_ + tx_pos_, output_buffer->size(), output_buffer->begin()); + tx_pos_ += output_buffer->size(); + *output_buffer = output_buffer->skip(output_buffer->size()); + + // advance progress (to next output or 0) + expected_ep_ = (idx + 1) % (trigger_ep_ + n_inputs_ + 1 + n_outputs_); + } + + return true; + } +} + +} diff --git a/cpp/legacy_object_server.hpp b/cpp/legacy_object_server.hpp new file mode 100644 index 0000000..bbe7752 --- /dev/null +++ b/cpp/legacy_object_server.hpp @@ -0,0 +1,43 @@ +#ifndef __LEGACY_OBJECT_SERVER_HPP +#define __LEGACY_OBJECT_SERVER_HPP + +#include +#include +#include + +namespace fibre { + +class Domain; + +struct LegacyObjectServer { + uint8_t rx_buf_[128]; + size_t rx_pos_; + uint8_t tx_buf_[128]; + size_t tx_pos_; + size_t expected_ep_ = 0; // 0 while no call in progress + size_t trigger_ep_; + size_t n_inputs_; + size_t n_outputs_; + size_t output_size_; + + uint8_t call_state_[256]; + + void reset() { + rx_pos_ = 0; + tx_pos_ = 0; + expected_ep_ = 0; + trigger_ep_ = 0; + n_inputs_ = 0; + n_outputs_ = 0; + output_size_ = 0; + } + + bool endpoint_handler(Domain* domain, int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer); +}; + +extern const uint16_t json_crc_; +extern const uint32_t json_version_id_; + +} + +#endif // __LEGACY_OBJECT_SERVER_HPP diff --git a/cpp/legacy_protocol.cpp b/cpp/legacy_protocol.cpp index 7afd137..2c2933f 100644 --- a/cpp/legacy_protocol.cpp +++ b/cpp/legacy_protocol.cpp @@ -292,33 +292,6 @@ void LegacyProtocolPacketBased::cancel_endpoint_operation(EndpointOperationHandl #endif -#if FIBRE_ENABLE_SERVER - -// Returns part of the JSON interface definition. -bool fibre::endpoint0_handler(fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { - // The request must contain a 32 bit integer to specify an offset - std::optional offset = read_le(input_buffer); - - if (!offset.has_value()) { - // Didn't receive any offset - return false; - } else if (*offset == 0xffffffff) { - // If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead - return write_le(json_version_id_, output_buffer); - } else if (*offset >= embedded_json_length) { - // Attempt to read beyond the buffer end - return empty response - return true; - } else { - // Return part of the json file - size_t n_copy = std::min(output_buffer->size(), embedded_json_length - (size_t)*offset); - memcpy(output_buffer->begin(), embedded_json + *offset, n_copy); - *output_buffer = output_buffer->skip(n_copy); - return true; - } -} - -#endif - void LegacyProtocolPacketBased::on_write_finished(WriteResult result) { tx_handle_ = 0; @@ -487,9 +460,9 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { if (expected_response_length > tx_mtu_ - 2) expected_response_length = tx_mtu_ - 2; - fibre::cbufptr_t input_buffer{rx_buf.begin(), rx_buf.end() - 2}; - fibre::bufptr_t output_buffer{tx_buf_ + 2, expected_response_length}; - fibre::endpoint_handler(endpoint_id, &input_buffer, &output_buffer); + cbufptr_t input_buffer{rx_buf.begin(), rx_buf.end() - 2}; + bufptr_t output_buffer{tx_buf_ + 2, expected_response_length}; + server_.endpoint_handler(domain_, endpoint_id, &input_buffer, &output_buffer); // Send response if (expect_response) { diff --git a/cpp/legacy_protocol.hpp b/cpp/legacy_protocol.hpp index 962f412..68c153b 100644 --- a/cpp/legacy_protocol.hpp +++ b/cpp/legacy_protocol.hpp @@ -9,6 +9,9 @@ #include #include #endif +#if FIBRE_ENABLE_SERVER +#include "legacy_object_server.hpp" +#endif namespace fibre { @@ -90,11 +93,12 @@ class PacketUnwrapper : public AsyncStreamSource { struct LegacyProtocolPacketBased { public: - LegacyProtocolPacketBased(AsyncStreamSource* rx_channel, AsyncStreamSink* tx_channel, size_t tx_mtu) - : rx_channel_(rx_channel), tx_channel_(tx_channel), tx_mtu_(std::min(tx_mtu, sizeof(tx_buf_))) {} + LegacyProtocolPacketBased(Domain* domain, AsyncStreamSource* rx_channel, AsyncStreamSink* tx_channel, size_t tx_mtu) + : domain_(domain), rx_channel_(rx_channel), tx_channel_(tx_channel), tx_mtu_(std::min(tx_mtu, sizeof(tx_buf_))) {} - AsyncStreamSource* rx_channel_ = nullptr; - AsyncStreamSink* tx_channel_ = nullptr; + Domain* domain_; + AsyncStreamSource* rx_channel_; + AsyncStreamSink* tx_channel_; size_t tx_mtu_; uint8_t tx_buf_[128]; uint8_t rx_buf_[128]; @@ -114,6 +118,10 @@ struct LegacyProtocolPacketBased { LegacyObjectClient client_{this}; #endif +#if FIBRE_ENABLE_SERVER + LegacyObjectServer server_; +#endif + #if FIBRE_ENABLE_CLIENT void start(Callback> on_found_root_object, Callback> on_lost_root_object, Callback on_stopped); #else @@ -150,8 +158,8 @@ struct LegacyProtocolPacketBased { struct LegacyProtocolStreamBased { public: - LegacyProtocolStreamBased(AsyncStreamSource* rx_channel, AsyncStreamSink* tx_channel) - : unwrapper_(rx_channel), wrapper_(tx_channel) {} + LegacyProtocolStreamBased(Domain* domain, AsyncStreamSource* rx_channel, AsyncStreamSink* tx_channel) + : unwrapper_(rx_channel), wrapper_(tx_channel), inner_protocol_{domain, &unwrapper_, &wrapper_, 127} {} #if FIBRE_ENABLE_CLIENT @@ -165,7 +173,7 @@ struct LegacyProtocolStreamBased { private: PacketUnwrapper unwrapper_; PacketWrapper wrapper_; - LegacyProtocolPacketBased inner_protocol_{&unwrapper_, &wrapper_, 127}; + LegacyProtocolPacketBased inner_protocol_; }; } diff --git a/cpp/package.lua b/cpp/package.lua index 599e85b..ce1fe98 100644 --- a/cpp/package.lua +++ b/cpp/package.lua @@ -92,6 +92,9 @@ function get_fibre_package(args) if args.enable_client then pkg.code_files += 'legacy_object_client.cpp' end + if args.enable_server then + pkg.code_files += 'legacy_object_server.cpp' + end if args.enable_client or args.enable_server then pkg.code_files += 'legacy_protocol.cpp' end diff --git a/cpp/platform_support/posix_socket.cpp b/cpp/platform_support/posix_socket.cpp index 53475fc..fe34937 100644 --- a/cpp/platform_support/posix_socket.cpp +++ b/cpp/platform_support/posix_socket.cpp @@ -12,6 +12,7 @@ #include #include #include +#include DEFINE_LOG_TOPIC(SOCKET); USE_LOG_TOPIC(SOCKET); @@ -69,7 +70,7 @@ struct fibre::AddressResolutionContext { struct gaicb gaicb{}; struct gaicb* list[1]; - void on_gai_completed(); + void on_gai_completed(uint32_t); }; bool fibre::start_resolving_address(EventLoop* event_loop, std::tuple address, bool passive, AddressResolutionContext** handle, Callback> callback) { @@ -106,10 +107,18 @@ bool fibre::start_resolving_address(EventLoop* event_loop, std::tupleevent_loop->post(MEMBER_CB(ctx, on_gai_completed)); + auto ctx = ((AddressResolutionContext*)sigval.sival_ptr); + const uint64_t val = 1; + write(ctx->cmpl_fd, &val, sizeof(val)); }; + + // Note: we can't use event_loop->post from the other thread because in the + // meantime the ref count of the event loop might have gone to zero. + + ctx->cmpl_fd = eventfd(0, 0); + event_loop->register_event(ctx->cmpl_fd, EPOLLIN, MEMBER_CB(ctx, on_gai_completed)); + FIBRE_LOG(D) << "starting address resolution for " << ctx->address_str; if (getaddrinfo_a(GAI_NOWAIT, ctx->list, 1, &sig) != 0) { FIBRE_LOG(E) << "getaddrinfo_a() failed"; @@ -124,7 +133,11 @@ void fibre::cancel_resolving_address(AddressResolutionContext* handle) { gai_cancel(&handle->gaicb); } -void AddressResolutionContext::on_gai_completed() { +void AddressResolutionContext::on_gai_completed(uint32_t) { + if (!event_loop->deregister_event(cmpl_fd)) { + FIBRE_LOG(W) << "failed to deregister event"; + } + FIBRE_LOG(D) << "address resolution complete"; if (gai_error(&gaicb) != 0) { FIBRE_LOG(W) << "failed to resolve " << address_str << ": " << sys_err(); diff --git a/cpp/property.hpp b/cpp/property.hpp new file mode 100644 index 0000000..f121f91 --- /dev/null +++ b/cpp/property.hpp @@ -0,0 +1,49 @@ +#ifndef __FIBRE_PROPERTY_HPP +#define __FIBRE_PROPERTY_HPP + +namespace fibre { + +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; } + + T read() const { + return (*getter_)(ctx_); + } + + T exchange(T value) const { + T old_value = (*getter_)(ctx_); + (*setter_)(ctx_, value); + return old_value; + } + + void* ctx_; + T(*getter_)(void*); + void(*setter_)(void*, T); +}; + +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; } + + T read() const { + return (*getter_)(ctx_); + } + + void* ctx_; + T(*getter_)(void*); +}; + +} + +#endif // __FIBRE_PROPERTY_HPP diff --git a/cpp/protocol.hpp b/cpp/protocol.hpp index f37d3c1..5f072d7 100644 --- a/cpp/protocol.hpp +++ b/cpp/protocol.hpp @@ -24,94 +24,6 @@ typedef struct { } endpoint_ref_t; -namespace fibre { -// These symbols are defined in the autogenerated endpoints.hpp -extern const unsigned char embedded_json[]; -extern const size_t embedded_json_length; -extern const uint16_t json_crc_; -extern const uint32_t json_version_id_; -bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer); -bool endpoint0_handler(cbufptr_t* input_buffer, bufptr_t* output_buffer); -bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref); -bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value); -} - - - -namespace fibre { -template -struct Codec { - static std::optional decode(cbufptr_t* buffer) { return std::nullopt; } -}; - -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return (buffer->begin() == buffer->end()) ? std::nullopt : std::make_optional((bool)*(buffer->begin()++)); } - static bool encode(bool value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(int8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(uint8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(int16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(uint16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(int32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(uint32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(int64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } - static bool encode(uint64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { - std::optional int_val = Codec::decode(buffer); - return int_val.has_value() ? std::optional(*reinterpret_cast(&*int_val)) : std::nullopt; - } - static bool encode(float value, bufptr_t* buffer) { - void* ptr = &value; - return Codec::encode(*reinterpret_cast(ptr), buffer); - } -}; -template -struct Codec::value>> { - using int_type = std::underlying_type_t; - static std::optional decode(cbufptr_t* buffer) { - std::optional int_val = SimpleSerializer::read(&(buffer->begin()), buffer->end()); - return int_val.has_value() ? std::make_optional(static_cast(*int_val)) : std::nullopt; - } - static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } -}; -template<> struct Codec { - static std::optional decode(cbufptr_t* buffer) { - std::optional val0 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); - std::optional val1 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); - return (val0.has_value() && val1.has_value()) ? std::make_optional(endpoint_ref_t{*val1, *val0}) : std::nullopt; - } - static bool encode(endpoint_ref_t value, bufptr_t* buffer) { - return SimpleSerializer::write(value.endpoint_id, &(buffer->begin()), buffer->end()) - && SimpleSerializer::write(value.json_crc, &(buffer->begin()), buffer->end()); - } -}; -} - /* ToString / FromString functions -------------------------------------------*/ /* @@ -254,48 +166,5 @@ bool set_from_float(float value, T* property) { } -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; } - - T read() const { - return (*getter_)(ctx_); - } - - T exchange(std::optional value) const { - T old_value = (*getter_)(ctx_); - if (value.has_value()) { - (*setter_)(ctx_, *value); - } - return old_value; - } - - void* ctx_; - T(*getter_)(void*); - void(*setter_)(void*, T); -}; - -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; } - - T read() const { - return (*getter_)(ctx_); - } - - void* ctx_; - T(*getter_)(void*); -}; - #endif diff --git a/cpp/static_exports.hpp b/cpp/static_exports.hpp new file mode 100644 index 0000000..44a59c9 --- /dev/null +++ b/cpp/static_exports.hpp @@ -0,0 +1,22 @@ +#ifndef __STATIC_EXPORTS_HPP +#define __STATIC_EXPORTS_HPP + +#include + +#if FIBRE_ENABLE_SERVER +namespace fibre { + +// Forward declarations for autogenerated code in static_exports.cpp +template ServerInterfaceId get_interface_id(); + +extern ServerFunctionDefinition static_server_function_table[]; +extern size_t n_static_server_functions; +//extern ServerInterfaceDefinition static_server_interface_table[]; + +extern ServerObjectDefinition static_server_object_table[]; +extern size_t n_static_server_objects; + +} +#endif + +#endif // __STATIC_EXPORTS_HPP diff --git a/cpp/static_exports_template.j2 b/cpp/static_exports_template.j2 new file mode 100644 index 0000000..de0d06e --- /dev/null +++ b/cpp/static_exports_template.j2 @@ -0,0 +1,39 @@ + +#include +#include "interfaces.hpp" + +using namespace fibre; + +[%- macro intf_name(intf) %] +[%- if intf.builtin %]fibre::Property<[['const ' if intf.mode == 'readonly']][[type_name(intf.value_type)]]> +[%- else %] +[%- for elem in intf.name %][[elem | to_pascal_case]][['_' if not loop.last]][% endfor %]Intf +[%- endif %] +[%- endmacro %] + +[%- macro type_name(type) %] +[%- if type.c_name %][[type.c_name]][% else %]unknown name for [[type]][% endif %] +[%- endmacro %] + +ServerFunctionDefinition fibre::static_server_function_table[] = { +[%- for func in exported_functions %] + { MEMBER_CB((&sync_member_func_wrapper), entrypoint) }, +[%- endfor %] +}; +[% for intf in exported_interfaces %] +template<> ServerInterfaceId fibre::get_interface_id<[[intf_name(intf)]]>() { return [[intf.id]]; }; +[%- endfor %] + +// Must be defined by the application +[%- for obj in published_objects %] +extern [[intf_name(obj.type)]]* [[obj.name[0]]]_ptr; +[%- endfor %] + +ServerObjectDefinition fibre::static_server_object_table[] = { +[%- for name, obj in exported_objects.items() %] + make_obj([[obj.name[0]]]_ptr[% for elem in obj.name[1:] %]->get_[[elem]]()[% endfor %]), +[%- endfor %] +}; + +size_t fibre::n_static_server_functions = [[exported_functions | length]]; +size_t fibre::n_static_server_objects = [[exported_objects | length]]; diff --git a/python/fibre/libfibre.py b/python/fibre/libfibre.py index e0c0f2e..4eeca11 100644 --- a/python/fibre/libfibre.py +++ b/python/fibre/libfibre.py @@ -935,7 +935,9 @@ def _start_discovery(self): async def _discover_one(self): discovery = self._start_discovery() obj = await discovery._next() - discovery._stop() + # TODO: this would tear down all discovered objects. Need to think about + # how to implement this properly (object ref count?). + #discovery._stop() return obj def discover_one(self): diff --git a/test/Tupfile.lua b/test/Tupfile.lua index 50bdd4c..b6bf288 100644 --- a/test/Tupfile.lua +++ b/test/Tupfile.lua @@ -8,10 +8,9 @@ interface_generator = '../tools/interface_generator.py' interface_yaml = 'test-interface.yaml' root_interface = 'TestIntf1' +tup.frule{inputs={'../cpp/static_exports_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/static_exports.cpp'} tup.frule{inputs={'../cpp/interfaces_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/interfaces.hpp'} -tup.frule{inputs={'../cpp/function_stubs_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/function_stubs.hpp'} -tup.frule{inputs={'../cpp/endpoints_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --generate-endpoints '..root_interface..' --template %f --output %o', outputs='autogen/endpoints.hpp'} ---tup.frule{inputs={'../cpp/type_info_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/type_info.hpp'} +tup.frule{inputs={'../cpp/legacy_endpoints_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/endpoints.hpp'} CXX='clang++' @@ -22,7 +21,7 @@ LDFLAGS={} function compile(src_file) obj_file = 'build/'..tup.file(src_file)..'.o' tup.frule{ - inputs={src_file, extra_inputs={'autogen/interfaces.hpp', 'autogen/endpoints.hpp', 'autogen/function_stubs.hpp'}}, + inputs={src_file, extra_inputs={'autogen/interfaces.hpp', 'autogen/endpoints.hpp'}}, command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', outputs={obj_file} } @@ -48,6 +47,8 @@ end object_files = {} object_files += compile('test_server.cpp') +object_files += compile('autogen/static_exports.cpp') +--object_files += compile('new_autogen/endpoints_new.cpp') for _, src in pairs(fibre_pkg.code_files) do object_files += compile(fibre_cpp_dir..'/'..src, fibre_cpp_dir..'/'..src..'.o') diff --git a/test/test-interface.yaml b/test/test-interface.yaml index ec01423..b9cc529 100644 --- a/test/test-interface.yaml +++ b/test/test-interface.yaml @@ -16,6 +16,12 @@ interfaces: prop_uint32: readonly uint32 prop_uint32_rw: uint32 #prop_uint32: readonly uint32 + subobj: + c_is_class: True + functions: + subfunc: {out: {out1: uint32}} + #attributes: + # parent: TestIntf1 functions: func00: func01: {out: {out1: uint32}} @@ -26,3 +32,6 @@ interfaces: func20: {in: {in1: uint32, in2: uint32}} func21: {in: {in1: uint32, in2: uint32}, out: {out1: uint32}} func22: {in: {in1: uint32, in2: uint32}, out: {out1: uint32, out2: uint32}} + +exports: + test_object: TestIntf1 diff --git a/test/test_client.py b/test/test_client.py index 2af3221..95c730b 100755 --- a/test/test_client.py +++ b/test/test_client.py @@ -6,12 +6,19 @@ with fibre.Domain("tcp-client:address=localhost,port=14220") as domain: obj = domain.discover_one() + assert(obj.prop_uint32 == 135) + assert(obj.prop_uint32 == 135) + assert(obj.prop_uint32_rw == 246) + assert(obj.prop_uint32_rw == 246) + obj.prop_uint32_rw = 789 + assert(obj.prop_uint32_rw == 789) obj.func00() - obj.func01() - obj.func02() + assert(obj.func01() == 123) + assert(obj.subobj.subfunc() == 321) + assert(obj.func02() == (456, 789)) obj.func10(1) - obj.func11(1) - obj.func12(1) + assert(obj.func11(1) == 123) + assert(obj.func12(1) == (456, 789)) obj.func20(1, 2) - obj.func21(1, 2) - obj.func22(1, 2) + assert(obj.func21(1, 2) == 123) + assert(obj.func22(1, 2) == (456, 789)) diff --git a/test/test_server.cpp b/test/test_server.cpp index 8bd4de0..3f935ab 100644 --- a/test/test_server.cpp +++ b/test/test_server.cpp @@ -1,14 +1,26 @@ -#include "autogen/interfaces.hpp" // TODO: remove this include +#include "autogen/interfaces.hpp" +#include #include +#include #include #include #include -class TestClass : public TestIntf1Intf { +class Subclass { public: - uint32_t prop_uint32_; - uint32_t prop_uint32_rw_; + uint32_t subfunc() { + std::cout << "subfunc called" << std::endl; + return 321; + } +}; + +class TestClass { +public: + Subclass subobj_; + + uint32_t prop_uint32_ = 135; + uint32_t prop_uint32_rw_ = 246; void func00() { std::cout << "func00 called" << std::endl; @@ -21,7 +33,7 @@ class TestClass : public TestIntf1Intf { std::tuple func02() { std::cout << "func02 called" << std::endl; - return {123, 123}; + return {456, 789}; } void func10(uint32_t) { @@ -35,7 +47,7 @@ class TestClass : public TestIntf1Intf { std::tuple func12(uint32_t) { std::cout << "func12 called" << std::endl; - return {123, 123}; + return {456, 789}; } void func20(uint32_t, uint32_t) { @@ -49,12 +61,15 @@ class TestClass : public TestIntf1Intf { std::tuple func22(uint32_t, uint32_t) { std::cout << "func22 called" << std::endl; - return {123, 123}; + return {456, 789}; } }; TestClass test_object; +TestIntf1Wrapper test_object_wrapper{test_object}; +TestIntf1Intf* test_object_ptr = &test_object_wrapper; + int main() { printf("Starting Fibre server...\n"); @@ -62,15 +77,12 @@ int main() { printf("Hello from event loop...\n"); auto fibre_ctx = fibre::open(event_loop); fibre_ctx->create_domain("tcp-server:address=localhost,port=14220"); + // auto domain = + // fibre_ctx->create_domain("tcp-server:address=localhost,port=14220"); + // domain->publish_function(); }); printf("test server terminated %s\n", ok ? "nominally" : "with an error"); return ok ? 0 : 1; } - -#include "autogen/function_stubs.hpp" -#include -#include -TestClass& ep_root = test_object; -#include "autogen/endpoints.hpp" diff --git a/tools/interface-definition-file.md b/tools/interface-definition-file.md new file mode 100644 index 0000000..267c560 --- /dev/null +++ b/tools/interface-definition-file.md @@ -0,0 +1,60 @@ + +## Interface Definition File Structure + +This section describes the structure of Fibre interface definition YAML files. Use this as reference if you're writing YAML files for Fibre. + +**TODO** + + +## Template API + +This section describes what data is available to a template that is run through the `interface_generator.py`. Use this as reference if you're writing template files. + +### Globals + + - `interfaces` (`list`): List of all interfaces defined in the input file(s) including on-demand generated interfaces (e.g. `fibre.Property`) + - `enums` (`list`): List of all enums defined in the input file(s) + - `exported_functions` (`list`): List of all statically exported functions + - `exported_interfaces` (`list`): List of all statically exported interfaces + - `exported_objects` (`list`): Expanded list of all statically exported objects that should be directly addressable by object ID. + - `published_objects` (`list`): List of toplevel published objects. This is a subset of `exported_objects`. + +### `interface` object + - `name` (`name_info`): Name of the interface. + - `id` (`int`): If this interface is statically exported, this is the ID of the interface. + - `functions` (`dict`): Functions implemented by the interface + - `attributes` (`dict`): Attributes implemented by the interface + +### `enum` object + - `name` (`name_info`): Name of the enum + - `is_flags` (`bool`): Indicates if this enum is a bit field or not + - `nullflag` (`string`): Name of the enumerator that represents the absence of any flags (only present if `is_flags == true`) + - `values` (`list`): Values of the enum + +### `enumerator` object + - `name` (`string`): Name of the enumerator value + - `value` (`int`): Value of the enumerator value + +### `name_info` object + - `get_fibre_name() -> string`: Returns a string of the full name in canonical Fibre notation + +### `function` object + - `name` (`name_info`): Full name of the function including the interface. + - `id` (`int`): If this function is statically exported, this is the ID of the function. + - `intf` (`interface`): The interface that contains this function. + - `in` (`dict`): Input arguments + - `out` (`dict`): Output arguments + +### `argument` object + - `type` (`value_type`): Type of the argument + +### `value_type` object + - `name` (`name_info`): Name of the value type + - `c_type` (`string`): C name of the data type (only present for basic value types, not for enums) + +### `attribute` object + - `name` (`name_info`): Name of the attribute + - `type` (`interface`): Interface implemented by the attribute + +### `object` object + - `name` (`name_info`): Full name of the object including its parent objects diff --git a/tools/interface_generator.py b/tools/interface_generator.py index 517662f..e9a35c4 100644 --- a/tools/interface_generator.py +++ b/tools/interface_generator.py @@ -111,6 +111,9 @@ valuetypes: type: object additionalProperties: { "$ref": "#/definitions/valuetype" } + exports: + type: object + additionalProperties: { "$ref": "#/definitions/intf_type_ref" } userdata: type: object __line__: {type: object} @@ -160,7 +163,9 @@ def join_name(*names, delimiter: str = '.'): Joins two name components. e.g. 'io.helloworld' + 'sayhello' => 'io.helloworld.sayhello' """ - return delimiter.join(y for x in names for y in x.split(delimiter) if y != '') + + #return delimiter.join(y for x in names for y in x.split(delimiter) if y != '') + return NameInfo(*names) def split_name(name, delimiter: str = '.'): def replace_delimiter_in_parentheses(): @@ -176,42 +181,31 @@ def to_macro_case(s): return '_'.join(get_words(s)).upper() def to_snake_case(s): return '_'.join(get_words(s)).lower() def to_kebab_case(s): return '-'.join(get_words(s)).lower() -value_types = OrderedDict({ - 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_name': 'bool', 'py_type': 'bool'}, - 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_name': 'float', 'py_type': 'float'}, - 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_name': 'uint8_t', 'py_type': 'int'}, - 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_name': 'uint16_t', 'py_type': 'int'}, - 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_name': 'uint32_t', 'py_type': 'int'}, - 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_name': 'uint64_t', 'py_type': 'int'}, - 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_name': 'int8_t', 'py_type': 'int'}, - 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_name': 'int16_t', 'py_type': 'int'}, - 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_name': 'int32_t', 'py_type': 'int'}, - 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_name': 'int64_t', 'py_type': 'int'}, - 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_name': 'endpoint_ref_t', 'py_type': '[not implemented]'}, -}) -enums = OrderedDict() -interfaces = OrderedDict() -userdata = OrderedDict() # Arbitrary data passed from the definition file to the template +class NameInfo(): + def __init__(self, *parts): + flat_parts = [] + for p in parts: + if isinstance(p, NameInfo): + flat_parts += list(p) + elif isinstance(p, str): + flat_parts += split_name(p) + else: + raise Exception("unexpected type " + str(type(p))) + self.__parts = flat_parts -def make_ref_type(interface): - name = 'Ref<' + interface.fullname + '>' - fullname = join_name('fibre', name) - if fullname in interfaces: - return interfaces[fullname] - - ref_type = { - 'builtin': True, - 'name': name, - 'fullname': fullname, - 'c_name': interface.fullname.replace('.', 'Intf::') + 'Intf*' - } - value_types[fullname] = ref_type + def __getitem__(self, index): + return self.__parts[index] - return ref_type + def __len__(self): + return len(self.__parts) + + def __repr__(self): + return "Fibre Name \"" + self.get_fibre_name() + "\"" + + def get_fibre_name(self): + return ".".join((n for n in self)) -def get_dict(elem, key): - return elem.get(key, None) or OrderedDict() class ArgumentElement(): def __init__(self, path, name, elem): @@ -219,58 +213,9 @@ def __init__(self, path, name, elem): elem = {} elif isinstance(elem, str): elem = {'type': elem} - self.name = name - self.fullname = path = join_name(path, name) - self.type = regularize_valuetype(path, name, elem['type']) - -def regularize_func(path, name, elem, prepend_args): - if elem is None: - elem = {} - elem['name'] = name - elem['fullname'] = path = join_name(path, name) - elem['in'] = OrderedDict((n, ArgumentElement(path, n, arg)) - for n, arg in (*prepend_args.items(), *get_dict(elem, 'in').items())) - elem['out'] = OrderedDict((n, ArgumentElement(path, n, arg)) - for n, arg in get_dict(elem, 'out').items()) - return elem - -def regularize_attribute(parent, name, elem, c_is_class): - if elem is None: - elem = {} - if isinstance(elem, str): - elem = {'type': elem} - elif not 'type' in elem: - elem['type'] = {} - if 'attributes' in elem: elem['type']['attributes'] = elem.pop('attributes') - if 'functions' in elem: elem['type']['functions'] = elem.pop('functions') - if 'implements' in elem: elem['type']['implements'] = elem.pop('implements') - if 'c_is_class' in elem: elem['type']['c_is_class'] = elem.pop('c_is_class') - if 'values' in elem: elem['type']['values'] = elem.pop('values') - if 'flags' in elem: elem['type']['flags'] = elem.pop('flags') - if 'nullflag' in elem: elem['type']['nullflag'] = elem.pop('nullflag') - - elem['name'] = name - elem['fullname'] = join_name(parent.fullname, name) - elem['parent'] = parent - elem['typeargs'] = elem.get('typeargs', {}) - elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) - 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'] + ' = ') + self.name = join_name(path, name) + self.type = ValueTypeRefElement(path, name, elem['type'], []) - 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'] = InterfaceRefElement(parent.fullname, None, 'fibre.Property', elem['typeargs']) - 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(parent.fullname, to_pascal_case(name), elem['type']) - elem['type'] = InterfaceRefElement(parent.fullname, None, 'fibre.Property', elem['typeargs']) - if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') - else: - elem['type'] = InterfaceRefElement(parent.fullname, to_pascal_case(name), elem['type'], elem['typeargs']) - return elem class InterfaceRefElement(): def __init__(self, scope, name, elem, typeargs): @@ -291,47 +236,82 @@ def resolve(self): At every scope level, if no matching interface is found, it is checked if a matching value type exists. If so, the interface type fibre.Property is returned. + + This also adds the interface element to the global interface list and + recursively resolves any on-demand created types. """ if not self._intf is None: + if self._intf.name.get_fibre_name() in interfaces: + raise Exception("redefinition of " + str(self._intf.name)) + interfaces[self._intf.name.get_fibre_name()] = self._intf + deep_resolve(self._intf) return self._intf typeargs = self._typeargs - if 'fibre.Property.type' in typeargs: - typeargs['fibre.Property.type'] = resolve_valuetype(self._scope, typeargs['fibre.Property.type']) + if 'fibre.Property.type' in typeargs and isinstance(typeargs['fibre.Property.type'], ValueTypeRefElement): + typeargs['fibre.Property.type'] = typeargs['fibre.Property.type'].resolve() - scope = self._scope.split('.') - for probe_scope in [join_name(*scope[:(len(scope)-i)]) for i in range(len(scope)+1)]: - probe_name = join_name(probe_scope, self._name) + for probe_scope in [join_name(*self._scope[:(len(self._scope)-i)]) for i in range(len(self._scope)+1)]: + probe_name = join_name(probe_scope, self._name).get_fibre_name() #print('probing ' + probe_name) if probe_name in interfaces: return interfaces[probe_name] elif probe_name in value_types: typeargs['fibre.Property.type'] = value_types[probe_name] - return make_property_type(typeargs) + return get_property_type(typeargs) elif probe_name in generics: return generics[probe_name](typeargs) raise Exception('could not resolve type {} in {}. Known interfaces are: {}. Known value types are: {}'.format(self._name, self._scope, list(interfaces.keys()), list(value_types.keys()))) + +class ValueTypeRefElement(): + def __init__(self, scope, name, elem, typeargs = []): + if isinstance(elem, str): + self._val_type = None + self._scope = scope + self._name = elem + else: + self._val_type = ValueTypeElement(scope, name, elem) + self._scope = None + self._name = None + self._typeargs = typeargs + + def resolve(self): + """ + Resolves this value type reference to an actual ValueTypeElement instance. + The innermost scope is searched first. + """ + if not self._val_type is None: + value_types[self._val_type.name.get_fibre_name()] = self._intf + return self._val_type + + for probe_scope in [join_name(*self._scope[:(len(self._scope)-i)]) for i in range(len(self._scope)+1)]: + probe_name = join_name(probe_scope, self._name).get_fibre_name() + if probe_name in value_types: + return value_types[probe_name] + elif probe_name.startswith("fibre.Ref<"): + return get_ref_type(interfaces[probe_name[10:-1]]) # TODO: this is a bit hacky + + raise Exception('could not resolve type {} in {}. Known value types are: {}'.format(self._name, self._scope, list(value_types.keys()))) + + class InterfaceElement(): def __init__(self, path, name, elem): if elem is None: elem = {} assert(isinstance(elem, dict)) - - path = join_name(path, name) - interfaces[path] = self - self.name = split_name(name)[-1] - self.fullname = path - self.c_name = elem.get('c_name', self.fullname.replace('.', 'Intf::')) + 'Intf' + #self.name = split_name(name)[-1] + self.name = NameInfo(path, name) + #self.c_name = elem.get('c_name', self.fullname.replace('.', 'Intf::')) + 'Intf' if not 'implements' in elem: elem['implements'] = [] elif isinstance(elem['implements'], str): elem['implements'] = [elem['implements']] - self.implements = [InterfaceRefElement(path, None, elem, {}) for elem in elem['implements']] - self.functions = OrderedDict((name, regularize_func(path, name, func, {'obj': {'type': make_ref_type(self)}})) + self.implements = [InterfaceRefElement(self.name, None, elem, {}) for elem in elem['implements']] + self.functions = OrderedDict((name, regularize_func(self, name, func, {'obj': {'type': 'fibre.Ref<' + self.name.get_fibre_name() + '>'}})) for name, func in get_dict(elem, 'functions').items()) if not 'c_is_class' in elem: raise Exception(elem) @@ -357,12 +337,68 @@ def get_all_functions(self, stack=[]): result.update(self.functions) return result + +class ValueTypeElement(): + def __init__(self, path, name, elem): + if elem is None: + elem = {} + print(type(elem)) + assert(isinstance(elem, dict)) + + self.name = join_name(path, name) + #elem['c_name'] = elem.get('c_name', elem['fullname'].replace('.', 'Intf::')) + #value_types[self.name.get_fibre_name()] = self + + if 'flags' in elem: # treat as flags + bit = 0 + for k, v in elem['flags'].items(): + elem['flags'][k] = elem['flags'][k] or OrderedDict() + elem['flags'][k]['name'] = k + current_bit = elem['flags'][k].get('bit', bit) + elem['flags'][k]['bit'] = current_bit + elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) + bit = bit if current_bit is None else current_bit + 1 + if 'nullflag' in elem: + elem['flags'] = OrderedDict([(elem['nullflag'], OrderedDict({'name': elem['nullflag'], 'value': 0, 'bit': None})), *elem['flags'].items()]) + self.values = elem['flags'] + self.is_flags = True + self.is_enum = True + enums[path] = elem + + elif 'values' in elem: # treat as enum + val = 0 + self.values = {} + for k, v in elem['values'].items(): + self.values[k] = elem['values'][k] or OrderedDict() + self.values[k]['name'] = k + val = self.values[k].get('value', val) + self.values[k]['value'] = val + val += 1 + enums[path] = elem + self.is_flags = False + self.is_enum = True + + else: + print(elem) + raise Exception("unable to interpret as value type") + + +class RefType(ValueTypeElement): + def __init__(self, interface): + assert(isinstance(interface, InterfaceElement)) + self.name = NameInfo('fibre', 'Ref<' + interface.name.get_fibre_name() + '>') + + +class BasicValueType(ValueTypeElement): + def __init__(self, name, info): + self.name = name + self.c_name = info['c_name'] + + class PropertyInterfaceElement(InterfaceElement): - def __init__(self, name, fullname, mode, value_type): + def __init__(self, name, mode, value_type): self.name = name - self.fullname = fullname - self.purename = 'fibre.Property' - self.c_name = 'Property<' + ('const ' if mode == 'readonly' else '') + value_type['c_name'] + '>' + #self.c_name = 'Property<' + ('const ' if mode == 'readonly' else '') + value_type['c_name'] + '>' self.value_type = value_type # TODO: should be a metaarg self.mode = mode # TODO: should be a metaarg self.builtin = True @@ -370,97 +406,98 @@ def __init__(self, name, fullname, mode, value_type): self.functions = OrderedDict() if mode != 'readonly': self.functions['exchange'] = { - 'name': 'exchange', - 'fullname': join_name(fullname, 'exchange'), - 'in': OrderedDict([('obj', {'name': 'obj', 'type': {'c_name': self.c_name}}), ('value', {'name': 'value', 'type': value_type, 'optional': True})]), - 'out': OrderedDict([('value', {'name': 'value', 'type': value_type})]), + 'name': join_name(name, 'exchange'), + 'intf': self, + 'in': OrderedDict([('obj', ArgumentElement(name, 'obj', 'fibre.Ref<' + self.name.get_fibre_name() + '>')), + ('value', ArgumentElement(name, 'value', value_type.name.get_fibre_name()))]), + 'out': OrderedDict([('value', ArgumentElement(name, 'value', value_type.name.get_fibre_name()))]), #'implementation': 'fibre_property_exchange<' + value_type['c_name'] + '>' } - else: - self.functions['read'] = { - 'name': 'read', - 'fullname': join_name(fullname, 'read'), - 'in': OrderedDict([('obj', {'name': 'obj', 'type': {'c_name': self.c_name}})]), - 'out': OrderedDict([('value', {'name': 'value', 'type': value_type})]), - #'implementation': 'fibre_property_read<' + value_type['c_name'] + '>' - } + self.functions['read'] = { + 'name': join_name(name, 'read'), + 'intf': self, + 'in': OrderedDict([('obj', ArgumentElement(name, 'obj', 'fibre.Ref<' + self.name.get_fibre_name() + '>'))]), + 'out': OrderedDict([('value', ArgumentElement(name, 'value', value_type.name.get_fibre_name()))]), + #'implementation': 'fibre_property_read<' + value_type['c_name'] + '>' + } - interfaces[fullname] = self # TODO: not good to write to a global here -def make_property_type(typeargs): - value_type = resolve_valuetype('', typeargs['fibre.Property.type']) - mode = typeargs.get('fibre.Property.mode', 'readwrite') - name = 'Property<' + value_type['fullname'] + ', ' + mode + '>' - fullname = join_name('fibre', name) - if fullname in interfaces: - return interfaces[fullname] - else: - prop = PropertyInterfaceElement(name, fullname, mode, value_type) - interfaces[fullname] = prop - return prop +def get_dict(elem, key): + return elem.get(key, None) or OrderedDict() -generics = { - 'fibre.Property': make_property_type # TODO: improve generic support -} +def regularize_func(intf, name, elem, prepend_args): + if elem is None: + elem = {} + elem['intf'] = intf + elem['name'] = join_name(intf.name, name) + elem['in'] = OrderedDict((n, ArgumentElement(elem['name'], n, arg)) + for n, arg in (*prepend_args.items(), *get_dict(elem, 'in').items())) + elem['out'] = OrderedDict((n, ArgumentElement(elem['name'], n, arg)) + for n, arg in get_dict(elem, 'out').items()) + return elem -def regularize_valuetype(path, name, elem): +def regularize_attribute(parent, name, elem, c_is_class): if elem is None: elem = {} if isinstance(elem, str): - return elem # will be resolved during type resolution - elem['name'] = split_name(name)[-1] - elem['fullname'] = path = join_name(path, name) - elem['c_name'] = elem.get('c_name', elem['fullname'].replace('.', 'Intf::')) - value_types[path] = elem - - if 'flags' in elem: # treat as flags - bit = 0 - for k, v in elem['flags'].items(): - elem['flags'][k] = elem['flags'][k] or OrderedDict() - elem['flags'][k]['name'] = k - current_bit = elem['flags'][k].get('bit', bit) - elem['flags'][k]['bit'] = current_bit - elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) - bit = bit if current_bit is None else current_bit + 1 - if 'nullflag' in elem: - elem['flags'] = OrderedDict([(elem['nullflag'], OrderedDict({'name': elem['nullflag'], 'value': 0, 'bit': None})), *elem['flags'].items()]) - elem['values'] = elem['flags'] - elem['is_flags'] = True - elem['is_enum'] = True - enums[path] = elem - - elif 'values' in elem: # treat as enum - val = 0 - for k, v in elem['values'].items(): - elem['values'][k] = elem['values'][k] or OrderedDict() - elem['values'][k]['name'] = k - val = elem['values'][k].get('value', val) - elem['values'][k]['value'] = val - val += 1 - enums[path] = elem - elem['is_enum'] = True + elem = {'type': elem} + elif not 'type' in elem: + elem['type'] = {} + if 'attributes' in elem: elem['type']['attributes'] = elem.pop('attributes') + if 'functions' in elem: elem['type']['functions'] = elem.pop('functions') + if 'implements' in elem: elem['type']['implements'] = elem.pop('implements') + if 'c_is_class' in elem: elem['type']['c_is_class'] = elem.pop('c_is_class') + if 'values' in elem: elem['type']['values'] = elem.pop('values') + if 'flags' in elem: elem['type']['flags'] = elem.pop('flags') + if 'nullflag' in elem: elem['type']['nullflag'] = elem.pop('nullflag') + #elem['name'] = name + elem['name'] = join_name(parent.name, name) + elem['parent'] = parent + elem['typeargs'] = elem.get('typeargs', {}) + #elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) + 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'] = ValueTypeRefElement(parent.name, None, elem['type'][len('readonly '):]) + elem['type'] = InterfaceRefElement(parent.name, None, 'fibre.Property', elem['typeargs']) + 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'] = ValueTypeRefElement(parent.name, to_pascal_case(name), elem['type']) + elem['type'] = InterfaceRefElement(parent.name, None, 'fibre.Property', elem['typeargs']) + if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') + else: + elem['type'] = InterfaceRefElement(parent.name, to_pascal_case(name), elem['type'], elem['typeargs']) return elem -def resolve_valuetype(scope, name): - """ - Resolves a type name given as a string to the type object. - The innermost scope is searched first. - """ - if not isinstance(name, str): - return name - - scope = scope.split('.') - for probe_scope in [join_name(*scope[:(len(scope)-i)]) for i in range(len(scope)+1)]: - probe_name = join_name(probe_scope, name) - if probe_name in value_types: - return value_types[probe_name] +def get_ref_type(interface): + name = NameInfo('fibre', 'Ref<' + interface.name.get_fibre_name() + '>') + ref_type = value_types.get(name.get_fibre_name(), None) + if ref_type is None: + value_types[name.get_fibre_name()] = ref_type = RefType(interface) + return ref_type + +def get_property_type(typeargs): + assert(isinstance(typeargs['fibre.Property.type'], ValueTypeElement)) + value_type = typeargs['fibre.Property.type'] + mode = typeargs.get('fibre.Property.mode', 'readwrite') + name = NameInfo('fibre', 'Property<' + value_type.name.get_fibre_name() + ', ' + mode + '>') + prop_type = interfaces.get(name.get_fibre_name(), None) + if prop_type is None: + interfaces[name.get_fibre_name()] = prop_type = PropertyInterfaceElement(name, mode, value_type) + deep_resolve(prop_type) + return prop_type + + - raise Exception('could not resolve type {} in {}. Known value types are: {}'.format(name, join_name(*scope), list(value_types.keys()))) def map_to_fibre01_type(t): - if t.get('is_enum', False): + if hasattr(t, 'is_enum') and t.is_enum: max_val = max(v['value'] for v in t['values'].values()) if max_val <= 0xff: return 'uint8' @@ -472,9 +509,12 @@ def map_to_fibre01_type(t): return 'uint64' else: raise Exception("enum with a maximum value of " + str(max_val) + " not supported") - elif t['fullname'] == 'float32': + elif t.name.get_fibre_name() == 'float32': return 'float' - return t['fullname'] + return t.name.get_fibre_name() + +legacy_sizes = {'uint8': 1, 'int8': 1, 'uint16': 2, 'int16': 2, 'uint32': 4, + 'int32': 4, 'uint64': 8, 'int64': 8, 'float': 1} def generate_endpoint_for_property(prop, attr_bindto, idx): prop_intf = interfaces[prop['type'].fullname] @@ -504,7 +544,7 @@ def generate_endpoint_table(intf, bindto, idx): cnt = 0 for k, prop in intf.get_all_attributes().items(): - property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', prop['type'].fullname) + property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', prop['type'].name.get_fibre_name()) #attr_bindto = join_name(bindto, bindings_map.get(join_name(intf['fullname'], k), k + ('_' if len(intf['functions']) or (intf['fullname'] in treat_as_classes) else ''))) attr_bindto = intf.c_name + '::get_' + prop['name'] + '(' + bindto + ')' if len(property_value_type): @@ -535,14 +575,14 @@ def generate_endpoint_table(intf, bindto, idx): for i, (k_arg, arg) in enumerate(list(func['in'].items())[1:]): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg.name, - 'type': make_property_type({'fibre.Property.type': arg.type, 'fibre.Property.mode': 'readwrite'}) + 'type': get_property_type({'fibre.Property.type': arg.type, 'fibre.Property.mode': 'readwrite'}) }, intf.c_name + '::get_' + func['name'] + '_in_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + 1 + i) endpoints.append(endpoint) in_def.append(endpoint_definition) for i, (k_arg, arg) in enumerate(func['out'].items()): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg.name, - 'type': make_property_type({'fibre.Property.type': arg.type, 'fibre.Property.mode': 'readonly'}) + 'type': get_property_type({'fibre.Property.type': arg.type, 'fibre.Property.mode': 'readonly'}) }, intf.c_name + '::get_' + func['name'] + '_out_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + len(func['in']) + i) endpoints.append(endpoint) out_def.append(endpoint_definition) @@ -580,7 +620,7 @@ def generate_endpoint_table(intf, bindto, idx): args = parser.parse_args() if args.version: - print("0.0.1") + print("0.1.0") sys.exit(0) @@ -590,6 +630,26 @@ def generate_endpoint_table(intf, bindto, idx): # Load definition files +# Add built-in types +value_types = OrderedDict({ + 'bool': BasicValueType(NameInfo('bool'), {'c_name': 'bool', 'py_type': 'bool'}), + 'float32': BasicValueType(NameInfo('float32'), {'c_name': 'float', 'py_type': 'float'}), + 'uint8': BasicValueType(NameInfo('uint8'), {'c_name': 'uint8_t', 'py_type': 'int'}), + 'uint16': BasicValueType(NameInfo('uint16'), {'c_name': 'uint16_t', 'py_type': 'int'}), + 'uint32': BasicValueType(NameInfo('uint32'), {'c_name': 'uint32_t', 'py_type': 'int'}), + 'uint64': BasicValueType(NameInfo('uint64'), {'c_name': 'uint64_t', 'py_type': 'int'}), + 'int8': BasicValueType(NameInfo('int8'), {'c_name': 'int8_t', 'py_type': 'int'}), + 'int16': BasicValueType(NameInfo('int16'), {'c_name': 'int16_t', 'py_type': 'int'}), + 'int32': BasicValueType(NameInfo('int32'), {'c_name': 'int32_t', 'py_type': 'int'}), + 'int64': BasicValueType(NameInfo('int64'), {'c_name': 'int64_t', 'py_type': 'int'}), + 'endpoint_ref': BasicValueType(NameInfo('endpoint_ref'), {'c_name': 'endpoint_ref_t', 'py_type': '[not implemented]'}), +}) + +enums = OrderedDict() +interfaces = OrderedDict() +userdata = OrderedDict() # Arbitrary data passed from the definition file to the template +exports = OrderedDict() + for definition_file in definition_files: try: file_content = yaml.load(definition_file, Loader=SafeLineLoader) @@ -604,19 +664,20 @@ def generate_endpoint_table(intf, bindto, idx): #instance = err.instance.get(re.findall("([^']*)' (?:was|were) unexpected\)", err.message)[0], err.instance) # TODO: print line number raise Exception(err.message + '\nat ' + str(list(err.absolute_path))) - interfaces.update(get_dict(file_content, 'interfaces')) - value_types.update(get_dict(file_content, 'valuetypes')) + + # Regularize everything into a wellknown form + for k, item in list(get_dict(file_content, 'interfaces').items()): + interfaces[NameInfo(k).get_fibre_name()] = InterfaceElement(NameInfo(), k, item) + for k, item in list(get_dict(file_content, 'valuetypes').items()): + value_types[NameInfo(k).get_fibre_name()] = ValueTypeElement(NameInfo(), k, item) + for k, item in list(get_dict(file_content, 'exports').items()): + exports[k] = InterfaceRefElement(NameInfo(), k, item, []) + userdata.update(get_dict(file_content, 'userdata')) dictionary += file_content.get('dictionary', None) or [] -# Preprocess definitions -# Regularize everything into a wellknown form -for k, item in list(interfaces.items()): - InterfaceElement('', k, item) -for k, item in list(value_types.items()): - regularize_valuetype('', k, item) if args.verbose: print('Known interfaces: ' + ''.join([('\n ' + k) for k in interfaces.keys()])) @@ -627,16 +688,28 @@ def generate_endpoint_table(intf, bindto, idx): print("**Error**: Found both an interface and a value type with the name {}. This is not allowed, interfaces and value types (such as enums) share the same namespace.".format(clashing_names[0]), file=sys.stderr) sys.exit(1) +# We init generics this late because they must not be resolved prior to the +# resolve phase. +generics = { + 'fibre.Property': get_property_type # TODO: improve generic support +} + +def deep_resolve(intf): + #print("resolving", intf.name, "") + assert(isinstance(intf, InterfaceElement)) + for k, attr in intf.attributes.items(): + #print("resolving attr ", k) + attr['type'] = attr['type'].resolve() + for _, func in intf.functions.items(): + for _, arg in func['in'].items(): + arg.type = arg.type.resolve() + for _, arg in func['out'].items(): + arg.type = arg.type.resolve() + # Resolve all types to references for _, item in list(interfaces.items()): item.implements = [ref.resolve() for ref in item.implements] - for _, prop in item.attributes.items(): - prop['type'] = prop['type'].resolve() - for _, func in item.functions.items(): - for _, arg in func['in'].items(): - arg.type = resolve_valuetype(item.fullname, arg.type) - for _, arg in func['out'].items(): - arg.type = resolve_valuetype(item.fullname, arg.type) + deep_resolve(item) # Attach interfaces to their parents toplevel_interfaces = [] @@ -646,7 +719,7 @@ def generate_endpoint_table(intf, bindto, idx): toplevel_interfaces.append(item) else: if k[:-1] != ['fibre']: # TODO: remove special handling - parent = interfaces[join_name(*k[:-1])] + parent = interfaces[join_name(*k[:-1]).get_fibre_name()] parent.interfaces.append(item) item.parent = parent toplevel_enums = [] @@ -656,18 +729,140 @@ def generate_endpoint_table(intf, bindto, idx): toplevel_enums.append(item) else: if k[:-1] != ['fibre']: # TODO: remove special handling - parent = interfaces[join_name(*k[:-1])] + parent = interfaces[join_name(*k[:-1]).get_fibre_name()] parent.enums.append(item) item['parent'] = parent -if args.generate_endpoints: - endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces[args.generate_endpoints], '&ep_root', 1) # TODO: make user-configurable - embedded_endpoint_definitions = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] + embedded_endpoint_definitions - endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints -else: - embedded_endpoint_definitions = None - endpoints = None +exported_functions = [] +exported_interfaces = [] +exported_objects = {} +published_objects = [] +endpoint_table = [{}] # LEGACY + +def exported_func(func): + if func in exported_functions: + return + func['id'] = len(exported_functions) + exported_functions.append(func) + +def export_intf(intf): + if intf in exported_interfaces: + return + intf.id = len(exported_interfaces) + exported_interfaces.append(intf) + for k, attr in intf.attributes.items(): + export_intf(attr['type']) + for _, func in intf.functions.items(): + exported_func(func) + +def export_obj(obj, intf_stack): + if obj['type'] in intf_stack: + raise Exception("circular attribute tree not yet supported") + + obj['id'] = len(exported_objects) + intf = obj['type'] + exported_objects[obj['name'].get_fibre_name()] = obj + + + # LEGACY SUPPORT + property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', intf.name.get_fibre_name()) + if len(property_value_type): + # Special handling for Property<...> attributes: they resolve to one single endpoint + endpoint_descr = { + 'name': obj['name'][-1], + 'id': len(endpoint_table), + 'type': map_to_fibre01_type(intf.value_type), + 'access': 'r' if intf.mode == 'readonly' else 'rw', + } + endpoint_table.append( + '{.type = EndpointType::kRoProperty, .ro_property = {.read_function_id = ' + str(intf.functions['read']['id']) + ', .object_id = ' + str(obj['id']) + '}}' + if intf.mode == 'readonly' else + '{.type = EndpointType::kRwProperty, .rw_property = {.read_function_id = ' + str(intf.functions['read']['id']) + ', .exchange_function_id = ' + str(intf.functions['exchange']['id']) + ', .object_id = ' + str(obj['id']) + '}}' + ) + else: + endpoint_descr = { + 'name': obj['name'][-1], + 'type': 'object', + 'members': [] + } + for k, func in intf.get_all_functions().items(): + fn_desc = { + 'name': k, + 'id': len(endpoint_table), + 'type': 'function', + 'inputs': [], + 'outputs': [] + } + endpoint_table.append( + '{.type = EndpointType::kFunctionTrigger, .function_trigger = {.function_id = ' + str(func['id']) + ', .object_id = ' + str(obj['id']) + '}}' + ) + for i, (k_arg, arg) in enumerate(list(func['in'].items())[1:]): + fn_desc['inputs'].append({ + 'name': k_arg, + 'id': len(endpoint_table), + 'type': map_to_fibre01_type(arg.type), + 'access': 'rw', + }) + endpoint_table.append( + '{.type = EndpointType::kFunctionInput, .function_input = {.size = ' + str(legacy_sizes[map_to_fibre01_type(arg.type)]) + '}}' + ) + for i, (k_arg, arg) in enumerate(func['out'].items()): + fn_desc['outputs'].append({ + 'name': k_arg, + 'id': len(endpoint_table), + 'type': map_to_fibre01_type(arg.type), + 'access': 'r', + }) + endpoint_table.append( + '{.type = EndpointType::kFunctionOutput, .function_output = {.size = ' + str(legacy_sizes[map_to_fibre01_type(arg.type)]) + '}}' + ) + endpoint_descr['members'].append(fn_desc) + + for k, attr in intf.attributes.items(): + endpoint_descr['members'].append(export_obj({'name': NameInfo(obj['name'], k), 'type': attr['type']}, intf_stack + [obj['type']])) + + return endpoint_descr + + + +endpoint_descr = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] # LEGACY + + + +for name, export in exports.items(): + intf = export.resolve() + export_intf(intf) + obj = {'name': NameInfo(name), 'type': intf} + endpoint_descr += export_obj(obj, [])['members'] + published_objects.append(obj) + + + +## Legacy support + + +#def generate_endpoints(intf): +# table = [] +# +# for k, prop in intf.get_all_attributes().items(): +# return table + + +#for name, export in exports.items(): +# intf = export.resolve() +# endpoint_descr += generate_endpoints(intf) + +#if args.generate_endpoints: +# endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces[args.generate_endpoints]) +# embedded_endpoint_definitions = +# +# +# + embedded_endpoint_definitions +# endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints +#else: +# embedded_endpoint_definitions = None +# endpoints = None # Render template @@ -740,9 +935,13 @@ def token_transform(token): 'interfaces': interfaces, 'value_types': value_types, 'toplevel_interfaces': toplevel_interfaces, + 'exported_functions': exported_functions, + 'exported_interfaces': exported_interfaces, + 'exported_objects': exported_objects, + 'published_objects': published_objects, 'userdata': userdata, - 'endpoints': endpoints, - 'embedded_endpoint_definitions': embedded_endpoint_definitions + 'endpoint_table': endpoint_table, # deprecated + 'endpoint_descr': endpoint_descr, # deprecated } if not args.output is None: