diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fc1c748..ae7bcb4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,27 @@ set(MP_PUBLIC_HEADERS include/mp/proxy-io.h include/mp/proxy-types.h include/mp/proxy.h + include/mp/type-char.h + include/mp/type-chrono.h + include/mp/type-context.h + include/mp/type-data.h + include/mp/type-decay.h + include/mp/type-exception.h + include/mp/type-function.h + include/mp/type-interface.h + include/mp/type-map.h + include/mp/type-message.h + include/mp/type-number.h + include/mp/type-optional.h + include/mp/type-pair.h + include/mp/type-pointer.h + include/mp/type-set.h + include/mp/type-string.h + include/mp/type-struct.h + include/mp/type-threadmap.h + include/mp/type-tuple.h + include/mp/type-vector.h + include/mp/type-void.h include/mp/util.h) add_library(multiprocess STATIC ${MP_PROXY_SRCS} diff --git a/example/calculator.capnp b/example/calculator.capnp index e9d45a28..8f546552 100644 --- a/example/calculator.capnp +++ b/example/calculator.capnp @@ -8,6 +8,7 @@ using Cxx = import "/capnp/c++.capnp"; using Proxy = import "/mp/proxy.capnp"; $Proxy.include("calculator.h"); +$Proxy.includeTypes("types.h"); interface CalculatorInterface $Proxy.wrap("Calculator") { destroy @0 (context :Proxy.Context) -> (); diff --git a/example/printer.capnp b/example/printer.capnp index 51fe92e5..e27ce412 100644 --- a/example/printer.capnp +++ b/example/printer.capnp @@ -8,6 +8,7 @@ using Cxx = import "/capnp/c++.capnp"; using Proxy = import "/mp/proxy.capnp"; $Proxy.include("printer.h"); +$Proxy.includeTypes("types.h"); interface PrinterInterface $Proxy.wrap("Printer") { destroy @0 (context :Proxy.Context) -> (); diff --git a/example/types.h b/example/types.h new file mode 100644 index 00000000..0c0bd934 --- /dev/null +++ b/example/types.h @@ -0,0 +1,14 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef EXAMPLE_TYPES_H +#define EXAMPLE_TYPES_H + +#include +#include +#include +#include +#include + +#endif // EXAMPLE_TYPES_H diff --git a/include/mp/proxy-types.h b/include/mp/proxy-types.h index e1bede06..6c1b1aae 100644 --- a/include/mp/proxy-types.h +++ b/include/mp/proxy-types.h @@ -53,168 +53,10 @@ struct StructField // clang-format on }; -template -void CustomBuildField(TypeList<>, - Priority<1>, - ClientInvokeContext& invoke_context, - Output&& output, - typename std::enable_if::value>::type* enable = nullptr) -{ - auto& connection = invoke_context.connection; - auto& thread_context = invoke_context.thread_context; - - // Create local Thread::Server object corresponding to the current thread - // and pass a Thread::Client reference to it in the Context.callbackThread - // field so the function being called can make callbacks to this thread. - // Also store the Thread::Client reference in the callback_threads map so - // future calls over this connection can reuse it. - auto [callback_thread, _]{SetThread( - thread_context.callback_threads, thread_context.waiter->m_mutex, &connection, - [&] { return connection.m_threads.add(kj::heap>(thread_context, std::thread{})); })}; - - // Call remote ThreadMap.makeThread function so server will create a - // dedicated worker thread to run function calls from this thread. Store the - // Thread::Client reference it returns in the request_threads map. - auto make_request_thread{[&]{ - // This code will only run if an IPC client call is being made for the - // first time on this thread. After the first call, subsequent calls - // will use the existing request thread. This code will also never run at - // all if the current thread is a request thread created for a different - // IPC client, because in that case PassField code (below) will have set - // request_thread to point to the calling thread. - auto request = connection.m_thread_map.makeThreadRequest(); - request.setName(thread_context.thread_name); - return request.send().getResult(); // Nonblocking due to capnp request pipelining. - }}; - auto [request_thread, _1]{SetThread( - thread_context.request_threads, thread_context.waiter->m_mutex, - &connection, make_request_thread)}; - - auto context = output.init(); - context.setThread(request_thread->second.m_client); - context.setCallbackThread(callback_thread->second.m_client); -} - -//! PassField override for mp.Context arguments. Return asynchronously and call -//! function on other thread found in context. -template -auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) -> - typename std::enable_if< - std::is_same::value, - kj::Promise>::type -{ - const auto& params = server_context.call_context.getParams(); - Context::Reader context_arg = Accessor::get(params); - auto future = kj::newPromiseAndFulfiller(); - auto& server = server_context.proxy_server; - int req = server_context.req; - auto invoke = MakeAsyncCallable( - [fulfiller = kj::mv(future.fulfiller), - call_context = kj::mv(server_context.call_context), &server, req, fn, args...]() mutable { - const auto& params = call_context.getParams(); - Context::Reader context_arg = Accessor::get(params); - ServerContext server_context{server, call_context, req}; - bool disconnected{false}; - { - // Before invoking the function, store a reference to the - // callbackThread provided by the client in the - // thread_local.request_threads map. This way, if this - // server thread needs to execute any RPCs that call back to - // the client, they will happen on the same client thread - // that is waiting for this function, just like what would - // happen if this were a normal function call made on the - // local stack. - // - // If the request_threads map already has an entry for this - // connection, it will be left unchanged, and it indicates - // that the current thread is an RPC client thread which is - // in the middle of an RPC call, and the current RPC call is - // a nested call from the remote thread handling that RPC - // call. In this case, the callbackThread value should point - // to the same thread already in the map, so there is no - // need to update the map. - auto& thread_context = g_thread_context; - auto& request_threads = thread_context.request_threads; - auto [request_thread, inserted]{SetThread( - request_threads, thread_context.waiter->m_mutex, - server.m_context.connection, - [&] { return context_arg.getCallbackThread(); })}; - - // If an entry was inserted into the requests_threads map, - // remove it after calling fn.invoke. If an entry was not - // inserted, one already existed, meaning this must be a - // recursive call (IPC call calling back to the caller which - // makes another IPC call), so avoid modifying the map. - const bool erase_thread{inserted}; - KJ_DEFER({ - std::unique_lock lock(thread_context.waiter->m_mutex); - // Call erase here with a Connection* argument instead - // of an iterator argument, because the `request_thread` - // iterator may be invalid if the connection is closed - // during this function call. More specifically, the - // iterator may be invalid because SetThread adds a - // cleanup callback to the Connection destructor that - // erases the thread from the map, and also because the - // ProxyServer destructor calls - // request_threads.clear(). - if (erase_thread) { - disconnected = !request_threads.erase(server.m_context.connection); - } else { - disconnected = !request_threads.count(server.m_context.connection); - } - }); - fn.invoke(server_context, args...); - } - if (disconnected) { - // If disconnected is true, the Connection object was - // destroyed during the method call. Deal with this by - // returning without ever fulfilling the promise, which will - // cause the ProxyServer object to leak. This is not ideal, - // but fixing the leak will require nontrivial code changes - // because there is a lot of code assuming ProxyServer - // objects are destroyed before Connection objects. - return; - } - KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { - server.m_context.connection->m_loop.sync([&] { - auto fulfiller_dispose = kj::mv(fulfiller); - fulfiller_dispose->fulfill(kj::mv(call_context)); - }); - })) - { - server.m_context.connection->m_loop.sync([&]() { - auto fulfiller_dispose = kj::mv(fulfiller); - fulfiller_dispose->reject(kj::mv(*exception)); - }); - } - }); - // Lookup Thread object specified by the client. The specified thread should - // be a local Thread::Server object, but it needs to be looked up - // asynchronously with getLocalServer(). - auto thread_client = context_arg.getThread(); - return server.m_context.connection->m_threads.getLocalServer(thread_client) - .then([&server, invoke, req](const kj::Maybe& perhaps) { - // Assuming the thread object is found, pass it a pointer to the - // `invoke` lambda above which will invoke the function on that - // thread. - KJ_IF_MAYBE (thread_server, perhaps) { - const auto& thread = static_cast&>(*thread_server); - server.m_context.connection->m_loop.log() - << "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}"; - thread.m_thread_context.waiter->post(std::move(invoke)); - } else { - server.m_context.connection->m_loop.log() - << "IPC server error request #" << req << ", missing thread to execute request"; - throw std::runtime_error("invalid thread handle"); - } - }) - // Wait for the invocation to finish before returning to the caller. - .then([invoke_wait = kj::mv(future.promise)]() mutable { return kj::mv(invoke_wait); }); -} // Destination parameter type that can be passed to ReadField function as an -// alternative to ReadDestValue. It allows the ReadField implementation to call +// alternative to ReadDestUpdate. It allows the ReadField implementation to call // the provided emplace_fn function with constructor arguments, so it only needs // to determine the arguments, and can let the emplace function decide how to // actually construct the read destination object. For example, if a std::string @@ -274,9 +116,9 @@ auto ReadDestTemp() //! construct a new value, it just takes a reference to an existing value and //! assigns a new value to it. template -struct ReadDestValue +struct ReadDestUpdate { - ReadDestValue(Value& value) : m_value(value) {} + ReadDestUpdate(Value& value) : m_value(value) {} //! Simple case. If ReadField works by calling update() just forward arguments to update_fn. template @@ -299,391 +141,6 @@ struct ReadDestValue Value& m_value; }; -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!input.has()) { - value.reset(); - } else if (value) { - ReadField(TypeList(), invoke_context, input, ReadDestValue(*value)); - } else { - ReadField(TypeList(), invoke_context, input, - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value.emplace(std::forward(args)...); - return *value; - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<0>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!input.has()) { - value.reset(); - } else if (value) { - ReadField(TypeList(), invoke_context, input, ReadDestValue(*value)); - } else { - ReadField(TypeList(), invoke_context, input, - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value = std::make_shared(std::forward(args)...); - return *value; - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (value) { - ReadField(TypeList(), invoke_context, std::forward(input), ReadDestValue(*value)); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - if (!input.has()) { - value.reset(); - return; - } - ReadField(TypeList(), invoke_context, std::forward(input), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value = std::make_shared(std::forward(args)...); - return *value; - })); - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - auto data = input.get(); - value.clear(); - value.reserve(data.size()); - for (auto item : data) { - ReadField(TypeList(), invoke_context, Make(item), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - value.emplace_back(std::forward(args)...); - return value.back(); - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - auto data = input.get(); - value.clear(); - value.reserve(data.size()); - for (auto item : data) { - value.push_back(ReadField(TypeList(), invoke_context, Make(item), ReadDestTemp())); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - auto data = input.get(); - value.clear(); - for (auto item : data) { - ReadField(TypeList(), invoke_context, Make(item), - ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { - return *value.emplace(std::forward(args)...).first; - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - auto data = input.get(); - value.clear(); - for (auto item : data) { - ReadField(TypeList>(), invoke_context, - Make(item), - ReadDestEmplace( - TypeList>(), [&](auto&&... args) -> auto& { - return *value.emplace(std::forward(args)...).first; - })); - } - }); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - const auto& pair = input.get(); - using Accessors = typename ProxyStruct::Reads>::Accessors; - - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestEmplace(TypeList(), [&](auto&&... key_args) -> auto& { - KeyLocalType* key = nullptr; - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestEmplace(TypeList(), [&](auto&&... value_args) -> auto& { - auto& ret = read_dest.construct(std::piecewise_construct, std::forward_as_tuple(key_args...), - std::forward_as_tuple(value_args...)); - key = &ret.first; - return ret.second; - })); - return *key; - })); -} - -// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - const auto& pair = input.get(); - using Struct = ProxyStruct::Reads>; - using Accessors = typename Struct::Accessors; - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestValue(std::get<0>(value))); - ReadField(TypeList(), invoke_context, Make>(pair), - ReadDestValue(std::get<1>(value))); - }); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = 0) -{ - return read_dest.construct(static_cast(input.get())); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = nullptr) -{ - auto value = input.get(); - if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { - throw std::range_error("out of bound int received"); - } - return read_dest.construct(static_cast(value)); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = 0) -{ - auto value = input.get(); - static_assert(std::is_same::value, "floating point type mismatch"); - return read_dest.construct(value); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - auto data = input.get(); - return read_dest.construct(CharCast(data.begin()), data.size()); -} - -template -decltype(auto) CustomReadField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - return read_dest.update([&](auto& value) { - auto data = input.get(); - memcpy(value, data.begin(), size); - }); -} - -template -std::unique_ptr MakeProxyClient(InvokeContext& context, typename Interface::Client&& client) -{ - return std::make_unique>( - std::move(client), &context.connection, /* destroy_connection= */ false); -} - -template -std::unique_ptr CustomMakeProxyClient(InvokeContext& context, typename Interface::Client&& client) -{ - return MakeProxyClient(context, kj::mv(client)); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename Decay::Calls* enable = nullptr) -{ - using Interface = typename Decay::Calls; - if (input.has()) { - return read_dest.construct( - CustomMakeProxyClient(invoke_context, std::move(input.get()))); - } - return read_dest.construct(); -} - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename Decay::Calls* enable = nullptr) -{ - using Interface = typename Decay::Calls; - if (input.has()) { - return read_dest.construct( - CustomMakeProxyClient(invoke_context, std::move(input.get()))); - } - return read_dest.construct(); -} - -// ProxyCallFn class is needed because c++11 doesn't support auto lambda parameters. -// It's equivalent c++14: [invoke_context](auto&& params) { -// invoke_context->call(std::forward(params)...) -template -struct ProxyCallFn -{ - InvokeContext m_proxy; - - template - decltype(auto) operator()(CallParams&&... params) { return this->m_proxy->call(std::forward(params)...); } -}; - -template -decltype(auto) CustomReadField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest) -{ - if (input.has()) { - using Interface = typename Decay::Calls; - auto client = std::make_shared>( - input.get(), &invoke_context.connection, /* destroy_connection= */ false); - return read_dest.construct(ProxyCallFn{std::move(client)}); - } - return read_dest.construct(); -}; - -template -void ReadOne(TypeList param, - InvokeContext& invoke_context, - Input&& input, - Value&& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ - using Index = std::integral_constant; - using Struct = typename ProxyType::Struct; - using Accessor = typename std::tuple_element::Accessors>::type; - const auto& struc = input.get(); - auto&& field_value = value.*ProxyType::get(Index()); - ReadField(TypeList>(), invoke_context, Make(struc), - ReadDestValue(field_value)); - ReadOne(param, invoke_context, input, value); -} - -template -void ReadOne(TypeList param, - InvokeContext& invoke_context, - Input& input, - Value& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ -} - -template -decltype(auto) CustomReadField(TypeList param, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - ReadDest&& read_dest, - typename ProxyType::Struct* enable = nullptr) -{ - return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); }); -} - -//! Overload CustomReadField to serialize objects that have CustomReadMessage -//! overloads. Defining a CustomReadMessage overload is simpler than defining a -//! CustomReadField overload because it only requires defining a normal -//! function, not a template function, but less flexible. -template -decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Reader&& reader, - ReadDest&& read_dest, - decltype(CustomReadMessage(invoke_context, reader.get(), - std::declval()))* enable = nullptr) -{ - return read_dest.update([&](auto& value) { if (reader.has()) CustomReadMessage(invoke_context, reader.get(), value); }); -} - template decltype(auto) ReadField(TypeList, Args&&... args) { @@ -708,50 +165,12 @@ void ThrowField(TypeList, InvokeContext& invoke_context, Input&& throw std::runtime_error(std::string(CharCast(data.begin()), data.size())); } -template -void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, ::capnp::Void, Output&& output) -{ -} - -template -void CustomBuildField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto result = output.init(value.size()); - memcpy(result.begin(), value.data(), value.size()); -} - -template -void CustomBuildField(TypeList, - Priority<3>, - InvokeContext& invoke_context, - const unsigned char (&value)[size], - Output&& output) -{ - auto result = output.init(size); - memcpy(result.begin(), value, size); -} - template bool CustomHasValue(InvokeContext& invoke_context, Values&&... value) { return true; } -//! Overload CustomBuildField to serialize objects that have CustomBuildMessage -//! overloads. Defining a CustomBuildMessage overload is simpler than defining a -//! CustomBuildField overload because it only requires defining a normal -//! function, not a template function, but less flexible. -template -void CustomBuildField(TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output, - decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr) -{ - CustomBuildMessage(invoke_context, value, std::move(output.init())); -} - template void BuildField(TypeList, Context& context, Output&& output, Values&&... values) { @@ -761,108 +180,6 @@ void BuildField(TypeList, Context& context, Output&& output, Valu } } -//! Adapter to convert ProxyCallback object call to function object call. -template -class ProxyCallbackImpl final : public ProxyCallback> -{ - using Fn = std::function; - Fn m_fn; - -public: - ProxyCallbackImpl(Fn fn) : m_fn(std::move(fn)) {} - Result call(Args&&... args) override { return m_fn(std::forward(args)...); } -}; - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value& value, - Output&& output) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - using Callback = ProxyCallbackImpl; - output.set(kj::heap>( - std::make_shared(std::forward(value)), invoke_context.connection)); - } -} - -template -kj::Own MakeProxyServer(InvokeContext& context, std::shared_ptr impl) -{ - return kj::heap>(std::move(impl), context.connection); -} - -template -kj::Own CustomMakeProxyServer(InvokeContext& context, std::shared_ptr&& impl) -{ - return MakeProxyServer(context, std::move(impl)); -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename Decay::Calls* enable = nullptr) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - output.set(CustomMakeProxyServer(invoke_context, std::shared_ptr(value.release()))); - } -} - -template -void CustomBuildField(TypeList>, - Priority<2>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename Decay::Calls* enable = nullptr) -{ - if (value) { - using Interface = typename decltype(output.get())::Calls; - output.set(CustomMakeProxyServer(invoke_context, std::move(value))); - } -} - -template -void CustomBuildField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - Impl& value, - Output&& output, - typename decltype(output.get())::Calls* enable = nullptr) -{ - // Disable deleter so proxy server object doesn't attempt to delete the - // wrapped implementation when the proxy client is destroyed or - // disconnected. - using Interface = typename decltype(output.get())::Calls; - output.set(CustomMakeProxyServer(invoke_context, std::shared_ptr(&value, [](Impl*){}))); -} - -template -void CustomBuildField(TypeList, Priority<3>, InvokeContext& invoke_context, Value&& value, Output&& output) -{ - if (value) { - BuildField(TypeList(), invoke_context, output, *value); - } -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - if (value) { - BuildField(TypeList(), invoke_context, output, *value); - } -} - // Adapter to let BuildField overloads methods work set & init list elements as // if they were fields of a struct. If BuildField is changed to use some kind of // accessor class instead of calling method pointers, then then maybe this could @@ -888,245 +205,12 @@ struct ListOutput<::capnp::List> // clang-format on }; -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - // FIXME dedup with set handler below - auto list = output.init(value.size()); - size_t i = 0; - for (auto it = value.begin(); it != value.end(); ++it, ++i) { - BuildField(TypeList(), invoke_context, ListOutput(list, i), *it); - } -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - // FIXME dededup with vector handler above - auto list = output.init(value.size()); - size_t i = 0; - for (const auto& elem : value) { - BuildField(TypeList(), invoke_context, ListOutput(list, i), elem); - ++i; - } -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - // FIXME dededup with vector handler above - auto list = output.init(value.size()); - size_t i = 0; - for (const auto& elem : value) { - BuildField(TypeList>(), invoke_context, - ListOutput(list, i), elem); - ++i; - } -} -template -::capnp::Void BuildPrimitive(InvokeContext& invoke_context, Value&&, TypeList<::capnp::Void>) -{ - return {}; -} - -inline static bool BuildPrimitive(InvokeContext& invoke_context, std::vector::const_reference value, TypeList) -{ - return value; -} - -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value>::type* enable = nullptr) -{ - using E = std::make_unsigned_t>; - using T = std::make_unsigned_t; - static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral/enum types"); - return static_cast(value); -} - -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value, int>::type* enable = nullptr) -{ - static_assert( - std::numeric_limits::lowest() <= std::numeric_limits::lowest(), "mismatched integral types"); - static_assert( - std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral types"); - return value; -} - -template -LocalType BuildPrimitive(InvokeContext& invoke_context, - const Value& value, - TypeList, - typename std::enable_if::value>::type* enable = nullptr) -{ - static_assert(std::is_same::value, - "mismatched floating point types. please fix message.capnp type declaration to match wrapped interface"); - return value; -} - -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - if (value) { - output.setHas(); - // FIXME: should std::move value if destvalue is rref? - BuildField(TypeList(), invoke_context, output, *value); - } -} - -template -void CustomBuildField(TypeList, - Priority<1>, - InvokeContext& invoke_context, - const std::exception& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::string(value.what())); -} - -// FIXME: Overload on output type instead of value type and switch to std::get and merge with next overload -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto pair = output.init(); - using Accessors = typename ProxyStruct::Accessors; - BuildField(TypeList(), invoke_context, Make>(pair), value.first); - BuildField(TypeList(), invoke_context, Make>(pair), value.second); -} - -// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. -template -void CustomBuildField(TypeList>, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - auto pair = output.init(); - using Accessors = typename ProxyStruct::Accessors; - BuildField(TypeList(), invoke_context, Make>(pair), std::get<0>(value)); - BuildField(TypeList(), invoke_context, Make>(pair), std::get<1>(value)); -} - -template -void CustomBuildField(TypeList, - Priority<0>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} - -template -void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} - -template -void CustomBuildField(TypeList, - Priority<0>, - InvokeContext& invoke_context, - Value&& value, - Output&& output) -{ - BuildField(TypeList(), invoke_context, output, std::forward(value)); -} - template void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) { output.set(BuildPrimitive(invoke_context, std::forward(value), TypeList())); } -template -void BuildOne(TypeList param, - InvokeContext& invoke_context, - Output&& output, - Value&& value, - typename std::enable_if < index::fields>::type * enable = nullptr) -{ - using Index = std::integral_constant; - using Struct = typename ProxyType::Struct; - using Accessor = typename std::tuple_element::Accessors>::type; - auto&& field_output = Make(output); - auto&& field_value = value.*ProxyType::get(Index()); - BuildField(TypeList>(), invoke_context, field_output, field_value); - BuildOne(param, invoke_context, output, value); -} - -template -void BuildOne(TypeList param, - InvokeContext& invoke_context, - Output&& output, - Value&& value, - typename std::enable_if::fields>::type* enable = nullptr) -{ -} - -template -void CustomBuildField(TypeList local_type, - Priority<1>, - InvokeContext& invoke_context, - Value&& value, - Output&& output, - typename ProxyType::Struct* enable = nullptr) -{ - BuildOne<0>(local_type, invoke_context, output.init(), value); -} - -//! PassField override for C++ pointer arguments. -template -void PassField(Priority<1>, TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) -{ - const auto& params = server_context.call_context.getParams(); - const auto& input = Make(params); - - if (!input.want()) { - fn.invoke(server_context, std::forward(args)..., nullptr); - return; - } - - InvokeContext& invoke_context = server_context; - Decay param; - - MaybeReadField(std::integral_constant(), TypeList(), invoke_context, input, - ReadDestValue(param)); - - fn.invoke(server_context, std::forward(args)..., ¶m); - - auto&& results = server_context.call_context.getResults(); - MaybeBuildField(std::integral_constant(), TypeList(), invoke_context, - Make(results), param); -} - //! PassField override for callable interface reference arguments. template auto PassField(Priority<1>, TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) @@ -1213,35 +297,6 @@ void PassField(Priority<0>, TypeList<>, ServerContext& server_context, const Fn& BuildField(TypeList<>(), server_context, Make(results)); } -template <> -struct ProxyServer final : public virtual ThreadMap::Server -{ -public: - ProxyServer(Connection& connection); - kj::Promise makeThread(MakeThreadContext context) override; - Connection& m_connection; -}; - -template -void CustomBuildField(TypeList<>, - Priority<1>, - InvokeContext& invoke_context, - Output&& output, - typename std::enable_if::value>::type* enable = nullptr) -{ - output.set(kj::heap>(invoke_context.connection)); -} - -template -decltype(auto) CustomReadField(TypeList<>, - Priority<1>, - InvokeContext& invoke_context, - Input&& input, - typename std::enable_if::value>::type* enable = nullptr) -{ - invoke_context.connection.m_thread_map = input.get(); -} - template struct IterateFieldsHelper { @@ -1355,7 +410,7 @@ struct ClientParam -> typename std::enable_if::type { MaybeReadField(std::integral_constant(), TypeList...>(), invoke_context, - Make(results), ReadDestValue(values)...); + Make(results), ReadDestUpdate(values)...); } ReadResults(ClientParam* client_param) : m_client_param(client_param) {} @@ -1439,34 +494,6 @@ ::capnp::Void MaybeGet(...) return {}; } -//! Helper for CustomPassField below. Call Accessor::init method if it has one, -//! otherwise do nothing. -template -decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr) -{ - return Accessor::init(message); -} - -template -::capnp::Void MaybeInit(...) -{ - return {}; -} - -//! Overload CustomPassField to serialize objects that have CustomPassMessage -//! overloads. Defining a CustomPassMessage overload is simpler than defining a -//! CustomPassField overload because it only requires defining a normal -//! function, not a template function, but less flexible. -template -auto CustomPassField(TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) - -> decltype(CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), - MaybeGet(server_context.call_context.getResults()), nullptr)) -{ - CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), - MaybeInit(server_context.call_context.getResults()), - [&](LocalTypes... param) { fn.invoke(server_context, std::forward(args)..., param...); }); -} - template void CustomPassField(); diff --git a/include/mp/type-char.h b/include/mp/type-char.h new file mode 100644 index 00000000..d1d27b62 --- /dev/null +++ b/include/mp/type-char.h @@ -0,0 +1,36 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_CHAR_H +#define MP_PROXY_TYPE_CHAR_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList, + Priority<3>, + InvokeContext& invoke_context, + const unsigned char (&value)[size], + Output&& output) +{ + auto result = output.init(size); + memcpy(result.begin(), value, size); +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + memcpy(value, data.begin(), size); + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_CHAR_H diff --git a/include/mp/type-chrono.h b/include/mp/type-chrono.h new file mode 100644 index 00000000..a17d9a99 --- /dev/null +++ b/include/mp/type-chrono.h @@ -0,0 +1,34 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_CHRONO_H +#define MP_PROXY_TYPE_CHRONO_H + +#include + +#include + +namespace mp { +//! Overload CustomBuildField and CustomReadField to serialize std::chrono +//! parameters and return values as numbers. +template +void CustomBuildField(TypeList>, Priority<1>, InvokeContext& invoke_context, Value&& value, + Output&& output) +{ + static_assert(std::numeric_limits::lowest() <= std::numeric_limits::lowest(), + "capnp type does not have enough range to hold lowest std::chrono::duration value"); + static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), + "capnp type does not have enough range to hold highest std::chrono::duration value"); + output.set(value.count()); +} + +template +decltype(auto) CustomReadField(TypeList>, Priority<1>, InvokeContext& invoke_context, + Input&& input, ReadDest&& read_dest) +{ + return read_dest.construct(input.get()); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_CHRONO_H diff --git a/include/mp/type-context.h b/include/mp/type-context.h new file mode 100644 index 00000000..7c12afe2 --- /dev/null +++ b/include/mp/type-context.h @@ -0,0 +1,173 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_CONTEXT_H +#define MP_PROXY_TYPE_CONTEXT_H + +#include +#include + +namespace mp { +template +void CustomBuildField(TypeList<>, + Priority<1>, + ClientInvokeContext& invoke_context, + Output&& output, + typename std::enable_if::value>::type* enable = nullptr) +{ + auto& connection = invoke_context.connection; + auto& thread_context = invoke_context.thread_context; + + // Create local Thread::Server object corresponding to the current thread + // and pass a Thread::Client reference to it in the Context.callbackThread + // field so the function being called can make callbacks to this thread. + // Also store the Thread::Client reference in the callback_threads map so + // future calls over this connection can reuse it. + auto [callback_thread, _]{SetThread( + thread_context.callback_threads, thread_context.waiter->m_mutex, &connection, + [&] { return connection.m_threads.add(kj::heap>(thread_context, std::thread{})); })}; + + // Call remote ThreadMap.makeThread function so server will create a + // dedicated worker thread to run function calls from this thread. Store the + // Thread::Client reference it returns in the request_threads map. + auto make_request_thread{[&]{ + // This code will only run if an IPC client call is being made for the + // first time on this thread. After the first call, subsequent calls + // will use the existing request thread. This code will also never run at + // all if the current thread is a request thread created for a different + // IPC client, because in that case PassField code (below) will have set + // request_thread to point to the calling thread. + auto request = connection.m_thread_map.makeThreadRequest(); + request.setName(thread_context.thread_name); + return request.send().getResult(); // Nonblocking due to capnp request pipelining. + }}; + auto [request_thread, _1]{SetThread( + thread_context.request_threads, thread_context.waiter->m_mutex, + &connection, make_request_thread)}; + + auto context = output.init(); + context.setThread(request_thread->second.m_client); + context.setCallbackThread(callback_thread->second.m_client); +} + +//! PassField override for mp.Context arguments. Return asynchronously and call +//! function on other thread found in context. +template +auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& fn, Args&&... args) -> + typename std::enable_if< + std::is_same::value, + kj::Promise>::type +{ + const auto& params = server_context.call_context.getParams(); + Context::Reader context_arg = Accessor::get(params); + auto future = kj::newPromiseAndFulfiller(); + auto& server = server_context.proxy_server; + int req = server_context.req; + auto invoke = MakeAsyncCallable( + [fulfiller = kj::mv(future.fulfiller), + call_context = kj::mv(server_context.call_context), &server, req, fn, args...]() mutable { + const auto& params = call_context.getParams(); + Context::Reader context_arg = Accessor::get(params); + ServerContext server_context{server, call_context, req}; + bool disconnected{false}; + { + // Before invoking the function, store a reference to the + // callbackThread provided by the client in the + // thread_local.request_threads map. This way, if this + // server thread needs to execute any RPCs that call back to + // the client, they will happen on the same client thread + // that is waiting for this function, just like what would + // happen if this were a normal function call made on the + // local stack. + // + // If the request_threads map already has an entry for this + // connection, it will be left unchanged, and it indicates + // that the current thread is an RPC client thread which is + // in the middle of an RPC call, and the current RPC call is + // a nested call from the remote thread handling that RPC + // call. In this case, the callbackThread value should point + // to the same thread already in the map, so there is no + // need to update the map. + auto& thread_context = g_thread_context; + auto& request_threads = thread_context.request_threads; + auto [request_thread, inserted]{SetThread( + request_threads, thread_context.waiter->m_mutex, + server.m_context.connection, + [&] { return context_arg.getCallbackThread(); })}; + + // If an entry was inserted into the requests_threads map, + // remove it after calling fn.invoke. If an entry was not + // inserted, one already existed, meaning this must be a + // recursive call (IPC call calling back to the caller which + // makes another IPC call), so avoid modifying the map. + const bool erase_thread{inserted}; + KJ_DEFER({ + std::unique_lock lock(thread_context.waiter->m_mutex); + // Call erase here with a Connection* argument instead + // of an iterator argument, because the `request_thread` + // iterator may be invalid if the connection is closed + // during this function call. More specifically, the + // iterator may be invalid because SetThread adds a + // cleanup callback to the Connection destructor that + // erases the thread from the map, and also because the + // ProxyServer destructor calls + // request_threads.clear(). + if (erase_thread) { + disconnected = !request_threads.erase(server.m_context.connection); + } else { + disconnected = !request_threads.count(server.m_context.connection); + } + }); + fn.invoke(server_context, args...); + } + if (disconnected) { + // If disconnected is true, the Connection object was + // destroyed during the method call. Deal with this by + // returning without ever fulfilling the promise, which will + // cause the ProxyServer object to leak. This is not ideal, + // but fixing the leak will require nontrivial code changes + // because there is a lot of code assuming ProxyServer + // objects are destroyed before Connection objects. + return; + } + KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { + server.m_context.connection->m_loop.sync([&] { + auto fulfiller_dispose = kj::mv(fulfiller); + fulfiller_dispose->fulfill(kj::mv(call_context)); + }); + })) + { + server.m_context.connection->m_loop.sync([&]() { + auto fulfiller_dispose = kj::mv(fulfiller); + fulfiller_dispose->reject(kj::mv(*exception)); + }); + } + }); + + // Lookup Thread object specified by the client. The specified thread should + // be a local Thread::Server object, but it needs to be looked up + // asynchronously with getLocalServer(). + auto thread_client = context_arg.getThread(); + return server.m_context.connection->m_threads.getLocalServer(thread_client) + .then([&server, invoke, req](const kj::Maybe& perhaps) { + // Assuming the thread object is found, pass it a pointer to the + // `invoke` lambda above which will invoke the function on that + // thread. + KJ_IF_MAYBE (thread_server, perhaps) { + const auto& thread = static_cast&>(*thread_server); + server.m_context.connection->m_loop.log() + << "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}"; + thread.m_thread_context.waiter->post(std::move(invoke)); + } else { + server.m_context.connection->m_loop.log() + << "IPC server error request #" << req << ", missing thread to execute request"; + throw std::runtime_error("invalid thread handle"); + } + }) + // Wait for the invocation to finish before returning to the caller. + .then([invoke_wait = kj::mv(future.promise)]() mutable { return kj::mv(invoke_wait); }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_CONTEXT_H diff --git a/include/mp/type-data.h b/include/mp/type-data.h new file mode 100644 index 00000000..46a2b2fc --- /dev/null +++ b/include/mp/type-data.h @@ -0,0 +1,46 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_DATA_H +#define MP_PROXY_TYPE_DATA_H + +#include + +namespace mp { +template +concept IsSpanOf = + std::convertible_to> && + std::constructible_from; + +template +concept IsByteSpan = + IsSpanOf || + IsSpanOf || + IsSpanOf || + IsSpanOf; + +//! Generic ::capnp::Data field builder for any C++ type that can be converted +//! to a span of bytes, like std::vector or std::array, or custom +//! blob types like uint256 or PKHash with data() and size() methods pointing to +//! bytes. +template +void CustomBuildField(TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output) +requires (std::is_same_v && IsByteSpan) +{ + auto data = std::span{value}; + auto result = output.init(data.size()); + memcpy(result.begin(), data.data(), data.size()); +} + +template +decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) +requires (std::is_same_v && IsByteSpan) +{ + using ByteType = decltype(std::span{std::declval().begin(), std::declval().end()})::element_type; + const kj::byte *begin{input.get().begin()}, *end{input.get().end()}; + return read_dest.construct(reinterpret_cast(begin), reinterpret_cast(end)); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_DATA_H diff --git a/include/mp/type-decay.h b/include/mp/type-decay.h new file mode 100644 index 00000000..7b203c86 --- /dev/null +++ b/include/mp/type-decay.h @@ -0,0 +1,38 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_DECAY_H +#define MP_PROXY_TYPE_DECAY_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList, + Priority<0>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::forward(value)); +} + +template +void CustomBuildField(TypeList, Priority<0>, InvokeContext& invoke_context, Value&& value, Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::forward(value)); +} + +template +void CustomBuildField(TypeList, + Priority<0>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::forward(value)); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_DECAY_H diff --git a/include/mp/type-exception.h b/include/mp/type-exception.h new file mode 100644 index 00000000..3e2fcac2 --- /dev/null +++ b/include/mp/type-exception.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_EXCEPTION_H +#define MP_PROXY_TYPE_EXCEPTION_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + const std::exception& value, + Output&& output) +{ + BuildField(TypeList(), invoke_context, output, std::string(value.what())); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_EXCEPTION_H diff --git a/include/mp/type-function.h b/include/mp/type-function.h new file mode 100644 index 00000000..bf00c581 --- /dev/null +++ b/include/mp/type-function.h @@ -0,0 +1,67 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_FUNCTION_H +#define MP_PROXY_TYPE_FUNCTION_H + +#include + +namespace mp { +//! Adapter to convert ProxyCallback object call to function object call. +template +class ProxyCallbackImpl final : public ProxyCallback> +{ + using Fn = std::function; + Fn m_fn; + +public: + ProxyCallbackImpl(Fn fn) : m_fn(std::move(fn)) {} + Result call(Args&&... args) override { return m_fn(std::forward(args)...); } +}; + +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value& value, + Output&& output) +{ + if (value) { + using Interface = typename decltype(output.get())::Calls; + using Callback = ProxyCallbackImpl; + output.set(kj::heap>( + std::make_shared(std::forward(value)), invoke_context.connection)); + } +} + +// ProxyCallFn class is needed because c++11 doesn't support auto lambda parameters. +// It's equivalent c++14: [invoke_context](auto&& params) { +// invoke_context->call(std::forward(params)...) +template +struct ProxyCallFn +{ + InvokeContext m_proxy; + + template + decltype(auto) operator()(CallParams&&... params) { return this->m_proxy->call(std::forward(params)...); } +}; + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + if (input.has()) { + using Interface = typename Decay::Calls; + auto client = std::make_shared>( + input.get(), &invoke_context.connection, /* destroy_connection= */ false); + return read_dest.construct(ProxyCallFn{std::move(client)}); + } + return read_dest.construct(); +}; +} // namespace mp + +#endif // MP_PROXY_TYPE_FUNCTION_H diff --git a/include/mp/type-interface.h b/include/mp/type-interface.h new file mode 100644 index 00000000..99adf2ab --- /dev/null +++ b/include/mp/type-interface.h @@ -0,0 +1,112 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_INTERFACE_H +#define MP_PROXY_TYPE_INTERFACE_H + +#include + +namespace mp { +template +kj::Own MakeProxyServer(InvokeContext& context, std::shared_ptr impl) +{ + return kj::heap>(std::move(impl), context.connection); +} + +template +kj::Own CustomMakeProxyServer(InvokeContext& context, std::shared_ptr&& impl) +{ + return MakeProxyServer(context, std::move(impl)); +} + +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output, + typename Decay::Calls* enable = nullptr) +{ + if (value) { + using Interface = typename decltype(output.get())::Calls; + output.set(CustomMakeProxyServer(invoke_context, std::shared_ptr(value.release()))); + } +} + +template +void CustomBuildField(TypeList>, + Priority<2>, + InvokeContext& invoke_context, + Value&& value, + Output&& output, + typename Decay::Calls* enable = nullptr) +{ + if (value) { + using Interface = typename decltype(output.get())::Calls; + output.set(CustomMakeProxyServer(invoke_context, std::move(value))); + } +} + +template +void CustomBuildField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Impl& value, + Output&& output, + typename decltype(output.get())::Calls* enable = nullptr) +{ + // Disable deleter so proxy server object doesn't attempt to delete the + // wrapped implementation when the proxy client is destroyed or + // disconnected. + using Interface = typename decltype(output.get())::Calls; + output.set(CustomMakeProxyServer(invoke_context, std::shared_ptr(&value, [](Impl*){}))); +} + +template +std::unique_ptr MakeProxyClient(InvokeContext& context, typename Interface::Client&& client) +{ + return std::make_unique>( + std::move(client), &context.connection, /* destroy_connection= */ false); +} + +template +std::unique_ptr CustomMakeProxyClient(InvokeContext& context, typename Interface::Client&& client) +{ + return MakeProxyClient(context, kj::mv(client)); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename Decay::Calls* enable = nullptr) +{ + using Interface = typename Decay::Calls; + if (input.has()) { + return read_dest.construct( + CustomMakeProxyClient(invoke_context, std::move(input.get()))); + } + return read_dest.construct(); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename Decay::Calls* enable = nullptr) +{ + using Interface = typename Decay::Calls; + if (input.has()) { + return read_dest.construct( + CustomMakeProxyClient(invoke_context, std::move(input.get()))); + } + return read_dest.construct(); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_INTERFACE_H diff --git a/include/mp/type-map.h b/include/mp/type-map.h new file mode 100644 index 00000000..bc1b2276 --- /dev/null +++ b/include/mp/type-map.h @@ -0,0 +1,52 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_MAP_H +#define MP_PROXY_TYPE_MAP_H + +#include +#include +#include + +namespace mp { +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + // FIXME dededup with vector handler above + auto list = output.init(value.size()); + size_t i = 0; + for (const auto& elem : value) { + BuildField(TypeList>(), invoke_context, + ListOutput(list, i), elem); + ++i; + } +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + for (auto item : data) { + ReadField(TypeList>(), invoke_context, + Make(item), + ReadDestEmplace( + TypeList>(), [&](auto&&... args) -> auto& { + return *value.emplace(std::forward(args)...).first; + })); + } + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_MAP_H diff --git a/include/mp/type-message.h b/include/mp/type-message.h new file mode 100644 index 00000000..d80f43c8 --- /dev/null +++ b/include/mp/type-message.h @@ -0,0 +1,64 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_MESSAGE_H +#define MP_PROXY_TYPE_MESSAGE_H + +#include + +namespace mp { +//! Overload CustomBuildField to serialize objects that have CustomBuildMessage +//! overloads. Defining a CustomBuildMessage overload is simpler than defining a +//! CustomBuildField overload because it only requires defining a normal +//! function, not a template function, but less flexible. +template +void CustomBuildField(TypeList, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output, + decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr) +{ + CustomBuildMessage(invoke_context, value, std::move(output.init())); +} + +//! Overload CustomReadField to serialize objects that have CustomReadMessage +//! overloads. Defining a CustomReadMessage overload is simpler than defining a +//! CustomReadField overload because it only requires defining a normal +//! function, not a template function, but less flexible. +template +decltype(auto) CustomReadField(TypeList, Priority<2>, InvokeContext& invoke_context, Reader&& reader, + ReadDest&& read_dest, + decltype(CustomReadMessage(invoke_context, reader.get(), + std::declval()))* enable = nullptr) +{ + return read_dest.update([&](auto& value) { if (reader.has()) CustomReadMessage(invoke_context, reader.get(), value); }); +} + +//! Helper for CustomPassField below. Call Accessor::init method if it has one, +//! otherwise do nothing. +template +decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr) +{ + return Accessor::init(message); +} + +template +::capnp::Void MaybeInit(...) +{ + return {}; +} + +//! Overload CustomPassField to serialize objects that have CustomPassMessage +//! overloads. Defining a CustomPassMessage overload is simpler than defining a +//! CustomPassField overload because it only requires defining a normal +//! function, not a template function, but less flexible. +template +auto CustomPassField(TypeList, ServerContext& server_context, Fn&& fn, Args&&... args) + -> decltype(CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), + MaybeGet(server_context.call_context.getResults()), nullptr)) +{ + CustomPassMessage(server_context, MaybeGet(server_context.call_context.getParams()), + MaybeInit(server_context.call_context.getResults()), + [&](LocalTypes... param) { fn.invoke(server_context, std::forward(args)..., param...); }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_MESSAGE_H diff --git a/include/mp/type-number.h b/include/mp/type-number.h new file mode 100644 index 00000000..9d269be6 --- /dev/null +++ b/include/mp/type-number.h @@ -0,0 +1,87 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_NUMBER_H +#define MP_PROXY_TYPE_NUMBER_H + +#include + +namespace mp { +template +LocalType BuildPrimitive(InvokeContext& invoke_context, + const Value& value, + TypeList, + typename std::enable_if::value>::type* enable = nullptr) +{ + using E = std::make_unsigned_t>; + using T = std::make_unsigned_t; + static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral/enum types"); + return static_cast(value); +} + +template +LocalType BuildPrimitive(InvokeContext& invoke_context, + const Value& value, + TypeList, + typename std::enable_if::value, int>::type* enable = nullptr) +{ + static_assert( + std::numeric_limits::lowest() <= std::numeric_limits::lowest(), "mismatched integral types"); + static_assert( + std::numeric_limits::max() >= std::numeric_limits::max(), "mismatched integral types"); + return value; +} + +template +LocalType BuildPrimitive(InvokeContext& invoke_context, + const Value& value, + TypeList, + typename std::enable_if::value>::type* enable = nullptr) +{ + static_assert(std::is_same::value, + "mismatched floating point types. please fix message.capnp type declaration to match wrapped interface"); + return value; +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename std::enable_if::value>::type* enable = 0) +{ + return read_dest.construct(static_cast(input.get())); +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename std::enable_if::value>::type* enable = nullptr) +{ + auto value = input.get(); + if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { + throw std::range_error("out of bound int received"); + } + return read_dest.construct(static_cast(value)); +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename std::enable_if::value>::type* enable = 0) +{ + auto value = input.get(); + static_assert(std::is_same::value, "floating point type mismatch"); + return read_dest.construct(value); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_NUMBER_H diff --git a/include/mp/type-optional.h b/include/mp/type-optional.h new file mode 100644 index 00000000..822508d5 --- /dev/null +++ b/include/mp/type-optional.h @@ -0,0 +1,48 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_OPTIONAL_H +#define MP_PROXY_TYPE_OPTIONAL_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + if (value) { + output.setHas(); + // FIXME: should std::move value if destvalue is rref? + BuildField(TypeList(), invoke_context, output, *value); + } +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + if (!input.has()) { + value.reset(); + } else if (value) { + ReadField(TypeList(), invoke_context, input, ReadDestUpdate(*value)); + } else { + ReadField(TypeList(), invoke_context, input, + ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { + value.emplace(std::forward(args)...); + return *value; + })); + } + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_OPTIONAL_H diff --git a/include/mp/type-pair.h b/include/mp/type-pair.h new file mode 100644 index 00000000..3af9c931 --- /dev/null +++ b/include/mp/type-pair.h @@ -0,0 +1,50 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_PAIR_H +#define MP_PROXY_TYPE_PAIR_H + +#include + +namespace mp { +// FIXME: Overload on output type instead of value type and switch to std::get and merge with next overload +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + auto pair = output.init(); + using Accessors = typename ProxyStruct::Accessors; + BuildField(TypeList(), invoke_context, Make>(pair), value.first); + BuildField(TypeList(), invoke_context, Make>(pair), value.second); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + const auto& pair = input.get(); + using Accessors = typename ProxyStruct::Reads>::Accessors; + + ReadField(TypeList(), invoke_context, Make>(pair), + ReadDestEmplace(TypeList(), [&](auto&&... key_args) -> auto& { + KeyLocalType* key = nullptr; + ReadField(TypeList(), invoke_context, Make>(pair), + ReadDestEmplace(TypeList(), [&](auto&&... value_args) -> auto& { + auto& ret = read_dest.construct(std::piecewise_construct, std::forward_as_tuple(key_args...), + std::forward_as_tuple(value_args...)); + key = &ret.first; + return ret.second; + })); + return *key; + })); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_PAIR_H diff --git a/include/mp/type-pointer.h b/include/mp/type-pointer.h new file mode 100644 index 00000000..5c79e8d2 --- /dev/null +++ b/include/mp/type-pointer.h @@ -0,0 +1,113 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_POINTER_H +#define MP_PROXY_TYPE_POINTER_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList, Priority<3>, InvokeContext& invoke_context, Value&& value, Output&& output) +{ + if (value) { + BuildField(TypeList(), invoke_context, output, *value); + } +} + +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + if (value) { + BuildField(TypeList(), invoke_context, output, *value); + } +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + if (value) { + ReadField(TypeList(), invoke_context, std::forward(input), ReadDestUpdate(*value)); + } + }); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<0>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + if (!input.has()) { + value.reset(); + } else if (value) { + ReadField(TypeList(), invoke_context, input, ReadDestUpdate(*value)); + } else { + ReadField(TypeList(), invoke_context, input, + ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { + value = std::make_shared(std::forward(args)...); + return *value; + })); + } + }); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + if (!input.has()) { + value.reset(); + return; + } + ReadField(TypeList(), invoke_context, std::forward(input), + ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { + value = std::make_shared(std::forward(args)...); + return *value; + })); + }); +} + +//! PassField override for C++ pointer arguments. +template +void PassField(Priority<1>, TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + const auto& params = server_context.call_context.getParams(); + const auto& input = Make(params); + + if (!input.want()) { + fn.invoke(server_context, std::forward(args)..., nullptr); + return; + } + + InvokeContext& invoke_context = server_context; + Decay param; + + MaybeReadField(std::integral_constant(), TypeList(), invoke_context, input, + ReadDestUpdate(param)); + + fn.invoke(server_context, std::forward(args)..., ¶m); + + auto&& results = server_context.call_context.getResults(); + MaybeBuildField(std::integral_constant(), TypeList(), invoke_context, + Make(results), param); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_POINTER_H diff --git a/include/mp/type-set.h b/include/mp/type-set.h new file mode 100644 index 00000000..ea60dc4d --- /dev/null +++ b/include/mp/type-set.h @@ -0,0 +1,48 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_SET_H +#define MP_PROXY_TYPE_SET_H + +#include +#include + +namespace mp { +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + // FIXME dededup with vector handler above + auto list = output.init(value.size()); + size_t i = 0; + for (const auto& elem : value) { + BuildField(TypeList(), invoke_context, ListOutput(list, i), elem); + ++i; + } +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + for (auto item : data) { + ReadField(TypeList(), invoke_context, Make(item), + ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { + return *value.emplace(std::forward(args)...).first; + })); + } + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_SET_H diff --git a/include/mp/type-string.h b/include/mp/type-string.h new file mode 100644 index 00000000..77d04acb --- /dev/null +++ b/include/mp/type-string.h @@ -0,0 +1,34 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_STRING_H +#define MP_PROXY_TYPE_STRING_H + +#include + +namespace mp { +template +void CustomBuildField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + auto result = output.init(value.size()); + memcpy(result.begin(), value.data(), value.size()); +} + +template +decltype(auto) CustomReadField(TypeList, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + auto data = input.get(); + return read_dest.construct(CharCast(data.begin()), data.size()); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_STRING_H diff --git a/include/mp/type-struct.h b/include/mp/type-struct.h new file mode 100644 index 00000000..d282e20e --- /dev/null +++ b/include/mp/type-struct.h @@ -0,0 +1,85 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_STRUCT_H +#define MP_PROXY_TYPE_STRUCT_H + +#include + +namespace mp { +template +void BuildOne(TypeList param, + InvokeContext& invoke_context, + Output&& output, + Value&& value, + typename std::enable_if < index::fields>::type * enable = nullptr) +{ + using Index = std::integral_constant; + using Struct = typename ProxyType::Struct; + using Accessor = typename std::tuple_element::Accessors>::type; + auto&& field_output = Make(output); + auto&& field_value = value.*ProxyType::get(Index()); + BuildField(TypeList>(), invoke_context, field_output, field_value); + BuildOne(param, invoke_context, output, value); +} + +template +void BuildOne(TypeList param, + InvokeContext& invoke_context, + Output&& output, + Value&& value, + typename std::enable_if::fields>::type* enable = nullptr) +{ +} + +template +void CustomBuildField(TypeList local_type, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output, + typename ProxyType::Struct* enable = nullptr) +{ + BuildOne<0>(local_type, invoke_context, output.init(), value); +} + +template +void ReadOne(TypeList param, + InvokeContext& invoke_context, + Input&& input, + Value&& value, + typename std::enable_if::fields>::type* enable = nullptr) +{ + using Index = std::integral_constant; + using Struct = typename ProxyType::Struct; + using Accessor = typename std::tuple_element::Accessors>::type; + const auto& struc = input.get(); + auto&& field_value = value.*ProxyType::get(Index()); + ReadField(TypeList>(), invoke_context, Make(struc), + ReadDestUpdate(field_value)); + ReadOne(param, invoke_context, input, value); +} + +template +void ReadOne(TypeList param, + InvokeContext& invoke_context, + Input& input, + Value& value, + typename std::enable_if::fields>::type* enable = nullptr) +{ +} + +template +decltype(auto) CustomReadField(TypeList param, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest, + typename ProxyType::Struct* enable = nullptr) +{ + return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_STRUCT_H diff --git a/include/mp/type-threadmap.h b/include/mp/type-threadmap.h new file mode 100644 index 00000000..683586fb --- /dev/null +++ b/include/mp/type-threadmap.h @@ -0,0 +1,41 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_THREADMAP_H +#define MP_PROXY_TYPE_THREADMAP_H + +#include + +namespace mp { +template <> +struct ProxyServer final : public virtual ThreadMap::Server +{ +public: + ProxyServer(Connection& connection); + kj::Promise makeThread(MakeThreadContext context) override; + Connection& m_connection; +}; + +template +void CustomBuildField(TypeList<>, + Priority<1>, + InvokeContext& invoke_context, + Output&& output, + typename std::enable_if::value>::type* enable = nullptr) +{ + output.set(kj::heap>(invoke_context.connection)); +} + +template +decltype(auto) CustomReadField(TypeList<>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + typename std::enable_if::value>::type* enable = nullptr) +{ + invoke_context.connection.m_thread_map = input.get(); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_THREADMAP_H diff --git a/include/mp/type-tuple.h b/include/mp/type-tuple.h new file mode 100644 index 00000000..50838872 --- /dev/null +++ b/include/mp/type-tuple.h @@ -0,0 +1,45 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_TUPLE_H +#define MP_PROXY_TYPE_TUPLE_H + +#include + +namespace mp { +// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + auto pair = output.init(); + using Accessors = typename ProxyStruct::Accessors; + BuildField(TypeList(), invoke_context, Make>(pair), std::get<0>(value)); + BuildField(TypeList(), invoke_context, Make>(pair), std::get<1>(value)); +} + +// TODO: Should generalize this to work with arbitrary length tuples, not just length 2-tuples. +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + const auto& pair = input.get(); + using Struct = ProxyStruct::Reads>; + using Accessors = typename Struct::Accessors; + ReadField(TypeList(), invoke_context, Make>(pair), + ReadDestUpdate(std::get<0>(value))); + ReadField(TypeList(), invoke_context, Make>(pair), + ReadDestUpdate(std::get<1>(value))); + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_TUPLE_H diff --git a/include/mp/type-vector.h b/include/mp/type-vector.h new file mode 100644 index 00000000..e4996e93 --- /dev/null +++ b/include/mp/type-vector.h @@ -0,0 +1,71 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_VECTOR_H +#define MP_PROXY_TYPE_VECTOR_H + +#include +#include + +namespace mp { +template +void CustomBuildField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Value&& value, + Output&& output) +{ + // FIXME dedup with set handler below + auto list = output.init(value.size()); + size_t i = 0; + for (auto it = value.begin(); it != value.end(); ++it, ++i) { + BuildField(TypeList(), invoke_context, ListOutput(list, i), *it); + } +} + +inline static bool BuildPrimitive(InvokeContext& invoke_context, std::vector::const_reference value, TypeList) +{ + return value; +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + value.reserve(data.size()); + for (auto item : data) { + ReadField(TypeList(), invoke_context, Make(item), + ReadDestEmplace(TypeList(), [&](auto&&... args) -> auto& { + value.emplace_back(std::forward(args)...); + return value.back(); + })); + } + }); +} + +template +decltype(auto) CustomReadField(TypeList>, + Priority<1>, + InvokeContext& invoke_context, + Input&& input, + ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + value.reserve(data.size()); + for (auto item : data) { + value.push_back(ReadField(TypeList(), invoke_context, Make(item), ReadDestTemp())); + } + }); +} +} // namespace mp + +#endif // MP_PROXY_TYPE_VECTOR_H diff --git a/include/mp/type-void.h b/include/mp/type-void.h new file mode 100644 index 00000000..0a887680 --- /dev/null +++ b/include/mp/type-void.h @@ -0,0 +1,23 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef MP_PROXY_TYPE_VOID_H +#define MP_PROXY_TYPE_VOID_H + +#include + +namespace mp { +template +::capnp::Void BuildPrimitive(InvokeContext& invoke_context, Value&&, TypeList<::capnp::Void>) +{ + return {}; +} + +template +void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_context, ::capnp::Void, Output&& output) +{ +} +} // namespace mp + +#endif // MP_PROXY_TYPE_VOID_H diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp index 08c77abf..8f34e82a 100644 --- a/src/mp/proxy.cpp +++ b/src/mp/proxy.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/test/mp/test/foo-types.h b/test/mp/test/foo-types.h index 347be20b..246b1948 100644 --- a/test/mp/test/foo-types.h +++ b/test/mp/test/foo-types.h @@ -6,6 +6,17 @@ #define MP_TEST_FOO_TYPES_H #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace mp { namespace test {