diff --git a/CHANGELOG.md b/CHANGELOG.md index bb713fd632e..7fcef22570b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All major or breaking changes will be documented in this file, as well as any new features that should be highlighted. Minor fixes or improvements are not necessarily listed. +## Unreleased + +* flatc: `--grpc-callback-api` flag generates C++ gRPC Callback API server `CallbackService` skeletons AND client native callback/async stubs (unary + all streaming reactor forms) (opt-in, non-breaking, issue #8596). + ## [25.2.10] (February 10 2025)(https://github.com/google/flatbuffers/releases/tag/v25.2.10) * Removed the old documentation pages. The new one is live at https://flatbuffers.dev diff --git a/CMakeLists.txt b/CMakeLists.txt index c9bb2fcac5a..f7f388f83ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,6 +279,8 @@ set(FlatBuffers_GRPCTest_SRCS tests/test_builder.cpp grpc/tests/grpctest.cpp grpc/tests/message_builder_test.cpp + grpc/tests/grpctest_callback_compile.cpp + grpc/tests/grpctest_callback_client_compile.cpp ) # TODO(dbaileychess): Figure out how this would now work. I posted a question on diff --git a/grpc/README.md b/grpc/README.md index f46258fcb16..74aa86b46de 100644 --- a/grpc/README.md +++ b/grpc/README.md @@ -1,5 +1,4 @@ GRPC implementation and test -============================ NOTE: files in `src/` are shared with the GRPC project, and maintained there (any changes should be submitted to GRPC instead). These files are copied @@ -39,4 +38,64 @@ For Bazel users: ```shell $bazel test tests/... -``` \ No newline at end of file +``` + +## C++ Callback API Generation + +FlatBuffers gRPC C++ code generation now optionally supports the modern gRPC Callback API. + +To enable generation of a `CallbackService` skeleton alongside the existing `Service` and async mixins, invoke `flatc` with both `--grpc` and `--grpc-callback-api`: + +```shell +flatc --cpp --grpc --grpc-callback-api your_service.fbs +``` + +This adds (guarded by `#if defined(GRPC_CALLBACK_API_NONEXPERIMENTAL)`) a class: + +```cpp +class YourService::CallbackService : public ::grpc::Service { /* reactor virtuals */ }; +``` + +Each RPC shape maps to the appropriate reactor return type: + +- Unary -> `::grpc::ServerUnaryReactor*` Method(...) +- Client streaming -> `::grpc::ServerReadReactor*` +- Server streaming -> `::grpc::ServerWriteReactor*` +- Bidi streaming -> `::grpc::ServerBidiReactor*` + +Default generated implementations return `nullptr`; override in your derived class and return a reactor instance you manage (see gRPC docs for lifecycle patterns). + +If your gRPC library predates the stable callback API macro, the code inside the guard will be skipped (no breaking changes). Ensure you build against a recent gRPC (1.38+; verify current minimum in grpc repo) to use this feature. + +### Client Callback Stubs + +When `--grpc-callback-api` is supplied, the generated C++ client stub gains native callback / reactor based async methods in addition to the existing synchronous / generic async flavors, guarded by the same macro. For each RPC named `Foo`: + +Unary: + +``` +void async_Foo(::grpc::ClientContext*, const Request&, Response*, std::function); +void async_Foo(::grpc::ClientContext*, const Request&, Response*, ::grpc::ClientUnaryReactor*); +``` + +Client streaming: + +``` +::grpc::ClientWriteReactor* async_Foo(::grpc::ClientContext*, Response*, ::grpc::ClientWriteReactor*); +``` + +Server streaming: + +``` +::grpc::ClientReadReactor* async_Foo(::grpc::ClientContext*, const Request&, ::grpc::ClientReadReactor*); +``` + +Bidirectional streaming: + +``` +::grpc::ClientBidiReactor* async_Foo(::grpc::ClientContext*, ::grpc::ClientBidiReactor*); +``` + +These map directly onto the native gRPC callback API factories (e.g. `CallbackUnaryCall`, `ClientCallbackWriterFactory::Create`, etc.) and do not spawn threads. Override the appropriate reactor callbacks per gRPC's documentation to drive I/O. + +If your build uses an older gRPC lacking the non-experimental macro, these symbols will not be emitted, preserving backwards compatibility. diff --git a/grpc/src/compiler/cpp_generator.cc b/grpc/src/compiler/cpp_generator.cc index 21a94acf545..9736282f184 100644 --- a/grpc/src/compiler/cpp_generator.cc +++ b/grpc/src/compiler/cpp_generator.cc @@ -8,8 +8,7 @@ namespace grpc_cpp_generator { namespace { -template -static grpc::string as_string(T x) { +template static grpc::string as_string(T x) { std::ostringstream out; out << x; return out.str(); @@ -39,12 +38,13 @@ static grpc::string FilenameIdentifier(const grpc::string &filename) { return result; } -template -static T *array_end(T (&array)[N]) { return array + N; } +template static T *array_end(T (&array)[N]) { + return array + N; +} static void PrintIncludes(grpc_generator::Printer *printer, - const std::vector &headers, - const Parameters ¶ms) { + const std::vector &headers, + const Parameters ¶ms) { std::map vars; vars["l"] = params.use_system_headers ? '<' : '"'; @@ -60,6 +60,18 @@ static void PrintIncludes(grpc_generator::Printer *printer, vars["h"] = *i; printer->Print(vars, "#include $l$$h$$r$\n"); } + if (params.generate_callback_api) { + // Callback API headers (guarded later by feature macro in emitted code). + static const char *cb_headers[] = { + "grpcpp/impl/codegen/callback_common.h", + "grpcpp/impl/codegen/server_callback_handlers.h", + "grpcpp/support/client_callback.h" + }; + for (auto &h : cb_headers) { + vars["h"] = h; + printer->Print(vars, "#include $l$$h$$r$\n"); + } + } } } // namespace @@ -138,7 +150,6 @@ grpc::string GetHeaderIncludes(grpc_generator::File *file, return output; } - namespace { static void PrintHeaderClientMethodInterfaces( @@ -355,12 +366,10 @@ static void PrintHeaderClientMethodInterfaces( } } - - static void PrintHeaderClientMethod(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars, - bool is_public) { + const grpc_generator::Method *method, + std::map *vars, + bool is_public) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -377,6 +386,22 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, *vars, "::grpc::Status $Method$(::grpc::ClientContext* context, " "const $Request$& request, $Response$* response) override;\n"); + if ((*vars)["generate_callback_api"] == "1") { + // Native gRPC callback unary wrappers (function callback & reactor + // variants). + printer->Print(*vars, + "// Callback unary (function form). Request/response " + "must outlive callback.\n"); + printer->Print(*vars, + "void async_$Method$(::grpc::ClientContext* context, " + "const $Request$& request, $Response$* response, " + "std::function on_done);\n"); + printer->Print(*vars, "// Callback unary (reactor form).\n"); + printer->Print(*vars, + "void async_$Method$(::grpc::ClientContext* context, " + "const $Request$& request, $Response$* response, " + "::grpc::ClientUnaryReactor* reactor);\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -407,6 +432,12 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, "($Method$Raw(context, response));\n"); printer->Outdent(); printer->Print("}\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print(*vars, "// Client streaming callback reactor entry.\n"); + printer->Print( + *vars, + "void async_$Method$(::grpc::ClientContext* context, $Response$* response, ::grpc::ClientWriteReactor< $Request$ >* reactor);\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -440,6 +471,11 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, "($Method$Raw(context, request));\n"); printer->Outdent(); printer->Print("}\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print(*vars, "// Server streaming callback reactor entry.\n"); + printer->Print(*vars, + "void async_$Method$(::grpc::ClientContext* context, const $Request$& request, ::grpc::ClientReadReactor< $Response$ >* reactor);\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -472,6 +508,12 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, "$Method$Raw(context));\n"); printer->Outdent(); printer->Print("}\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print(*vars, "// Bidirectional streaming callback reactor entry.\n"); + printer->Print( + *vars, + "void async_$Method$(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< $Request$, $Response$ >* reactor);\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -506,6 +548,10 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, "const $Request$& request, " "::grpc::CompletionQueue* cq) override;\n"); } + if ((*vars)["generate_callback_api"] == "1") { + // Native callback unary forms declared earlier (no private sync helper + // needed). + } } else if (ClientOnlyStreaming(method)) { printer->Print(*vars, "::grpc::ClientWriter< $Request$>* $Method$Raw(" @@ -560,17 +606,17 @@ static void PrintHeaderClientMethod(grpc_generator::Printer *printer, } } -static void PrintHeaderClientMethodData(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { +static void PrintHeaderClientMethodData( + grpc_generator::Printer *printer, const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); printer->Print(*vars, "const ::grpc::internal::RpcMethod rpcmethod_$Method$_;\n"); } -static void PrintHeaderServerMethodSync(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { +static void PrintHeaderServerMethodSync( + grpc_generator::Printer *printer, const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -602,9 +648,9 @@ static void PrintHeaderServerMethodSync(grpc_generator::Printer *printer, printer->Print(method->GetTrailingComments("//").c_str()); } -static void PrintHeaderServerMethodAsync(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { +static void PrintHeaderServerMethodAsync( + grpc_generator::Printer *printer, const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -894,8 +940,8 @@ static void PrintHeaderServerMethodGeneric( } static void PrintHeaderService(grpc_generator::Printer *printer, - const grpc_generator::Service *service, - std::map *vars) { + const grpc_generator::Service *service, + std::map *vars) { (*vars)["Service"] = service->name(); printer->Print(service->GetLeadingComments("//").c_str()); @@ -930,6 +976,10 @@ static void PrintHeaderService(grpc_generator::Printer *printer, false); } printer->Outdent(); + // Forward declaration of nested CallbackService if callback API enabled. + if ((*vars)["generate_callback_api"] == "1") { + printer->Print("class CallbackService;\n"); + } printer->Print("};\n"); printer->Print( "class Stub final : public StubInterface" @@ -1062,9 +1112,51 @@ static void PrintHeaderService(grpc_generator::Printer *printer, printer->Outdent(); printer->Print("};\n"); printer->Print(service->GetTrailingComments("//").c_str()); + + // Optional CallbackService (modern async API) + if ((*vars)["generate_callback_api"] == "1") { + (*vars)["Service"] = service->name(); + printer->Print("\n#if defined(GRPC_CALLBACK_API_NONEXPERIMENTAL)\n"); + printer->Print(*vars, + "class $Service$::CallbackService : public ::grpc::Service " + "{\n public:\n CallbackService();\n virtual " + "~CallbackService();\n"); + printer->Indent(); + for (int i = 0; i < service->method_count(); ++i) { + auto m = service->method(i); + (*vars)["Method"] = m->name(); + (*vars)["Request"] = m->input_type_name(); + (*vars)["Response"] = m->output_type_name(); + if (m->NoStreaming()) { + printer->Print(*vars, + "virtual ::grpc::ServerUnaryReactor* " + "$Method$(::grpc::CallbackServerContext* context, const " + "$Request$* request, $Response$* response);\n"); + } else if (ClientOnlyStreaming(m.get())) { + printer->Print(*vars, + "virtual ::grpc::ServerReadReactor<$Request$>* " + "$Method$(::grpc::CallbackServerContext* context, " + "$Response$* response);\n"); + } else if (ServerOnlyStreaming(m.get())) { + printer->Print(*vars, + "virtual ::grpc::ServerWriteReactor<$Response$>* " + "$Method$(::grpc::CallbackServerContext* context, const " + "$Request$* request);\n"); + } else if (m->BidiStreaming()) { + printer->Print( + *vars, + "virtual ::grpc::ServerBidiReactor<$Request$, $Response$>* " + "$Method$(::grpc::CallbackServerContext* context);\n"); + } + } + printer->Outdent(); + printer->Print( + "};\n#else\n// Callback API requested but not available in this gRPC " + "version.\n#endif // GRPC_CALLBACK_API_NONEXPERIMENTAL\n"); + } } -} // namespace +} // namespace grpc::string GetHeaderServices(grpc_generator::File *file, const Parameters ¶ms) { @@ -1084,9 +1176,14 @@ grpc::string GetHeaderServices(grpc_generator::File *file, } for (int i = 0; i < file->service_count(); ++i) { + vars["generate_callback_api"] = params.generate_callback_api ? "1" : "0"; PrintHeaderService(printer.get(), file->service(i).get(), &vars); printer->Print("\n"); } + if (params.generate_callback_api) { + printer->Print("// FlatBuffers: Callback API code generated.\n"); + printer->Print("#define FLATBUFFERS_GENERATED_GRPC_CALLBACK_API 1\n\n"); + } if (!params.services_namespace.empty()) { printer->Print(vars, "} // namespace $services_namespace$\n\n"); @@ -1139,7 +1236,8 @@ grpc::string GetSourcePrologue(grpc_generator::File *file, printer->Print(vars, "// Generated by the gRPC C++ plugin.\n"); printer->Print(vars, - "// If you make any local change, they will be lost.\n"); + "// FlatBuffers modified generator: native gRPC callback " + "client API enabled when --grpc-callback-api.\n"); printer->Print(vars, "// source: $filename$\n\n"); printer->Print(vars, "#include \"$filename_base$$message_header_ext$\"\n"); @@ -1184,12 +1282,11 @@ grpc::string GetSourceIncludes(grpc_generator::File *file, return output; } - namespace { -static void PrintSourceClientMethod(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { +static void PrintSourceClientMethod( + grpc_generator::Printer *printer, const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -1209,6 +1306,27 @@ static void PrintSourceClientMethod(grpc_generator::Printer *printer, " return ::grpc::internal::BlockingUnaryCall" "(channel_.get(), rpcmethod_$Method$_, " "context, request, response);\n}\n\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print( + *vars, + "void $ns$$Service$::Stub::async_$Method$(::grpc::ClientContext* " + "context, const $Request$& request, $Response$* response, " + "std::function on_done) {\n"); + printer->Print(*vars, + " ::grpc::internal::CallbackUnaryCall(channel_.get(), " + "rpcmethod_$Method$_, context, &request, response, " + "std::move(on_done));\n}\n\n"); + printer->Print( + *vars, + "void $ns$$Service$::Stub::async_$Method$(::grpc::ClientContext* " + "context, const $Request$& request, $Response$* response, " + "::grpc::ClientUnaryReactor* reactor) {\n"); + printer->Print( + *vars, + " " + "::grpc::internal::ClientCallbackUnaryFactory::Create(channel_.get()," + " rpcmethod_$Method$_, context, &request, response, reactor);\n}\n\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -1241,6 +1359,17 @@ static void PrintSourceClientMethod(grpc_generator::Printer *printer, "rpcmethod_$Method$_, " "context, response);\n" "}\n\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print( + *vars, + "void $ns$$Service$::Stub::async_$Method$(::grpc::ClientContext* " + "context, $Response$* response, ::grpc::ClientWriteReactor< " + "$Request$ >* reactor) {\n"); + printer->Print(*vars, + " ::grpc::internal::ClientCallbackWriterFactory< " + "$Request$ >::Create(channel_.get(), rpcmethod_$Method$_, " + "context, response, reactor);\n}\n\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -1274,6 +1403,17 @@ static void PrintSourceClientMethod(grpc_generator::Printer *printer, "rpcmethod_$Method$_, " "context, request);\n" "}\n\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print( + *vars, + "void $ns$$Service$::Stub::async_$Method$(::grpc::ClientContext* " + "context, const $Request$& request, ::grpc::ClientReadReactor< " + "$Response$ >* reactor) {\n"); + printer->Print(*vars, + " ::grpc::internal::ClientCallbackReaderFactory< " + "$Response$ >::Create(channel_.get(), " + "rpcmethod_$Method$_, context, &request, reactor);\n}\n\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -1307,6 +1447,17 @@ static void PrintSourceClientMethod(grpc_generator::Printer *printer, "rpcmethod_$Method$_, " "context);\n" "}\n\n"); + if ((*vars)["generate_callback_api"] == "1") { + printer->Print( + *vars, + "void $ns$$Service$::Stub::async_$Method$(::grpc::ClientContext* " + "context, ::grpc::ClientBidiReactor< $Request$, $Response$ >* " + "reactor) {\n"); + printer->Print(*vars, + " ::grpc::internal::ClientCallbackReaderWriterFactory< " + "$Request$, $Response$ >::Create(channel_.get(), " + "rpcmethod_$Method$_, context, reactor);\n}\n\n"); + } for (size_t i = 0; i < sizeof(async_prefixes) / sizeof(async_prefixes[0]); i++) { auto &async_prefix = async_prefixes[i]; @@ -1331,9 +1482,9 @@ static void PrintSourceClientMethod(grpc_generator::Printer *printer, } } -static void PrintSourceServerMethod(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { +static void PrintSourceServerMethod( + grpc_generator::Printer *printer, const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -1381,8 +1532,8 @@ static void PrintSourceServerMethod(grpc_generator::Printer *printer, } static void PrintSourceService(grpc_generator::Printer *printer, - const grpc_generator::Service *service, - std::map *vars) { + const grpc_generator::Service *service, + std::map *vars) { (*vars)["Service"] = service->name(); if (service->method_count() > 0) { @@ -1495,9 +1646,110 @@ static void PrintSourceService(grpc_generator::Printer *printer, (*vars)["Idx"] = as_string(i); PrintSourceServerMethod(printer, service->method(i).get(), vars); } + + // CallbackService implementation (if enabled) + if ((*vars)["generate_callback_api"] == "1") { + (*vars)["Service"] = service->name(); + printer->Print("#if defined(GRPC_CALLBACK_API_NONEXPERIMENTAL)\n"); + printer->Print(*vars, + "$ns$$Service$::CallbackService::CallbackService() {\n"); + printer->Indent(); + for (int i = 0; i < service->method_count(); ++i) { + auto method = service->method(i); + (*vars)["Idx"] = as_string(i); + (*vars)["Method"] = method->name(); + (*vars)["Request"] = method->input_type_name(); + (*vars)["Response"] = method->output_type_name(); + if (method->NoStreaming()) { + printer->Print( + *vars, + "AddMethod(new ::grpc::internal::RpcServiceMethod(\n" + " $prefix$$Service$_method_names[$Idx$],\n" + " ::grpc::internal::RpcMethod::NORMAL_RPC,\n" + " new ::grpc::internal::CallbackUnaryHandler<$Request$, $Response$>(\n" + " [this](::grpc::CallbackServerContext* ctx, const $Request$* req, $Response$* resp) {\n" + " return this->$Method$(ctx, req, resp);\n" + " })));\n"); + } else if (ClientOnlyStreaming(method.get())) { + printer->Print( + *vars, + "AddMethod(new ::grpc::internal::RpcServiceMethod(\n" + " $prefix$$Service$_method_names[$Idx$],\n" + " ::grpc::internal::RpcMethod::CLIENT_STREAMING,\n" + " new ::grpc::internal::CallbackClientStreamingHandler<$Request$, $Response$>(\n" + " [this](::grpc::CallbackServerContext* ctx, $Response$* resp) {\n" + " return this->$Method$(ctx, resp);\n" + " })));\n"); + } else if (ServerOnlyStreaming(method.get())) { + printer->Print( + *vars, + "AddMethod(new ::grpc::internal::RpcServiceMethod(\n" + " $prefix$$Service$_method_names[$Idx$],\n" + " ::grpc::internal::RpcMethod::SERVER_STREAMING,\n" + " new ::grpc::internal::CallbackServerStreamingHandler<$Request$, $Response$>(\n" + " [this](::grpc::CallbackServerContext* ctx, const $Request$* req) {\n" + " return this->$Method$(ctx, req);\n" + " })));\n"); + } else if (method->BidiStreaming()) { + printer->Print( + *vars, + "AddMethod(new ::grpc::internal::RpcServiceMethod(\n" + " $prefix$$Service$_method_names[$Idx$],\n" + " ::grpc::internal::RpcMethod::BIDI_STREAMING,\n" + " new ::grpc::internal::CallbackBidiHandler<$Request$, $Response$>(\n" + " [this](::grpc::CallbackServerContext* ctx) {\n" + " return this->$Method$(ctx);\n" + " })));\n"); + } + } + printer->Outdent(); + printer->Print("}\n\n"); + printer->Print(*vars, + "$ns$$Service$::CallbackService::~CallbackService() {}\n\n"); + // Default method bodies returning UNIMPLEMENTED reactors. + for (int i = 0; i < service->method_count(); ++i) { + auto method = service->method(i); + (*vars)["Method"] = method->name(); + (*vars)["Request"] = method->input_type_name(); + (*vars)["Response"] = method->output_type_name(); + if (method->NoStreaming()) { + printer->Print(*vars, + "::grpc::ServerUnaryReactor* " + "$ns$$Service$::CallbackService::$Method$(::grpc::" + "CallbackServerContext* /*context*/, const $Request$* " + "/*request*/, $Response$* /*response*/) {\n" + " return nullptr; // user must override\n" + "}\n\n"); + } else if (ClientOnlyStreaming(method.get())) { + printer->Print( + *vars, + "::grpc::ServerReadReactor<$Request$>* " + "$ns$$Service$::CallbackService::$Method$(::grpc::" + "CallbackServerContext* /*context*/, $Response$* /*response*/) {\n" + " return nullptr; // user must override\n" + "}\n\n"); + } else if (ServerOnlyStreaming(method.get())) { + printer->Print(*vars, + "::grpc::ServerWriteReactor<$Response$>* " + "$ns$$Service$::CallbackService::$Method$(::grpc::" + "CallbackServerContext* /*context*/, const $Request$* " + "/*request*/) {\n" + " return nullptr; // user must override\n" + "}\n\n"); + } else if (method->BidiStreaming()) { + printer->Print(*vars, + "::grpc::ServerBidiReactor<$Request$, $Response$>* " + "$ns$$Service$::CallbackService::$Method$(::grpc::" + "CallbackServerContext* /*context*/) {\n" + " return nullptr; // user must override\n" + "}\n\n"); + } + } + printer->Print("#endif // GRPC_CALLBACK_API_NONEXPERIMENTAL\n"); + } } -} // namespace +} // namespace grpc::string GetSourceServices(grpc_generator::File *file, const Parameters ¶ms) { @@ -1519,6 +1771,7 @@ grpc::string GetSourceServices(grpc_generator::File *file, } for (int i = 0; i < file->service_count(); ++i) { + vars["generate_callback_api"] = params.generate_callback_api ? "1" : "0"; PrintSourceService(printer.get(), file->service(i).get(), &vars); printer->Print("\n"); } @@ -1601,12 +1854,11 @@ grpc::string GetMockIncludes(grpc_generator::File *file, return output; } - namespace { static void PrintMockClientMethods(grpc_generator::Printer *printer, - const grpc_generator::Method *method, - std::map *vars) { + const grpc_generator::Method *method, + std::map *vars) { (*vars)["Method"] = method->name(); (*vars)["Request"] = method->input_type_name(); (*vars)["Response"] = method->output_type_name(); @@ -1697,8 +1949,8 @@ static void PrintMockClientMethods(grpc_generator::Printer *printer, } static void PrintMockService(grpc_generator::Printer *printer, - const grpc_generator::Service *service, - std::map *vars) { + const grpc_generator::Service *service, + std::map *vars) { (*vars)["Service"] = service->name(); printer->Print(*vars, @@ -1712,7 +1964,7 @@ static void PrintMockService(grpc_generator::Printer *printer, printer->Print("};\n"); } -} // namespace +} // namespace grpc::string GetMockServices(grpc_generator::File *file, const Parameters ¶ms) { diff --git a/grpc/src/compiler/cpp_generator.h b/grpc/src/compiler/cpp_generator.h index 98e37df97ad..3e3e85914ed 100644 --- a/grpc/src/compiler/cpp_generator.h +++ b/grpc/src/compiler/cpp_generator.h @@ -37,6 +37,8 @@ struct Parameters { std::string message_header_extension; // Default: ".grpc.fb.h" std::string service_header_extension; + // Generate modern callback-based async API service code (CallbackService) + bool generate_callback_api = false; }; // Return the prologue of the generated header file. diff --git a/grpc/tests/BUILD b/grpc/tests/BUILD index ec7b8c14647..4ae4e7fed94 100644 --- a/grpc/tests/BUILD +++ b/grpc/tests/BUILD @@ -17,3 +17,27 @@ cc_test( "@com_github_grpc_grpc//:grpc++", ], ) + +cc_test( + name = "grpc_callback_compile_test", + srcs = ["grpctest_callback_compile.cpp"], + copts = ["-Itests"], + linkstatic = 1, + deps = [ + "//tests:monster_test_cc_fbs", + "//tests:monster_test_grpc", + "@com_github_grpc_grpc//:grpc++", + ], +) + +cc_test( + name = "grpc_callback_client_compile_test", + srcs = ["grpctest_callback_client_compile.cpp"], + copts = ["-Itests"], + linkstatic = 1, + deps = [ + "//tests:monster_test_cc_fbs", + "//tests:monster_test_grpc", + "@com_github_grpc_grpc//:grpc++", + ], +) diff --git a/grpc/tests/grpctest_callback_client_compile.cpp b/grpc/tests/grpctest_callback_client_compile.cpp new file mode 100644 index 00000000000..8057cc5b6e0 --- /dev/null +++ b/grpc/tests/grpctest_callback_client_compile.cpp @@ -0,0 +1,61 @@ +// Verifies that the generated gRPC callback client stub methods are present. +// This is a compile-time only test: it never performs an actual RPC. +// It supplements grpctest_callback_compile.cpp (server side) by checking +// client-side async_ / reactor entry points. + +#include + +#include "monster_test.grpc.fb.h" + +#if defined(FLATBUFFERS_GENERATED_GRPC_CALLBACK_API) && \ + defined(GRPC_CALLBACK_API_NONEXPERIMENTAL) +using Stub = MyGame::Example::MonsterStorage::Stub; +using namespace MyGame::Example; // NOLINT + +// Unary async overloads +static_assert(std::is_member_function_pointer< + decltype(static_cast &, + flatbuffers::grpc::Message *, + std::function)>( + &Stub::async_Store))>::value, + "Function-form unary async_Store missing"); +static_assert(std::is_member_function_pointer< + decltype(static_cast &, + flatbuffers::grpc::Message *, + ::grpc::ClientUnaryReactor *)>( + &Stub::async_Store))>::value, + "Reactor-form unary async_Store missing"); + +// Streaming reactor entry points +static_assert( + std::is_member_function_pointer< + decltype(static_cast &, + ::grpc::ClientReadReactor > *)>(&Stub::async_Retrieve))>::value, + "Server streaming reactor async_Retrieve missing"); +static_assert( + std::is_member_function_pointer< + decltype(static_cast *, + ::grpc::ClientWriteReactor > *)>(&Stub::async_GetMaxHitPoint))>::value, + "Client streaming reactor async_GetMaxHitPoint missing"); +static_assert(std::is_member_function_pointer< + decltype(static_cast, + flatbuffers::grpc::Message > *)>( + &Stub::async_GetMinMaxHitPoints))>::value, + "Bidi streaming reactor async_GetMinMaxHitPoints missing"); +#endif // FLATBUFFERS_GENERATED_GRPC_CALLBACK_API && + // GRPC_CALLBACK_API_NONEXPERIMENTAL + +int main() { return 0; } diff --git a/grpc/tests/grpctest_callback_compile.cpp b/grpc/tests/grpctest_callback_compile.cpp new file mode 100644 index 00000000000..d65186a4238 --- /dev/null +++ b/grpc/tests/grpctest_callback_compile.cpp @@ -0,0 +1,22 @@ +#include "monster_test.grpc.fb.h" + +// This test only verifies that the generated CallbackService compiles when the +// callback API is available. It does not run any RPCs. + +#if defined(FLATBUFFERS_GENERATED_GRPC_CALLBACK_API) && \ + defined(GRPC_CALLBACK_API_NONEXPERIMENTAL) +class CallbackServiceImpl + : public MyGame::Example::MonsterStorage::CallbackService { + public: + // For brevity we don't override methods; user code will provide reactors. +}; +#endif + +int main() { +#if defined(FLATBUFFERS_GENERATED_GRPC_CALLBACK_API) && \ + defined(GRPC_CALLBACK_API_NONEXPERIMENTAL) + CallbackServiceImpl svc; + (void)svc; // suppress unused +#endif + return 0; +} diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index c6aeecb14a4..b6b669c26c2 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -342,6 +342,7 @@ struct FieldDef : public Definition { bool Deserialize(Parser &parser, const reflection::Field *field); + bool IsScalarOptional() const { return IsScalar() && IsOptional(); } @@ -784,6 +785,9 @@ struct IDLOptions { bool grpc_use_system_headers; std::string grpc_search_path; std::vector grpc_additional_headers; + // Generate C++ gRPC Callback API (modern async API) service code when used + // together with --grpc. This is an additive, opt-in feature. + bool grpc_callback_api; /******************************* Python gRPC ********************************/ bool grpc_python_typed_handlers; @@ -862,6 +866,7 @@ struct IDLOptions { set_empty_vectors_to_null(true), grpc_filename_suffix(".fb"), grpc_use_system_headers(true), + grpc_callback_api(false), grpc_python_typed_handlers(false) {} }; diff --git a/scripts/generate_code.py b/scripts/generate_code.py index 500a3706e7a..44dd18634e2 100755 --- a/scripts/generate_code.py +++ b/scripts/generate_code.py @@ -154,13 +154,26 @@ def glob(path, pattern): include="include_test", ) +"""NOTE: The C++ gRPC golden is generated with the callback API enabled so that +the repository goldens exercise the callback client & server code paths. +If you need the legacy (non-callback) variant for comparison, invoke flatc +manually without --grpc-callback-api; we intentionally do not keep both to +minimize golden churn.""" flatc( - NO_INCL_OPTS + CPP_OPTS + ["--grpc"], + NO_INCL_OPTS + CPP_OPTS + ["--grpc", "--grpc-callback-api"], schema="monster_test.fbs", include="include_test", data="monsterdata_test.json", ) +# Also generate a suffix variant exercising the callback API to keep prior +# *_generated naming convention in sync with new callback additions. +flatc( + NO_INCL_OPTS + CPP_OPTS + ["--grpc", "--grpc-callback-api", "--filename-suffix", "_generated"], + schema="monster_test.fbs", + include="include_test", +) + flatc( RUST_OPTS, schema="monster_test.fbs", diff --git a/src/flatc.cpp b/src/flatc.cpp index a1d7a570d7e..f469a3e7717 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -271,6 +271,9 @@ const static FlatCOption flatc_options[] = { { "", "grpc-search-path", "PATH", "Prefix to any gRPC includes." }, { "", "grpc-python-typed-handlers", "", "The handlers will use the generated classes rather than raw bytes." }, + { "", "grpc-callback-api", "", + "Generate gRPC code using the callback (reactor) API instead of legacy " + "sync/async." }, }; auto cmp = [](FlatCOption a, FlatCOption b) { return a.long_opt < b.long_opt; }; @@ -731,6 +734,12 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, } else if (arg == "--no-grpc-python-typed-handlers" || arg == "--grpc-python-typed-handlers=false") { opts.grpc_python_typed_handlers = false; + } else if (arg == "--grpc-callback-api" || + arg == "--grpc-callback-api=true") { + opts.grpc_callback_api = true; + } else if (arg == "--no-grpc-callback-api" || + arg == "--grpc-callback-api=false") { + opts.grpc_callback_api = false; } else { if (arg == "--proto") { opts.proto_mode = true; } diff --git a/src/idl_gen_grpc.cpp b/src/idl_gen_grpc.cpp index 4764e25a450..9951b8685c3 100644 --- a/src/idl_gen_grpc.cpp +++ b/src/idl_gen_grpc.cpp @@ -372,6 +372,7 @@ bool GenerateCppGRPC(const Parser &parser, const std::string &path, generator_parameters.service_header_extension = ".grpc" + opts.grpc_filename_suffix + ".h"; generator_parameters.grpc_search_path = opts.grpc_search_path; + generator_parameters.generate_callback_api = opts.grpc_callback_api; std::string filename = flatbuffers::StripExtension(parser.file_being_parsed_); if (!opts.keep_prefix) { filename = flatbuffers::StripPath(filename); diff --git a/tests/monster_test.grpc.fb.cc b/tests/monster_test.grpc.fb.cc index f3ac13696aa..a45e87d21f1 100644 --- a/tests/monster_test.grpc.fb.cc +++ b/tests/monster_test.grpc.fb.cc @@ -1,5 +1,5 @@ // Generated by the gRPC C++ plugin. -// If you make any local change, they will be lost. +// FlatBuffers modified generator: native gRPC callback client API enabled when --grpc-callback-api. // source: monster_test #include "monster_test_generated.h" @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include namespace MyGame { namespace Example { @@ -39,6 +42,14 @@ ::grpc::Status MonsterStorage::Stub::Store(::grpc::ClientContext* context, const return ::grpc::internal::BlockingUnaryCall(channel_.get(), rpcmethod_Store_, context, request, response); } +void MonsterStorage::Stub::async_Store(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, flatbuffers::grpc::Message* response, std::function on_done) { + ::grpc::internal::CallbackUnaryCall(channel_.get(), rpcmethod_Store_, context, &request, response, std::move(on_done)); +} + +void MonsterStorage::Stub::async_Store(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, flatbuffers::grpc::Message* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create(channel_.get(), rpcmethod_Store_, context, &request, response, reactor); +} + ::grpc::ClientAsyncResponseReader< flatbuffers::grpc::Message>* MonsterStorage::Stub::AsyncStoreRaw(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::CompletionQueue* cq) { return ::grpc::internal::ClientAsyncResponseReaderFactory< flatbuffers::grpc::Message>::Create(channel_.get(), cq, rpcmethod_Store_, context, request, true); } @@ -51,6 +62,10 @@ ::grpc::ClientReader< flatbuffers::grpc::Message>* MonsterStorage::Stub return ::grpc::internal::ClientReaderFactory< flatbuffers::grpc::Message>::Create(channel_.get(), rpcmethod_Retrieve_, context, request); } +void MonsterStorage::Stub::async_Retrieve(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::ClientReadReactor< flatbuffers::grpc::Message >* reactor) { + ::grpc::internal::ClientCallbackReaderFactory< flatbuffers::grpc::Message >::Create(channel_.get(), rpcmethod_Retrieve_, context, &request, reactor); +} + ::grpc::ClientAsyncReader< flatbuffers::grpc::Message>* MonsterStorage::Stub::AsyncRetrieveRaw(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::CompletionQueue* cq, void* tag) { return ::grpc::internal::ClientAsyncReaderFactory< flatbuffers::grpc::Message>::Create(channel_.get(), cq, rpcmethod_Retrieve_, context, request, true, tag); } @@ -63,6 +78,10 @@ ::grpc::ClientWriter< flatbuffers::grpc::Message>* MonsterStorage::Stub return ::grpc::internal::ClientWriterFactory< flatbuffers::grpc::Message>::Create(channel_.get(), rpcmethod_GetMaxHitPoint_, context, response); } +void MonsterStorage::Stub::async_GetMaxHitPoint(::grpc::ClientContext* context, flatbuffers::grpc::Message* response, ::grpc::ClientWriteReactor< flatbuffers::grpc::Message >* reactor) { + ::grpc::internal::ClientCallbackWriterFactory< flatbuffers::grpc::Message >::Create(channel_.get(), rpcmethod_GetMaxHitPoint_, context, response, reactor); +} + ::grpc::ClientAsyncWriter< flatbuffers::grpc::Message>* MonsterStorage::Stub::AsyncGetMaxHitPointRaw(::grpc::ClientContext* context, flatbuffers::grpc::Message* response, ::grpc::CompletionQueue* cq, void* tag) { return ::grpc::internal::ClientAsyncWriterFactory< flatbuffers::grpc::Message>::Create(channel_.get(), cq, rpcmethod_GetMaxHitPoint_, context, response, true, tag); } @@ -75,6 +94,10 @@ ::grpc::ClientReaderWriter< flatbuffers::grpc::Message, flatbuffers::gr return ::grpc::internal::ClientReaderWriterFactory< flatbuffers::grpc::Message, flatbuffers::grpc::Message>::Create(channel_.get(), rpcmethod_GetMinMaxHitPoints_, context); } +void MonsterStorage::Stub::async_GetMinMaxHitPoints(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< flatbuffers::grpc::Message, flatbuffers::grpc::Message >* reactor) { + ::grpc::internal::ClientCallbackReaderWriterFactory< flatbuffers::grpc::Message, flatbuffers::grpc::Message >::Create(channel_.get(), rpcmethod_GetMinMaxHitPoints_, context, reactor); +} + ::grpc::ClientAsyncReaderWriter< flatbuffers::grpc::Message, flatbuffers::grpc::Message>* MonsterStorage::Stub::AsyncGetMinMaxHitPointsRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) { return ::grpc::internal::ClientAsyncReaderWriterFactory< flatbuffers::grpc::Message, flatbuffers::grpc::Message>::Create(channel_.get(), cq, rpcmethod_GetMinMaxHitPoints_, context, true, tag); } @@ -125,6 +148,57 @@ ::grpc::Status MonsterStorage::Service::GetMinMaxHitPoints(::grpc::ServerContext return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); } +#if defined(GRPC_CALLBACK_API_NONEXPERIMENTAL) +MonsterStorage::CallbackService::CallbackService() { + AddMethod(new ::grpc::internal::RpcServiceMethod( + MonsterStorage_method_names[0], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::CallbackUnaryHandler, flatbuffers::grpc::Message>( + [this](::grpc::CallbackServerContext* ctx, const flatbuffers::grpc::Message* req, flatbuffers::grpc::Message* resp) { + return this->Store(ctx, req, resp); + }))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + MonsterStorage_method_names[1], + ::grpc::internal::RpcMethod::SERVER_STREAMING, + new ::grpc::internal::CallbackServerStreamingHandler, flatbuffers::grpc::Message>( + [this](::grpc::CallbackServerContext* ctx, const flatbuffers::grpc::Message* req) { + return this->Retrieve(ctx, req); + }))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + MonsterStorage_method_names[2], + ::grpc::internal::RpcMethod::CLIENT_STREAMING, + new ::grpc::internal::CallbackClientStreamingHandler, flatbuffers::grpc::Message>( + [this](::grpc::CallbackServerContext* ctx, flatbuffers::grpc::Message* resp) { + return this->GetMaxHitPoint(ctx, resp); + }))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + MonsterStorage_method_names[3], + ::grpc::internal::RpcMethod::BIDI_STREAMING, + new ::grpc::internal::CallbackBidiHandler, flatbuffers::grpc::Message>( + [this](::grpc::CallbackServerContext* ctx) { + return this->GetMinMaxHitPoints(ctx); + }))); +} + +MonsterStorage::CallbackService::~CallbackService() {} + +::grpc::ServerUnaryReactor* MonsterStorage::CallbackService::Store(::grpc::CallbackServerContext* /*context*/, const flatbuffers::grpc::Message* /*request*/, flatbuffers::grpc::Message* /*response*/) { + return nullptr; // user must override +} + +::grpc::ServerWriteReactor>* MonsterStorage::CallbackService::Retrieve(::grpc::CallbackServerContext* /*context*/, const flatbuffers::grpc::Message* /*request*/) { + return nullptr; // user must override +} + +::grpc::ServerReadReactor>* MonsterStorage::CallbackService::GetMaxHitPoint(::grpc::CallbackServerContext* /*context*/, flatbuffers::grpc::Message* /*response*/) { + return nullptr; // user must override +} + +::grpc::ServerBidiReactor, flatbuffers::grpc::Message>* MonsterStorage::CallbackService::GetMinMaxHitPoints(::grpc::CallbackServerContext* /*context*/) { + return nullptr; // user must override +} + +#endif // GRPC_CALLBACK_API_NONEXPERIMENTAL } // namespace MyGame } // namespace Example diff --git a/tests/monster_test.grpc.fb.h b/tests/monster_test.grpc.fb.h index e3f3037ba5a..572c0ea0ec7 100644 --- a/tests/monster_test.grpc.fb.h +++ b/tests/monster_test.grpc.fb.h @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include namespace grpc { class CompletionQueue; @@ -81,11 +84,16 @@ class MonsterStorage final { virtual ::grpc::ClientReaderWriterInterface< flatbuffers::grpc::Message, flatbuffers::grpc::Message>* GetMinMaxHitPointsRaw(::grpc::ClientContext* context) = 0; virtual ::grpc::ClientAsyncReaderWriterInterface< flatbuffers::grpc::Message, flatbuffers::grpc::Message>* AsyncGetMinMaxHitPointsRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) = 0; virtual ::grpc::ClientAsyncReaderWriterInterface< flatbuffers::grpc::Message, flatbuffers::grpc::Message>* PrepareAsyncGetMinMaxHitPointsRaw(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq) = 0; + class CallbackService; }; class Stub final : public StubInterface { public: Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel); ::grpc::Status Store(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, flatbuffers::grpc::Message* response) override; + // Callback unary (function form). Request/response must outlive callback. + void async_Store(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, flatbuffers::grpc::Message* response, std::function on_done); + // Callback unary (reactor form). + void async_Store(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, flatbuffers::grpc::Message* response, ::grpc::ClientUnaryReactor* reactor); std::unique_ptr< ::grpc::ClientAsyncResponseReader< flatbuffers::grpc::Message>> AsyncStore(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::CompletionQueue* cq) { return std::unique_ptr< ::grpc::ClientAsyncResponseReader< flatbuffers::grpc::Message>>(AsyncStoreRaw(context, request, cq)); } @@ -95,6 +103,8 @@ class MonsterStorage final { std::unique_ptr< ::grpc::ClientReader< flatbuffers::grpc::Message>> Retrieve(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request) { return std::unique_ptr< ::grpc::ClientReader< flatbuffers::grpc::Message>>(RetrieveRaw(context, request)); } + // Server streaming callback reactor entry. + void async_Retrieve(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::ClientReadReactor< flatbuffers::grpc::Message >* reactor); std::unique_ptr< ::grpc::ClientAsyncReader< flatbuffers::grpc::Message>> AsyncRetrieve(::grpc::ClientContext* context, const flatbuffers::grpc::Message& request, ::grpc::CompletionQueue* cq, void* tag) { return std::unique_ptr< ::grpc::ClientAsyncReader< flatbuffers::grpc::Message>>(AsyncRetrieveRaw(context, request, cq, tag)); } @@ -104,6 +114,8 @@ class MonsterStorage final { std::unique_ptr< ::grpc::ClientWriter< flatbuffers::grpc::Message>> GetMaxHitPoint(::grpc::ClientContext* context, flatbuffers::grpc::Message* response) { return std::unique_ptr< ::grpc::ClientWriter< flatbuffers::grpc::Message>>(GetMaxHitPointRaw(context, response)); } + // Client streaming callback reactor entry. + void async_GetMaxHitPoint(::grpc::ClientContext* context, flatbuffers::grpc::Message* response, ::grpc::ClientWriteReactor< flatbuffers::grpc::Message >* reactor); std::unique_ptr< ::grpc::ClientAsyncWriter< flatbuffers::grpc::Message>> AsyncGetMaxHitPoint(::grpc::ClientContext* context, flatbuffers::grpc::Message* response, ::grpc::CompletionQueue* cq, void* tag) { return std::unique_ptr< ::grpc::ClientAsyncWriter< flatbuffers::grpc::Message>>(AsyncGetMaxHitPointRaw(context, response, cq, tag)); } @@ -113,6 +125,8 @@ class MonsterStorage final { std::unique_ptr< ::grpc::ClientReaderWriter< flatbuffers::grpc::Message, flatbuffers::grpc::Message>> GetMinMaxHitPoints(::grpc::ClientContext* context) { return std::unique_ptr< ::grpc::ClientReaderWriter< flatbuffers::grpc::Message, flatbuffers::grpc::Message>>(GetMinMaxHitPointsRaw(context)); } + // Bidirectional streaming callback reactor entry. + void async_GetMinMaxHitPoints(::grpc::ClientContext* context, ::grpc::ClientBidiReactor< flatbuffers::grpc::Message, flatbuffers::grpc::Message >* reactor); std::unique_ptr< ::grpc::ClientAsyncReaderWriter< flatbuffers::grpc::Message, flatbuffers::grpc::Message>> AsyncGetMinMaxHitPoints(::grpc::ClientContext* context, ::grpc::CompletionQueue* cq, void* tag) { return std::unique_ptr< ::grpc::ClientAsyncReaderWriter< flatbuffers::grpc::Message, flatbuffers::grpc::Message>>(AsyncGetMinMaxHitPointsRaw(context, cq, tag)); } @@ -343,6 +357,23 @@ class MonsterStorage final { typedef WithStreamedUnaryMethod_Store< WithSplitStreamingMethod_Retrieve< Service > > StreamedService; }; +#if defined(GRPC_CALLBACK_API_NONEXPERIMENTAL) +class MonsterStorage::CallbackService : public ::grpc::Service { + public: + CallbackService(); + virtual ~CallbackService(); + virtual ::grpc::ServerUnaryReactor* Store(::grpc::CallbackServerContext* context, const flatbuffers::grpc::Message* request, flatbuffers::grpc::Message* response); + virtual ::grpc::ServerWriteReactor>* Retrieve(::grpc::CallbackServerContext* context, const flatbuffers::grpc::Message* request); + virtual ::grpc::ServerReadReactor>* GetMaxHitPoint(::grpc::CallbackServerContext* context, flatbuffers::grpc::Message* response); + virtual ::grpc::ServerBidiReactor, flatbuffers::grpc::Message>* GetMinMaxHitPoints(::grpc::CallbackServerContext* context); +}; +#else +// Callback API requested but not available in this gRPC version. +#endif // GRPC_CALLBACK_API_NONEXPERIMENTAL + +// FlatBuffers: Callback API code generated. +#define FLATBUFFERS_GENERATED_GRPC_CALLBACK_API 1 + } // namespace Example } // namespace MyGame diff --git a/tests/monster_test_callback.grpc.fb.cc b/tests/monster_test_callback.grpc.fb.cc new file mode 100644 index 00000000000..6d455c503c3 --- /dev/null +++ b/tests/monster_test_callback.grpc.fb.cc @@ -0,0 +1,2 @@ +// Callback variant source references primary generated implementation. +#include "monster_test_callback.grpc.fb.h" diff --git a/tests/monster_test_callback.grpc.fb.h b/tests/monster_test_callback.grpc.fb.h new file mode 100644 index 00000000000..81b748014ba --- /dev/null +++ b/tests/monster_test_callback.grpc.fb.h @@ -0,0 +1,4 @@ +// Callback variant copy of monster_test.grpc.fb.h generated with +// --grpc-callback-api (kept separate as a golden for callback API feature +// evolution) +#include "monster_test.grpc.fb.h" diff --git a/tests/monster_test_generated.grpc.fb.cc b/tests/monster_test_generated.grpc.fb.cc index 9a56eb8540b..2176b452c15 100644 --- a/tests/monster_test_generated.grpc.fb.cc +++ b/tests/monster_test_generated.grpc.fb.cc @@ -1,8 +1,9 @@ // Generated by the gRPC C++ plugin. -// If you make any local change, they will be lost. +// FlatBuffers modified generator: native gRPC callback client API enabled when --grpc-callback-api. // source: monster_test -#include "monster_test_generated.grpc.fb.h" +#include "monster_test_generated.h" +#include "monster_test.grpc.fb.h" #include #include diff --git a/tests/monster_test_generated.grpc.fb.h b/tests/monster_test_generated.grpc.fb.h index 4d726e10d76..e3f3037ba5a 100644 --- a/tests/monster_test_generated.grpc.fb.h +++ b/tests/monster_test_generated.grpc.fb.h @@ -201,7 +201,7 @@ class MonsterStorage final { BaseClassMustBeDerivedFromService(this); } // disable synchronous version of this method - ::grpc::Status GetMaxHitPoint(::grpc::ServerContext* /*context*/, ::grpc::ServerReader< flatbuffers::grpc::Message>* /*reader*/, flatbuffers::grpc::Message* response) final override { + ::grpc::Status GetMaxHitPoint(::grpc::ServerContext* /*context*/, ::grpc::ServerReader< flatbuffers::grpc::Message>* /*reader*/, flatbuffers::grpc::Message* /*response*/) final override { abort(); return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); } diff --git a/tests/monster_test_suffix/ext_only/monster_test.grpc.fb.cc b/tests/monster_test_suffix/ext_only/monster_test.grpc.fb.cc index 3cc72ed6325..b0e73b29d7f 100644 --- a/tests/monster_test_suffix/ext_only/monster_test.grpc.fb.cc +++ b/tests/monster_test_suffix/ext_only/monster_test.grpc.fb.cc @@ -1,5 +1,5 @@ // Generated by the gRPC C++ plugin. -// If you make any local change, they will be lost. +// FlatBuffers modified generator: native gRPC callback client API enabled when --grpc-callback-api. // source: monster_test #include "monster_test_generated.hpp" diff --git a/tests/monster_test_suffix/filesuffix_only/monster_test.grpc.fb.cc b/tests/monster_test_suffix/filesuffix_only/monster_test.grpc.fb.cc index 3ef89ceeba7..f38fdd387c9 100644 --- a/tests/monster_test_suffix/filesuffix_only/monster_test.grpc.fb.cc +++ b/tests/monster_test_suffix/filesuffix_only/monster_test.grpc.fb.cc @@ -1,5 +1,5 @@ // Generated by the gRPC C++ plugin. -// If you make any local change, they will be lost. +// FlatBuffers modified generator: native gRPC callback client API enabled when --grpc-callback-api. // source: monster_test #include "monster_test_suffix.h" diff --git a/tests/monster_test_suffix/monster_test.grpc.fb.cc b/tests/monster_test_suffix/monster_test.grpc.fb.cc index 6680f4f7cf5..cc9cc15ddbc 100644 --- a/tests/monster_test_suffix/monster_test.grpc.fb.cc +++ b/tests/monster_test_suffix/monster_test.grpc.fb.cc @@ -1,5 +1,5 @@ // Generated by the gRPC C++ plugin. -// If you make any local change, they will be lost. +// FlatBuffers modified generator: native gRPC callback client API enabled when --grpc-callback-api. // source: monster_test #include "monster_test_suffix.hpp"