Rework magic8ball example

Improve QML layout, GRPC communications, animation logic and
documentation. Simplify the GRPC server implementation removing Qt
parts.

Task-number: QTBUG-129571
Pick-to: 6.8
Change-Id: I8913ca3b52950d950dd5862bd986b222f0e6405e
Reviewed-by: Jaime Resano <Jaime.RESANO-AISA@qt.io>
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
Jaime Resano 2024-10-14 12:54:52 +02:00
parent 2683203c5d
commit c761459e59
18 changed files with 484 additions and 481 deletions

View File

@ -6,36 +6,161 @@ import QtQuick
Item {
id: root
signal closed()
property string currentAnswerText: ""
property string nextAnswerText: ""
property bool canRequestAnswer: true
property alias closingAnimation: closeAnimator
property alias openingAnimation: openAnimator
property alias animationText: result.text
state: "DISABLED"
states: [
State {
name: "DISABLED"
PropertyChanges {
root.canRequestAnswer: true
answerText.visible: false
waitingPlaceholder.visible: false
}
},
State {
name: "WAITING"
PropertyChanges {
root.canRequestAnswer: false
answerText.visible: false
waitingPlaceholder.visible: true
}
},
State {
name: "SHOWING"
PropertyChanges {
root.canRequestAnswer: false
answerText.visible: true
waitingPlaceholder.visible: false
}
},
State {
name: "PAUSED"
PropertyChanges {
root.canRequestAnswer: true
answerText.visible: true
waitingPlaceholder.visible: false
}
},
State {
name: "HIDING"
PropertyChanges {
root.canRequestAnswer: false
answerText.visible: true
waitingPlaceholder.visible: false
}
}
]
transitions: [
Transition {
from: "DISABLED,HIDING"
to: "WAITING"
SequentialAnimation {
id: waitingAnimation
loops: Animation.Infinite
ScaleAnimation {
target: root
mode: "ZoomIn"
}
ScaleAnimation {
target: root
mode: "ZoomOut"
}
}
onRunningChanged: {
if (!running) {
root.currentAnswerText = root.nextAnswerText;
root.nextAnswerText = "";
if (!root.currentAnswerText) {
root.state = "DISABLED";
}
}
}
},
Transition {
from: "WAITING,HIDING"
to: "SHOWING"
ScaleAnimation {
target: root
mode: "ZoomIn"
}
onRunningChanged: {
if (!running) {
root.state = "PAUSED";
}
}
},
Transition {
from: "PAUSED"
to: "HIDING"
ScaleAnimation {
target: root
mode: "ZoomOut"
}
onRunningChanged: {
if (!running) {
if (root.nextAnswerText) {
root.currentAnswerText = root.nextAnswerText;
root.nextAnswerText = "";
root.state = "SHOWING";
} else {
root.state = "WAITING";
}
}
}
}
]
function addAnswer(answer: string): void {
root.nextAnswerText = answer;
if (root.state == "WAITING") {
root.state = "SHOWING";
}
}
function startWaiting(): void {
if (root.state == "PAUSED") {
root.state = "HIDING";
return;
}
root.state = "WAITING";
}
function cancelAnimation(): void {
root.state = "DISABLED";
}
MagicText {
id: result
id: answerText
anchors.centerIn: parent
font.pointSize: text.length > 12 ? 14 : 16
font.pointSize: 20
color: "#2E53B6"
text: root.currentAnswerText
}
ScaleAnimator on scale {
id: openAnimator
target: result
from: 0
to: 1
duration: 2000
running: false
}
Row {
id: waitingPlaceholder
anchors.centerIn: parent
spacing: 12
ScaleAnimator on scale {
id: closeAnimator
target: result
from: 1
to: 0
duration: 2000
running: false
onStopped: root.closed()
Repeater {
model: 3
Rectangle {
width: 11
height: width
color: "#264BAF"
radius: 100
}
}
}
}

View File

@ -22,8 +22,6 @@ find_package(Qt6 REQUIRED COMPONENTS
qt_standard_project_setup()
add_subdirectory(grpc_server_example)
qt_add_executable(magic8ball
main.cpp
)
@ -50,11 +48,11 @@ qt_add_qml_module(magic8ball
VERSION 1.0
RESOURCE_PREFIX "/qt/qml"
QML_FILES
"WaitingAnimation.qml"
"AnimatedAnswer.qml"
"MagicText.qml"
"ProgressDot.qml"
"MagicBall.qml"
"Main.qml"
"ScaleAnimation.qml"
)
target_link_libraries(magic8ball PRIVATE
@ -71,3 +69,8 @@ install(TARGETS magic8ball
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)
add_subdirectory(server)
if(TARGET magic8ball_server)
add_dependencies(magic8ball magic8ball_server)
endif()

View File

@ -0,0 +1,113 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Shapes
Rectangle {
function addAnswer(answer: string): void {animatedAnswer.addAnswer(answer)}
function startWaiting(): void {animatedAnswer.startWaiting()}
function cancelAnimation(): void {animatedAnswer.cancelAnimation()}
property alias canRequestAnswer: animatedAnswer.canRequestAnswer
implicitWidth: 433
implicitHeight: width
color: "black"
radius: 300
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "#4b4b4b"
}
GradientStop {
position: 0.33
color: "#212121"
}
GradientStop {
position: 1.0
color: "#000000"
}
}
// Reflection decoration
Rectangle {
width: 300
height: width
anchors.centerIn: parent
anchors.horizontalCenterOffset: 6
radius: 300
color: "#bababa"
}
// Ball center
Rectangle {
anchors.centerIn: parent
width: 300
height: width
color: "black"
border.width: 1.5
border.color: "#bababa"
radius: 300
Shape {
id: ballTriangle
anchors.centerIn: parent
width: 250
height: 250
ShapePath {
strokeWidth: 4
strokeColor: "#213f94"
capStyle: ShapePath.RoundCap
fillGradient: RadialGradient {
centerX: ballTriangle.width / 2
centerY: ballTriangle.height / 2
focalX: centerX
focalY: centerY
centerRadius: 50
focalRadius: 0
GradientStop {
position: 0
color: "#1C2F60"
}
GradientStop {
position: 0.5
color: "#000547"
}
GradientStop {
position: 1
color: "#000324"
}
}
startX: 26
startY: 68
PathLine {
x: 125
y: 230
}
PathLine {
x: 224
y: 68
}
PathLine {
x: 26
y: 68
}
}
}
AnimatedAnswer {
id: animatedAnswer
anchors.centerIn: parent
}
}
}

View File

@ -2,10 +2,12 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Layouts
Text {
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
font.family: "Helvetica"
font.pointSize: 16

View File

@ -1,238 +1,119 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtGrpc
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Shapes
import QtQuick.Layouts
import qtgrpc.examples
import qtgrpc.examples.magic8ball
ApplicationWindow {
id: root
width: 665
height: width
minimumWidth: width
minimumHeight: height
property answerRequest answerReq
property string errorText: ""
property int errorCode: 0
//! [requestAnswerFunction]
function requestAnswer(question: string): void {
//! [requestAnswerFunction]
root.errorText = "";
magicBall.startWaiting();
//! [requestAnswerFunctionBody]
root.answerReq.question = question;
grpcClient.answerMethod(root.answerReq, finishCallback, errorCallback, grpcCallOptions);
}
//! [requestAnswerFunctionBody]
function finishCallback(response: answerResponse): void {
magicBall.addAnswer(response.message);
}
function errorCallback(error): void {
// error is received as a JavaScript object, but it is a QGrpcStatus instance
magicBall.cancelAnimation();
console.log(
`Error callback executed. Error message: "${error.message}" Code: ${error.code}`
);
root.errorText = error.message;
root.errorCode = error.code;
}
minimumWidth: rootLayout.implicitWidth + rootLayout.anchors.margins * 2
minimumHeight: rootLayout.implicitHeight + rootLayout.anchors.margins * 2
visible: true
title: qsTr("Magic-8-ball Qt GRPC Example")
Material.theme: Material.Light
font.pointSize: 18
property string textAnswer: ""
property string textError: ""
property answerRequest _answerReq
property answerResponse _answerResp
property var setResponse: function(value) { root._answerResp = value }
property var errorCallback: function() { console.log("Error can be handled also here") }
MagicText {
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width * 0.9
height: parent.height/3
color: "black"
text: qsTr("For fortune-telling and seeking advice ask the ball"
+ " a yes-no question and press the button.")
}
Rectangle {
id: magic8ball
anchors.centerIn: parent
width: 433
height: width
color: "#000000"
radius: 300
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.0; color: "#4b4b4b" }
GradientStop { position: 0.33; color: "#212121" }
GradientStop { position: 1.0; color: "#000000" }
}
}
Rectangle {
width: 244
height: width
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: 6
radius: 300
color: "#bababa"
}
Rectangle {
id: magic8ballCenter
anchors.centerIn: parent
width: 244
height: width
color: "black"
border.width: 1.5
border.color: "#bababa"
radius: 300
Shape {
anchors.centerIn: parent
width: 200
height: 200
ShapePath {
strokeWidth: 4
strokeColor: "#213f94"
capStyle: ShapePath.RoundCap
fillGradient: RadialGradient {
centerX: 100
centerY: 100
focalX: centerX
focalY: centerY
centerRadius: 50
focalRadius: 0
GradientStop { position: 0; color: "#1C2F60" }
GradientStop { position: 0.5; color: "#000547" }
GradientStop { position: 1; color: "#000324" }
}
startX: 10
startY: 40
PathLine { x: 100.5; y: 190 }
PathLine { x: 188; y: 40 }
PathLine { x: 10; y: 40 }
}
}
}
WaitingAnimation {
id: waitingAnimation
anchors.centerIn: parent
visible: false
}
AnimatedAnswer {
id: answer
anchors.centerIn: parent
visible: false
}
Connections {
target: grpcClient
function onErrorOccurred() {
root.textError = "No connection\nto\nserver"
}
}
Rectangle {
id: button
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
width: 200
height: 50
radius: 10
color: handler.pressed ? "#a5a5a5" : "#bebebe"
MagicText {
id: btnText
anchors.centerIn: parent
text: qsTr("Ask question")
color: "black"
}
TapHandler {
id: handler
onTapped: animationTimeout.start()
}
}
Connections {
target: answer.closingAnimation
function onStopped() {
answer.animationText = ""
answer.visible = false
waitingAnimation.visible = true
waitingAnimation.runAnimation = true
}
}
Connections {
target: waitingAnimation
function onRunAnimationChanged() {
if (!waitingAnimation.runAnimation) {
answer.visible = true
answer.openingAnimation.start()
}
}
}
Timer {
id: animationTimeout
interval: 5000
repeat: false
running: false
onTriggered: root.textAnswer = _answerResp.message
onRunningChanged: {
if (running) {
root.textError = ""
answer.closingAnimation.start()
root.sendRequest()
} else {
waitingAnimation.runAnimation = false
waitingAnimation.visible = false
answer.animationText = root.textError === "" ? root.textAnswer : root.textError
}
}
}
function sendRequest()
{
grpcClient.answerMethod(_answerReq, setResponse, errorCallback)
}
footer: MagicText {
text: root.textError === "" ? "" : "Please, start server: ../magic8ball/SimpleGrpcServer"
//! [channelOptions]
GrpcHttp2Channel {
id: grpcChannel
hostUri: "http://localhost:50051"
// Optionally, you can specify custom channel options here
// options: GrpcChannelOptions {}
}
//! [channelOptions]
//! [exampleServiceClient]
ExampleServiceClient {
id: grpcClient
channel: grpcChannel.channel
}
//! [exampleServiceClient]
GrpcHttp2Channel {
id: grpcChannel
hostUri: "http://localhost:50051"
options: GrpcChannelOptions {
//! [callOptions]
GrpcCallOptions {
id: grpcCallOptions
deadlineTimeout: 6000
}
//! [callOptions]
ColumnLayout {
id: rootLayout
anchors.margins: 10
anchors.fill: parent
spacing: 12
MagicText {
color: "black"
text: qsTr("Ask the ball a yes-no question and press the button.")
}
MagicBall {
id: magicBall
Layout.alignment: Qt.AlignCenter
}
TextField {
id: questionInput
Layout.alignment: Qt.AlignCenter
Layout.minimumWidth: 300
leftPadding: 10
rightPadding: 10
placeholderText: qsTr("Type here a question...")
}
Button {
onClicked: root.requestAnswer(questionInput.text)
enabled: magicBall.canRequestAnswer
Layout.alignment: Qt.AlignCenter
leftPadding: 16
rightPadding: 16
text: qsTr("Ask")
}
MagicText {
visible: root.errorText
text:
qsTr("Error: %1\n%2")
.arg(root.errorText)
.arg(root.errorCode == QtGrpc.StatusCode.Unavailable
? qsTr("Please, restart the server")
: "")
}
}
Component.onCompleted: {
_answerReq.message = "sleep"
}
}

View File

@ -1,12 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
Rectangle {
width: 11
height: width
color: "#264BAF"
radius: 100
}

View File

@ -0,0 +1,14 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
NumberAnimation {
property string mode: "ZoomIn"
property: "scale"
duration: 1000
easing.amplitude: 6.0
easing.period: 2.5
from: mode == "ZoomIn" ? 0.3 : 1.0
to: mode == "ZoomIn" ? 1.0 : 0.3
}

View File

@ -1,54 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
Rectangle {
id: root
property bool runAnimation: false
anchors.centerIn: parent
width: 100
height: width
color: "transparent"
Row {
id: scene
anchors.centerIn: parent
spacing: 12
Repeater {
model: 4
ProgressDot {}
}
}
ScaleAnimator on scale {
id: openning
target: root
from: 0.3
to: 1
duration: 1000
running: root.runAnimation
onStopped: closing.start()
easing.amplitude: 6.0
easing.period: 2.5
}
ScaleAnimator on scale {
id: closing
target: root
from: 1
to: 0.3
duration: 1000
running: false
onStopped: {
if (root.runAnimation)
openning.start()
}
easing.amplitude: 6.0
easing.period: 2.5
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -8,19 +8,18 @@
\meta tag {network,protobuf,grpc}
\title Magic 8 Ball
\brief Creating a HTTP2 connection between a Qt GRPC client and
a C++ gRPC server.
\brief Creating a HTTP2 connection between a Qt gRPC client and a C++ gRPC server.
Magic 8 ball shows an answer it receives from a server:
\image answer.webp
Magic 8 ball sends a question to a server and displays the received answer:
\image magic8ballScreenshot.webp "Magic 8 ball example screenshot"
Magic 8 ball has the following components:
The example code includes the following components:
\list
\li \c magic8ball Qt GRPC client application that includes
\li \c magic8ball Qt gRPC client application that uses
the \l {qt_add_protobuf}{qt_add_protobuf()} and
\l {qt_add_grpc}{qt_add_grpc()}
CMake functions for message and service Qt code generation.
\li \c SimpleGrpcServer application that calls C++ gRPC plugin
\li \c server application that calls C++ gRPC plugin
for generating server code and implementing simple server
logic.
\endlist
@ -28,32 +27,37 @@
\note you need the C++ gRPC plugin installed.
Find details here: \l {Module prerequisites}
Both components use generated messages from the protobuf schema
described in the \c {exampleservice.proto} file:
Both components use generated messages from the protobuf schema described in the
\c {exampleservice.proto} file:
\quotefromfile magic8ball/proto/exampleservice.proto
\skipto syntax = "proto3";
\printuntil
The client application connects to the \c localhost with port
\c 50051:
\quotefromfile magic8ball/Main.qml
\skipto id: grpcChannel
\printuntil hostUri: "http://localhost:50051"
The gRPC client is defined as a QML object \b{which is available after the code is compiled}.
\snippet magic8ball/Main.qml exampleServiceClient
The client service connects to the \c localhost with port \c {50051}, which is specified in the
gRPC channel options:
\snippet magic8ball/Main.qml channelOptions
And sends a request to the server part:
\quotefromfile magic8ball/Main.qml
\skipto function sendRequest()
\printuntil }
\snippet magic8ball/Main.qml requestAnswerFunction
\dots 8
\snippet magic8ball/Main.qml requestAnswerFunctionBody
Click the \uicontrol {Ask question} button to send
the request to the SimpleGrpcServer application.
\c answerMethod is a gRPC method that the client calls. It has four parameters: the request
object, a finish callback function, an error callback function and a
\l {GrpcCallOptions} object.
The SimpleGrpcServer application chooses a random answer from
the list of answers and sends the data to the client's port.
\quotefromfile magic8ball/grpc_server_example/serverrunner.cpp
\skipto Status ExampleServiceServiceImpl::answerMethod
\printuntil }
Click the \uicontrol {Ask} button to send the request to the magic8ball server.
After receiving a response the client application shows the answer.
\note You have to run the server in parallel with the client application.
The \c server application chooses a random answer from the list of answers and sends
the data to the client's port. It also checks that the request contains a non empty field
\c question. If the field is empty, it returns a \c StatusCode::INVALID_ARGUMENT
\snippet magic8ball/server/main.cpp answerMethod
After receiving a response, the client application shows the answer.
*/

View File

@ -1,15 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "serverrunner.h"
#include <QCoreApplication>
#include <memory>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto server = std::make_unique<ExampleServer>();
server->run();
return a.exec();
}

View File

@ -1,81 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "serverrunner.h"
#include "exampleservice.grpc.pb.h"
#include <QThread>
#include <QDebug>
#include <QRandomGenerator>
#include <grpc++/grpc++.h>
#include <memory>
#include <array>
#include <random>
namespace {
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerWriter;
using grpc::Status;
using qtgrpc::examples::AnswerRequest;
using qtgrpc::examples::AnswerResponse;
using qtgrpc::examples::ExampleService;
static const std::array<std::string_view, 10> answers = {"Yes",
"Yep",
"Most\nlikely",
"It is\ncertain",
"No",
"Nope",
"Try later",
"Are you\nsure?",
"Maybe",
"Very\ndoubtful"};
// Generates random index value.
static int generateRandomIndex()
{
static std::uniform_int_distribution<int> dist(0, answers.size() - 1);
return dist(*QRandomGenerator::global());
}
// Logic and data behind the server's behavior.
class ExampleServiceServiceImpl final : public qtgrpc::examples::ExampleService::Service
{
grpc::Status answerMethod(grpc::ServerContext *, const AnswerRequest *request,
AnswerResponse *response) override;
};
}
Status ExampleServiceServiceImpl::answerMethod(grpc::ServerContext *,
const AnswerRequest *request,
AnswerResponse *response)
{
if (request->message() == "sleep")
QThread::msleep(2000);
response->set_message(std::string(answers[generateRandomIndex()]));
return Status();
}
void ExampleServer::run()
{
std::string serverUri("127.0.0.1:50051");
ExampleServiceServiceImpl service;
grpc::ServerBuilder builder;
builder.AddListeningPort(serverUri, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
if (!server) {
qDebug() << "Creating grpc::Server failed.";
return;
}
qDebug() << "Server listening on " << serverUri;
server->Wait();
}

View File

@ -1,13 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef SERVER_RUNNER_H
#define SERVER_RUNNER_H
class ExampleServer
{
public:
void run();
};
#endif // SERVER_RUNNER_H

View File

@ -1,17 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, [](){
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
[]() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
engine.loadFromModule("qtgrpc.examples.magic8ball", "Main");
return app.exec();

View File

@ -5,7 +5,7 @@ syntax = "proto3";
package qtgrpc.examples;
message AnswerRequest {
string message = 1;
string question = 1;
}
message AnswerResponse {

View File

@ -1,20 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(MagicServerRunner LANGUAGES CXX)
project(magic8ball_server LANGUAGES CXX)
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
# Qt6::Grpc module is not used directly in this project. But this allows to find Qt6::Grpc's
# dependencies without setting extra cmake module paths.
find_package(Qt6 COMPONENTS Grpc)
find_package(protobuf)
find_package(gRPC)
if(NOT TARGET gRPC::grpc_cpp_plugin OR NOT TARGET WrapProtoc::WrapProtoc
OR NOT TARGET gRPC::grpc++)
message(WARNING "Dependencies of QtGrpc test server not found. Skipping.")
message(WARNING "Dependencies of QtGrpc magic8ball_server not found. Skipping.")
return()
endif()
@ -53,44 +50,24 @@ add_custom_command(
)
set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE)
add_library(ServerRunner_grpc_gen STATIC ${generated_files})
target_include_directories(ServerRunner_grpc_gen
qt_add_executable(magic8ball_server
${generated_files}
main.cpp
)
target_include_directories(magic8ball_server
PRIVATE
${out_dir}
)
target_link_libraries(ServerRunner_grpc_gen
target_link_libraries(magic8ball_server
PRIVATE
protobuf::libprotobuf
gRPC::grpc++
)
add_library(MagicServerRunner
STATIC
serverrunner.cpp
serverrunner.h
)
target_include_directories(MagicServerRunner PRIVATE ${out_dir})
target_link_libraries(MagicServerRunner
PRIVATE
ServerRunner_grpc_gen
protobuf::libprotobuf
gRPC::grpc++
Qt6::Core
)
qt_add_executable(SimpleGrpcServer
main.cpp
)
target_link_libraries(SimpleGrpcServer PRIVATE
Qt6::Core
MagicServerRunner
)
install(TARGETS SimpleGrpcServer
install(TARGETS magic8ball_server
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"

View File

@ -0,0 +1,60 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "exampleservice.grpc.pb.h"
#include <grpc++/grpc++.h>
#include <array>
#include <iostream>
using namespace qtgrpc::examples;
// Logic and data behind the server's behavior.
class ExampleServiceImpl final : public ExampleService::Service
{
inline static std::array<std::string, 10> answers = {
"Yes", "Yep", "Most\nlikely", "It is\ncertain", "No",
"Nope", "Try later", "Are you\nsure?", "Maybe", "Very\ndoubtful"
};
std::string getRandomAnswer()
{
return answers.at(rand() % answers.size());
}
//! [answerMethod]
grpc::Status answerMethod(grpc::ServerContext *, const AnswerRequest *request,
AnswerResponse *response) override
{
if (request->question().empty()) {
std::cerr << "Question is empty" << std::endl;
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Question is empty");
}
std::cout << "Received question: " << request->question() << std::endl;
response->set_message(getRandomAnswer());
return grpc::Status();
};
//! [answerMethod]
};
int main(int argc, char *argv[])
{
ExampleServiceImpl service;
grpc::ServerBuilder builder;
builder.AddListeningPort("127.0.0.1:50051", grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
if (!server) {
std::cout << "Creating server failed." << std::endl;
return -1;
}
std::cout << "Server listening on port 50051" << std::endl;
server->Wait();
return 0;
}