Repository: vizor-games/InfraworldRuntime Branch: master Commit: c3798cd62543 Files: 25 Total size: 112.5 KB Directory structure: gitextract_0btcdab7/ ├── .gitignore ├── InfraworldRuntime.uplugin ├── LICENSE ├── README.md ├── Setup.bat ├── Setup.command ├── Setup.sh └── Source/ └── InfraworldRuntime/ ├── InfraWorldRuntime.Build.cs ├── Private/ │ ├── ChannelCredentials.cpp │ ├── GrpcUriValidator.cpp │ ├── InfraworldRuntime.cpp │ ├── RpcClient.cpp │ └── RpcClientWorker.cpp └── Public/ ├── CastUtils.h ├── ChannelCredentials.h ├── ChannelProvider.h ├── Conduit.h ├── GenUtils.h ├── GrpcIncludesBegin.h ├── GrpcIncludesEnd.h ├── GrpcUriValidator.h ├── InfraworldRuntime.h ├── RpcClient.h ├── RpcClientWorker.h └── WorkerUtils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by https://www.gitignore.io/api/linux,macos,windows ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/linux,macos,windows Binaries GrpcIncludes GrpcLibraries GrpcPrograms Intermediate # grpc source codes grpc ================================================ FILE: InfraworldRuntime.uplugin ================================================ { "FileVersion": 3, "Version": 1, "VersionName": "1.0", "FriendlyName": "Infraworld Runtime", "Description": "Infraworld is a solution that enables Unreal Engine 4 to work with Google gRPC services from either C++ or Blueprints", "Category": "Networking", "CreatedBy": "Vizor Games LLC", "CreatedByURL": "http://vizor-interactive.com/en/", "DocsURL": "http://vizor-interactive.com/en/", "MarketplaceURL": "", "SupportURL": "mailto:roman.chehowski@vizor-games.com", "CanContainContent": false, "IsBetaVersion": false, "Installed": false, "Modules": [ { "Name": "InfraworldRuntime", "Type": "Runtime", "LoadingPhase": "PreDefault" } ] } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2018 Vizor Games LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ Vizor Infraworld ================ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Maintainability](https://api.codeclimate.com/v1/badges/d8740022fdc1bbc8277b/maintainability)](https://codeclimate.com/github/vizor-games/InfraworldRuntime/maintainability) Welcome to the Infraworld source code! ![](icon.png) Infraworld is a solution that enables [Unreal Engine 4](https://www.unrealengine.com/en-US) to work with [Google gRPC](https://gRPC.io) services using either **C++** or **Blueprints**. Infraworld is a fast, robust and cross platform. It fits any stage of development: either prototyping or production. Saving a tons of your team's time, you need to write your gRPC wrappers by hand no more. [A special converter utility](https://github.com/vizor-games/infraworld-cornerstone) will do it for you, producing high quality, debuggable and multi-threaded code, gaining lowest possible overhead to your game logic thread. You may also work with either generated or shipped with gRPC C functions and C++ classes in your own way, even completely ignoring runtime classes, since the InfraworldRuntime adds all required headers and wires all required libraries. Also, you may want to use a [protobuild](https://github.com/vizor-games/infraworld-protobuild) utility to automate cross-language gRPC wrapper generation. Getting started =============== ##### Building gRPC support At the first step, you need to build gRPC runtime libraries. Just run `Setup.sh` for Linux, `Setup.bat` for Windows or `Setup.command` for macOS (please don't use `Setup.sh` on macOS, because Linux and macOS build pipelines are completely different!). OR you may want to use our sweet [pre-compiled binaries](../../releases) to avoid manual building and save our planet from carbon emission disaster! The runtime uses gRPC branch `v1.23.x`. * For Windows, we recommend you to use [chocolatey](https://chocolatey.org) to install packages into your system. **Note** that you do need all these programs in your system's `PATH` ([See how to edit PATH on Windows](https://www.computerhope.com/issues/ch000549.htm)): * [Git VCS](https://git-scm.com/download/win) * [Visual Studio 2017](https://visualstudio.microsoft.com/downloads/) with VC++ tools v141 installed * [CMake](https://cmake.org) is used to generate a Visual Studio solution from the `CMakeLists.txt` provided with gRPC * [Strawberry perl](http://strawberryperl.com) 64bit version * [NASM](https://www.nasm.us) * [Golang](https://golang.org/doc/install) * For any distribution of Linux and for macOS systems you need (use `apt`, `pacman`, `emerge` or any other package manager to install this software): * git * automake, autoconf and libtool * make * strip * go * [Unreal Engine 4.22 installed](https://github.com/EpicGames/UnrealEngine/tree/4.22), additionally you need to `export UE_ROOT=/path/to/root/ue4/directory`, because you need UE4 to build GRPC for linux. * For macOS (use `homebrew` or `macports` to install this software): * git * xCode 10.0+ * go **Note** that required programs for Linux and MacOS systems are being checked in run-time. Then you may (or may not) import `GrpcIncludes` and `GrpcLibraries` folders into your VCS, but you need to build them manually at least one time for each platform. The build process requires an access to the Internet. ##### Installing the plugin Just copy the resulting folder into the your project’s Plugins folder (create it if you don’t have one). Then, after that project is being opened, a dialog box, telling that the plugin is need to be compiled should appear. Then confirm the dialog by clicking `Yes`. ##### Building and the converter Please take a look at the [infraworld-cornerstone documentation](https://github.com/vizor-games/infraworld-cornerstone) for details. ##### Using generated code. Please take a look at the [example project](https://drive.google.com/open?id=13EZzP_9033vBC7VzJf9LFrygg42LHaOW) for tutorial. Running the example project =========================== You should copy built plugin's folder into `InfraworldRuntimeExample/Plugins` folder. Then just open `InfraworldDemo.uproject`. Server code is in `DemoServer` folder. You are required to install dependencies using `pip` and `requirements.txt` file. For more details please check our [**InfraworldExample**](https://github.com/vizor-games/InfraworldRuntimeExample) repository Debugging ========= Since the plugin itself is an open source software, you may want to debug it or add some extra functionality. Since it is distributed as an Unreal Engine plugin, you can add it into your own game and then generate **Visual Studio solution**, **XCode project** or **CMakeLists**. Use `Development` or `DebugGame` run configuration! Contribution ============ Please feel free to report known bugs, propose new features and improve tests using Github's pull request system. Please do not add either build libraries for an any platform or header files into your commits. Thank you very much for contributing into free software. References ========== * [Introduction to UE4 Plugins](https://wiki.unrealengine.com/An_Introduction_to_UE4_Plugins) * [gRPC API docs](https://gRPC.io/docs/) ================================================ FILE: Setup.bat ================================================ @echo off ::#####################################VARS############################################################################# set SCRIPT_FOLDER=%cd% set GRPC_ROOT=%SCRIPT_FOLDER%\grpc set GRPC_INCLUDE_DIR=%SCRIPT_FOLDER%\GrpcIncludes set GRPC_LIBRARIES_DIR=%SCRIPT_FOLDER%\GrpcLibraries\Win64 set GRPC_PROGRAMS_DIR=%SCRIPT_FOLDER%\GrpcPrograms\Win64 set CMAKE_BUILD_DIR=%GRPC_ROOT%\.build set REMOTE_ORIGIN=https://github.com/grpc/grpc.git set BRANCH=v1.23.x ::#####################################VARS############################################################################# :GET_UE_ROOT IF "%UE_ROOT%" == "" (echo "UE_ROOT directory does not exist, please set correct UE_ROOT via SET UE_ROOT=" && GOTO ABORT) :CLEAN echo ">>>>>>>>>> clean" IF EXIST "%GRPC_ROOT%" (cd "%GRPC_ROOT%" && git clean -fdx && git submodule foreach git clean -fdx && cd "%SCRIPT_FOLDER%") IF EXIST "%GRPC_INCLUDE_DIR%" (rmdir "%GRPC_INCLUDE_DIR%" /s /q) IF EXIST "%GRPC_LIBRARIES_DIR%" (rmdir "%GRPC_LIBRARIES_DIR%" /s /q) IF EXIST "%GRPC_PROGRAMS_DIR%" (rmdir "%GRPC_PROGRAMS_DIR%" /s /q) :CLONE_OR_PULL echo ">>>>>>>>>> clone git" if EXIST "%GRPC_ROOT%" (cd "%GRPC_ROOT%" && echo Pulling repo && git pull) else (call git clone "%REMOTE_ORIGIN%" && cd "%GRPC_ROOT%") git fetch git checkout -f git checkout -t origin/%BRANCH% git submodule update --init :BUILD_ALL mkdir "%CMAKE_BUILD_DIR%" && cd "%CMAKE_BUILD_DIR%" call cmake .. -G "Visual Studio 16 2019" -A x64 ^ -DCMAKE_CXX_STANDARD_LIBRARIES="Crypt32.Lib User32.lib Advapi32.lib" ^ -DCMAKE_BUILD_TYPE=Release ^ -DCMAKE_CONFIGURATION_TYPES=Release ^ -Dprotobuf_BUILD_TESTS=OFF ^ -DgRPC_ZLIB_PROVIDER=package ^ -DZLIB_INCLUDE_DIR="%UE_ROOT%\Engine\Source\ThirdParty\zlib\v1.2.8\include\Win64\VS2015" ^ -DZLIB_LIBRARY_DEBUG="%UE_ROOT%\Engine\Source\ThirdParty\zlib\v1.2.8\lib\Win64\VS2015\Debug\zlibstatic.lib" ^ -DZLIB_LIBRARY_RELEASE="%UE_ROOT%\Engine\Source\ThirdParty\zlib\v1.2.8\lib\Win64\VS2015\Release\zlibstatic.lib" ^ -DgRPC_SSL_PROVIDER=package ^ -DLIB_EAY_LIBRARY_DEBUG="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libcrypto.lib" ^ -DLIB_EAY_LIBRARY_RELEASE="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libcrypto.lib" ^ -DLIB_EAY_DEBUG="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libcrypto.lib" ^ -DLIB_EAY_RELEASE="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libcrypto.lib" ^ -DOPENSSL_INCLUDE_DIR="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\include\Win64\VS2015" ^ -DSSL_EAY_DEBUG="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libssl.lib" ^ -DSSL_EAY_LIBRARY_DEBUG="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libssl.lib" ^ -DSSL_EAY_LIBRARY_RELEASE="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libssl.lib" ^ -DSSL_EAY_RELEASE="%UE_ROOT%\Engine\Source\ThirdParty\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libssl.lib" call cmake --build . --target ALL_BUILD --config Release :COPY_HEADERS echo ">>>>>>>>>> copy headers" robocopy "%GRPC_ROOT%\include" "%GRPC_INCLUDE_DIR%\include" /E robocopy "%GRPC_ROOT%\third_party\protobuf\src" "%GRPC_INCLUDE_DIR%\third_party\protobuf\src" /E :PATCH_HEADERS echo ">>>>>>>>>> copy headers" set GENERATED_MESSAGE_TABLE_DRIVEN_FILE="%SCRIPT_FOLDER%\third_party\protobuf\src\google\protobuf\" :COPY_LIBRARIES echo ">>>>>>>>>> copy libraries" robocopy "%CMAKE_BUILD_DIR%\Release" "%GRPC_LIBRARIES_DIR%" *.lib /R:0 /S robocopy "%CMAKE_BUILD_DIR%\third_party\cares\cares\lib\Release" "%GRPC_LIBRARIES_DIR%" *.lib /R:0 /S robocopy "%CMAKE_BUILD_DIR%\third_party\benchmark\src\Release" "%GRPC_LIBRARIES_DIR%" *.lib /R:0 /S robocopy "%CMAKE_BUILD_DIR%\third_party\gflags\Release" "%GRPC_LIBRARIES_DIR%" *.lib /R:0 /S robocopy "%CMAKE_BUILD_DIR%\third_party\protobuf\Release" "%GRPC_LIBRARIES_DIR%" *.lib /R:0 /S :COPY_PROGRAMS echo ">>>>>>>>>> copy programs" robocopy "%CMAKE_BUILD_DIR%\Release" "%GRPC_PROGRAMS_DIR%" *.exe /R:0 /S copy "%CMAKE_BUILD_DIR%\third_party\protobuf\Release\protoc.exe" "%GRPC_PROGRAMS_DIR%\protoc.exe" :REMOVE_USELESS_LIBRARIES echo ">>>>>>>>>> remove useless libraries" del "%GRPC_LIBRARIES_DIR%\grpc_csharp_ext.lib" del "%GRPC_LIBRARIES_DIR%\gflags_static.lib" del "%GRPC_LIBRARIES_DIR%\gflags_nothreads_static.lib" del "%GRPC_LIBRARIES_DIR%\benchmark.lib" :Finish echo ">>>>>>>>>> finish" cd "%SCRIPT_FOLDER%" GOTO GRACEFULEXIT :ABORT pause echo Aborted... :GRACEFULEXIT echo Build done! ================================================ FILE: Setup.command ================================================ #!/bin/bash # Exit on errors if any set -e ############################################################################### # Should be defined as an environment variable, will be v1.3.x otherwise branch=${branch:-v1.23.x} clean=${clean:-true} VAR_GIT_BRANCH=$branch VAR_CLEAR_REPO=$clean REMOTE_ORIGIN="https://github.com/grpc/grpc.git" GOSUPPORT_REMOTE_ORIGIN="https://github.com/golang/protobuf.git" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" GRPC_FOLDER_NAME=grpc GRPC_ROOT="${SCRIPT_DIR}/${GRPC_FOLDER_NAME}" CMAKE_BUILD_DIR="${GRPC_ROOT}/.build" DEPS=(git automake autoconf libtool make strip clang++ go) ############################################################################### echo "SCRIPT_DIR=${SCRIPT_DIR}" echo "GRPC_ROOT=${GRPC_ROOT}" UE_ROOT=${UE_ROOT:-"/var/lib/jenkins/UE_4.20.2-release"} if [ ! -d "$UE_ROOT" ]; then echo "UE_ROOT directory ${UE_ROOT} does not exist, please set correct UE_ROOT" exit 1 fi; # Check if all tools are installed for i in ${DEPS[@]}; do if [ ! "$(which ${i})" ];then echo "${i} not found, install via 'brew install ${i}'" && exit 1 fi done # Check if ran under Darwin if [ $(uname) != 'Darwin' ]; then echo "Can not work under $(uname) operating system, should be Darwin! Exiting..." exit 1 fi; # Clone or pull if [ ! -d "$GRPC_ROOT" ]; then echo "Cloning repo into ${GRPC_ROOT}" git clone $REMOTE_ORIGIN $GRPC_ROOT else # [[ ${VAR_CLEAR_REPO} ]] && cd $GRPC_ROOT && git merge --abort || true; git clean -fdx && git checkout -f . echo "Pulling repo" (cd $GRPC_ROOT && git pull) fi echo "Checking out branch ${VAR_GIT_BRANCH}" (cd $GRPC_ROOT && git fetch) (cd $GRPC_ROOT && git checkout -f) (cd $GRPC_ROOT && git checkout -t origin/$VAR_GIT_BRANCH || true) # Update submodules (cd $GRPC_ROOT && git submodule update --init) if [ "$VAR_CLEAR_REPO" = "true" ]; then echo "Cleaning repo and submodules because VAR_CLEAR_REPO is set to ${VAR_CLEAR_REPO}" (cd $GRPC_ROOT && make clean) (cd $GRPC_ROOT && git clean -fdx) (cd $GRPC_ROOT && git submodule foreach git clean -fdx) elif [ "$VAR_CLEAR_REPO" = "false" ]; then echo "Cleaning is not needed!" else echo "Undefined behaviour, VAR_CLEAR_REPO is ${VAR_CLEAR_REPO}!" exit 1 fi # Copy INCLUDE folders, should copy: # - grpc/include # - grpc/third_party/protobuf/src HEADERS_DIR="${SCRIPT_DIR}/GrpcIncludes" PROTOBUF_SRC_DIR="${HEADERS_DIR}/third_party/protobuf" # (re)-create headers directory if [ -d "$HEADERS_DIR" ]; then printf '%s\n' "Removing old $HEADERS_DIR" rm -rf "$HEADERS_DIR" fi mkdir $HEADERS_DIR mkdir -p $PROTOBUF_SRC_DIR cp -R "${GRPC_ROOT}/include" $HEADERS_DIR cp -R "${GRPC_ROOT}/third_party/protobuf/src" $PROTOBUF_SRC_DIR # Build all if [ -d "${CMAKE_BUILD_DIR}" ]; then printf '%s\n' "Removing old ${CMAKE_BUILD_DIR}" rm -rf "${CMAKE_BUILD_DIR}" fi mkdir -p ${CMAKE_BUILD_DIR} && cd ${CMAKE_BUILD_DIR} echo "BUILD STARTED!" cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES=Release -Dprotobuf_BUILD_TESTS=OFF -DgRPC_ZLIB_PROVIDER=package -DZLIB_INCLUDE_DIR=${UE_ROOT}/Engine/Source/ThirdParty/zlib/v1.2.8/include/Mac -DZLIB_LIBRARY_DEBUG=${UE_ROOT}/Engine/Source/ThirdParty/zlib/v1.2.8/lib/Mac/libz.a -DZLIB_LIBRARY_RELEASE=${UE_ROOT}/Engine/Source/ThirdParty/zlib/v1.2.8/lib/Mac/libz.a -DgRPC_SSL_PROVIDER=package -DLIB_EAY_LIBRARY_DEBUG=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Debug/libcrypto.a -DLIB_EAY_LIBRARY_RELEASE=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Release/libcrypto.a -DLIB_EAY_DEBUG=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Debug/libcrypto.a -DLIB_EAY_RELEASE=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Release/libcrypto.a -DOPENSSL_INCLUDE_DIR=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/include/Mac -DSSL_EAY_DEBUG=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Debug/libssl.a -DSSL_EAY_LIBRARY_DEBUG=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Debug/libssl.a -DSSL_EAY_LIBRARY_RELEASE=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Release/libssl.a -DSSL_EAY_RELEASE=${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1.0.2g/lib/Mac/Release/libssl.a make cd ${SCRIPT_DIR} # Copy artifacts LIBS_DIR="${SCRIPT_DIR}/GrpcLibraries" BIN_DIR="${SCRIPT_DIR}/GrpcPrograms" echo "LIBS_DIR is ${LIBS_DIR}" echo "BIN_DIR is ${BIN_DIR}" ARCH_LIBS_DIR="${LIBS_DIR}/Mac" ARCH_BIN_DIR="${BIN_DIR}/Mac" echo "ARCH_LIBS_DIR is ${ARCH_LIBS_DIR}" echo "ARCH_BIN_DIR is ${ARCH_BIN_DIR}" # Remove old libs and binaries directories if [ -d "$ARCH_LIBS_DIR" ]; then printf '%s\n' "Removing old $ARCH_LIBS_DIR" rm -rf "$ARCH_LIBS_DIR" fi if [ -d "$ARCH_BIN_DIR" ]; then printf '%s\n' "Removing old $ARCH_BIN_DIR" rm -rf "$ARCH_BIN_DIR" fi # Create platform-specific artifacts directory mkdir -p $ARCH_LIBS_DIR mkdir -p $ARCH_BIN_DIR SRC_LIBS_FOLDER=${CMAKE_BUILD_DIR} echo "SRC_LIBS_FOLDER=${SRC_LIBS_FOLDER}" if [ -d "$SRC_LIBS_FOLDER" ]; then echo "Copying grpc libraries from ${SRC_LIBS_FOLDER} to ${ARCH_LIBS_DIR}" (cd $SRC_LIBS_FOLDER && find . -name '*.a' -exec cp -vf '{}' $ARCH_LIBS_DIR ";") fi # Strip all symbols from libraries (cd $ARCH_LIBS_DIR && strip -S *.a) # Copy binaries (plugins & protoc) echo "Copying executables to ${ARCH_BIN_DIR}" cp ${SRC_LIBS_FOLDER}/grpc_cpp_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_csharp_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_node_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_objective_c_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_php_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_python_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/grpc_ruby_plugin ${ARCH_BIN_DIR}/ cp ${SRC_LIBS_FOLDER}/third_party/protobuf/protoc ${ARCH_BIN_DIR}/ GOROOT_DIR="${GRPC_ROOT}/go_packages" GOPROTO_DIR="${GOROOT_DIR}/src/github.com/golang/protobuf" echo "Building golang support in ${GOPROTO_DIR}" if [ ! -d "${GOPROTO_DIR}" ]; then (cd $GRPC_ROOT && git clone $GOSUPPORT_REMOTE_ORIGIN $GOPROTO_DIR) else (cd $GOPROTO_DIR && git pull) fi # Add gopath with protobuf libs export GOPATH=$GOROOT_DIR # Run go build (cd "${GOPROTO_DIR}/protoc-gen-go" && go build) (cp "${GOPROTO_DIR}/protoc-gen-go/protoc-gen-go" $ARCH_BIN_DIR) # Finally, strip binaries (programs) (cd $ARCH_BIN_DIR && strip -S *) echo 'BUILD DONE!' ================================================ FILE: Setup.sh ================================================ #!/bin/bash # Exit on errors if any set -e ############################################################################### # Should be defined as an environment variable, will be v1.3.x otherwise branch=${branch:-v1.23.x} clean=${clean:-true} VAR_GIT_BRANCH=$branch VAR_CLEAR_REPO=$clean REMOTE_ORIGIN="https://github.com/grpc/grpc.git" GOSUPPORT_REMOTE_ORIGIN="https://github.com/golang/protobuf.git" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" GRPC_FOLDER_NAME=grpc GRPC_ROOT="${SCRIPT_DIR}/${GRPC_FOLDER_NAME}" DEPS=(git automake autoconf libtool make strip go pkg-config) # Linux needs an existing UE installation UE_ROOT=${UE_ROOT:-"/var/lib/jenkins/UE_4.20.2-release"} if [ ! -d "$UE_ROOT" ]; then echo "UE_ROOT directory ${UE_ROOT} does not exist, please set correct UE_ROOT" exit 1 fi; UE_PREREQUISITES="${UE_ROOT}/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v13_clang-7.0.1-centos7/x86_64-unknown-linux-gnu" ############################################################################### OPENSSL_LIB="${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1_0_2h/lib/Linux/x86_64-unknown-linux-gnu" OPENSSL_INCLUDE="${UE_ROOT}/Engine/Source/ThirdParty/OpenSSL/1_0_2h/include/Linux/x86_64-unknown-linux-gnu" echo "SCRIPT_DIR=${SCRIPT_DIR}" echo "GRPC_ROOT=${GRPC_ROOT}" # Check if all tools are installed for i in ${DEPS[@]}; do if [ ! "$(which ${i})" ];then echo "${i} not found, install via 'apt-get install ${i}'" && exit 1 fi done # Check if ran under Linux if [ $(uname) != 'Linux' ]; then echo "Can not work under $(uname) operating system, should be Linux! Exiting..." exit 1 fi; # Clone or pull if [ ! -d "$GRPC_ROOT" ]; then echo "Cloning repo into ${GRPC_ROOT}" git clone $REMOTE_ORIGIN $GRPC_ROOT else # [[ ${VAR_CLEAR_REPO} ]] && cd $GRPC_ROOT && git merge --abort || true; git clean -fdx && git checkout -f . echo "Pulling repo" (cd $GRPC_ROOT && git pull) fi echo "Checking out branch ${VAR_GIT_BRANCH}" (cd $GRPC_ROOT && git fetch) (cd $GRPC_ROOT && git checkout -f) (cd $GRPC_ROOT && git checkout -t origin/$VAR_GIT_BRANCH || true) # Update submodules (cd $GRPC_ROOT && git submodule update --init) if [ "$VAR_CLEAR_REPO" = "true" ]; then echo "Cleaning repo and submodules because VAR_CLEAR_REPO is set to ${VAR_CLEAR_REPO}" (cd $GRPC_ROOT && make clean) (cd $GRPC_ROOT && git clean -fdx) (cd $GRPC_ROOT && git submodule foreach git clean -fdx) elif [ "$VAR_CLEAR_REPO" = "false" ]; then echo "Cleaning is not needed!" else echo "Undefined behaviour, VAR_CLEAR_REPO is ${VAR_CLEAR_REPO}!" exit 1 fi # Copy INCLUDE folders, should copy: # - grpc/include # - grpc/third_party/protobuf/src HEADERS_DIR="${SCRIPT_DIR}/GrpcIncludes" PROTOBUF_SRC_DIR="${HEADERS_DIR}/third_party/protobuf" # (re)-create headers directory if [ -d "$HEADERS_DIR" ]; then printf '%s\n' "Removing old $HEADERS_DIR" rm -rf "$HEADERS_DIR" fi mkdir $HEADERS_DIR mkdir -p $PROTOBUF_SRC_DIR cp -R "${GRPC_ROOT}/include" $HEADERS_DIR cp -R "${GRPC_ROOT}/third_party/protobuf/src" $PROTOBUF_SRC_DIR # Compute arch string using uname UNAME_MACH=$(echo $(uname -m) | tr '[:upper:]' '[:lower:]') UNAME_OS=$(echo $(uname) | tr '[:upper:]' '[:lower:]') UNAME_ARCH="${UNAME_MACH}-unknown-${UNAME_OS}-gnu" LIBCXX_UE_DIR="${UE_ROOT}/Engine/Source/ThirdParty/Linux/LibCxx/include" LIBC_UE_DIR="${UE_ROOT}/Engine/Source/ThirdParty/Linux/LibCxx/include" export CC="${UE_PREREQUISITES}/bin/clang" export CC_FOR_BUILD=${CC} export CXX="${UE_PREREQUISITES}/bin/clang++" export CXX_FOR_BUILD=${CXX} # we need this to avoid 'unknow flavor: old-gnu' error if [ ! -e "${UE_PREREQUISITES}/bin/lld-gnu" ]; then ln -s "${UE_PREREQUISITES}/bin/ld.lld" "${UE_PREREQUISITES}/bin/lld-gnu" fi find "${UE_PREREQUISITES}/usr/lib64" -name '*.o' -exec cp -vfs '{}' "${UE_PREREQUISITES}/lib64" ";" # this thing avoid us from gcc usage, we don't need it export VALID_CONFIG_gcov=0 # force compile protobuf, libz and libares export HAS_SYSTEM_CARES=false export HAS_SYSTEM_PROTOBUF=false export HAS_SYSTEM_ZLIB=false # funny, but in grpc Makefile LD and LDXX associated with compilers export LD="${CC}" export LDXX="${CXX}" export DEFAULT_CC="${CC}" export DEFAULT_CXX="${CXX}" export CFLAGS="-fPIC -Wno-error --sysroot=${UE_PREREQUISITES}" export CFLAGS_FOR_BUILD=${CFLAGS} export CXXFLAGS="-std=c++14 -fPIC -nostdinc++ -Wno-expansion-to-defined -Wno-error -I${LIBCXX_UE_DIR} -I${LIBCXX_UE_DIR}/c++/v1 -I${OPENSSL_INCLUDE}" export CXXFLAGS_FOR_BUILD=${CXXFLAGS} export LIBRARY_PATH="${UE_PREREQUISITES}/usr/lib64" export LDFLAGS="-L${UE_ROOT}/Engine/Source/ThirdParty/Linux/LibCxx/lib/Linux/${UNAME_ARCH} -L${OPENSSL_LIB} -fuse-ld=${UE_PREREQUISITES}/bin/lld-gnu" export LDFLAGS_FOR_BUILD=${LDFLAGS} export LDLIBS="-lc++ -lc++abi -lc" export PROTOBUF_LDFLAGS_EXTRA="${LDFLAGS} ${LDLIBS}" # Create an alias 'clocale -> xlocale.h' (if does not exist) if [ ! -e "${LIBCXX_UE_DIR}/c++/v1/xlocale.h" ]; then if [ ! -e "${LIBCXX_UE_DIR}/c++/v1/clocale" ]; then echo "${LIBCXX_UE_DIR}/c++/v1/clocale must exist in UE src dir. Exiting..." && exit 1 fi (cd "${LIBCXX_UE_DIR}/c++/v1" && ln -s clocale xlocale.h) echo "Created an alias to xlocale.h" fi echo "CFLAGS=${CFLAGS}, CXXFLAGS=${CXXFLAGS}, LDFLAGS=${LDFLAGS}, LDLIBS=${LDLIBS}, PROTOBUF_LDFLAGS_EXTRA=${PROTOBUF_LDFLAGS_EXTRA}" # Build GRPC (cd $GRPC_ROOT && make CC=${CC} CXX=${CXX}) # Copy artifacts LIBS_DIR="${SCRIPT_DIR}/GrpcLibraries" BIN_DIR="${SCRIPT_DIR}/GrpcPrograms" echo "LIBS_DIR is ${LIBS_DIR}" echo "BIN_DIR is ${BIN_DIR}" if [ $(uname) != 'Darwin' ]; then ARCH_LIBS_DIR="${LIBS_DIR}/"$(uname) ARCH_BIN_DIR="${BIN_DIR}/"$(uname) else ARCH_LIBS_DIR="${LIBS_DIR}/Mac" ARCH_BIN_DIR="${BIN_DIR}/Mac" fi echo "ARCH_LIBS_DIR is ${ARCH_LIBS_DIR}" echo "ARCH_BIN_DIR is ${ARCH_BIN_DIR}" # Remove old libs and binaries directories if [ -d "$ARCH_LIBS_DIR" ]; then printf '%s\n' "Removing old $ARCH_LIBS_DIR" rm -rf "$ARCH_LIBS_DIR" fi if [ -d "$ARCH_BIN_DIR" ]; then printf '%s\n' "Removing old $ARCH_BIN_DIR" rm -rf "$ARCH_BIN_DIR" fi # Create platform-specific artifacts directory mkdir -p $ARCH_LIBS_DIR mkdir -p $ARCH_BIN_DIR SRC_LIBS_FOLDER_GRPC=$GRPC_ROOT/libs/opt SRC_LIBS_FOLDER_PROTOBUF=$PROTOBUF_ROOT/src/.libs # Force recursively copy if [ -d "$SRC_LIBS_FOLDER_PROTOBUF" ]; then echo "Copying protobuf libraries from ${SRC_LIBS_FOLDER_PROTOBUF} to ${ARCH_LIBS_DIR}" (cd $SRC_LIBS_FOLDER_PROTOBUF && find . -name '*.a' -exec cp -vf '{}' $ARCH_LIBS_DIR ";") fi if [ -d "$SRC_LIBS_FOLDER_GRPC" ]; then echo "Copying grpc libraries from ${SRC_LIBS_FOLDER_GRPC} to ${ARCH_LIBS_DIR}" (cd $SRC_LIBS_FOLDER_GRPC && find . -name '*.a' -exec cp -vf '{}' $ARCH_LIBS_DIR ";") fi # Strip all symbols from libraries (cd $ARCH_LIBS_DIR && strip -S *.a) # Copy binaries (plugins & protoc) echo "Copying executables to ${ARCH_BIN_DIR}" (cp -a "${GRPC_ROOT}/bins/opt/." $ARCH_BIN_DIR) (cp -a "${GRPC_ROOT}/bins/opt/protobuf/." $ARCH_BIN_DIR) # This seems to be a hack, should modify (cp -a "${GRPC_ROOT}/bins/opt/." $BIN_DIR) to copy only files, bot dirs (cd $ARCH_BIN_DIR && rm -rf protobuf) # # Build go support GOROOT_DIR="${GRPC_ROOT}/go_packages" GOPROTO_DIR="${GOROOT_DIR}/src/github.com/golang/protobuf" echo "Building golang support in ${GOPROTO_DIR}" if [ ! -d "${GOPROTO_DIR}" ]; then (cd $GRPC_ROOT && git clone $GOSUPPORT_REMOTE_ORIGIN $GOPROTO_DIR) else (cd $GOPROTO_DIR && git pull) fi # Add gopath with protobuf libs export GOPATH=$GOROOT_DIR # # Run go build (cd "${GOPROTO_DIR}/protoc-gen-go" && go build) (cp "${GOPROTO_DIR}/protoc-gen-go/protoc-gen-go" $ARCH_BIN_DIR) # Strip binaries (programs) (cd $ARCH_BIN_DIR && strip -S *) # Finnaly, clean all stuff rm "${LIBCXX_UE_DIR}/c++/v1/xlocale.h" rm "${UE_PREREQUISITES}/bin/lld-gnu" find "${UE_PREREQUISITES}/lib64" -name '*.o' -type f -delete # Copy source echo 'BUILD DONE!' ================================================ FILE: Source/InfraworldRuntime/InfraWorldRuntime.Build.cs ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ // #define TRACE using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.NetworkInformation; using System.Text; using System.Text.RegularExpressions; using UnrealBuildTool; public class InfraworldRuntime : ModuleRules { private UnrealTargetPlatform Platform; private UnrealTargetConfiguration Configuration; // name of root folders in the project folder private static readonly string GRPC_STRIPPED_FOLDER = "GrpcIncludes"; private static readonly string GRPC_LIBS_FOLDER = "GrpcLibraries"; private string INCLUDE_ROOT; private string LIB_ROOT; public class ModuleDepPaths { public readonly string[] HeaderPaths; public readonly string[] LibraryPaths; public ModuleDepPaths(string[] headerPaths, string[] libraryPaths) { HeaderPaths = headerPaths; LibraryPaths = libraryPaths; } public override string ToString() { return "Headers:\n" + string.Join("\n", HeaderPaths) + "\nLibs:\n" + string.Join("\n", LibraryPaths); } } [Conditional("DEBUG")] [Conditional("TRACE")] private void clog(params object[] objects) { Console.WriteLine(string.Join(", ", objects)); } private IEnumerable FindFilesInDirectory(string dir, string suffix = "") { List matches = new List(); if (Directory.Exists(dir)) { string[] files = Directory.GetFiles(dir); Regex regex = new Regex(".+\\." + suffix); foreach (string file in files) { if (regex.Match(file).Success) matches.Add(file); } } return matches; } private string GetConfigurationString() { return (Configuration == UnrealTargetConfiguration.Shipping) ? "Release" : "Debug"; } public ModuleDepPaths GatherDeps() { string RootPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../../")); INCLUDE_ROOT = Path.Combine(RootPath, GRPC_STRIPPED_FOLDER); LIB_ROOT = Path.Combine(RootPath, GRPC_LIBS_FOLDER); List headers = new List(); List libs = new List(); string PlatformLibRoot = ""; if (Platform == UnrealTargetPlatform.Win64) { PlatformLibRoot = Path.Combine(LIB_ROOT, Platform.ToString()); libs.AddRange(FindFilesInDirectory(PlatformLibRoot, "lib")); } else { PlatformLibRoot = Path.Combine(LIB_ROOT, Platform.ToString()); libs.AddRange(FindFilesInDirectory(PlatformLibRoot, "a")); } clog("PlatformLibRoot: " + PlatformLibRoot); headers.Add(Path.Combine(INCLUDE_ROOT, "include")); headers.Add(Path.Combine(INCLUDE_ROOT, "third_party", "protobuf", "src")); return new ModuleDepPaths(headers.ToArray(), libs.ToArray()); } public InfraworldRuntime(ReadOnlyTargetRules Target) : base(Target) { PublicDefinitions.Add("GOOGLE_PROTOBUF_NO_RTTI"); PublicDefinitions.Add("GPR_FORBID_UNREACHABLE_CODE"); PublicDefinitions.Add("GRPC_ALLOW_EXCEPTIONS=0"); //TODO: We do this because in file generated_message_table_driven.h that located in protobuf sources //TODO: line 174: static_assert(std::is_pod::value, ""); //TODO: causes С4647 level 3 warning __is_pod behavior change //TODO: UE4 threading some warnings as errors, and we have no chance to suppress this stuff //TODO: So, we don't want to change any third-party code, this why we add this definition PublicDefinitions.Add("__NVCC__"); Platform = Target.Platform; Configuration = Target.Configuration; ModuleDepPaths moduleDepPaths = GatherDeps(); Console.WriteLine(moduleDepPaths.ToString()); PublicIncludePaths.AddRange(moduleDepPaths.HeaderPaths); PublicAdditionalLibraries.AddRange(moduleDepPaths.LibraryPaths); PublicDependencyModuleNames.AddRange(new string[] { "Core" }); AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenSSL"); AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib"); PrivateDependencyModuleNames.AddRange(new string[] { "CoreUObject", "Engine" }); } } ================================================ FILE: Source/InfraworldRuntime/Private/ChannelCredentials.cpp ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #include "ChannelCredentials.h" #include "InfraworldRuntime.h" #include "GrpcIncludesBegin.h" #include #include "GrpcIncludesEnd.h" UChannelCredentials* UChannelCredentials::MakeGoogleDefaultCredentials() { return NewObject(); } UChannelCredentials* UChannelCredentials::MakeSslCredentials(FString PemRootCerts, FString PemPrivateKey, FString PemCertChain) { USslCredentials* const SslCredentials = NewObject(); SslCredentials->PemRootCerts = PemRootCerts; SslCredentials->PemPrivateKey = PemPrivateKey; SslCredentials->PemCertChain = PemCertChain; return SslCredentials; } UChannelCredentials* UChannelCredentials::MakeInsecureChannelCredentials() { return NewObject(); } #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" #endif ================================================ FILE: Source/InfraworldRuntime/Private/GrpcUriValidator.cpp ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #include "GrpcUriValidator.h" #include "InfraworldRuntime.h" #include "Misc/DefaultValueHelper.h" class FGrpcUriValidator_Internal { public: static bool ValidatePort(const FString& MaybePort, FString& OutError); static bool ValidateIp(const FString& MaybeIpAddress, FString& OutError); static bool ValidateDomainName(const FString& MaybeDomainName, FString& OutError); static bool DoesHostLookLikeIp(const FString& MaybeIpAddress); private: static bool TCharIsLetter(TCHAR character); static bool TCharIsDigit(TCHAR character); }; /// FGrpcUriValidator_Internal interface bool FGrpcUriValidator_Internal::ValidatePort(const FString& MaybePort, FString& OutError) { int32 PortAsInteger; if (FDefaultValueHelper::ParseInt(MaybePort, PortAsInteger)) { const TRange ValidPortRange(0, 0xFFFF); if (!ValidPortRange.Contains(PortAsInteger)) { OutError = FString::Printf(TEXT("Invalid port number: \"%d\", must be within [%d - %d)"), PortAsInteger, 0, 0xFFFF); return false; } } else { OutError = FString::Printf(TEXT("Can not parse port \"%s\" into an integer. The format is invalid."), *MaybePort); return false; } return true; } bool FGrpcUriValidator_Internal::ValidateIp(const FString& MaybeIpAddress, FString& OutError) { TArray Octets; MaybeIpAddress.ParseIntoArray(Octets, TEXT(".")); if (Octets.Num() == 4) { const TRange OctetRange(0, 0xFF); for (const FString& Octet : Octets) { int32 Out; if (FDefaultValueHelper::ParseInt(Octet, Out)) { if (!OctetRange.Contains(Out)) { OutError = FString::Printf(TEXT("An octet \"%s\" in the IPv4 address (which is \"%s\") is of range [0 - 256)"), *Octet, *MaybeIpAddress); return false; } } else { OutError = FString::Printf(TEXT("\"%s\" in \"%s\" does not seems to be int32"), *Octet, *MaybeIpAddress); return false; } } } else { OutError = FString::Printf(TEXT("Can not parse IPv4 address (which is \"%s\") into TArray, or invalid number of octets"), *MaybeIpAddress); return false; } return true; } bool FGrpcUriValidator_Internal::ValidateDomainName(const FString& MaybeDomainName, FString& OutError) { for (TCHAR Character : MaybeDomainName) { if (!TCharIsLetter(Character) && !TCharIsDigit(Character) && (Character != TEXT('-')) && (Character != TEXT('.'))) { OutError = FString::Printf(TEXT("\"%s\" domain name contains forbidden character: \"%c\""), *MaybeDomainName, Character); return false; } } return true; } bool FGrpcUriValidator_Internal::DoesHostLookLikeIp(const FString& MaybeIpAddress) { for (TCHAR Character : MaybeIpAddress) { if (!TCharIsDigit(Character) && (Character != TEXT('.'))) return false; } return true; } bool FGrpcUriValidator_Internal::TCharIsLetter(TCHAR character) { const bool bUpperCase = (character >= TEXT('A')) && (character <= TEXT('Z')); const bool bLowerCase = (character >= TEXT('a')) && (character <= TEXT('z')); return bUpperCase || bLowerCase; } bool FGrpcUriValidator_Internal::TCharIsDigit(TCHAR character) { return (character >= TEXT('0')) && (character <= TEXT('9')); } /// FGrpcUriValidator interface bool FGrpcUriValidator::Validate(const FString& MaybeGrpcUri, FString& OutError) { static const FString SchemeSeparator(TEXT("://")); const int32 IndexOfSchemeSeparator = MaybeGrpcUri.Find(SchemeSeparator); if (IndexOfSchemeSeparator >= 0) { const FString& Scheme = MaybeGrpcUri.Mid(0, IndexOfSchemeSeparator); OutError = FString::Printf(TEXT("GRPC URI \"%s\" must not contain a URL scheme (\"%s\" provided). GRPC forbids explicit schemes."), *MaybeGrpcUri, *Scheme); return false; } const int32 PathSeparatorIndex = MaybeGrpcUri.Find(TEXT("/")); const bool bHasPathSeparator = PathSeparatorIndex >= 0; const int32 PortSeparatorIndex = MaybeGrpcUri.Find(TEXT(":"), ESearchCase::IgnoreCase, ESearchDir::FromEnd, (PathSeparatorIndex >= 0) ? PathSeparatorIndex : INDEX_NONE); const bool bHasPortSeparator = PortSeparatorIndex >= 0; FString GrpcHostName = TEXT(""); FString GrpcPort = TEXT("80"); if (bHasPortSeparator) { GrpcHostName = MaybeGrpcUri.Mid(0, PortSeparatorIndex); const int32 PortSubstringStart = PortSeparatorIndex + 1; if (bHasPathSeparator) { GrpcPort = MaybeGrpcUri.Mid(PortSubstringStart, (PathSeparatorIndex - PortSubstringStart)); } else { GrpcPort = MaybeGrpcUri.Mid(PortSubstringStart); } } else { if (bHasPathSeparator) { GrpcHostName = MaybeGrpcUri.Mid(0, PathSeparatorIndex); } else { GrpcHostName = MaybeGrpcUri; } } if (bHasPathSeparator) { const FString& RestOfAddress = MaybeGrpcUri.Mid(PathSeparatorIndex); if (!RestOfAddress.IsEmpty()) { OutError = FString::Printf(TEXT("Path of the \"%s\" uri, must be empty. Actually it is: \"%s\""), *MaybeGrpcUri, *RestOfAddress); return false; } } if (FGrpcUriValidator_Internal::DoesHostLookLikeIp(GrpcHostName)) { // Validate as IP address if (!FGrpcUriValidator_Internal::ValidateIp(GrpcHostName, OutError)) return false; } else { // Validate as domain name if (!FGrpcUriValidator_Internal::ValidateDomainName(GrpcHostName, OutError)) return false; } // Anyway, validate port if (!FGrpcUriValidator_Internal::ValidatePort(GrpcPort, OutError)) return false; return true; } ================================================ FILE: Source/InfraworldRuntime/Private/InfraworldRuntime.cpp ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #include "InfraworldRuntime.h" DEFINE_LOG_CATEGORY(LogInfraworldRuntime); #define LOCTEXT_NAMESPACE "FInfraworldRuntimeModule" void FInfraworldRuntimeModule::StartupModule() { // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module } void FInfraworldRuntimeModule::ShutdownModule() { // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, // we call this function before unloading the module. } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FInfraworldRuntimeModule, InfraworldRuntime) ================================================ FILE: Source/InfraworldRuntime/Private/RpcClient.cpp ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #include "RpcClient.h" #include "InfraworldRuntime.h" #include "RpcClientWorker.h" #include "GrpcUriValidator.h" #include "Containers/Ticker.h" #include "Misc/CoreDelegates.h" #include "Misc/DefaultValueHelper.h" #include "HAL/RunnableThread.h" #include "Kismet/KismetStringLibrary.h" // ============ RpcClient implementation =========== bool URpcClient::Init(const FString& URI, UChannelCredentials* ChannelCredentials) { if (bCanSendRequests) { UE_LOG(LogInfraworldRuntime, Error, TEXT("You're trying to initialize an RPC Client more than once")); return true; } FString ErrorMessage; if (!FGrpcUriValidator::Validate(URI, ErrorMessage)) { UE_LOG(LogInfraworldRuntime, Error, TEXT("%s Unable to validate URI: %s"), *(GetClass()->GetName()), *ErrorMessage); } // Do it if and only if the thread is not yet created. if (Thread == nullptr) { UE_LOG(LogInfraworldRuntime, Log, TEXT("RpcClient at [%p], Thread == nullptr, initializing"), this); // Launch 'chaining' hierarchical init, which will init a superclass (a concrete implementation). HierarchicalInit(); UE_LOG(LogInfraworldRuntime, Log, TEXT("RpcClient at [%p], finished HierarchicalInit"), this); // Retrieve and set an Error Message Queue if (InnerWorker) { UE_LOG(LogInfraworldRuntime, Log, TEXT("RpcClient at [%p], InnerWorker = %p"), this, InnerWorker.Get()); InnerWorker->URI = URI; InnerWorker->ChannelCredentials = ChannelCredentials; InnerWorker->ErrorMessageQueue = &ErrorMessageQueue; const FString ThreadName(FString::Printf(TEXT("RPC Client Thread %s %d"), *(GetClass()->GetName()), FMath::RandRange(0, TNumericLimits::Max()))); Thread = FRunnableThread::Create(InnerWorker.Get(), *ThreadName); bCanSendRequests = true; UE_LOG(LogInfraworldRuntime, Log, TEXT("Just made a thread: %s, address %p"), *ThreadName, InnerWorker.Get()); } else { UE_LOG(LogInfraworldRuntime, Fatal, TEXT("An inner worker of %s wasn't initialized"), *(GetClass()->GetName())); } } if (CanSendRequests()) { TickDelegateHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float) { if (!ErrorMessageQueue.IsEmpty()) { FRpcError ReceivedError; ErrorMessageQueue.Dequeue(ReceivedError); EventRpcError.Broadcast(this, ReceivedError); // No need to call URpcClient::HierarchicalUpdate() if got any errors (Errors first) } else { HierarchicalUpdate(); } return true; })); } return bCanSendRequests; } URpcClient::URpcClient() : InnerWorker(nullptr), TickDelegateHandle() { } URpcClient::~URpcClient() { UE_LOG(LogInfraworldRuntime, Verbose, TEXT("An instance of RPC Client has been destroyed. Still can send requests: %s"), *UKismetStringLibrary::Conv_BoolToString(CanSendRequests())); } void URpcClient::Update() { // Occasionally left blank } bool URpcClient::CanSendRequests() const { return bCanSendRequests; } URpcClient* URpcClient::CreateRpcClient(TSubclassOf Class, FRpcClientInstantiationParameters InstantiationParameters, UObject* Outer) { const FString& URI = FString::Printf(TEXT("%s:%d"), *(InstantiationParameters.Ip), InstantiationParameters.Port); return CreateRpcClientUri(Class, URI, InstantiationParameters.ChannelCredentials, Outer); } URpcClient* URpcClient::CreateRpcClientUri(TSubclassOf Class, const FString& URI, UChannelCredentials* ChannelCredentials, UObject* Outer) { UObject* const RealOuter = Outer ? Outer : (UObject*)GetTransientPackage(); if (URpcClient* const CreatedClient = NewObject(RealOuter, *Class)) { UE_LOG(LogInfraworldRuntime, Log, TEXT("Created RpcClient at [%p] with outer [%p]"), CreatedClient, RealOuter); bool IsClientInitialized = CreatedClient->Init(URI, ChannelCredentials); if (!IsClientInitialized) { UE_LOG(LogInfraworldRuntime, Error, TEXT("Unable to initialize an RPC client (%s::Init() failed"), *(Class->GetName())); return nullptr; } else { UE_LOG(LogInfraworldRuntime, Verbose, TEXT("An instance of %s has been created and initialized"), *(Class->GetName())); return CreatedClient; } } else { UE_LOG(LogInfraworldRuntime, Fatal, TEXT("Unable to create an instance of RPC client (NewObject<%s>() failed)"), *(Class->GetName())); return nullptr; } } void URpcClient::BeginDestroy() { // Being called when GC'ed, should be called synchronously. if (CanSendRequests()) { Stop(true); } Super::BeginDestroy(); } void URpcClient::Stop(bool bSynchronous) { FRunnableThread* ThreadToStop = Thread.Exchange(nullptr); if (ThreadToStop) { if (!InnerWorker->IsPendingStopped()) InnerWorker->MarkPendingStopped(); bCanSendRequests = false; UE_LOG(LogInfraworldRuntime, Verbose, TEXT("Scheduled to stop %s via setting 'bCanSendRequests = false', address %p"), *(GetClass()->GetName()), InnerWorker.Get()); // Should be synchronous in (almost) any case ThreadToStop->Kill(bSynchronous); delete ThreadToStop; ThreadToStop = nullptr; FTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle); } else { UE_LOG(LogInfraworldRuntime, Error, TEXT("Can not call Stop() for an already stopped (or penfing asinchronously stopped) instance of '%s'"), *(GetClass()->GetName())); } } ================================================ FILE: Source/InfraworldRuntime/Private/RpcClientWorker.cpp ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #include "RpcClientWorker.h" #include "InfraworldRuntime.h" #include "HAL/PlatformTime.h" #include "GenUtils.h" #include "GrpcIncludesBegin.h" #include #include #include "GrpcIncludesEnd.h" #include "WorkerUtils.h" // ========= RpcClientWorker implementation ======== RpcClientWorker::RpcClientWorker() : WorkerState(ERpcWorkerState::PendingInitialization) { } RpcClientWorker::~RpcClientWorker() { } uint32 RpcClientWorker::Run() { // If channel has not been created - we set bPendingStopped = true to StopBackground. if (!ensureAlways(WorkerState.Exchange(ERpcWorkerState::Initializing) == ERpcWorkerState::PendingInitialization)) { return 2; } if (HierarchicalInit()) { UE_LOG(LogInfraworldRuntime, Log, TEXT("Finished initialization via HierarchicalInit!")); ERpcWorkerState ExpectedState = ERpcWorkerState::Initializing; // this will set WorkerState to Working only if no one overwrote it from Initializing if (!WorkerState.CompareExchange(ExpectedState, ERpcWorkerState::Working)) { UE_LOG(LogInfraworldRuntime, Log, TEXT("Worker already marked pending stopped. Its state is %d"), static_cast(ExpectedState)); } } else return 1; // Update until not pending stopped while (WorkerState == ERpcWorkerState::Working) { UE_LOG(LogInfraworldRuntime, Verbose, TEXT("Updating via HierarchicalUpdate()")); HierarchicalUpdate(); FPlatformProcess::Sleep(0.1f); } WorkerState.Exchange(ERpcWorkerState::Shutdown); return 0; } void RpcClientWorker::DispatchError(const FString& ErrorMessage) { UE_CLOG(!ErrorMessageQueue, LogInfraworldRuntime, Fatal, TEXT("Can not dispatch an error message, because ErrorMessageQueue is null")); FRpcError Error; Error.ErrorMessage = ErrorMessage; ErrorMessageQueue->Enqueue(Error); } #if PLATFORM_WINDOWS #include "Windows/HideWindowsPlatformTypes.h" #endif ================================================ FILE: Source/InfraworldRuntime/Public/CastUtils.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include #include #include #include "GrpcIncludesBegin.h" #include #include #include #include "GrpcIncludesEnd.h" // Should be imported to avoid long chrono-related instructions. using std::chrono::milliseconds; using std::chrono::system_clock; // // Generic casts. Should be specified in each header file for each generated structure. // namespace casts { // ~~~~~ Unreal TMap and TArray, protobuf google::protobuf::RepeatedField and google::protobuf::Map) ~~~~~ template using _UnrealMap = TMap; template using _ProtobufMap = google::protobuf::Map; template using _UnrealArray = TArray; template using _ProtobufArray = google::protobuf::RepeatedField; template using _ProtobufPtrArray = google::protobuf::RepeatedPtrField; // ~~~~~ CAST PROTOTYPES (GENERIC) ~~~~~ template FORCEINLINE OutT Proto_Cast(const InT &Item) { // WARNING!!! // If you're receiving an error message, telling you something like: // // error: no matching conversion for static_cast from 'xxx' to 'yyy' // return (OutT) t; // ^~~~~ // error: no matching function for call to 'Proto_Cast' // consider creating your own template specification of Proto_Cast() // because by default it can only cast statically return static_cast(Item); } // ~~~~~ CAST FUNCTIONS (MAPS) ~~~~~ // TMap -> Protobuf Map template FORCEINLINE _UnrealMap Proto_MapCast(const _ProtobufMap& Map) { _UnrealMap OutMap; for (auto It = Map.cbegin(); It != Map.cend(); ++It) OutMap.Add(Proto_Cast(It->first), Proto_Cast(It->second)); return OutMap; } // Protobuf Map -> TMap template FORCEINLINE _ProtobufMap Proto_MapCast(const _UnrealMap& Map) { _ProtobufMap OutMap; for (const TPair& Pair : Map) OutMap.insert(google::protobuf::MapPair(Proto_Cast(Pair.Key), Proto_Cast(Pair.Value))); return OutMap; } // ~~~~~ CAST FUNCTIONS (ARRAYS) ~~~~~ template FORCEINLINE _ProtobufArray Proto_ArrayCast(const _UnrealArray& Array) { // Allocate a protobuf array and reserve capacity _ProtobufArray OutArray; OutArray.Reserve((int32)Array.Num()); // Each item shall be individually casted to OutT for (const InT& Item : Array) OutArray.Add(Proto_Cast(Item)); return OutArray; } template FORCEINLINE _UnrealArray Proto_ArrayCast(const _ProtobufArray& Array) { // Allocate a TArray and reserve capacity _UnrealArray OutArray; OutArray.Reserve((int32)Array.size()); // Each item shall be individually casted to OutT std::for_each(Array.cbegin(), Array.cend(), [&OutArray](InT Item) { OutArray.Add(Proto_Cast(Item)); }); return OutArray; } // Overload for _ProtobufPtrArray (google::protobuf::RepeatedPtrField) // _ProtobufPtrArray (aka google::protobuf::RepeatedPtrField) // is the same as RepeatedField, but used for repeated strings or messages template FORCEINLINE _ProtobufPtrArray Proto_PtrArrayCast(const _UnrealArray& Array) { // Allocate a protobuf array and reserve capacity _ProtobufPtrArray OutArray; OutArray.Reserve((int32)Array.Num()); // Each item shall be individually casted to OutT for (const InT& Item : Array) { const OutT& cast_result = Proto_Cast(Item); // We need to create a new instance of OutT to be posessed by the OutArray. OutArray.AddAllocated(new OutT(cast_result)); } return OutArray; } template FORCEINLINE _UnrealArray Proto_PtrArrayCast(const _ProtobufPtrArray& Array) { // Allocate a TArray and reserve capacity _UnrealArray OutArray; OutArray.Reserve((int32)Array.size()); // Each item shall be individually casted to OutT std::for_each(Array.cbegin(), Array.cend(), [&OutArray](InT Item) { OutArray.Add(Proto_Cast(Item)); }); return OutArray; } // Casting enums value-wise does not require any specialization. Thou can override the template function. template FORCEINLINE OutT Proto_EnumCast(const InT &Item) { return static_cast((int)Item); }; // ~~~~~ CAST FUNCTIONS (BYTE ARRAY), in protobuf byte arrays are std::strings ~~~~~ template <> FORCEINLINE FByteArray Proto_Cast(const std::string& String) { // Allocate a TArray TArray OutArray; // Put String's content into the array. We can not (and do not need) to cast away const qualifier. OutArray.Insert(reinterpret_cast(String.c_str()), String.size(), 0); // Finally, wrap all data into FByteArray return FByteArray(OutArray); } template <> FORCEINLINE std::string Proto_Cast(const FByteArray& Item) { const TArray& Arr = Item.Bytes; return std::string(reinterpret_cast(Arr.GetData()), Arr.Num()); } // ~~~~~ CAST FUNCTIONS (UNREAL STRING and PROTOBUF STRING) ~~~~~ template <> FORCEINLINE std::string Proto_Cast(const FString& String) { return std::string(TCHAR_TO_UTF8(*String)); } template <> FORCEINLINE FString Proto_Cast(const std::string& String) { return FString(String.c_str()); } /** * Casts an UE4-compatible client context to the GRPC-compatible context. * * @note That grpc::ClientContext doesn't have a copy constructor, so grpc::ClientContext can not be returned from a * ProtoCast(). Thus the method should set an instance of grpc::ClientContext that already exist. * @param InContext Input UE4-compatible client context. * @param OutContext Output GRPC-compatible client context. */ FORCEINLINE void CastClientContext(const FGrpcClientContext &InContext, grpc::ClientContext &OutContext) { // Cast and set metadata, checking for errors. for (const TPair& Pair : InContext.Metadata) { if (Pair.Key.IsEmpty()) { UE_LOG(LogTemp, Error, TEXT("Metadata key is empty for mapping '%s'->'%s' and thus won't be added to the client context. Behaviour is restricted by %s"), *Pair.Key, *Pair.Value, TEXT("grpc/core/lib/surface/validate_metadata:80")); } else if (Pair.Key.StartsWith(":")) { UE_LOG(LogTemp, Error, TEXT("Metadata key statrs with ':' for mapping '%s'->'%s' and thus won't be added to the client context. Behaviour is restricted by %s"), *Pair.Key, *Pair.Value, TEXT("grpc/core/lib/surface/validate_metadata:84")); } else { OutContext.AddMetadata(casts::Proto_Cast(Pair.Key), casts::Proto_Cast(Pair.Value)); } // TODO: Add some other validation checks if necessary. } // Set deadline (Only if it has a positive value. It is -1 by default) if (InContext.DeadlineSeconds > .0f) { const int64 Milliseconds = static_cast(((double)InContext.DeadlineSeconds * 1000.0)); OutContext.set_deadline(system_clock::now() + milliseconds(Milliseconds)); } // Set boolean parameters OutContext.set_idempotent(InContext.bIdempotent); OutContext.set_cacheable(InContext.bCacheable); OutContext.set_wait_for_ready(InContext.bWaitForReady); // Set authority OutContext.set_authority(casts::Proto_Cast(InContext.Authority)); // Set Compression Algorithm OutContext.set_compression_algorithm(Proto_EnumCast(InContext.GrpcCompressionAlgorithm)); // Set Initial Metadata Corked OutContext.set_initial_metadata_corked(InContext.bInitialMetadataCorked); } FORCEINLINE void CastStatus(const grpc::Status& InStatus, FGrpcStatus& OutStatus) { OutStatus.ErrorCode = Proto_EnumCast(InStatus.error_code()); OutStatus.ErrorMessage = Proto_Cast(InStatus.error_message()); OutStatus.ErrorDetails = Proto_Cast(InStatus.error_details()); } // Since we have no support for unsigned types in Blueprints, we need to template <> FORCEINLINE _UnrealArray Proto_ArrayCast(const _ProtobufArray& Array) { // Allocate a TArray and reserve capacity _UnrealArray OutArray; OutArray.Reserve((int32)Array.size()); // Each item shall be individually casted to OutT std::for_each(Array.cbegin(), Array.cend(), [&OutArray](uint32 Item) { OutArray.Add(Proto_Cast(Item)); }); return OutArray; } template <> FORCEINLINE _ProtobufArray Proto_ArrayCast(const _UnrealArray& Array) { // Allocate a protobuf array and reserve capacity _ProtobufArray OutArray; OutArray.Reserve((int32)Array.Num()); // Each item shall be individually casted to OutT for (const int32 Item : Array) OutArray.Add(Proto_Cast(Item)); return OutArray; } } ================================================ FILE: Source/InfraworldRuntime/Public/ChannelCredentials.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include #include "ChannelCredentials.generated.h" // namespace grpc // { // class ChannelCredentials; // } USTRUCT(BlueprintType) struct INFRAWORLDRUNTIME_API FRpcError { GENERATED_USTRUCT_BODY() UPROPERTY(BlueprintReadOnly, Category=Grpc) FString ErrorMessage; }; /** * A channel credentials object encapsulates all the state needed by a client * to authenticate with a server for a given channel. * It can make various assertions, e.g., about the client’s identity, role * for all the calls on that channel. */ UCLASS(NotBlueprintable, NotBlueprintType, notplaceable, noteditinlinenew, hidedropdown, Transient, Abstract) class INFRAWORLDRUNTIME_API UChannelCredentials : public UObject { GENERATED_BODY() public: /** * Builds credentials with reasonable defaults. * * \warning Only use these credentials when connecting to a Google endpoint. * Using these credentials to connect to any other service may result in this * service being able to impersonate your client for requests to Google * services. * @return Google Default Credentials */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="Vizor|RPC Credentials") static UChannelCredentials* MakeGoogleDefaultCredentials(); /** * Builds SSL Credentials given SSL specific options. * * @param PemRootCerts * The buffer containing the PEM encoding of the server root certificates. If * this parameter is empty, the default roots will be used. The default * roots can be overridden using the \a GRPC_DEFAULT_SSL_ROOTS_FILE_PATH * environment variable pointing to a file on the file system containing the * roots. * @param PemPrivateKey * The buffer containing the PEM encoding of the client's private key. This * parameter can be empty if the client does not have a private key. * @param PemCertChain * The buffer containing the PEM encoding of the client's certificate chain. * This parameter can be empty if the client does not have a certificate * chain. * @return Ssl Credentials */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="Vizor|RPC Credentials") static UChannelCredentials* MakeSslCredentials( UPARAM(DisplayName="PEM Root Certificates") FString PemRootCerts, UPARAM(DisplayName="PEM Private Key") FString PemPrivateKey, UPARAM(DisplayName="PEM Certificate Chain") FString PemCertChain ); /** * Builds credentials for an unencrypted, unauthenticated channel. * * @return Insecure Channel Credentials */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="Vizor|RPC Credentials") static UChannelCredentials* MakeInsecureChannelCredentials(); }; /** * Builds credentials with reasonable defaults. * * \warning Only use these credentials when connecting to a Google endpoint. * Using these credentials to connect to any other service may result in this * service being able to impersonate your client for requests to Google * services. */ UCLASS() class INFRAWORLDRUNTIME_API UGoogleDefaultCredentials : public UChannelCredentials { GENERATED_BODY() public: }; /** * Builds SSL Credentials given SSL specific options */ UCLASS() class INFRAWORLDRUNTIME_API USslCredentials : public UChannelCredentials { GENERATED_BODY() public: UPROPERTY(BlueprintReadOnly, Transient, DisplayName="PEM Root Certificates", Category=GrpcCerts) FString PemRootCerts; UPROPERTY(BlueprintReadOnly, Transient, DisplayName="PEM Private Key", Category=GrpcCerts) FString PemPrivateKey; UPROPERTY(BlueprintReadOnly, Transient, DisplayName="PEM Certificate Chain", Category=GrpcCerts) FString PemCertChain; }; /** * Credentials for an unencrypted, unauthenticated channel */ UCLASS() class INFRAWORLDRUNTIME_API UInsecureChannelCredentials : public UChannelCredentials { GENERATED_BODY() public: }; /** * Instantiation parameters are used to create an RPC client. */ USTRUCT(BlueprintType) struct INFRAWORLDRUNTIME_API FRpcClientInstantiationParameters { GENERATED_BODY() /** * The IP address of the endpoint to connect to. */ UPROPERTY(BlueprintReadWrite, Category=Endpoint) FString Ip; /** * The port of the endpoint to connect to. */ UPROPERTY(BlueprintReadWrite, Category=EndpointPort) int32 Port; /** * Credentials to use for the created RPC client. If it does not hold * an object or is invalid, an error will be thrown. */ UPROPERTY(BlueprintReadWrite, Category=Credentials) UChannelCredentials* ChannelCredentials; /** * Gets a GRPC URI for current ip address and port. * * @return URI for channel instantiation. */ FString GetURI() const { return FString::Printf(TEXT("%s:%d"), *Ip, Port); } /** * Gets a string representation of current FRpcClientInstantiationParameters. * * @return FRpcClientInstantiationParameters string representation. */ FString GetName() const { const FString& ParamsURI = GetURI(); const FString& CredentialsClassName = ChannelCredentials ? *(ChannelCredentials->GetClass()->GetName()) : TEXT("nullptr (DANGER!)"); return FString::Printf(TEXT("URI: %s, Credentials: %s"), *ParamsURI, *CredentialsClassName); } }; ================================================ FILE: Source/InfraworldRuntime/Public/ChannelProvider.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include #include #include #include "RpcClientWorker.h" #include "ChannelCredentials.h" namespace channel { FORCEINLINE bool WaitUntilChannelIsReady(const std::shared_ptr& Channel, std::chrono::system_clock::time_point Deadline) { grpc_connectivity_state State = Channel->GetState(true); while (State != GRPC_CHANNEL_READY) { if (!Channel->WaitForStateChange(State, Deadline)) return false; State = Channel->GetState(true); } return true; } FORCEINLINE bool WaitForConnection(float Seconds, const std::shared_ptr& Channel) { bool IsConnected = false; const int64 Milliseconds = (int64)((double) Seconds * 1000.0); std::chrono::system_clock::time_point start_tp = std::chrono::system_clock::now(); std::chrono::system_clock::time_point end_tp = std::chrono::system_clock::now() + std::chrono::milliseconds(Milliseconds); std::chrono::system_clock::time_point current_tp = start_tp; while (!IsConnected) { std::chrono::system_clock::time_point delta_tp = std::chrono::system_clock::now() + std::chrono::milliseconds(100); if (current_tp < end_tp) IsConnected = WaitUntilChannelIsReady(Channel, delta_tp); else break; current_tp = std::chrono::system_clock::now(); } return IsConnected; } FORCEINLINE std::shared_ptr GetGrpcCredentials(UChannelCredentials* const Credentials) { // Check whether provided credentails are null. if (!Credentials) { UE_LOG(LogTemp, Error, TEXT("Provided credentials are NULL. (Did you forget to pass ChannelCredentials to instantiation parameters?). Replacement is " "grpc::InsecureChannelCredentials().")); return grpc::InsecureChannelCredentials(); } // Classify the credentials if (Credentials->IsA()) { return grpc::GoogleDefaultCredentials(); } else if (Credentials->IsA()) { return grpc::InsecureChannelCredentials(); } else if (const USslCredentials* const SslCredentials = Cast(Credentials)) { grpc::SslCredentialsOptions Options; if (SslCredentials->PemRootCerts.Len() > 0) Options.pem_root_certs = TCHAR_TO_ANSI(*(SslCredentials->PemRootCerts)); if (SslCredentials->PemPrivateKey.Len() > 0) Options.pem_private_key = TCHAR_TO_ANSI(*(SslCredentials->PemPrivateKey)); if (SslCredentials->PemCertChain.Len() > 0) Options.pem_cert_chain = TCHAR_TO_ANSI(*(SslCredentials->PemCertChain)); return grpc::SslCredentials(Options); } // Unknown credentials UE_LOG(LogTemp, Error, TEXT("Don't know how to process credentials:'%s'. Replacement is grpc::InsecureChannelCredentials()."), *(Credentials->GetClass()->GetName())); return grpc::InsecureChannelCredentials(); } FORCEINLINE std::shared_ptr CreateChannel(RpcClientWorker* Worker) { UChannelCredentials* const ChannelCredentials = Worker->ChannelCredentials; UE_CLOG(!ChannelCredentials, LogTemp, Fatal, TEXT("Channel Credentials mustn't be null")); const FString& URI = Worker->URI; UE_LOG(LogTemp, Display, TEXT("The following Channel Credentials is used: \"%s\". Connecting to: \"%s\""), *(ChannelCredentials->GetName()), *URI); std::shared_ptr GrpcCredentials = GetGrpcCredentials(ChannelCredentials); std::shared_ptr Channel = grpc::CreateChannel(TCHAR_TO_ANSI(*URI), GrpcCredentials); bool bConnectionWasSuccessful = WaitForConnection(3, Channel); if (!bConnectionWasSuccessful) { Worker->DispatchError( NSLOCTEXT("InfraworldChannelProvider", "InfraworldChannelProviderGrpcServiceConnectionError", "Service connection failure!").ToString()); return std::shared_ptr(nullptr); } else { UE_LOG(LogTemp, Verbose, TEXT("%s"), *NSLOCTEXT("InfraworldChannelProvider", "InfraworldChannelProviderGrpcServiceConnectionSuccess", "Service connection established!").ToString()); } return Channel; } } ================================================ FILE: Source/InfraworldRuntime/Public/Conduit.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include "HAL/PlatformTLS.h" #include "Containers/Queue.h" /** * A conduit is a combination of two channel: The Request channel, and the Response channel, representing bidirectional queue. * A conduit is optimized to work efficiently and lock-free between two threads: the 'Request writer' thread and the * 'Response writer' thread. * One should call Acquire(Requests/Response)Producer() in the thread, which should produce Requests or Responses. */ template class TConduit { FORCEINLINE uint32 ThreadID() const { return FPlatformTLS::GetCurrentThreadId(); } public: TConduit() : RequestsProducerID(-1), ResponsesProducerID(-1) { } ~TConduit(); /** * Should be called from a Request producer thread. * After that: * - Only THIS thread can call Enqueue(Request), * - Only THIS thread can call Dequeue(Response), * - Calling IsEmpty() will tell whether the Request channel is empty. */ void AcquireRequestsProducer() { RequestsProducerID = ThreadID(); } /** * Should be called from a Response producer thread. * After that: * - Only THIS thread can call Enqueue(Response), * - Only THIS thread can call Dequeue(Request), * - Calling IsEmpty() will tell whether the Response channel is empty. */ void AcquireResponsesProducer() { ResponsesProducerID = ThreadID(); } // Enqueue: bool Enqueue(const TRequest& Item) { UE_CLOG(ThreadID() != RequestsProducerID, LogTemp, Fatal, TEXT("Can't call Enqueue(const TRequest&), invalid thread. Expected: %u, got: %u"), ResponsesProducerID, ThreadID()); return Requests.Enqueue(Item); } bool Enqueue(const TResponse& Item) { UE_CLOG(ThreadID() != ResponsesProducerID, LogTemp, Fatal, TEXT("Can't call Enqueue(const TResponse&), invalid thread. Expected: %u, got: %u"), RequestsProducerID, ThreadID()); return Responses.Enqueue(Item); } bool Enqueue(TRequest&& Item) { UE_CLOG(ThreadID() != RequestsProducerID, LogTemp, Fatal, TEXT("Can't call Enqueue(const TRequest&), invalid thread. Expected: %u, got: %u"), ResponsesProducerID, ThreadID()); return Requests.Enqueue(Item); } bool Enqueue(TResponse&& Item) { UE_CLOG(ThreadID() != ResponsesProducerID, LogTemp, Fatal, TEXT("Can't call Enqueue(const TResponse&), invalid thread. Expected: %u, got: %u"), RequestsProducerID, ThreadID()); return Responses.Enqueue(Item); } // Dequeue bool Dequeue(TRequest& OutItem) { UE_CLOG(ThreadID() != ResponsesProducerID, LogTemp, Fatal, TEXT("Can't call Dequeue(TRequest& OutItem), invalid thread. Expected: %u, got: %u"), ResponsesProducerID, ThreadID()); return Requests.Dequeue(OutItem); } bool Dequeue(TResponse& OutItem) { UE_CLOG(ThreadID() != RequestsProducerID, LogTemp, Fatal, TEXT("Can't call Dequeue(TResponse& OutItem), invalid thread. Expected: %u, got: %u"), RequestsProducerID, ThreadID()); return Responses.Dequeue(OutItem); } // Is Empty? bool IsEmpty() const { const uint32 Id = ThreadID(); if (Id == RequestsProducerID) return Responses.IsEmpty(); else if (Id == ResponsesProducerID) return Requests.IsEmpty(); else { UE_LOG(LogTemp, Fatal, TEXT("Can't call IsEmpty(), from an unknown thread: %d, RequestsProducerID: %u, ResponsesProducerID: %u"), Id, RequestsProducerID, ResponsesProducerID); return false; } } private: TQueue Requests; TQueue Responses; volatile uint32 RequestsProducerID; volatile uint32 ResponsesProducerID; }; template TConduit::~TConduit() { } ================================================ FILE: Source/InfraworldRuntime/Public/GenUtils.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include "GenUtils.generated.h" // XX - major version // YY - minor version // ZZ - patch #define INFRAWORLD_RUNTIME_VERSION 010000 /** * A wrapper for a TArray used to avoid inner generics. */ USTRUCT(BlueprintType) struct INFRAWORLDRUNTIME_API FByteArray { GENERATED_USTRUCT_BODY() UPROPERTY(BlueprintReadWrite, Category=ArrayWrapper) TArray Bytes; FByteArray() { } FByteArray(const TArray &InBytes) : Bytes(InBytes) { } }; // ~~~~~ Network CONTEXT ~~~~~ /** * The various compression algorithms supported by gRPC. */ UENUM(BlueprintType) enum class EGrpcCompressionAlgorithm : uint8 { CompressNone, CompressDeflate, CompressGzip }; /** * A ClientContext allows the person implementing a service client to: * * @todo Implement census/context if necessary. * * - Add custom metadata key-value pairs that will propagated to the server side. * - Control call settings such as compression and authentication. * - Initial and trailing metadata coming from the server. * - Get performance metrics (ie, census). * * Context settings are only relevant to the call they are invoked with, that * is to say, they aren't sticky. Some of these settings, such as the * compression options, can be made persistent at channel construction time * @see grpc::CreateCustomChannel() * * @warning ClientContext instances should not be reused across rpcs. * @see https://grpc.io/grpc/cpp/classgrpc_1_1_client_context.html#details */ USTRUCT(BlueprintType) struct INFRAWORLDRUNTIME_API FGrpcClientContext { GENERATED_USTRUCT_BODY() /** * Whether the optional Metadata attribute of the context is set. */ UPROPERTY(EditAnywhere, meta=(InlineEditConditionToggle), Category=Metadata) bool bOverride_Metadata = false; /** * Note: Toggle an 'eye' if you DON'T NEED metadata at all. * * Add the (Key -> Value) pair to the metadata associated with a client call. These are made available at the server * side by the grpc::ServerContext::client_metadata() method. * * Restrictions: * - Metadata keys can't be empty. * - Metadata keys can't start with ':'. * * Note: This method should only be called before invoking the rpc. * Note: Keys are metadata keys. If meta_value is binary data, it must end in "-bin". * Note: Value The metadata value. If its value is binary, it must be base64-encoding * https://tools.ietf.org/html/rfc4648#section-4 and Key must end in "-bin". */ UPROPERTY(BlueprintReadWrite, meta=(editcondition=bOverride_Metadata), Category=Metadata) TMap Metadata; /** * Set the deadline for the client call. Won't apply any deadline if values are (-INF, 0]. * @warning This method should only be called before invoking the rpc. */ UPROPERTY(BlueprintReadWrite, Category=Metadata) float DeadlineSeconds = -1.0f; /** * Set the per call authority header. * @see https://tools.ietf.org/html/rfc7540#section-8.1.2.3. */ UPROPERTY(BlueprintReadWrite, Category=Metadata) FString Authority; /** * Set an algorithm to be the compression algorithm used for the client call. */ UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Metadata) EGrpcCompressionAlgorithm GrpcCompressionAlgorithm; /** * EXPERIMENTAL: Set this request to be idempotent. */ UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Metadata) bool bIdempotent; /** * EXPERIMENTAL: Set this request to be cacheable. */ UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Metadata) bool bCacheable; /** * EXPERIMENTAL: Trigger wait-for-ready or not on this request. */ UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Metadata) bool bWaitForReady; /** * Flag whether the initial metadata should be corked. If corked is true, then the initial metadata will be colasced * with the write of first message in the stream. */ UPROPERTY(BlueprintReadWrite, AdvancedDisplay, Category=Metadata) bool bInitialMetadataCorked; }; // ~~~~~ Wrappers for CONTEXT and STATUS ~~~~~ template struct TRequestWithContext { TRequestType Request; FGrpcClientContext Context; TRequestWithContext() { } TRequestWithContext(const TRequestType& InRequest, const FGrpcClientContext& InContext) : Request(InRequest), Context(InContext) { } }; // Special constructor, automatically inferring arguments. template TRequestWithContext TRequestWithContext$New(const T& InRequest, const FGrpcClientContext& InContext) { return TRequestWithContext(InRequest, InContext); } /** * GRPC error code. * * @see https://grpc.io/grpc/cpp/namespacegrpc.html#aff1730578c90160528f6a8d67ef5c43b */ UENUM(BlueprintType) enum class EGrpcStatusCode : uint8 { /** * Not an error, returned on success. */ Ok = 0, /** * The operation was cancelled (typically by the caller). */ Cancelled = 1, /** * Unknown error. An example of where this error may be returned is if a * Status value received from another address space belongs to an error-space * that is not known in this address space. Also errors raised by APIs that * do not return enough error information may be converted to this error. */ Unknown = 2, /** * Client specified an invalid argument. Note that this differs from * FailedPrecondition. InvalidArgument indicates arguments that are * problematic regardless of the state of the system (e.g., a malformed file * name). */ InvalidArgument = 3, /** * Deadline expired before operation could complete. For operations that * change the state of the system, this error may be returned even if the * operation has completed successfully. For example, a successful response * from a server could have been delayed long enough for the deadline to * expire. */ DeadlineExceeded = 4, /** * Some requested entity (e.g., file or directory) was not found. */ NotFound = 5, /** * Some entity that we attempted to create (e.g., file or directory) already * exists. */ AlreadyExists = 6, /** * The caller does not have permission to execute the specified operation. * PermissionDenied must not be used for rejections caused by exhausting * some resource (use ResourceExhausted instead for those errors). * PermissionDenied must not be used if the caller can not be identified * (use Unauthenticated instead for those errors). */ PermissionDenied = 7, /** * The request does not have valid authentication credentials for the * operation. */ Unauthenticated = 16, /** * Some resource has been exhausted, perhaps a per-user quota, or perhaps the * entire file system is out of space. */ ResourceExhausted = 8, /** * Operation was rejected because the system is not in a state required for * the operation's execution. For example, directory to be deleted may be * non-empty, an rmdir operation is applied to a non-directory, etc. * * A litmus test that may help a service implementor in deciding * between FailedPrecondition, Aborted, and Unavailable: * (a) Use Unavailable if the client can retry just the failing call. * (b) Use Aborted if the client should retry at a higher-level * (e.g., restarting a read-modify-write sequence). * (c) Use FailedPrecondition if the client should not retry until * the system state has been explicitly fixed. E.g., if an "rmdir" * fails because the directory is non-empty, FailedPrecondition * should be returned since the client should not retry unless * they have first fixed up the directory by deleting files from it. * (d) Use FailedPrecondition if the client performs conditional * REST Get/Update/Delete on a resource and the resource on the * server does not match the condition. E.g., conflicting * read-modify-write on the same resource. */ FailedPrecondition = 9, /** * The operation was aborted, typically due to a concurrency issue l * * sequencer check failures, transaction aborts, etc. * * See litmus test above for deciding between FailedPrecondition, Aborted, * and Unavailable. */ Aborted = 10, /** * Operation was attempted past the valid range. E.g., seeking or reading * past end of file. * * Unlike InvalidArgument, this error indicates a problem that may be fixed * if the system state changes. For example, a 32-bit file system will * generate InvalidArgument if asked to read at an offset that is not in the * range [0,2^32-1], but it will generate OutOfRange if asked to read from * an offset past the current file size. * * There is a fair bit of overlap between FailedPrecondition and * OutOfRange. We recommend using OutOfRange (the more specific error) * when it applies so that callers who are iterating through a space can * easily look for an OutOfRange error to detect when they are done. */ OutOfRange = 11, /** * Operation is not implemented or not supported/enabled in this service. */ Unimplemented = 12, /** * Internal errors. Means some invariants expected by underlying System has * been broken. If you see one of these errors, Something is very broken. */ Internal = 13, /** * The service is currently unavailable. This is a most likely a transient * condition and may be corrected by retrying with a backoff. */ /** * See litmus test above for deciding between FailedPrecondition, Aborted, * and Unavailable. */ Unavailable = 14, /** * Unrecoverable data loss or corruption. */ DataLoss = 15 }; /** * Did it work? If it didn't, why? * * @see https://grpc.io/grpc/cpp/classgrpc_1_1_status.html#details */ USTRUCT(BlueprintType) struct INFRAWORLDRUNTIME_API FGrpcStatus { GENERATED_USTRUCT_BODY() /** * Return the instance's error code. */ UPROPERTY(BlueprintReadOnly, Category=Status) EGrpcStatusCode ErrorCode; /** * Return the instance's error message. */ UPROPERTY(BlueprintReadOnly, Category=Status) FString ErrorMessage; /** * The (binary) error details. Usually it contains a serialized google.rpc.Status proto. */ UPROPERTY(BlueprintReadOnly, Category=Status) FString ErrorDetails; }; template struct TResponseWithStatus { TResponseType Response; FGrpcStatus Status; TResponseWithStatus() { } TResponseWithStatus(const TResponseType& InResponse, const FGrpcStatus& InStatus) : Response(InResponse), Status(InStatus) { } }; ================================================ FILE: Source/InfraworldRuntime/Public/GrpcIncludesBegin.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ // Disable some warnings in GRPC #if PLATFORM_WINDOWS #pragma warning(push) #pragma warning (disable : 4125)// decimal digit terminates... #pragma warning (disable : 4647)// behavior change __is_pod... #pragma warning (disable : 4668)// 'symbol' is not defined as a preprocessor macro... #pragma warning (disable : 4456)// declaration of 'size' hides previous local declaration #pragma warning (disable : 4577)// 'noexcept' used with no exception handling mode specified #pragma warning (disable : 4946)// reinterpret_cast used between related classes #pragma warning (disable : 4005)// 'TEXT': macro redefinition #pragma warning (disable : 4582)// constructor is not implicitly called #pragma warning (disable : 4583)// destructor is not implicitly called #pragma warning (disable : 4800)// Implicit conversion from 'type' to bool. Possible information loss #ifdef WINDOWS_PLATFORM_TYPES_GUARD #pragma warning(push) #include "Windows/HideWindowsPlatformTypes.h" #endif #elif PLATFORM_COMPILER_CLANG #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundef" #pragma clang diagnostic ignored "-Wshadow" #endif ================================================ FILE: Source/InfraworldRuntime/Public/GrpcIncludesEnd.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ // Since GRPC actively uses winapi, we need to forbid windows macros // (such as GetMessage, MemoryBarrier, etc.) in our remaining code. // To do it, we 'wrap' all the C++ file's including ANY GRPC header files // content into Allow/Hide WindowsPlatformTypes. // We're unable to 'isolate' the WinAPI usage within a single C++ file thanks to Unity Build. #if PLATFORM_WINDOWS #pragma warning(pop) #ifndef UE4_MINIMAL_WINDOWS_INCLUDE #define UE4_MINIMAL_WINDOWS_INCLUDE #endif #include "Windows/AllowWindowsPlatformTypes.h" #elif PLATFORM_COMPILER_CLANG #pragma clang diagnostic pop #endif ================================================ FILE: Source/InfraworldRuntime/Public/GrpcUriValidator.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" class FGrpcUriValidator { public: /** * Attempts to validate a URI, further provided to grpc::CreateChannel function. * It does not tries to establish any kind of connections, so it checks only format. * * @param MaybeGrpcUri Grpc URI to validate. * @param OutError Error message if any. * * @return True if the URI is valid and thus could be used, false otherwise. */ static bool Validate(const FString& MaybeGrpcUri, FString& OutError); }; ================================================ FILE: Source/InfraworldRuntime/Public/InfraworldRuntime.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "Modules/ModuleManager.h" DECLARE_LOG_CATEGORY_EXTERN(LogInfraworldRuntime, Log, All); class FInfraworldRuntimeModule : public IModuleInterface { public: /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; }; ================================================ FILE: Source/InfraworldRuntime/Public/RpcClient.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "Containers/Queue.h" #include "Templates/SubclassOf.h" #include "Templates/Atomic.h" #include "RpcClientWorker.h" #include "ChannelCredentials.h" #include "RpcClient.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FRpcErrorSignature, URpcClient*, Dispatcher, const FRpcError&, Error); /** * An RPC client used to interact with GRPC services from Blueprints and UE-compatible C++ code. * Being used as a base class for generated RPC clients, an RPC Client contains a set of methods to manage an RPC Channel. * * @note You should never instantiate it directly. * * You should use a Proto2Cpp converter to create GRPC wrappers for an every single service. */ UCLASS(Abstract, BlueprintType, Blueprintable) class INFRAWORLDRUNTIME_API URpcClient : public UObject { GENERATED_BODY() bool Init(const FString& URI, UChannelCredentials* ChannelCredentials); public: URpcClient(); virtual ~URpcClient(); // Being called in implementations virtual void HierarchicalInit() PURE_VIRTUAL(URpcClient::HierarchicalInit,); // Being called in implementations virtual void HierarchicalUpdate() PURE_VIRTUAL(URpcClient::HierarchicalUpdate,); /** * Stops and disables this instance of RPC Client. * This operation is irreversible, you can't either send or receive requests and responses after doing so. * * @param bSynchronous * Stop and wait a sec. Oh when you look at me like that my darling - What did you expect? * If seriously - you should think thousand times before unchecking this option, because * you can experience heavy and unpredictable racing hazard. */ UFUNCTION(BlueprintCallable, Category="Vizor|RPC Client") void Stop(bool bSynchronous = true); /** * An update function, used to tell a Client, when to check queries and dispatch messages. * In widget (for example) you should call it every update (or redraw/invalidate). * * @note that frequency, you're calling this method won't ever affect the speed of message processing. * @deprecated No need to call this function anymore. */ UFUNCTION(BlueprintCallable, Category = "Vizor|RPC Client", meta = (DeprecatedFunction, DeprecationMessage = "No need to call this function anymore: Updates now are being dispatched automatically")) void Update(); /** * Checks whether the RPC Client could send requests. * * @return True if the RPC Client is properly initialized and can send requests. If not - all requests will be ignored. */ UFUNCTION(BlueprintCallable, BlueprintPure, Category="Vizor|RPC Client", meta=(DisplayName="Can Send Requests?")) bool CanSendRequests() const; /** * Instantiates a new RPC Dispatcher. You should use this function, not 'Construct Object from Class', to properly initialize the instance. * * @param Class * A subclass of RPC Client, that will be instantiated. Please don't select an abstract 'Rpc Client'. * @param InstantiationParameters * Parameters, will be used for instantiation, * @param Outer * An outer object, None (or empty) is valid as well, if so, GetTransientPackage() will be used to retrieve a static outer. * @return A newly created instance of RPC Client. */ UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Vizor|RPC Client", meta=(DisplayName="Create RPC Client", DeterminesOutputType="Class", DeprecatedFunction, DeprecationMessage="Use function, accepting URI and ChannelCredentials instead")) static URpcClient* CreateRpcClient(TSubclassOf Class, FRpcClientInstantiationParameters InstantiationParameters, UObject* Outer = nullptr); /** * Instantiates a new RPC Dispatcher. You should use this function, not 'Construct Object from Class', to properly initialize the instance. * * @param Class * A subclass of RPC Client, that will be instantiated. Please don't select an abstract 'Rpc Client'. * @param URI * Endpoint URI, will be used to establish connection. * Must not start with 'http://' or 'https://' or any URL scheme. * Must not have URL path, such as 'www.hello.eu/paths/are/not/allowed' * Must be either a domain name or an IP address with or without an explicit port. 80'th port is used by default. * @param ChannelCredentials * Credentials to use for the created RPC client. * @param Outer * An outer object, None (or empty) is valid as well, if so, GetTransientPackage() will be used to retrieve a static outer. * @return A newly created instance of RPC Client. */ UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="Vizor|RPC Client", meta=(DisplayName="Create RPC Client", DeterminesOutputType="Class")) static URpcClient* CreateRpcClientUri(TSubclassOf Class, const FString& URI, UChannelCredentials* ChannelCredentials, UObject* Outer = nullptr); /** * Called when did received any kind of error. */ UPROPERTY(BlueprintAssignable, Category="Vizor|RPC Client", meta=(DisplayName="Event RPC Error")) FRpcErrorSignature EventRpcError; protected: /** A pointer to an inner RpcClientWorker sending and receiving messages */ TUniquePtr InnerWorker; private: virtual void BeginDestroy() override; /** Whether the RPC Client could send requests or not */ bool bCanSendRequests = false; /** A thread, where RPC client worker will reside */ TAtomic Thread = { nullptr }; /** An accumulator for error messages */ TQueue ErrorMessageQueue; /** * Global engine ticker handler * Only "IsValid() -> true" if this RPC client "CanSendRequests() -> true" */ FDelegateHandle TickDelegateHandle; }; template FORCEINLINE T* NewRpcClient(const FString& URI, UChannelCredentials* ChannelCredentials, UObject* Outer = nullptr) { static_assert(TIsDerivedFrom::IsDerived, "T must derive URpcClient"); static_assert(!TIsSame::Value, "T must derive URpcClient, but mustn't be a bare URpcClient"); return Cast(URpcClient::CreateRpcClientUri(T::StaticClass(), URI, ChannelCredentials, Outer)); } ================================================ FILE: Source/InfraworldRuntime/Public/RpcClientWorker.h ================================================ /* * Copyright 2018 Vizor Games LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the * License for the specific language governing permissions and limitations * under the License. */ #pragma once #include "CoreMinimal.h" #include "Containers/Queue.h" #include "ChannelCredentials.h" #include "HAL/Runnable.h" #include "InfraworldRuntime.h" #include #include #include "Templates/Atomic.h" enum class ERpcWorkerState : uint8 { PendingInitialization, Initializing, Working, PendingShutdown, Shutdown }; class FGenAsyncRequest; /** * Base RPC Client Worker, it 'lives' in a separate thread and updates all conduits with responses. */ class INFRAWORLDRUNTIME_API RpcClientWorker : public FRunnable { public: RpcClientWorker(); virtual ~RpcClientWorker(); virtual uint32 Run() override; FORCEINLINE bool IsPendingStopped() const { return WorkerState.Load() == ERpcWorkerState::PendingShutdown; } FORCEINLINE void MarkPendingStopped() { UE_LOG(LogInfraworldRuntime, Log, TEXT("RpcClientWorker at [%p] Marking pending stopped"), this); const ERpcWorkerState PreviousWorkerState = WorkerState.Exchange(ERpcWorkerState::PendingShutdown); static const TSet ExpectedWorkerStates = { ERpcWorkerState::PendingInitialization, ERpcWorkerState::Initializing, ERpcWorkerState::Working }; ensureAlways(ExpectedWorkerStates.Contains(PreviousWorkerState)); } virtual bool HierarchicalInit() = 0; virtual void HierarchicalUpdate() = 0; void DispatchError(const FString& ErrorMessage); //public: FString URI; UChannelCredentials* ChannelCredentials; TQueue* ErrorMessageQueue; protected: TAtomic WorkerState; }; ================================================ FILE: Source/InfraworldRuntime/Public/WorkerUtils.h ================================================ #pragma once #include "GenUtils.h" #include "CastUtils.h" #include "Templates/Invoke.h" #include "RpcClientWorker.h" #include "GrpcIncludesBegin.h" #include "ChannelProvider.h" #include #include #include #include #include #include "GrpcIncludesEnd.h" template class TStubbedRpcWorker : public RpcClientWorker { public: template TResponseWithStatus AsyncRequest(const TUnrealRequest Request, const FGrpcClientContext Context, const TStubRequestFunctionPointer MemberPointer) { const TProtoRequest ClientRequest = casts::Proto_Cast(Request); grpc::ClientContext ClientContext; casts::CastClientContext(Context, ClientContext); grpc::CompletionQueue Queue; grpc::Status Status; std::unique_ptr> Rpc(Invoke(MemberPointer, Stub.get(), &ClientContext, ClientRequest, &Queue)); TProtoResponse Response; Rpc->Finish(&Response, &Status, (void*)1); void* got_tag; bool ok = false; while (true) { const std::chrono::seconds SingleWaitDuration = std::chrono::seconds(1); const std::chrono::time_point Deadline = std::chrono::system_clock::now() + SingleWaitDuration; const grpc::CompletionQueue::NextStatus NextStatus = Queue.AsyncNext(&got_tag, &ok, Deadline); if (NextStatus == grpc::CompletionQueue::NextStatus::GOT_EVENT) { break; } if (IsPendingStopped()) { ClientContext.TryCancel(); } } GPR_ASSERT(got_tag == (void*)1); GPR_ASSERT(ok); FGrpcStatus GrpcStatus; casts::CastStatus(Status, GrpcStatus); TResponseWithStatus Result(casts::Proto_Cast(Response), GrpcStatus); return Result; } protected: std::unique_ptr Stub; };