diff options
author | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2021-09-22 23:16:23 +0200 |
---|---|---|
committer | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2022-06-15 21:38:12 +0200 |
commit | e19b3701260d4a855736c623b8c8a270413f041e (patch) | |
tree | 905bce590cbc44c4077defa52276bac245c22a2a | |
parent | 8c18112f5dcf96bf24fab14ab4470c00ab7f7f70 (diff) | |
download | fork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.tar.gz fork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.tar.bz2 fork-godot-webrtc-native-e19b3701260d4a855736c623b8c8a270413f041e.zip |
Use libdatachannel library, add Godot 4 support.
-rw-r--r-- | .github/actions/webrtc-download/action.yml | 51 | ||||
-rw-r--r-- | .github/workflows/build_release.yml | 188 | ||||
-rw-r--r-- | .gitmodules | 11 | ||||
-rw-r--r-- | README.md | 55 | ||||
-rw-r--r-- | SConstruct | 434 | ||||
-rw-r--r-- | builders.py | 236 | ||||
-rw-r--r-- | deps/build/.gitignore (renamed from webrtc/.gitignore) | 0 | ||||
m--------- | deps/libdatachannel | 0 | ||||
m--------- | deps/openssl | 0 | ||||
m--------- | godot-cpp | 0 | ||||
m--------- | godot-cpp-3.x | 0 | ||||
-rwxr-xr-x | misc/scripts/file_format.sh | 2 | ||||
-rw-r--r-- | misc/webrtc.gdextension | 18 | ||||
-rw-r--r-- | misc/webrtc.tres (renamed from misc/gdnlib.tres) | 24 | ||||
-rw-r--r-- | src/WebRTCLibDataChannel.cpp | 232 | ||||
-rw-r--r-- | src/WebRTCLibDataChannel.hpp | 98 | ||||
-rw-r--r-- | src/WebRTCLibPeerConnection.cpp | 382 | ||||
-rw-r--r-- | src/WebRTCLibPeerConnection.hpp | 111 | ||||
-rw-r--r-- | src/init_gdextension.cpp | 72 | ||||
-rw-r--r-- | src/init_gdnative.cpp (renamed from src/init.cpp) | 10 | ||||
-rw-r--r-- | src/net/WebRTCDataChannelNative.cpp | 38 | ||||
-rw-r--r-- | src/net/WebRTCDataChannelNative.hpp | 40 | ||||
-rw-r--r-- | src/net/WebRTCPeerConnectionNative.cpp | 24 | ||||
-rw-r--r-- | src/net/WebRTCPeerConnectionNative.hpp | 22 |
24 files changed, 1030 insertions, 1018 deletions
diff --git a/.github/actions/webrtc-download/action.yml b/.github/actions/webrtc-download/action.yml deleted file mode 100644 index 75e268d..0000000 --- a/.github/actions/webrtc-download/action.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: 'Get WebRTC Library' -description: 'Get pre-build statically linked WebRTC library from Faless/webrtc-builds' -inputs: - repo: - description: 'Base repository' - required: true - default: "godotengine/webrtc-actions" - release: - description: 'Release tag' - required: true - default: '4472-33644-92ba70c' - webrtc-base-name: - description: 'The WebRTC version' - required: true - default: "webrtc-33644-92ba70c" - out-dir: - description: 'Directory where to extract the library' - required: true - default: "webrtc" - platform: - description: 'Platform to download' - required: true - archs: - description: 'Space separated list of architecture to fetch' - required: true -runs: - using: "composite" - steps: - - shell: bash - env: - RTC_BASE_URL: https://github.com/${{ inputs.repo }}/releases/download/${{ inputs.release }}/${{ inputs.webrtc-base-name }} - run: | - cd ${{ inputs.out-dir }} - - libplat=${{ inputs.platform }} - if [ "${{ inputs.platform }}" = "windows" ]; then - libplat=win - elif [ "${{ inputs.platform }}" = "osx" ]; then - libplat=mac - fi - - for arch in ${{ inputs.archs }} - do - echo "Downloading ${{ env.RTC_BASE_URL }}-${{ inputs.platform }}-${arch}.tar.gz" - curl -L ${{ env.RTC_BASE_URL }}-${libplat}-${arch}.tar.gz -o ${arch}.tar.gz - tar -xzf ${arch}.tar.gz - done - - mv lib ${{ inputs.platform }} - ls -l - ls -l * diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 2ab5787..d9cfd6e 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -33,77 +33,84 @@ jobs: name: 🔧 Build needs: static-checks strategy: + fail-fast: false matrix: include: # Android - platform: android - arch: 'x86' - sconsflags: 'android_arch=x86' + arch: 'x86_32' + gdnative_flags: 'android_arch=x86' + sconsflags: '' os: 'ubuntu-20.04' - platform: android - arch: 'x64' - sconsflags: 'android_arch=x86_64' + arch: 'x86_64' + gdnative_flags: 'android_arch=x86_64' + sconsflags: '' os: 'ubuntu-20.04' - platform: android - arch: 'arm' - sconsflags: 'android_arch=armv7' + arch: 'arm32' + gdnative_flags: 'android_arch=armv7' + sconsflags: '' os: 'ubuntu-20.04' - platform: android arch: 'arm64' - sconsflags: 'android_arch=arm64v8' + gdnative_flags: 'android_arch=arm64v8' + sconsflags: '' os: 'ubuntu-20.04' # iOS - platform: ios - arch: 'x64' - sconsflags: 'ios_arch=x86_64 ios_simulator=true' - os: 'macos-latest' - - platform: ios - arch: 'arm' - sconsflags: 'ios_arch=armv7' + arch: 'x86_64' + gdnative_flags: 'ios_arch=x86_64' + sconsflags: 'ios_simulator=true' os: 'macos-latest' - platform: ios arch: 'arm64' - sconsflags: 'ios_arch=arm64' - os: 'macos-latest' + gdnative_flags: 'ios_arch=arm64' + sconsflags: '' + os: 'macos-11' + # Linux - platform: linux - arch: 'x86' - sconsflags: 'bits=32' + arch: 'x86_32' + gdnative_flags: 'bits=32' + sconsflags: '' os: 'ubuntu-20.04' - platform: linux - arch: 'x64' - sconsflags: 'bits=64' + arch: 'x86_64' + gdnative_flags: 'bits=64' + sconsflags: '' os: 'ubuntu-20.04' # macOS - platform: osx - arch: 'x64' - sconsflags: 'bits=64' - os: 'macos-latest' + arch: 'x86_64' + gdnative_flags: 'macos_arch=x86_64 bits=64' + sconsflags: '' + os: 'macos-11' - platform: osx + gdnative_flags: 'macos_arch=arm64 bits=64' arch: 'arm64' - sconsflags: 'bits=64 macos_arch=arm64 macos_sdk_path=/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk/' - os: 'macos-10.15' + sconsflags: '' + os: 'macos-11' # Windows - platform: windows - arch: 'x86' - sconsflags: 'bits=32' - os: 'windows-latest' + arch: 'x86_32' + gdnative_flags: 'bits=32' + sconsflags: 'use_mingw=yes' + os: 'ubuntu-20.04' msvc_arch: amd64_x86 - platform: windows - arch: 'x64' - sconsflags: 'bits=64' - os: 'windows-latest' + arch: 'x86_64' + gdnative_flags: 'bits=64' + sconsflags: 'use_mingw=yes' + os: 'ubuntu-20.04' msvc_arch: amd64 env: - SCONSFLAGS: ${{ matrix.sconsflags }} platform=${{ matrix.platform }} --jobs=2 - NDK_VERSION: 22b - ANDROID_NDK_ROOT: ${{github.workspace}}/android-ndk-r22b - MSVC_VARS: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat' + SCONSFLAGS: ${{ matrix.sconsflags }} platform=${{ matrix.platform }} arch=${{ matrix.arch }} --jobs=2 defaults: run: @@ -114,31 +121,22 @@ jobs: with: submodules: recursive - - name: Cache NDK - id: cache-ndk - if: ${{ matrix.platform == 'android' }} - uses: actions/cache@v2 - with: - path: ${{ env.ANDROID_NDK_ROOT }} - key: ndk-${{ env.NDK_VERSION }} - - - name: Download NDK - if: ${{ matrix.platform == 'android' && steps.cache-ndk.outputs.cache-hit != 'true' }} - id: setup-ndk - run: | - cd ${{ github.workspace }} - curl -L https://dl.google.com/android/repository/android-ndk-r${{ env.NDK_VERSION }}-linux-x86_64.zip -o ndk.zip - unzip ndk.zip - ls - - - name: Setup MSVC build environment for ${{ matrix.msvc_arch }} + - name: Install Windows build dependencies if: ${{ matrix.platform == 'windows' }} - run: "'${{ env.MSVC_VARS }}' ${{ matrix.msvc_arch }}" + run: | + sudo apt-get update + sudo apt-get install build-essential mingw-w64 + sudo update-alternatives --set i686-w64-mingw32-gcc /usr/bin/i686-w64-mingw32-gcc-posix + sudo update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix + sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix + sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix + dpkg -l | grep ii | grep mingw + update-alternatives --get-selections | grep mingw - name: Install Linux build dependencies if: ${{ matrix.platform == 'linux' }} run: | - sudo apt-get install build-essential gcc-multilib wget g++-multilib + sudo apt-get install build-essential gcc-multilib g++-multilib - name: Set up Python 3.x uses: actions/setup-python@v2 @@ -152,35 +150,25 @@ jobs: python -m pip install scons python --version scons --version + cmake --version - - name: Get WebRTC package for ${{ matrix.platform }} - ${{ matrix.arch }} - uses: ./.github/actions/webrtc-download - with: - platform: ${{ matrix.platform }} - archs: ${{ matrix.arch }} - - - name: Fix godot-cpp revision and file names for OSX arm64 build. - if: ${{ matrix.platform == 'osx' && matrix.arch == 'arm64' }} + - name: Compile Extension - debug - ${{ matrix.platform }} - ${{ matrix.arch }} run: | - cd godot-cpp - git checkout e08ecdc28c5409cb5366027227e996c342dcee93 - rm -rf src/gen/ - rm -rf include/gen/ - mkdir bin - ln -s libgodot-cpp.osx.debug.64.a bin/libgodot-cpp.osx.debug.arm64.a - ln -s libgodot-cpp.osx.release.64.a bin/libgodot-cpp.osx.release.arm64.a - - - name: Compilation ${{ matrix.platform }} - ${{ matrix.arch }} - godot-cpp + scons target=debug generate_bindings=yes + + - name: Compile GDNative - debug - ${{ matrix.platform }} - ${{ matrix.arch }} run: | - scons -C godot-cpp target=debug generate_bindings=yes - scons -C godot-cpp target=release + scons target=debug generate_bindings=yes ${{ matrix.gdnative_flags }} godot_version=3 - - name: Compilation ${{ matrix.platform }} - ${{ matrix.arch }} - webrtc-native + - name: Compile Extension - release - ${{ matrix.platform }} - ${{ matrix.arch }} run: | - scons target=debug scons target=release - - uses: actions/upload-artifact@v2 + - name: Compile GDNative - release ${{ matrix.platform }} - ${{ matrix.arch }} + run: | + scons target=release ${{ matrix.gdnative_flags }} godot_version=3 + + - uses: actions/upload-artifact@v3 with: name: ${{ github.job }}-${{ matrix.platform }}-${{ matrix.arch }} path: bin/* @@ -191,32 +179,54 @@ jobs: runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v2 + with: + submodules: recursive - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: path: artifacts + - name: Bundle licenses. + run: | + cp LICENSE artifacts/LICENSE.webrtc-native + cp deps/libdatachannel/LICENSE artifacts/LICENSE.libdatachannel + cp deps/openssl/LICENSE.txt artifacts/LICENSE.openssl + cp deps/libdatachannel/deps/libjuice/LICENSE artifacts/LICENSE.libjuice + cp deps/libdatachannel/deps/usrsctp/LICENSE.md artifacts/LICENSE.usrsctp + cp deps/libdatachannel/deps/libsrtp/LICENSE artifacts/LICENSE.libsrtp + cp deps/libdatachannel/deps/json/LICENSE.MIT artifacts/LICENSE.json + cp deps/libdatachannel/deps/plog/LICENSE artifacts/LICENSE.plog + - name: Package artifacts for release run: | mkdir release cd release - for name in webrtc webrtc_debug + + ls -R + + for version in extension gdnative do - mkdir -p ${name}/lib/ - find ../artifacts -wholename "*/${name}/lib/*" | xargs cp -t ${name}/lib/ - find ../artifacts -wholename "*/${name}/${name}.tres" | head -n 1 | xargs cp -t ${name}/ + for name in webrtc webrtc_debug + do + destdir="${version}/${name}" + mkdir -p ${destdir}/lib + find ../artifacts -wholename "*/${destdir}/lib/*" | xargs cp -t ${destdir}/lib/ + find ../artifacts -wholename "*/${destdir}/${name}.tres" -or -wholename "*/${destdir}/${name}.gdextension" | head -n 1 | xargs cp -t ${destdir}/ + find ../artifacts -wholename "*/LICENSE*" | xargs cp -t ${destdir}/ + cd ${version} + zip -r ../godot-${version}-${name}.zip ${name} + cd .. + done done - zip -r godot-webrtc-native-release.zip webrtc - zip -r godot-webrtc-native-debug.zip webrtc_debug ls -R - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: - name: godot-webrtc-native-debug.zip - path: release/godot-webrtc-native-debug.zip + name: godot-webrtc-extension + path: release/*-extension-*.zip - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: - name: godot-webrtc-native-release.zip - path: release/godot-webrtc-native-release.zip + name: godot-webrtc-gdnative + path: release/*-gdnative-*.zip diff --git a/.gitmodules b/.gitmodules index 64fc4d4..1aa8d48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ +[submodule "godot-cpp-3.x"] + path = godot-cpp-3.x + url = https://github.com/godotengine/godot-cpp.git [submodule "godot-cpp"] path = godot-cpp - url = https://github.com/GodotNativeTools/godot-cpp + url = https://github.com/godotengine/godot-cpp.git +[submodule "libdatachannel"] + path = deps/libdatachannel + url = https://github.com/paullouisageneau/libdatachannel.git +[submodule "openssl"] + path = deps/openssl + url = https://github.com/openssl/openssl.git @@ -7,65 +7,44 @@ ### Compiling -Clone this repository with the following command to checkout both [godot-cpp](https://github.com/godotengine/godot-cpp) and [godot-headers](https://github.com/godotengine/godot-headers) dependencies. +Clone this repository with the following command to checkout all the dependencies: [godot-cpp](https://github.com/godotengine/godot-cpp), [openssl](https://www.openssl.org/) and [libdatachannel](https://github.com/paullouisageneau/libdatachannel) (and sub-dependencies). ``` $ git clone --recurse-submodules https://github.com/godotengine/webrtc-native.git ``` -Note that if you wish to use a specific branch, add the -b option to the clone command: -``` -$ git clone --recurse-submodules -b 3.2 https://github.com/godotengine/webrtc-native.git -``` - If you already checked out the branch use the following commands to update the dependencies: ``` $ git submodule update --init --recursive ``` -Right now our directory structure should look like this: -``` -webrtc-native/ -├─bin/ -├─godot-cpp/ -| └─godot-headers/ -├─src/ -└─webrtc/ -``` +### Compiling the extension. + +To build the GDExtension version of the plugin (Godot 4.0) run the following command from the `webrtc-native` folder: -### Compiling the cpp bindings library -First, we need to compile our cpp bindings library: ``` -$ cd godot-cpp -$ scons platform=<your platform> generate_bindings=yes -$ cd .. +$ scons platform=<your platform> ``` -> Replace `<your platform>` with either `windows`, `linux` or `osx`. - -> Include `use_llvm=yes` for using clang++ +This will build all the required dependencies into a single shared library. -> Include `target=runtime` to build a runtime build (windows only at the moment) +To build the GDNative version of the plugin (Godot 3.x) run the following command instead: -> Include `target=release` or `target=debug` for release or debug build. - -> The resulting library will be created in `godot-cpp/bin/`, take note of its name as it will be different depending on platform. - -### Building WebRTC +``` +$ scons platform=<your platform> godot_version=3 +``` -Building WebRTC is quite a complex task, involves huge downloads and long build times, and produces multiple output libraries that needs to bundled together. +> Replace `<your platform>` with either `windows`, `linux`, `osx`, `android`, or `ios`. -To make things easier, a set of [GitHub Actions](https://docs.github.com/en/actions) are used to generate the library for this plugin, [available in this repository](https://github.com/godotengine/webrtc-actions). +> Include `target=release` or `target=debug` for release or debug build (default is `debug`). -Alternatively, [**download the latest pre-compiled libraries**](https://github.com/godotengine/webrtc-actions/releases). +The resulting library and associated `tres` or `gdextension` will be created in `bin/[extension|gdnative]/webrtc[_debug]` depending on the `target` and `godot_version`. -Extract content of `include` into `webrtc/include` and content of `bin` into `webrtc/<your platform>` +You simply need to copy that folder to the root folder of your project. Note that you will have to build the library for all the desired export platforms. -### Compiling the plugin. +### License -``` -$ scons platform=<your platform> target=<your target> -``` +The `webrtc-native` plugin is licensed under the MIT license (see [LICENSE](https://github.com/godotengine/webrtc-native/blob/master/LICENSE)), while `libdatachannel` and one of its dependencies (`libjuice`) are licensed under LGPLv2.1 or later, see [libdatachannel LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE) and [libjuice LICENSE](https://github.com/paullouisageneau/libjuice/blob/master/LICENSE). -The generated library and associated `tres` will be placed in `bin/webrtc/` or `bin/webrtc_debug/` according to the desired target. You simply need to copy that folder to the root folder of your project. +Make sure you understand and comply with the LGPLv2.1 license when redistributing this plugin. @@ -2,17 +2,7 @@ import os, sys, platform, json, subprocess -if sys.version_info < (3,): - - def decode_utf8(x): - return x - - -else: - import codecs - - def decode_utf8(x): - return codecs.utf_8_decode(x)[0] +import builders def add_sources(sources, dirpath, extension): @@ -21,367 +11,123 @@ def add_sources(sources, dirpath, extension): sources.append(dirpath + "/" + f) -def gen_gdnative_lib(target, source, env): - for t in target: - with open(t.srcnode().path, "w") as w: - w.write( - decode_utf8(source[0].get_contents()) - .replace("{GDNATIVE_PATH}", os.path.splitext(t.name)[0]) - .replace("{TARGET}", env["target"]) - ) +def replace_flags(flags, replaces): + for k, v in replaces.items(): + if k in flags: + flags[flags.index(k)] = v env = Environment() - -target_arch = ARGUMENTS.get("b", ARGUMENTS.get("bits", "64")) -target_platform = ARGUMENTS.get("p", ARGUMENTS.get("platform", "linux")) -if target_platform == "windows": - # This makes sure to keep the session environment variables on windows, - # that way you can run scons in a vs 2017 prompt and it will find all the required tools - if target_arch == "64": - env = Environment(ENV=os.environ, TARGET_ARCH="amd64") - else: - env = Environment(ENV=os.environ, TARGET_ARCH="x86") - -env.Append(BUILDERS={"GDNativeLibBuilder": Builder(action=gen_gdnative_lib)}) - -customs = ["custom.py"] -opts = Variables(customs, ARGUMENTS) - -opts.Add(BoolVariable("use_llvm", "Use the LLVM compiler", False)) -opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "release"))) - -opts.Add(EnumVariable("android_arch", "Target Android architecture", "armv7", ["armv7", "arm64v8", "x86", "x86_64"])) -opts.Add( - "android_api_level", - "Target Android API level", - "18" if ARGUMENTS.get("android_arch", "armv7") in ["armv7", "x86"] else "21", -) -opts.Add( - "ANDROID_NDK_ROOT", - "Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.", - os.environ.get("ANDROID_NDK_ROOT", None), -) - -opts.Add(EnumVariable("ios_arch", "Target iOS architecture", "arm64", ["armv7", "arm64", "x86_64"])) -opts.Add(BoolVariable("ios_simulator", "Target iOS Simulator", False)) -opts.Add( - "IPHONEPATH", - "Path to iPhone toolchain", - "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain", -) -opts.Add("macos_sdk_path", "macOS SDK path", "") -opts.Add(EnumVariable("macos_arch", "Target macOS architecture", "x86_64", ["x86_64", "arm64"])) - -# Update environment (parse options) +opts = Variables(["customs.py"], ARGUMENTS) +opts.Add(EnumVariable("godot_version", "The Godot target version", "4", ["3", "4"])) opts.Update(env) -target = env["target"] - -if target_platform == "android": - target_arch = env["android_arch"] -elif target_platform == "ios": - target_arch = env["ios_arch"] - -host_platform = platform.system() -# Local dependency paths, adapt them to your setup -godot_headers = ARGUMENTS.get("headers", "godot-cpp/godot-headers") -godot_cpp_headers = ARGUMENTS.get("godot_cpp_headers", "godot-cpp/include") -godot_cpp_lib_dir = ARGUMENTS.get("godot_cpp_lib_dir", "godot-cpp/bin") -result_path = os.path.join("bin", "webrtc" if env["target"] == "release" else "webrtc_debug", "lib") -lib_prefix = "" - -# Convenience check to enforce the use_llvm overrides when CXX is clang(++) -if "CXX" in env and "clang" in os.path.basename(env["CXX"]): - env["use_llvm"] = True - -if target_platform == "linux": - env["CXX"] = "g++" - - # LLVM - if env["use_llvm"]: - if "clang++" not in os.path.basename(env["CXX"]): - env["CC"] = "clang" - env["CXX"] = "clang++" - env["LINK"] = "clang++" - - if env["target"] == "debug": - env.Prepend(CCFLAGS=["-g3"]) - env.Append(LINKFLAGS=["-rdynamic"]) - else: - env.Prepend(CCFLAGS=["-O3"]) - - env.Append(CCFLAGS=["-fPIC", "-std=c++14"]) - - if target_arch == "32": - env.Append(CCFLAGS=["-m32"]) - env.Append(LINKFLAGS=["-m32"]) - elif target_arch == "64": - env.Append(CCFLAGS=["-m64"]) - env.Append(LINKFLAGS=["-m64"]) - # i386 does not like static libstdc++ - env.Append(LINKFLAGS=["-static-libgcc", "-static-libstdc++"]) - -elif target_platform == "windows": - if host_platform == "Windows": - - lib_prefix = "lib" - env.Append(LINKFLAGS=["/WX"]) - if target == "debug": - env.Append(CCFLAGS=["/EHsc", "/D_DEBUG", "/MDd"]) - else: - env.Append(CCFLAGS=["/O2", "/EHsc", "/DNDEBUG", "/MD"]) - else: - if target_arch == "32": - env["CXX"] = "i686-w64-mingw32-g++" - elif target_arch == "64": - env["CXX"] = "x86_64-w64-mingw32-g++" - - if env["target"] == "debug": - env.Append(CCFLAGS=["-Og", "-g"]) - elif env["target"] == "release": - env.Append(CCFLAGS=["-O3"]) - - env.Append(CCFLAGS=["-std=c++14", "-Wwrite-strings"]) - env.Append(LINKFLAGS=["--static", "-Wl,--no-undefined", "-static-libgcc", "-static-libstdc++"]) - -elif target_platform == "osx": - if env["use_llvm"]: - env["CXX"] = "clang++" - - # Only 64-bits is supported for OS X - target_arch = "64" - if env["macos_arch"] != "x86_64": - target_arch = "arm64" +if env["godot_version"] == "3": + env = SConscript("godot-cpp-3.x/SConstruct") - env.Append(CCFLAGS=["-std=c++14", "-arch", env["macos_arch"]]) - env.Append(LINKFLAGS=["-arch", env["macos_arch"], "-framework", "Cocoa", "-Wl,-undefined,dynamic_lookup"]) + # Patch base env + replace_flags(env["CCFLAGS"], { + "-mios-simulator-version-min=10.0": "-mios-simulator-version-min=11.0", + "-miphoneos-version-min=10.0": "-miphoneos-version-min=11.0", + "/std:c++14": "/std:c++17", + "-std=c++14": "-std=c++17", + }) - if env["macos_sdk_path"]: - env.Append(CCFLAGS=["-isysroot", env["macos_sdk_path"]]) - env.Append(LINKFLAGS=["-isysroot", env["macos_sdk_path"]]) + env = env.Clone() if env["target"] == "debug": - env.Append(CCFLAGS=["-Og", "-g"]) - elif env["target"] == "release": - env.Append(CCFLAGS=["-O3"]) - -elif target_platform == "ios": - if env["ios_simulator"]: - sdk_name = "iphonesimulator" - env.Append(CCFLAGS=["-mios-simulator-version-min=10.0"]) - env["LIBSUFFIX"] = ".simulator" + env["LIBSUFFIX"] - else: - sdk_name = "iphoneos" - env.Append(CCFLAGS=["-miphoneos-version-min=10.0"]) - - try: - sdk_path = decode_utf8(subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip()) - except (subprocess.CalledProcessError, OSError): - raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name)) - - compiler_path = env["IPHONEPATH"] + "/usr/bin/" - env["ENV"]["PATH"] = env["IPHONEPATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] - - env["CC"] = compiler_path + "clang" - env["CXX"] = compiler_path + "clang++" - env["AR"] = compiler_path + "ar" - env["RANLIB"] = compiler_path + "ranlib" - - env.Append(CCFLAGS=["-std=c++14", "-arch", env["ios_arch"], "-isysroot", sdk_path]) - env.Append( - LINKFLAGS=["-arch", env["ios_arch"], "-Wl,-undefined,dynamic_lookup", "-isysroot", sdk_path, "-F" + sdk_path] - ) - - if env["target"] == "debug": - env.Append(CCFLAGS=["-Og", "-g"]) - elif env["target"] == "release": - env.Append(CCFLAGS=["-O3"]) - - -elif target_platform == "android": - # Verify NDK root - if not "ANDROID_NDK_ROOT" in env: - raise ValueError( - "To build for Android, ANDROID_NDK_ROOT must be defined. Please set ANDROID_NDK_ROOT to the root folder of your Android NDK installation." - ) - - # Validate API level - api_level = int(env["android_api_level"]) - if target_arch in ["arm64v8", "x86_64"] and api_level < 21: - print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21") - env["android_api_level"] = "21" - api_level = 21 - - # Setup toolchain - toolchain = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/" - if host_platform == "Windows": - toolchain += "windows" - import platform as pltfm - - if pltfm.machine().endswith("64"): - toolchain += "-x86_64" - elif host_platform == "Linux": - toolchain += "linux-x86_64" - elif host_platform == "Darwin": - toolchain += "darwin-x86_64" - - # Get architecture info - arch_info_table = { - "armv7": { - "march": "armv7-a", - "target": "armv7a-linux-androideabi", - "tool_path": "arm-linux-androideabi", - "compiler_path": "armv7a-linux-androideabi", - "ccflags": ["-mfpu=neon"], - }, - "arm64v8": { - "march": "armv8-a", - "target": "aarch64-linux-android", - "tool_path": "aarch64-linux-android", - "compiler_path": "aarch64-linux-android", - "ccflags": [], - }, - "x86": { - "march": "i686", - "target": "i686-linux-android", - "tool_path": "i686-linux-android", - "compiler_path": "i686-linux-android", - "ccflags": ["-mstackrealign"], - }, - "x86_64": { - "march": "x86-64", - "target": "x86_64-linux-android", - "tool_path": "x86_64-linux-android", - "compiler_path": "x86_64-linux-android", - "ccflags": [], - }, - } - arch_info = arch_info_table[target_arch] - - # Setup tools - env["CC"] = toolchain + "/bin/clang" - env["CXX"] = toolchain + "/bin/clang++" - - env.Append( - CCFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"], "-fPIC"] - ) - env.Append(CCFLAGS=arch_info["ccflags"]) - env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]]) - - if env["target"] == "debug": - env.Append(CCFLAGS=["-Og", "-g"]) - elif env["target"] == "release": - env.Append(CCFLAGS=["-O3"]) - + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + + if env["platform"] == "windows" and env["use_mingw"]: + env.Append(LINKFLAGS=["-static-libgcc"]) + + # Normalize suffix + if env["platform"] in ["windows", "linux"]: + env["arch"] = "x86_32" if env["bits"] == "32" else "x86_64" + env["arch_suffix"] = env["arch"] + elif env["platform"] == "osx": + env["arch"] = env["macos_arch"] + env["arch_suffix"] = env["arch"] + elif env["platform"] == "ios": + env["arch"] = "arm32" if env["ios_arch"] == "armv7" else env["ios_arch"] + env["arch_suffix"] = env["ios_arch"] + (".simulator" if env["ios_simulator"] else "") + elif env["platform"] == "android": + env["arch"] = { + "armv7": "arm32", + "arm64v8": "arm64", + "x86": "x86_32", + "x86_64": "x86_64", + }[env["android_arch"]] + env["arch_suffix"] = env["arch"] else: - print("No valid target platform selected.") - sys.exit(1) - -# Godot CPP bindings -env.Append(CPPPATH=[godot_headers]) -env.Append(CPPPATH=[godot_cpp_headers, godot_cpp_headers + "/core", godot_cpp_headers + "/gen"]) -env.Append(LIBPATH=[godot_cpp_lib_dir]) -env.Append( - LIBS=[ - "%sgodot-cpp.%s.%s.%s%s" - % (lib_prefix, target_platform, target, target_arch, ".simulator" if env["ios_simulator"] else "") - ] -) - -# WebRTC stuff -webrtc_dir = "webrtc" -lib_name = "libwebrtc_full" -lib_path = os.path.join(webrtc_dir, target_platform) + env = SConscript("godot-cpp/SConstruct") + replace_flags(env["CCFLAGS"], { + "-mios-simulator-version-min=10.0": "-mios-simulator-version-min=11.0", + "-miphoneos-version-min=10.0": "-miphoneos-version-min=11.0", + }) + env = env.Clone() -lib_path += { - "32": "/x86", - "64": "/x64", - "armv7": "/arm", - "arm64v8": "/arm64", - "arm64": "/arm64", - "x86": "/x86", - "x86_64": "/x64", -}[target_arch] - -if target == "debug": - lib_path += "/Debug" -else: - lib_path += "/Release" - -env.Append(CPPPATH=[webrtc_dir + "/include", webrtc_dir + "/include/third_party/abseil-cpp"]) +opts.Update(env) -if target_platform == "linux": - env.Append(LIBS=[lib_name, "atomic"]) - env.Append(LIBPATH=[lib_path]) - # env.Append(CCFLAGS=["-std=c++11"]) - env.Append(CCFLAGS=["-DWEBRTC_POSIX", "-DWEBRTC_LINUX"]) - env.Append(CCFLAGS=["-DRTC_UNUSED=''", "-DNO_RETURN=''"]) +target = env["target"] +result_path = os.path.join("bin", "gdnative" if env["godot_version"] == "3" else "extension", "webrtc" if env["target"] == "release" else "webrtc_debug") -elif target_platform == "windows": - # Mostly VisualStudio - if env["CC"] == "cl": - env.Append(CCFLAGS=["/DWEBRTC_WIN", "/DWIN32_LEAN_AND_MEAN", "/DNOMINMAX", "/DRTC_UNUSED=", "/DNO_RETURN="]) - env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in ["secur32", "advapi32", "winmm", lib_name]]) - env.Append(LIBPATH=[lib_path]) - # Mostly "gcc" - else: - env.Append( - CCFLAGS=[ - "-DWINVER=0x0603", - "-D_WIN32_WINNT=0x0603", - "-DWEBRTC_WIN", - "-DWIN32_LEAN_AND_MEAN", - "-DNOMINMAX", - "-DRTC_UNUSED=", - "-DNO_RETURN=", - ] - ) - env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in ["secur32", "advapi32", "winmm", lib_name]]) - env.Append(LIBPATH=[lib_path]) +# Dependencies +deps_source_dir = "deps" +env.Append(BUILDERS={ + "BuildOpenSSL": env.Builder(action=builders.ssl_action, emitter=builders.ssl_emitter), + "BuildLibDataChannel": env.Builder(action=builders.rtc_action, emitter=builders.rtc_emitter), +}) -elif target_platform == "osx": - env.Append(LIBS=[lib_name]) - env.Append(LIBPATH=[lib_path]) - env.Append(CCFLAGS=["-DWEBRTC_POSIX", "-DWEBRTC_MAC"]) - env.Append(CCFLAGS=["-DRTC_UNUSED=''", "-DNO_RETURN=''"]) +# SSL +ssl = env.BuildOpenSSL(env.Dir(builders.get_ssl_build_dir(env)), env.Dir(builders.get_ssl_source_dir(env))) -elif target_platform == "ios": - env.Append(LIBS=[lib_name]) - env.Append(LIBPATH=[lib_path]) - env.Append(CCFLAGS=["-DWEBRTC_POSIX", "-DWEBRTC_MAC", "-DWEBRTC_IOS"]) - env.Append(CCFLAGS=["-DRTC_UNUSED=''", "-DNO_RETURN=''"]) +env.Prepend(CPPPATH=[builders.get_ssl_include_dir(env)]) +env.Prepend(LIBPATH=[builders.get_ssl_build_dir(env)]) +env.Append(LIBS=[ssl]) -elif target_platform == "android": - env.Append(LIBS=["log"]) - env.Append(LIBS=[lib_name]) - env.Append(LIBPATH=[lib_path]) - env.Append(CCFLAGS=["-DWEBRTC_POSIX", "-DWEBRTC_LINUX", "-DWEBRTC_ANDROID"]) - env.Append(CCFLAGS=["-DRTC_UNUSED=''", "-DNO_RETURN=''"]) +# RTC +rtc = env.BuildLibDataChannel(env.Dir(builders.get_rtc_build_dir(env)), [env.Dir(builders.get_rtc_source_dir(env))] + ssl) - if target_arch == "arm64v8": - env.Append(CCFLAGS=["-DWEBRTC_ARCH_ARM64", "-DWEBRTC_HAS_NEON"]) - elif target_arch == "armv7": - env.Append(CCFLAGS=["-DWEBRTC_ARCH_ARM", "-DWEBRTC_ARCH_ARM_V7", "-DWEBRTC_HAS_NEON"]) +env.Append(LIBPATH=[builders.get_rtc_build_dir(env)]) +env.Append(CPPPATH=[builders.get_rtc_include_dir(env)]) +env.Prepend(LIBS=[rtc]) # Our includes and sources env.Append(CPPPATH=["src/"]) sources = [] -add_sources(sources, "src/", "cpp") -add_sources(sources, "src/net/", "cpp") +sources.append( + [ + "src/WebRTCLibDataChannel.cpp", + "src/WebRTCLibPeerConnection.cpp", + ] +) +if env["godot_version"] == "4": + sources.append("src/init_gdextension.cpp") +else: + env.Append(CPPDEFINES=["GDNATIVE_WEBRTC"]) + sources.append("src/init_gdnative.cpp") + add_sources(sources, "src/net/", "cpp") -# Suffix -suffix = ".%s.%s" % (target, target_arch) -env["SHOBJSUFFIX"] = suffix + env["SHOBJSUFFIX"] +env.Depends(sources, [ssl, rtc]) # Make the shared library -result_name = "webrtc_native.%s.%s.%s" % (target_platform, target, target_arch) + env["SHLIBSUFFIX"] +result_name = "webrtc_native.{}.{}.{}{}".format(env["platform"], env["target"], env["arch_suffix"], env["SHLIBSUFFIX"]) +env.Depends(sources, ssl) + +if env["platform"] == "windows" and env["use_mingw"]: + env.Append(LIBS=["iphlpapi", "ws2_32", "bcrypt"]) -library = env.SharedLibrary(target=os.path.join(result_path, result_name), source=sources) +library = env.SharedLibrary(target=os.path.join(result_path, "lib", result_name), source=sources) Default(library) # GDNativeLibrary gdnlib = "webrtc" if target != "release": gdnlib += "_debug" -Default(env.GDNativeLibBuilder([os.path.join("bin", gdnlib, gdnlib + ".tres")], ["misc/gdnlib.tres"])) +ext = ".tres" if env["godot_version"] == "3" else ".gdextension" +extfile = env.Substfile(os.path.join(result_path, gdnlib + ext), "misc/webrtc" + ext, SUBST_DICT={ + "{GDNATIVE_PATH}": gdnlib, + "{TARGET}": env["target"], +}) +Default(extfile) diff --git a/builders.py b/builders.py new file mode 100644 index 0000000..32dd1f3 --- /dev/null +++ b/builders.py @@ -0,0 +1,236 @@ +import os +from SCons.Script import Environment + + +def get_android_api(env): + return env["android_api_level"] if int(env["android_api_level"]) > 28 else "28" + + +def get_deps_dir(env): + return env.Dir("#deps").abspath + + +def get_deps_build_dir(env): + return get_deps_dir(env) + "/build/{}.{}.{}.dir".format(env["platform"], env["target"], env["arch_suffix"]) + + +def get_rtc_source_dir(env): + return get_deps_dir(env) + "/libdatachannel" + + +def get_rtc_build_dir(env): + return get_deps_build_dir(env) + "/libdatachannel" + + +def get_rtc_include_dir(env): + return get_rtc_source_dir(env) + "/include" + + +def get_ssl_source_dir(env): + return get_deps_dir(env) + "/openssl" + + +def get_ssl_build_dir(env): + return get_deps_build_dir(env) + "/openssl" + + +def get_ssl_install_dir(env): + return get_ssl_build_dir(env) + "/dest" + + +def get_ssl_include_dir(env): + return get_ssl_install_dir(env) + "/include" + + +def ssl_emitter(target, source, env): + build_dir = get_ssl_build_dir(env) + libs = ["libssl.a", "libcrypto.a"] + install_dir = get_ssl_install_dir(env) + ssl_include = os.path.join(source[0].abspath, "include") + return [env.File(build_dir + "/" + l) for l in libs], source + + +def ssl_action(target, source, env): + build_dir = get_ssl_build_dir(env) + source_dir = source[0].abspath + + ssl_env = Environment() + install_dir = get_ssl_install_dir(env) + args = [ + "no-ssl3", + "no-weak-ssl-ciphers", + "no-legacy", + "--prefix=%s" % install_dir, + "--openssldir=%s" % install_dir, + ] + if env["platform"] != "windows": + args.append("no-shared") # Windows "app" doesn't like static-only builds. + if env["platform"] == "linux": + if env["arch"] == "x86_32": + args.extend(["linux-x86"]) + else: + args.extend(["linux-x86_64"]) + + elif env["platform"] == "android": + args.extend([ + { + "arm64": "android-arm64", + "arm32": "android-arm", + "x86_32": "android-x86", + "x86_64": "android-x86_64", + }[env["arch"]], + "-D__ANDROID_API__=%s" % get_android_api(env), + ]) + # Setup toolchain path. + ssl_env.PrependENVPath("PATH", os.path.dirname(env["CC"])) + ssl_env["ENV"]["ANDROID_NDK_ROOT"] = os.environ.get("ANDROID_NDK_ROOT", "") + + elif env["platform"] == "osx": + if env["arch"] == "x86_64": + args.extend(["darwin64-x86_64"]) + elif env["arch"] == "arm64": + args.extend(["darwin64-arm64"]) + else: + raise ValueError("macOS architecture not supported: %s" % env["arch"]) + + elif env["platform"] == "ios": + if env["ios_simulator"]: + args.extend(["iossimulator-xcrun"]) + elif env["arch"] == "arm32": + args.extend(["ios-xcrun"]) + elif env["arch"] == "arm64": + args.extend(["ios64-xcrun"]) + else: + raise ValueError("iOS architecture not supported: %s" % env["arch"]) + + elif env["platform"] == "windows": + if env["arch"] == "x86_32": + if env["use_mingw"]: + args.extend([ + "mingw", + "--cross-compile-prefix=i686-w64-mingw32-", + ]) + else: + args.extend(["VC-WIN32"]) + else: + if env["use_mingw"]: + args.extend([ + "mingw64", + "--cross-compile-prefix=x86_64-w64-mingw32-", + ]) + else: + args.extend(["VC-WIN64A"]) + + jobs = env.GetOption("num_jobs") + ssl_env.Execute([ + "mkdir -p " + build_dir, # TODO python? + ("cd %s && %s/Configure " % (build_dir, source_dir)) + " ".join(args), + "make -C %s -j%s" % (build_dir, jobs), + "make -C %s install_sw install_ssldirs -j%s" % (build_dir, jobs), + ] + ) + return None + + +def rtc_emitter(target, source, env): + build_dir = get_rtc_build_dir(env) + libs = ["libdatachannel-static.a", "libjuice-static.a", "libsrtp2.a", "libusrsctp.a"] + lib_paths = [ + build_dir, + os.path.join(build_dir, "deps/libjuice"), + os.path.join(build_dir, "deps/libsrtp"), + os.path.join(build_dir, "deps/usrsctp/usrsctplib"), + ] + return [env.File(lib_paths[i] + "/" + libs[i]) for i in range(len(libs))], source + + +def rtc_action(target, source, env): + build_dir = get_rtc_build_dir(env) + source_dir = source[0].abspath + args = [ + "-B", + build_dir, + "-DUSE_NICE=0", + "-DNO_WEBSOCKET=1", + #"-DNO_MEDIA=1", # Windows builds fail without it. + "-DNO_EXAMPLES=1", + "-DNO_WEBSOCKET=1", + "-DNO_TESTS=1", + "-DOPENSSL_USE_STATIC_LIBS=1", + "-DOPENSSL_INCLUDE_DIR=%s" % get_ssl_include_dir(env), + "-DOPENSSL_SSL_LIBRARY=%s/libssl.a" % get_ssl_build_dir(env), + "-DOPENSSL_CRYPTO_LIBRARY=%s/libcrypto.a" % get_ssl_build_dir(env), + ] + if env["platform"] == "android": + abi = { + "arm64": "arm64-v8a", + "arm32": "armeabi-v7a", + "x86_32": "x86", + "x86_64": "x86_64", + }[env["arch"]] + args.extend([ + "-DCMAKE_SYSTEM_NAME=Android", + "-DCMAKE_SYSTEM_VERSION=%s" % get_android_api(env), + "-DCMAKE_ANDROID_ARCH_ABI=%s" % abi, + "-DANDROID_ABI=%s" % abi, + "-DCMAKE_TOOLCHAIN_FILE=%s/build/cmake/android.toolchain.cmake" % os.environ.get("ANDROID_NDK_ROOT", ""), + "-DCMAKE_ANDROID_STL_TYPE=c++_static", + ]) + elif env["platform"] == "linux": + if env["arch"] == "x86_32": + args.extend([ + "-DCMAKE_C_FLAGS=-m32", + "-DCMAKE_CXX_FLAGS=-m32" + ]) + else: + args.extend([ + "-DCMAKE_C_FLAGS=-m64", + "-DCMAKE_CXX_FLAGS=-m64" + ]) + elif env["platform"] == "osx": + if env["macos_deployment_target"] != "default": + args.extend(["-DCMAKE_OSX_DEPLOYMENT_TARGET=%s" % env["macos_deployment_target"]]) + if env["arch"] == "x86_64": + args.extend(["-DCMAKE_OSX_ARCHITECTURES=x86_64"]) + elif env["arch"] == "arm64": + args.extend(["-DCMAKE_OSX_ARCHITECTURES=arm64"]) + else: + raise ValueError("OSX architecture not supported: %s" % env["arch"]) + elif env["platform"] == "ios": + if env["arch"] == "universal": + raise ValueError("iOS architecture not supported: %s" % env["arch"]) + args.extend([ + "-DCMAKE_SYSTEM_NAME=iOS", + "-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0", + "-DCMAKE_OSX_ARCHITECTURES=%s" % env["arch"], + ]) + if env["ios_simulator"]: + args.extend(["-DCMAKE_OSX_SYSROOT=iphonesimulator"]) + elif env["platform"] == "windows": + args.extend(["-DOPENSSL_ROOT_DIR=%s" % get_ssl_build_dir(env)]) + if env["arch"] == "x86_32": + if env["use_mingw"]: + args.extend([ + "-G 'Unix Makefiles'", + "-DCMAKE_C_COMPILER=i686-w64-mingw32-gcc", + "-DCMAKE_CXX_COMPILER=i686-w64-mingw32-g++", + "-DCMAKE_SYSTEM_NAME=Windows", + ]) + else: + if env["use_mingw"]: + args.extend([ + "-G 'Unix Makefiles'", + "-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc", + "-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++", + "-DCMAKE_SYSTEM_NAME=Windows" + ]) + + args.append(source_dir) + jobs = env.GetOption("num_jobs") + rtc_env = Environment() + rtc_env.Execute([ + "cmake " + " ".join(args), + "cmake --build %s -t datachannel-static -j%s" % (build_dir, jobs), + ] + ) + return None diff --git a/webrtc/.gitignore b/deps/build/.gitignore index d6b7ef3..d6b7ef3 100644 --- a/webrtc/.gitignore +++ b/deps/build/.gitignore diff --git a/deps/libdatachannel b/deps/libdatachannel new file mode 160000 +Subproject 59cc9f214c7435c394e531b0c67ff1b86279f2b diff --git a/deps/openssl b/deps/openssl new file mode 160000 +Subproject 4d346a188c27bdf78aa76590c641e1217732ca4 diff --git a/godot-cpp b/godot-cpp -Subproject 77d41fa179e40560f1e264ed483638bf5171377 +Subproject 40f5bfda226b71b629742c8314f2f175da7b523 diff --git a/godot-cpp-3.x b/godot-cpp-3.x new file mode 160000 +Subproject ac572d5f847572ee90e32e014854894dda8b4ce diff --git a/misc/scripts/file_format.sh b/misc/scripts/file_format.sh index e6e1fe2..3bb4d8d 100755 --- a/misc/scripts/file_format.sh +++ b/misc/scripts/file_format.sh @@ -20,6 +20,8 @@ while IFS= read -rd '' f; do continue elif [[ "$f" == "godot-cpp"* ]]; then continue + elif [[ "$f" == "misc/patches"* ]]; then + continue fi # Ensure that files are UTF-8 formatted. recode UTF-8 "$f" 2> /dev/null diff --git a/misc/webrtc.gdextension b/misc/webrtc.gdextension new file mode 100644 index 0000000..a00d370 --- /dev/null +++ b/misc/webrtc.gdextension @@ -0,0 +1,18 @@ +[configuration] + +entry_symbol = "webrtc_extension_init" + +[libraries] + +linux.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_64.so" +linux.x86_32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_32.so" +osx.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.osx.{TARGET}.x86_64.dylib" +osx.arm64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.osx.{TARGET}.arm64.dylib" +windows.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.windows.{TARGET}.x86_64.dll" +windows.x86_32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.windows.{TARGET}.x86_32.dll" +android.arm64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.arm64.so" +android.arm32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.arm32.so" +android.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.x86_64.so" +android.x86_32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.x86_32.so" +ios.arm64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.arm64.dylib" +ios.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.x86_64.simulator.dylib" diff --git a/misc/gdnlib.tres b/misc/webrtc.tres index eb291a9..9519d9f 100644 --- a/misc/gdnlib.tres +++ b/misc/webrtc.tres @@ -3,21 +3,21 @@ [resource] singleton = true reloadable = false -entry/OSX.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.osx.{TARGET}.64.dylib" +entry/OSX.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.osx.{TARGET}.x86_64.dylib" entry/OSX.arm64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.osx.{TARGET}.arm64.dylib" -entry/Windows.64 = "res://{GDNATIVE_PATH}/lib/webrtc_native.windows.{TARGET}.64.dll" -entry/Windows.32 = "res://{GDNATIVE_PATH}/lib/webrtc_native.windows.{TARGET}.32.dll" -entry/X11.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.64.so" -entry/X11.32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.32.so" -entry/Server.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.64.so" -entry/Server.32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.32.so" -entry/Android.armeabi-v7a = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.armv7.so" -entry/Android.arm64-v8a = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.arm64v8.so" +entry/Windows.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.windows.{TARGET}.x86_64.dll" +entry/Windows.32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.windows.{TARGET}.x86_32.dll" +entry/X11.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_64.so" +entry/X11.32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_32.so" +entry/Server.64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_64.so" +entry/Server.32 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.linux.{TARGET}.x86_32.so" +entry/Android.armeabi-v7a = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.arm32.so" +entry/Android.arm64-v8a = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.arm64.so" entry/Android.x64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.x86_64.so" -entry/Android.x86 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.x86.so" -entry/iOS.armv7 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.armv7.dylib" +entry/Android.x86 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.android.{TARGET}.x86_32.so" +entry/iOS.armv7 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.armv32.dylib" entry/iOS.arm64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.arm64.dylib" -entry/iOS.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.x86_64.dylib" +entry/iOS.x86_64 = "res://{GDNATIVE_PATH}/lib/libwebrtc_native.ios.{TARGET}.x86_64.simulator.dylib" dependency/Windows.64 = [ ] dependency/Windows.32 = [ ] dependency/X11.64 = [ ] diff --git a/src/WebRTCLibDataChannel.cpp b/src/WebRTCLibDataChannel.cpp index bab9c79..5c4ad5d 100644 --- a/src/WebRTCLibDataChannel.cpp +++ b/src/WebRTCLibDataChannel.cpp @@ -30,138 +30,155 @@ #include "WebRTCLibDataChannel.hpp" +#ifdef GDNATIVE_WEBRTC #include "GDNativeLibrary.hpp" #include "NativeScript.hpp" +#define ERR_UNAVAILABLE GODOT_ERR_UNAVAILABLE +#define FAILED GODOT_FAILED +#define ERR_INVALID_PARAMETER GODOT_ERR_INVALID_PARAMETER +#define OK GODOT_OK +#endif -using namespace godot_webrtc; - -// Channel observer -WebRTCLibDataChannel::ChannelObserver::ChannelObserver(WebRTCLibDataChannel *parent) { - this->parent = parent; -} - -void WebRTCLibDataChannel::ChannelObserver::OnMessage(const webrtc::DataBuffer &buffer) { - parent->queue_packet(buffer.data.data<uint8_t>(), buffer.data.size()); -} - -void WebRTCLibDataChannel::ChannelObserver::OnStateChange() { -} +#include <stdio.h> +#include <string.h> +#include <cstring> -void WebRTCLibDataChannel::ChannelObserver::OnBufferedAmountChange(uint64_t previous_amount) { -} +using namespace godot; +using namespace godot_webrtc; // DataChannel -WebRTCLibDataChannel *WebRTCLibDataChannel::new_data_channel(rtc::scoped_refptr<webrtc::DataChannelInterface> p_channel) { +WebRTCLibDataChannel *WebRTCLibDataChannel::new_data_channel(std::shared_ptr<rtc::DataChannel> p_channel, bool p_negotiated) { // Invalid channel result in NULL return - ERR_FAIL_COND_V(p_channel.get() == nullptr, NULL); + ERR_FAIL_COND_V(!p_channel, nullptr); +#ifdef GDNATIVE_WEBRTC // Instance a WebRTCDataChannelGDNative object - godot::WebRTCDataChannelGDNative *out = godot::WebRTCDataChannelGDNative::_new(); - // Set our implementation as it's script - godot::NativeScript *script = godot::NativeScript::_new(); - script->set_library(godot::detail::get_wrapper<godot::GDNativeLibrary>((godot_object *)godot::gdnlib)); + WebRTCDataChannelGDNative *native = WebRTCDataChannelGDNative::_new(); + // Set our implementation as its script + NativeScript *script = NativeScript::_new(); + script->set_library(detail::get_wrapper<GDNativeLibrary>((godot_object *)gdnlib)); script->set_class_name("WebRTCLibDataChannel"); - out->set_script(script); - - // Bind the data channel to the ScriptInstance userdata (our script) - WebRTCLibDataChannel *tmp = out->cast_to<WebRTCLibDataChannel>(out); - tmp->bind_channel(p_channel); - - return tmp; + native->set_script(script); + WebRTCLibDataChannel *out = native->cast_to<WebRTCLibDataChannel>(native); +#else + WebRTCLibDataChannel *out = memnew(WebRTCLibDataChannel); +#endif + // Bind the library data channel to our object. + out->bind_channel(p_channel, p_negotiated); + return out; } -void WebRTCLibDataChannel::bind_channel(rtc::scoped_refptr<webrtc::DataChannelInterface> p_channel) { - ERR_FAIL_COND(p_channel.get() == nullptr); +void WebRTCLibDataChannel::bind_channel(std::shared_ptr<rtc::DataChannel> p_channel, bool p_negotiated) { + ERR_FAIL_COND(!p_channel); channel = p_channel; - label = p_channel->label(); - protocol = p_channel->protocol(); - channel->RegisterObserver(&observer); -} - -void WebRTCLibDataChannel::queue_packet(const uint8_t *data, uint32_t size) { + negotiated = p_negotiated; + + // Binding this should be fine as long as we call close when going out of scope. + p_channel->onMessage([this](auto message) { + if (std::holds_alternative<rtc::string>(message)) { + rtc::string str = std::get<rtc::string>(message); + queue_packet(reinterpret_cast<const uint8_t *>(str.c_str()), str.size(), true); + } else if (std::holds_alternative<rtc::binary>(message)) { + rtc::binary bin = std::get<rtc::binary>(message); + queue_packet(reinterpret_cast<const uint8_t *>(&bin[0]), bin.size(), false); + } else { + ERR_PRINT("Message parsing bug. Unknown message type."); + } + }); + p_channel->onOpen([this]() { + channel_state = STATE_OPEN; + }); + p_channel->onClosed([this]() { + channel_state = STATE_CLOSED; + }); + p_channel->onError([](auto error) { + ERR_PRINT("Channel Error: " + String(std::string(error).c_str())); + }); +} + +void WebRTCLibDataChannel::queue_packet(const uint8_t *data, uint32_t size, bool p_is_string) { mutex->lock(); - godot::PoolByteArray packet; + std::vector<uint8_t> packet; packet.resize(size); - { - godot::PoolByteArray::Write w = packet.write(); - memcpy(w.ptr(), data, size); - } - packet_queue.push(packet); + memcpy(&packet[0], data, size); + packet_queue.push(QueuedPacket(packet, p_is_string)); mutex->unlock(); } -void WebRTCLibDataChannel::set_write_mode(godot_int mode) { +void WebRTCLibDataChannel::_set_write_mode(int64_t p_mode) { + ERR_FAIL_COND(p_mode != WRITE_MODE_TEXT && p_mode != WRITE_MODE_BINARY); + write_mode = (WriteMode)p_mode; } -godot_int WebRTCLibDataChannel::get_write_mode() const { - return 0; +int64_t WebRTCLibDataChannel::_get_write_mode() const { + return write_mode; } -bool WebRTCLibDataChannel::was_string_packet() const { - return false; +bool WebRTCLibDataChannel::_was_string_packet() const { + return current_packet.second; } -WebRTCLibDataChannel::ChannelState WebRTCLibDataChannel::get_ready_state() const { - ERR_FAIL_COND_V(channel.get() == nullptr, STATE_CLOSED); - return (ChannelState)channel->state(); +int64_t WebRTCLibDataChannel::_get_ready_state() const { + ERR_FAIL_COND_V(!channel, STATE_CLOSED); + return channel_state; } -const char *WebRTCLibDataChannel::get_label() const { - ERR_FAIL_COND_V(channel.get() == nullptr, ""); - return label.c_str(); +String WebRTCLibDataChannel::_get_label() const { + ERR_FAIL_COND_V(!channel, ""); + return channel->label().c_str(); } -bool WebRTCLibDataChannel::is_ordered() const { - ERR_FAIL_COND_V(channel.get() == nullptr, false); - return channel->ordered(); +bool WebRTCLibDataChannel::_is_ordered() const { + ERR_FAIL_COND_V(!channel, false); + return channel->reliability().unordered == false; } -int WebRTCLibDataChannel::get_id() const { - ERR_FAIL_COND_V(channel.get() == nullptr, -1); - return channel->id(); +int64_t WebRTCLibDataChannel::_get_id() const { + ERR_FAIL_COND_V(!channel, -1); + return channel->id().value_or(-1); } -int WebRTCLibDataChannel::get_max_packet_life_time() const { - ERR_FAIL_COND_V(channel.get() == nullptr, 0); - return channel->maxRetransmitTime(); +int64_t WebRTCLibDataChannel::_get_max_packet_life_time() const { + ERR_FAIL_COND_V(!channel, 0); + return channel->reliability().type == rtc::Reliability::Type::Timed ? std::get<std::chrono::milliseconds>(channel->reliability().rexmit).count() : -1; } -int WebRTCLibDataChannel::get_max_retransmits() const { - ERR_FAIL_COND_V(channel.get() == nullptr, 0); - return channel->maxRetransmits(); +int64_t WebRTCLibDataChannel::_get_max_retransmits() const { + ERR_FAIL_COND_V(!channel, 0); + return channel->reliability().type == rtc::Reliability::Type::Rexmit ? std::get<int>(channel->reliability().rexmit) : -1; } -const char *WebRTCLibDataChannel::get_protocol() const { - ERR_FAIL_COND_V(channel.get() == nullptr, ""); - return protocol.c_str(); +String WebRTCLibDataChannel::_get_protocol() const { + ERR_FAIL_COND_V(!channel, ""); + return channel->protocol().c_str(); } -bool WebRTCLibDataChannel::is_negotiated() const { - ERR_FAIL_COND_V(channel.get() == nullptr, false); - return channel->negotiated(); +bool WebRTCLibDataChannel::_is_negotiated() const { + ERR_FAIL_COND_V(!channel, false); + return negotiated; } -int WebRTCLibDataChannel::get_buffered_amount() const { - ERR_FAIL_COND_V(channel.get() == nullptr, 0); - return channel->buffered_amount(); +int64_t WebRTCLibDataChannel::_get_buffered_amount() const { + ERR_FAIL_COND_V(!channel, 0); + return channel->bufferedAmount(); } -godot_error WebRTCLibDataChannel::poll() { - return GODOT_OK; +int64_t WebRTCLibDataChannel::_poll() { + return OK; } -void WebRTCLibDataChannel::close() { - if (channel.get() != nullptr) { - channel->Close(); - channel->UnregisterObserver(); +void WebRTCLibDataChannel::_close() try { + if (channel) { + channel->close(); } +} catch (...) { } -godot_error WebRTCLibDataChannel::get_packet(const uint8_t **r_buffer, int *r_len) { - ERR_FAIL_COND_V(packet_queue.empty(), GODOT_ERR_UNAVAILABLE); +int64_t WebRTCLibDataChannel::_get_packet(const uint8_t **r_buffer, int32_t *r_len) { + ERR_FAIL_COND_V(packet_queue.empty(), ERR_UNAVAILABLE); mutex->lock(); @@ -169,47 +186,46 @@ godot_error WebRTCLibDataChannel::get_packet(const uint8_t **r_buffer, int *r_le current_packet = packet_queue.front(); packet_queue.pop(); // Set out buffer and size (buffer will be gone at next get_packet or close) - *r_buffer = current_packet.read().ptr(); - *r_len = current_packet.size(); + *r_buffer = ¤t_packet.first[0]; + *r_len = current_packet.first.size(); mutex->unlock(); - return GODOT_OK; + return 0; } -godot_error WebRTCLibDataChannel::put_packet(const uint8_t *p_buffer, int p_len) { - ERR_FAIL_COND_V(channel.get() == nullptr, GODOT_ERR_UNAVAILABLE); - - webrtc::DataBuffer webrtc_buffer(rtc::CopyOnWriteBuffer(p_buffer, p_len), true); - ERR_FAIL_COND_V(!channel->Send(webrtc_buffer), GODOT_FAILED); - - return GODOT_OK; +int64_t WebRTCLibDataChannel::_put_packet(const uint8_t *p_buffer, int64_t p_len) try { + ERR_FAIL_COND_V(!channel, FAILED); + ERR_FAIL_COND_V(channel->isClosed(), FAILED); + if (write_mode == WRITE_MODE_TEXT) { + std::string str(p_len, '\x00'); + std::strncpy(str.data(), (const char *)p_buffer, p_len); + channel->send(str); + } else if (write_mode == WRITE_MODE_BINARY) { + channel->send(reinterpret_cast<const std::byte *>(p_buffer), p_len); + } else { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } + return OK; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(FAILED); } -godot_int WebRTCLibDataChannel::get_available_packet_count() const { +int64_t WebRTCLibDataChannel::_get_available_packet_count() const { return packet_queue.size(); } -godot_int WebRTCLibDataChannel::get_max_packet_size() const { - return 1200; -} - -void WebRTCLibDataChannel::_register_methods() { +int64_t WebRTCLibDataChannel::_get_max_packet_size() const { + return 16384; // See RFC-8831 section 6.6: https://datatracker.ietf.org/doc/rfc8831/ } -void WebRTCLibDataChannel::_init() { - register_interface(&interface); -} - -WebRTCLibDataChannel::WebRTCLibDataChannel() : - observer(this) { +WebRTCLibDataChannel::WebRTCLibDataChannel() { mutex = new std::mutex; } WebRTCLibDataChannel::~WebRTCLibDataChannel() { - close(); - if (_owner) { - register_interface(NULL); - } + _close(); + channel = nullptr; delete mutex; } diff --git a/src/WebRTCLibDataChannel.hpp b/src/WebRTCLibDataChannel.hpp index 63c7fdd..b79eeaa 100644 --- a/src/WebRTCLibDataChannel.hpp +++ b/src/WebRTCLibDataChannel.hpp @@ -31,72 +31,72 @@ #ifndef WEBRTC_DATA_CHANNEL_H #define WEBRTC_DATA_CHANNEL_H +#ifdef GDNATIVE_WEBRTC #include <Godot.hpp> // Godot.hpp must go first, or windows builds breaks -#include "api/peer_connection_interface.h" // interface for all things needed from WebRTC -#include "media/base/media_engine.h" // needed for CreateModularPeerConnectionFactory - -#include "PoolArrays.hpp" #include "net/WebRTCDataChannelNative.hpp" +#define WebRTCDataChannelExtension WebRTCDataChannelNative +#if !defined(GDCLASS) +#define GDCLASS(arg1, arg2) GODOT_CLASS(arg1, arg2) +#endif +#else +#include <godot_cpp/classes/web_rtc_data_channel_extension.hpp> +#endif + #include <mutex> +#include <queue> +#include <utility> + +#include "rtc/rtc.hpp" namespace godot_webrtc { -class WebRTCLibDataChannel : public WebRTCDataChannelNative { - GODOT_CLASS(WebRTCLibDataChannel, WebRTCDataChannelNative); +class WebRTCLibDataChannel : public godot::WebRTCDataChannelExtension { + GDCLASS(WebRTCLibDataChannel, WebRTCDataChannelExtension); private: - class ChannelObserver : public webrtc::DataChannelObserver { - public: - WebRTCLibDataChannel *parent; + using QueuedPacket = std::pair<std::vector<uint8_t>, bool>; + std::mutex *mutex; + std::queue<QueuedPacket> packet_queue; + QueuedPacket current_packet; + std::shared_ptr<rtc::DataChannel> channel = nullptr; - ChannelObserver(WebRTCLibDataChannel *parent); - void OnMessage(const webrtc::DataBuffer &buffer) override; - void OnStateChange() override; // UNUSED - void OnBufferedAmountChange(uint64_t previous_amount) override; // UNUSED - }; + WriteMode write_mode = WRITE_MODE_TEXT; + ChannelState channel_state = STATE_CONNECTING; + bool negotiated = false; - ChannelObserver observer; - rtc::scoped_refptr<webrtc::DataChannelInterface> channel; + void queue_packet(const uint8_t *data, uint32_t size, bool p_is_string); + void bind_channel(std::shared_ptr<rtc::DataChannel> p_channel, bool p_negotiated); - std::mutex *mutex; - std::queue<godot::PoolByteArray> packet_queue; - godot::PoolByteArray current_packet; - std::string label; - std::string protocol; +protected: + static void _bind_methods() {} public: - static WebRTCLibDataChannel *new_data_channel(rtc::scoped_refptr<webrtc::DataChannelInterface> p_channel); - static void _register_methods(); + static WebRTCLibDataChannel *new_data_channel(std::shared_ptr<rtc::DataChannel> p_channel, bool p_negotiated); - void _init(); - - void bind_channel(rtc::scoped_refptr<webrtc::DataChannelInterface> p_channel); - void queue_packet(const uint8_t *data, uint32_t size); + /* PacketPeer */ + virtual int64_t _get_packet(const uint8_t **r_buffer, int32_t *r_len) override; + virtual int64_t _put_packet(const uint8_t *p_buffer, int64_t p_len) override; + virtual int64_t _get_available_packet_count() const override; + virtual int64_t _get_max_packet_size() const override; /* WebRTCDataChannel */ - void set_write_mode(godot_int mode); - godot_int get_write_mode() const; - bool was_string_packet() const; - - ChannelState get_ready_state() const; - const char *get_label() const; - bool is_ordered() const; - int get_id() const; - int get_max_packet_life_time() const; - int get_max_retransmits() const; - const char *get_protocol() const; - bool is_negotiated() const; - int get_buffered_amount() const; - - godot_error poll(); - void close(); - - /* PacketPeer */ - virtual godot_error get_packet(const uint8_t **r_buffer, int *r_len); - virtual godot_error put_packet(const uint8_t *p_buffer, int p_len); - virtual godot_int get_available_packet_count() const; - virtual godot_int get_max_packet_size() const; + int64_t _poll() override; + void _close() override; + + void _set_write_mode(int64_t p_mode) override; + int64_t _get_write_mode() const override; + bool _was_string_packet() const override; + + int64_t _get_ready_state() const override; + godot::String _get_label() const override; + bool _is_ordered() const override; + int64_t _get_id() const override; + int64_t _get_max_packet_life_time() const override; + int64_t _get_max_retransmits() const override; + godot::String _get_protocol() const override; + bool _is_negotiated() const override; + int64_t _get_buffered_amount() const override; WebRTCLibDataChannel(); ~WebRTCLibDataChannel(); diff --git a/src/WebRTCLibPeerConnection.cpp b/src/WebRTCLibPeerConnection.cpp index d090654..cda018a 100644 --- a/src/WebRTCLibPeerConnection.cpp +++ b/src/WebRTCLibPeerConnection.cpp @@ -29,214 +29,201 @@ /*************************************************************************/ #include "WebRTCLibPeerConnection.hpp" -#include "WebRTCDataChannel.hpp" -#include "WebRTCDataChannelGDNative.hpp" #include "WebRTCLibDataChannel.hpp" +using namespace godot; using namespace godot_webrtc; -std::unique_ptr<rtc::Thread> WebRTCLibPeerConnection::signaling_thread = nullptr; - -// PeerConnectionObserver -void WebRTCLibPeerConnection::GodotPCO::OnIceCandidate(const webrtc::IceCandidateInterface *candidate) { - godot::Dictionary candidateSDP; - godot::String candidateSdpMidName = candidate->sdp_mid().c_str(); - int candidateSdpMlineIndexName = candidate->sdp_mline_index(); - std::string sdp; - candidate->ToString(&sdp); - godot::String candidateSdpName = sdp.c_str(); - parent->queue_signal("ice_candidate_created", 3, candidateSdpMidName, candidateSdpMlineIndexName, candidateSdpName); -} +#ifdef GDNATIVE_WEBRTC +struct CastableError { + godot::Error err_enum; + int64_t err_int; -// SetSessionDescriptionObserver -void WebRTCLibPeerConnection::GodotSSDO::OnSuccess() { - if (make_offer) { - make_offer = false; - parent->peer_connection->CreateAnswer(parent->ptr_csdo, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); + operator int64_t() { return err_int; } + operator godot::Error() { return err_enum; } + CastableError(godot::Error p_enum, int64_t p_int) { + err_enum = p_enum; + err_int = p_int; } -} - -// CreateSessionDescriptionObserver -void WebRTCLibPeerConnection::GodotCSDO::OnSuccess(webrtc::SessionDescriptionInterface *desc) { - // serialize this offer and send it to the remote peer: - std::string sdp; - desc->ToString(&sdp); - parent->queue_signal("session_description_created", 2, desc->type().c_str(), sdp.c_str()); -} +}; +#define MKERR(m_err) CastableError(godot::Error::m_err, GODOT_##m_err) +#define OK MKERR(OK) +#define FAILED MKERR(FAILED) +#define ERR_UNCONFIGURED MKERR(ERR_UNCONFIGURED) +#define ERR_UNAVAILABLE MKERR(ERR_UNAVAILABLE) +#define ERR_INVALID_PARAMETER MKERR(ERR_INVALID_PARAMETER) +#define ERR_BUG MKERR(ERR_BUG) +#endif void WebRTCLibPeerConnection::initialize_signaling() { - if (signaling_thread.get() == nullptr) { - signaling_thread = rtc::Thread::Create(); - } - signaling_thread->Start(); +#ifdef DEBUG_ENABLED + rtc::InitLogger(rtc::LogLevel::Debug); +#endif } void WebRTCLibPeerConnection::deinitialize_signaling() { - if (signaling_thread.get() != nullptr) { - signaling_thread->Stop(); - } } -godot_error _parse_ice_server(webrtc::PeerConnectionInterface::RTCConfiguration &r_config, godot::Dictionary p_server) { - godot::Variant v; - webrtc::PeerConnectionInterface::IceServer ice_server; - godot::String url; - - ERR_FAIL_COND_V(!p_server.has("urls"), GODOT_ERR_INVALID_PARAMETER); +Error WebRTCLibPeerConnection::_parse_ice_server(rtc::Configuration &r_config, Dictionary p_server) { + ERR_FAIL_COND_V(!p_server.has("urls"), ERR_INVALID_PARAMETER); // Parse mandatory URL - v = p_server["urls"]; - if (v.get_type() == godot::Variant::STRING) { - url = v; - ice_server.urls.push_back(url.utf8().get_data()); - } else if (v.get_type() == godot::Variant::ARRAY) { - godot::Array names = v; - for (int j = 0; j < names.size(); j++) { - v = names[j]; - ERR_FAIL_COND_V(v.get_type() != godot::Variant::STRING, GODOT_ERR_INVALID_PARAMETER); - url = v; - ice_server.urls.push_back(url.utf8().get_data()); - } + Array urls; + Variant urls_var = p_server["urls"]; + if (urls_var.get_type() == Variant::STRING) { + urls.push_back(urls_var); + } else if (urls_var.get_type() == Variant::ARRAY) { + urls = urls_var; } else { - ERR_FAIL_V(GODOT_ERR_INVALID_PARAMETER); + ERR_FAIL_V(ERR_INVALID_PARAMETER); } // Parse credentials (only meaningful for TURN, only support password) - if (p_server.has("username") && (v = p_server["username"]) && v.get_type() == godot::Variant::STRING) { - ice_server.username = (v.operator godot::String()).utf8().get_data(); + String username; + String credential; + if (p_server.has("username") && p_server["username"].get_type() == Variant::STRING) { + username = p_server["username"]; } - if (p_server.has("credential") && (v = p_server["credential"]) && v.get_type() == godot::Variant::STRING) { - ice_server.password = (v.operator godot::String()).utf8().get_data(); + if (p_server.has("credential") && p_server["credential"].get_type() == Variant::STRING) { + credential = p_server["credential"]; } - - r_config.servers.push_back(ice_server); - return GODOT_OK; + for (int i = 0; i < urls.size(); i++) { + rtc::IceServer srv(urls[i].operator String().utf8().get_data()); + srv.username = username.utf8().get_data(); + srv.password = credential.utf8().get_data(); + r_config.iceServers.push_back(srv); + } + return OK; } -godot_error _parse_channel_config(webrtc::DataChannelInit &r_config, godot::Dictionary p_dict) { - godot::Variant v; -#define _SET_N(PROP, PNAME, TYPE) \ - if (p_dict.has(#PROP)) { \ - v = p_dict[#PROP]; \ - if (v.get_type() == godot::Variant::TYPE) \ - r_config.PNAME = v; \ +Error WebRTCLibPeerConnection::_parse_channel_config(rtc::DataChannelInit &r_config, const Dictionary &p_dict) { + Variant nil; + Variant v; + if (p_dict.has("negotiated")) { + r_config.negotiated = p_dict["negotiated"].operator bool(); } -#define _SET(PROP, TYPE) _SET_N(PROP, PROP, TYPE) - _SET(negotiated, BOOL); - _SET(id, INT); - _SET_N(maxPacketLifeTime, maxRetransmitTime, INT); - _SET(maxRetransmits, INT); - _SET(ordered, BOOL); -#undef _SET - if (p_dict.has("protocol") && (v = p_dict["protocol"]) && v.get_type() == godot::Variant::STRING) { - r_config.protocol = v.operator godot::String().utf8().get_data(); + if (p_dict.has("id")) { + r_config.id = uint16_t(p_dict["id"].operator int32_t()); } - - // ID makes sense only when negotiated is true (and must be set in that case) - ERR_FAIL_COND_V(r_config.negotiated ? r_config.id == -1 : r_config.id != -1, GODOT_ERR_INVALID_PARAMETER); - // Only one of maxRetransmits and maxRetransmitTime can be set on a channel. - ERR_FAIL_COND_V(r_config.maxRetransmits && r_config.maxRetransmitTime, GODOT_ERR_INVALID_PARAMETER); - return GODOT_OK; + // If negotiated it must have an ID, and ID only makes sense when negotiated. + ERR_FAIL_COND_V(r_config.negotiated != r_config.id.has_value(), ERR_INVALID_PARAMETER); + // Channels cannot be both time-constrained and retry-constrained. + ERR_FAIL_COND_V(p_dict.has("maxPacketLifeTime") && p_dict.has("maxRetransmits"), ERR_INVALID_PARAMETER); + if (p_dict.has("maxPacketLifeTime")) { + r_config.reliability.type = rtc::Reliability::Type::Timed; + r_config.reliability.rexmit = std::chrono::milliseconds(p_dict["maxPacketLifeTime"].operator int32_t()); + } else if (p_dict.has("maxRetransmits")) { + r_config.reliability.type = rtc::Reliability::Type::Rexmit; + r_config.reliability.rexmit = p_dict["maxRetransmits"].operator int32_t(); + } + if (p_dict.has("ordered") && p_dict["ordered"].operator bool() == false) { + r_config.reliability.unordered = true; + } + if (p_dict.has("protocol")) { + r_config.protocol = p_dict["protocol"].operator String().utf8().get_data(); + } + return OK; } -WebRTCLibPeerConnection::ConnectionState WebRTCLibPeerConnection::get_connection_state() const { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, STATE_CLOSED); +int64_t WebRTCLibPeerConnection::_get_connection_state() const { + ERR_FAIL_COND_V(peer_connection == nullptr, STATE_CLOSED); - webrtc::PeerConnectionInterface::IceConnectionState state = peer_connection->ice_connection_state(); + rtc::PeerConnection::State state = peer_connection->state(); switch (state) { - case webrtc::PeerConnectionInterface::kIceConnectionNew: + case rtc::PeerConnection::State::New: return STATE_NEW; - case webrtc::PeerConnectionInterface::kIceConnectionChecking: + case rtc::PeerConnection::State::Connecting: return STATE_CONNECTING; - case webrtc::PeerConnectionInterface::kIceConnectionConnected: + case rtc::PeerConnection::State::Connected: return STATE_CONNECTED; - case webrtc::PeerConnectionInterface::kIceConnectionCompleted: - return STATE_CONNECTED; - case webrtc::PeerConnectionInterface::kIceConnectionFailed: - return STATE_FAILED; - case webrtc::PeerConnectionInterface::kIceConnectionDisconnected: + case rtc::PeerConnection::State::Disconnected: return STATE_DISCONNECTED; - case webrtc::PeerConnectionInterface::kIceConnectionClosed: - return STATE_CLOSED; + case rtc::PeerConnection::State::Failed: + return STATE_FAILED; default: return STATE_CLOSED; } } -godot_error WebRTCLibPeerConnection::initialize(const godot_dictionary *p_config) { - webrtc::PeerConnectionInterface::RTCConfiguration config; - godot::Dictionary d = *(godot::Dictionary *)p_config; - godot::Variant v; - if (d.has("iceServers") && (v = d["iceServers"]) && v.get_type() == godot::Variant::ARRAY) { - godot::Array servers = v; +int64_t WebRTCLibPeerConnection::_initialize(const Dictionary &p_config) { + rtc::Configuration config = {}; + if (p_config.has("iceServers") && p_config["iceServers"].get_type() == Variant::ARRAY) { + Array servers = p_config["iceServers"]; for (int i = 0; i < servers.size(); i++) { - v = servers[i]; - ERR_FAIL_COND_V(v.get_type() != godot::Variant::DICTIONARY, GODOT_ERR_INVALID_PARAMETER); - godot_error err; - godot::Dictionary server = v; - err = _parse_ice_server(config, server); - ERR_FAIL_COND_V(err != GODOT_OK, err); + ERR_FAIL_COND_V(servers[i].get_type() != Variant::DICTIONARY, ERR_INVALID_PARAMETER); + Dictionary server = servers[i]; + Error err = _parse_ice_server(config, server); + ERR_FAIL_COND_V(err != OK, FAILED); } } - return _create_pc(config); + return (int64_t)_create_pc(config); } -godot_object *WebRTCLibPeerConnection::create_data_channel(const char *p_channel, const godot_dictionary *p_channel_config) { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, NULL); +Object *WebRTCLibPeerConnection::_create_data_channel(const String &p_channel, const Dictionary &p_channel_config) try { + ERR_FAIL_COND_V(!peer_connection, nullptr); // Read config from dictionary - webrtc::DataChannelInit config; - godot::Dictionary d = *(godot::Dictionary *)p_channel_config; - godot_error err = _parse_channel_config(config, d); - ERR_FAIL_COND_V(err != GODOT_OK, NULL); - - WebRTCLibDataChannel *wrapper = WebRTCLibDataChannel::new_data_channel(peer_connection->CreateDataChannel(p_channel, &config)); - ERR_FAIL_COND_V(wrapper == NULL, NULL); - return wrapper->_owner; + rtc::DataChannelInit config; + + Error err = _parse_channel_config(config, p_channel_config); + ERR_FAIL_COND_V(err != OK, nullptr); + + std::shared_ptr<rtc::DataChannel> ch = peer_connection->createDataChannel(p_channel.utf8().get_data(), config); + ERR_FAIL_COND_V(ch == nullptr, nullptr); + + WebRTCLibDataChannel *wrapper = WebRTCLibDataChannel::new_data_channel(ch, ch->id().has_value()); + ERR_FAIL_COND_V(wrapper == nullptr, nullptr); + return wrapper; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(nullptr); } -godot_error WebRTCLibPeerConnection::create_offer() { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED); - peer_connection->CreateOffer(ptr_csdo, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions()); - return GODOT_OK; +int64_t WebRTCLibPeerConnection::_create_offer() try { + ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED); + ERR_FAIL_COND_V(_get_connection_state() != STATE_NEW, FAILED); + peer_connection->setLocalDescription(rtc::Description::Type::Offer); + return OK; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(FAILED); } -#define _MAKE_DESC(TYPE, SDP) webrtc::CreateSessionDescription((godot::String(TYPE) == godot::String("offer") ? webrtc::SdpType::kOffer : webrtc::SdpType::kAnswer), SDP) -godot_error WebRTCLibPeerConnection::set_remote_description(const char *type, const char *sdp) { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED); - std::unique_ptr<webrtc::SessionDescriptionInterface> desc = _MAKE_DESC(type, sdp); - if (desc->GetType() == webrtc::SdpType::kOffer) { - ptr_ssdo->make_offer = true; +int64_t WebRTCLibPeerConnection::_set_remote_description(const String &p_type, const String &p_sdp) try { + ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED); + std::string sdp(p_sdp.utf8().get_data()); + std::string type(p_type.utf8().get_data()); + rtc::Description desc(sdp, type); + peer_connection->setRemoteDescription(desc); + // Automatically create the answer. + if (p_type == String("offer")) { + peer_connection->setLocalDescription(rtc::Description::Type::Answer); } - peer_connection->SetRemoteDescription(ptr_ssdo, desc.release()); - return GODOT_OK; + return OK; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(FAILED); } -godot_error WebRTCLibPeerConnection::set_local_description(const char *type, const char *sdp) { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED); - std::unique_ptr<webrtc::SessionDescriptionInterface> desc = _MAKE_DESC(type, sdp); - peer_connection->SetLocalDescription(ptr_ssdo, desc.release()); - return GODOT_OK; +int64_t WebRTCLibPeerConnection::_set_local_description(const String &p_type, const String &p_sdp) { + ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED); + // XXX Library quirk. It doesn't seem possible to create offers/answers without setting the local description. + // Ignore this call for now to avoid crash (it's already set automatically!). + //peer_connection->setLocalDescription(p_type == String("offer") ? rtc::Description::Type::Offer : rtc::Description::Type::Answer); + return OK; } -#undef _MAKE_DESC - -godot_error WebRTCLibPeerConnection::add_ice_candidate(const char *sdpMidName, int sdpMlineIndexName, const char *sdpName) { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED); - - webrtc::SdpParseError *error = nullptr; - webrtc::IceCandidateInterface *candidate = webrtc::CreateIceCandidate( - sdpMidName, - sdpMlineIndexName, - sdpName, - error); - - ERR_FAIL_COND_V(error || !candidate, GODOT_ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!peer_connection->AddIceCandidate(candidate), GODOT_FAILED); - return GODOT_OK; +int64_t WebRTCLibPeerConnection::_add_ice_candidate(const String &sdpMidName, int64_t sdpMlineIndexName, const String &sdpName) try { + ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED); + rtc::Candidate candidate(sdpName.utf8().get_data(), sdpMidName.utf8().get_data()); + peer_connection->addRemoteCandidate(candidate); + return OK; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(FAILED); } -godot_error WebRTCLibPeerConnection::poll() { - ERR_FAIL_COND_V(peer_connection.get() == nullptr, GODOT_ERR_UNCONFIGURED); +int64_t WebRTCLibPeerConnection::_poll() { + ERR_FAIL_COND_V(!peer_connection, ERR_UNCONFIGURED); while (!signal_queue.empty()) { mutex_signal_queue->lock(); @@ -245,68 +232,83 @@ godot_error WebRTCLibPeerConnection::poll() { mutex_signal_queue->unlock(); signal.emit(this); } - return GODOT_OK; + return OK; } -void WebRTCLibPeerConnection::close() { - if (peer_connection.get() != nullptr) { - peer_connection->Close(); +void WebRTCLibPeerConnection::_close() { + if (peer_connection != nullptr) { + try { + peer_connection->close(); + } catch (...) { + } } - peer_connection = nullptr; + while (!signal_queue.empty()) { signal_queue.pop(); } } -void WebRTCLibPeerConnection::_register_methods() { -} - void WebRTCLibPeerConnection::_init() { +#ifdef GDNATIVE_WEBRTC register_interface(&interface); - - // initialize variables: +#endif mutex_signal_queue = new std::mutex; - // create a PeerConnectionFactoryInterface: - webrtc::PeerConnectionFactoryDependencies deps; - - ERR_FAIL_COND(signaling_thread.get() == nullptr); - deps.signaling_thread = signaling_thread.get(); - pc_factory = webrtc::CreateModularPeerConnectionFactory(std::move(deps)); - - // Create peer connection with default configuration. - webrtc::PeerConnectionInterface::RTCConfiguration config; - _create_pc(config); + _initialize(Dictionary()); } -godot_error WebRTCLibPeerConnection::_create_pc(webrtc::PeerConnectionInterface::RTCConfiguration &config) { - ERR_FAIL_COND_V(pc_factory.get() == nullptr, GODOT_ERR_BUG); - peer_connection = nullptr; - peer_connection = pc_factory->CreatePeerConnection(config, nullptr, nullptr, &pco); - if (peer_connection.get() == nullptr) { // PeerConnection couldn't be created. Fail the method call. - ERR_PRINT("PeerConnection could not be created"); - return GODOT_FAILED; - } - return GODOT_OK; +Error WebRTCLibPeerConnection::_create_pc(rtc::Configuration &r_config) try { + // Prevents libdatachannel from automatically creating offers. + r_config.disableAutoNegotiation = true; + + peer_connection = std::make_shared<rtc::PeerConnection>(r_config); + ERR_FAIL_COND_V(!peer_connection, FAILED); + + // Binding this should be fine as long as we call close when going out of scope. + peer_connection->onLocalDescription([this](rtc::Description description) { + String type = description.type() == rtc::Description::Type::Offer ? "offer" : "answer"; + queue_signal("session_description_created", 2, type, String(std::string(description).c_str())); + }); + peer_connection->onLocalCandidate([this](rtc::Candidate candidate) { + queue_signal("ice_candidate_created", 3, String(candidate.mid().c_str()), 0, String(candidate.candidate().c_str())); + }); + peer_connection->onDataChannel([this](std::shared_ptr<rtc::DataChannel> channel) { + queue_signal("data_channel_received", 1, WebRTCLibDataChannel::new_data_channel(channel, false)); + }); + /* + peer_connection->onStateChange([](rtc::PeerConnection::State state) { + std::cout << "[State: " << state << "]" << std::endl; + }); + + peer_connection->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) { + std::cout << "[Gathering State: " << state << "]" << std::endl; + }); + */ + return OK; +} catch (const std::exception &e) { + ERR_PRINT(e.what()); + ERR_FAIL_V(FAILED); } -WebRTCLibPeerConnection::WebRTCLibPeerConnection() : - pco(this), - ptr_csdo(new rtc::RefCountedObject<GodotCSDO>(this)), - ptr_ssdo(new rtc::RefCountedObject<GodotSSDO>(this)) { +WebRTCLibPeerConnection::WebRTCLibPeerConnection() { +#ifndef GDNATIVE_WEBRTC + _init(); +#endif } WebRTCLibPeerConnection::~WebRTCLibPeerConnection() { +#ifdef GDNATIVE_WEBRTC if (_owner) { - register_interface(NULL); + register_interface(nullptr); } - close(); +#endif + _close(); delete mutex_signal_queue; } -void WebRTCLibPeerConnection::queue_signal(godot::String p_name, int p_argc, const godot::Variant &p_arg1, const godot::Variant &p_arg2, const godot::Variant &p_arg3) { +void WebRTCLibPeerConnection::queue_signal(String p_name, int p_argc, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3) { mutex_signal_queue->lock(); - const godot::Variant argv[3] = { p_arg1, p_arg2, p_arg3 }; + const Variant argv[3] = { p_arg1, p_arg2, p_arg3 }; signal_queue.push(Signal(p_name, p_argc, argv)); mutex_signal_queue->unlock(); } diff --git a/src/WebRTCLibPeerConnection.hpp b/src/WebRTCLibPeerConnection.hpp index a265bc1..1a759b2 100644 --- a/src/WebRTCLibPeerConnection.hpp +++ b/src/WebRTCLibPeerConnection.hpp @@ -31,99 +31,61 @@ #ifndef WEBRTC_PEER_H #define WEBRTC_PEER_H +#ifdef GDNATIVE_WEBRTC #include <Godot.hpp> // Godot.hpp must go first, or windows builds breaks -#include "api/peer_connection_interface.h" // interface for all things needed from WebRTC -#include "media/base/media_engine.h" // needed for CreateModularPeerConnectionFactory -#include <mutex> - #include "net/WebRTCPeerConnectionNative.hpp" +#define WebRTCPeerConnectionExtension WebRTCPeerConnectionNative +#if !defined(GDCLASS) +#define GDCLASS(arg1, arg2) GODOT_CLASS(arg1, arg2) +#endif +#else +#include <godot_cpp/classes/web_rtc_peer_connection_extension.hpp> +#endif + +#include "rtc/rtc.hpp" + +#include <mutex> +#include <queue> namespace godot_webrtc { -class WebRTCLibPeerConnection : public WebRTCPeerConnectionNative { - GODOT_CLASS(WebRTCLibPeerConnection, WebRTCPeerConnectionNative); +class WebRTCLibPeerConnection : public godot::WebRTCPeerConnectionExtension { + GDCLASS(WebRTCLibPeerConnection, WebRTCPeerConnectionExtension); private: - godot_error _create_pc(webrtc::PeerConnectionInterface::RTCConfiguration &config); + std::shared_ptr<rtc::PeerConnection> peer_connection = nullptr; + godot::Array candidates; + + godot::Error _create_pc(rtc::Configuration &r_config); + godot::Error _parse_ice_server(rtc::Configuration &r_config, godot::Dictionary p_server); + godot::Error _parse_channel_config(rtc::DataChannelInit &r_config, const godot::Dictionary &p_dict); - static std::unique_ptr<rtc::Thread> signaling_thread; +protected: + static void _bind_methods() {} public: - static void _register_methods(); + static void _register_methods() {} static void initialize_signaling(); static void deinitialize_signaling(); void _init(); - ConnectionState get_connection_state() const; + int64_t _get_connection_state() const override; - godot_error initialize(const godot_dictionary *p_config); - godot_object *create_data_channel(const char *p_channel, const godot_dictionary *p_channel_config); - godot_error create_offer(); - godot_error set_remote_description(const char *type, const char *sdp); - godot_error set_local_description(const char *type, const char *sdp); - godot_error add_ice_candidate(const char *sdpMidName, int sdpMlineIndexName, const char *sdpName); - godot_error poll(); - void close(); + int64_t _initialize(const godot::Dictionary &p_config) override; + godot::Object *_create_data_channel(const godot::String &p_channel, const godot::Dictionary &p_channel_config) override; + int64_t _create_offer() override; + int64_t _set_remote_description(const godot::String &type, const godot::String &sdp) override; + int64_t _set_local_description(const godot::String &type, const godot::String &sdp) override; + int64_t _add_ice_candidate(const godot::String &sdpMidName, int64_t sdpMlineIndexName, const godot::String &sdpName) override; + int64_t _poll() override; + void _close() override; WebRTCLibPeerConnection(); ~WebRTCLibPeerConnection(); - /* helper functions */ private: - void queue_signal(godot::String p_name, int p_argc, const godot::Variant &p_arg1 = godot::Variant(), const godot::Variant &p_arg2 = godot::Variant(), const godot::Variant &p_arg3 = godot::Variant()); - void queue_packet(uint8_t *, int); - - /** PeerConnectionObserver callback functions **/ - class GodotPCO : public webrtc::PeerConnectionObserver { - public: - WebRTCLibPeerConnection *parent; - - GodotPCO(WebRTCLibPeerConnection *p_parent) { - parent = p_parent; - } - void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override; - - void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override {} - void OnAddStream(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {} - void OnRemoveStream(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {} - void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override {} - void OnRenegotiationNeeded() override {} - void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {} - void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override {} - }; - - /** CreateSessionDescriptionObserver callback functions **/ - class GodotCSDO : public webrtc::CreateSessionDescriptionObserver { - public: - WebRTCLibPeerConnection *parent = nullptr; - - GodotCSDO(WebRTCLibPeerConnection *p_parent) { - parent = p_parent; - } - void OnSuccess(webrtc::SessionDescriptionInterface *desc) override; - void OnFailure(webrtc::RTCError error) override { - ERR_PRINT(godot::String(error.message())); - } - }; - - /** SetSessionDescriptionObserver callback functions **/ - class GodotSSDO : public webrtc::SetSessionDescriptionObserver { - public: - WebRTCLibPeerConnection *parent = nullptr; - bool make_offer = false; - - GodotSSDO(WebRTCLibPeerConnection *p_parent) { - parent = p_parent; - } - void OnSuccess() override; - void OnFailure(webrtc::RTCError error) override { - make_offer = false; - ERR_PRINT(godot::String(error.message())); - } - }; - class Signal { godot::String method; godot::Variant argv[3]; @@ -151,15 +113,10 @@ private: } }; - GodotPCO pco; - rtc::scoped_refptr<GodotSSDO> ptr_ssdo; - rtc::scoped_refptr<GodotCSDO> ptr_csdo; - std::mutex *mutex_signal_queue = nullptr; std::queue<Signal> signal_queue; - rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> pc_factory; - rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection; + void queue_signal(godot::String p_name, int p_argc, const godot::Variant &p_arg1 = godot::Variant(), const godot::Variant &p_arg2 = godot::Variant(), const godot::Variant &p_arg3 = godot::Variant()); }; } // namespace godot_webrtc diff --git a/src/init_gdextension.cpp b/src/init_gdextension.cpp new file mode 100644 index 0000000..c923881 --- /dev/null +++ b/src/init_gdextension.cpp @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* init_gdextension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include <godot/gdnative_interface.h> + +#include <godot_cpp/core/class_db.hpp> +#include <godot_cpp/core/defs.hpp> +#include <godot_cpp/godot.hpp> + +#include "WebRTCLibDataChannel.hpp" +#include "WebRTCLibPeerConnection.hpp" + +using namespace godot; +using namespace godot_webrtc; + +void register_webrtc_extension_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + WebRTCLibPeerConnection::initialize_signaling(); + godot::ClassDB::register_class<WebRTCLibDataChannel>(); + godot::ClassDB::register_class<WebRTCLibPeerConnection>(); + WebRTCPeerConnection::set_default_extension("WebRTCLibPeerConnection"); +} + +void unregister_webrtc_extension_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + WebRTCLibPeerConnection::deinitialize_signaling(); +} + +extern "C" { +GDNativeBool GDN_EXPORT webrtc_extension_init(const GDNativeInterface *p_interface, const GDNativeExtensionClassLibraryPtr p_library, GDNativeInitialization *r_initialization) { + GDExtensionBinding::InitObject init_obj(p_interface, p_library, r_initialization); + + init_obj.register_initializer(register_webrtc_extension_types); + init_obj.register_terminator(unregister_webrtc_extension_types); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); +} +} diff --git a/src/init.cpp b/src/init_gdnative.cpp index a6066c3..77669af 100644 --- a/src/init.cpp +++ b/src/init_gdnative.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* init.cpp */ +/* init_gdnative.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -78,7 +78,7 @@ godot_net_webrtc_library library = { }; extern "C" void GDN_EXPORT godot_gdnative_singleton() { - if (WebRTCPeerConnectionNative::_net_api) { + if (godot::WebRTCPeerConnectionNative::_net_api) { ERR_FAIL_COND(!godot::gdnlib); _singleton_lib = godot::gdnlib; ERR_FAIL_COND(!godot::api); @@ -92,7 +92,7 @@ extern "C" void GDN_EXPORT godot_gdnative_singleton() { _set_library_mb = godot::api->godot_method_bind_get_method("NativeScript", "set_library"); ERR_FAIL_COND(!_set_library_mb); // If registration is successful _singleton will be set to true - _singleton = WebRTCPeerConnectionNative::_net_api->godot_net_set_webrtc_library(&library) == GODOT_OK; + _singleton = godot::WebRTCPeerConnectionNative::_net_api->godot_net_set_webrtc_library(&library) == GODOT_OK; if (!_singleton) ERR_PRINT("Failed initializing webrtc singleton library"); } @@ -111,7 +111,7 @@ extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) { break; if (net_api->next->version.major == 3 && net_api->next->version.minor == 2) { - WebRTCPeerConnectionNative::_net_api = (const godot_gdnative_ext_net_3_2_api_struct *)net_api->next; + godot::WebRTCPeerConnectionNative::_net_api = (const godot_gdnative_ext_net_3_2_api_struct *)net_api->next; } } @@ -121,7 +121,7 @@ extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) { extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) { if (_singleton) { // If we are the active singleton, unregister - WebRTCPeerConnectionNative::_net_api->godot_net_set_webrtc_library(NULL); + godot::WebRTCPeerConnectionNative::_net_api->godot_net_set_webrtc_library(NULL); } godot_webrtc::WebRTCLibPeerConnection::deinitialize_signaling(); godot::Godot::gdnative_terminate(o); diff --git a/src/net/WebRTCDataChannelNative.cpp b/src/net/WebRTCDataChannelNative.cpp index 4e6b114..4639021 100644 --- a/src/net/WebRTCDataChannelNative.cpp +++ b/src/net/WebRTCDataChannelNative.cpp @@ -31,6 +31,8 @@ #include "WebRTCDataChannelNative.hpp" #include "net/WebRTCPeerConnectionNative.hpp" +using namespace godot; + void WebRTCDataChannelNative::register_interface(const godot_net_webrtc_data_channel *p_interface) { ERR_FAIL_COND(!WebRTCPeerConnectionNative::_net_api); WebRTCPeerConnectionNative::_net_api->godot_net_bind_webrtc_data_channel(_owner, p_interface); @@ -54,73 +56,73 @@ WebRTCDataChannelNative::~WebRTCDataChannelNative() { * and you could use void *user for any kind of state struct pointer you have. */ godot_error get_packet_wdc(void *user, const uint8_t **r_buffer, int *r_len) { - return ((WebRTCDataChannelNative *)user)->get_packet(r_buffer, r_len); + return (godot_error)(((WebRTCDataChannelNative *)user)->_get_packet(r_buffer, r_len)); } godot_error put_packet_wdc(void *user, const uint8_t *p_buffer, int p_len) { - return ((WebRTCDataChannelNative *)user)->put_packet(p_buffer, p_len); + return (godot_error)(((WebRTCDataChannelNative *)user)->_put_packet(p_buffer, p_len)); } godot_int get_available_packet_count_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_available_packet_count(); + return ((WebRTCDataChannelNative *)user)->_get_available_packet_count(); } godot_int get_max_packet_size_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_max_packet_size(); + return ((WebRTCDataChannelNative *)user)->_get_max_packet_size(); } void set_write_mode_wdc(void *user, godot_int write_mode) { - ((WebRTCDataChannelNative *)user)->set_write_mode(write_mode); + ((WebRTCDataChannelNative *)user)->_set_write_mode(write_mode); } godot_int get_write_mode_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_write_mode(); + return ((WebRTCDataChannelNative *)user)->_get_write_mode(); } bool was_string_packet_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->was_string_packet(); + return ((WebRTCDataChannelNative *)user)->_was_string_packet(); } godot_int get_ready_state_wdc(const void *user) { - return (godot_int)(((WebRTCDataChannelNative *)user)->get_ready_state()); + return (godot_int)(((WebRTCDataChannelNative *)user)->_get_ready_state()); } const char *get_label_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_label(); + return ((WebRTCDataChannelNative *)user)->_get_label().utf8().get_data(); } bool is_ordered_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->is_ordered(); + return ((WebRTCDataChannelNative *)user)->_is_ordered(); } int get_id_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_id(); + return ((WebRTCDataChannelNative *)user)->_get_id(); } int get_max_packet_life_time_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_max_packet_life_time(); + return ((WebRTCDataChannelNative *)user)->_get_max_packet_life_time(); } int get_max_retransmits_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_max_retransmits(); + return ((WebRTCDataChannelNative *)user)->_get_max_retransmits(); } const char *get_protocol_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_protocol(); + return ((WebRTCDataChannelNative *)user)->_get_protocol().utf8().get_data(); } bool is_negotiated_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->is_negotiated(); + return ((WebRTCDataChannelNative *)user)->_is_negotiated(); } int get_buffered_amount_wdc(const void *user) { - return ((WebRTCDataChannelNative *)user)->get_buffered_amount(); + return ((WebRTCDataChannelNative *)user)->_get_buffered_amount(); } godot_error poll_wdc(void *user) { - return ((WebRTCDataChannelNative *)user)->poll(); + return (godot_error)(((WebRTCDataChannelNative *)user)->_poll()); } void close_wdc(void *user) { - ((WebRTCDataChannelNative *)user)->close(); + ((WebRTCDataChannelNative *)user)->_close(); } diff --git a/src/net/WebRTCDataChannelNative.hpp b/src/net/WebRTCDataChannelNative.hpp index 20ef38e..a8ab697 100644 --- a/src/net/WebRTCDataChannelNative.hpp +++ b/src/net/WebRTCDataChannelNative.hpp @@ -69,6 +69,8 @@ typedef struct { } #endif +namespace godot { + class WebRTCDataChannelNative : public godot::WebRTCDataChannelGDNative { GODOT_CLASS(WebRTCDataChannelNative, godot::WebRTCDataChannelGDNative); @@ -110,30 +112,32 @@ public: void _init(); void register_interface(const godot_net_webrtc_data_channel *interface); - virtual void set_write_mode(godot_int mode) = 0; - virtual godot_int get_write_mode() const = 0; - virtual bool was_string_packet() const = 0; + virtual void _set_write_mode(int64_t mode) = 0; + virtual int64_t _get_write_mode() const = 0; + virtual bool _was_string_packet() const = 0; - virtual ChannelState get_ready_state() const = 0; - virtual const char *get_label() const = 0; - virtual bool is_ordered() const = 0; - virtual int get_id() const = 0; - virtual int get_max_packet_life_time() const = 0; - virtual int get_max_retransmits() const = 0; - virtual const char *get_protocol() const = 0; - virtual bool is_negotiated() const = 0; - virtual int get_buffered_amount() const = 0; + virtual int64_t _get_ready_state() const = 0; + virtual godot::String _get_label() const = 0; + virtual bool _is_ordered() const = 0; + virtual int64_t _get_id() const = 0; + virtual int64_t _get_max_packet_life_time() const = 0; + virtual int64_t _get_max_retransmits() const = 0; + virtual godot::String _get_protocol() const = 0; + virtual bool _is_negotiated() const = 0; + virtual int64_t _get_buffered_amount() const = 0; - virtual godot_error poll() = 0; - virtual void close() = 0; + virtual int64_t _poll() = 0; + virtual void _close() = 0; /* PacketPeer */ - virtual godot_error get_packet(const uint8_t **r_buffer, int *r_len) = 0; - virtual godot_error put_packet(const uint8_t *p_buffer, int p_len) = 0; - virtual godot_int get_available_packet_count() const = 0; - virtual godot_int get_max_packet_size() const = 0; + virtual int64_t _get_packet(const uint8_t **r_buffer, int32_t *r_len) = 0; + virtual int64_t _put_packet(const uint8_t *p_buffer, int64_t p_len) = 0; + virtual int64_t _get_available_packet_count() const = 0; + virtual int64_t _get_max_packet_size() const = 0; ~WebRTCDataChannelNative(); }; +}; // namespace godot + #endif // WEBRTC_DATA_CHANNEL_NATIVE diff --git a/src/net/WebRTCPeerConnectionNative.cpp b/src/net/WebRTCPeerConnectionNative.cpp index c8c7587..508004f 100644 --- a/src/net/WebRTCPeerConnectionNative.cpp +++ b/src/net/WebRTCPeerConnectionNative.cpp @@ -30,6 +30,8 @@ #include "WebRTCPeerConnectionNative.hpp" +using namespace godot; + const godot_gdnative_ext_net_3_2_api_struct *WebRTCPeerConnectionNative::_net_api = NULL; void WebRTCPeerConnectionNative::register_interface(const godot_net_webrtc_peer_connection *p_interface) { @@ -55,19 +57,23 @@ WebRTCPeerConnectionNative::~WebRTCPeerConnectionNative() { * and you could use void *user for any kind of state struct pointer you have. */ godot_int get_connection_state_wp(const void *user) { - return (godot_int)((WebRTCPeerConnectionNative *)user)->get_connection_state(); + return (godot_int)((WebRTCPeerConnectionNative *)user)->_get_connection_state(); } godot_error initialize_wp(void *user, const godot_dictionary *p_config) { - return ((WebRTCPeerConnectionNative *)user)->initialize(p_config); + return (godot_error)(((WebRTCPeerConnectionNative *)user)->_initialize(*(Dictionary *)p_config)); } godot_object *create_data_channel_wp(void *user, const char *p_channel, const godot_dictionary *p_channel_config) { - return ((WebRTCPeerConnectionNative *)user)->create_data_channel(p_channel, p_channel_config); + Object *ptr = ((WebRTCPeerConnectionNative *)user)->_create_data_channel(p_channel, *(Dictionary *)p_channel_config); + if (ptr) { + return ptr->_owner; + } + return nullptr; } godot_error create_offer_wp(void *user) { - return ((WebRTCPeerConnectionNative *)user)->create_offer(); + return (godot_error)(((WebRTCPeerConnectionNative *)user)->_create_offer()); } godot_error create_answer_wp(void *user) { @@ -75,21 +81,21 @@ godot_error create_answer_wp(void *user) { } godot_error set_remote_description_wp(void *user, const char *type, const char *sdp) { - return ((WebRTCPeerConnectionNative *)user)->set_remote_description(type, sdp); + return (godot_error)(((WebRTCPeerConnectionNative *)user)->_set_remote_description(type, sdp)); } godot_error set_local_description_wp(void *user, const char *type, const char *sdp) { - return ((WebRTCPeerConnectionNative *)user)->set_local_description(type, sdp); + return (godot_error)(((WebRTCPeerConnectionNative *)user)->_set_local_description(type, sdp)); } godot_error add_ice_candidate_wp(void *user, const char *sdpMidName, int sdpMlineIndexName, const char *sdpName) { - return ((WebRTCPeerConnectionNative *)user)->add_ice_candidate(sdpMidName, sdpMlineIndexName, sdpName); + return (godot_error)(((WebRTCPeerConnectionNative *)user)->_add_ice_candidate(sdpMidName, sdpMlineIndexName, sdpName)); } godot_error poll_wp(void *user) { - return ((WebRTCPeerConnectionNative *)user)->poll(); + return (godot_error)((WebRTCPeerConnectionNative *)user)->_poll(); } void close_wp(void *user) { - ((WebRTCPeerConnectionNative *)user)->close(); + ((WebRTCPeerConnectionNative *)user)->_close(); } diff --git a/src/net/WebRTCPeerConnectionNative.hpp b/src/net/WebRTCPeerConnectionNative.hpp index fb75a46..1613b53 100644 --- a/src/net/WebRTCPeerConnectionNative.hpp +++ b/src/net/WebRTCPeerConnectionNative.hpp @@ -50,6 +50,8 @@ godot_error add_ice_candidate_wp(void *, const char *, int, const char *); godot_error poll_wp(void *); void close_wp(void *); +namespace godot { + class WebRTCPeerConnectionNative : public godot::WebRTCPeerConnectionGDNative { GODOT_CLASS(WebRTCPeerConnectionNative, godot::WebRTCPeerConnectionGDNative); @@ -79,18 +81,20 @@ public: void _init(); void register_interface(const godot_net_webrtc_peer_connection *interface); - virtual ConnectionState get_connection_state() const = 0; + virtual int64_t _get_connection_state() const = 0; - virtual godot_error initialize(const godot_dictionary *p_config) = 0; - virtual godot_object *create_data_channel(const char *p_channel, const godot_dictionary *p_channel_config) = 0; - virtual godot_error create_offer() = 0; - virtual godot_error set_remote_description(const char *type, const char *sdp) = 0; - virtual godot_error set_local_description(const char *type, const char *sdp) = 0; - virtual godot_error add_ice_candidate(const char *sdpMidName, int sdpMlineIndexName, const char *sdpName) = 0; - virtual godot_error poll() = 0; - virtual void close() = 0; + virtual int64_t _initialize(const godot::Dictionary &p_config) = 0; + virtual godot::Object *_create_data_channel(const godot::String &p_channel, const godot::Dictionary &p_channel_config) = 0; + virtual int64_t _create_offer() = 0; + virtual int64_t _set_remote_description(const godot::String &type, const godot::String &sdp) = 0; + virtual int64_t _set_local_description(const godot::String &type, const godot::String &sdp) = 0; + virtual int64_t _add_ice_candidate(const godot::String &sdpMidName, int64_t sdpMlineIndexName, const godot::String &sdpName) = 0; + virtual int64_t _poll() = 0; + virtual void _close() = 0; ~WebRTCPeerConnectionNative(); }; +}; // namespace godot + #endif // WEBRTC_PEER_NATIVE |