pax_global_header00006660000000000000000000000064151762516310014521gustar00rootroot0000000000000052 comment=3a761a1cf3016e559a2df4ca6fbc9052e0695f50 liblsl-1.17.7/000077500000000000000000000000001517625163100130775ustar00rootroot00000000000000liblsl-1.17.7/.appveyor_disabled.yml000066400000000000000000000030201517625163100173670ustar00rootroot00000000000000version: 1.14.{build} pull_requests: do_not_increment_build_number: true shallow_clone: true environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1604 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 LSLARCH: Win32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 LSLARCH: x64 build_script: - cmd: cmake -DCMAKE_INSTALL_PREFIX=install DCMAKE_BUILD_TYPE=Release -Dlslgitrevision=%APPVEYOR_REPO_COMMIT% -Dlslgitbranch=%APPVEYOR_REPO_BRANCH% -S . -B build -T v140,host=x86 -A %LSLARCH% - sh: cmake -DCMAKE_INSTALL_PREFIX=install DCMAKE_BUILD_TYPE=Release -Dlslgitrevision=${APPVEYOR_REPO_COMMIT} -Dlslgitbranch=${APPVEYOR_REPO_BRANCH} -S . -B build - cmake -DLSL_UNITTESTS=ON -DLSL_BUILD_EXAMPLES=ON build - cmd: cmake --build build --config Release -j --target package - sh: /usr/bin/time -v cmake --build build -j 3 --target package - sh: cmake -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON build - cd build - sh: sudo dpkg -i *.deb - sh: cpack -G DEB - sh: ifconfig - cmd: testing\Release\lsl_test_internal.exe --wait-for-keypress never - cmd: set PATH=Release;%PATH% && testing\Release\lsl_test_exported.exe --wait-for-keypress never - sh: sudo route add -net 224.0.0.0 netmask 240.0.0.0 lo - sh: testing/lsl_test_internal --wait-for-keypress never - sh: testing/lsl_test_exported --wait-for-keypress never artifacts: - path: 'build/*.zip' - path: 'build/*.deb' deploy: provider: GitHub auth_token: secure: XzGnVTRjZI2AuQzR5A6qPgZViAAbBVq7/VhM7O8kyTmjIkvUjH1RrwWiFLuinsus force_update: true on: APPVEYOR_REPO_TAG: true liblsl-1.17.7/.artifactignore000066400000000000000000000001321517625163100160750ustar00rootroot00000000000000**/* !build/*.deb !build/*.tar.bz2 !build/*.dmg !build/*.7z !build/*.so !build/install/** liblsl-1.17.7/.clang-format000066400000000000000000000004771517625163100154620ustar00rootroot00000000000000--- BasedOnStyle: LLVM ColumnLimit: 100 UseTab: Always TabWidth: 4 IndentWidth: 4 AccessModifierOffset: -4 AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true AlignAfterOpenBracket: false MaxEmptyLinesToKeep: 2 ... liblsl-1.17.7/.clang-tidy000066400000000000000000000010621517625163100151320ustar00rootroot00000000000000--- Checks: | modernize-*, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, -modernize-use-nodiscard, isc-*, performance-*, readability-*, -readability-braces-around-statements, -readability-implicit-bool-conversion, -readability-magic-numbers, -readability-isolate-declaration, -readability-function-cognitive-complexity, -readability-identifier-length, bugprone-*, -bugprone-easily-swappable-parameters, concurrency-*, portability-* ... liblsl-1.17.7/.editorconfig000066400000000000000000000003031517625163100155500ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = tab [*.{yaml,yml}] indent_style = space indent_size = 2 insert_final_newline = true end_of_line = lf liblsl-1.17.7/.github/000077500000000000000000000000001517625163100144375ustar00rootroot00000000000000liblsl-1.17.7/.github/actions/000077500000000000000000000000001517625163100160775ustar00rootroot00000000000000liblsl-1.17.7/.github/actions/install-apple-certs/000077500000000000000000000000001517625163100217625ustar00rootroot00000000000000liblsl-1.17.7/.github/actions/install-apple-certs/action.yml000066400000000000000000000040621517625163100237640ustar00rootroot00000000000000name: 'Install Apple Certificates' description: 'Installs Apple signing and notarization certificates and sets up the keychain' inputs: MACOS_CERTIFICATE: required: true description: 'Base64-encoded Developer ID certificates (Application and Installer) in PKCS#12 format' MACOS_CERTIFICATE_PWD: required: true description: 'Password to decode the Apple certificates' runs: using: "composite" steps: - name: Install certificates and provisioning profiles shell: bash run: | # Create temporary keychain KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain MACOS_CI_KEYCHAIN_PWD=$(openssl rand -base64 32) security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH # Import certificate (containing both Application and Installer identities) from secret to keychain CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 echo -n "${{ inputs.MACOS_CERTIFICATE }}" | base64 --decode -o $CERTIFICATE_PATH security import $CERTIFICATE_PATH -P "${{ inputs.MACOS_CERTIFICATE_PWD }}" -k $KEYCHAIN_PATH -A -t cert -f pkcs12 # Set trusted partitions (groups of applications) that can access the keychain items security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH # Get certificate identities into environment variables CERT_IDENTITY_APP=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}') echo "APPLE_CODE_SIGN_IDENTITY_APP=$CERT_IDENTITY_APP" >> $GITHUB_ENV CERT_IDENTITY_INST=$(security find-identity -v -p basic $KEYCHAIN_PATH | grep "Developer ID Installer" | head -1 | awk -F'"' '{print $2}') echo "APPLE_CODE_SIGN_IDENTITY_INST=$CERT_IDENTITY_INST" >> $GITHUB_ENV liblsl-1.17.7/.github/workflows/000077500000000000000000000000001517625163100164745ustar00rootroot00000000000000liblsl-1.17.7/.github/workflows/android.yml000066400000000000000000000027231517625163100206430ustar00rootroot00000000000000name: Android on: push: branches: ['androidci'] paths: - '**' - '!docs/**' - '!.github/**' - '.github/workflows/android.yml' workflow_dispatch: inputs: cmakeextra: description: 'Extra CMake options' required: false default: '' defaults: run: shell: bash jobs: build: name: ${{ matrix.config.arch }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: config: - {arch: "x86"} - {arch: "armeabi-v7a" } - {arch: "arm64-v8a"} steps: - uses: actions/checkout@v4 - name: Configure CMake run: | cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PWD}/install \ -DLSL_UNITTESTS=ON \ -DCPACK_PACKAGE_DIRECTORY=${PWD}/package \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ -DCMAKE_SYSTEM_NAME=Android \ -DCMAKE_SYSTEM_VERSION=30 \ -DCMAKE_ANDROID_ARCH_ABI=${{ matrix.config.arch }} ${{ matrix.config.cmake_extra }} echo ${PWD} - name: make run: cmake --build build --target install --config Release -j - name: upload install dir uses: actions/upload-artifact@master with: name: build-android-${{ matrix.config.arch }} path: install liblsl-1.17.7/.github/workflows/apple.yml000066400000000000000000000151641517625163100203270ustar00rootroot00000000000000name: Apple CI on: push: branches: - main - dev tags: ['*'] paths: - '**' - '!docs/**' - '!.github/**' - '.github/workflows/apple.yml' pull_request: release: types: ['created'] workflow_dispatch: inputs: cmakeextra: description: 'Extra CMake options' required: false default: '' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true defaults: run: shell: bash jobs: build: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - {name: "macOS-latest", os: "macOS-latest", platform: "macos", cmake_extra: "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DLSL_UNITTESTS=ON -DLSL_BENCHMARKS=ON -DCMAKE_OSX_ARCHITECTURES=\"x86_64;arm64\"" } - {name: "iOS", os: "macOS-latest", platform: "ios", cmake_extra: "-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake -DPLATFORM=OS64" } - {name: "iOS Simulator", os: "macOS-latest", platform: "ios-simulator", cmake_extra: "-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake -DPLATFORM=SIMULATOR64COMBINED -G Xcode" } steps: - uses: actions/checkout@v4 - name: Install certificates and provisioning profiles uses: ./.github/actions/install-apple-certs with: MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} - name: Configure CMake env: APPLE_DEVELOPMENT_TEAM: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} run: | cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PWD}/install \ -DCPACK_PACKAGE_DIRECTORY=${PWD}/package \ -DLSL_FRAMEWORK=ON \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ ${{ matrix.config.cmake_extra }} \ ${{ github.event.inputs.cmakeextra }} echo ${PWD} - name: make run: cmake --build build --config Release -j - name: make install run: cmake --build build --config Release --target install - name: test install using examples if: matrix.config.name == 'macOS-latest' run: | # Test that the in-tree install was successful by building the examples cmake -S examples -B examples/build \ -DLSL_INSTALL_ROOT=${PWD}/install \ -DCMAKE_INSTALL_PREFIX=examples/build/install \ -DLSL_COMFY_DEFAULTS=ON \ ${{ matrix.config.cmake_extra }} \ ${{ github.event.inputs.cmakeextra }} cmake --build examples/build --target install --config Release -j ./examples/build/install/bin/HandleMetaData - name: Codesign Framework run: | ./scripts/apple_codesign.sh install/Frameworks/lsl.framework \ --platform ${{ matrix.config.platform }} # run internal tests - name: unit tests if: matrix.config.name == 'macOS-latest' run: | mkdir -p dumps install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes install/bin/lsl_test_exported --order rand --wait-for-keypress never --durations yes timeout-minutes: 10 - name: Package and Notarize macOS Installer if: matrix.config.name == 'macOS-latest' env: APPLE_DEVELOPMENT_TEAM: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} APPLE_NOTARIZE_USERNAME: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} APPLE_NOTARIZE_PASSWORD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} run: | ./scripts/apple_package_notarize.sh install/Frameworks/lsl.framework \ --notarize \ --output package - name: upload dump if: failure() uses: actions/upload-artifact@v4 with: name: dumps-${{ matrix.config.name }} path: dumps - name: Zip LSL Framework run: | cd install/Frameworks zip -ry lsl.framework.zip lsl.framework cd ../.. - name: Upload macOS Package and Framework if: matrix.config.name == 'macOS-latest' uses: actions/upload-artifact@v4 with: name: build-macOS-latest path: | package/*.pkg install/Frameworks/lsl.framework.zip - name: Upload iOS Framework if: matrix.config.name == 'iOS' uses: actions/upload-artifact@v4 with: name: build-iOS path: install/Frameworks/lsl.framework.zip - name: Upload iOS Simulator Framework if: matrix.config.name == 'iOS Simulator' uses: actions/upload-artifact@v4 with: name: build-iOS-Simulator path: install/Frameworks/lsl.framework.zip xcframework_and_deploy: name: XCFramework and Deploy needs: build runs-on: macOS-latest steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: build-macOS-latest path: build-macOS-latest - uses: actions/download-artifact@v4 with: name: build-iOS path: build-iOS - uses: actions/download-artifact@v4 with: name: build-iOS-Simulator path: build-iOS-Simulator - name: Unzip Frameworks run: | unzip build-macOS-latest/install/Frameworks/lsl.framework.zip -d build-macOS-latest/Frameworks unzip build-iOS/lsl.framework.zip -d build-iOS/Frameworks unzip build-iOS-Simulator/lsl.framework.zip -d build-iOS-Simulator/Frameworks - name: Install certificates and provisioning profiles uses: ./.github/actions/install-apple-certs with: MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} - name: Create and Sign XCFramework run: | ./scripts/apple_create_xcframework.sh \ --macos build-macOS-latest/Frameworks/lsl.framework \ --ios build-iOS/Frameworks/lsl.framework \ --ios-simulator build-iOS-Simulator/Frameworks/lsl.framework \ --output . - name: upload artifacts uses: actions/upload-artifact@v4 with: name: mac-packages path: | lsl.xcframework.*.zip build-macOS-latest/package/ - name: Upload to release if: github.event_name == 'release' uses: softprops/action-gh-release@v2 with: files: | lsl.xcframework.*.zip build-macOS-latest/package/*.* liblsl-1.17.7/.github/workflows/cppcmake.yml000066400000000000000000000220061517625163100210020ustar00rootroot00000000000000name: C/C++ CI on: push: branches: - main - dev tags: ['*'] paths: - '**' - '!docs/**' - '!.github/**' - '.github/workflows/cppcmake.yml' pull_request: release: types: ['created'] workflow_dispatch: inputs: cmakeextra: description: 'Extra CMake options' required: false default: '' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true defaults: run: shell: bash jobs: build: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: config: # x86_64 Linux builds - {name: "ubuntu-22.04-x64", os: "ubuntu-22.04", arch: "x86_64" } - {name: "ubuntu-24.04-x64", os: "ubuntu-24.04", arch: "x86_64" } # ARM Linux builds - cross-compiled with QEMU testing (Jetson, Raspberry Pi compatible) # SHLIBDEPS must be disabled for cross-compiled packages as the host system # does not have the target architecture libraries installed - {name: "ubuntu-22.04-arm64", os: "ubuntu-22.04", arch: "aarch64", cross_compile: true, cmake_extra: "-DLSL_DISABLE_PACKAGE_SHLIBDEPS=ON" } - {name: "ubuntu-22.04-armhf", os: "ubuntu-22.04", arch: "armv7", cross_compile: true, cmake_extra: "-DLSL_DISABLE_PACKAGE_SHLIBDEPS=ON" } # Native ARM build # - {name: "ubuntu-24.04-arm64-native", os: "ubuntu-24.04-arm", arch: "aarch64" } # Windows builds - {name: "windows-x64", os: "windows-latest", arch: "x86_64", cmake_extra: "-T v142,host=x86"} - {name: "windows-x86", os: "windows-latest", arch: "x86", cmake_extra: "-T v142,host=x86 -A Win32"} - {name: "windows-arm64", os: "windows-11-arm", arch: "aarch64", cmake_extra: "-T v143,host=ARM64 -A ARM64"} # macOS non-framework builds (build-only, no tests or release uploads) # These verify compatibility with package managers like conda-forge - {name: "macos-dylib-x64", os: "macos-15-intel", arch: "x86_64", cmake_extra: "-DLSL_FRAMEWORK=OFF", build_only: true} - {name: "macos-dylib-arm64", os: "macos-latest", arch: "arm64", cmake_extra: "-DLSL_FRAMEWORK=OFF", build_only: true} steps: - uses: actions/checkout@v5 # Set up cross-compilation toolchain for ARM on Linux - name: Install cross-compilation toolchain if: matrix.config.cross_compile && runner.os == 'Linux' run: | sudo apt-get update if [[ "${{ matrix.config.arch }}" == "aarch64" ]]; then sudo apt-get install -y --no-install-recommends \ gcc-aarch64-linux-gnu \ g++-aarch64-linux-gnu echo "CMAKE_TOOLCHAIN=-DCMAKE_TOOLCHAIN_FILE=${GITHUB_WORKSPACE}/cmake/toolchains/aarch64-linux-gnu.cmake" >> $GITHUB_ENV elif [[ "${{ matrix.config.arch }}" == "armv7" ]]; then sudo apt-get install -y --no-install-recommends \ gcc-arm-linux-gnueabihf \ g++-arm-linux-gnueabihf echo "CMAKE_TOOLCHAIN=-DCMAKE_TOOLCHAIN_FILE=${GITHUB_WORKSPACE}/cmake/toolchains/arm-linux-gnueabihf.cmake" >> $GITHUB_ENV fi # Cache dependencies for Linux - name: Cache APT packages if: runner.os == 'Linux' uses: actions/cache@v4 with: path: /var/cache/apt/archives key: ${{ runner.os }}-apt-${{ matrix.config.name }}-${{ hashFiles('.github/workflows/cppcmake.yml') }} restore-keys: | ${{ runner.os }}-apt-${{ matrix.config.name }}- ${{ runner.os }}-apt- # Cache build artifacts with ccache - name: Cache ccache if: runner.os == 'Linux' || runner.os == 'macOS' uses: actions/cache@v4 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ matrix.config.name }}-${{ github.sha }} restore-keys: | ${{ runner.os }}-ccache-${{ matrix.config.name }}- ${{ runner.os }}-ccache- - name: Install ccache if: runner.os == 'Linux' || runner.os == 'macOS' run: | if [[ "${{ runner.os }}" == "Linux" ]]; then sudo apt-get install -y ccache elif [[ "${{ runner.os }}" == "macOS" ]]; then brew install ccache fi ccache --max-size=500M ccache --set-config=compression=true - name: Configure CMake run: | # Set up ccache as compiler wrapper (skip for cross-compilation) if [[ "${{ runner.os }}" == "Linux" || "${{ runner.os }}" == "macOS" ]] && [[ "${{ matrix.config.cross_compile }}" != "true" ]]; then export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" export CC="ccache gcc" export CXX="ccache g++" fi cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PWD}/install \ -DLSL_UNITTESTS=ON \ -DLSL_BENCHMARKS=ON \ -DCPACK_PACKAGE_DIRECTORY=${PWD}/package \ -DCPACK_PACKAGE_FILE_NAME="liblsl-${{ matrix.config.arch }}" \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ ${CMAKE_TOOLCHAIN} \ ${{ matrix.config.cmake_extra }} \ ${{ github.event.inputs.cmakeextra }} echo "Build directory: ${PWD}" - name: Build run: | cmake --build build --config Release -j # Show ccache statistics if command -v ccache &> /dev/null; then ccache -s fi - name: Install run: cmake --build build --config Release --target install - name: Test install using examples run: | # Test that the in-tree install was successful by building the examples cmake -S examples -B examples/build \ -DLSL_INSTALL_ROOT=${PWD}/install \ -DCMAKE_INSTALL_PREFIX=examples/build/install \ -DLSL_COMFY_DEFAULTS=ON \ ${CMAKE_TOOLCHAIN} \ ${{ matrix.config.cmake_extra }} \ ${{ github.event.inputs.cmakeextra }} cmake --build examples/build --target install --config Release -j # Run example binary (skip for cross-compiled builds - no QEMU) if [[ "${{ matrix.config.cross_compile }}" != "true" ]]; then ./examples/build/install/bin/HandleMetaData else echo "Skipping example execution for cross-compiled build (requires real ARM hardware)" echo "Build validation successful - binary can be tested on target hardware" fi - name: Package if: matrix.config.build_only != true run: | echo "Creating package for ${{ matrix.config.arch }}" cmake --build build --target package --config Release -j # On Debian / Ubuntu the dependencies can only be resolved for # already installed packages (only for native builds, not cross-compiled) if [[ "${{ matrix.config.os }}" == ubuntu-* ]] && [[ "${{ matrix.config.cross_compile }}" != "true" ]]; then cmake -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON build sudo dpkg -i package/*.deb cmake --build build --target package --config Release -j dpkg -I package/liblsl*.deb fi if [[ "${{ matrix.config.cross_compile }}" == "true" ]]; then echo "Cross-compiled ${{ matrix.config.arch }} package created" ls -lh package/ fi cmake -E remove_directory package/_CPack_Packages cp testing/lslcfgs/default.cfg . 2>/dev/null || true - name: upload install dir if: matrix.config.build_only != true uses: actions/upload-artifact@master with: name: build-${{ matrix.config.name }} path: install - name: upload package if: matrix.config.build_only != true uses: actions/upload-artifact@master with: name: pkg-${{ matrix.config.name }} path: package - name: print network config run: | which ifconfig && ifconfig if [ `which ip` ]; then ip link ip addr ip route ip -6 route fi # Run tests (native builds only, skip build_only configs) - name: Unit tests if: matrix.config.cross_compile != true && matrix.config.build_only != true run: | # Set up core dumps for debugging if [[ "${{ matrix.config.name }}" == ubuntu-* ]]; then ulimit -c unlimited echo "$PWD/dumps/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern fi mkdir -p dumps # Run unit tests install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes install/bin/lsl_test_exported --order rand --wait-for-keypress never --durations yes timeout-minutes: 10 - name: upload dump if: failure() uses: actions/upload-artifact@master with: name: dumps-${{ matrix.config.name }} path: dumps - name: Upload to release if: github.event_name == 'release' && matrix.config.build_only != true uses: softprops/action-gh-release@v2 with: files: package/*.* liblsl-1.17.7/.github/workflows/mingw_static.yml000066400000000000000000000041021517625163100217040ustar00rootroot00000000000000name: MinGW Windows static test on: push: branches: ['*'] paths: - 'src' - 'include' - 'cmake' # - '!docs/**' # - '!.github/**' - '.github/workflows/mingw_static.yml' pull_request: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: build: name: MinGW batteries-included strategy: matrix: include: # - { sys: mingw64, env: x86_64 } # - { sys: mingw32, env: i686 } - { sys: ucrt64, env: ucrt-x86_64 } # - { sys: clang64, env: clang-x86_64 } runs-on: windows-latest defaults: run: shell: 'msys2 {0}' steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: msystem: ${{matrix.sys}} update: true cache: true install: >- git make pacboy: >- toolchain:p cmake:p ninja:p - name: Configure CMake run: | cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PWD}/install \ -DLSL_UNITTESTS=ON \ -DLSL_BUILD_STATIC=ON \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ -DLSL_OPTIMIZATIONS=OFF \ -G Ninja - name: make run: cmake --build build --target install --config Release -j --verbose - name: upload install dir uses: actions/upload-artifact@master with: name: mingw_artifacts path: install # run internal tests, ignore test failures on docker (missing IPv6 connectivity) - name: unit tests (internal functions) run: 'install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes' timeout-minutes: 5 - name: unit tests (exported functions) run: install/bin/lsl_test_exported --wait-for-keypress never --durations yes timeout-minutes: 5 liblsl-1.17.7/.github/workflows/raspberry_pi_manual.yml000066400000000000000000000126361517625163100232650ustar00rootroot00000000000000name: Raspberry Pi Native Build (Manual trigger) on: workflow_dispatch: inputs: cmakeextra: description: 'Extra CMake options' required: false default: '' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true defaults: run: shell: bash jobs: build: name: Raspberry Pi ${{ matrix.config.name }} runs-on: ubuntu-latest timeout-minutes: 30 strategy: fail-fast: false matrix: config: # cortex-a7 -> RPi 3+, if we need 1,2,zero, needs to use arm1176 # see: https://github.com/marketplace/actions/arm-runner # adapted from: https://github.com/castle-engine/castle-engine/blob/master/.github/workflows/test-and-pack-arm-runner.yml - {name: "rpi-armv7l", os: "raspios_lite:latest", cpu: "cortex-a7", cpu_info: "cpuinfo/raspberrypi_3b", cmake_extra: "-DLSL_UNITTESTS=ON -DLSL_BENCHMARKS=ON" } - {name: "rpi-aarch64", os: "raspios_lite_arm64:latest", cpu: "cortex-a53", cpu_info: "cpuinfo/raspberrypi_4b", cmake_extra: "-DLSL_UNITTESTS=ON -DLSL_BENCHMARKS=ON" } steps: - uses: actions/checkout@v5 - uses: pguyot/arm-runner-action@v2 with: base_image: ${{ matrix.config.os }} cpu: ${{ matrix.config.cpu }} cpu_info: ${{ matrix.config.cpu_info }} shell: /bin/bash -eo pipefail image_additional_mb: 1024 bind_mount_repository: true - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y cmake build-essential pkg-config - name: Configure CMake run: | cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=${PWD}/install \ -DLSL_UNITTESTS=ON \ -DLSL_BENCHMARKS=ON \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ ${{ matrix.config.cmake_extra }} - name: make run: cmake --build build --config Release -j - name: make install run: cmake --build build --config Release --target install -j - name: test install using examples run: | # Test that the in-tree install was successful by building the examples cmake -S examples -B examples/build \ -DLSL_INSTALL_ROOT=${PWD}/install \ -DCMAKE_INSTALL_PREFIX=examples/build/install \ -DLSL_COMFY_DEFAULTS=ON \ ${{ matrix.config.cmake_extra }} \ ${{ github.event.inputs.cmakeextra }} cmake --build examples/build --target install --config Release -j ./examples/build/install/bin/HandleMetaData - name: package run: | echo $GITHUB_REF cmake --build build --target package --config Release -j echo $PWD ls -la cmake -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON . sudo dpkg -i build/*.deb cmake --build build --target package --config Release -j dpkg -I build/liblsl*.deb cmake -E remove_directory build/_CPack_Packages cp testing/lslcfgs/default.cfg . - name: upload install dir uses: actions/upload-artifact@master with: name: build-${{ matrix.config.name }} path: install - name: upload package uses: actions/upload-artifact@master with: name: pkg-${{ matrix.config.name }} path: package # Run tests - name: unit tests run: | if [[ "${{ matrix.config.name }}" = ubuntu-2* ]]; then ulimit -c unlimited echo "$PWD/dumps/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern fi mkdir -p dumps install/bin/lsl_test_internal --order rand --wait-for-keypress never --durations yes install/bin/lsl_test_exported --order rand --wait-for-keypress never --durations yes timeout-minutes: 10 - name: upload dump if: failure() uses: actions/upload-artifact@master with: name: dumps-${{ matrix.config.name }} path: dumps - name: upload to release page if: github.event_name == 'release' env: TOKEN: "token ${{ secrets.GITHUB_TOKEN }}" TAG: ${{ github.event.release.tag_name }} UPLOAD_URL: ${{ github.event.release.upload_url }} run: | # Do try this at home! The REST API is documented at # https://docs.github.com/en/free-pro-team@latest/rest and you can get a personal # access token at https://github.com/settings/tokens # (set TOKEN to "bearer abcdef1234") # you can get the UPLOAD_URL with a short bash snippet; make sure to set the env var TAG: # UPLOAD_URL=$(curl -H 'Accept: application/vnd.github.v3+json' $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/$TAG | jq -r .upload_url) UPLOAD_URL=${UPLOAD_URL%\{*} # remove "{name,label}" suffix for pkg in package/*.*; do NAME=$(basename $pkg) MIME=$(file --mime-type $pkg|cut -d ' ' -f2) curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: $TOKEN" -H "Content-Type: $MIME" --data-binary @$pkg $UPLOAD_URL?name=$NAME done liblsl-1.17.7/.github/workflows/sanitize.yml000066400000000000000000000051451517625163100210520ustar00rootroot00000000000000name: Clang Sanitizer on: workflow_dispatch: inputs: cmakeextra: description: "Extra CMake options" required: false default: "" sanitizer: description: 'Sanitizer to run' required: true default: 'address' # caution: memory sanitizer is currently broken with Catch options: ['address', 'thread', 'memory', 'undefined'] type: choice defaults: run: shell: bash env: LLVM_VERSION: 18 jobs: build: name: "${{ github.event.inputs.sanitizer }}" runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v4 - name: Install build toolchain run: | curl -sSL https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/llvm.gpg echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-$LLVM_VERSION main" | sudo tee /etc/apt/sources.list.d/llvm.list sudo apt update sudo apt install -y libpugixml-dev clang-$LLVM_VERSION gdb - name: Configure CMake run: | # linking a C++ library to a C program fails with ubsan enabled; disable lslver for this run sed -i -e'/lslver/d' CMakeLists.txt cmake --version cmake -S . -B build \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_C_COMPILER=clang-$LLVM_VERSION \ -DCMAKE_{CXX_COMPILER,LINKER}=clang++-$LLVM_VERSION \ -DCMAKE_{C,CXX,EXE_LINKER,SHARED_LINKER}_FLAGS="-fsanitize=${{ github.event.inputs.sanitizer }}" \ -DLSL_COMFY_DEFAULTS=ON \ -DLSL_UNITTESTS=ON \ -DLSL_BENCHMARKS=ON \ -DLSL_BUILD_EXAMPLES=OFF \ -DLSL_BUNDLED_PUGIXML=OFF \ -DLSL_SLIMARCHIVE=ON \ -DLSL_OPTIMIZATIONS=ON \ -Dlslgitrevision=${{ github.sha }} \ -Dlslgitbranch=${{ github.ref }} \ ${{ github.event.inputs.cmakeextra }} echo ${PWD} - name: make run: cmake --build build --config RelWithDebInfo -j - name: run unit tests run: | # alias gdbwrap="gdb --batch -ex 'run --order rand --wait-for-keypress never --durations yes' -ex 'thread apply all bt' -return-child-result" gdb --batch -ex 'run --order rand --wait-for-keypress never --durations yes' -ex 'thread apply all bt' -return-child-result build/testing/lsl_test_internal gdb --batch -ex 'run --order rand --wait-for-keypress never --durations yes' -ex 'thread apply all bt' -return-child-result build/testing/lsl_test_exported timeout-minutes: 15 liblsl-1.17.7/.gitignore000066400000000000000000000003031517625163100150630ustar00rootroot00000000000000/build*/ /install/ /package/ /CMakeLists.txt.user /CMakeLists.json /CMakeSettings.json /docs/liblsl_* /docs/_build /.vs/ /.cache/ .DS_Store /out/ # CLion .idea/ /cmake-build-*/ /examples/build*/ liblsl-1.17.7/.readthedocs.yml000066400000000000000000000002131517625163100161610ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py # formats: all python: version: 3 install: - requirements: docs/requirements.txt liblsl-1.17.7/CHANGELOG.md000066400000000000000000000353651517625163100147240ustar00rootroot00000000000000# Changes for liblsl 1.17 * Refactor CMake builds for clarity, for apple frameworks, code-signing and notarization, less interference with user-projects (Chadwick Boulay) * Update dependencies (@zeyus) * Fix extra dereference that led to sigsegv upon cleanup in some scenarios (Mathieu Scheltienne) # Changes for liblsl 1.16 * add: optional, minimal header-only replacement for Boost.Serialization (Tristan Stenner) * add: extensible `lsl_create_inlet_ex()` for high-precision buffer lengths (Chadwick Boulay) * change: replace Boost.Uuid, Boost.Random and Boost.Thread with built-in functions (Tristan Stenner) * change: replace Boost.Asio with upstream Asio (Tristan Stenner) * change: update bundled Boost to 1.78 (Tristan Stenner) * change: allow building against system Boost again (@chausner) * change: speed up resolving a fixed number of streams (Tristan Stenner) * change: reduce Asio operation overhead (Tristan Stenner) * change: IPv6 is enabled by default on macOS (Tristan Stenner) * change: share io contexts for IPv4+IPv6 services (Tristan Stenner) * **change**: send resolve requests from all local network interfaces (Tristan Stenner) * fix: fix a minor memory leak when closing streams (Tristan Stenner) # Changes for liblsl 1.15.2 * fix: bump artifact / soname version # Changes for liblsl 1.15.1 * change: update Boost / Boost.Asio to 1.75 (Tristan Stenner) * change: replace Boost.UUID generator * fix: work around faulty MinGW code generation for thread local variables (Tristan Stenner, Tobias Herzke) # Changes for liblsl 1.15 * add: thread-safe `lsl_last_error()` function, returns a description of the last error (https://github.com/sccn/liblsl/pull/75, Tristan Stenner) * change: object handles in the C++ API use smart pointers internally. The smart pointer to the object can be retrieved with the `handle()` function * **change**: The `stream_info` copy constructor creates a shallow copy. Use `stream_info::clone()` for the previous behavior (deep copy). * change: replace more Boost libraries with C++11 counterparts (Tristan Stenner) * change: speed up `push_chunk_*` calls by caching the sampling rate (Chadwick Boulay) * change: postprocessing parameters are reset after dis- and re-enabling postprocessing (Tristan Stenner) * fix: postprocessing now works even when flushing samples (Tristan Stenner) * fix: pulling strings with embedded NULL chars now works (Tristan Stenner, Chadwick Boulay) * fix: various build system and deployment fixes (Chadwick Boulay, Tristan Stenner) * fix: building with MinGW now works (Tobias Herzke) * fix: `max_buflen` documentation and edge cases (Chadwick Boulay) * fix: preliminary support for deploying Qt6 apps with CMake (Chadwick Boulay, Tristan Stenner) * fix: packages for Ubuntu 18.04 no longer depend on the unavailable libgcc_s1 package (Tristan Stenner, many thanks to Tobias Herzke for reporting the issue) # Changes for liblsl 1.14 * **change**: CMake doesn't set `CMAKE_INSTALL_PREFIX`and `CMAKE_BUILD_TYPE` by default unless `LSL_COMFY_DEFAULTS` is set (Tristan Stenner) * fix: prevent race condition when two threads modify an overflowing buffer (https://github.com/sccn/liblsl/pull/71, Jérémy Frey) * fix: improve latency and CPU usage (https://github.com/sccn/liblsl/pull/71, Jérémy Frey) * fix: various build system fixes and improvments (Chadwick Boulay, Tristan Stenner) * added: enumerate network interfaces on startup, for now unused. Requires Android SDK >=26 (Tristan Stenner) * added: lsl_inlet_flush() to drop all outstanding samples from an inlet to avoid the pull_ overhead in realtime applications (Tristan Stenner) * change: the binary names are changed so that the OS resolver (e.g. `find_library(lsl)`) searches for the correct library. Windows: liblsl32.dll / liblsl64.dll -> lsl.dll, Unix: liblsl.so. (Tristan Stenner) * change: the lsl_c.h header is now split into several header files (Tristan Stenner) * change: refactor logging, add `log.file` config option (Tristan Stenner) * change: replace various Boost libraries with C++11 counterparts (Tristan Stenner) * change: liblsl requires a compiler with C++11 support, MSVC 2015 on Windows # Changes for liblsl 1.13 (released 2020-01-30) * fix: avoid an infinite loop if no port can be bound to (Tristan Stenner, discovered by Maximilian Kraus) * add: unit tests to test basic operations on CI systems (Tristan Stenner, Matthew Grivich) * fix: Fix for the sometimes appearing "no clock offsets found"-bug * add: the path to the lsl_api.cfg can now be specified via the environment variable LSLAPICFG * fix: updates to the included Boost, fixing Android compilation and reducing the number of warnings (Tristan Stenner) * fix: patch Boost.Thread to avoid latency spikes on Windows (Tristan Stenner) * add: lsl_stream_info_matches_query() to test a resolved stream against a query (Tristan Stenner) * add: lsl_library_info() function returning some information about the dll/so file (Tristan Stenner) * add: Raspberry Pi cross compilation support (Chadwick Boulay) * fix: Boost.Serialization objects are only defined to fix compilation on ARM (Tristan Stenner) * change: added move-semantics to most C++ API classes (Tristan Stenner) * change: move error handling from C API to C++ implementation (Tristan Stenner) * fix: Fix possible string stream corruption on 32bit platforms (Tristan Stenner) * change: add some Continuous Integration and packaging configuration (Tristan Stenner) * change: remove the option to build liblsl against system Boost (Tristan Stenner) * fix: prevent undefined shift (Tristan Stenner) * fix: several fixes to export macros (Tristan Stenner) * change: lots of improvements on OS X (Chadwick Boulay) * change: a lot of work on making Qt work on OS X (Chadwick Boulay) * change: override default timestamps with lsl timestamps (Zechari Tempesta) * change: Switched the build system to CMake (Tristan Stenner, Chadwick Boulay, David Medine, Matthew Grivich) * fix: Fix for ipv6 link-local addresses on Linux. Previously would get a "Invalid argument" error. (Matthew Grivich) * fix: fix several memory handling issues (Matthew Grivich) * fix: Added an optional time_correction prototype to return uncertainty and remote_time as well as offset. (Matthew Grivich) * fix: merged memory leak fixes (thanks to xloem) # Changes for liblsl 1.12 * Added support for iOS. (Matthew Grivich; de3f1318) * changed constructor of lsl::xml_element to have default (David Medine; fd6a87b7) * Add support for gcc 5 / MinGW (BorisMansencal) * Fixed warning in GCC 4.9: 'lsl::stream_info' has a field 'lsl::stream_info::obj' whose type uses the anonymous namespace 1> class stream_info { (Matthew Grivich; 072563f5) * Added optional time-stamp post-processing to the LSL inlet (Christian Kothe; d29ebde8) * fixed broken relative paths (Giso Grimm; 1396ce6e) * automatic version number extraction (Giso Grimm; a3e912ad) * version number extraction and new location (Giso Grimm; 56f210a2) Changes for liblsl 1.11 * Updated C/C++ examples to conform to XDF meta-data spec (per Luca Mussi). (Christian Kothe; 61d83de6) * pugixml 1.0->1.7 . This fixes a build error in OS X. Also tested in Win 10 binaries with no apparent problems. (Chadwick Boulay; 067e1330) * several changes to the build system and include files (Matthew Grivich, Chadwick Boulay) * Fixed remaining references to old documentation. (Kyu Crane; ecf9cd09) * Some fixes and updates here and there. (Christian Kothe; 65fa64e2) * Fixed bug in push_chunk_multiplexed (Christian Kothe; 89339d08) * Fixed regression in samples_available() (Christian Kothe; 5d0659da) * Delete ._stream_info_impl.cpp (Christian Kothe; 92bfd87c) * Updated consumer_queue::pop_sample() (Christian Kothe; 22662cb7) * Fixed consumer_queue::pop_sample() to respect the timeout. (Steven Boswell; 678995dd) * Fixed some memory leaks in the C# interop layer. (Steven Boswell; 06684ec6) * Now multicasting can specify the local IP address to bind to. (Steven Boswell; 04a397e4) * attempting to strip binaries (David Medine; bc216cc2) Changes for liblsl 1.10 [Here a lot of changes happened that aren't included yet] Changes for distribution 1.0.10 (library version 0.91) * fix: in the C++ interface, the template pull_chunk() that fills a vector of time stamps did not clear this vector initially (no recompile necessary, just a header fix) * change: simplified the rule for deciding which chunk size to use when both the outlet and the inlet have a preference: now the inlet's preference overrides the one of the outlet, if present. Changes for distribution 1.0.18 (library version 0.92) * fix: rebuild Linux and Mac libraries with most boost symbols stripped off (reducing the conflict of name clashes during dynamic linking, e.g. in MATLAB). * Adding support for constructing inlets with unresolved stream_info's (if they are fully specified at construction). Changes for distribution 1.0.19 (library version 0.93) * removed the entire boost static library dependency across Win/Linux/Mac (instead now using the relevant .cpp files directly). This simplifies the build process and reduces the symbol pollution on Linux/Mac. * Documented compatibility with pugixml in C API. * Documented the timeout in resolve_all better in C API. Changes for distribution 1.0.20 (library version 1.00) -- internal code review. * Added a continuous_resolver class that allows to resolve streams in the background. * Added ability to query receive buffer size. * Extended the error reporting in the C API (some functions now return error conditions). * Added some extra argument checking in the stream_info constructor (negative channel count / sample rate, empty name). * Upgraded from boost 1.47.0 to boost 1.50.0, and from portable_archive 4.2 to portable_archive 5.0. * Improved code documentation and slightly refactored some of the library code for better readability / comprehensibility. - renamed some internal variables - enhanced documentation - switched largely to boost.chrono based timing instead of boost.date_time (new standard) - following boost synchronization guidelines more strictly (preferentially using the simplest primitive that gets the job done) - made the stream_inlet more tolerant to protocol errors at the sender side (threads do not exit but try to reconnect until successful). - using incomplete struct types in the LSL C API to prevent accidental type confusions. - simplified (and robustified) the data and info fetching threads in the inlet. - reduced the amount of data copying when pushing samples into the library - the IPv4 and IPv6 stacks in the outlet now run on two separate threads to improve robustness in case an OS implements a protocol badly - improved efficiency of stream_info accessors (now return const references instead of copies) - now using boost.container.flat_set in the send_buffer instead of std.set (more efficient) * Fixed a bug that prevented the use of LSL_FOREVER in the C API under 32-bit Mac (and possibly 32-bit Linux, too). * Fixed some potential hangups in the inlet when the connection gets lost irrecoverably (affected info(), time_correction() and pull_sample()). * Fixed several missing exception handlers for the C API wrappers (increasing robustness in case of bad errors). * Fixed a minor bug when resolving streams from a list of known peers (used the wrong time constant). * Fixed 2 cases where strings were allocated incorrectly in the liblsl C API (lsl_get_xml() and lsl_pull_sample_str()). * Fixed a bug where -0.0 would be transmitted as +0.0 (fixed in the portable_archive upgrade). * Fixed several destructors that could have thrown exceptions and thus caused application termination. * Fixed a bug that prevented the use of minimum buffer sizes that were larger than the lower capacity bound (then 4096). Changes for distribution 1.0.21 (library version 1.02) -- after extensive stress testing. * Added a stress-testing app under testing/StressTest * Relaxed the timing of reconnect attempts by the inlet a bit. * Fixed the handling of the chunk size when passed in at construction of an inlet or outlet (oversight) * Fixed a rare abstract function call or access violation when cancelling resolve attempts * Fixed a relatively rare access violation in the TCP server shutdown * Fixed a relatively rare access violation in the stream outlet shutdown * Fixed an occasional "not a socket" error in the TCP server shutdown * Fixed handling of some unusual parameter settings in the inlet (buffer size=0) Library version 1.03 * Added support for Windows XP (required more tolerance in case of missing IPv6 support) * Fixed a case where an inlet could spam an outlet with rapid reconnect requests * Rebuilt against a sufficiently old Linux to run out-of-the-box for most distributions Library version 1.03.1 * Made the resolver tolerant w.r.t. invalid hostnames in the KnownPeers config variable Library version 1.03.2 * Moved a definition inside the C++ header file to make SWIG happy (which is needed for certain features in the Python wrapper to be enabled) * pylsl should now work out of the box on all Windows platforms and all recent Python versions Library Version 1.03.3 * renamed internal use of boost namespace into lslboost to avoid link-time symbol conflicts * Added the ability to push/pull length-delimeted string values in the C API (lsl_push_sample_buf / lsl_pull_sample_buf) in addition to 0-terminated * Added an experimental Java wrapper Library Version 1.04 * Added a preprocessor switch to toggle between using the system-supplied boost or the shrink-wrapped version (in external/lslboost) * Added documentation on how to get an updated boost distribution included with liblsl (and added a script to automate that) Library Version 1.05 * Suppressed a spurious warning about non-matching query ids Library Version 1.10 (mostly throughput optimizations) * Added the ability to allocate ports outside the default port range once if it is exhausted (configurable, enabled by default). * Replaced consumer_queue implementation by a lock-free single-producer/single-consumer queue * Minor speed improvements in data_receiver: Removed a slow check in pull_sample() and calling local_clock() only every k'th iteration in data_thread(). * Changed the data layout of the sample class for faster allocation/deallocation. * Speeded up the assignment/retrieval functions of the sample class (uses memcpy where appropriate). * Added a factory class for fast allocation/deallocation of samples. * Implemented a faster data transmission protocol (1.10) for samples, as alternative to the baseline protocol (1.00). * Implemented a new version of protocol negotiation between tcp_server and data_receiver, using an HTTP-like request/response style. * Fixed some potential rare hang bugs during shutdown of inlets and outlets (deadlocks/livelocks) * Added the ability to transmit multiplexed chunks to the C and C++ APIs * Minor improvements to documentation * Added clean cross-platform Python and C# wrappers (pylsl.py and LSL.cs) liblsl-1.17.7/CMakeLists.txt000066400000000000000000000034711517625163100156440ustar00rootroot00000000000000cmake_minimum_required (VERSION 3.23) project (liblsl VERSION 1.17.7 LANGUAGES C CXX DESCRIPTION "Labstreaminglayer C/C++ library" HOMEPAGE_URL "https://github.com/sccn/liblsl" ) # API version, to be incremented on backwards-incompatible ABI changes set(LSL_ABI_VERSION 2) # Enable folder organization in IDEs (Visual Studio, Xcode, CLion) set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Include modular configuration files include(cmake/ProjectOptions.cmake) # include(cmake/CompilerSettings.cmake) # include(cmake/Dependencies.cmake) # include(cmake/SourceFiles.cmake) # include(cmake/TargetObjLib.cmake) # include(cmake/TargetLib.cmake) # include(cmake/Installation.cmake) # include(cmake/TargetOther.cmake) if(LSL_UNITTESTS AND NOT IOS) add_subdirectory(testing) endif() # Config for packaging include(cmake/LSLCMake.cmake) LSL_get_target_arch() LSL_get_os_name() set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_VENDOR "Labstreaminglayer") set(CPACK_STRIP_FILES ON) if(APPLE) set(CPACK_GENERATOR TGZ) elseif(WIN32) set(CPACK_GENERATOR ZIP) else() set(CPACK_GENERATOR DEB TGZ) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "LabStreamingLayer Developers") set(CPACK_DEBIAN_PACKAGE_SECTION "science") if(LSL_ARCH STREQUAL "amd64" OR LSL_ARCH STREQUAL "arm64" OR LSL_ARCH STREQUAL "i386") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${LSL_ARCH}") elseif(LSL_ARCH STREQUAL "arm") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "armhf") elseif(LSL_ARCH STREQUAL "ppc64") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "ppc64el") elseif(LSL_ARCH STREQUAL "powerpc") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "powerpc") endif() endif() set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${LSL_OS}_${LSL_ARCH}") include(CPack) liblsl-1.17.7/LICENSE000066400000000000000000000025351517625163100141110ustar00rootroot00000000000000Copyright (C) 2012 Christian A. Kothe 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. This software uses the following libraries: pugixml (https://pugixml.org), MIT. pugixml is Copyright (C) 2006-2018 Arseny Kapoulkine. Boost (https://boost.org), Boost Software License Loguru (https://github.com/emilk/loguru), Public Domain Catch2 (https://github.com/catchorg/Catch2), Boost Software License liblsl-1.17.7/README.md000066400000000000000000000124401517625163100143570ustar00rootroot00000000000000[![GitHub Actions Status](https://github.com/sccn/liblsl/workflows/C%2FC++%20CI/badge.svg)](https://github.com/sccn/liblsl/actions) [![Azure Build Status](https://dev.azure.com/labstreaminglayer/liblsl/_apis/build/status/sccn.liblsl?branchName=master)](https://dev.azure.com/labstreaminglayer/liblsl/_build/latest?definitionId=1&branchName=master) [![DOI](https://zenodo.org/badge/123265865.svg)](https://zenodo.org/badge/latestdoi/123265865) # Lab Streaming Layer library The lab streaming layer is a simple all-in-one approach to streaming experiment data between applications in a lab, e.g. instrument time series, event markers, audio, and so on. For more information, please read the [online documentation](https://labstreaminglayer.readthedocs.io) These repository is for the core library: `liblsl` ## Getting and using liblsl The most up-to-date instructions to use liblsl are in the [quick start online documentation](https://labstreaminglayer.readthedocs.io/info/getting_started.html). You might also be interested in [apps to connect to recording equipment](https://labstreaminglayer.readthedocs.io/info/supported_devices.html) and the [LabRecorder](https://github.com/labstreaminglayer/App-LabRecorder) to record streams to disk. To retrieve the latest liblsl release, you have a few options. Precompiled packages are uploaded - to the [Release page](https://github.com/sccn/liblsl/releases) - the [Anaconda cloud](https://anaconda.org/conda-forge/liblsl), install with `conda install -c conda-forge liblsl` liblsl is also available via the following package managers: - [vcpkg](https://vcpkg.io) - [Conan](https://conan.io/center/liblsl) - [homebrew](https://brew.sh/) via `brew install labstreaminglayer/tap/lsl` If you cannot find a liblsl for you via any of the above methods, then fear not because for most users it is simple to build. ## Building liblsl To compile the library yourself from source, please follow the [online documentation](https://labstreaminglayer.readthedocs.io/dev/lib_dev.html). For single board computers running linux, you can also try `standalone_compilation_linux.sh`. ## Design goals The design goals of the library are: a) The interface shall be as simple as possible, allowing programs or drivers to send or receive data in just 3-5 lines of code. b) The library should be available for a variety of languages (currently C, C++, Matlab, Python, Java) and platforms (Windows, Mac OS X, Linux, 32/64 bit) and be fully interoperable between them. c) Data transmission should work "out of the box", even across networks with no need to configure IP addresses / hostnames and such (thanks to on-the-fly service discovery), also time synchronization and failure recovery should work out of the box. d) The library should be fully featured. It should cover the relevant streaming data formats incl. multi-channel signals, regular/irregular sampling rate and the major channel data types (int8, int16, int32, float, double, string) in a simple interface. Generic stream meta-data should be supported. Advanced transmission features should be available if desired (but not in the way for simple uses), including custom ways of chunking and buffering the data. It should be possible to configure and tune the behavior of the library (e.g. networking features) via configuration files in a way that is transparent to the applications. e) Network and processor overhead should be reasonably low to not get in the way. Package overview: * The API headers are in the [`include/`](include/) directory. * The library source code is in the [`src/`](src/) directory. * Unit tests are in the [`testing/`](testing/) directory To connect an application to the lab streaming layer: * Include the header for your language (`lsl_c.h` for C, `lsl_cpp.h for C++`) (automatically done when using CMake) or get [bindings for your preferred language](https://github.com/sccn/labstreaminglayer/tree/master/LSL) * Make sure that the library file (`liblsl.so`/`liblsl.dylib`/`lsl.dll`) is found by your application. On Windows, it should be enough to put it in the same folder as your executable. When building a Windows app, also make sure that the `lsl.lib` file is visible to your build environment. * To provide data, create a new streaminfo to describe your stream and create a new outlet with that info. Push samples into the outlet as your app produces them. Destroy the outlet when you're done. * To receive data, resolve a stream that matches your citeria (e.g. name or type), which gives you a streaminfo and create a new inlet with that streaminfo. Pull samples from the inlet. Destroy the inlet when you're done. * Have a look at the example sources in the [`examples/`](examples/) folder. The library and example applications are licensed under the MIT license. The library uses code that is licensed under the Boost software license. # Acknowledgements The original version of this software was written at the Swartz Center for Computational Neuroscience, UCSD. This work was funded by the Army Research Laboratory under Cooperative Agreement Number W911NF-10-2-0022 as well as through NINDS grant 3R01NS047293-06S1. # Citing liblsl [![DOI](https://zenodo.org/badge/123265865.svg)](https://zenodo.org/badge/latestdoi/123265865) Information about versioning: https://help.zenodo.org/#versioning liblsl-1.17.7/azure-pipelines.yml000066400000000000000000000007111517625163100167350ustar00rootroot00000000000000trigger: branches: {include: ['*'] } paths: include: ['*'] exclude: ['docs/*', '.github/**'] stages: - stage: build jobs: - job: manylinux2010 pool: { vmImage: 'ubuntu-latest' } container: quay.io/pypa/manylinux2010_x86_64:latest steps: - bash: CFLAGS="-flto -static-libstdc++ -std=c++14" ./standalone_compilation_linux.sh - bash: strip --strip-unneeded liblsl.so - publish: liblsl.so artifact: manylinux2010 liblsl-1.17.7/cmake/000077500000000000000000000000001517625163100141575ustar00rootroot00000000000000liblsl-1.17.7/cmake/CompilerSettings.cmake000066400000000000000000000025501517625163100204560ustar00rootroot00000000000000# Compiler and language settings set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # generate a compilation database (compile_commands.json) for clang tooling set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(LSL_WINVER "0x0601" CACHE STRING "Windows version (_WIN32_WINNT) to target (defaults to 0x0601 for Windows 7)") # Configure RPATH for installed executables (must be set before targets are created) # This ensures test executables can find lsl.framework at runtime if(APPLE AND LSL_FRAMEWORK) set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks") elseif(UNIX AND NOT ANDROID) set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/../lib") endif() set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # Determine library type if(LSL_BUILD_STATIC) set(LSL_LIB_TYPE STATIC) else() set(LSL_LIB_TYPE SHARED) endif() # Enable position independent code for shared libraries if(NOT LSL_BUILD_STATIC OR UNIX) # shared libraries require relocatable symbols so we enable them by default set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() # Enable optimizations if requested if(LSL_OPTIMIZATIONS) # enable LTO (https://en.wikipedia.org/wiki/Interprocedural_optimization set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) endif() # Platform-specific settings if(WIN32) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() liblsl-1.17.7/cmake/CreateFrameworkSymlinks.cmake.in000066400000000000000000000027121517625163100224030ustar00rootroot00000000000000# This script is executed at install time. # It was generated from CreateFrameworkSymlinks.cmake.in # Handle both absolute and relative CMAKE_INSTALL_FRAMEWORK_DIR paths if(IS_ABSOLUTE "@CMAKE_INSTALL_FRAMEWORK_DIR@") set(FRAMEWORK_DIR "@CMAKE_INSTALL_FRAMEWORK_DIR@/lsl.framework") else() set(FRAMEWORK_DIR "${CMAKE_INSTALL_PREFIX}/@CMAKE_INSTALL_FRAMEWORK_DIR@/lsl.framework") endif() message(STATUS "Executing configured symlink script.") message(STATUS " -- Target Directory='${FRAMEWORK_DIR}'") if(NOT EXISTS "${FRAMEWORK_DIR}") message(FATAL_ERROR "Framework version directory does not exist. Cannot create symlink.") endif() message(STATUS " -- Framework version directory exists. Creating symlink...") execute_process( COMMAND ln -sf include Headers WORKING_DIRECTORY "${FRAMEWORK_DIR}/Versions/A" RESULT_VARIABLE result ERROR_VARIABLE error ) if(NOT result EQUAL 0) message(FATAL_ERROR "Failed to create Headers->include symlink: ${error}") endif() execute_process( COMMAND ln -sf Versions/Current/Headers WORKING_DIRECTORY "${FRAMEWORK_DIR}" RESULT_VARIABLE result ERROR_VARIABLE error ) if(NOT result EQUAL 0) message(FATAL_ERROR "Failed to create Headers->Versions/Current/Headers symlink: ${error}") endif() if(NOT result EQUAL 0) message(FATAL_ERROR "Failed to create Headers symlink in framework: ${error}") endif() message(STATUS " -- Framework symlink created successfully.") liblsl-1.17.7/cmake/Dependencies.cmake000066400000000000000000000062021517625163100175470ustar00rootroot00000000000000find_package(Threads REQUIRED) # Git version information include(cmake/GitVersion.cmake) # PugiXML dependency # Note: FetchContent fetches pugixml 1.15 which has string_view support. # System pugixml may be older, but the code handles both via pugi_str() helper. if(LSL_FETCH_PUGIXML) message(STATUS "Fetching pugixml via FetchContent") include(FetchContent) set(PUGIXML_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(PUGIXML_NO_EXCEPTIONS OFF CACHE BOOL "" FORCE) set(PUGIXML_INSTALL OFF CACHE BOOL "" FORCE) # Force static library even if parent project sets BUILD_SHARED_LIBS set(PUGIXML_BUILD_SHARED_AND_STATIC_LIBS OFF CACHE BOOL "" FORCE) set(_lsl_saved_build_shared_libs ${BUILD_SHARED_LIBS}) set(BUILD_SHARED_LIBS OFF) FetchContent_Declare( pugixml GIT_REPOSITORY https://github.com/zeux/pugixml.git GIT_TAG v1.15 GIT_SHALLOW TRUE EXCLUDE_FROM_ALL ) FetchContent_MakeAvailable(pugixml) set(BUILD_SHARED_LIBS ${_lsl_saved_build_shared_libs}) unset(_lsl_saved_build_shared_libs) if(TARGET pugixml AND NOT TARGET pugixml::pugixml) add_library(pugixml::pugixml ALIAS pugixml) endif() # Hide pugixml symbols - apply hidden visibility to the pugixml target set_target_properties(pugixml PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON ) set(LSL_PUGIXML_IS_FETCHED TRUE) else() message(STATUS "Using system pugixml") # Warn if building universal binary on Apple - system pugixml is likely single-arch if(APPLE AND CMAKE_OSX_ARCHITECTURES) list(LENGTH CMAKE_OSX_ARCHITECTURES _lsl_arch_count) if(_lsl_arch_count GREATER 1) message(WARNING "Building universal binary with system pugixml. " "Homebrew and most package managers provide single-architecture binaries. " "Consider using -DLSL_FETCH_PUGIXML=ON or setting -DCMAKE_OSX_ARCHITECTURES " "to a single architecture.") endif() unset(_lsl_arch_count) endif() find_package(pugixml REQUIRED) if(NOT TARGET pugixml::pugixml) add_library(pugixml::pugixml ALIAS pugixml) endif() set(LSL_PUGIXML_IS_FETCHED FALSE) endif() # Create lslboost target add_library(lslboost INTERFACE) set_target_properties(lslboost PROPERTIES FOLDER "liblsl") if(LSL_BUNDLED_BOOST) message(STATUS "Using bundled header-only Boost") target_include_directories(lslboost INTERFACE $ ) else() message(STATUS "Using system Boost") find_package(Boost REQUIRED) # Map `lslboost` namespace, which LSL code base uses, to system `boost` namespace/headers. target_compile_definitions(lslboost INTERFACE lslboost=boost) target_link_libraries(lslboost INTERFACE Boost::boost Boost::disable_autolinking) endif() target_compile_definitions(lslboost INTERFACE BOOST_ALL_NO_LIB) # Enable LOGURU stack traces if LSL_DEBUGLOG is enabled. set_source_files_properties("thirdparty/loguru/loguru.cpp" PROPERTIES COMPILE_DEFINITIONS LOGURU_STACKTRACES=$) liblsl-1.17.7/cmake/GitVersion.cmake000066400000000000000000000021761517625163100172600ustar00rootroot00000000000000# Try to find out which revision is currently checked out find_package(Git) if(lslgitrevision AND lslgitbranch) message(STATUS "Got git information ${lslgitrevision}/${lslgitbranch} from the command line") elseif(GIT_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} describe --tags HEAD WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE lslgitrevision OUTPUT_STRIP_TRAILING_WHITESPACE ) execute_process( COMMAND ${GIT_EXECUTABLE} rev-parse --symbolic-full-name --abbrev-ref @ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE lslgitbranch OUTPUT_STRIP_TRAILING_WHITESPACE ) message(STATUS "Git version information: ${lslgitbranch}/${lslgitrevision}") else() set(lslgitrevision "unknown") set(lslgitbranch "unknown") endif() # Generate version information string # that can be retrieved with the exported lsl_library_info() function set(LSL_VERSION_INFO "git:${lslgitrevision}/branch:${lslgitbranch}/build:${CMAKE_BUILD_TYPE}/compiler:${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}") liblsl-1.17.7/cmake/Installation.cmake000066400000000000000000000076611517625163100176340ustar00rootroot00000000000000# Skip installation when included as a subproject (controlled by LSL_INSTALL option) if(NOT LSL_INSTALL) return() endif() # Configure installation include(CMakePackageConfigHelpers) # Paths if(LSL_UNIXFOLDERS) include(GNUInstallDirs) set(CMAKE_INSTALL_FRAMEWORK_DIR ${FRAMEWORK_DIR_DEFAULT} CACHE PATH "Install directory for frameworks on macOS") else() set(CMAKE_INSTALL_BINDIR LSL) set(CMAKE_INSTALL_LIBDIR LSL) set(CMAKE_INSTALL_INCLUDEDIR LSL/include) set(CMAKE_INSTALL_FRAMEWORK_DIR LSL/Frameworks CACHE PATH "Install directory for frameworks on macOS") endif() # For Apple frameworks, we need to next the install directories within the framework. if(APPLE AND LSL_FRAMEWORK) # For the includes, this is insufficient. Later we will create more accessible symlinks. if(IOS) set(LSL_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/Headers) set(LSL_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/CMake) else() set(LSL_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/Versions/A/include) set(LSL_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_FRAMEWORK_DIR}/lsl.framework/Resources/CMake) endif() else() set(LSL_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}) set(LSL_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/lsl) endif() # Generate a version file for the package. write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/LSLConfigVersion.cmake" VERSION "${liblsl_VERSION_MAJOR}.${liblsl_VERSION_MINOR}.${liblsl_VERSION_PATCH}" COMPATIBILITY AnyNewerVersion ) # Define installation targets # Note: We only export the final 'lsl' library, not intermediate targets like lslobj. # Consumers should link to LSL::lsl, not internal object libraries. set(LSLTargets lsl) # Install the targets and store configuration information. install(TARGETS ${LSLTargets} EXPORT LSLTargets COMPONENT liblsl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} FRAMEWORK DESTINATION ${CMAKE_INSTALL_FRAMEWORK_DIR} ) # Unfortunately, `INCLUDES DESTINATION` does not work. # PUBLIC_HEADER does not work because it flattens the tree. # FILE_SET is preferable but does not work with frameworks. # So we are stuck manually specifying the headers to be installed. install(DIRECTORY include/lsl DESTINATION ${LSL_INSTALL_INCLUDEDIR}) install(FILES include/lsl_c.h include/lsl_cpp.h DESTINATION ${LSL_INSTALL_INCLUDEDIR}) # Generate the LSLConfig.cmake file and mark it for installation install(EXPORT LSLTargets FILE LSLConfig.cmake COMPONENT liblsl NAMESPACE "LSL::" DESTINATION ${LSL_CONFIG_INSTALL_DIR} ) # A common alternative to installing the exported package config file is to generate it from a template. #configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/lslConfig.cmake.in # ${CMAKE_CURRENT_BINARY_DIR}/LSLConfig.cmake # INSTALL_DESTINATION ${LSL_CONFIG_INSTALL_DIR}) # If we use this method, then we need a corresponding install(FILES ...) command to install the generated file. # Install the version file and the helper CMake script. install( FILES cmake/LSLCMake.cmake ${CMAKE_CURRENT_BINARY_DIR}/LSLConfigVersion.cmake COMPONENT liblsl DESTINATION ${LSL_CONFIG_INSTALL_DIR} ) if(APPLE AND LSL_FRAMEWORK AND NOT IOS) # Create symlinks for the framework. The variables we want to use to identify the symlink locations # are not available at install time. Instead, we create a script during configuration time that will # be run at install time to create the symlinks. configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CreateFrameworkSymlinks.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/CreateFrameworkSymlinks.cmake @ONLY ) install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/CreateFrameworkSymlinks.cmake COMPONENT liblsl) endif() liblsl-1.17.7/cmake/LSLCMake.cmake000066400000000000000000000456511517625163100165270ustar00rootroot00000000000000# ============================================================================= # LSLCMake.cmake - Common CMake utilities for LSL applications # ============================================================================= # This module provides helper functions for LSL applications. # # Functions: # LSL_get_target_arch() - Detect target architecture for package naming # LSL_get_os_name() - Detect OS name for package naming # LSL_configure_rpath() - Configure RPATH for all platforms # LSL_install_liblsl() - Install liblsl with the application # LSL_install_mingw_runtime() - Install MinGW runtime DLLs (Windows only) # LSL_deploy_qt() - Deploy Qt libraries with the application # LSL_codesign() - Sign macOS app bundles with entitlements # # Usage: # find_package(LSL REQUIRED) # include(LSLCMake) # Now available after finding LSL # # LSL_configure_rpath() # Call before creating targets # # ... create your targets ... # LSL_install_liblsl(DESTINATION "." FRAMEWORK_DESTINATION "MyApp.app/Contents/Frameworks") # LSL_install_mingw_runtime(DESTINATION ".") # ============================================================================= cmake_minimum_required(VERSION 3.28) message(STATUS "Included LSLCMake helpers, rev. 18") # ============================================================================= # LSL_get_target_arch() # ============================================================================= # Detects the target architecture by compiling a small test file. # Works correctly even when cross-compiling. # # Sets: LSL_ARCH (cached) - one of: arm64, arm, i386, amd64, ia64, ppc64, powerpc, unknown # # Example: # LSL_get_target_arch() # message(STATUS "Building for ${LSL_ARCH}") # ============================================================================= function(LSL_get_target_arch) if(LSL_ARCH) return() endif() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" " #if defined(__ARM_ARCH_ISA_A64) || defined(__aarch64__) #error cmake_ARCH arm64 #elif defined(__arm__) || defined(__TARGET_ARCH_ARM) #error cmake_ARCH arm #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) #error cmake_ARCH i386 #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) #error cmake_ARCH amd64 #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) #error cmake_ARCH ia64 #elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) #error cmake_ARCH ppc64 #else #error cmake_ARCH powerpc #endif #else #error cmake_ARCH unknown #endif") enable_language(C) try_compile(dummy_result "${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_BINARY_DIR}/arch.c" OUTPUT_VARIABLE ARCH) string(REGEX REPLACE ".*cmake_ARCH ([a-z0-9]+).*" "\\1" ARCH "${ARCH}") message(STATUS "LSL: Detected architecture: ${ARCH}") set(LSL_ARCH "${ARCH}" CACHE INTERNAL "Target architecture") endfunction() # ============================================================================= # LSL_get_os_name() # ============================================================================= # Detects the OS name for package naming. # On Linux, attempts to get the distribution codename (e.g., "jammy", "noble"). # # Sets: LSL_OS (cached) - e.g., "macOS", "Win", "jammy", "Linux" # # Example: # LSL_get_os_name() # set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${LSL_OS}_${LSL_ARCH}") # ============================================================================= function(LSL_get_os_name) if(LSL_OS) return() endif() if(APPLE) set(_os "macOS") elseif(WIN32) set(_os "Win") else() # Try to get Linux distribution codename find_program(LSB_RELEASE lsb_release) if(LSB_RELEASE) execute_process( COMMAND ${LSB_RELEASE} -cs OUTPUT_VARIABLE _codename OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) if(_codename AND NOT _codename STREQUAL "n/a") set(_os "${_codename}") else() set(_os "Linux") endif() else() set(_os "Linux") endif() endif() message(STATUS "LSL: Detected OS: ${_os}") set(LSL_OS "${_os}" CACHE INTERNAL "Target OS name") endfunction() # ============================================================================= # LSL_configure_rpath() # ============================================================================= # Configures RPATH for the current project. Must be called BEFORE creating targets. # # On macOS: Sets @executable_path-relative paths for both app bundles and CLI tools # On Linux: Sets $ORIGIN-relative paths # On Windows: No-op (Windows uses PATH / same-directory lookup) # # Example: # LSL_configure_rpath() # add_executable(MyApp main.cpp) # ============================================================================= function(LSL_configure_rpath) if(APPLE) # Support both: # - App bundles: @executable_path/../Frameworks # - CLI tools: @executable_path/Frameworks or @executable_path set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks" "@executable_path/Frameworks" "@executable_path" PARENT_SCOPE ) elseif(UNIX AND NOT ANDROID) set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/../lib" PARENT_SCOPE) endif() set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE PARENT_SCOPE) endfunction() # ============================================================================= # LSL_install_liblsl() # ============================================================================= # Installs liblsl alongside the application, handling platform differences: # - macOS: Copies lsl.framework to specified destination # - Windows: Copies lsl.dll # - Linux: Copies liblsl.so with proper symlinks # # Automatically detects whether liblsl was found via find_package (imported target) # or built via FetchContent (regular target) and handles each case appropriately. # # Arguments: # DESTINATION - Install destination for DLL/so (required for Windows/Linux) # FRAMEWORK_DESTINATION - Install destination for framework (required for macOS GUI apps) # # Example (GUI app): # LSL_install_liblsl( # DESTINATION "." # FRAMEWORK_DESTINATION "${PROJECT_NAME}.app/Contents/Frameworks" # ) # # Example (CLI app or Linux/Windows): # LSL_install_liblsl(DESTINATION "${CMAKE_INSTALL_LIBDIR}") # ============================================================================= function(LSL_install_liblsl) cmake_parse_arguments(ARG "" "DESTINATION;FRAMEWORK_DESTINATION" "" ${ARGN}) # Detect if liblsl is from FetchContent (regular target) or find_package (imported) set(_lsl_is_fetched FALSE) if(TARGET lsl) get_target_property(_lsl_imported lsl IMPORTED) if(NOT _lsl_imported) set(_lsl_is_fetched TRUE) endif() endif() if(APPLE) if(NOT ARG_FRAMEWORK_DESTINATION AND NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_install_liblsl: FRAMEWORK_DESTINATION or DESTINATION required on macOS") endif() # Determine destination - prefer FRAMEWORK_DESTINATION for GUI apps set(_fw_dest "${ARG_FRAMEWORK_DESTINATION}") if(NOT _fw_dest) set(_fw_dest "${ARG_DESTINATION}") endif() # Use install(CODE) with generator expressions for FetchContent compatibility install(CODE " set(_lsl_binary \"$\") cmake_path(GET _lsl_binary PARENT_PATH _lsl_fw_dir) # Versions/A cmake_path(GET _lsl_fw_dir PARENT_PATH _lsl_fw_dir) # Versions cmake_path(GET _lsl_fw_dir PARENT_PATH _lsl_fw_dir) # lsl.framework message(STATUS \"LSL: Bundling lsl.framework from: \${_lsl_fw_dir}\") file(COPY \"\${_lsl_fw_dir}\" DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${_fw_dest}\" USE_SOURCE_PERMISSIONS ) ") elseif(WIN32) if(NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_install_liblsl: DESTINATION required on Windows") endif() if(_lsl_is_fetched) install(TARGETS lsl RUNTIME DESTINATION "${ARG_DESTINATION}") else() install(IMPORTED_RUNTIME_ARTIFACTS LSL::lsl RUNTIME DESTINATION "${ARG_DESTINATION}") endif() else() # Linux if(NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_install_liblsl: DESTINATION required on Linux") endif() if(_lsl_is_fetched) install(TARGETS lsl LIBRARY DESTINATION "${ARG_DESTINATION}") else() install(IMPORTED_RUNTIME_ARTIFACTS LSL::lsl LIBRARY DESTINATION "${ARG_DESTINATION}") endif() endif() endfunction() # ============================================================================= # LSL_install_mingw_runtime() # ============================================================================= # Installs MinGW runtime DLLs so executables work outside the build environment. # Only has effect when building with MinGW; no-op on other compilers. # # Arguments: # DESTINATION - Install destination for DLLs (required) # # Example: # LSL_install_mingw_runtime(DESTINATION ".") # ============================================================================= function(LSL_install_mingw_runtime) if(NOT MINGW) return() endif() cmake_parse_arguments(ARG "" "DESTINATION" "" ${ARGN}) if(NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_install_mingw_runtime: DESTINATION required") endif() get_filename_component(MINGW_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) set(MINGW_RUNTIME_DLLS "${MINGW_BIN_DIR}/libgcc_s_seh-1.dll" "${MINGW_BIN_DIR}/libstdc++-6.dll" "${MINGW_BIN_DIR}/libwinpthread-1.dll" ) foreach(_dll ${MINGW_RUNTIME_DLLS}) if(EXISTS "${_dll}") install(FILES "${_dll}" DESTINATION "${ARG_DESTINATION}") endif() endforeach() endfunction() # ============================================================================= # LSL_deploy_qt() # ============================================================================= # Deploys Qt libraries alongside the application using platform-specific tools. # - Windows: Uses windeployqt to copy Qt DLLs and plugins # - macOS: Uses macdeployqt to bundle Qt frameworks # - Linux: No-op (Qt libraries typically come from system packages) # # Requires Qt6::qmake target to be available (from find_package(Qt6)). # # Arguments: # TARGET - Target name (without .exe or .app extension) # DESTINATION - Install destination directory # # Example: # LSL_deploy_qt(TARGET "${PROJECT_NAME}" DESTINATION ".") # ============================================================================= function(LSL_deploy_qt) cmake_parse_arguments(ARG "" "TARGET;DESTINATION" "" ${ARGN}) if(NOT ARG_TARGET) message(FATAL_ERROR "LSL_deploy_qt: TARGET required") endif() if(NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_deploy_qt: DESTINATION required") endif() if(NOT TARGET Qt6::qmake) message(WARNING "LSL_deploy_qt: Qt6::qmake not found, skipping Qt deployment") return() endif() get_target_property(_qt_qmake_executable Qt6::qmake IMPORTED_LOCATION) get_filename_component(_qt_bin_dir "${_qt_qmake_executable}" DIRECTORY) if(WIN32) find_program(_windeployqt_executable windeployqt HINTS "${_qt_bin_dir}") if(_windeployqt_executable) install(CODE " message(STATUS \"Running windeployqt...\") execute_process( COMMAND \"${_windeployqt_executable}\" --no-translations --no-system-d3d-compiler --no-opengl-sw --no-compiler-runtime --dir \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}\" \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${ARG_TARGET}.exe\" ) ") else() message(WARNING "LSL_deploy_qt: windeployqt not found") endif() elseif(APPLE) find_program(_macdeployqt_executable macdeployqt HINTS "${_qt_bin_dir}") if(_macdeployqt_executable) install(CODE " message(STATUS \"Running macdeployqt...\") execute_process( COMMAND \"${_macdeployqt_executable}\" \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${ARG_TARGET}.app\" -verbose=0 -always-overwrite RESULT_VARIABLE _deploy_result ERROR_QUIET ) if(NOT _deploy_result EQUAL 0) message(WARNING \"macdeployqt returned \${_deploy_result}\") endif() ") else() message(WARNING "LSL_deploy_qt: macdeployqt not found") endif() endif() # Linux: No-op - Qt libraries typically installed via system packages endfunction() # ============================================================================= # LSL_codesign() # ============================================================================= # Signs macOS app bundles or executables with entitlements for ad-hoc local development. # Uses the "-" identity (ad-hoc signing) which doesn't require a Developer ID certificate. # # For release builds with proper signing/notarization, use a separate CI script # with an actual Developer ID certificate. # # Arguments: # TARGET - Target name (without .app extension for bundles) # DESTINATION - Install destination directory # ENTITLEMENTS - Path to entitlements file (required) # BUNDLE - If set, signs as app bundle (.app), otherwise signs as executable # FRAMEWORK - Optional: Path to framework to sign before the executable (for CLI apps) # # Example (GUI app bundle): # LSL_codesign( # TARGET "${PROJECT_NAME}" # DESTINATION "${INSTALL_BINDIR}" # ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements" # BUNDLE # ) # # Example (CLI executable with framework): # LSL_codesign( # TARGET "${PROJECT_NAME}CLI" # DESTINATION "${INSTALL_BINDIR}" # ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/app.entitlements" # FRAMEWORK "Frameworks/lsl.framework" # ) # ============================================================================= function(LSL_codesign) if(NOT APPLE) return() endif() cmake_parse_arguments(ARG "BUNDLE" "TARGET;DESTINATION;ENTITLEMENTS;FRAMEWORK" "" ${ARGN}) if(NOT ARG_TARGET) message(FATAL_ERROR "LSL_codesign: TARGET required") endif() if(NOT ARG_DESTINATION) message(FATAL_ERROR "LSL_codesign: DESTINATION required") endif() if(NOT ARG_ENTITLEMENTS) message(FATAL_ERROR "LSL_codesign: ENTITLEMENTS required") endif() if(ARG_BUNDLE) # Sign app bundle install(CODE " set(_app \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${ARG_TARGET}.app\") set(_ent \"${ARG_ENTITLEMENTS}\") message(STATUS \"Signing app bundle...\") execute_process( COMMAND codesign --force --deep --sign - --entitlements \"\${_ent}\" \"\${_app}\" RESULT_VARIABLE _sign_result ) execute_process(COMMAND codesign --verify --verbose \"\${_app}\" RESULT_VARIABLE _verify_result) if(_verify_result EQUAL 0) message(STATUS \"App bundle signature verified successfully\") else() message(WARNING \"App bundle signature verification failed!\") endif() ") else() # Sign executable (and optionally framework first) install(CODE " set(_exe \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${ARG_TARGET}\") set(_ent \"${ARG_ENTITLEMENTS}\") # Sign framework first if specified if(NOT \"${ARG_FRAMEWORK}\" STREQUAL \"\") set(_fw \"\${CMAKE_INSTALL_PREFIX}/${ARG_FRAMEWORK}\") message(STATUS \"Signing framework: \${_fw}\") execute_process(COMMAND codesign --force --sign - \"\${_fw}\") endif() message(STATUS \"Signing executable: \${_exe}\") execute_process( COMMAND codesign --force --sign - --entitlements \"\${_ent}\" \"\${_exe}\" RESULT_VARIABLE _sign_result ) ") endif() endfunction() # ============================================================================= # DEPRECATED FUNCTIONS # ============================================================================= # The following functions are deprecated and will be removed in a future version. # They are kept for backward compatibility with existing liblsl builds. # New applications should use the modern functions above. # ============================================================================= # Deprecated: Use standard install() commands instead function(installLSLApp target) message(DEPRECATION "installLSLApp() is deprecated. Use standard CMake install() commands.") # Basic install for the target include(GNUInstallDirs) install(TARGETS ${target} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} ) endfunction() # Deprecated: Use standard install(FILES/DIRECTORY) instead function(installLSLAuxFiles target) message(DEPRECATION "installLSLAuxFiles() is deprecated. Use standard CMake install() commands.") include(GNUInstallDirs) set(destdir "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}") if("${ARGV1}" STREQUAL "directory") install(DIRECTORY ${ARGV2} DESTINATION ${destdir}) else() install(FILES ${ARGN} DESTINATION ${destdir}) endif() endfunction() # Deprecated: Apps should configure CPack themselves macro(LSLGenerateCPackConfig) message(DEPRECATION "LSLGenerateCPackConfig() is deprecated. Configure CPack directly in your CMakeLists.txt.") LSL_get_target_arch() LSL_get_os_name() set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") if(NOT CPACK_PACKAGE_VENDOR) set(CPACK_PACKAGE_VENDOR "Labstreaminglayer") endif() set(CPACK_STRIP_FILES ON) if(APPLE) set(CPACK_GENERATOR TGZ) elseif(WIN32) set(CPACK_GENERATOR ZIP) else() set(CPACK_GENERATOR DEB TGZ) if(NOT CPACK_DEBIAN_PACKAGE_MAINTAINER) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "LabStreamingLayer Developers") endif() set(CPACK_DEBIAN_PACKAGE_SECTION "science") endif() set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${LSL_OS}_${LSL_ARCH}") include(CPack) endmacro() liblsl-1.17.7/cmake/ProjectOptions.cmake000066400000000000000000000031061517625163100201430ustar00rootroot00000000000000include(CMakeDependentOption) # Project build options # Default LSL_DEBUGLOG to ON for Debug builds (single-config generators only) string(COMPARE EQUAL "${CMAKE_BUILD_TYPE}" "Debug" _LSL_DEBUGLOG_DEFAULT) option(LSL_DEBUGLOG "Enable (lots of) additional debug messages" ${_LSL_DEBUGLOG_DEFAULT}) option(LSL_UNIXFOLDERS "Use the unix folder layout for install targets" ON) option(LSL_BUILD_STATIC "Build LSL as a static library." OFF) option(LSL_FRAMEWORK "Build LSL as an Apple Framework (Mac only)" ON) option(LSL_LEGACY_CPP_ABI "Build legacy C++ ABI into lsl-static" OFF) option(LSL_OPTIMIZATIONS "Enable some more compiler optimizations" ON) option(LSL_BUNDLED_BOOST "Use the bundled Boost by default" ON) option(LSL_FETCH_PUGIXML "Fetch pugixml via FetchContent instead of using system package" ON) cmake_dependent_option(LSL_TOOLS "Build some experimental tools for in-depth tests" OFF "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) cmake_dependent_option(LSL_UNITTESTS "Build LSL library unit tests" OFF "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) cmake_dependent_option(LSL_INSTALL "Generate install targets" ON "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) option(LSL_FORCE_FANCY_LIBNAME "Add library name decorations (32/64/-debug)" OFF) mark_as_advanced(LSL_FORCE_FANCY_LIBNAME) # If we install to the system then we want the framework to land in # `Library/Frameworks`, otherwise (e.g., Homebrew) we want `Frameworks` if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(FRAMEWORK_DIR_DEFAULT Library/Frameworks) else() set(FRAMEWORK_DIR_DEFAULT Frameworks) endif() liblsl-1.17.7/cmake/SourceFiles.cmake000066400000000000000000000043171517625163100174110ustar00rootroot00000000000000set(lslsources src/api_config.cpp src/api_config.h src/api_types.hpp src/cancellable_streambuf.h src/cancellation.h src/cancellation.cpp src/common.cpp src/common.h src/consumer_queue.cpp src/consumer_queue.h src/data_receiver.cpp src/data_receiver.h src/forward.h src/info_receiver.cpp src/info_receiver.h src/inlet_connection.cpp src/inlet_connection.h src/lsl_resolver_c.cpp src/lsl_inlet_c.cpp src/lsl_outlet_c.cpp src/lsl_streaminfo_c.cpp src/lsl_xml_element_c.cpp src/netinterfaces.h src/netinterfaces.cpp src/portable_archive/portable_archive_exception.hpp src/portable_archive/portable_archive_includes.hpp src/portable_archive/portable_iarchive.hpp src/portable_archive/portable_oarchive.hpp src/resolver_impl.cpp src/resolver_impl.h src/resolve_attempt_udp.cpp src/resolve_attempt_udp.h src/sample.cpp src/sample.h src/send_buffer.cpp src/send_buffer.h src/socket_utils.cpp src/socket_utils.h src/stream_info_impl.cpp src/stream_info_impl.h src/stream_inlet_impl.h src/stream_outlet_impl.cpp src/stream_outlet_impl.h src/tcp_server.cpp src/tcp_server.h src/time_postprocessor.cpp src/time_postprocessor.h src/time_receiver.cpp src/time_receiver.h src/udp_server.cpp src/udp_server.h src/util/cast.hpp src/util/cast.cpp src/util/endian.cpp src/util/endian.hpp src/util/inireader.hpp src/util/inireader.cpp src/util/strfuns.hpp src/util/strfuns.cpp src/util/uuid.hpp thirdparty/loguru/loguru.cpp $<$:src/legacy/legacy_abi.cpp src/legacy/legacy_abi.h> ) set(lslheaders include/lsl_c.h include/lsl_cpp.h include/lsl/common.h include/lsl/inlet.h include/lsl/outlet.h include/lsl/resolver.h include/lsl/streaminfo.h include/lsl/types.h include/lsl/xml.h )liblsl-1.17.7/cmake/TargetLib.cmake000066400000000000000000000104621517625163100170410ustar00rootroot00000000000000# Create main library # It contains one source with the version info string because some generators require it. # The remaining source code is built in the lslobj target and later linked into this library. set_source_files_properties("src/buildinfo.cpp" PROPERTIES COMPILE_DEFINITIONS LSL_LIBRARY_INFO_STR="${LSL_VERSION_INFO}/link:${LSL_LIB_TYPE}" ) add_library(lsl ${LSL_LIB_TYPE} src/buildinfo.cpp) add_library(LSL::lsl ALIAS lsl) set_target_properties(lsl PROPERTIES FOLDER "liblsl") # Configure main library # Some naming metadata for export set_target_properties(lsl PROPERTIES VERSION ${liblsl_VERSION_MAJOR}.${liblsl_VERSION_MINOR}.${liblsl_VERSION_PATCH} SOVERSION ${LSL_ABI_VERSION} ) if(LSL_FORCE_FANCY_LIBNAME) math(EXPR lslplatform "8 * ${CMAKE_SIZEOF_VOID_P}") set_target_properties(lsl PROPERTIES PREFIX "" OUTPUT_NAME "liblsl${lslplatform}" DEBUG_POSTFIX "-debug" ) endif() # Link dependencies. The only dependency is lslobj, which contains the bulk of the library code and linkages. # Note: We link PRIVATE because lslobj exposes extra symbols that are not part of the public API # but are used by the internal tests. # Note: We use BUILD_INTERFACE to avoid requiring lslobj in the export set - the object library's # objects are linked directly into lsl, so consumers don't need lslobj. target_link_libraries(lsl PRIVATE $) # Set the include directories for the lsl target. # Note: We had to link lslobj as a PRIVATE dependency, therefore we must manually expose the include directories if(APPLE AND LSL_FRAMEWORK) # For frameworks, the install interface needs to point into the framework bundle if(LSL_UNIXFOLDERS) set(LSL_INSTALL_INTERFACE_INCLUDE_DIR "${FRAMEWORK_DIR_DEFAULT}/lsl.framework/Versions/A/Headers") else() set(LSL_INSTALL_INTERFACE_INCLUDE_DIR "LSL/Frameworks/lsl.framework/Versions/A/Headers") endif() else() set(LSL_INSTALL_INTERFACE_INCLUDE_DIR "include") endif() target_include_directories(lsl PUBLIC $ $ ) # Set compile definitions for lsl target_compile_definitions(lsl PUBLIC # defines for LSL_CPP_API export header (shared: dllimport/dllexport) $,LIBLSL_STATIC,LIBLSL_EXPORTS> # don't use #pragma(lib) in MSVC builds. TODO: Maybe this can be inherited from lslobj or removed on lslobj? $<$:LSLNOAUTOLINK> ) # Extra configuration for Apple targets -- set xcode attributes if(APPLE) set_target_properties(lsl PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.labstreaminglayer.liblsl" ) # If environment variables are set for Apple Development Team and Code Sign Identity then add these to the target # -> if `-G Xcode` generator is used then Xcode will use these variables to sign the framework. # Note, however, that it is likely that the build products will be modified post-build, invalidating the signature, # so post-hoc signing will be required. Nevertheless, this is useful for initial signing and normal Xcode workflow. if(DEFINED ENV{APPLE_DEVELOPMENT_TEAM} AND DEFINED ENV{APPLE_CODE_SIGN_IDENTITY_APP}) set_target_properties(lsl PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY $ENV{APPLE_CODE_SIGN_IDENTITY_APP} XCODE_ATTRIBUTE_DEVELOPMENT_TEAM $ENV{APPLE_DEVELOPMENT_TEAM} XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual" XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING YES # this is needed for strip symbols XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep" ) endif() # Configure Apple Framework if(LSL_FRAMEWORK) set_target_properties(lsl PROPERTIES FRAMEWORK TRUE FRAMEWORK_VERSION A # Ignored on iOS MACOSX_FRAMEWORK_IDENTIFIER "org.labstreaminglayer.liblsl" MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${liblsl_VERSION_MAJOR}.${liblsl_VERSION_MINOR}" MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION} XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/lsl.entitlements" ) endif(LSL_FRAMEWORK) endif(APPLE) liblsl-1.17.7/cmake/TargetObjLib.cmake000066400000000000000000000072071517625163100174770ustar00rootroot00000000000000# Create object library so all files are only compiled once add_library(lslobj OBJECT ${lslsources} ${lslheaders} ) set_target_properties(lslobj PROPERTIES FOLDER "liblsl") # Set the includes/headers for the lslobj target # Note: We cannot use PUBLIC_HEADER because it flattens the include tree upon install # Note: We cannot use FILE_SET because it is not compatible with HEADERS. target_include_directories(lslobj PUBLIC $ $ # For targets that link to the installed lslobj ) # Link system libs # (boost might be bundled or system) target_link_libraries(lslobj PRIVATE lslboost Threads::Threads) if(MINGW) target_link_libraries(lslobj PRIVATE bcrypt) endif() if(UNIX AND NOT APPLE) # check that clock_gettime is present in the stdlib, link against librt otherwise include(CheckSymbolExists) check_symbol_exists(clock_gettime time.h HAS_GETTIME) if(NOT HAS_GETTIME) target_link_libraries(lslobj PRIVATE rt) endif() if(LSL_DEBUGLOG) target_link_libraries(lslobj PRIVATE dl) endif() elseif(WIN32) target_link_libraries(lslobj PRIVATE iphlpapi winmm mswsock ws2_32) endif() # Compiler settings target_compile_definitions(lslobj PRIVATE LIBLSL_EXPORTS LOGURU_DEBUG_LOGGING=$ # Prevent asio from overriding CMAKE_CXX_VISIBILITY_PRESET with its own # #pragma GCC visibility push(default). This ensures asio symbols stay # hidden in the final shared library, avoiding ODR violations when # applications also use standalone asio. ASIO_DISABLE_VISIBILITY PUBLIC ASIO_NO_DEPRECATED $<$:LSLNOAUTOLINK> # don't use #pragma(lib) in CMake builds ) if(WIN32) target_compile_definitions(lslobj PRIVATE _CRT_SECURE_NO_WARNINGS PUBLIC _WIN32_WINNT=${LSL_WINVER} ) if(BUILD_SHARED_LIBS) # set_target_properties(lslobj # PROPERTIES # WINDOWS_EXPORT_ALL_SYMBOLS ON # ) endif(BUILD_SHARED_LIBS) endif() # Link in 3rd party dependencies # - loguru and asio header-only target_include_directories(lslobj # Note: We use `SYSTEM` to suppress warnings from 3rd party headers and put these at the end of the include path. # Note: We use `PUBLIC` because 'internal tests' import individual source files and link lslobj. SYSTEM PUBLIC $ $ ) if(NOT LSL_OPTIMIZATIONS) # build one object file for Asio instead of once every time an Asio function is called. See # https://think-async.com/Asio/asio-1.18.2/doc/asio/using.html#asio.using.optional_separate_compilation target_sources(lslobj PRIVATE thirdparty/asio_objects.cpp) target_compile_definitions(lslobj PUBLIC ASIO_SEPARATE_COMPILATION) endif() # - pugixml (either fetched via FetchContent or system package) if(LSL_PUGIXML_IS_FETCHED) # Fetched pugixml is always static - use BUILD_INTERFACE to avoid requiring # pugixml in the export set since the static library objects are linked # into lsl directly. target_link_libraries(lslobj PRIVATE $) # Hide pugixml symbols from the shared library on Linux if(UNIX AND NOT APPLE) target_link_options(lslobj PRIVATE "LINKER:--exclude-libs,libpugixml.a") endif() else() # System pugixml may be shared or static target_link_libraries(lslobj PRIVATE pugixml::pugixml) endif() liblsl-1.17.7/cmake/TargetOther.cmake000066400000000000000000000015421517625163100174130ustar00rootroot00000000000000# Build utilities (only when liblsl is the top-level project) if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) include(GNUInstallDirs) add_executable(lslver testing/lslver.c) target_link_libraries(lslver PRIVATE lsl) set_target_properties(lslver PROPERTIES FOLDER "liblsl") if(LSL_INSTALL AND NOT IOS) install(TARGETS lslver RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() if(NOT WIN32 AND LSL_TOOLS) add_executable(blackhole testing/blackhole.cpp) target_link_libraries(blackhole PRIVATE Threads::Threads) target_include_directories(blackhole PRIVATE "thirdparty/asio/") set_target_properties(blackhole PROPERTIES FOLDER "liblsl") if(LSL_INSTALL AND NOT IOS) install(TARGETS blackhole RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() endif() endif() liblsl-1.17.7/cmake/toolchains/000077500000000000000000000000001517625163100163225ustar00rootroot00000000000000liblsl-1.17.7/cmake/toolchains/README.md000066400000000000000000000016421517625163100176040ustar00rootroot00000000000000# CMake toolchains To build locally for ARM64: ```bash cmake -S . -B build-arm64 \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/aarch64-linux-gnu.cmake \ -DCMAKE_BUILD_TYPE=Release cmake --build build-arm64 ``` To build locally for ARMv7: ```bash cmake -S . -B build-armv7 \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/arm-linux-gnueabihf.cmake \ -DCMAKE_BUILD_TYPE=Release cmake --build build-armv7 ``` It is also possible to cross-compile from other hosts but the toolchain files may need to be adjusted accordingly. ## Testing Cross-Compiled Binaries With QEMU: ```bash # Install QEMU sudo apt-get install qemu-user-static # Run ARM64 binary qemu-aarch64-static -L /usr/aarch64-linux-gnu ./build-arm64/your-binary # Run ARMv7 binary qemu-arm-static -L /usr/arm-linux-gnueabihf ./build-armv7/your-binary ``` ## NVIDIA Jetson The ARM64 build (`aarch64-linux-gnu.cmake`) binaries should be compatible with NVIDIA Jetson. liblsl-1.17.7/cmake/toolchains/aarch64-linux-gnu.cmake000066400000000000000000000016401517625163100225010ustar00rootroot00000000000000# CMake toolchain file for cross-compiling to ARM64/aarch64 # Compatible with NVIDIA Jetson, Raspberry Pi 4/5, and other ARM64 Linux systems set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) # Specify the cross compiler set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) # Where to look for the target environment set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) # Search for programs in the build host directories set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # Search for libraries and headers in the target directories set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) # Set additional compiler flags for better compatibility set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a" CACHE STRING "C flags") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a" CACHE STRING "C++ flags") liblsl-1.17.7/cmake/toolchains/arm-linux-gnueabihf.cmake000066400000000000000000000017611517625163100231730ustar00rootroot00000000000000# CMake toolchain file for cross-compiling to ARMv7 (32-bit ARM with hard float) # Compatible with Raspberry Pi 2/3 and other ARMv7 Linux systems set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR armv7l) # Specify the cross compiler set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) # Where to look for the target environment set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf) # Search for programs in the build host directories set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # Search for libraries and headers in the target directories set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) # Set additional compiler flags for ARMv7 with NEON support set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard" CACHE STRING "C flags") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard" CACHE STRING "C++ flags") liblsl-1.17.7/conda/000077500000000000000000000000001517625163100141635ustar00rootroot00000000000000liblsl-1.17.7/conda/meta.yaml000066400000000000000000000014141517625163100157750ustar00rootroot00000000000000# Conda build recipe for liblsl # {% set name = "liblsl" %} package: name: {{ name|lower }} version: {{ GIT_DESCRIBE_TAG | replace('-', '.') }} source: git_url: .. build: number: {{ GIT_DESCRIBE_NUMBER }} string: {{ GIT_BUILD_STR }} script: - mkdir build - 'cmake -DLSL_UNIXFOLDERS=1 -DCMAKE_INSTALL_PREFIX:PATH="{{ PREFIX }}" -DCMAKE_INSTALL_RPATH:PATH="{{ PREFIX }}/lib" -S . -B build -G Ninja' - 'cmake --build build -j --target install' requirements: build: - cmake >=3.12 - {{ compiler("cxx") }} - git - ninja host: tests: commands: - lslver about: home: https://github.com/sccn/liblsl license: MIT license_family: MIT license_file: LICENSE summary: 'Multi-modal time-synched data transmission over local network' liblsl-1.17.7/docs/000077500000000000000000000000001517625163100140275ustar00rootroot00000000000000liblsl-1.17.7/docs/Doxyfile000066400000000000000000000021111517625163100155300ustar00rootroot00000000000000# Doxyfile 1.8.13 DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = liblsl_internal PROJECT_NUMBER = 1.12 PROJECT_BRIEF = "Multi-modal time-synched data transmission over local network" OUTPUT_DIRECTORY = liblsl_internal FULL_PATH_NAMES = NO DISTRIBUTE_GROUP_DOC = YES INLINE_SIMPLE_STRUCTS = YES EXTRACT_ALL = YES SHOW_NAMESPACES = NO INPUT = ../src \ ../include FILE_PATTERNS = *.c \ *.cpp \ *.h *.hpp EXCLUDE_SYMBOLS = lslboost SOURCE_BROWSER = YES CLANG_ASSISTED_PARSING = NO CLANG_OPTIONS = -I../lslboost ALPHABETICAL_INDEX = NO HTML_DYNAMIC_SECTIONS = YES GENERATE_TREEVIEW = YES GENERATE_QHP = YES QCH_FILE = lsl.qch QHP_NAMESPACE = edu.sccn.labstreaminglayer.liblsl QHG_LOCATION = GENERATE_XML = NO CLASS_DIAGRAMS = NO COLLABORATION_GRAPH = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES DOT_IMAGE_FORMAT = svg liblsl-1.17.7/docs/Doxyfile_API000066400000000000000000000022521517625163100162270ustar00rootroot00000000000000# Doxyfile 1.8.13 DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = liblsl_api PROJECT_NUMBER = 1.14 PROJECT_BRIEF = "Multi-modal time-synched data transmission over local network" OUTPUT_DIRECTORY = liblsl_api FULL_PATH_NAMES = NO DISTRIBUTE_GROUP_DOC = YES INLINE_SIMPLE_STRUCTS = YES WARN_IF_UNDOCUMENTED = NO SHOW_NAMESPACES = NO INPUT = ../include ../include/lsl/ JAVADOC_AUTOBRIEF = YES FILE_PATTERNS = *.h EXCLUDE_SYMBOLS = lslboost SOURCE_BROWSER = YES CLANG_ASSISTED_PARSING = NO CLANG_OPTIONS = -I../lslboost MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES ENABLE_PREPROCESSING = YES PREDEFINED = LIBLSL_C_API LSL_DOXYGEN ALPHABETICAL_INDEX = NO HTML_DYNAMIC_SECTIONS = YES GENERATE_TREEVIEW = YES GENERATE_QHP = YES QCH_FILE = lsl.qch QHP_NAMESPACE = edu.sccn.labstreaminglayer.liblsl QHG_LOCATION = GENERATE_XML = YES GENERATE_LATEX = NO CLASS_DIAGRAMS = NO COLLABORATION_GRAPH = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES DOT_IMAGE_FORMAT = svg liblsl-1.17.7/docs/Makefile000066400000000000000000000011721517625163100154700ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) liblsl-1.17.7/docs/conf.py000066400000000000000000000026331517625163100153320ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # -- Project information ----------------------------------------------------- import subprocess, os project = 'liblsl' copyright = '2019, Christian Kothe, MIT' author = 'Christian Kothe, David Medine, Chadwick Boulay, Matthew Grivich, Tristan Stenner' # The full version, including alpha/beta/rc tags try: gitver = subprocess.run(['git', 'describe', '--tags', 'HEAD'], capture_output=True) release = gitver.stdout.strip().decode() except: release = '1.14' # -- General configuration --------------------------------------------------- extensions = [ 'breathe', 'sphinx.ext.intersphinx', ] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] master_doc = 'index' # for Sphinx < 2.0 # html_static_path = ['_static'] breathe_projects = { 'liblsl': 'liblsl_api/xml/'} breathe_default_project = 'liblsl' # The reST default role (used for this markup: `text`) to use for all documents. default_role = 'cpp:any' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True highlight_language = 'c++' primary_domain = 'cpp' # intersphinx intersphinx_mapping = { 'lsl': ('https://labstreaminglayer.readthedocs.io/', None), } read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' if read_the_docs_build: subprocess.call(['doxygen', 'Doxyfile_API']) liblsl-1.17.7/docs/index.rst000066400000000000000000000006771517625163100157020ustar00rootroot00000000000000liblsl documentation ==================== This is the in-progress API documentation for liblsl. The complete labstreaminglayer documentation with concepts, a :doc:`quickstart guide ` and links to precompiled packages is at the :doc:`lsl:index`. .. toctree:: :maxdepth: 1 :caption: API reference: :glob: ref/* Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` liblsl-1.17.7/docs/make.bat000066400000000000000000000014331517625163100154350ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd liblsl-1.17.7/docs/ref/000077500000000000000000000000001517625163100146035ustar00rootroot00000000000000liblsl-1.17.7/docs/ref/enums.rst000066400000000000000000000003271517625163100164660ustar00rootroot00000000000000Enumerations / Flags ==================== Postprocessing options: .. doxygenenum:: lsl_processing_options_t Channel formats: .. doxygenenum:: lsl_channel_format_t Error codes: .. doxygenenum:: lsl_error_code_tliblsl-1.17.7/docs/ref/freefuncs.rst000066400000000000000000000005421517625163100173160ustar00rootroot00000000000000LSL freestanding functions ========================== .. doxygenfunction:: lsl_local_clock Here we talk about `lsl_time_correction_ex`: .. doxygenfunction:: lsl_time_correction_ex() And here we list some other functions: .. doxygenfunction:: lsl_library_version() .. doxygenfunction:: lsl_library_info() .. doxygenfunction:: lsl_protocol_version() liblsl-1.17.7/docs/ref/inlet.rst000066400000000000000000000002321517625163100164450ustar00rootroot00000000000000Stream Inlets ============= Another text here :term:`lsl:Stream Inlet` Doxygen output -------------- .. doxygenclass:: lsl::stream_inlet :members: liblsl-1.17.7/docs/ref/outlet.rst000066400000000000000000000002271517625163100166520ustar00rootroot00000000000000Stream Outlets ============== Text here :term:`lsl:Stream Outlet` Doxygen output -------------- .. doxygenclass:: lsl::stream_outlet :members: liblsl-1.17.7/docs/ref/streaminfo.rst000066400000000000000000000002501517625163100175010ustar00rootroot00000000000000Stream Info =========== A stream info object stores the stream's :term:`lsl:Metadata`. Doxygen output -------------- .. doxygenclass:: lsl::stream_info :members: liblsl-1.17.7/docs/requirements.txt000066400000000000000000000000101517625163100173020ustar00rootroot00000000000000breathe liblsl-1.17.7/examples/000077500000000000000000000000001517625163100147155ustar00rootroot00000000000000liblsl-1.17.7/examples/CMakeLists.txt000066400000000000000000000076251517625163100174670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.23) project(lslexamples LANGUAGES C CXX VERSION 0.2.0) include(GNUInstallDirs) set(LSL_SEARCH_PATHS ${LSL_INSTALL_ROOT} "${CMAKE_CURRENT_LIST_DIR}/../install" # GHA scripts default install directory "${CMAKE_CURRENT_LIST_DIR}/../cmake-build-release/install" # CLion default if using -DCMAKE_INSTALL_PREFIX=install ) if(APPLE) # Also search in a Frameworks subdirectory for each path foreach(p IN LISTS LSL_SEARCH_PATHS) if(p) list(APPEND LSL_SEARCH_PATHS "${p}/Frameworks") endif() endforeach() else() # Add MSVC-specific paths if not on Apple list(APPEND LSL_SEARCH_PATHS "${CMAKE_CURRENT_LIST_DIR}/../cmake-build-release-visual-studio/install" # CLion default if using VS compiler "${CMAKE_CURRENT_LIST_DIR}/../out/build/x64-Release/install" # MSVC default if using -DCMAKE_INSTALL_PREFIX=install ) endif() find_package(LSL REQUIRED HINTS ${LSL_SEARCH_PATHS} PATH_SUFFIXES share/LSL ) get_filename_component(LSL_PATH ${LSL_CONFIG} DIRECTORY) message(STATUS "Found LSL lib in ${LSL_PATH}") # Include the LSLCMake.cmake file, just for testing. # This doesn't do much for us now that we don't use installLSLApp() anymore. include("${LSL_PATH}/LSLCMake.cmake") # convenience function to add an example file # this creates a target, links the necessary libraries and # creates an install target function(addlslexample name extension) add_executable(${name} ${name}.${extension} ) target_link_libraries(${name} PRIVATE LSL::lsl) target_compile_features(${name} PRIVATE cxx_constexpr) # Set RPATH properties for macOS and Linux set_target_properties(${name} PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE BUILD_WITH_INSTALL_RPATH FALSE ) # One might also want to do the following, to give the option of copying the # LSL library to the same directory or install tree as the executable. # However, this is not necessary for the examples, as they are not intended to be relocated. # if(APPLE) # set_target_properties(${name} PROPERTIES # INSTALL_RPATH "@loader_path;@loader_path/../lib;@loader_path/../Frameworks" # ) # elseif(UNIX) # set_target_properties(${name} PROPERTIES # INSTALL_RPATH "$ORIGIN:$ORIGIN/../lib" # ) # endif() install(TARGETS ${name} COMPONENT ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) endfunction() find_package(Threads) addlslexample(GetAllStreams cpp) addlslexample(GetFullinfo cpp) addlslexample(GetTimeCorrection cpp) addlslexample(HandleMetaData cpp) addlslexample(HandleMetaDataC c) addlslexample(ReceiveData cpp) addlslexample(ReceiveDataC c) addlslexample(ReceiveDataInChunks cpp) addlslexample(ReceiveDataSimple cpp) addlslexample(ReceiveStringMarkers cpp) addlslexample(ReceiveStringMarkersC c) addlslexample(SendData cpp) addlslexample(SendDataC c) addlslexample(SendDataInChunks cpp) addlslexample(SendDataSimple cpp) addlslexample(SendMultipleStreams cpp) addlslexample(SendStringMarkers cpp) addlslexample(SendStringMarkersC c) addlslexample(TestSyncWithoutData cpp) target_link_libraries(TestSyncWithoutData PRIVATE Threads::Threads) # Windows doesn't have RPATH so we put the dll into the same directory as the executable. if(WIN32) # For one of the targets, copy the lsl.dll into the build directory so we can debug the examples if needed. add_custom_command(TARGET HandleMetaData POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" $ COMMENT "Copying lsl.dll to examples build directory" ) # Install the lsl.dll to the same directory as the executable. install( CODE "file(INSTALL DESTINATION \"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}\" TYPE SHARED_LIBRARY FILES \"$\")" COMPONENT ${PROJECT_NAME} ) endif() liblsl-1.17.7/examples/GetAllStreams.cpp000066400000000000000000000037261517625163100201400ustar00rootroot00000000000000#include #include #include #include #include #include /** * This example program shows how all streams that are currently present on the lab network can be * resolved and displayed. This is useful for browsing applications. */ int main(int argc, char *argv[]) { try { std::cout << "Here is a one-shot resolve of all current streams:" << std::endl; // discover all streams on the network std::vector results = lsl::resolve_streams(); std::map found_streams; // display them for (auto &stream : results) { found_streams.emplace(std::make_pair(stream.uid(), stream)); std::cout << stream.as_xml() << "\n\n"; } std::cout << "Press any key to switch to the continuous resolver test: " << std::endl; std::cin.get(); lsl::continuous_resolver r; while (true) { auto results = r.results(); for (auto &stream : results) { auto uid = stream.uid(); if (found_streams.find(uid) == found_streams.end()) { found_streams.emplace(std::make_pair(uid, stream)); std::cout << "Found " << stream.name() << '@' << stream.hostname() << std::endl; } } auto n_missing = found_streams.size() - results.size(); if (n_missing) { std::vector missing; missing.reserve(n_missing); for (const auto &pair : found_streams) if (std::none_of(results.begin(), results.end(), [uid = pair.first]( const lsl::stream_info &info) { return info.uid() == uid; })) { missing.push_back(pair.first); std::cout << "Lost " << pair.second.name() << '@' << pair.second.hostname() << std::endl; } for (const auto &uid : missing) found_streams.erase(uid); } std::this_thread::sleep_for(std::chrono::seconds(1)); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/GetFullinfo.cpp000066400000000000000000000030611517625163100176370ustar00rootroot00000000000000#include #include /** * This example program demonstrates how the full version of the stream info (i.e. including the * potentially large .desc() field) can be obtained from an inlet. Note that the output of the * resolve functions only includes the core information otherwise. */ int main(int argc, char *argv[]) { std::string field, value; if (argc != 3) { std::cout << "This connects to a stream which has a particular value for a given field and " "displays its full stream_info contentss." << std::endl; std::cout << "Please enter a field name and the desired value (e.g. \"type EEG\" (without " "the quotes)):" << std::endl; std::cin >> field >> value; } else { field = argv[1]; value = argv[2]; } try { // resolve the stream of interet std::cout << "Now resolving streams..." << std::endl; std::vector results = lsl::resolve_stream(field, value); std::cout << "Here is what was resolved: " << std::endl; std::cout << results[0].as_xml() << std::endl; // make an inlet to get data from it std::cout << "Now creating the inlet..." << std::endl; lsl::stream_inlet inlet(results[0]); // get & display the info std::cout << "The information about this stream is displayed in the following: " << std::endl; lsl::stream_info info = inlet.info(); std::cout << info.as_xml() << std::endl; } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/GetTimeCorrection.cpp000066400000000000000000000034121517625163100210070ustar00rootroot00000000000000#include #include /** * This example demonstrates how a time-correction value can be obtained on demand for a particular * stream on the net. This time-correction value, when added to the time stamp of an obtained * sample, remaps the sample's time stamp into the local clock domain (so it is in the same domain * as what lsl::local_clock() would return). For streams coming from the same computer, this value * should be approx. 0 (up to some tolerance). */ int main(int argc, char *argv[]) { std::string field, value; if (argc != 3) { std::cout << "This connects to a stream which has a particular value for a given field and " "gets the time-synchronization information for it." << std::endl; std::cout << "Please enter a field name and the desired value (e.g. \"type EEG\" (without " "the quotes)):" << std::endl; std::cin >> field >> value; } else { field = argv[1]; value = argv[2]; } try { // resolve the stream of interet std::cout << "Now resolving streams..." << std::endl; std::vector results = lsl::resolve_stream(field, value); std::cout << "Here is what was resolved: " << std::endl; std::cout << results[0].as_xml() << std::endl; // make an inlet to get data from it std::cout << "Now creating the inlet..." << std::endl; lsl::stream_inlet inlet(results[0]); // start receiving & displaying the data std::cout << "Press [Enter] to query a new correction value (clocks may drift)..." << std::endl; while (true) { std::cout << inlet.time_correction() << std::endl; std::cin.get(); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/HandleMetaData.cpp000066400000000000000000000036411517625163100202210ustar00rootroot00000000000000#include #include int main(int argc, char *argv[]) { try { // create a new stream_info and declare some meta-data (in accordance with XDF format) const char *name = argc > 1 ? argv[1] : "MetaTester"; lsl::stream_info info(name, "EEG", 8, 100, lsl::cf_float32, "myuid323457"); lsl::xml_element chns = info.desc().append_child("channels"); const char *labels[] = {"C3", "C4", "Cz", "FPz", "POz", "CPz", "O1", "O2"}; for (const char *label : labels) chns.append_child("channel") .append_child_value("label", label) .append_child_value("unit", "microvolts") .append_child_value("type", "EEG"); info.desc().append_child_value("manufacturer", "SCCN"); info.desc() .append_child("cap") .append_child_value("name", "EasyCap") .append_child_value("size", "54") .append_child_value("labelscheme", "10-20"); // create outlet for the stream lsl::stream_outlet outlet(info); // === the following could run on another computer === // resolve the stream and open an inlet std::vector results = lsl::resolve_stream("name", name); lsl::stream_inlet inlet(results[0]); // get the full stream info (including custom meta-data) and dissect it lsl::stream_info inf = inlet.info(); std::cout << "The stream's XML meta-data is: \n" << inf.as_xml() << "\nThe manufacturer is: " << inf.desc().child_value("manufacturer") << "\nThe cap circumference is: " << inf.desc().child("cap").child_value("size") << "\nThe channel labels are as follows:\n"; lsl::xml_element ch = inf.desc().child("channels").child("channel"); for (int k = 0; k < info.channel_count(); k++) { std::cout << " " << ch.child_value("label") << std::endl; ch = ch.next_sibling(); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } // std::cout << "Press any key to exit. " << std::endl; // std::cin.get(); return 0; } liblsl-1.17.7/examples/HandleMetaDataC.c000066400000000000000000000044741517625163100177710ustar00rootroot00000000000000#include #include int main(int argc, char *argv[]) { int c; /* channel index */ lsl_xml_ptr desc, chns, chn, cap; /* some xml element pointers */ lsl_outlet outlet; /* stream outlet */ lsl_streaminfo info, inf; /* streaminfo objects */ lsl_inlet inlet; /* a stream inlet to get samples from */ int errcode; /* error code (lsl_lost_error or timeouts) */ const char *labels[] = {"C3", "C4", "Cz", "FPz", "POz", "CPz", "O1", "O2"}; /* create a new streaminfo and declare some meta-data (in accordance with XDF format) */ const char *name = argc > 1 ? argv[1] : "MetaTester"; info = lsl_create_streaminfo(name, "EEG", 8, 100, cft_float32, "myuid323457"); desc = lsl_get_desc(info); chns = lsl_append_child(desc, "channels"); for (c = 0; c < 8; c++) { chn = lsl_append_child(chns, "channel"); lsl_append_child_value(chn, "label", labels[c]); lsl_append_child_value(chn, "unit", "microvolts"); lsl_append_child_value(chn, "type", "EEG"); } lsl_append_child_value(desc, "manufacturer", "SCCN"); cap = lsl_append_child(desc, "cap"); lsl_append_child_value(cap, "name", "EasyCap"); lsl_append_child_value(cap, "size", "54"); lsl_append_child_value(cap, "labelscheme", "10-20"); /* create outlet for the stream */ outlet = lsl_create_outlet(info, 0, 360); /* === the following could run on another computer === */ /* resolve the stream and open an inlet */ lsl_resolve_byprop(&info, 1, "name", "MetaTester", 1, LSL_FOREVER); inlet = lsl_create_inlet(info, 360, LSL_NO_PREFERENCE, 1); inf = lsl_get_fullinfo(inlet, LSL_FOREVER, &errcode); printf("The stream's XML meta-data is: \n"); printf("%s\n", lsl_get_xml(inf)); printf("The manufacturer is: %s\n", lsl_child_value_n(lsl_get_desc(inf), "manufacturer")); printf("The cap circumference is: %s\n", lsl_child_value_n(lsl_child(lsl_get_desc(inf), "cap"), "size")); printf("The channel labels are as follows:\n"); chn = lsl_child(lsl_child(lsl_get_desc(inf), "channels"), "channel"); for (c = 0; c < lsl_get_channel_count(inf); c++) { printf(" %s\n", lsl_child_value_n(chn, "label")); chn = lsl_next_sibling(chn); } /* destroy objects and free memory */ lsl_destroy_streaminfo(inf); lsl_destroy_inlet(inlet); lsl_destroy_outlet(outlet); lsl_destroy_streaminfo(info); /* wait for keypress */ getchar(); return 0; } liblsl-1.17.7/examples/LICENSE.txt000066400000000000000000000021011517625163100165320ustar00rootroot00000000000000MIT License Copyright (c) 2018 Christian Kothe, Tristan Stenner 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. liblsl-1.17.7/examples/README.md000066400000000000000000000172131517625163100162000ustar00rootroot00000000000000# LSL examples This repository contains small examples to demonstrate the basic usage of LSL. ## LSL Coding Fundamentals There are essentially two types of programs interacting with LSL: programs that provide data, such as a data source that represents a particular acquisition device, and programs that consume data (and occasionally mixtures of the two), such as a viewer, a recorder, or a program that takes some action based on real-time data. The main difference between LSL and other data transport interfaces is that it is not connection-oriented (although the underlying implementation uses connection-oriented transport). It is closer to a "publish-subscribe" model. A data producer creates a named "outlet" object (perhaps with meta-data) and pushes samples into it without ever caring about who is listening. So functionally it is offering a stream of data plus some meta-data about it. A data consumer who is interested in a particular type of stream queries the network for a matching one ("resolves it"), for example based on the name, type or some other content-based query and then creates an "inlet" object to receive samples from the stream. It can also separately obtain the meta-data of the stream. The consumer then pulls out samples from the stream. The sequence of samples that the consumer sees is in order, without omissions (unless a buffer's memory is exhausted), and type-safe. The data is buffered at both endpoints if there are transmission delays anywhere (e.g., interrupted network connection) but otherwise delivered as fast as the system allows. The objects and functions to perform these tasks are provided by a single cross-platform library (liblsl). The library comes with a C header file, a C++ header file and wrapper classes for other languages. Note: if you have trouble establishing communication between these programs across different computers especially on Windows, take a look at the NetworkConnectivity page and read the Network Troubleshooting section. ## [FAQs](https://labstreaminglayer.readthedocs.io/info/faqs.html) ## C Example Programs: Basic to Advanced These two example programs illustrate the bread-and-butter use of LSL as it is executing in almost any device module that comes with the distribution: - [Sending a multi-channel time series into LSL.](https://github.com/sccn/liblsl/blob/main/examples/SendDataC.c) - [Receiving a multi-channel time series from LSL.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveDataC.c) These two example programs illustrate a more special-purpose use case, namely sending arbitrary string-formatted data at irregular sampling rate. Such streams are used by programs that produce event markers, for example: - [Sending a stream of strings with irregular timing.](https://github.com/sccn/liblsl/blob/main/examples/SendStringMarkersC.c) - [Receiving a stream of strings with irregular timing.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveStringMarkersC.c) The last example shows how to attach properly formatted meta-data to a stream, and how to read it out again at the receiving end. While meta-data is strictly optional, it is very useful to make streams self-describing. LSL has adopted the convention to name meta-data fields according to the XDF file format specification whenever the content type matches (for example EEG, Gaze, MoCap, VideoRaw, etc); the spec is [here](https://github.com/sccn/xdf/wiki/Meta-Data). Note that some older example programs (SendData/ReceiveData) predate this convention and name the channels inconsistently. - [Handling stream meta-data.](https://github.com/sccn/liblsl/blob/main/examples/HandleMetaDataC.c) ## C++ Example Programs: Basic to Advanced These two example programs illustrate the shortest amount of code that is necessary to get a C++ program linked to LSL: - [Minimal data sending example.](https://github.com/sccn/liblsl/blob/main/examples/SendDataSimple.cpp) - [Minimal data receiving example.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveDataSimple.cpp) These two example programs demonstrate how to write more complete LSL clients in C++ (they are 1:1 equivalents of the corresponding C programs): - [Sending a multi-channel time series into LSL.](https://github.com/sccn/liblsl/blob/main/examples/SendData.cpp) - [Receiving a multi-channel time series from LSL.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveData.cpp) These two programs transmit their data at the granularity of chunks instead of samples. This is mostly a convenience matter, since inlets and outlets can be configured to automatically batch samples into chunks for transmission. Note that for LSL the data is always a linear sequence of samples and data that is pushed as samples can be pulled out as chunks or vice versa. They also show how structs can be used to represent the sample data, instead of numeric arrays (which is mostly a syntactic difference): - [Sending a multi-channel time series at chunk granularity.](https://github.com/sccn/liblsl/blob/main/examples/SendDataInChunks.cpp) - [Receiving a multi-channel time series at chunk granularity.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveDataInChunks.cpp) These two example programs illustrate a more special-purpose use case, namely sending arbitrary string-formatted data at irregular sampling rate. Such streams are used by programs that produce event markers, for example. These are 1:1 equivalents of the corresponding C programs: - [Sending a stream of strings with irregular timing.](https://github.com/sccn/liblsl/blob/main/examples/SendStringMarkers.cpp) - [Receiving a stream of strings with irregular timing.](https://github.com/sccn/liblsl/blob/main/examples/ReceiveStringMarkers.cpp) The last example shows how to attach properly formatted meta-data to a stream, and how to read it out again at the receiving end. While meta-data is strictly optional, it is very useful to make streams self-describing. LSL has adopted the convention to name meta-data fields according to the XDF file format specification whenever the content type matches (for example EEG, Gaze, MoCap, VideoRaw, etc); the spec is [here](https://github.com/sccn/xdf/wiki/Meta-Data). - [Handling stream meta-data.](https://github.com/sccn/liblsl/blob/main/examples/HandleMetaData.cpp) ## C/C++ Special-Purpose Example Programs These programs illustrate some special use cases of LSL that are also relevant for C programmers. See the lsl\_c.h header for the corresponding C APIs (they are very similar to the C++ code shown here). This example illustrates in more detail how streams can be resolved on the network: - [Resolving all streams on the lab network, one-shot and continuous.](https://github.com/sccn/liblsl/blob/main/examples/GetAllStreams.cpp) This example shows how to query the full XML meta-data of a stream (which may be several megabytes large): - [Retrieving the XML meta-data of a stream.](https://github.com/sccn/liblsl/blob/main/examples/GetFullinfo.cpp) This example shows how to obtain time-correction values for a given stream. These time-correction values are offsets (in seconds) that are used to remap any stream's timestamps into the own local clock domain (just by adding the offset to the timestamp): - [Querying the time-correction information for a stream.](https://github.com/sccn/liblsl/blob/main/examples/GetTimeCorrection.cpp) ## License Since using the examples as a starting point is actively encouraged, they are licensed under the [MIT](https://choosealicense.com/licenses/mit/) license. Still, you might want to consider a less permissive license (i.e. with more requirements to share modifications) like the [MPL](https://choosealicense.com/licenses/mpl-2.0/) or [GPL](https://choosealicense.com/licenses/gpl-3.0/). liblsl-1.17.7/examples/ReceiveData.cpp000066400000000000000000000045551517625163100176060ustar00rootroot00000000000000#include #include #include #include /** * Example program that demonstrates how to resolve a specific stream on the lab * network and how to connect to it in order to receive data. */ void printChunk(const std::vector &chunk, std::size_t n_channels) { for (std::size_t i = 0; i < chunk.size(); ++i) { std::cout << chunk[i] << ' '; if (i % n_channels == n_channels - 1) std::cout << '\n'; } } void printChunk(const std::vector> &chunk) { for (const auto &vec : chunk) printChunk(vec, vec.size()); } int main(int argc, char *argv[]) { std::string field, value; const int max_samples = argc > 3 ? std::stoi(argv[3]) : 10; if (argc < 3) { std::cout << "This connects to a stream which has a particular value for a " "given field and receives data.\nPlease enter a field name and the desired " "value (e.g. \"type EEG\" (without the quotes)):" << std::endl; std::cin >> field >> value; } else { field = argv[1]; value = argv[2]; } // resolve the stream of interet std::cout << "Now resolving streams..." << std::endl; std::vector results = lsl::resolve_stream(field, value); if (results.empty()) throw std::runtime_error("No stream found"); std::cout << "Here is what was resolved: " << std::endl; std::cout << results[0].as_xml() << std::endl; // make an inlet to get data from it std::cout << "Now creating the inlet..." << std::endl; lsl::stream_inlet inlet(results[0]); // start receiving & displaying the data std::cout << "Now pulling samples..." << std::endl; std::vector sample; std::vector> chunk_nested_vector; for (int i = 0; i < max_samples; ++i) { // pull a single sample inlet.pull_sample(sample); printChunk(sample, inlet.get_channel_count()); // Sleep so the outlet will have time to push some samples std::this_thread::sleep_for(std::chrono::milliseconds(500)); // pull a chunk into a nested vector - easier, but slower inlet.pull_chunk(chunk_nested_vector); printChunk(chunk_nested_vector); std::this_thread::sleep_for(std::chrono::milliseconds(500)); // pull a multiplexed chunk into a flat vector inlet.pull_chunk_multiplexed(sample); printChunk(sample, inlet.get_channel_count()); } if (argc == 1) { std::cout << "Press any key to exit. " << std::endl; std::cin.get(); } return 0; } liblsl-1.17.7/examples/ReceiveDataC.c000066400000000000000000000040251517625163100173410ustar00rootroot00000000000000#include #include /** * Example program that demonstrates how to resolve a specific stream on the lab network and how to * connect to it in order to receive data. */ #define NCHANS 8 int main(int argc, char *argv[]) { unsigned k, t; /* channel index */ lsl_streaminfo info; /* the streaminfo returned by the resolve call */ lsl_inlet inlet; /* a stream inlet to get samples from */ int errcode; /* error code (lsl_lost_error or timeouts) */ float cursample[NCHANS]; /* array to hold our current sample */ double timestamp; /* time stamp of the current sample (in sender time) */ /* resolve the stream of interest (result array: info, array capacity: 1 element, type shall be * EEG, resolve at least 1 stream, wait forever if necessary) */ printf("Now waiting for an EEG stream...\n"); lsl_resolve_byprop(&info, 1, "type", "EEG", 1, LSL_FOREVER); /* These next two variables aren't used for anything in this example. * They simply demonstrate how to use streaminfo getters. */ lsl_channel_format_t fmt = lsl_get_channel_format(info); double srate = lsl_get_nominal_srate(info); /* make an inlet to read data from the stream (buffer max. 300 seconds of data, no preference * regarding chunking, automatic recovery enabled) */ inlet = lsl_create_inlet(info, 300, LSL_NO_PREFERENCE, 1); /* subscribe to the stream (automatically done by push, but a nice way of checking early on that * we can connect successfully) */ lsl_open_stream(inlet, LSL_FOREVER, &errcode); if (errcode != 0) return errcode; printf("Displaying data...\n"); for (t = 0; t < 100000000; t++) { /* get the next sample form the inlet (read into cursample, 8 values, wait forever if * necessary) and return the timestamp if we got something */ timestamp = lsl_pull_sample_f(inlet, cursample, NCHANS, LSL_FOREVER, &errcode); /* print the data */ printf("%.2f", timestamp); for (k = 0; k < 8; ++k) printf("\t%.2f", cursample[k]); printf("\n"); } /* we never get here, but anyway */ lsl_destroy_inlet(inlet); return 0; } liblsl-1.17.7/examples/ReceiveDataInChunks.cpp000066400000000000000000000046651517625163100212530ustar00rootroot00000000000000#include #include #include #include #include int main(int argc, char **argv) { std::cout << "ReceiveDataInChunks" << std::endl; std::cout << "ReceiveDataInChunks StreamName max_buflen flush" << std::endl; std::cout << "- max_buffered -- duration in sec (or x100 samples if samplerate is 0) to buffer " "in the receiver" << std::endl; std::cout << "- flush -- set non-zero to flush data instead of pulling; useful for testing throughput" << std::endl; try { std::string name{argc > 1 ? argv[1] : "MyAudioStream"}; double max_buffered = argc > 2 ? std::stod(argv[2]) : 360.; bool flush = argc > 3; // resolve the stream of interest & make an inlet lsl::stream_info inlet_info = lsl::resolve_stream("name", name).at(0); lsl::stream_inlet inlet(inlet_info, (int32_t)max_buffered); // Use set_postprocessing to get the timestamps in a common base clock. // Do not use if this application will record timestamps to disk -- it is better to // do posthoc synchronization. inlet.set_postprocessing(lsl::post_ALL); // Inlet opening is implicit when doing pull_sample or pull_chunk. // Here we open the stream explicitly because we might be doing // `flush` only. inlet.open_stream(); double starttime = lsl::local_clock(), next_display = starttime + 1, next_reset = starttime + 10; // and retrieve the chunks uint64_t k = 0, num_samples = 0; std::vector> result; auto fetch_interval = std::chrono::milliseconds(20); auto next_fetch = std::chrono::steady_clock::now() + fetch_interval; while (true) { std::this_thread::sleep_until(next_fetch); if (flush) { // You almost certainly don't want to use flush. This is here so we // can test maximum outlet throughput. num_samples += inlet.flush(); } else { if (double timestamp = inlet.pull_chunk(result)) num_samples += result.size(); } k++; next_fetch += fetch_interval; if (k % 50 == 0) { double now = lsl::local_clock(); std::cout << num_samples / (now - starttime) << " samples/sec" << std::endl; if (now > next_reset) { std::cout << "Resetting counters..." << std::endl; starttime = now; next_reset = now + 10; num_samples = 0; } } } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/ReceiveDataSimple.cpp000066400000000000000000000015761517625163100207600ustar00rootroot00000000000000#include #include #include /** * This is a minimal example that demonstrates how a multi-channel stream (here 128ch) of a * particular name (here: SimpleStream) can be resolved into an inlet, and how the raw sample data & * time stamps are pulled from the inlet. This example does not display the obtained data. */ int main(int argc, char **argv) { using namespace lsl; // resolve the stream of interest & make an inlet to get data from the first result std::vector results = resolve_stream("name", argc > 1 ? argv[1] : "SimpleStream"); stream_inlet inlet(results.at(0)); // receive data & time stamps forever (not displaying them here) std::vector sample; while (true) { double timestamp = inlet.pull_sample(sample); std::cout << timestamp << "\t" << sample[0] << "\t" << sample[1] << "..." << std::endl; } return 0; } liblsl-1.17.7/examples/ReceiveStringMarkers.cpp000066400000000000000000000022251517625163100215200ustar00rootroot00000000000000#include #include #include #include /** * This example program demonstrates how a Marker string stream on the network (here of any name) * can be resolved, and how the marker strings (assumed to be delivered at irregular rate) can be * pulled from their source */ int main(int, char **) { // resolve the stream of interet std::vector> inlets; for (auto &stream_info : lsl::resolve_stream("type", "Markers")) { if (stream_info.channel_count() != 1) std::cerr << "Skipping stream " << stream_info.name() << " because it has more than one channel" << std::endl; else { inlets.emplace_back(new lsl::stream_inlet(stream_info)); std::cout << "Listening to " << stream_info.name() << std::endl; } } if (inlets.empty()) { std::cerr << "No marker stream found" << std::endl; return 0; } std::string sample; double starttime = lsl::local_clock(); while (true) { for (auto &inlet : inlets) if (double ts = inlet->pull_sample(&sample, 1, .2)) std::cout << (ts - inlet->time_correction(1) - starttime) << '\t' << sample << std::endl; } return 0; } liblsl-1.17.7/examples/ReceiveStringMarkersC.c000066400000000000000000000025331517625163100212650ustar00rootroot00000000000000#include #include /** * Example program that demonstrates how to resolve a specific stream on the lab network and how * to connect to it in order to receive string-formatted markers. */ int main(int argc, char *argv[]) { lsl_streaminfo info; /* the streaminfo returned by the resolve call */ lsl_inlet inlet; /* a stream inlet to get samples from */ int errcode; /* error code (lsl_lost_error or timeouts) */ char *cursample; /* array to hold our current sample */ double timestamp; /* time stamp of the current sample (in sender time) */ /* resolve the stream of interest (result array: info, array capacity: 1 element, type shall be * EEG, resolve at least 1 stream, wait forever if necessary) */ printf("Now waiting for a Markers stream...\n"); lsl_resolve_byprop(&info, 1, "type", "Markers", 1, LSL_FOREVER); /* make an inlet to read data from the stream (buffer max. 300k markers, no preference regarding * chunking, automatic recovery enabled) */ inlet = lsl_create_inlet(info, 300, LSL_NO_PREFERENCE, 1); /* start receiving & displaying the data */ printf("Displaying data...\n"); while (1) { timestamp = lsl_pull_sample_str(inlet, &cursample, 1, LSL_FOREVER, &errcode); printf("got: %s at time %.3f\n", cursample, timestamp); } /* we never get here, but anyway */ lsl_destroy_inlet(inlet); return 0; } liblsl-1.17.7/examples/SendData.cpp000066400000000000000000000062601517625163100171100ustar00rootroot00000000000000#include "lsl_cpp.h" #include #include #include #include #include /** * This example program offers an 8-channel stream, float-formatted, that resembles EEG data. * The example demonstrates also how per-channel meta-data can be specified using the .desc() field * of the stream information object. * * Note that the timer used in the send loop of this program is not particularly accurate. */ const char *channels[] = {"C3", "C4", "Cz", "FPz", "POz", "CPz", "O1", "O2"}; int main(int argc, char *argv[]) { std::string name, type; if (argc < 3) { std::cout << "This opens a stream under some user-defined name and with a user-defined content " "type." << std::endl; std::cout << "SendData Name Type [n_channels=8] [srate=100] [max_buffered=360]" << std::endl; std::cout << "Please enter the stream name and the stream type (e.g. \"BioSemi EEG\" (without " "the quotes)):" << std::endl; std::cin >> name >> type; } else { name = argv[1]; type = argv[2]; } int n_channels = argc > 3 ? std::stol(argv[3]) : 8; n_channels = n_channels < 8 ? 8 : n_channels; int samplingrate = argc > 4 ? std::stol(argv[4]) : 100; int max_buffered = argc > 5 ? std::stol(argv[5]) : 360; try { // make a new stream_info (100 Hz) lsl::stream_info info( name, type, n_channels, samplingrate, lsl::cf_float32, std::string(name) += type); // add some description fields info.desc().append_child_value("manufacturer", "LSL"); lsl::xml_element chns = info.desc().append_child("channels"); for (int k = 0; k < n_channels; k++) chns.append_child("channel") .append_child_value("label", k < 8 ? channels[k] : "Chan-" + std::to_string(k + 1)) .append_child_value("unit", "microvolts") .append_child_value("type", type); // make a new outlet lsl::stream_outlet outlet(info, 0, max_buffered); std::vector sample(n_channels, 0.0); // Your device might have its own timer. Or you can decide how often to poll // your device, as we do here. int32_t sample_dur_us = 1000000 / (samplingrate > 0 ? samplingrate : 100); auto t_start = std::chrono::high_resolution_clock::now(); auto next_sample_time = t_start; // send data forever std::cout << "Now sending data... " << std::endl; double starttime = ((double)clock()) / CLOCKS_PER_SEC; for (unsigned t = 0;; t++) { // Create random data for the first 8 channels. for (int c = 0; c < 8; c++) sample[c] = (float)((rand() % 1500) / 500.0 - 1.5); // For the remaining channels, fill them with a sample counter (wraps at 1M). std::fill(sample.begin() + 8, sample.end(), (float)(t % 1000000)); // Wait until the next expected sample time. next_sample_time += std::chrono::microseconds(sample_dur_us); std::this_thread::sleep_until(next_sample_time); // send the sample std::cout << sample[0] << "\t" << sample[n_channels-1] << std::endl; outlet.push_sample(sample); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/SendDataC.c000066400000000000000000000044411517625163100166520ustar00rootroot00000000000000#include #include #include /** * This example program offers an 8-channel stream, float-formatted, that resembles EEG data. * The example demonstrates also how per-channel meta-data can be specified using the description * field of the streaminfo object. * * Note that the app sends data as fast as it can * Generally, you should sleep between sending samples and send multiple samples at once * (push_chunk_*-functions) */ int main(int argc, char *argv[]) { printf("SendDataC example program. Sends 8 float channels as fast as possible.\n"); printf("Usage: %s [streamname] [streamuid]\n", argv[0]); printf("Using lsl %d, lsl_library_info: %s\n", lsl_library_version(), lsl_library_info()); const char *name = argc > 1 ? argv[1] : "SendDataC"; const char *uid = argc > 2 ? argv[2] : "325wqer4354"; /* declare a new streaminfo (name: SendDataC / argument 1, content type: EEG, 8 channels, 500 * Hz, float values, some made-up device id (can also be empty) */ lsl_streaminfo info = lsl_create_streaminfo(name, "EEG", 8, 500, cft_float32, uid); /* add some meta-data fields to it */ /* (for more standard fields, see https://github.com/sccn/xdf/wiki/Meta-Data) */ lsl_xml_ptr desc = lsl_get_desc(info); lsl_append_child_value(desc, "manufacturer", "LSL"); const char *channels[] = {"C3", "C4", "Cz", "FPz", "POz", "CPz", "O1", "O2"}; lsl_xml_ptr chns = lsl_append_child(desc, "channels"); for (int c = 0; c < 8; c++) { lsl_xml_ptr chn = lsl_append_child(chns, "channel"); lsl_append_child_value(chn, "label", channels[c]); lsl_append_child_value(chn, "unit", "microvolts"); lsl_append_child_value(chn, "type", "EEG"); } /* make a new outlet (chunking: default, buffering: 360 seconds) */ lsl_outlet outlet = lsl_create_outlet(info, 0, 360); do printf("Waiting for consumers\n"); while (!lsl_wait_for_consumers(outlet, 120)); printf("Now sending data...\n"); /* send data until the last consumer has disconnected */ for (int t = 0; lsl_have_consumers(outlet); t++) { float cursample[8]; /* the current sample */ cursample[0] = (float)t; for (int c = 1; c < 8; c++) cursample[c] = (float)((rand() % 1500) / 500.0 - 1.5); lsl_push_sample_f(outlet, cursample); } printf("Lost the last consumer, shutting down\n"); lsl_destroy_outlet(outlet); return 0; } liblsl-1.17.7/examples/SendDataInChunks.cpp000066400000000000000000000146711517625163100205600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // define a packed sample struct (here: a 16 bit stereo sample). #pragma pack(1) struct stereo_sample { int16_t l, r; }; struct fake_device { /* We create a fake device that will generate data. The inner details are not so important because typically it will be up to the real data source + SDK to provide a way to get data. */ std::size_t n_channels; double srate; std::size_t pattern_samples; std::size_t head; std::vector pattern; std::chrono::steady_clock::time_point last_time; fake_device(const int16_t n_channels, const float srate) : n_channels(n_channels), srate(srate), head(0) { pattern_samples = (std::size_t)(srate - 0.5) + 1; // truncate OK. // Pre-allocate entire test pattern. The data _could_ be generated on the fly // for a much smaller memory hit, but we also use this example application // to test LSL Outlet performance so we want to reduce out-of-LSL CPU // utilization. int64_t magnitude = std::numeric_limits::max(); int64_t offset_0 = magnitude / 2; int64_t offset_step = magnitude / n_channels; pattern.reserve(pattern_samples * n_channels); for (auto sample_ix = 0; sample_ix < pattern_samples; ++sample_ix) { for (auto chan_ix = 0; chan_ix < n_channels; ++chan_ix) { // sin(2*pi*f*t), where f cycles from 1 Hz to Nyquist: srate / 2 double f = (chan_ix + 1) % (int)(srate / 2); pattern.emplace_back( static_cast( offset_0 + chan_ix * offset_step + magnitude * sin(2 * M_PI * f * sample_ix / srate))); } } last_time = std::chrono::steady_clock::now(); } std::vector get_data() { auto now = std::chrono::steady_clock::now(); auto elapsed_nano = std::chrono::duration_cast(now - last_time).count(); std::size_t elapsed_samples = std::size_t(elapsed_nano * srate * 1e-9); // truncate OK. std::vector result; result.resize(elapsed_samples * n_channels); int64_t ret_samples = get_data(result); std::vector output(result.begin(), result.begin() + ret_samples); return output; } std::size_t get_data(std::vector &buffer, bool nodata = false) { auto now = std::chrono::steady_clock::now(); auto elapsed_nano = std::chrono::duration_cast(now - last_time).count(); std::size_t elapsed_samples = std::size_t(elapsed_nano * srate * 1e-9); // truncate OK. elapsed_samples = std::min(elapsed_samples, (std::size_t)(buffer.size() / n_channels)); if (nodata) { // The fastest but no patterns. // memset(&buffer[0], 23, buffer.size() * sizeof buffer[0]); } else { std::size_t end_sample = head + elapsed_samples; std::size_t nowrap_samples = std::min(pattern_samples - head, elapsed_samples); memcpy(&buffer[0], &(pattern[head]), nowrap_samples); if (end_sample > pattern_samples) { memcpy(&buffer[nowrap_samples], &(pattern[0]), elapsed_samples - nowrap_samples); } } head = (head + elapsed_samples) % pattern_samples; last_time += std::chrono::nanoseconds(int64_t(1e9 * elapsed_samples / srate)); return elapsed_samples; } }; int main(int argc, char **argv) { std::cout << "SendDataInChunks" << std::endl; std::cout << "SendDataInChunks StreamName StreamType samplerate n_channels max_buffered chunk_rate" << std::endl; std::cout << "- max_buffered -- duration in sec (or x100 samples if samplerate is 0) to buffer for each outlet" << std::endl; std::cout << "- chunk_rate -- number of chunks pushed per second. For this example, make it a common factor of samplingrate and 1000." << std::endl; std::string name{argc > 1 ? argv[1] : "MyAudioStream"}, type{argc > 2 ? argv[2] : "Audio"}; int samplingrate = argc > 3 ? std::stol(argv[3]) : 44100; // Here we specify srate, but typically this would come from the device. int n_channels = argc > 4 ? std::stol(argv[4]) : 2; // Here we specify n_chans, but typically this would come from theh device. double max_buffered = argc > 5 ? std::stod(argv[5]) : 360.; int32_t chunk_rate = argc > 6 ? std::stol(argv[6]) : 10; // Chunks per second. int32_t chunk_samples = samplingrate > 0 ? std::max((samplingrate / chunk_rate), 1) : 100; // Samples per chunk. int32_t chunk_duration = 1000 / chunk_rate; // Milliseconds per chunk try { // Prepare the LSL stream. lsl::stream_info info( name, type, n_channels, samplingrate, lsl::cf_int16, "example-SendDataInChunks"); lsl::xml_element desc = info.desc(); desc.append_child_value("manufacturer", "LSL"); lsl::xml_element chns = desc.append_child("channels"); for (int c = 0; c < n_channels; c++) { lsl::xml_element chn = chns.append_child("channel"); chn.append_child_value("label", "Chan-" + std::to_string(c)); chn.append_child_value("unit", "microvolts"); chn.append_child_value("type", type); } int32_t buf_samples = (int32_t)(max_buffered * samplingrate); lsl::stream_outlet outlet(info, chunk_samples, buf_samples); info = outlet.info(); // Refresh info with whatever the outlet captured. std::cout << "Stream UID: " << info.uid() << std::endl; // Create a connection to our device. fake_device my_device(n_channels, (float)samplingrate); // Prepare buffer to get data from 'device'. // The buffer should be larger than you think you need. Here we make it 4x as large. std::vector chunk_buffer(4 * chunk_samples * n_channels); std::cout << "Now sending data..." << std::endl; // Your device might have its own timer. Or you can decide how often to poll // your device, as we do here. auto t_start = std::chrono::steady_clock::now(); auto next_chunk_time = t_start; for (unsigned c = 0;; c++) { // wait a bit next_chunk_time += std::chrono::milliseconds(chunk_duration); std::this_thread::sleep_until(next_chunk_time); // Get data from device std::size_t returned_samples = my_device.get_data(chunk_buffer); // send it to the outlet. push_chunk_multiplexed is one of the more complicated approaches. // other push_chunk methods are easier but slightly slower. double ts = lsl::local_clock(); outlet.push_chunk_multiplexed( chunk_buffer.data(), returned_samples * n_channels, ts, true); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/SendDataMinimal.cpp000066400000000000000000000016061517625163100204160ustar00rootroot00000000000000#include #include #include /** * This is a minimal example of how a multi-channel data stream can be sent to LSL. * Here, the stream is named SimpleStream, has content-type EEG, 8 channels, and 200 Hz. * The transmitted samples contain random numbers. */ const int nchannels = 8; int main(int argc, char *argv[]) { // make a new stream_info and open an outlet with it lsl::stream_info info("SimpleStream", "EEG", nchannels, 200.0); lsl::stream_outlet outlet(info); // send data forever float sample[nchannels]; while (true) { // generate random data for (int c = 0; c < nchannels; c++) sample[c] = (float)((rand() % 1500) / 500.0 - 1.5); // send it outlet.push_sample(sample); // maintain our desired sampling rate (approximately; real software might do something else) std::this_thread::sleep_for(std::chrono::milliseconds(5)); } return 0; } liblsl-1.17.7/examples/SendDataSimple.cpp000066400000000000000000000017241517625163100202620ustar00rootroot00000000000000#include // std::chrono::seconds #include #include // std::this_thread::sleep_for /** * This is an example of how a simple data stream can be offered on the network. * Here, the stream is named SimpleStream, has content-type EEG, and 8 channels. * The transmitted samples contain random numbers (and the sampling rate is irregular) */ const int nchannels = 8; int main(int argc, char *argv[]) { // make a new stream_info (nchannelsch) and open an outlet with it lsl::stream_info info(argc > 1 ? argv[1] : "SimpleStream", "EEG", nchannels); lsl::stream_outlet outlet(info); while (!outlet.wait_for_consumers(120)) ; // send data forever float sample[nchannels]; while (outlet.have_consumers()) { // generate random data for (int c = 0; c < nchannels; c++) sample[c] = (float)((rand() % 1500) / 500.0 - 1.5); // send it outlet.push_sample(sample); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } return 0; } liblsl-1.17.7/examples/SendMultipleStreams.cpp000066400000000000000000000022241517625163100213650ustar00rootroot00000000000000#include #include #include #include #include using Clock = std::chrono::steady_clock; int main(int argc, char **argv) { try { const char *name = argc > 1 ? argv[1] : "MultiStream"; const int rate = argc > 2 ? std::stoi(argv[2]) : 1000; std::vector outlets; for (auto fmt : {lsl::cf_int16, lsl::cf_int32, lsl::cf_int64, lsl::cf_double64, lsl::cf_string}) outlets.emplace_back( lsl::stream_info(name + std::to_string(fmt), "Example", 1, rate, fmt), 0, 360); std::cout << "Now sending data..." << std::endl; std::vector mychunk(rate); auto nextsample = Clock::now(); for (int c = 0; c < rate * 600;) { // increment the sample counter for (auto &sample : mychunk) sample = c++; nextsample += std::chrono::seconds(1); std::this_thread::sleep_until(nextsample); double ts = lsl::local_clock(); for (auto &outlet : outlets) outlet.push_chunk_multiplexed(mychunk, ts); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/SendStringMarkers.cpp000066400000000000000000000026321517625163100210310ustar00rootroot00000000000000#include #include #include #include /** * This example program offers a 1-channel stream which contains strings. * The stream has the "Marker" content type and irregular rate. * The name of the stream can be chosen as a startup parameter. */ int main(int argc, char *argv[]) { try { const char *name = argc > 1 ? argv[1] : "MyEventStream"; // make a new stream_info lsl::stream_info info(name, "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_string, "id23443"); // make a new outlet lsl::stream_outlet outlet(info); // send random marker strings std::cout << "Now sending markers... " << std::endl; std::vector markertypes{ "Test", "Blah", "Marker", "XXX", "Testtest", "Test-1-2-3"}; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution rnd(0, markertypes.size() - 1); std::uniform_int_distribution delayrnd(0, 1000); while (true) { // wait for a 20ms std::this_thread::sleep_for(std::chrono::milliseconds(delayrnd(gen))); // and choose the marker to send std::string mrk = markertypes[rnd(gen)]; std::cout << "now sending: " << mrk << std::endl; // now send it (note the &) outlet.push_sample(&mrk); } } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } std::cout << "Press any key to exit. " << std::endl; std::cin.get(); return 0; } liblsl-1.17.7/examples/SendStringMarkersC.c000066400000000000000000000032331517625163100205720ustar00rootroot00000000000000#include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include void sleep_(int ms) { Sleep(ms); } #else #include void sleep_(int ms) { usleep(ms * 1000); } #endif /** * This example program offers a 1-channel stream which contains strings. * The stream has the "Marker" content type and irregular rate. */ int main(int argc, char *argv[]) { lsl_streaminfo info; /* out stream declaration object */ lsl_outlet outlet; /* stream outlet */ const char *mrk; /* marker to send next */ /* declare a new streaminfo (name: "MyEventStream", content type: "Markers", 1 channel, * irregular rate, ... */ /* ... string values, some made-up source id (can also be empty) */ const char *name = argc > 1 ? argv[1] : "MyEventStream"; info = lsl_create_streaminfo(name, "Markers", 1, LSL_IRREGULAR_RATE, cft_string, "id23443"); /* make a new outlet (chunking: default, buffering: 360k markers) */ outlet = lsl_create_outlet(info, 0, 360); /* send random marker streams (note: this loop is keeping the CPU busy, normally one would sleep * or yield here) */ printf("Now sending markers...\n"); const char *markertypes[] = {"Test", "Blah", "Marker", "XXX", "Testtest", "Test-1-2-3"}; while (1) { /* wait for a random period of time */ sleep_(rand() % 1000); /* and choose the marker to send */ mrk = markertypes[rand() % (sizeof(markertypes) / sizeof(markertypes[0]))]; printf("now sending: %s\n", mrk); /* now send it (note the &, since this function takes an array of char*) */ lsl_push_sample_str(outlet, &mrk); } /* we never get here, buy anyway */ lsl_destroy_outlet(outlet); return 0; } liblsl-1.17.7/examples/TestSyncWithoutData.cpp000066400000000000000000000026541517625163100213620ustar00rootroot00000000000000#include // std::chrono::seconds #include #include #include // std::this_thread::sleep_for int main(int argc, char *argv[]) { try { lsl::stream_info info("SyncTest", "Test", 1, lsl::IRREGULAR_RATE, lsl::cf_int16, "id23443"); // make a new outlet lsl::stream_outlet outlet(info); auto found_stream_info = lsl::resolve_stream("name", "SyncTest"); if (found_stream_info.empty()) throw std::runtime_error("Sender outlet not found!"); lsl::stream_info si = found_stream_info[0]; std::cout << "Found " << si.name() << '@' << si.hostname() << std::endl; lsl::stream_inlet inlet(si); // inlet.open_stream(2); // outlet.wait_for_consumers(2); std::thread push_thread{[&]() { std::this_thread::sleep_for(std::chrono::seconds(10)); std::cout << "Pushing data now" << std::endl; for (int16_t i = 0; i < 10; ++i) { outlet.push_sample(&i); std::this_thread::sleep_for(std::chrono::seconds(1)); } }}; for (auto end = lsl::local_clock() + 20; lsl::local_clock() < end;) { try { std::cout << "Got time correction: " << inlet.time_correction(1) << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } catch (std::exception &e) { std::cout << "Error getting time correction data: " << e.what() << std::endl; } } push_thread.join(); } catch (std::exception &e) { std::cerr << "Got an exception: " << e.what() << std::endl; } return 0; } liblsl-1.17.7/include/000077500000000000000000000000001517625163100145225ustar00rootroot00000000000000liblsl-1.17.7/include/lsl/000077500000000000000000000000001517625163100153145ustar00rootroot00000000000000liblsl-1.17.7/include/lsl/common.h000066400000000000000000000207321517625163100167610ustar00rootroot00000000000000#pragma once //! @file common.h Global constants for liblsl #if defined(LIBLSL_FFI) // Skip any typedefs that might confuse a FFI header parser, e.g. cffi #elif defined(_MSC_VER) && _MSC_VER < 1600 typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef signed long long int64_t; typedef unsigned int uint32_t; #else #include #endif #if defined(_MSC_VER) && _MSC_VER < 1900 #define __func__ __FUNCTION__ #endif /// LIBLSL_C_API expands function attributes needed for the linker #if defined(LIBLSL_STATIC) || defined(LIBLSL_FFI) #define LIBLSL_C_API #elif defined _WIN32 || defined __CYGWIN__ #if defined LIBLSL_EXPORTS #define LIBLSL_C_API __declspec(dllexport) #else #define LIBLSL_C_API __declspec(dllimport) #ifndef LSLNOAUTOLINK #pragma comment(lib, "lsl.lib") #endif #endif #pragma warning(disable : 4275) #else // Linux / OS X #define LIBLSL_C_API __attribute__((visibility("default"))) #endif /// Constant to indicate that a stream has variable sampling rate. #define LSL_IRREGULAR_RATE 0.0 /** * Constant to indicate that a sample has the next successive time stamp. * * This is an optional optimization to transmit less data per sample. * The stamp is then deduced from the preceding one according to the stream's * sampling rate (in the case of an irregular rate, the same time stamp as * before will is assumed). */ #define LSL_DEDUCED_TIMESTAMP -1.0 /// A very large time value (ca. 1 year); can be used in timeouts. #define LSL_FOREVER 32000000.0 /** * Constant to indicate that there is no preference about how a data stream * shall be chunked for transmission. * (can be used for the chunking parameters in the inlet or the outlet). */ #define LSL_NO_PREFERENCE 0 /// Data format of a channel (each transmitted sample holds an array of channels), 4 bytes wide typedef enum { /** For up to 24-bit precision measurements in the appropriate physical unit (e.g., microvolts). * Integers from -16777216 to 16777216 are represented accurately. */ cft_float32 = 1, /** For universal numeric data as long as permitted by network & disk budget. * The largest representable integer is 53-bit. */ cft_double64 = 2, /** For variable-length ASCII strings or data blobs, such as video frames, complex event descriptions, etc. */ cft_string = 3, /** For high-rate digitized formats that require 32-bit precision. * Depends critically on meta-data to represent meaningful units. * Useful for application event codes or other coded data. */ cft_int32 = 4, /** For very high rate signals (40Khz+) or consumer-grade audio. * For professional audio float is recommended. */ cft_int16 = 5, /// For binary signals or other coded data. Not recommended for encoding string data. cft_int8 = 6, /** 64 bit integers. Support for this type is not yet exposed in all languages. * Also, some builds of liblsl will not be able to send or receive data of this type. */ cft_int64 = 7, /// Can not be transmitted. cft_undefined = 0, // prevent compilers from assuming an instance fits in a single byte _cft_maxval = 0x7f000000 } lsl_channel_format_t; // Abort compilation if lsl_channel_format_t isn't exactly 4 bytes wide #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L _Static_assert(sizeof(lsl_channel_format_t) == 4, "lsl_channel_format_t size breaks the LSL ABI"); #elif defined(__cplusplus) && __cplusplus >= 201103L static_assert (sizeof(lsl_channel_format_t) == 4, "lsl_channel_format_t size breaks the LSL ABI"); #elif !defined(LIBLSL_FFI) static char _lsl_channel_format_size_check[1 - 2*!(sizeof(lsl_channel_format_t)==4)]; #endif /// Post-processing options for stream inlets. typedef enum { /** No automatic post-processing; return the ground-truth time stamps for manual post-processing. This is the default behavior of the inlet. */ proc_none = 0, /** Perform automatic clock synchronization; equivalent to manually adding the time_correction() value to the received time stamps. */ proc_clocksync = 1, /** Remove jitter from time stamps. * * This will apply a smoothing algorithm to the received time stamps; the smoothing needs to see * a minimum number of samples (30-120 seconds worst-case) until the remaining jitter is * consistently below 1ms. */ proc_dejitter = 2, /** Force the time-stamps to be monotonically ascending. * * Only makes sense if timestamps are dejittered. */ proc_monotonize = 4, /** Post-processing is thread-safe (same inlet can be read from by multiple threads); * uses somewhat more CPU. */ proc_threadsafe = 8, /// The combination of all possible post-processing options. proc_ALL = 1 | 2 | 4 | 8, // prevent compilers from assuming an instance fits in a single byte _proc_maxval = 0x7f000000 } lsl_processing_options_t; /// Possible error codes. typedef enum { /// No error occurred lsl_no_error = 0, /// The operation failed due to a timeout. lsl_timeout_error = -1, /// The stream has been lost. lsl_lost_error = -2, /// An argument was incorrectly specified (e.g., wrong format or wrong length). lsl_argument_error = -3, /// Some other internal error has happened. lsl_internal_error = -4, // prevent compilers from assuming an instance fits in a single byte _lsl_error_code_maxval = 0x7f000000 } lsl_error_code_t; /// Flags for outlet_ex and inlet_ex typedef enum { /// Keep legacy behavior: max_buffered / max_buflen is in seconds; use asynch transfer. transp_default = 0, /// The supplied max_buf value is in samples. transp_bufsize_samples = 1, /// The supplied max_buf should be scaled by 0.001. transp_bufsize_thousandths = 2, // prevent compilers from assuming an instance fits in a single byte _lsl_transport_options_maxval = 0x7f000000 } lsl_transport_options_t; /// Return an explanation for the last error extern LIBLSL_C_API const char *lsl_last_error(void); /** * LSL version the binary was compiled against * * Used either to check if the same version is used * (`if(lsl_protocol_version()!=LIBLSL_COMPILE_HEADER_VERSION`) … * or to require a certain set of features: * ``` * #if LIBLSL_COMPILE_HEADER_VERSION > 113 * do_stuff(); * #endif * ``` * */ #define LIBLSL_COMPILE_HEADER_VERSION 114 /** * Protocol version. * * The major version is `protocol_version() / 100;` * The minor version is `protocol_version() % 100;` * * Clients with different minor versions are protocol-compatible while clients * with different major versions will refuse to work together. */ extern LIBLSL_C_API int32_t lsl_protocol_version(); /** * Version of the liblsl library. * * The major version is `library_version() / 100;` * The minor version is `library_version() % 100;` */ extern LIBLSL_C_API int32_t lsl_library_version(); /** * Get a string containing library information. * * The format of the string shouldn't be used for anything important except giving a debugging * person a good idea which exact library version is used. */ extern LIBLSL_C_API const char *lsl_library_info(void); /** * Obtain a local system time stamp in seconds. * * The resolution is better than a millisecond. * This reading can be used to assign time stamps to samples as they are being acquired. * If the "age" of a sample is known at a particular time (e.g., from USB transmission * delays), it can be used as an offset to lsl_local_clock() to obtain a better estimate of * when a sample was actually captured. See lsl_push_sample() for a use case. */ extern LIBLSL_C_API double lsl_local_clock(); /** * Deallocate a string that has been transferred to the application. * * Rarely used: the only use case is to deallocate the contents of * string-valued samples received from LSL in an application where * no free() method is available (e.g., in some scripting languages). */ extern LIBLSL_C_API void lsl_destroy_string(char *s); /** * Set the path of the configuration file to be used by liblsl. * * This must be called before any other LSL function; otherwise the setting * has no effect, as the configuration is loaded lazily on first use. * See also the precedence list in api_config.h. */ extern LIBLSL_C_API void lsl_set_config_filename(const char *filename); /** * Set the configuration content (as an INI string) to be used by liblsl. * * This must be called before any other LSL function; otherwise the setting * has no effect, as the configuration is loaded lazily on first use. * When set, this content takes precedence over any configuration file. * * @note The content is discarded after liblsl has initialized. */ extern LIBLSL_C_API void lsl_set_config_content(const char *content); liblsl-1.17.7/include/lsl/inlet.h000066400000000000000000000425541517625163100166120ustar00rootroot00000000000000#pragma once #include "common.h" #include "types.h" /// @file inlet.h Stream inlet functions /** @defgroup lsl_inlet The lsl_inlet object * @{ */ /** * Construct a new stream inlet from a resolved stream info. * @param info A resolved stream info object (as coming from one of the resolver functions). * @note The inlet makes a copy of the info object at its construction. * @note The stream_inlet may also be constructed with a fully-specified stream_info, if the desired * channel format and count is already known up-front, but this is strongly discouraged and should * only ever be done if there is no time to resolve the stream up-front (e.g., due to limitations * in the client program). * @param max_buflen Optionally the maximum amount of data to buffer (in seconds if there is a * nominal sampling rate, otherwise x100 in samples). * * Recording applications want to use a fairly large buffer size here, while real-time applications * would only buffer as much as they need to perform their next calculation. * * A good default is 360, which corresponds to 6 minutes of data. * @param max_chunklen Optionally the maximum size, in samples, at which chunks are transmitted. * If specified as 0, the chunk sizes preferred by the sender are used. * Recording applications can use a generous size here (leaving it to the network how to pack * things), while real-time applications may want a finer (perhaps 1-sample) granularity. * @param recover Try to silently recover lost streams that are recoverable (=those that that have a * source_id set). * * It is generally a good idea to enable this, unless the application wants to act in a special way * when a data provider has temporarily crashed. * * If recover is 0 or the stream is not recoverable, most outlet functions will return an * #lsl_lost_error if the stream's source is lost. * @return A newly created lsl_inlet handle or NULL in the event that an error occurred. */ extern LIBLSL_C_API lsl_inlet lsl_create_inlet(lsl_streaminfo info, int32_t max_buflen, int32_t max_chunklen, int32_t recover); /** @copydoc lsl_create_inlet() * @param flags An integer that is the result of bitwise OR'ing one or more options from * #lsl_transport_options_t together (e.g., #transp_bufsize_samples) */ extern LIBLSL_C_API lsl_inlet lsl_create_inlet_ex(lsl_streaminfo info, int32_t max_buflen, int32_t max_chunklen, int32_t recover, lsl_transport_options_t flags); /** * Destructor. * The inlet will automatically disconnect if destroyed. */ extern LIBLSL_C_API void lsl_destroy_inlet(lsl_inlet in); /** * Retrieve the complete information of the given stream, including the extended description. * Can be invoked at any time of the stream's lifetime. * @param in The lsl_inlet object to act on. * @param timeout Timeout of the operation. Use LSL_FOREVER to effectively disable it. * @param[out] ec Error code: if nonzero, can be either lsl_timeout_error (if the timeout has * expired) or #lsl_lost_error (if the stream source has been lost). * @return A copy of the full streaminfo of the inlet or NULL in the event that an error happened. * @note It is the user's responsibility to destroy it when it is no longer needed. */ extern LIBLSL_C_API lsl_streaminfo lsl_get_fullinfo(lsl_inlet in, double timeout, int32_t *ec); /** * Subscribe to the data stream. * * All samples pushed in at the other end from this moment onwards will be queued and * eventually be delivered in response to pull_sample() calls. * Pulling a sample without some preceding lsl_open_stream() is permitted (the stream will then be * opened implicitly). * @param in The lsl_inlet object to act on. * @param timeout Optional timeout of the operation. Use LSL_FOREVER to effectively disable it. * @param[out] ec Error code: if nonzero, can be either #lsl_timeout_error (if the timeout has * expired) or lsl_lost_error (if the stream source has been lost). */ extern LIBLSL_C_API void lsl_open_stream(lsl_inlet in, double timeout, int32_t *ec); /** * Drop the current data stream. * * All samples that are still buffered or in flight will be dropped and transmission * and buffering of data for this inlet will be stopped. If an application stops being * interested in data from a source (temporarily or not) but keeps the outlet alive, * it should call lsl_close_stream() to not waste unnecessary system and network * resources. */ extern LIBLSL_C_API void lsl_close_stream(lsl_inlet in); /** * @brief Retrieve an estimated time correction offset for the given stream. * * The first call to this function takes several milliseconds until a reliable first estimate is * obtained. Subsequent calls are instantaneous (and rely on periodic background updates). * * On a well-behaved network, the precision of these estimates should be below 1 ms (empirically it * is within +/-0.2 ms). * * To get a measure of whether the network is well-behaved, use #lsl_time_correction_ex and check * uncertainty (which maps to round-trip-time). 0.2 ms is typical of wired networks. * * 2 ms is typical of wireless networks. The number can be much higher on poor networks. * * @param in The lsl_inlet object to act on. * @param timeout Timeout to acquire the first time-correction estimate. * Use LSL_FOREVER to defuse the timeout. * @param[out] ec Error code: if nonzero, can be either #lsl_timeout_error (if the timeout has * expired) or lsl_lost_error (if the stream source has been lost). * @return The time correction estimate. * This is the number that needs to be added to a time stamp that was remotely generated via * lsl_local_clock() to map it into the local clock domain of this machine. */ extern LIBLSL_C_API double lsl_time_correction(lsl_inlet in, double timeout, int32_t *ec); /** @copydoc lsl_time_correction() * @param remote_time The current time of the remote computer that was used to generate this * time_correction. * If desired, the client can fit time_correction vs remote_time to improve the real-time * time_correction further. * @param uncertainty The maximum uncertainty of the given time correction. */ extern LIBLSL_C_API double lsl_time_correction_ex(lsl_inlet in, double *remote_time, double *uncertainty, double timeout, int32_t *ec); /** * Set post-processing flags to use. * * By default, the inlet performs NO post-processing and returns the ground-truth time stamps, which * can then be manually synchronized using time_correction(), and then smoothed/dejittered if * desired. * * This function allows automating these two and possibly more operations. * @warning When you enable this, you will no longer receive or be able to recover the original time * stamps. * @param in The lsl_inlet object to act on. * @param flags An integer that is the result of bitwise OR'ing one or more options from * #lsl_processing_options_t together (e.g., #proc_clocksync|#proc_dejitter); * a good setting is to use #proc_ALL. * @return The error code: if nonzero, can be #lsl_argument_error if an unknown flag was passed in. */ extern LIBLSL_C_API int32_t lsl_set_postprocessing(lsl_inlet in, uint32_t flags); /* === Pulling a sample from the inlet === */ /** * Pull a sample from the inlet and read it into a pointer to values. * Handles type checking & conversion. * @param in The #lsl_inlet object to act on. * @param[out] buffer A pointer to hold the resulting values. * @param buffer_elements The number of samples allocated in the buffer. * @attention It is the responsibility of the user to allocate enough memory. * @param timeout The timeout for this operation, if any. * Use #LSL_FOREVER to effectively disable it. It is also permitted to use 0.0 here; * in this case a sample is only returned if one is currently buffered. * @param[out] ec Error code: can be either no error or #lsl_lost_error * (if the stream source has been lost).
* @note If the timeout expires before a new sample was received the function returns 0.0; * ec is *not* set to #lsl_timeout_error (because this case is not considered an error condition). * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * lsl_time_correction() to it. * @{ */ extern LIBLSL_C_API double lsl_pull_sample_f(lsl_inlet in, float *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_d(lsl_inlet in, double *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_l(lsl_inlet in, int64_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_i(lsl_inlet in, int32_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_s(lsl_inlet in, int16_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_c(lsl_inlet in, char *buffer, int32_t buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API double lsl_pull_sample_str(lsl_inlet in, char **buffer, int32_t buffer_elements, double timeout, int32_t *ec); ///@} /** @copydoc lsl_pull_sample_f * These strings may contains 0's, therefore the lengths are read into the buffer_lengths array. * @param buffer_lengths * A pointer to an array that holds the resulting lengths for each returned binary string.*/ extern LIBLSL_C_API double lsl_pull_sample_buf(lsl_inlet in, char **buffer, uint32_t *buffer_lengths, int32_t buffer_elements, double timeout, int32_t *ec); /** * Pull a sample from the inlet and read it into a custom struct or buffer. * * Overall size checking but no type checking or conversion are done. * Do not use for variable-size/string-formatted streams. * @param in The #lsl_inlet object to act on. * @param[out] buffer A pointer to hold the resulting values. * @param buffer_bytes Length of the array held by buffer in bytes, not items * @param timeout The timeout for this operation, if any. * Use #LSL_FOREVER to effectively disable it. It is also permitted to use 0.0 here; * in this case a sample is only returned if one is currently buffered. * @param[out] ec Error code: can be either no error or #lsl_lost_error * (if the stream source has been lost).
* @note If the timeout expires before a new sample was received the function returns 0.0; * ec is *not* set to #lsl_timeout_error (because this case is not considered an error condition). * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * lsl_time_correction() to it. */ extern LIBLSL_C_API double lsl_pull_sample_v(lsl_inlet in, void *buffer, int32_t buffer_bytes, double timeout, int32_t *ec); /** * Pull a chunk of data from the inlet and read it into a buffer. * * Handles type checking & conversion. * * @attention Note that the provided data buffer size is measured in channel values (e.g. floats) * rather than in samples. * @param in The lsl_inlet object to act on. * @param[out] data_buffer A pointer to a buffer of data values where the results shall be stored. * @param[out] timestamp_buffer A pointer to a double buffer where time stamps shall be stored. * * If this is NULL, no time stamps will be returned. * @param data_buffer_elements The size of the data buffer, in channel data elements (of type T). * Must be a multiple of the stream's channel count. * @param timestamp_buffer_elements The size of the timestamp buffer. * * If a timestamp buffer is provided then this must correspond to the same number of samples as * data_buffer_elements. * @param timeout The timeout for this operation, if any. * * When the timeout expires, the function may return before the entire buffer is filled. * The default value of 0.0 will retrieve only data available for immediate pickup. * @param[out] ec Error code: can be either no error or #lsl_lost_error (if the stream source has * been lost). * @note if the timeout expires before a new sample was received the function returns 0.0; * ec is *not* set to #lsl_timeout_error (because this case is not considered an error condition). * @return data_elements_written Number of channel data elements written to the data buffer. * @{ */ extern LIBLSL_C_API unsigned long lsl_pull_chunk_f(lsl_inlet in, float *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_d(lsl_inlet in, double *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_l(lsl_inlet in, int64_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_i(lsl_inlet in, int32_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_s(lsl_inlet in, int16_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_c(lsl_inlet in, char *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); extern LIBLSL_C_API unsigned long lsl_pull_chunk_str(lsl_inlet in, char **data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); ///@} /** * Pull a chunk of data from the inlet and read it into an array of binary strings. * * These strings may contains 0's, therefore the lengths are read into the lengths_buffer array. * Handles type checking & conversion. * IMPORTANT: Note that the provided data buffer size is measured in channel values (e.g., floats) * rather than in samples. * @param in The lsl_inlet object to act on. * @param[out] data_buffer A pointer to a buffer of data values where the results shall be stored. * @param[out] lengths_buffer A pointer to an array that holds the resulting lengths for each * returned binary string. * @param timestamp_buffer A pointer to a buffer of timestamp values where time stamps shall be * stored. If this is NULL, no time stamps will be returned. * @param data_buffer_elements The size of the data buffer, in channel data elements (of type T). * Must be a multiple of the stream's channel count. * @param timestamp_buffer_elements The size of the timestamp buffer. If a timestamp buffer is * provided then this must correspond to the same number of samples as data_buffer_elements. * @param timeout The timeout for this operation, if any. * * When the timeout expires, the function may return before the entire buffer is filled. * * The default value of 0.0 will retrieve only data available for immediate pickup. * @param[out] ec Error code: can be either no error or #lsl_lost_error (if the stream source has * been lost). * @note If the timeout expires before a new sample was received the function returns 0.0; ec is * *not* set to #lsl_timeout_error (because this case is not considered an error condition). * @return data_elements_written Number of channel data elements written to the data buffer. */ extern LIBLSL_C_API unsigned long lsl_pull_chunk_buf(lsl_inlet in, char **data_buffer, uint32_t *lengths_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec); /** * Query whether samples are currently available for immediate pickup. * * Note that it is not a good idea to use samples_available() to determine whether * a pull_*() call would block: to be sure, set the pull timeout to 0.0 or an acceptably * low value. If the underlying implementation supports it, the value will be the number of * samples available (otherwise it will be 1 or 0). */ extern LIBLSL_C_API uint32_t lsl_samples_available(lsl_inlet in); /// Drop all queued not-yet pulled samples, return the nr of dropped samples extern LIBLSL_C_API uint32_t lsl_inlet_flush(lsl_inlet in); /** * Query whether the clock was potentially reset since the last call to lsl_was_clock_reset(). * * This is rarely-used function is only needed for applications that combine multiple time_correction * values to estimate precise clock drift if they should tolerate cases where the source machine was * hot-swapped or restarted. */ extern LIBLSL_C_API uint32_t lsl_was_clock_reset(lsl_inlet in); /** * Override the half-time (forget factor) of the time-stamp smoothing. * * The default is 90 seconds unless a different value is set in the config file. * * Using a longer window will yield lower jitter in the time stamps, but longer windows will have * trouble tracking changes in the clock rate (usually due to temperature changes); the default is * able to track changes up to 10 degrees C per minute sufficiently well. * @param in The lsl_inlet object to act on. * @param value The new value, in seconds. This is the time after which a past sample * will be weighted by 1/2 in the exponential smoothing window. * @return The error code: if nonzero, can be #lsl_argument_error if an unknown flag was passed in. */ extern LIBLSL_C_API int32_t lsl_smoothing_halftime(lsl_inlet in, float value); /// @} liblsl-1.17.7/include/lsl/outlet.h000066400000000000000000000403331517625163100170040ustar00rootroot00000000000000#pragma once #include "./common.h" #include "types.h" /// @file outlet.h Stream outlet functions /** @defgroup outlet The lsl_outlet object * * This object represents an outlet sending data to all connected inlets. * * The data is pushed sample-by-sample or chunk-by-chunk into the outlet, and can consist of single- * or multichannel data, regular or irregular sampling rate, with uniform value types (integers, * floats, doubles, strings). * * Streams can have arbitrary XML meta-data (akin to a file header). * By creating an outlet the stream is made visible to a collection of computers (defined by the * network settings/layout) where one can subscribe to it by creating an inlet. * @{ */ /** * Establish a new stream outlet. This makes the stream discoverable. * @param info The stream information to use for creating this stream. * Stays constant over the lifetime of the outlet. * @note the outlet makes a copy of the streaminfo object upon construction (so the old info should * still be destroyed.) * @param chunk_size Optionally the desired chunk granularity (in samples) for transmission. * If specified as 0, each push operation yields one chunk. * Stream recipients can have this setting bypassed. * @param max_buffered Optionally the maximum amount of data to buffer (in seconds if there is a * nominal sampling rate, otherwise x100 in samples). A good default is 360, which corresponds to 6 * minutes of data. Note that, for high-bandwidth data you will almost certainly want to use a lower * value here to avoid running out of RAM. * @return A newly created lsl_outlet handle or NULL in the event that an error occurred. */ extern LIBLSL_C_API lsl_outlet lsl_create_outlet(lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered); /** @copydoc lsl_create_outlet() * @param flags An integer that is the result of bitwise OR'ing one or more options from * #lsl_transport_options_t together (e.g., #transp_bufsize_samples|#transp_bufsize_thousandths) */ extern LIBLSL_C_API lsl_outlet lsl_create_outlet_ex( lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered, lsl_transport_options_t flags); /** * Destroy an outlet. * The outlet will no longer be discoverable after destruction and all connected inlets will stop * delivering data. */ extern LIBLSL_C_API void lsl_destroy_outlet(lsl_outlet out); /** Push a pointer to some values as a sample into the outlet. * Handles type checking & conversion. * @param out The lsl_outlet object through which to push the data. * @param data A pointer to values to push. The number of values pointed to must be no less than the * number of channels in the sample. * #lsl_local_clock(); if omitted, the current time is used. * @return Error code of the operation or lsl_no_error if successful (usually attributed to the * wrong data type). * @{ */ extern LIBLSL_C_API int32_t lsl_push_sample_f(lsl_outlet out, const float *data); extern LIBLSL_C_API int32_t lsl_push_sample_d(lsl_outlet out, const double *data); extern LIBLSL_C_API int32_t lsl_push_sample_l(lsl_outlet out, const int64_t *data); extern LIBLSL_C_API int32_t lsl_push_sample_i(lsl_outlet out, const int32_t *data); extern LIBLSL_C_API int32_t lsl_push_sample_s(lsl_outlet out, const int16_t *data); extern LIBLSL_C_API int32_t lsl_push_sample_c(lsl_outlet out, const char *data); extern LIBLSL_C_API int32_t lsl_push_sample_str(lsl_outlet out, const char **data); extern LIBLSL_C_API int32_t lsl_push_sample_v(lsl_outlet out, const void *data); /// @} /** @copydoc lsl_push_sample_f * @param timestamp Optionally the capture time of the sample, in agreement with * lsl_local_clock(); if omitted, the current time is used. * @{ */ extern LIBLSL_C_API int32_t lsl_push_sample_ft(lsl_outlet out, const float *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_dt(lsl_outlet out, const double *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_lt(lsl_outlet out, const int64_t *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_it(lsl_outlet out, const int32_t *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_st(lsl_outlet out, const int16_t *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_ct(lsl_outlet out, const char *data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_strt(lsl_outlet out, const char **data, double timestamp); extern LIBLSL_C_API int32_t lsl_push_sample_vt(lsl_outlet out, const void *data, double timestamp); /// @} /** @copydoc lsl_push_sample_ft * @param pushthrough Whether to push the sample through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. * @{ */ extern LIBLSL_C_API int32_t lsl_push_sample_ftp(lsl_outlet out, const float *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_dtp(lsl_outlet out, const double *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_ltp(lsl_outlet out, const int64_t *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_itp(lsl_outlet out, const int32_t *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_stp(lsl_outlet out, const int16_t *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_ctp(lsl_outlet out, const char *data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_strtp(lsl_outlet out, const char **data, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_sample_vtp(lsl_outlet out, const void *data, double timestamp, int32_t pushthrough); ///@} /** @copybrief lsl_push_sample_ftp * @see lsl_push_sample_ftp * @param out The lsl_outlet object through which to push the data. * @param data A pointer to values to push. The number of values pointed to must be no less than the * number of channels in the sample. * @param lengths A pointer the number of elements to push for each channel (string lengths). */ extern LIBLSL_C_API int32_t lsl_push_sample_buf(lsl_outlet out, const char **data, const uint32_t *lengths); /** @copydoc lsl_push_sample_buf * @param timestamp @see lsl_push_sample_ftp */ extern LIBLSL_C_API int32_t lsl_push_sample_buft(lsl_outlet out, const char **data, const uint32_t *lengths, double timestamp); /** @copydoc lsl_push_sample_buft * @param pushthrough @see lsl_push_sample_ftp */ extern LIBLSL_C_API int32_t lsl_push_sample_buftp(lsl_outlet out, const char **data, const uint32_t *lengths, double timestamp, int32_t pushthrough); /** Push a chunk of multiplexed samples into the outlet. One timestamp per sample is provided. * * @attention Note that the provided buffer size is measured in channel values (e.g. floats) rather * than in samples. * * Handles type checking & conversion. * @param out The lsl_outlet object through which to push the data. * @param data A buffer of channel values holding the data for zero or more successive samples to * send. * @param data_elements The number of data values (of type T) in the data buffer. Must be a multiple * of the channel count. * @return Error code of the operation (usually attributed to the wrong data type). * @{ */ extern LIBLSL_C_API int32_t lsl_push_chunk_f(lsl_outlet out, const float *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_d(lsl_outlet out, const double *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_l(lsl_outlet out, const int64_t *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_i(lsl_outlet out, const int32_t *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_s(lsl_outlet out, const int16_t *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_c(lsl_outlet out, const char *data, unsigned long data_elements); extern LIBLSL_C_API int32_t lsl_push_chunk_str(lsl_outlet out, const char **data, unsigned long data_elements); /// @} /** @copydoc lsl_push_chunk_f * @param timestamp Optionally the capture time of the most recent sample, in agreement with * lsl_local_clock(); if omitted, the current time is used. * The time stamps of other samples are automatically derived based on the sampling rate of the * stream. * @{ */ extern LIBLSL_C_API int32_t lsl_push_chunk_ft(lsl_outlet out, const float *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_dt(lsl_outlet out, const double *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_lt(lsl_outlet out, const int64_t *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_it(lsl_outlet out, const int32_t *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_st(lsl_outlet out, const int16_t *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_ct(lsl_outlet out, const char *data, unsigned long data_elements, double timestamp); extern LIBLSL_C_API int32_t lsl_push_chunk_strt(lsl_outlet out, const char **data, unsigned long data_elements, double timestamp); /// @} /** @copydoc lsl_push_chunk_ft * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. * @{ */ extern LIBLSL_C_API int32_t lsl_push_chunk_ftp(lsl_outlet out, const float *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_dtp(lsl_outlet out, const double *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_ltp(lsl_outlet out, const int64_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_itp(lsl_outlet out, const int32_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_stp(lsl_outlet out, const int16_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_ctp(lsl_outlet out, const char *data, unsigned long data_elements, double timestamp, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_strtp(lsl_outlet out, const char **data, unsigned long data_elements, double timestamp, int32_t pushthrough); /// @} /** @copydoc lsl_push_chunk_f * @param timestamps Buffer holding one time stamp for each sample in the data buffer. * @{ */ extern LIBLSL_C_API int32_t lsl_push_chunk_ftn(lsl_outlet out, const float *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_dtn(lsl_outlet out, const double *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_ltn(lsl_outlet out, const int64_t *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_itn(lsl_outlet out, const int32_t *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_stn(lsl_outlet out, const int16_t *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_ctn(lsl_outlet out, const char *data, unsigned long data_elements, const double *timestamps); extern LIBLSL_C_API int32_t lsl_push_chunk_strtn(lsl_outlet out, const char **data, unsigned long data_elements, const double *timestamps); /// @} /** @copydoc lsl_push_chunk_ftn * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. * @{ */ extern LIBLSL_C_API int32_t lsl_push_chunk_ftnp(lsl_outlet out, const float *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_dtnp(lsl_outlet out, const double *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_ltnp(lsl_outlet out, const int64_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_itnp(lsl_outlet out, const int32_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_stnp(lsl_outlet out, const int16_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_ctnp(lsl_outlet out, const char *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); extern LIBLSL_C_API int32_t lsl_push_chunk_strtnp(lsl_outlet out, const char **data, unsigned long data_elements, const double *timestamps, int32_t pushthrough); ///@} /** @copybrief lsl_push_chunk_ftp * @sa lsl_push_chunk_ftp * @param out The lsl_outlet object through which to push the data. * @param data An array of channel values holding the data to push. * @param lengths Pointer the number of elements to push for each value (string lengths) so that * `size(data[i])==lengths[i]`. * @param data_elements The number of data values in the data buffer. * Must be a multiple of the channel count. */ extern LIBLSL_C_API int32_t lsl_push_chunk_buf(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements); /** @copydoc lsl_push_chunk_buf @sa lsl_push_chunk_ftp @sa lsl_push_chunk_buf * @param timestamp Optionally the capture time of the most recent sample, in agreement with * lsl_local_clock(); if omitted, the current time is used. * The time stamps of other samples are automatically derived based on the sampling rate of the * stream. */ extern LIBLSL_C_API int32_t lsl_push_chunk_buft(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, double timestamp); /** @copydoc lsl_push_chunk_buft @sa lsl_push_chunk_ftp @sa lsl_push_chunk_buf * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ extern LIBLSL_C_API int32_t lsl_push_chunk_buftp(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, double timestamp, int32_t pushthrough); /** @copydoc lsl_push_chunk_buf @sa lsl_push_chunk_ftp @sa lsl_push_chunk_buf * @param timestamps Buffer holding one time stamp for each sample in the data buffer. */ extern LIBLSL_C_API int32_t lsl_push_chunk_buftn(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, const double *timestamps); /** @copydoc lsl_push_chunk_buftn @sa lsl_push_chunk_ftp @sa lsl_push_chunk_buf * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ extern LIBLSL_C_API int32_t lsl_push_chunk_buftnp(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, const double *timestamps, int32_t pushthrough); /** * Check whether consumers are currently registered. * While it does not hurt, there is technically no reason to push samples if there is no consumer. */ extern LIBLSL_C_API int32_t lsl_have_consumers(lsl_outlet out); /** * Wait until some consumer shows up (without wasting resources). * @return True if the wait was successful, false if the timeout expired. */ extern LIBLSL_C_API int32_t lsl_wait_for_consumers(lsl_outlet out, double timeout); /** * Retrieve a handle to the stream info provided by this outlet. * This is what was used to create the stream (and also has the Additional Network Information * fields assigned). * @return A copy of the streaminfo of the outlet or NULL in the event that an error occurred. * @note It is the user's responsibility to destroy it when it is no longer needed. * @sa lsl_destroy_streaminfo() */ extern LIBLSL_C_API lsl_streaminfo lsl_get_info(lsl_outlet out); ///@} liblsl-1.17.7/include/lsl/resolver.h000066400000000000000000000211071517625163100173270ustar00rootroot00000000000000#pragma once #include "common.h" #include "types.h" /// @file resolver.h Stream resolution functions /** @defgroup continuous_resolver The lsl_continuous_resolver * @ingroup resolve * * Streams can be resolved at a single timepoint once (@ref resolve) or continuously in the * background. * @{ */ /** * Construct a new #lsl_continuous_resolver that resolves all streams on the network. * * This is analogous to the functionality offered by the free function lsl_resolve_all(). * @param forget_after When a stream is no longer visible on the network (e.g. because it was shut * down), this is the time in seconds after which it is no longer reported by the resolver. * * The recommended default value is 5.0. */ extern LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver(double forget_after); /** * Construct a new lsl_continuous_resolver that resolves all streams with a specific value for a given * property. * * This is analogous to the functionality provided by the free function lsl_resolve_byprop() * @param prop The #lsl_streaminfo property that should have a specific value (e.g., "name", "type", * "source_id", or "desc/manufaturer"). * @param value The string value that the property should have (e.g., "EEG" as the type property). * @param forget_after When a stream is no longer visible on the network (e.g., because it was shut * down), this is the time in seconds after which it is no longer reported by the resolver. * The recommended default value is 5.0. */ extern LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_byprop(const char *prop, const char *value, double forget_after); /** * Construct a new lsl_continuous_resolver that resolves all streams that match a given XPath 1.0 * predicate. * * This is analogous to the functionality provided by the free function lsl_resolve_bypred() * @param pred The predicate string, e.g. * `"name='BioSemi'" or "type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32"` * @param forget_after When a stream is no longer visible on the network (e.g., because it was shut * down), this is the time in seconds after which it is no longer reported by the resolver. * The recommended default value is 5.0. */ extern LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_bypred(const char *pred, double forget_after); /** * Obtain the set of currently present streams on the network (i.e. resolve result). * * @param res A continuous resolver (previously created with one of the * lsl_create_continuous_resolver() functions). * @param buffer A user-allocated buffer to hold the current resolve results.
* @attention It is the user's responsibility to either destroy the resulting streaminfo objects or * to pass them back to the LSL during during creation of an inlet. * @attention The stream_infos returned by the resolver are only short versions that do not include * the lsl_get_desc() field (which can be arbitrarily big). * * To obtain the full stream information you need to call lsl_get_info() on the inlet after you have * created one. * @param buffer_elements The user-provided buffer length. * @return The number of results written into the buffer (never more than the provided # of slots) * or a negative number if an error has occurred (values corresponding to #lsl_error_code_t). */ extern LIBLSL_C_API int32_t lsl_resolver_results(lsl_continuous_resolver res, lsl_streaminfo *buffer, uint32_t buffer_elements); /// Destructor for the continuous resolver. extern LIBLSL_C_API void lsl_destroy_continuous_resolver(lsl_continuous_resolver res); /// @} /** @defgroup resolve Resolving streams on the network * @{*/ /** * Resolve all streams on the network. * * This function returns all currently available streams from any outlet on the network. * The network is usually the subnet specified at the local router, but may also include a multicast * group of machines (given that the network supports it), or a list of hostnames.
* These details may optionally be customized by the experimenter in a configuration file * (see page Network Connectivity in the LSL wiki). * This is the default mechanism used by the browsing programs and the recording program. * @param[out] buffer A user-allocated buffer to hold the resolve results. * @attention It is the user's responsibility to either destroy the resulting streaminfo objects or * to pass them back to the LSL during during creation of an inlet. * * @attention The stream_info's returned by the resolver are only short versions that do not include * the lsl_get_desc() field (which can be arbitrarily big). * To obtain the full stream information you need to call lsl_get_info() on the inlet after you have * created one. * @param buffer_elements The user-provided buffer length. * @param wait_time The waiting time for the operation, in seconds, to search for streams. * The recommended wait time is 1 second (or 2 for a busy and large recording operation). * @warning If this is too short (<0.5s) only a subset (or none) of the outlets that are present on * the network may be returned. * @return The number of results written into the buffer (never more than the provided # of slots) * or a negative number if an error has occurred (values corresponding to lsl_error_code_t). */ extern LIBLSL_C_API int32_t lsl_resolve_all(lsl_streaminfo *buffer, uint32_t buffer_elements, double wait_time); /** * Resolve all streams with a given value for a property. * * If the goal is to resolve a specific stream, this method is preferred over resolving all streams * and then selecting the desired one. * @param[out] buffer A user-allocated buffer to hold the resolve results. * @attention It is the user's responsibility to either destroy the resulting streaminfo objects or * to pass them back to the LSL during during creation of an inlet. * * @attention The stream_info's returned by the resolver are only short versions that do not include * the lsl_get_desc() field (which can be arbitrarily big). To obtain the full stream information * you need to call lsl_get_info() on the inlet after you have created one. * @param buffer_elements The user-provided buffer length. * @param prop The streaminfo property that should have a specific value (`"name"`, `"type"`, * `"source_id"`, or, e.g., `"desc/manufaturer"` if present). * @param value The string value that the property should have (e.g., "EEG" as the type). * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly none) will be returned. * @return The number of results written into the buffer (never more than the provided # of slots) * or a negative number if an error has occurred (values corresponding to #lsl_error_code_t). */ extern LIBLSL_C_API int32_t lsl_resolve_byprop(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *prop, const char *value, int32_t minimum, double timeout); /** * Resolve all streams that match a given predicate. * * Advanced query that allows to impose more conditions on the retrieved streams; * the given string is an [XPath 1.0 predicate](http://en.wikipedia.org/w/index.php?title=XPath_1.0) * for the `` node (omitting the surrounding []'s) * @param[out] buffer A user-allocated buffer to hold the resolve results. * @attention It is the user's responsibility to either destroy the resulting streaminfo objects or * to pass them back to the LSL during during creation of an inlet. * * @attention The stream_info's returned by the resolver are only short versions that do not include * the lsl_get_desc() field (which can be arbitrarily big). To obtain the full stream information * you need to call lsl_get_info() on the inlet after you have created one. * @param buffer_elements The user-provided buffer length. * @param pred The predicate string, e.g. * `name='BioSemi'` or `type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32` * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly none) * will be returned. * @return The number of results written into the buffer (never more than the provided # of slots) * or a negative number if an error has occurred (values corresponding to lsl_error_code_t). */ extern LIBLSL_C_API int32_t lsl_resolve_bypred(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *pred, int32_t minimum, double timeout); /// @} liblsl-1.17.7/include/lsl/streaminfo.h000066400000000000000000000215461517625163100176440ustar00rootroot00000000000000#pragma once #include "common.h" #include "types.h" /// @file streaminfo.h Stream info functions /** @defgroup streaminfo The lsl_streaminfo object * * The #lsl_streaminfo object keeps a stream's meta data and connection settings. * @{ */ /** * Construct a new streaminfo object. * * Core stream information is specified here. Any remaining meta-data can be added later. * @param name Name of the stream.
* Describes the device (or product series) that this stream makes available * (for use by programs, experimenters or data analysts). Cannot be empty. * @param type Content type of the stream. Please see https://github.com/sccn/xdf/wiki/Meta-Data (or * web search for: XDF meta-data) for pre-defined content-type names, but you can also make up your * own. The content type is the preferred way to find streams (as opposed to searching by name). * @param channel_count Number of channels per sample. * This stays constant for the lifetime of the stream. * @param nominal_srate The sampling rate (in Hz) as advertised by the * datasource, if regular (otherwise set to #LSL_IRREGULAR_RATE). * @param channel_format Format/type of each channel.
* If your channels have different formats, consider supplying multiple streams * or use the largest type that can hold them all (such as #cft_double64). * * A good default is #cft_float32. * @param source_id Unique identifier of the source or device, if available (e.g. a serial number). * Allows recipients to recover from failure even after the serving app or device crashes. * May in some cases also be constructed from device settings. * @return A newly created streaminfo handle or NULL in the event that an error occurred. */ extern LIBLSL_C_API lsl_streaminfo lsl_create_streaminfo(const char *name, const char *type, int32_t channel_count, double nominal_srate, lsl_channel_format_t channel_format, const char *source_id); /// Destroy a previously created streaminfo object. extern LIBLSL_C_API void lsl_destroy_streaminfo(lsl_streaminfo info); /// Copy an existing streaminfo object (rarely used). extern LIBLSL_C_API lsl_streaminfo lsl_copy_streaminfo(lsl_streaminfo info); /** * Name of the stream. * * This is a human-readable name. * For streams offered by device modules, it refers to the type of device or product series that is * generating the data of the stream. If the source is an application, the name may be a more * generic or specific identifier. Multiple streams with the same name can coexist, though * potentially at the cost of ambiguity (for the recording app or experimenter). * @return An immutable library-owned pointer to the string value. @sa lsl_destroy_string() */ extern LIBLSL_C_API const char *lsl_get_name(lsl_streaminfo info); /** * Content type of the stream. * * The content type is a short string such as "EEG", "Gaze" which describes the content carried by * the channel (if known). If a stream contains mixed content this value need not be assigned but * may instead be stored in the description of channel types. To be useful to applications and * automated processing systems using the recommended content types is preferred. Content types * usually follow those pre-defined in the [wiki](https://github.com/sccn/xdf/wiki/Meta-Data) (or * web search for: XDF meta-data). * @return An immutable library-owned pointer to the string value. @sa lsl_destroy_string() */ extern LIBLSL_C_API const char *lsl_get_type(lsl_streaminfo info); /** * Number of channels of the stream. * A stream has at least one channels; the channel count stays constant for all samples. */ extern LIBLSL_C_API int32_t lsl_get_channel_count(lsl_streaminfo info); /** * Sampling rate of the stream, according to the source (in Hz). * * If a stream is irregularly sampled, this should be set to #LSL_IRREGULAR_RATE. * * Note that no data will be lost even if this sampling rate is incorrect or if a device has * temporary hiccups, since all samples will be recorded anyway (except for those dropped by the * device itself). However, when the recording is imported into an application, a good importer may * correct such errors more accurately if the advertised sampling rate was close to the specs of the * device. */ extern LIBLSL_C_API double lsl_get_nominal_srate(lsl_streaminfo info); /** * Channel format of the stream. * All channels in a stream have the same format. * However, a device might offer multiple time-synched streams each with its own format. */ extern LIBLSL_C_API lsl_channel_format_t lsl_get_channel_format(lsl_streaminfo info); /** * Unique identifier of the stream's source, if available. * * The unique source (or device) identifier is an optional piece of information that, if available, * allows that endpoints (such as the recording program) can re-acquire a stream automatically once * it is back online. * @return An immutable library-owned pointer to the string value. @sa lsl_destroy_string() */ extern LIBLSL_C_API const char *lsl_get_source_id(lsl_streaminfo info); /** * Protocol version used to deliver the stream. */ extern LIBLSL_C_API int32_t lsl_get_version(lsl_streaminfo info); /** * Creation time stamp of the stream. * * This is the time stamp when the stream was first created * (as determined via local_clock() on the providing machine). */ extern LIBLSL_C_API double lsl_get_created_at(lsl_streaminfo info); /** * Unique ID of the stream outlet (once assigned). * * This is a unique identifier of the stream outlet, and is guaranteed to be different * across multiple instantiations of the same outlet (e.g., after a re-start). * @return An immutable library-owned pointer to the string value. @sa lsl_destroy_string() */ extern LIBLSL_C_API const char *lsl_get_uid(lsl_streaminfo info); /** * Session ID for the given stream. * * The session id is an optional human-assigned identifier of the recording session. * While it is rarely used, it can be used to prevent concurrent recording activitites * on the same sub-network (e.g., in multiple experiment areas) from seeing each other's streams * (assigned via a configuration file by the experimenter, see Network Connectivity on the LSL * wiki). * @return An immutable library-owned pointer to the string value. @sa lsl_destroy_string() */ extern LIBLSL_C_API const char *lsl_get_session_id(lsl_streaminfo info); /// Hostname of the providing machine (once bound to an outlet). Modification is not permitted. extern LIBLSL_C_API const char *lsl_get_hostname(lsl_streaminfo info); /** * Extended description of the stream. * * It is highly recommended that at least the channel labels are described here. * See code examples on the LSL wiki. Other information, such as amplifier settings, * measurement units if deviating from defaults, setup information, subject information, etc., * can be specified here, as well. Meta-data recommendations follow the XDF file format project * (github.com/sccn/xdf/wiki/Meta-Data or web search for: XDF meta-data). * * @attention if you use a stream content type for which meta-data recommendations exist, please * try to lay out your meta-data in agreement with these recommendations for compatibility with other applications. */ extern LIBLSL_C_API lsl_xml_ptr lsl_get_desc(lsl_streaminfo info); /** * Retrieve the entire streaminfo in XML format. * * This yields an XML document (in string form) whose top-level element is ``. The info * element contains one element for each field of the streaminfo class, including: * * - the core elements ``, ``, ``, ``, * ``, `` * - the misc elements ``, ``, ``, ``, * ``, ``, ``, ``, ``, * `` * - the extended description element `` with user-defined sub-elements. * @return A pointer to a copy of the XML text or NULL in the event that an error occurred. * @note It is the user's responsibility to deallocate this string when it is no longer needed. */ extern LIBLSL_C_API char *lsl_get_xml(lsl_streaminfo info); /// Number of bytes occupied by a channel (0 for string-typed channels). extern LIBLSL_C_API int32_t lsl_get_channel_bytes(lsl_streaminfo info); /// Number of bytes occupied by a sample (0 for string-typed channels). extern LIBLSL_C_API int32_t lsl_get_sample_bytes(lsl_streaminfo info); /** * Tries to match the stream info XML element @p info against an * XPath query. * * Example query strings: * @code * channel_count>5 and type='EEG' * type='TestStream' or contains(name,'Brain') * name='ExampleStream' * @endcode */ extern LIBLSL_C_API int32_t lsl_stream_info_matches_query(lsl_streaminfo info, const char *query); /// Create a streaminfo object from an XML representation extern LIBLSL_C_API lsl_streaminfo lsl_streaminfo_from_xml(const char *xml); /// @} liblsl-1.17.7/include/lsl/types.h000066400000000000000000000044301517625163100166320ustar00rootroot00000000000000#ifndef LSL_TYPES #define LSL_TYPES /** * @class lsl_streaminfo * Handle to a stream info object. * * Stores the declaration of a data stream. * Represents the following information: * * - stream data format (number of channels, channel format) * - core information (stream name, content type, sampling rate) * - optional meta-data about the stream content (channel labels, measurement units, etc.) * * Whenever a program wants to provide a new stream on the lab network it will typically first * create an lsl_streaminfo to describe its properties and then construct an #lsl_outlet with it to * create the stream on the network. Other parties who discover/resolve the outlet on the network * can query the stream info; it is also written to disk when recording the stream (playing a * similar role as a file header). */ typedef struct lsl_streaminfo_struct_ *lsl_streaminfo; /** * @class lsl_outlet * A stream outlet handle. * Outlets are used to make streaming data (and the meta-data) available on the lab network. */ typedef struct lsl_outlet_struct_ *lsl_outlet; /** * @class lsl_inlet * A stream inlet handle. * Inlets are used to receive streaming data (and meta-data) from the lab network. */ typedef struct lsl_inlet_struct_ *lsl_inlet; /** * @class lsl_xml_ptr * A lightweight XML element tree handle; models the description of a streaminfo object. * XML elements behave like advanced pointers into memory that is owned by some respective * streaminfo. * Has a name and can have multiple named children or have text content as value; * attributes are omitted. * @note The interface is modeled after a subset of pugixml's node type and is compatible with it. * Type-casts between pugi::xml_node_struct* and #lsl_xml_ptr are permitted (in both directions) * since the types are binary compatible. * @sa [pugixml documentation](https://pugixml.org/docs/manual.html#access). */ typedef struct lsl_xml_ptr_struct_ *lsl_xml_ptr; /** * @class lsl_continuous_resolver * * Handle to a convenience object that resolves streams continuously in the background throughout * its lifetime and which can be queried at any time for the set of streams that are currently * visible on the network. */ typedef struct lsl_continuous_resolver_ *lsl_continuous_resolver; #endif // LSL_TYPES liblsl-1.17.7/include/lsl/xml.h000066400000000000000000000071551517625163100162750ustar00rootroot00000000000000#pragma once #include "common.h" #include "types.h" /// @file inlet.h XML functions /** @defgroup xml_ptr The lsl_xml_ptr object * @{ */ // XML Tree Navigation /** Get the first child of the element. */ extern LIBLSL_C_API lsl_xml_ptr lsl_first_child(lsl_xml_ptr e); /** Get the last child of the element. */ extern LIBLSL_C_API lsl_xml_ptr lsl_last_child(lsl_xml_ptr e); /** Get the next sibling in the children list of the parent node. */ extern LIBLSL_C_API lsl_xml_ptr lsl_next_sibling(lsl_xml_ptr e); /** Get the previous sibling in the children list of the parent node. */ extern LIBLSL_C_API lsl_xml_ptr lsl_previous_sibling(lsl_xml_ptr e); /** Get the parent node. */ extern LIBLSL_C_API lsl_xml_ptr lsl_parent(lsl_xml_ptr e); // XML Tree Navigation by Name /** Get a child with a specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_child(lsl_xml_ptr e, const char *name); /** Get the next sibling with the specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_next_sibling_n(lsl_xml_ptr e, const char *name); /** Get the previous sibling with the specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_previous_sibling_n(lsl_xml_ptr e, const char *name); // Content Queries /** Whether this node is empty. */ extern LIBLSL_C_API int32_t lsl_empty(lsl_xml_ptr e); /** Whether this is a text body (instead of an XML element). True both for plain char data and CData. */ extern LIBLSL_C_API int32_t lsl_is_text(lsl_xml_ptr e); /** Name of the element. */ extern LIBLSL_C_API const char *lsl_name(lsl_xml_ptr e); /** Value of the element. */ extern LIBLSL_C_API const char *lsl_value(lsl_xml_ptr e); /** Get child value (value of the first child that is text). */ extern LIBLSL_C_API const char *lsl_child_value(lsl_xml_ptr e); /** Get child value of a child with a specified name. */ extern LIBLSL_C_API const char *lsl_child_value_n(lsl_xml_ptr e, const char *name); // Data Modification /// Append a child node with a given name, which has a (nameless) plain-text child with the given text value. extern LIBLSL_C_API lsl_xml_ptr lsl_append_child_value(lsl_xml_ptr e, const char *name, const char *value); /// Prepend a child node with a given name, which has a (nameless) plain-text child with the given text value. extern LIBLSL_C_API lsl_xml_ptr lsl_prepend_child_value(lsl_xml_ptr e, const char *name, const char *value); /// Set the text value of the (nameless) plain-text child of a named child node. extern LIBLSL_C_API int32_t lsl_set_child_value(lsl_xml_ptr e, const char *name, const char *value); /** * Set the element's name. * @return 0 if the node is empty (or if out of memory). */ extern LIBLSL_C_API int32_t lsl_set_name(lsl_xml_ptr e, const char *rhs); /** * Set the element's value. * @return 0 if the node is empty (or if out of memory). */ extern LIBLSL_C_API int32_t lsl_set_value(lsl_xml_ptr e, const char *rhs); /** Append a child element with the specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_append_child(lsl_xml_ptr e, const char *name); /** Prepend a child element with the specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_prepend_child(lsl_xml_ptr e, const char *name); /** Append a copy of the specified element as a child. */ extern LIBLSL_C_API lsl_xml_ptr lsl_append_copy(lsl_xml_ptr e, lsl_xml_ptr e2); /** Prepend a child element with the specified name. */ extern LIBLSL_C_API lsl_xml_ptr lsl_prepend_copy(lsl_xml_ptr e, lsl_xml_ptr e2); /** Remove a child element with the specified name. */ extern LIBLSL_C_API void lsl_remove_child_n(lsl_xml_ptr e, const char *name); /** Remove a specified child element. */ extern LIBLSL_C_API void lsl_remove_child(lsl_xml_ptr e, lsl_xml_ptr e2); /// @} liblsl-1.17.7/include/lsl_c.h000066400000000000000000000022101517625163100157620ustar00rootroot00000000000000#ifndef LSL_C_H #define LSL_C_H /** @file lsl_c.h LSL C API for the lab streaming layer * * The lab streaming layer provides a set of functions to make instrument data accessible * in real time within a lab network. From there, streams can be picked up by recording programs, * viewing programs or custom experiment applications that access data streams in real time. * * The API covers two areas: * - The "push API" allows to create stream outlets and to push data (regular or irregular * measurement time series, event data, coded audio/video frames, etc.) into them. * - The "pull API" allows to create stream inlets and read time-synched experiment data from them * (for recording, viewing or experiment control). * * To use this library you need to link to the liblsl library that comes with * this header. Under Visual Studio the library is linked in automatically. */ #ifdef __cplusplus extern "C" { #endif #include "lsl/common.h" #include "lsl/inlet.h" #include "lsl/outlet.h" #include "lsl/resolver.h" #include "lsl/streaminfo.h" #include "lsl/types.h" #include "lsl/xml.h" #ifdef __cplusplus } /* end extern "C" */ #endif #endif liblsl-1.17.7/include/lsl_cpp.h000066400000000000000000002252541517625163100163410ustar00rootroot00000000000000#ifndef LSL_CPP_H #define LSL_CPP_H /** * @file lsl_cpp.h * * C++ API for the lab streaming layer. * * The lab streaming layer provides a set of functions to make instrument data accessible * in real time within a lab network. From there, streams can be picked up by recording programs, * viewing programs or custom experiment applications that access data streams in real time. * * The API covers two areas: * - The "push API" allows to create stream outlets and to push data (regular or irregular * measurement time series, event data, coded audio/video frames, etc.) into them. * - The "pull API" allows to create stream inlets and read time-synched experiment data from them * (for recording, viewing or experiment control). * * To use this library you need to link to the shared library (lsl) that comes with * this header. Under Visual Studio the library is linked in automatically. */ #include #include #include #include extern "C" { #include "lsl_c.h" } namespace lsl { /// Assert that no error happened; throw appropriate exception otherwise int32_t check_error(int32_t ec); /// Constant to indicate that a stream has variable sampling rate. const double IRREGULAR_RATE = 0.0; /** * Constant to indicate that a sample has the next successive time stamp. * * This is an optional optimization to transmit less data per sample. * The stamp is then deduced from the preceding one according to the stream's sampling rate * (in the case of an irregular rate, the same time stamp as before will is assumed). */ const double DEDUCED_TIMESTAMP = -1.0; /** * A very large time duration (> 1 year) for timeout values. * * Note that significantly larger numbers can cause the timeout to be invalid on some operating * systems (e.g., 32-bit UNIX). */ const double FOREVER = 32000000.0; /// Data format of a channel (each transmitted sample holds an array of channels). enum channel_format_t { /** For up to 24-bit precision measurements in the appropriate physical unit (e.g., microvolts). Integers from -16777216 to 16777216 are represented accurately.*/ cf_float32 = 1, /// For universal numeric data as long as permitted by network & disk budget. /// The largest representable integer is 53-bit. cf_double64 = 2, /// For variable-length ASCII strings or data blobs, such as video frames, complex event /// descriptions, etc. cf_string = 3, /// For high-rate digitized formats that require 32-bit precision. /// Depends critically on meta-data to represent meaningful units. /// Useful for application event codes or other coded data. cf_int32 = 4, /// For very high rate signals (40Khz+) or consumer-grade audio (for professional audio float is /// recommended). cf_int16 = 5, /// For binary signals or other coded data. Not recommended for encoding string data. cf_int8 = 6, /// For now only for future compatibility. Support for this type is not yet exposed in all /// languages. Also, some builds of liblsl will not be able to send or receive data of this /// type. cf_int64 = 7, /// Can not be transmitted. cf_undefined = 0 }; /// Post-processing options for stream inlets. enum processing_options_t { /// No automatic post-processing; return the ground-truth time stamps for manual post-processing /// (this is the default behavior of the inlet). post_none = 0, /// Perform automatic clock synchronization; equivalent to manually adding the time_correction() /// value to the received time stamps. post_clocksync = 1, /// Remove jitter from time stamps. This will apply a smoothing algorithm to the received time /// stamps; the smoothing needs to see a minimum number of samples (30-120 seconds worst-case) /// until the remaining jitter is consistently below 1ms. post_dejitter = 2, /// Force the time-stamps to be monotonically ascending (only makes sense if timestamps are /// dejittered). post_monotonize = 4, /// Post-processing is thread-safe (same inlet can be read from by multiple threads); uses /// somewhat more CPU. post_threadsafe = 8, /// The combination of all possible post-processing options. post_ALL = 1 | 2 | 4 | 8 }; /** * Protocol version. * * The major version is `protocol_version() / 100`; * The minor version is `protocol_version() % 100`; * Clients with different minor versions are protocol-compatible with each other * while clients with different major versions will refuse to work together. */ inline int32_t protocol_version() { return lsl_protocol_version(); } /// @copydoc ::lsl_library_version() inline int32_t library_version() { return lsl_library_version(); } /** * Get a string containing library information. * * The format of the string shouldn't be used for anything important except giving a a debugging * person a good idea which exact library version is used. */ inline const char *library_info() { return lsl_library_info(); } /** * Obtain a local system time stamp in seconds. * * The resolution is better than a millisecond. * This reading can be used to assign time stamps to samples as they are being acquired. * If the "age" of a sample is known at a particular time (e.g., from USB transmission * delays), it can be used as an offset to local_clock() to obtain a better estimate of * when a sample was actually captured. See stream_outlet::push_sample() for a use case. */ inline double local_clock() { return lsl_local_clock(); } /// @section Stream Declaration class xml_element; /** * The stream_info object stores the declaration of a data stream. * * Represents the following information: * a) stream data format (number of channels, channel format) * b) core information (stream name, content type, sampling rate) * c) optional meta-data about the stream content (channel labels, measurement units, etc.) * * Whenever a program wants to provide a new stream on the lab network it will typically first * create a stream_info to describe its properties and then construct a stream_outlet with it to * create the stream on the network. Recipients who discover the outlet can query the stream_info; * it is also written to disk when recording the stream (playing a similar role as a file header). */ class stream_info { public: /** * Construct a new stream_info object. * * Core stream information is specified here. Any remaining meta-data can be added later. * @param name Name of the stream. Describes the device (or product series) that this stream * makes available (for use by programs, experimenters or data analysts). Cannot be empty. * @param type Content type of the stream. Please see https://github.com/sccn/xdf/wiki/Meta-Data * (or web search for: XDF meta-data) for pre-defined content-type names, but you can also make * up your own. The content type is the preferred way to find streams (as opposed to searching * by name). * @param channel_count Number of channels per sample. This stays constant for the lifetime of * the stream. * @param nominal_srate The sampling rate (in Hz) as advertised by the data source, if regular * (otherwise set to IRREGULAR_RATE). * @param channel_format Format/type of each channel. If your channels have different formats, * consider supplying multiple streams or use the largest type that can hold them all (such as * cf_double64). * @param source_id Unique identifier of the device or source of the data, if available (such as * the serial number). This is critical for system robustness since it allows recipients to * recover from failure even after the serving app, device or computer crashes (just by finding * a stream with the same source id on the network again). Therefore, it is highly recommended * to always try to provide whatever information can uniquely identify the data source itself. */ stream_info(const std::string &name, const std::string &type, int32_t channel_count = 1, double nominal_srate = IRREGULAR_RATE, channel_format_t channel_format = cf_float32, const std::string &source_id = std::string()) : obj(lsl_create_streaminfo((name.c_str()), (type.c_str()), channel_count, nominal_srate, (lsl_channel_format_t)channel_format, (source_id.c_str())), &lsl_destroy_streaminfo) { if (obj == nullptr) throw std::invalid_argument(lsl_last_error()); } /// Default contructor. stream_info(): stream_info("untitled", "", 0, 0, cf_undefined, ""){} /// Copy constructor. Only increments the reference count! @see clone() stream_info(const stream_info &) noexcept = default; stream_info(lsl_streaminfo handle) : obj(handle, &lsl_destroy_streaminfo) {} /// Clones a streaminfo object. stream_info clone() { return stream_info(lsl_copy_streaminfo(obj.get())); } // ======================== // === Core Information === // ======================== // (these fields are assigned at construction) /** * Name of the stream. * * This is a human-readable name. For streams offered by device modules, it refers to the type * of device or product series that is generating the data of the stream. If the source is an * application, the name may be a more generic or specific identifier. Multiple streams with the * same name can coexist, though potentially at the cost of ambiguity (for the recording app or * experimenter). */ std::string name() const { return lsl_get_name(obj.get()); } /** * Content type of the stream. * * The content type is a short string such as "EEG", "Gaze" which describes the content carried * by the channel (if known). If a stream contains mixed content this value need not be assigned * but may instead be stored in the description of channel types. To be useful to applications * and automated processing systems using the recommended content types is preferred. Content * types usually follow those pre-defined in https://github.com/sccn/xdf/wiki/Meta-Data (or web * search for: XDF meta-data). */ std::string type() const { return lsl_get_type(obj.get()); } /** * Number of channels of the stream. * * A stream has at least one channel; the channel count stays constant for all samples. */ int32_t channel_count() const { return lsl_get_channel_count(obj.get()); } /** * Sampling rate of the stream, according to the source (in Hz). * * If a stream is irregularly sampled, this should be set to IRREGULAR_RATE. * * Note that no data will be lost even if this sampling rate is incorrect or if a device has * temporary hiccups, since all samples will be recorded anyway (except for those dropped by the * device itself). However, when the recording is imported into an application, a good importer * may correct such errors more accurately if the advertised sampling rate was close to the * specs of the device. */ double nominal_srate() const { return lsl_get_nominal_srate(obj.get()); } /** * Channel format of the stream. * * All channels in a stream have the same format. However, a device might offer multiple * time-synched streams each with its own format. */ channel_format_t channel_format() const { return static_cast(lsl_get_channel_format(obj.get())); } /** * Unique identifier of the stream's source, if available. * * The unique source (or device) identifier is an optional piece of information that, if * available, allows that endpoints (such as the recording program) can re-acquire a stream * automatically once it is back online. */ std::string source_id() const { return lsl_get_source_id(obj.get()); } // ====================================== // === Additional Hosting Information === // ====================================== // (these fields are implicitly assigned once bound to an outlet/inlet) /// Protocol version used to deliver the stream. int32_t version() const { return lsl_get_version(obj.get()); } /** * Creation time stamp of the stream. * * This is the time stamp when the stream was first created * (as determined via #lsl::local_clock() on the providing machine). */ double created_at() const { return lsl_get_created_at(obj.get()); } /** * Unique ID of the stream outlet instance (once assigned). * * This is a unique identifier of the stream outlet, and is guaranteed to be different * across multiple instantiations of the same outlet (e.g., after a re-start). */ std::string uid() const { return lsl_get_uid(obj.get()); } /** * Session ID for the given stream. * * The session id is an optional human-assigned identifier of the recording session. * While it is rarely used, it can be used to prevent concurrent recording activitites * on the same sub-network (e.g., in multiple experiment areas) from seeing each other's streams * (assigned via a configuration file by the experimenter, see Network Connectivity in the LSL * wiki). */ std::string session_id() const { return lsl_get_session_id(obj.get()); } /// Hostname of the providing machine. std::string hostname() const { return lsl_get_hostname(obj.get()); } // ======================== // === Data Description === // ======================== /** * Extended description of the stream. * * It is highly recommended that at least the channel labels are described here. * See code examples on the LSL wiki. Other information, such as amplifier settings, * measurement units if deviating from defaults, setup information, subject information, etc., * can be specified here, as well. Meta-data recommendations follow the XDF file format project * (github.com/sccn/xdf/wiki/Meta-Data or web search for: XDF meta-data). * * Important: if you use a stream content type for which meta-data recommendations exist, please * try to lay out your meta-data in agreement with these recommendations for compatibility with * other applications. */ xml_element desc(); /// lsl_stream_info_matches_query bool matches_query(const char *query) const { return lsl_stream_info_matches_query(obj.get(), query) != 0; } // =============================== // === Miscellaneous Functions === // =============================== /** Retrieve the entire streaminfo in XML format. * This yields an XML document (in string form) whose top-level element is ``. The info * element contains one element for each field of the streaminfo class, including: * * - the core elements ``, ``, ``, ``, * ``, `` * - the misc elements ``, ``, ``, ``, * ``, ``, ``, ``, ``, * `` * - the extended description element `` with user-defined sub-elements. */ std::string as_xml() const { char *tmp = lsl_get_xml(obj.get()); std::string result(tmp); lsl_destroy_string(tmp); return result; } /// Number of bytes occupied by a channel (0 for string-typed channels). int32_t channel_bytes() const { return lsl_get_channel_bytes(obj.get()); } /// Number of bytes occupied by a sample (0 for string-typed channels). int32_t sample_bytes() const { return lsl_get_sample_bytes(obj.get()); } /// Get the implementation handle. std::shared_ptr handle() const { return obj; } /// Assignment operator. stream_info &operator=(const stream_info &rhs) { if (this != &rhs) obj = stream_info(rhs).handle(); return *this; } stream_info(stream_info &&rhs) noexcept = default; stream_info &operator=(stream_info &&rhs) noexcept = default; /// Utility function to create a stream_info from an XML representation static stream_info from_xml(const std::string &xml) { return stream_info(lsl_streaminfo_from_xml(xml.c_str())); } private: std::shared_ptr obj; }; // ======================= // ==== Stream Outlet ==== // ======================= /** A stream outlet. * Outlets are used to make streaming data (and the meta-data) available on the lab network. */ class stream_outlet { public: /** Establish a new stream outlet. This makes the stream discoverable. * @param info The stream information to use for creating this stream. Stays constant over the * lifetime of the outlet. * @param chunk_size Optionally the desired chunk granularity (in samples) for transmission. If * unspecified, each push operation yields one chunk. Inlets can override this setting. * @param max_buffered Optionally the maximum amount of data to buffer (in seconds if there is a * nominal sampling rate, otherwise x100 in samples). The default is 6 minutes of data. */ stream_outlet(const stream_info &info, int32_t chunk_size = 0, int32_t max_buffered = 360, lsl_transport_options_t flags = transp_default) : channel_count(info.channel_count()), sample_rate(info.nominal_srate()), obj(lsl_create_outlet_ex(info.handle().get(), chunk_size, max_buffered, flags), &lsl_destroy_outlet) {} // ======================================== // === Pushing a sample into the outlet === // ======================================== /** Push a C array of values as a sample into the outlet. * Each entry in the array corresponds to one channel. * The function handles type checking & conversion. * @param data An array of values to push (one per channel). * @param timestamp Optionally the capture time of the sample, in agreement with * lsl::local_clock(); if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of * buffering it with subsequent samples. * Note that the chunk_size, if specified at outlet construction, takes precedence over the * pushthrough flag. */ template void push_sample(const T data[N], double timestamp = 0.0, bool pushthrough = true) { check_numchan(N); push_sample(&data[0], timestamp, pushthrough); } /** Push a std vector of values as a sample into the outlet. * Each entry in the vector corresponds to one channel. The function handles type checking & * conversion. * @param data A vector of values to push (one for each channel). * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); * if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it with subsequent samples. Note that the chunk_size, if specified at outlet construction, * takes precedence over the pushthrough flag. */ template void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan(data.size()); push_sample(data.data(), timestamp, pushthrough); } /** Push a pointer to some values as a sample into the outlet. * This is a lower-level function for cases where data is available in some buffer. * Handles type checking & conversion. * @param data A pointer to values to push. The number of values pointed to must not be less * than the number of channels in the sample. * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); * if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it with subsequent samples. Note that the chunk_size, if specified at outlet construction, * takes precedence over the pushthrough flag. */ void push_sample(const float *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_ftp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const double *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_dtp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const int64_t *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_ltp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const int32_t *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_itp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const int16_t *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_stp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const char *data, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_ctp(obj.get(), (data), timestamp, pushthrough); } void push_sample(const std::string *data, double timestamp = 0.0, bool pushthrough = true) { std::vector lengths(channel_count); std::vector pointers(channel_count); for (int32_t k = 0; k < channel_count; k++) { pointers[k] = data[k].c_str(); lengths[k] = (uint32_t)data[k].size(); } lsl_push_sample_buftp(obj.get(), pointers.data(), lengths.data(), timestamp, pushthrough); } /** Push a packed C struct (of numeric data) as one sample into the outlet (search for * [`#``pragma pack`](https://stackoverflow.com/a/3318475/73299) for information on packing * structs appropriately).
* Overall size checking but no type checking or conversion are done.
* Can not be used forvariable-size / string-formatted data. * @param sample The sample struct to push. * @param timestamp Optionally the capture time of the sample, in agreement with * local_clock(); if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of * buffering it with subsequent samples. Note that the chunk_size, if specified at outlet * construction, takes precedence over the pushthrough flag. */ template void push_numeric_struct(const T &sample, double timestamp = 0.0, bool pushthrough = true) { if (info().sample_bytes() != sizeof(T)) throw std::runtime_error( "Provided object size does not match the stream's sample size."); push_numeric_raw((void *)&sample, timestamp, pushthrough); } /** Push a pointer to raw numeric data as one sample into the outlet. * This is the lowest-level function; performs no checking whatsoever. Cannot be used for * variable-size / string-formatted channels. * @param sample A pointer to the raw sample data to push. * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); * if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it with subsequent samples. Note that the chunk_size, if specified at outlet construction, * takes precedence over the pushthrough flag. */ void push_numeric_raw(const void *sample, double timestamp = 0.0, bool pushthrough = true) { lsl_push_sample_vtp(obj.get(), (sample), timestamp, pushthrough); } // =================================================== // === Pushing an chunk of samples into the outlet === // =================================================== /** Push a chunk of samples (batched into an STL vector) into the outlet. * @param samples A vector of samples in some supported format (each sample can be a data * pointer, data array, or std vector of data). * @param timestamp Optionally the capture time of the most recent sample, in agreement with * local_clock(); if omitted, the current time is used. The time stamps of other samples are * automatically derived according to the sampling rate of the stream. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk( const std::vector &samples, double timestamp = 0.0, bool pushthrough = true) { if (!samples.empty()) { if (timestamp == 0.0) timestamp = local_clock(); if (sample_rate != IRREGULAR_RATE) timestamp = timestamp - (samples.size() - 1) / sample_rate; push_sample(samples[0], timestamp, pushthrough && samples.size() == 1); for (std::size_t k = 1; k < samples.size(); k++) push_sample(samples[k], DEDUCED_TIMESTAMP, pushthrough && k == samples.size() - 1); } } /** Push a chunk of samples (batched into an STL vector) into the outlet. * Allows to specify a separate time stamp for each sample (for irregular-rate streams). * @param samples A vector of samples in some supported format (each sample can be a data * pointer, data array, or std vector of data). * @param timestamps A vector of capture times for each sample, in agreement with local_clock(). * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk(const std::vector &samples, const std::vector ×tamps, bool pushthrough = true) { for (unsigned k = 0; k < samples.size() - 1; k++) push_sample(samples[k], timestamps[k], false); if (!samples.empty()) push_sample(samples.back(), timestamps.back(), pushthrough); } /** Push a chunk of numeric data as C-style structs (batched into an STL vector) into the * outlet. This performs some size checking but no type checking. Can not be used for * variable-size / string-formatted data. * @param samples A vector of samples, as C structs. * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); * if omitted, the current time is used. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_numeric_structs( const std::vector &samples, double timestamp = 0.0, bool pushthrough = true) { if (!samples.empty()) { if (timestamp == 0.0) timestamp = local_clock(); if (sample_rate != IRREGULAR_RATE) timestamp = timestamp - (samples.size() - 1) / sample_rate; push_numeric_struct(samples[0], timestamp, pushthrough && samples.size() == 1); for (std::size_t k = 1; k < samples.size(); k++) push_numeric_struct( samples[k], DEDUCED_TIMESTAMP, pushthrough && k == samples.size() - 1); } } /** Push a chunk of numeric data from C-style structs (batched into an STL vector), into the * outlet. This performs some size checking but no type checking. Can not be used for * variable-size / string-formatted data. * @param samples A vector of samples, as C structs. * @param timestamps A vector of capture times for each sample, in agreement with local_clock(). * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_numeric_structs(const std::vector &samples, const std::vector ×tamps, bool pushthrough = true) { for (unsigned k = 0; k < samples.size() - 1; k++) push_numeric_struct(samples[k], timestamps[k], false); if (!samples.empty()) push_numeric_struct(samples.back(), timestamps.back(), pushthrough); } /** Push a chunk of multiplexed data into the outlet. * @name Push functions * @param buffer A buffer of channel values holding the data for zero or more successive samples * to send. * @param timestamp Optionally the capture time of the most recent sample, in agreement with * local_clock(); if omitted, the current time is used. The time stamps of other samples are * automatically derived according to the sampling rate of the stream. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_multiplexed( const std::vector &buffer, double timestamp = 0.0, bool pushthrough = true) { if (!buffer.empty()) push_chunk_multiplexed( buffer.data(), static_cast(buffer.size()), timestamp, pushthrough); } /** Push a chunk of multiplexed data into the outlet. One timestamp per sample is provided. * Allows to specify a separate time stamp for each sample (for irregular-rate streams). * @param buffer A buffer of channel values holding the data for zero or more successive samples * to send. * @param timestamps A buffer of timestamp values holding time stamps for each sample in the * data buffer. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_multiplexed(const std::vector &buffer, const std::vector ×tamps, bool pushthrough = true) { if (!buffer.empty() && !timestamps.empty()) push_chunk_multiplexed( buffer.data(), static_cast(buffer.size()), timestamps.data(), pushthrough); } /** Push a chunk of multiplexed samples into the outlet. Single timestamp provided. * @warning The provided buffer size is measured in channel values (e.g., floats), not samples. * @param buffer A buffer of channel values holding the data for zero or more successive samples * to send. * @param buffer_elements The number of channel values (of type T) in the buffer. Must be a * multiple of the channel count. * @param timestamp Optionally the capture time of the most recent sample, in agreement with * local_clock(); if omitted, the current time is used. The time stamps of other samples are * automatically derived based on the sampling rate of the stream. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the stream_outlet() constructur parameter @p chunk_size, * if specified at outlet construction, takes precedence over the pushthrough flag. */ void push_chunk_multiplexed(const float *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_ftp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const double *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_dtp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const int64_t *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_ltp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const int32_t *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_itp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const int16_t *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_stp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const char *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { lsl_push_chunk_ctp(obj.get(), buffer, static_cast(buffer_elements), timestamp, pushthrough); } void push_chunk_multiplexed(const std::string *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { if (buffer_elements) { std::vector lengths(buffer_elements); std::vector pointers(buffer_elements); for (std::size_t k = 0; k < buffer_elements; k++) { pointers[k] = buffer[k].c_str(); lengths[k] = (uint32_t)buffer[k].size(); } lsl_push_chunk_buftp(obj.get(), pointers.data(), lengths.data(), static_cast(buffer_elements), timestamp, pushthrough); } } /** Push a chunk of multiplexed samples into the outlet. One timestamp per sample is provided. * @warning Note that the provided buffer size is measured in channel values (e.g., floats) * rather than in samples. * @param data_buffer A buffer of channel values holding the data for zero or more successive * samples to send. * @param timestamp_buffer A buffer of timestamp values holding time stamps for each sample in * the data buffer. * @param data_buffer_elements The number of data values (of type T) in the data buffer. Must be * a multiple of the channel count. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ void push_chunk_multiplexed(const float *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_ftnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const double *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_dtnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const int64_t *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_ltnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const int32_t *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_itnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const int16_t *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_stnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const char *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { lsl_push_chunk_ctnp(obj.get(), data_buffer, static_cast(data_buffer_elements), (timestamp_buffer), pushthrough); } void push_chunk_multiplexed(const std::string *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { if (data_buffer_elements) { std::vector lengths(data_buffer_elements); std::vector pointers(data_buffer_elements); for (std::size_t k = 0; k < data_buffer_elements; k++) { pointers[k] = data_buffer[k].c_str(); lengths[k] = (uint32_t)data_buffer[k].size(); } lsl_push_chunk_buftnp(obj.get(), pointers.data(), lengths.data(), static_cast(data_buffer_elements), timestamp_buffer, pushthrough); } } // =============================== // === Miscellaneous Functions === // =============================== /** Check whether consumers are currently registered. * While it does not hurt, there is technically no reason to push samples if there is no * consumer. */ bool have_consumers() { return lsl_have_consumers(obj.get()) != 0; } /** Wait until some consumer shows up (without wasting resources). * @return True if the wait was successful, false if the timeout expired. */ bool wait_for_consumers(double timeout) { return lsl_wait_for_consumers(obj.get(), timeout) != 0; } /** Retrieve the stream info provided by this outlet. * This is what was used to create the stream (and also has the Additional Network Information * fields assigned). */ stream_info info() const { return stream_info(lsl_get_info(obj.get())); } /// Return a shared pointer to pass to C-API functions that aren't wrapped yet /// /// Example: @code lsl_push_chunk_buft(outlet.handle().get(), data, …); @endcode std::shared_ptr handle() { return obj; } /** Destructor. * The stream will no longer be discoverable after destruction and all paired inlets will stop * delivering data. */ ~stream_outlet() = default; /// stream_outlet move constructor stream_outlet(stream_outlet &&res) noexcept = default; stream_outlet &operator=(stream_outlet &&rhs) noexcept = default; private: // The outlet is a non-copyable object. stream_outlet(const stream_outlet &rhs); stream_outlet &operator=(const stream_outlet &rhs); /// Check whether a given data length matches the number of channels; throw if not void check_numchan(std::size_t N) const { if (N != static_cast(channel_count)) throw std::runtime_error("Provided element count (" + std::to_string(N) + ") does not match the stream's channel count (" + std::to_string(channel_count) + '.'); } int32_t channel_count; double sample_rate; std::shared_ptr obj; }; // =========================== // ==== Resolve Functions ==== // =========================== /** Resolve all streams on the network. * This function returns all currently available streams from any outlet on the network. * The network is usually the subnet specified at the local router, but may also include * a multicast group of machines (given that the network supports it), or list of hostnames. * These details may optionally be customized by the experimenter in a configuration file * (see Network Connectivity in the LSL wiki). * This is the default mechanism used by the browsing programs and the recording program. * @param wait_time The waiting time for the operation, in seconds, to search for streams. * If this is too short (<0.5s) only a subset (or none) of the outlets that are present on the * network may be returned. * @return A vector of stream info objects (excluding their desc field), any of which can * subsequently be used to open an inlet. The full info can be retrieve from the inlet. */ inline std::vector resolve_streams(double wait_time = 1.0) { lsl_streaminfo buffer[1024]; int nres = check_error(lsl_resolve_all(buffer, sizeof(buffer) / sizeof(lsl_streaminfo), wait_time)); return std::vector(&buffer[0], &buffer[nres]); } /** Resolve all streams with a specific value for a given property. * If the goal is to resolve a specific stream, this method is preferred over resolving all streams * and then selecting the desired one. * @param prop The stream_info property that should have a specific value (e.g., "name", "type", * "source_id", or "desc/manufaturer"). * @param value The string value that the property should have (e.g., "EEG" as the type property). * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly none) * will be returned. * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ inline std::vector resolve_stream(const std::string &prop, const std::string &value, int32_t minimum = 1, double timeout = FOREVER) { lsl_streaminfo buffer[1024]; int nres = check_error( lsl_resolve_byprop(buffer, sizeof(buffer) / sizeof(lsl_streaminfo), prop.c_str(), value.c_str(), minimum, timeout)); return std::vector(&buffer[0], &buffer[nres]); } /** Resolve all streams that match a given predicate. * * Advanced query that allows to impose more conditions on the retrieved streams; the given * string is an [XPath 1.0](http://en.wikipedia.org/w/index.php?title=XPath_1.0) predicate for * the `` node (omitting the surrounding []'s) * @param pred The predicate string, e.g. `name='BioSemi'` or * `type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32` * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly * none) will be returned. * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ inline std::vector resolve_stream( const std::string &pred, int32_t minimum = 1, double timeout = FOREVER) { lsl_streaminfo buffer[1024]; int nres = check_error(lsl_resolve_bypred(buffer, sizeof(buffer) / sizeof(lsl_streaminfo), pred.c_str(), minimum, timeout)); return std::vector(&buffer[0], &buffer[nres]); } // ====================== // ==== Stream Inlet ==== // ====================== /** A stream inlet. * Inlets are used to receive streaming data (and meta-data) from the lab network. */ class stream_inlet { public: /** * Construct a new stream inlet from a resolved stream info. * @param info A resolved stream info object (as coming from one of the resolver functions). * Note: The stream_inlet may also be constructed with a fully-specified stream_info, if the * desired channel format and count is already known up-front, but this is strongly discouraged * and should only ever be done if there is no time to resolve the stream up-front (e.g., due * to limitations in the client program). * @param max_buflen Optionally the maximum amount of data to buffer (in seconds if there is a * nominal sampling rate, otherwise x100 in samples). Recording applications want to use a * fairly large buffer size here, while real-time applications would only buffer as much as * they need to perform their next calculation. * @param max_chunklen Optionally the maximum size, in samples, at which chunks are transmitted * (the default corresponds to the chunk sizes used by the sender). * Recording applications can use a generous size here (leaving it to the network how to pack * things), while real-time applications may want a finer (perhaps 1-sample) granularity. * If left unspecified (=0), the sender determines the chunk granularity. * @param recover Try to silently recover lost streams that are recoverable (=those that that * have a source_id set). * In all other cases (recover is false or the stream is not recoverable) functions may throw a * lsl::lost_error if the stream's source is lost (e.g., due to an app or computer crash). */ stream_inlet(const stream_info &info, int32_t max_buflen = 360, int32_t max_chunklen = 0, bool recover = true, lsl_transport_options_t flags = transp_default) : channel_count(info.channel_count()), obj(lsl_create_inlet_ex(info.handle().get(), max_buflen, max_chunklen, recover, flags), &lsl_destroy_inlet) {} /// Return a shared pointer to pass to C-API functions that aren't wrapped yet /// /// Example: @code lsl_pull_sample_buf(inlet.handle().get(), buf, …); @endcode std::shared_ptr handle() { return obj; } /// Move constructor for stream_inlet stream_inlet(stream_inlet &&rhs) noexcept = default; stream_inlet &operator=(stream_inlet &&rhs) noexcept= default; /** Retrieve the complete information of the given stream, including the extended description. * Can be invoked at any time of the stream's lifetime. * @param timeout Timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been * lost). */ stream_info info(double timeout = FOREVER) { int32_t ec = 0; lsl_streaminfo res = lsl_get_fullinfo(obj.get(), timeout, &ec); check_error(ec); return stream_info(res); } /** Subscribe to the data stream. * All samples pushed in at the other end from this moment onwards will be queued and * eventually be delivered in response to pull_sample() or pull_chunk() calls. * Pulling a sample without some preceding open_stream() is permitted (the stream will then be * opened implicitly). * @param timeout Optional timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been * lost). */ void open_stream(double timeout = FOREVER) { int32_t ec = 0; lsl_open_stream(obj.get(), timeout, &ec); check_error(ec); } /** Drop the current data stream. * * All samples that are still buffered or in flight will be dropped and transmission * and buffering of data for this inlet will be stopped. If an application stops being * interested in data from a source (temporarily or not) but keeps the outlet alive, * it should call close_stream() to not waste unnecessary system and network * resources. */ void close_stream() { lsl_close_stream(obj.get()); } /** Retrieve an estimated time correction offset for the given stream. * * The first call to this function takes several milliseconds until a reliable first estimate * is obtained. Subsequent calls are instantaneous (and rely on periodic background updates). * On a well-behaved network, the precision of these estimates should be below 1 ms * (empirically it is within +/-0.2 ms). * * To get a measure of whether the network is well-behaved, use the extended version * time_correction(double*,double*,double) and check uncertainty (i.e. the round-trip-time). * * 0.2 ms is typical of wired networks. 2 ms is typical of wireless networks. * The number can be much higher on poor networks. * * @param timeout Timeout to acquire the first time-correction estimate (default: no timeout). * @return The time correction estimate. This is the number that needs to be added to a time * stamp that was remotely generated via lsl_local_clock() to map it into the local clock * domain of this machine. * @throws #lsl::timeout_error (if the timeout expires), or #lsl::lost_error (if the stream * source has been lost). */ double time_correction(double timeout = FOREVER) { int32_t ec = 0; double res = lsl_time_correction(obj.get(), timeout, &ec); check_error(ec); return res; } /** @copydoc time_correction(double) * @param remote_time The current time of the remote computer that was used to generate this * time_correction. If desired, the client can fit time_correction vs remote_time to improve * the real-time time_correction further. * @param uncertainty The maximum uncertainty of the given time correction. */ double time_correction(double *remote_time, double *uncertainty, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_time_correction_ex(obj.get(), remote_time, uncertainty, timeout, &ec); check_error(ec); return res; } /** Set post-processing flags to use. * * By default, the inlet performs NO post-processing and returns the ground-truth time * stamps, which can then be manually synchronized using .time_correction(), and then * smoothed/dejittered if desired.
* This function allows automating these two and possibly more operations.
* @warning When you enable this, you will no longer receive or be able to recover the original * time stamps. * @param flags An integer that is the result of bitwise OR'ing one or more options from * processing_options_t together (e.g., `post_clocksync|post_dejitter`); the default is to * enable all options. */ void set_postprocessing(uint32_t flags = post_ALL) { check_error(lsl_set_postprocessing(obj.get(), flags)); } // ======================================= // === Pulling a sample from the inlet === // ======================================= /** Pull a sample from the inlet and read it into an array of values. * Handles type checking & conversion. * @param sample An array to hold the resulting values. * @param timeout The timeout for this operation, if any. Use 0.0 to make the function * non-blocking. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ template double pull_sample(T sample[N], double timeout = FOREVER) { return pull_sample(&sample[0], N, timeout); } /** Pull a sample from the inlet and read it into a std vector of values. * Handles type checking & conversion and allocates the necessary memory in the vector if * necessary. * @param sample An STL vector to hold the resulting values. * @param timeout The timeout for this operation, if any. Use 0.0 to make the function * non-blocking. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } double pull_sample(std::vector &sample, double timeout = FOREVER) { sample.resize(channel_count); return pull_sample(&sample[0], (int32_t)sample.size(), timeout); } /** Pull a sample from the inlet and read it into a pointer to values. * Handles type checking & conversion. * @param buffer A pointer to hold the resulting values. * @param buffer_elements The number of samples allocated in the buffer. Note: it is the * responsibility of the user to allocate enough memory. * @param timeout The timeout for this operation, if any. Use 0.0 to make the function * non-blocking. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_sample(float *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_f(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(double *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_d(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(int64_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_l(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(int32_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_i(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(int16_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_s(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(char *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_c(obj.get(), buffer, buffer_elements, timeout, &ec); check_error(ec); return res; } double pull_sample(std::string *buffer, int32_t buffer_elements, double timeout = FOREVER) { int32_t ec = 0; if (buffer_elements) { std::vector result_strings(buffer_elements); std::vector result_lengths(buffer_elements); double res = lsl_pull_sample_buf( obj.get(), result_strings.data(), result_lengths.data(), buffer_elements, timeout, &ec); check_error(ec); for (int32_t k = 0; k < buffer_elements; k++) { buffer[k].assign(result_strings[k], result_lengths[k]); lsl_destroy_string(result_strings[k]); } return res; } else throw std::runtime_error( "Provided element count does not match the stream's channel count."); } /** * Pull a sample from the inlet and read it into a custom C-style struct. * * Overall size checking but no type checking or conversion are done. * Do not use for variable-size/string-formatted streams. * @param sample The raw sample object to hold the data (packed C-style struct). * Search for [`#``pragma pack`](https://stackoverflow.com/a/3318475/73299) for information * on how to pack structs correctly. * @param timeout The timeout for this operation, if any. Use 0.0 to make the function * non-blocking. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ template double pull_numeric_struct(T &sample, double timeout = FOREVER) { return pull_numeric_raw((void *)&sample, sizeof(T), timeout); } /** * Pull a sample from the inlet and read it into a pointer to raw data. * * No type checking or conversions are done (not recommended!).
* Do not use for variable-size/string-formatted streams. * @param sample A pointer to hold the resulting raw sample data. * @param buffer_bytes The number of bytes allocated in the buffer.
* Note: it is the responsibility of the user to allocate enough memory. * @param timeout The timeout for this operation, if any. Use 0.0 to make the function * non-blocking. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_numeric_raw(void *sample, int32_t buffer_bytes, double timeout = FOREVER) { int32_t ec = 0; double res = lsl_pull_sample_v(obj.get(), sample, buffer_bytes, timeout, &ec); check_error(ec); return res; } // ================================================= // === Pulling a chunk of samples from the inlet === // ================================================= /** * Pull a chunk of samples from the inlet. * * This is the most complete version, returning both the data and a timestamp for each sample. * @param chunk A vector of vectors to hold the samples. * @param timestamps A vector to hold the time stamps. * @return True if some data was obtained. * @throws lost_error (if the stream source has been lost). */ template bool pull_chunk(std::vector> &chunk, std::vector ×tamps) { std::vector sample; chunk.clear(); timestamps.clear(); while (double ts = pull_sample(sample, 0.0)) { chunk.push_back(sample); timestamps.push_back(ts); } return !chunk.empty(); } /** * Pull a chunk of samples from the inlet. * * This version returns only the most recent sample's time stamp. * @param chunk A vector of vectors to hold the samples. * @return The time when the most recent sample was captured * on the remote machine, or 0.0 if no new sample was available. * @throws lost_error (if the stream source has been lost) */ template double pull_chunk(std::vector> &chunk) { double timestamp = 0.0; std::vector sample; chunk.clear(); while (double ts = pull_sample(sample, 0.0)) { chunk.push_back(sample); timestamp = ts; } return timestamp; } /** * Pull a chunk of samples from the inlet. * * This function does not return time stamps for the samples. Invoked as: mychunk = * pull_chunk(); * @return A vector of vectors containing the obtained samples; may be empty. * @throws lost_error (if the stream source has been lost) */ template std::vector> pull_chunk() { std::vector> result; std::vector sample; while (pull_sample(sample, 0.0)) result.push_back(sample); return result; } /** * Pull a chunk of data from the inlet into a pre-allocated buffer. * * This is a high-performance function that performs no memory allocations * (useful for very high data rates or on low-powered devices). * @warning The provided buffer size is measured in channel values (e.g., floats), not samples. * @param data_buffer A pointer to a buffer of data values where the results shall be stored. * @param timestamp_buffer A pointer to a buffer of timestamp values where time stamps shall be * stored. If this is NULL, no time stamps will be returned. * @param data_buffer_elements The size of the data buffer, in channel data elements (of type * T). Must be a multiple of the stream's channel count. * @param timestamp_buffer_elements The size of the timestamp buffer. If a timestamp buffer is * provided then this must correspond to the same number of samples as data_buffer_elements. * @param timeout The timeout for this operation, if any. When the timeout expires, the function * may return before the entire buffer is filled. The default value of 0.0 will retrieve only * data available for immediate pickup. * @return data_elements_written Number of channel data elements written to the data buffer. * @throws lost_error (if the stream source has been lost). */ std::size_t pull_chunk_multiplexed(float *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_f(obj.get(), data_buffer, timestamp_buffer, (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(double *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_d(obj.get(), data_buffer, timestamp_buffer, (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(int64_t *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_l(obj.get(), data_buffer, timestamp_buffer, (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(int32_t *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_i(obj.get(), data_buffer, timestamp_buffer, (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(int16_t *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_s(obj.get(), data_buffer, timestamp_buffer, (unsigned long)data_buffer_elements, (unsigned long)timestamp_buffer_elements, timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(char *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; std::size_t res = lsl_pull_chunk_c(obj.get(), data_buffer, timestamp_buffer, static_cast(data_buffer_elements), static_cast(timestamp_buffer_elements), timeout, &ec); check_error(ec); return res; } std::size_t pull_chunk_multiplexed(std::string *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { int32_t ec = 0; if (data_buffer_elements) { std::vector result_strings(data_buffer_elements); std::vector result_lengths(data_buffer_elements); std::size_t num = lsl_pull_chunk_buf(obj.get(), result_strings.data(), result_lengths.data(), timestamp_buffer, static_cast(data_buffer_elements), static_cast(timestamp_buffer_elements), timeout, &ec); check_error(ec); for (std::size_t k = 0; k < num; k++) { data_buffer[k].assign(result_strings[k], result_lengths[k]); lsl_destroy_string(result_strings[k]); } return num; }; return 0; } /** * Pull a multiplexed chunk of samples and optionally the sample timestamps from the inlet. * * @param chunk A vector to hold the multiplexed (Sample 1 Channel 1, * S1C2, S2C1, S2C2, S3C1, S3C2, ...) samples * @param timestamps A vector to hold the timestamps or nullptr * @param timeout Time to wait for the first sample. The default value of 0.0 will not wait * for data to arrive, pulling only samples already received. * @param append (True:) Append data or (false:) clear them first * @return True if some data was obtained. * @throws lost_error (if the stream source has been lost). */ template bool pull_chunk_multiplexed(std::vector &chunk, std::vector *timestamps = nullptr, double timeout = 0.0, bool append = false) { if (!append) { chunk.clear(); if (timestamps) timestamps->clear(); } std::vector sample; double ts; if ((ts = pull_sample(sample, timeout)) == 0.0) return false; chunk.insert(chunk.end(), sample.begin(), sample.end()); if (timestamps) timestamps->push_back(ts); const auto target = samples_available(); chunk.reserve(chunk.size() + target * this->channel_count); if (timestamps) timestamps->reserve(timestamps->size() + target); while ((ts = pull_sample(sample, 0.0)) != 0.0) { #if LSL_CPP11 chunk.insert(chunk.end(), std::make_move_iterator(sample.begin()), std::make_move_iterator(sample.end())); #else chunk.insert(chunk.end(), sample.begin(), sample.end()); #endif if (timestamps) timestamps->push_back(ts); } return true; } /** * Pull a chunk of samples from the inlet. * * This is the most complete version, returning both the data and a timestamp for each sample. * @param chunk A vector of C-style structs to hold the samples. * @param timestamps A vector to hold the time stamps. * @return True if some data was obtained. * @throws lost_error (if the stream source has been lost) */ template bool pull_chunk_numeric_structs(std::vector &chunk, std::vector ×tamps) { T sample; chunk.clear(); timestamps.clear(); while (double ts = pull_numeric_struct(sample, 0.0)) { chunk.push_back(sample); timestamps.push_back(ts); } return !chunk.empty(); } /** * Pull a chunk of samples from the inlet. * * This version returns only the most recent sample's time stamp. * @param chunk A vector of C-style structs to hold the samples. * @return The time when the most recent sample was captured * on the remote machine, or 0.0 if no new sample was available. * @throws lost_error (if the stream source has been lost) */ template double pull_chunk_numeric_structs(std::vector &chunk) { double timestamp = 0.0; T sample; chunk.clear(); while (double ts = pull_numeric_struct(sample, 0.0)) { chunk.push_back(sample); timestamp = ts; } return timestamp; } /** * Pull a chunk of samples from the inlet. * * This function does not return time stamps. Invoked as: mychunk = pull_chunk(); * @return A vector of C-style structs containing the obtained samples; may be empty. * @throws lost_error (if the stream source has been lost) */ template std::vector pull_chunk_numeric_structs() { std::vector result; T sample; while (pull_numeric_struct(sample, 0.0)) result.push_back(sample); return result; } /** * Query whether samples are currently available for immediate pickup. * * Note that it is not a good idea to use samples_available() to determine whether * a pull_*() call would block: to be sure, set the pull timeout to 0.0 or an acceptably * low value. If the underlying implementation supports it, the value will be the number of * samples available (otherwise it will be 1 or 0). */ std::size_t samples_available() { return lsl_samples_available(obj.get()); } /// Drop all queued not-yet pulled samples, return the nr of dropped samples uint32_t flush() noexcept { return lsl_inlet_flush(obj.get()); } /** * Query whether the clock was potentially reset since the last call to was_clock_reset(). * * This is a rarely-used function that is only useful to applications that combine multiple * time_correction values to estimate precise clock drift; it allows to tolerate cases where the * source machine was hot-swapped or restarted in between two measurements. */ bool was_clock_reset() { return lsl_was_clock_reset(obj.get()) != 0; } /** * Override the half-time (forget factor) of the time-stamp smoothing. * * The default is 90 seconds unless a different value is set in the config file. * Using a longer window will yield lower jitter in the time stamps, but longer * windows will have trouble tracking changes in the clock rate (usually due to * temperature changes); the default is able to track changes up to 10 * degrees C per minute sufficiently well. */ void smoothing_halftime(float value) { check_error(lsl_smoothing_halftime(obj.get(), value)); } int get_channel_count() const { return channel_count; } private: // The inlet is a non-copyable object. stream_inlet(const stream_inlet &rhs); stream_inlet &operator=(const stream_inlet &rhs); int32_t channel_count; std::shared_ptr obj; }; // ===================== // ==== XML Element ==== // ===================== /** * A lightweight XML element tree; models the .desc() field of stream_info. * * Has a name and can have multiple named children or have text content as value; attributes are * omitted. Insider note: The interface is modeled after a subset of pugixml's node type and is * compatible with it. See also * https://pugixml.org/docs/manual.html#access for additional documentation. */ class xml_element { public: /// Constructor. xml_element(lsl_xml_ptr obj = 0) : obj(obj) {} // === Tree Navigation === /// Get the first child of the element. xml_element first_child() const { return lsl_first_child(obj); } /// Get the last child of the element. xml_element last_child() const { return lsl_last_child(obj); } /// Get the next sibling in the children list of the parent node. xml_element next_sibling() const { return lsl_next_sibling(obj); } /// Get the previous sibling in the children list of the parent node. xml_element previous_sibling() const { return lsl_previous_sibling(obj); } /// Get the parent node. xml_element parent() const { return lsl_parent(obj); } // === Tree Navigation by Name === /// Get a child with a specified name. xml_element child(const std::string &name) const { return lsl_child(obj, (name.c_str())); } /// Get the next sibling with the specified name. xml_element next_sibling(const std::string &name) const { return lsl_next_sibling_n(obj, (name.c_str())); } /// Get the previous sibling with the specified name. xml_element previous_sibling(const std::string &name) const { return lsl_previous_sibling_n(obj, (name.c_str())); } // === Content Queries === /// Whether this node is empty. bool empty() const { return lsl_empty(obj) != 0; } /// Is this a text body (instead of an XML element)? True both for plain char data and CData. bool is_text() const { return lsl_is_text(obj) != 0; } /// Name of the element. const char *name() const { return lsl_name(obj); } /// Value of the element. const char *value() const { return lsl_value(obj); } /// Get child value (value of the first child that is text). const char *child_value() const { return lsl_child_value(obj); } /// Get child value of a child with a specified name. const char *child_value(const std::string &name) const { return lsl_child_value_n(obj, (name.c_str())); } // === Modification === /// Append a child node with a given name, which has a (nameless) plain-text child with the /// given text value. xml_element append_child_value(const std::string &name, const std::string &value) { return lsl_append_child_value(obj, (name.c_str()), (value.c_str())); } /// Prepend a child node with a given name, which has a (nameless) plain-text child with the /// given text value. xml_element prepend_child_value(const std::string &name, const std::string &value) { return lsl_prepend_child_value(obj, (name.c_str()), (value.c_str())); } /// Set the text value of the (nameless) plain-text child of a named child node. bool set_child_value(const std::string &name, const std::string &value) { return lsl_set_child_value(obj, (name.c_str()), (value.c_str())) != 0; } /** * Set the element's name. * @return False if the node is empty (or if out of memory). */ bool set_name(const std::string &rhs) { return lsl_set_name(obj, rhs.c_str()) != 0; } /** * Set the element's value. * @return False if the node is empty (or if out of memory). */ bool set_value(const std::string &rhs) { return lsl_set_value(obj, rhs.c_str()) != 0; } /// Append a child element with the specified name. xml_element append_child(const std::string &name) { return lsl_append_child(obj, name.c_str()); } /// Prepend a child element with the specified name. xml_element prepend_child(const std::string &name) { return lsl_prepend_child(obj, (name.c_str())); } /// Append a copy of the specified element as a child. xml_element append_copy(const xml_element &e) { return lsl_append_copy(obj, e.obj); } /// Prepend a child element with the specified name. xml_element prepend_copy(const xml_element &e) { return lsl_prepend_copy(obj, e.obj); } /// Remove a child element with the specified name. void remove_child(const std::string &name) { lsl_remove_child_n(obj, (name.c_str())); } /// Remove a specified child element. void remove_child(const xml_element &e) { lsl_remove_child(obj, e.obj); } private: lsl_xml_ptr obj; }; inline xml_element stream_info::desc() { return lsl_get_desc(obj.get()); } // ============================= // ==== Continuous Resolver ==== // ============================= /** * A convenience class that resolves streams continuously in the background throughout * its lifetime and which can be queried at any time for the set of streams that are currently * visible on the network. */ class continuous_resolver { public: /** * Construct a new continuous_resolver that resolves all streams on the network. * * This is analogous to the functionality offered by the free function resolve_streams(). * @param forget_after When a stream is no longer visible on the network (e.g., because it was * shut down), this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver(double forget_after = 5.0) : obj(lsl_create_continuous_resolver(forget_after), &lsl_destroy_continuous_resolver) {} /** * Construct a new continuous_resolver that resolves all streams with a specific value for a * given property. * * This is analogous to the functionality provided by the free function resolve_stream(prop,value). * @param prop The stream_info property that should have a specific value (e.g., "name", "type", * "source_id", or "desc/manufaturer"). * @param value The string value that the property should have (e.g., "EEG" as the type * property). * @param forget_after When a stream is no longer visible on the network (e.g., because it was * shut down), this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver( const std::string &prop, const std::string &value, double forget_after = 5.0) : obj(lsl_create_continuous_resolver_byprop((prop.c_str()), (value.c_str()), forget_after), &lsl_destroy_continuous_resolver) {} /** * Construct a new continuous_resolver that resolves all streams that match a given XPath 1.0 * predicate. * * This is analogous to the functionality provided by the free function resolve_stream(pred). * @param pred The predicate string, e.g. * `name='BioSemi'` or * `type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32` * @param forget_after When a stream is no longer visible on the network (e.g., because it was * shut down), this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver(const std::string &pred, double forget_after = 5.0) : obj(lsl_create_continuous_resolver_bypred((pred.c_str()), forget_after), &lsl_destroy_continuous_resolver) {} /** * Obtain the set of currently present streams on the network (i.e. resolve result). * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ std::vector results() { lsl_streaminfo buffer[1024]; return std::vector( buffer, buffer + check_error(lsl_resolver_results(obj.get(), buffer, sizeof(buffer)))); } /// Move constructor for stream_inlet continuous_resolver(continuous_resolver &&rhs) noexcept = default; continuous_resolver &operator=(continuous_resolver &&rhs) noexcept = default; private: std::unique_ptr obj; }; // =============================== // ==== Exception Definitions ==== // =============================== /// Exception class that indicates that a stream inlet's source has been irrecoverably lost. class lost_error : public std::runtime_error { public: explicit lost_error(const std::string &msg) : std::runtime_error(msg) {} }; /// Exception class that indicates that an operation failed due to a timeout. class timeout_error : public std::runtime_error { public: explicit timeout_error(const std::string &msg) : std::runtime_error(msg) {} }; /// Check error codes returned from the C interface and translate into appropriate exceptions. inline int32_t check_error(int32_t ec) { if (ec < 0) { switch (ec) { case lsl_timeout_error: throw timeout_error("The operation has timed out."); case lsl_lost_error: throw lost_error( "The stream has been lost; to continue reading, you need to re-resolve it."); case lsl_argument_error: throw std::invalid_argument("An argument was incorrectly specified."); case lsl_internal_error: throw std::runtime_error("An internal error has occurred."); default: throw std::runtime_error("An unknown error has occurred."); } } return ec; } } // namespace lsl #endif // LSL_CPP_H liblsl-1.17.7/lsl.entitlements000066400000000000000000000011721517625163100163270ustar00rootroot00000000000000 com.apple.security.network.client com.apple.security.network.server com.apple.security.network.multicast liblsl-1.17.7/pi.cmake000066400000000000000000000007741517625163100145210ustar00rootroot00000000000000SET(CMAKE_SYSTEM_NAME Linux) SET(CMAKE_SYSTEM_VERSION 1) SET(CMAKE_C_COMPILER $ENV{PITOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc) SET(CMAKE_CXX_COMPILER $ENV{PITOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc) SET(CMAKE_FIND_ROOT_PATH $ENV{PITOOLS}/arm-bcm2708/arm-linux-gnueabihf/arm-linux-gnueabihf/sysroot/) SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) add_definitions(-Wall) liblsl-1.17.7/project/000077500000000000000000000000001517625163100145455ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/000077500000000000000000000000001517625163100156075ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetAllStreams/000077500000000000000000000000001517625163100203165ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetAllStreams/GetAllStreams.xcodeproj/000077500000000000000000000000001517625163100250215ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetAllStreams/GetAllStreams.xcodeproj/project.pbxproj000066400000000000000000000236131517625163100301020ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* GetAllStreams.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* GetAllStreams.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* GetAllStreams */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = GetAllStreams; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* GetAllStreams.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GetAllStreams.cpp; path = ../../../../../Apps/Examples/GetAllStreams.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* GetAllStreams.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* GetAllStreams */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* GetAllStreams */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetAllStreams" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = GetAllStreams; productName = GetAllStreams; productReference = F572E40521B208F200C0D82F /* GetAllStreams */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetAllStreams" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* GetAllStreams */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* GetAllStreams.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetAllStreams" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetAllStreams" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/GetFullinfo/000077500000000000000000000000001517625163100200255ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetFullinfo/GetFullinfo.xcodeproj/000077500000000000000000000000001517625163100242375ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetFullinfo/GetFullinfo.xcodeproj/project.pbxproj000066400000000000000000000235451517625163100273240ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* GetFullinfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* GetFullinfo.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* GetFullinfo */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = GetFullinfo; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* GetFullinfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GetFullinfo.cpp; path = ../../../../../Apps/Examples/GetFullinfo.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* GetFullinfo.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* GetFullinfo */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* GetFullinfo */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetFullinfo" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = GetFullinfo; productName = GetFullinfo; productReference = F572E40521B208F200C0D82F /* GetFullinfo */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetFullinfo" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* GetFullinfo */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* GetFullinfo.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetFullinfo" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetFullinfo" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/GetTimeCorrection/000077500000000000000000000000001517625163100211755ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetTimeCorrection/GetTimeCorrection.xcodeproj/000077500000000000000000000000001517625163100265575ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/GetTimeCorrection/GetTimeCorrection.xcodeproj/project.pbxproj000066400000000000000000000237271517625163100316460ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* GetTimeCorrection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* GetTimeCorrection.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* GetTimeCorrection */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = GetTimeCorrection; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* GetTimeCorrection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = GetTimeCorrection.cpp; path = ../../../../../Apps/Examples/GetTimeCorrection.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* GetTimeCorrection.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* GetTimeCorrection */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* GetTimeCorrection */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetTimeCorrection" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = GetTimeCorrection; productName = GetTimeCorrection; productReference = F572E40521B208F200C0D82F /* GetTimeCorrection */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetTimeCorrection" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* GetTimeCorrection */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* GetTimeCorrection.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "GetTimeCorrection" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "GetTimeCorrection" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/ReceiveData/000077500000000000000000000000001517625163100177635ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveData/ReceiveData.xcodeproj/000077500000000000000000000000001517625163100241335ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveData/ReceiveData.xcodeproj/project.pbxproj000066400000000000000000000233451517625163100272160ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* ReceiveData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* ReceiveData.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* ReceiveData */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ReceiveData; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* ReceiveData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ReceiveData.cpp; path = ../../../../../Apps/Examples/ReceiveData.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* ReceiveData.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* ReceiveData */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* ReceiveData */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveData" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = ReceiveData; productName = ReceiveData; productReference = F572E40521B208F200C0D82F /* ReceiveData */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveData" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* ReceiveData */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* ReceiveData.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveData" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveData" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/ReceiveDataInChunks/000077500000000000000000000000001517625163100214265ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveDataInChunks/ReceiveDataInChunks.xcodeproj/000077500000000000000000000000001517625163100272415ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveDataInChunks/ReceiveDataInChunks.xcodeproj/project.pbxproj000066400000000000000000000237751517625163100323330ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* ReceiveDataInChunks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* ReceiveDataInChunks.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* ReceiveDataInChunks */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ReceiveDataInChunks; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* ReceiveDataInChunks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ReceiveDataInChunks.cpp; path = ../../../../../Apps/Examples/ReceiveDataInChunks.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* ReceiveDataInChunks.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* ReceiveDataInChunks */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* ReceiveDataInChunks */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveDataInChunks" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = ReceiveDataInChunks; productName = ReceiveDataInChunks; productReference = F572E40521B208F200C0D82F /* ReceiveDataInChunks */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveDataInChunks" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* ReceiveDataInChunks */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* ReceiveDataInChunks.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveDataInChunks" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveDataInChunks" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/ReceiveDataSimple/000077500000000000000000000000001517625163100211355ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveDataSimple/ReceiveDataSimple.xcodeproj/000077500000000000000000000000001517625163100264575ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveDataSimple/ReceiveDataSimple.xcodeproj/project.pbxproj000066400000000000000000000235271517625163100315440ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* ReceiveDataSimple.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* ReceiveDataSimple.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* ReceiveDataSimple */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ReceiveDataSimple; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* ReceiveDataSimple.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ReceiveDataSimple.cpp; path = ../../../../../Apps/Examples/ReceiveDataSimple.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* ReceiveDataSimple.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* ReceiveDataSimple */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* ReceiveDataSimple */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveDataSimple" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = ReceiveDataSimple; productName = ReceiveDataSimple; productReference = F572E40521B208F200C0D82F /* ReceiveDataSimple */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveDataSimple" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* ReceiveDataSimple */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* ReceiveDataSimple.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveDataSimple" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveDataSimple" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/ReceiveStringMarkers/000077500000000000000000000000001517625163100217055ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveStringMarkers/ReceiveStringMarkers.xcodeproj/000077500000000000000000000000001517625163100277775ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/ReceiveStringMarkers/ReceiveStringMarkers.xcodeproj/project.pbxproj000066400000000000000000000236201517625163100330560ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* ReceiveStringMarkers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* ReceiveStringMarkers.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* ReceiveStringMarkers */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ReceiveStringMarkers; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* ReceiveStringMarkers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ReceiveStringMarkers.cpp; path = ../../../../../Apps/Examples/ReceiveStringMarkers.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* ReceiveStringMarkers.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* ReceiveStringMarkers */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* ReceiveStringMarkers */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveStringMarkers" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = ReceiveStringMarkers; productName = ReceiveStringMarkers; productReference = F572E40521B208F200C0D82F /* ReceiveStringMarkers */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveStringMarkers" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* ReceiveStringMarkers */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* ReceiveStringMarkers.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "ReceiveStringMarkers" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "ReceiveStringMarkers" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/SendData/000077500000000000000000000000001517625163100172725ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendData/SendData.xcodeproj/000077500000000000000000000000001517625163100227515ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendData/SendData.xcodeproj/project.pbxproj000066400000000000000000000240301517625163100260240ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* SendDataSimple.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* SendDataSimple.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* SendData */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SendData; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* SendDataSimple.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SendDataSimple.cpp; path = ../../../../../Apps/Examples/SendDataSimple.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* SendDataSimple.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* SendData */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* SendData */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendData" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = SendData; productName = SendData; productReference = F572E40521B208F200C0D82F /* SendData */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendData" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* SendData */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* SendDataSimple.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = ( "$(SRCROOT)/../../../include", /Users/peter_pebler/labstreaminglayer/LSL/liblsl/project/Xcode/liblsl/liblsl, ); }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = ( "$(SRCROOT)/../../../include", /Users/peter_pebler/labstreaminglayer/LSL/liblsl/project/Xcode/liblsl/liblsl, ); }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendData" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendData" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/SendDataInChunks/000077500000000000000000000000001517625163100207355ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendDataInChunks/SendDataInChunks.xcodeproj/000077500000000000000000000000001517625163100260575ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendDataInChunks/SendDataInChunks.xcodeproj/project.pbxproj000066400000000000000000000237041517625163100311410ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* SendDataInChunks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* SendDataInChunks.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* SendDataInChunks */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SendDataInChunks; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* SendDataInChunks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SendDataInChunks.cpp; path = ../../../../../Apps/Examples/SendDataInChunks.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* SendDataInChunks.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* SendDataInChunks */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* SendDataInChunks */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendDataInChunks" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = SendDataInChunks; productName = SendDataInChunks; productReference = F572E40521B208F200C0D82F /* SendDataInChunks */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendDataInChunks" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* SendDataInChunks */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* SendDataInChunks.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendDataInChunks" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendDataInChunks" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/SendStringMarkers/000077500000000000000000000000001517625163100212145ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendStringMarkers/SendStringMarkers.xcodeproj/000077500000000000000000000000001517625163100266155ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/SendStringMarkers/SendStringMarkers.xcodeproj/project.pbxproj000066400000000000000000000237271517625163100317040ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* SendStringMarkers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* SendStringMarkers.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F572E40521B208F200C0D82F /* SendStringMarkers */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SendStringMarkers; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* SendStringMarkers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SendStringMarkers.cpp; path = ../../../../../Apps/Examples/SendStringMarkers.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F5DA928E21B5DFDB00348AC0 /* SendStringMarkers.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* SendStringMarkers */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* SendStringMarkers */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendStringMarkers" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = SendStringMarkers; productName = SendStringMarkers; productReference = F572E40521B208F200C0D82F /* SendStringMarkers */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendStringMarkers" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* SendStringMarkers */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* SendStringMarkers.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../include"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "SendStringMarkers" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "SendStringMarkers" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/StressTest/000077500000000000000000000000001517625163100177325ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/StressTest/StressTest.xcodeproj/000077500000000000000000000000001517625163100240515ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/StressTest/StressTest.xcodeproj/project.pbxproj000066400000000000000000000301441517625163100271270ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F55426D021B5EEC8007C93F1 /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F55426CE21B5EEC8007C93F1 /* thread.cpp */; }; F55426D121B5EEC8007C93F1 /* once.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F55426CF21B5EEC8007C93F1 /* once.cpp */; }; F55426D321B5EEDD007C93F1 /* tss_null.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F55426D221B5EEDD007C93F1 /* tss_null.cpp */; }; F55426D621B5EF19007C93F1 /* error_code.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F55426D521B5EF19007C93F1 /* error_code.cpp */; }; F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F572E41621B20E0300C0D82F /* liblsl64.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; F5DA928F21B5DFDB00348AC0 /* StressTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5DA928E21B5DFDB00348AC0 /* StressTest.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ F572E40321B208F200C0D82F /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ F55426CE21B5EEC8007C93F1 /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = thread.cpp; path = ../../../lslboost/libs/thread/src/pthread/thread.cpp; sourceTree = ""; }; F55426CF21B5EEC8007C93F1 /* once.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = once.cpp; path = ../../../lslboost/libs/thread/src/pthread/once.cpp; sourceTree = ""; }; F55426D221B5EEDD007C93F1 /* tss_null.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tss_null.cpp; path = ../../../lslboost/libs/thread/src/tss_null.cpp; sourceTree = ""; }; F55426D521B5EF19007C93F1 /* error_code.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = error_code.cpp; path = ../../../lslboost/libs/system/src/error_code.cpp; sourceTree = ""; }; F572E40521B208F200C0D82F /* StressTest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = StressTest; sourceTree = BUILT_PRODUCTS_DIR; }; F572E41621B20E0300C0D82F /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928E21B5DFDB00348AC0 /* StressTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StressTest.cpp; path = ../../../testing/StressTest/StressTest.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F572E40221B208F200C0D82F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F572E41721B20E0300C0D82F /* liblsl64.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F55426CC21B5EE9B007C93F1 /* thread */ = { isa = PBXGroup; children = ( F55426D221B5EEDD007C93F1 /* tss_null.cpp */, F55426CD21B5EEA8007C93F1 /* pthread */, ); name = thread; sourceTree = ""; }; F55426CD21B5EEA8007C93F1 /* pthread */ = { isa = PBXGroup; children = ( F55426CF21B5EEC8007C93F1 /* once.cpp */, F55426CE21B5EEC8007C93F1 /* thread.cpp */, ); name = pthread; sourceTree = ""; }; F55426D421B5EEEF007C93F1 /* system */ = { isa = PBXGroup; children = ( F55426D521B5EF19007C93F1 /* error_code.cpp */, ); name = system; sourceTree = ""; }; F572E3FC21B208F200C0D82F = { isa = PBXGroup; children = ( F55426D421B5EEEF007C93F1 /* system */, F55426CC21B5EE9B007C93F1 /* thread */, F5DA928E21B5DFDB00348AC0 /* StressTest.cpp */, F572E40621B208F200C0D82F /* Products */, F572E41521B20E0300C0D82F /* Frameworks */, ); sourceTree = ""; }; F572E40621B208F200C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E40521B208F200C0D82F /* StressTest */, ); name = Products; sourceTree = ""; }; F572E41521B20E0300C0D82F /* Frameworks */ = { isa = PBXGroup; children = ( F572E41621B20E0300C0D82F /* liblsl64.dylib */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ F572E40421B208F200C0D82F /* StressTest */ = { isa = PBXNativeTarget; buildConfigurationList = F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "StressTest" */; buildPhases = ( F572E40121B208F200C0D82F /* Sources */, F572E40221B208F200C0D82F /* Frameworks */, F572E40321B208F200C0D82F /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = StressTest; productName = StressTest; productReference = F572E40521B208F200C0D82F /* StressTest */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F572E3FD21B208F200C0D82F /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F572E40421B208F200C0D82F = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F572E40021B208F200C0D82F /* Build configuration list for PBXProject "StressTest" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F572E3FC21B208F200C0D82F; productRefGroup = F572E40621B208F200C0D82F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F572E40421B208F200C0D82F /* StressTest */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F572E40121B208F200C0D82F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA928F21B5DFDB00348AC0 /* StressTest.cpp in Sources */, F55426D621B5EF19007C93F1 /* error_code.cpp in Sources */, F55426D321B5EEDD007C93F1 /* tss_null.cpp in Sources */, F55426D021B5EEC8007C93F1 /* thread.cpp in Sources */, F55426D121B5EEC8007C93F1 /* once.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F572E40A21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../lslboost"; }; name = Debug; }; F572E40B21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; STRIP_INSTALLED_PRODUCT = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../lslboost"; }; name = Release; }; F572E40D21B208F200C0D82F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; F572E40E21B208F200C0D82F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F572E40021B208F200C0D82F /* Build configuration list for PBXProject "StressTest" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40A21B208F200C0D82F /* Debug */, F572E40B21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F572E40C21B208F200C0D82F /* Build configuration list for PBXNativeTarget "StressTest" */ = { isa = XCConfigurationList; buildConfigurations = ( F572E40D21B208F200C0D82F /* Debug */, F572E40E21B208F200C0D82F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F572E3FD21B208F200C0D82F /* Project object */; } liblsl-1.17.7/project/Xcode/Xcode.xcodeproj/000077500000000000000000000000001517625163100206455ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/Xcode.xcodeproj/project.pbxproj000066400000000000000000000401071517625163100237230ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXContainerItemProxy section */ F554269621B5EC39007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F554269221B5EC39007C93F1 /* GetAllStreams.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = GetAllStreams; }; F55426A021B5EC43007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F554269C21B5EC43007C93F1 /* GetFullinfo.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = GetFullinfo; }; F55426A621B5EC56007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426A221B5EC56007C93F1 /* GetTimeCorrection.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = GetTimeCorrection; }; F55426AC21B5EC63007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426A821B5EC63007C93F1 /* ReceiveData.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = ReceiveData; }; F55426B221B5EC6D007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426AE21B5EC6D007C93F1 /* ReceiveDataInChunks.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = ReceiveDataInChunks; }; F55426B821B5EC75007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426B421B5EC75007C93F1 /* SendData.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = SendData; }; F55426BE21B5EC7F007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426BA21B5EC7F007C93F1 /* ReceiveDataSimple.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = ReceiveDataSimple; }; F55426C421B5EC90007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426C021B5EC90007C93F1 /* SendDataInChunks.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = SendDataInChunks; }; F55426CA21B5ECA1007C93F1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F55426C621B5ECA1007C93F1 /* StressTest.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = StressTest; }; F572E3FA21B208AF00C0D82F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F572E3F621B208AF00C0D82F /* liblsl.xcodeproj */; proxyType = 2; remoteGlobalIDString = F5EA8FA021B2080C00DE3C7B; remoteInfo = liblsl; }; F5DA928C21B5DFBE00348AC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F572E3F621B208AF00C0D82F /* liblsl.xcodeproj */; proxyType = 2; remoteGlobalIDString = F5C0A97721B5DD8A00168AAF; remoteInfo = lsl_ios; }; F5E6640A21B5E47E00B96DFE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F572E40F21B2091A00C0D82F /* SendStringMarkers.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = SendStringMarkers; }; F5E6641021B5E4A300B96DFE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F5E6640C21B5E4A300B96DFE /* ReceiveStringMarkers.xcodeproj */; proxyType = 2; remoteGlobalIDString = F572E40521B208F200C0D82F; remoteInfo = ReceiveStringMarkers; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ F554269221B5EC39007C93F1 /* GetAllStreams.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GetAllStreams.xcodeproj; path = GetAllStreams/GetAllStreams.xcodeproj; sourceTree = ""; }; F554269C21B5EC43007C93F1 /* GetFullinfo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GetFullinfo.xcodeproj; path = GetFullinfo/GetFullinfo.xcodeproj; sourceTree = ""; }; F55426A221B5EC56007C93F1 /* GetTimeCorrection.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GetTimeCorrection.xcodeproj; path = GetTimeCorrection/GetTimeCorrection.xcodeproj; sourceTree = ""; }; F55426A821B5EC63007C93F1 /* ReceiveData.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReceiveData.xcodeproj; path = ReceiveData/ReceiveData.xcodeproj; sourceTree = ""; }; F55426AE21B5EC6D007C93F1 /* ReceiveDataInChunks.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReceiveDataInChunks.xcodeproj; path = ReceiveDataInChunks/ReceiveDataInChunks.xcodeproj; sourceTree = ""; }; F55426B421B5EC75007C93F1 /* SendData.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SendData.xcodeproj; path = SendData/SendData.xcodeproj; sourceTree = ""; }; F55426BA21B5EC7F007C93F1 /* ReceiveDataSimple.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReceiveDataSimple.xcodeproj; path = ReceiveDataSimple/ReceiveDataSimple.xcodeproj; sourceTree = ""; }; F55426C021B5EC90007C93F1 /* SendDataInChunks.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SendDataInChunks.xcodeproj; path = SendDataInChunks/SendDataInChunks.xcodeproj; sourceTree = ""; }; F55426C621B5ECA1007C93F1 /* StressTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = StressTest.xcodeproj; path = StressTest/StressTest.xcodeproj; sourceTree = ""; }; F572E3F621B208AF00C0D82F /* liblsl.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = liblsl.xcodeproj; path = liblsl/liblsl.xcodeproj; sourceTree = ""; }; F572E40F21B2091A00C0D82F /* SendStringMarkers.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SendStringMarkers.xcodeproj; path = SendStringMarkers/SendStringMarkers.xcodeproj; sourceTree = SOURCE_ROOT; }; F5E6640C21B5E4A300B96DFE /* ReceiveStringMarkers.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReceiveStringMarkers.xcodeproj; path = ReceiveStringMarkers/ReceiveStringMarkers.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXGroup section */ F554269321B5EC39007C93F1 /* Products */ = { isa = PBXGroup; children = ( F554269721B5EC39007C93F1 /* GetAllStreams */, ); name = Products; sourceTree = ""; }; F554269D21B5EC43007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426A121B5EC43007C93F1 /* GetFullinfo */, ); name = Products; sourceTree = ""; }; F55426A321B5EC56007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426A721B5EC56007C93F1 /* GetTimeCorrection */, ); name = Products; sourceTree = ""; }; F55426A921B5EC63007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426AD21B5EC63007C93F1 /* ReceiveData */, ); name = Products; sourceTree = ""; }; F55426AF21B5EC6D007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426B321B5EC6D007C93F1 /* ReceiveDataInChunks */, ); name = Products; sourceTree = ""; }; F55426B521B5EC75007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426B921B5EC75007C93F1 /* SendData */, ); name = Products; sourceTree = ""; }; F55426BB21B5EC7F007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426BF21B5EC7F007C93F1 /* ReceiveDataSimple */, ); name = Products; sourceTree = ""; }; F55426C121B5EC90007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426C521B5EC90007C93F1 /* SendDataInChunks */, ); name = Products; sourceTree = ""; }; F55426C721B5ECA1007C93F1 /* Products */ = { isa = PBXGroup; children = ( F55426CB21B5ECA1007C93F1 /* StressTest */, ); name = Products; sourceTree = ""; }; F572E3F721B208AF00C0D82F /* Products */ = { isa = PBXGroup; children = ( F572E3FB21B208AF00C0D82F /* liblsl64.dylib */, F5DA928D21B5DFBE00348AC0 /* liblsl_ios.dylib */, ); name = Products; sourceTree = ""; }; F5E6640521B5E47D00B96DFE /* Products */ = { isa = PBXGroup; children = ( F5E6640B21B5E47E00B96DFE /* SendStringMarkers */, ); name = Products; sourceTree = ""; }; F5E6640D21B5E4A300B96DFE /* Products */ = { isa = PBXGroup; children = ( F5E6641121B5E4A300B96DFE /* ReceiveStringMarkers */, ); name = Products; sourceTree = ""; }; F5EA8F9021B207C400DE3C7B = { isa = PBXGroup; children = ( F55426C621B5ECA1007C93F1 /* StressTest.xcodeproj */, F55426C021B5EC90007C93F1 /* SendDataInChunks.xcodeproj */, F55426BA21B5EC7F007C93F1 /* ReceiveDataSimple.xcodeproj */, F55426B421B5EC75007C93F1 /* SendData.xcodeproj */, F55426AE21B5EC6D007C93F1 /* ReceiveDataInChunks.xcodeproj */, F55426A821B5EC63007C93F1 /* ReceiveData.xcodeproj */, F55426A221B5EC56007C93F1 /* GetTimeCorrection.xcodeproj */, F554269C21B5EC43007C93F1 /* GetFullinfo.xcodeproj */, F554269221B5EC39007C93F1 /* GetAllStreams.xcodeproj */, F5E6640C21B5E4A300B96DFE /* ReceiveStringMarkers.xcodeproj */, F572E40F21B2091A00C0D82F /* SendStringMarkers.xcodeproj */, F572E3F621B208AF00C0D82F /* liblsl.xcodeproj */, ); sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXProject section */ F5EA8F9121B207C400DE3C7B /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; }; buildConfigurationList = F5EA8F9421B207C400DE3C7B /* Build configuration list for PBXProject "Xcode" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F5EA8F9021B207C400DE3C7B; projectDirPath = ""; projectReferences = ( { ProductGroup = F554269321B5EC39007C93F1 /* Products */; ProjectRef = F554269221B5EC39007C93F1 /* GetAllStreams.xcodeproj */; }, { ProductGroup = F554269D21B5EC43007C93F1 /* Products */; ProjectRef = F554269C21B5EC43007C93F1 /* GetFullinfo.xcodeproj */; }, { ProductGroup = F55426A321B5EC56007C93F1 /* Products */; ProjectRef = F55426A221B5EC56007C93F1 /* GetTimeCorrection.xcodeproj */; }, { ProductGroup = F572E3F721B208AF00C0D82F /* Products */; ProjectRef = F572E3F621B208AF00C0D82F /* liblsl.xcodeproj */; }, { ProductGroup = F55426A921B5EC63007C93F1 /* Products */; ProjectRef = F55426A821B5EC63007C93F1 /* ReceiveData.xcodeproj */; }, { ProductGroup = F55426AF21B5EC6D007C93F1 /* Products */; ProjectRef = F55426AE21B5EC6D007C93F1 /* ReceiveDataInChunks.xcodeproj */; }, { ProductGroup = F55426BB21B5EC7F007C93F1 /* Products */; ProjectRef = F55426BA21B5EC7F007C93F1 /* ReceiveDataSimple.xcodeproj */; }, { ProductGroup = F5E6640D21B5E4A300B96DFE /* Products */; ProjectRef = F5E6640C21B5E4A300B96DFE /* ReceiveStringMarkers.xcodeproj */; }, { ProductGroup = F55426B521B5EC75007C93F1 /* Products */; ProjectRef = F55426B421B5EC75007C93F1 /* SendData.xcodeproj */; }, { ProductGroup = F55426C121B5EC90007C93F1 /* Products */; ProjectRef = F55426C021B5EC90007C93F1 /* SendDataInChunks.xcodeproj */; }, { ProductGroup = F5E6640521B5E47D00B96DFE /* Products */; ProjectRef = F572E40F21B2091A00C0D82F /* SendStringMarkers.xcodeproj */; }, { ProductGroup = F55426C721B5ECA1007C93F1 /* Products */; ProjectRef = F55426C621B5ECA1007C93F1 /* StressTest.xcodeproj */; }, ); projectRoot = ""; targets = ( ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ F554269721B5EC39007C93F1 /* GetAllStreams */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = GetAllStreams; remoteRef = F554269621B5EC39007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426A121B5EC43007C93F1 /* GetFullinfo */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = GetFullinfo; remoteRef = F55426A021B5EC43007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426A721B5EC56007C93F1 /* GetTimeCorrection */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = GetTimeCorrection; remoteRef = F55426A621B5EC56007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426AD21B5EC63007C93F1 /* ReceiveData */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = ReceiveData; remoteRef = F55426AC21B5EC63007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426B321B5EC6D007C93F1 /* ReceiveDataInChunks */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = ReceiveDataInChunks; remoteRef = F55426B221B5EC6D007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426B921B5EC75007C93F1 /* SendData */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = SendData; remoteRef = F55426B821B5EC75007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426BF21B5EC7F007C93F1 /* ReceiveDataSimple */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = ReceiveDataSimple; remoteRef = F55426BE21B5EC7F007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426C521B5EC90007C93F1 /* SendDataInChunks */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = SendDataInChunks; remoteRef = F55426C421B5EC90007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F55426CB21B5ECA1007C93F1 /* StressTest */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = StressTest; remoteRef = F55426CA21B5ECA1007C93F1 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F572E3FB21B208AF00C0D82F /* liblsl64.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = liblsl64.dylib; remoteRef = F572E3FA21B208AF00C0D82F /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F5DA928D21B5DFBE00348AC0 /* liblsl_ios.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; path = liblsl_ios.dylib; remoteRef = F5DA928C21B5DFBE00348AC0 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F5E6640B21B5E47E00B96DFE /* SendStringMarkers */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = SendStringMarkers; remoteRef = F5E6640A21B5E47E00B96DFE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; F5E6641121B5E4A300B96DFE /* ReceiveStringMarkers */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = ReceiveStringMarkers; remoteRef = F5E6641021B5E4A300B96DFE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin XCBuildConfiguration section */ F5EA8F9521B207C400DE3C7B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Debug; }; F5EA8F9621B207C400DE3C7B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F5EA8F9421B207C400DE3C7B /* Build configuration list for PBXProject "Xcode" */ = { isa = XCConfigurationList; buildConfigurations = ( F5EA8F9521B207C400DE3C7B /* Debug */, F5EA8F9621B207C400DE3C7B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F5EA8F9121B207C400DE3C7B /* Project object */; } liblsl-1.17.7/project/Xcode/liblsl/000077500000000000000000000000001517625163100170705ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/liblsl/liblsl.xcodeproj/000077500000000000000000000000001517625163100223455ustar00rootroot00000000000000liblsl-1.17.7/project/Xcode/liblsl/liblsl.xcodeproj/project.pbxproj000066400000000000000000001620221517625163100254240ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ F572E41B21B211D500C0D82F /* lsl_cpp.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E41921B211D500C0D82F /* lsl_cpp.h */; }; F572E41C21B211D500C0D82F /* lsl_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E41A21B211D500C0D82F /* lsl_c.h */; }; F572E42321B213A200C0D82F /* lsl_xml_element_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41D21B213A200C0D82F /* lsl_xml_element_c.cpp */; }; F572E42421B213A200C0D82F /* lsl_outlet_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41E21B213A200C0D82F /* lsl_outlet_c.cpp */; }; F572E42521B213A200C0D82F /* lsl_inlet_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41F21B213A200C0D82F /* lsl_inlet_c.cpp */; }; F572E42621B213A200C0D82F /* lsl_streaminfo_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42021B213A200C0D82F /* lsl_streaminfo_c.cpp */; }; F572E42721B213A200C0D82F /* lsl_continuous_resolver_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42121B213A200C0D82F /* lsl_continuous_resolver_c.cpp */; }; F572E42821B213A200C0D82F /* lsl_freefuncs_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42221B213A200C0D82F /* lsl_freefuncs_c.cpp */; }; F572E43121B214D800C0D82F /* chrono.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43021B214D800C0D82F /* chrono.cpp */; }; F572E45A21B2155300C0D82F /* basic_oarchive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43321B2155100C0D82F /* basic_oarchive.cpp */; }; F572E45B21B2155300C0D82F /* basic_oserializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43421B2155100C0D82F /* basic_oserializer.cpp */; }; F572E45C21B2155300C0D82F /* basic_serializer_map.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43521B2155100C0D82F /* basic_serializer_map.cpp */; }; F572E45E21B2155300C0D82F /* basic_archive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43721B2155100C0D82F /* basic_archive.cpp */; }; F572E46021B2155300C0D82F /* extended_type_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43921B2155100C0D82F /* extended_type_info.cpp */; }; F572E46521B2155300C0D82F /* void_cast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43E21B2155100C0D82F /* void_cast.cpp */; }; F572E46621B2155300C0D82F /* basic_iserializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43F21B2155100C0D82F /* basic_iserializer.cpp */; }; F572E46921B2155300C0D82F /* basic_iarchive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E44221B2155100C0D82F /* basic_iarchive.cpp */; }; F572E47721B2155300C0D82F /* archive_exception.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E45021B2155200C0D82F /* archive_exception.cpp */; }; F572E48221B215EA00C0D82F /* extended_type_info_typeid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48121B215EA00C0D82F /* extended_type_info_typeid.cpp */; }; F572E48521B2162800C0D82F /* error_code.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48421B2162800C0D82F /* error_code.cpp */; }; F572E48821B2164F00C0D82F /* tss_null.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48721B2164F00C0D82F /* tss_null.cpp */; }; F572E48C21B2167600C0D82F /* once.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48A21B2167600C0D82F /* once.cpp */; }; F572E48D21B2167600C0D82F /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48B21B2167600C0D82F /* thread.cpp */; }; F572E49221B216DF00C0D82F /* pugixml.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F572E48F21B216DE00C0D82F /* pugixml.hpp */; }; F572E49321B216DF00C0D82F /* pugiconfig.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F572E49021B216DE00C0D82F /* pugiconfig.hpp */; }; F572E49421B216DF00C0D82F /* pugixml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49121B216DF00C0D82F /* pugixml.cpp */; }; F572E49F21B2170F00C0D82F /* consumer_queue.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49521B2170E00C0D82F /* consumer_queue.h */; }; F572E4A021B2170F00C0D82F /* data_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49621B2170E00C0D82F /* data_receiver.h */; }; F572E4A121B2170F00C0D82F /* cancellation.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49721B2170E00C0D82F /* cancellation.h */; }; F572E4A221B2170F00C0D82F /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49821B2170E00C0D82F /* common.cpp */; }; F572E4A321B2170F00C0D82F /* api_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49921B2170F00C0D82F /* api_config.cpp */; }; F572E4A421B2170F00C0D82F /* common.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49A21B2170F00C0D82F /* common.h */; }; F572E4A521B2170F00C0D82F /* consumer_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49B21B2170F00C0D82F /* consumer_queue.cpp */; }; F572E4A621B2170F00C0D82F /* api_config.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49C21B2170F00C0D82F /* api_config.h */; }; F572E4A721B2170F00C0D82F /* cancellable_streambuf.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49D21B2170F00C0D82F /* cancellable_streambuf.h */; }; F572E4A821B2170F00C0D82F /* data_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49E21B2170F00C0D82F /* data_receiver.cpp */; }; F572E4AD21B2173200C0D82F /* inlet_connection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4A921B2173200C0D82F /* inlet_connection.cpp */; }; F572E4AE21B2173200C0D82F /* info_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4AA21B2173200C0D82F /* info_receiver.h */; }; F572E4AF21B2173200C0D82F /* inlet_connection.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4AB21B2173200C0D82F /* inlet_connection.h */; }; F572E4B021B2173200C0D82F /* info_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4AC21B2173200C0D82F /* info_receiver.cpp */; }; F572E4C821B2175D00C0D82F /* socket_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B121B2175C00C0D82F /* socket_utils.cpp */; }; F572E4C921B2175D00C0D82F /* sample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B221B2175C00C0D82F /* sample.cpp */; }; F572E4CA21B2175D00C0D82F /* time_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B321B2175C00C0D82F /* time_receiver.cpp */; }; F572E4CB21B2175D00C0D82F /* send_buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B421B2175C00C0D82F /* send_buffer.h */; }; F572E4CC21B2175D00C0D82F /* resolve_attempt_udp.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B521B2175C00C0D82F /* resolve_attempt_udp.h */; }; F572E4CD21B2175D00C0D82F /* sample.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B621B2175C00C0D82F /* sample.h */; }; F572E4CE21B2175D00C0D82F /* stream_info_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B721B2175C00C0D82F /* stream_info_impl.h */; }; F572E4CF21B2175D00C0D82F /* socket_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B821B2175C00C0D82F /* socket_utils.h */; }; F572E4D021B2175D00C0D82F /* resolver_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B921B2175C00C0D82F /* resolver_impl.h */; }; F572E4D121B2175D00C0D82F /* stream_info_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BA21B2175C00C0D82F /* stream_info_impl.cpp */; }; F572E4D221B2175D00C0D82F /* tcp_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BB21B2175C00C0D82F /* tcp_server.cpp */; }; F572E4D321B2175D00C0D82F /* stream_outlet_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BC21B2175D00C0D82F /* stream_outlet_impl.cpp */; }; F572E4D421B2175D00C0D82F /* tcp_server.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BD21B2175D00C0D82F /* tcp_server.h */; }; F572E4D521B2175D00C0D82F /* stream_outlet_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BE21B2175D00C0D82F /* stream_outlet_impl.h */; }; F572E4D621B2175D00C0D82F /* stream_inlet_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BF21B2175D00C0D82F /* stream_inlet_impl.h */; }; F572E4D721B2175D00C0D82F /* time_postprocessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C021B2175D00C0D82F /* time_postprocessor.cpp */; }; F572E4D821B2175D00C0D82F /* time_postprocessor.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C121B2175D00C0D82F /* time_postprocessor.h */; }; F572E4D921B2175D00C0D82F /* send_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C221B2175D00C0D82F /* send_buffer.cpp */; }; F572E4DA21B2175D00C0D82F /* resolver_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C321B2175D00C0D82F /* resolver_impl.cpp */; }; F572E4DB21B2175D00C0D82F /* resolve_attempt_udp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C421B2175D00C0D82F /* resolve_attempt_udp.cpp */; }; F572E4DC21B2175D00C0D82F /* udp_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C521B2175D00C0D82F /* udp_server.cpp */; }; F572E4DD21B2175D00C0D82F /* udp_server.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C621B2175D00C0D82F /* udp_server.h */; }; F572E4DE21B2175D00C0D82F /* time_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C721B2175D00C0D82F /* time_receiver.h */; }; F5DA929021B5E02A00348AC0 /* resolve_attempt_udp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C421B2175D00C0D82F /* resolve_attempt_udp.cpp */; }; F5DA929121B5E02A00348AC0 /* resolve_attempt_udp.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B521B2175C00C0D82F /* resolve_attempt_udp.h */; }; F5DA929221B5E02A00348AC0 /* resolver_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C321B2175D00C0D82F /* resolver_impl.cpp */; }; F5DA929321B5E02A00348AC0 /* resolver_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B921B2175C00C0D82F /* resolver_impl.h */; }; F5DA929421B5E02A00348AC0 /* sample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B221B2175C00C0D82F /* sample.cpp */; }; F5DA929521B5E02A00348AC0 /* sample.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B621B2175C00C0D82F /* sample.h */; }; F5DA929621B5E02A00348AC0 /* send_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C221B2175D00C0D82F /* send_buffer.cpp */; }; F5DA929721B5E02A00348AC0 /* send_buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B421B2175C00C0D82F /* send_buffer.h */; }; F5DA929821B5E02A00348AC0 /* socket_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B121B2175C00C0D82F /* socket_utils.cpp */; }; F5DA929921B5E02A00348AC0 /* socket_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B821B2175C00C0D82F /* socket_utils.h */; }; F5DA929A21B5E02A00348AC0 /* stream_info_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BA21B2175C00C0D82F /* stream_info_impl.cpp */; }; F5DA929B21B5E02A00348AC0 /* stream_info_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4B721B2175C00C0D82F /* stream_info_impl.h */; }; F5DA929C21B5E02A00348AC0 /* stream_inlet_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BF21B2175D00C0D82F /* stream_inlet_impl.h */; }; F5DA929D21B5E02A00348AC0 /* stream_outlet_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BC21B2175D00C0D82F /* stream_outlet_impl.cpp */; }; F5DA929E21B5E02A00348AC0 /* stream_outlet_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BE21B2175D00C0D82F /* stream_outlet_impl.h */; }; F5DA929F21B5E02A00348AC0 /* tcp_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4BB21B2175C00C0D82F /* tcp_server.cpp */; }; F5DA92A021B5E02A00348AC0 /* tcp_server.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4BD21B2175D00C0D82F /* tcp_server.h */; }; F5DA92A121B5E02A00348AC0 /* time_postprocessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C021B2175D00C0D82F /* time_postprocessor.cpp */; }; F5DA92A221B5E02A00348AC0 /* time_postprocessor.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C121B2175D00C0D82F /* time_postprocessor.h */; }; F5DA92A321B5E02A00348AC0 /* time_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4B321B2175C00C0D82F /* time_receiver.cpp */; }; F5DA92A421B5E02A00348AC0 /* time_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C721B2175D00C0D82F /* time_receiver.h */; }; F5DA92A521B5E02A00348AC0 /* udp_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4C521B2175D00C0D82F /* udp_server.cpp */; }; F5DA92A621B5E02A00348AC0 /* udp_server.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4C621B2175D00C0D82F /* udp_server.h */; }; F5DA92A721B5E02A00348AC0 /* info_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4AC21B2173200C0D82F /* info_receiver.cpp */; }; F5DA92A821B5E02A00348AC0 /* info_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4AA21B2173200C0D82F /* info_receiver.h */; }; F5DA92A921B5E02A00348AC0 /* inlet_connection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E4A921B2173200C0D82F /* inlet_connection.cpp */; }; F5DA92AA21B5E02A00348AC0 /* inlet_connection.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E4AB21B2173200C0D82F /* inlet_connection.h */; }; F5DA92AB21B5E02A00348AC0 /* api_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49921B2170F00C0D82F /* api_config.cpp */; }; F5DA92AC21B5E02A00348AC0 /* api_config.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49C21B2170F00C0D82F /* api_config.h */; }; F5DA92AD21B5E02A00348AC0 /* cancellable_streambuf.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49D21B2170F00C0D82F /* cancellable_streambuf.h */; }; F5DA92AE21B5E02A00348AC0 /* cancellation.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49721B2170E00C0D82F /* cancellation.h */; }; F5DA92AF21B5E02A00348AC0 /* common.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49821B2170E00C0D82F /* common.cpp */; }; F5DA92B021B5E02A00348AC0 /* common.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49A21B2170F00C0D82F /* common.h */; }; F5DA92B121B5E02A00348AC0 /* consumer_queue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49B21B2170F00C0D82F /* consumer_queue.cpp */; }; F5DA92B221B5E02A00348AC0 /* consumer_queue.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49521B2170E00C0D82F /* consumer_queue.h */; }; F5DA92B321B5E02A00348AC0 /* data_receiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49E21B2170F00C0D82F /* data_receiver.cpp */; }; F5DA92B421B5E02A00348AC0 /* data_receiver.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E49621B2170E00C0D82F /* data_receiver.h */; }; F5DA92B521B5E03000348AC0 /* pugiconfig.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F572E49021B216DE00C0D82F /* pugiconfig.hpp */; }; F5DA92B621B5E03000348AC0 /* pugixml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E49121B216DF00C0D82F /* pugixml.cpp */; }; F5DA92B721B5E03000348AC0 /* pugixml.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F572E48F21B216DE00C0D82F /* pugixml.hpp */; }; F5DA92B821B5E03C00348AC0 /* once.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48A21B2167600C0D82F /* once.cpp */; }; F5DA92B921B5E03C00348AC0 /* thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48B21B2167600C0D82F /* thread.cpp */; }; F5DA92BA21B5E03C00348AC0 /* tss_null.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48721B2164F00C0D82F /* tss_null.cpp */; }; F5DA92BB21B5E04100348AC0 /* error_code.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48421B2162800C0D82F /* error_code.cpp */; }; F5DA92BC21B5E04D00348AC0 /* archive_exception.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E45021B2155200C0D82F /* archive_exception.cpp */; }; F5DA92BD21B5E04D00348AC0 /* basic_archive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43721B2155100C0D82F /* basic_archive.cpp */; }; F5DA92BE21B5E04D00348AC0 /* basic_iarchive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E44221B2155100C0D82F /* basic_iarchive.cpp */; }; F5DA92BF21B5E04D00348AC0 /* basic_iserializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43F21B2155100C0D82F /* basic_iserializer.cpp */; }; F5DA92C021B5E04D00348AC0 /* basic_oarchive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43321B2155100C0D82F /* basic_oarchive.cpp */; }; F5DA92C121B5E04D00348AC0 /* basic_oserializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43421B2155100C0D82F /* basic_oserializer.cpp */; }; F5DA92C221B5E04D00348AC0 /* basic_serializer_map.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43521B2155100C0D82F /* basic_serializer_map.cpp */; }; F5DA92C321B5E04D00348AC0 /* extended_type_info_typeid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E48121B215EA00C0D82F /* extended_type_info_typeid.cpp */; }; F5DA92C421B5E04D00348AC0 /* extended_type_info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43921B2155100C0D82F /* extended_type_info.cpp */; }; F5DA92C521B5E04D00348AC0 /* void_cast.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43E21B2155100C0D82F /* void_cast.cpp */; }; F5DA92C621B5E05100348AC0 /* chrono.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E43021B214D800C0D82F /* chrono.cpp */; }; F5DA92C721B5E05800348AC0 /* lsl_continuous_resolver_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42121B213A200C0D82F /* lsl_continuous_resolver_c.cpp */; }; F5DA92C821B5E05800348AC0 /* lsl_freefuncs_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42221B213A200C0D82F /* lsl_freefuncs_c.cpp */; }; F5DA92C921B5E05800348AC0 /* lsl_inlet_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41F21B213A200C0D82F /* lsl_inlet_c.cpp */; }; F5DA92CA21B5E05800348AC0 /* lsl_outlet_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41E21B213A200C0D82F /* lsl_outlet_c.cpp */; }; F5DA92CB21B5E05800348AC0 /* lsl_streaminfo_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E42021B213A200C0D82F /* lsl_streaminfo_c.cpp */; }; F5DA92CC21B5E05800348AC0 /* lsl_xml_element_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F572E41D21B213A200C0D82F /* lsl_xml_element_c.cpp */; }; F5DA92CD21B5E05800348AC0 /* lsl_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E41A21B211D500C0D82F /* lsl_c.h */; }; F5DA92CE21B5E05800348AC0 /* lsl_cpp.h in Headers */ = {isa = PBXBuildFile; fileRef = F572E41921B211D500C0D82F /* lsl_cpp.h */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ F572E41921B211D500C0D82F /* lsl_cpp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lsl_cpp.h; path = ../../../include/lsl_cpp.h; sourceTree = ""; }; F572E41A21B211D500C0D82F /* lsl_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lsl_c.h; path = ../../../include/lsl_c.h; sourceTree = ""; }; F572E41D21B213A200C0D82F /* lsl_xml_element_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_xml_element_c.cpp; path = ../../../src/lsl_xml_element_c.cpp; sourceTree = ""; }; F572E41E21B213A200C0D82F /* lsl_outlet_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_outlet_c.cpp; path = ../../../src/lsl_outlet_c.cpp; sourceTree = ""; }; F572E41F21B213A200C0D82F /* lsl_inlet_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_inlet_c.cpp; path = ../../../src/lsl_inlet_c.cpp; sourceTree = ""; }; F572E42021B213A200C0D82F /* lsl_streaminfo_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_streaminfo_c.cpp; path = ../../../src/lsl_streaminfo_c.cpp; sourceTree = ""; }; F572E42121B213A200C0D82F /* lsl_continuous_resolver_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_continuous_resolver_c.cpp; path = ../../../src/lsl_continuous_resolver_c.cpp; sourceTree = ""; }; F572E42221B213A200C0D82F /* lsl_freefuncs_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lsl_freefuncs_c.cpp; path = ../../../src/lsl_freefuncs_c.cpp; sourceTree = ""; }; F572E43021B214D800C0D82F /* chrono.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = chrono.cpp; path = ../../../lslboost/libs/chrono/src/chrono.cpp; sourceTree = ""; }; F572E43321B2155100C0D82F /* basic_oarchive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_oarchive.cpp; path = ../../../lslboost/libs/serialization/src/basic_oarchive.cpp; sourceTree = ""; }; F572E43421B2155100C0D82F /* basic_oserializer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_oserializer.cpp; path = ../../../lslboost/libs/serialization/src/basic_oserializer.cpp; sourceTree = ""; }; F572E43521B2155100C0D82F /* basic_serializer_map.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_serializer_map.cpp; path = ../../../lslboost/libs/serialization/src/basic_serializer_map.cpp; sourceTree = ""; }; F572E43721B2155100C0D82F /* basic_archive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_archive.cpp; path = ../../../lslboost/libs/serialization/src/basic_archive.cpp; sourceTree = ""; }; F572E43921B2155100C0D82F /* extended_type_info.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = extended_type_info.cpp; path = ../../../lslboost/libs/serialization/src/extended_type_info.cpp; sourceTree = ""; }; F572E43E21B2155100C0D82F /* void_cast.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = void_cast.cpp; path = ../../../lslboost/libs/serialization/src/void_cast.cpp; sourceTree = ""; }; F572E43F21B2155100C0D82F /* basic_iserializer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_iserializer.cpp; path = ../../../lslboost/libs/serialization/src/basic_iserializer.cpp; sourceTree = ""; }; F572E44221B2155100C0D82F /* basic_iarchive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_iarchive.cpp; path = ../../../lslboost/libs/serialization/src/basic_iarchive.cpp; sourceTree = ""; }; F572E45021B2155200C0D82F /* archive_exception.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = archive_exception.cpp; path = ../../../lslboost/libs/serialization/src/archive_exception.cpp; sourceTree = ""; }; F572E48121B215EA00C0D82F /* extended_type_info_typeid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = extended_type_info_typeid.cpp; path = ../../../lslboost/libs/serialization/src/extended_type_info_typeid.cpp; sourceTree = ""; }; F572E48421B2162800C0D82F /* error_code.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = error_code.cpp; path = ../../../lslboost/libs/system/src/error_code.cpp; sourceTree = ""; }; F572E48721B2164F00C0D82F /* tss_null.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tss_null.cpp; path = ../../../lslboost/libs/thread/src/tss_null.cpp; sourceTree = ""; }; F572E48A21B2167600C0D82F /* once.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = once.cpp; path = ../../../lslboost/libs/thread/src/pthread/once.cpp; sourceTree = ""; }; F572E48B21B2167600C0D82F /* thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = thread.cpp; path = ../../../lslboost/libs/thread/src/pthread/thread.cpp; sourceTree = ""; }; F572E48F21B216DE00C0D82F /* pugixml.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = pugixml.hpp; path = ../../../src/pugixml/pugixml.hpp; sourceTree = ""; }; F572E49021B216DE00C0D82F /* pugiconfig.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = pugiconfig.hpp; path = ../../../src/pugixml/pugiconfig.hpp; sourceTree = ""; }; F572E49121B216DF00C0D82F /* pugixml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pugixml.cpp; path = ../../../src/pugixml/pugixml.cpp; sourceTree = ""; }; F572E49521B2170E00C0D82F /* consumer_queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = consumer_queue.h; path = ../../../src/consumer_queue.h; sourceTree = ""; }; F572E49621B2170E00C0D82F /* data_receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = data_receiver.h; path = ../../../src/data_receiver.h; sourceTree = ""; }; F572E49721B2170E00C0D82F /* cancellation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cancellation.h; path = ../../../src/cancellation.h; sourceTree = ""; }; F572E49821B2170E00C0D82F /* common.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = common.cpp; path = ../../../src/common.cpp; sourceTree = ""; }; F572E49921B2170F00C0D82F /* api_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = api_config.cpp; path = ../../../src/api_config.cpp; sourceTree = ""; }; F572E49A21B2170F00C0D82F /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = ../../../src/common.h; sourceTree = ""; }; F572E49B21B2170F00C0D82F /* consumer_queue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = consumer_queue.cpp; path = ../../../src/consumer_queue.cpp; sourceTree = ""; }; F572E49C21B2170F00C0D82F /* api_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = api_config.h; path = ../../../src/api_config.h; sourceTree = ""; }; F572E49D21B2170F00C0D82F /* cancellable_streambuf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cancellable_streambuf.h; path = ../../../src/cancellable_streambuf.h; sourceTree = ""; }; F572E49E21B2170F00C0D82F /* data_receiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = data_receiver.cpp; path = ../../../src/data_receiver.cpp; sourceTree = ""; }; F572E4A921B2173200C0D82F /* inlet_connection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = inlet_connection.cpp; path = ../../../src/inlet_connection.cpp; sourceTree = ""; }; F572E4AA21B2173200C0D82F /* info_receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = info_receiver.h; path = ../../../src/info_receiver.h; sourceTree = ""; }; F572E4AB21B2173200C0D82F /* inlet_connection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = inlet_connection.h; path = ../../../src/inlet_connection.h; sourceTree = ""; }; F572E4AC21B2173200C0D82F /* info_receiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = info_receiver.cpp; path = ../../../src/info_receiver.cpp; sourceTree = ""; }; F572E4B121B2175C00C0D82F /* socket_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = socket_utils.cpp; path = ../../../src/socket_utils.cpp; sourceTree = ""; }; F572E4B221B2175C00C0D82F /* sample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sample.cpp; path = ../../../src/sample.cpp; sourceTree = ""; }; F572E4B321B2175C00C0D82F /* time_receiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = time_receiver.cpp; path = ../../../src/time_receiver.cpp; sourceTree = ""; }; F572E4B421B2175C00C0D82F /* send_buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = send_buffer.h; path = ../../../src/send_buffer.h; sourceTree = ""; }; F572E4B521B2175C00C0D82F /* resolve_attempt_udp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resolve_attempt_udp.h; path = ../../../src/resolve_attempt_udp.h; sourceTree = ""; }; F572E4B621B2175C00C0D82F /* sample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sample.h; path = ../../../src/sample.h; sourceTree = ""; }; F572E4B721B2175C00C0D82F /* stream_info_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stream_info_impl.h; path = ../../../src/stream_info_impl.h; sourceTree = ""; }; F572E4B821B2175C00C0D82F /* socket_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = socket_utils.h; path = ../../../src/socket_utils.h; sourceTree = ""; }; F572E4B921B2175C00C0D82F /* resolver_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = resolver_impl.h; path = ../../../src/resolver_impl.h; sourceTree = ""; }; F572E4BA21B2175C00C0D82F /* stream_info_impl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stream_info_impl.cpp; path = ../../../src/stream_info_impl.cpp; sourceTree = ""; }; F572E4BB21B2175C00C0D82F /* tcp_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tcp_server.cpp; path = ../../../src/tcp_server.cpp; sourceTree = ""; }; F572E4BC21B2175D00C0D82F /* stream_outlet_impl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = stream_outlet_impl.cpp; path = ../../../src/stream_outlet_impl.cpp; sourceTree = ""; }; F572E4BD21B2175D00C0D82F /* tcp_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tcp_server.h; path = ../../../src/tcp_server.h; sourceTree = ""; }; F572E4BE21B2175D00C0D82F /* stream_outlet_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stream_outlet_impl.h; path = ../../../src/stream_outlet_impl.h; sourceTree = ""; }; F572E4BF21B2175D00C0D82F /* stream_inlet_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stream_inlet_impl.h; path = ../../../src/stream_inlet_impl.h; sourceTree = ""; }; F572E4C021B2175D00C0D82F /* time_postprocessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = time_postprocessor.cpp; path = ../../../src/time_postprocessor.cpp; sourceTree = ""; }; F572E4C121B2175D00C0D82F /* time_postprocessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = time_postprocessor.h; path = ../../../src/time_postprocessor.h; sourceTree = ""; }; F572E4C221B2175D00C0D82F /* send_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = send_buffer.cpp; path = ../../../src/send_buffer.cpp; sourceTree = ""; }; F572E4C321B2175D00C0D82F /* resolver_impl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = resolver_impl.cpp; path = ../../../src/resolver_impl.cpp; sourceTree = ""; }; F572E4C421B2175D00C0D82F /* resolve_attempt_udp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = resolve_attempt_udp.cpp; path = ../../../src/resolve_attempt_udp.cpp; sourceTree = ""; }; F572E4C521B2175D00C0D82F /* udp_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = udp_server.cpp; path = ../../../src/udp_server.cpp; sourceTree = ""; }; F572E4C621B2175D00C0D82F /* udp_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = udp_server.h; path = ../../../src/udp_server.h; sourceTree = ""; }; F572E4C721B2175D00C0D82F /* time_receiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = time_receiver.h; path = ../../../src/time_receiver.h; sourceTree = ""; }; F5C0A97721B5DD8A00168AAF /* liblsl_ios.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblsl_ios.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F5EA8FA021B2080C00DE3C7B /* liblsl64.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblsl64.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ F5C0A97521B5DD8A00168AAF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F5EA8F9E21B2080C00DE3C7B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ F572E41821B211AE00C0D82F /* C/C++ API */ = { isa = PBXGroup; children = ( F572E42121B213A200C0D82F /* lsl_continuous_resolver_c.cpp */, F572E42221B213A200C0D82F /* lsl_freefuncs_c.cpp */, F572E41F21B213A200C0D82F /* lsl_inlet_c.cpp */, F572E41E21B213A200C0D82F /* lsl_outlet_c.cpp */, F572E42021B213A200C0D82F /* lsl_streaminfo_c.cpp */, F572E41D21B213A200C0D82F /* lsl_xml_element_c.cpp */, F572E41A21B211D500C0D82F /* lsl_c.h */, F572E41921B211D500C0D82F /* lsl_cpp.h */, ); name = "C/C++ API"; sourceTree = SOURCE_ROOT; }; F572E42921B213AC00C0D82F /* Source */ = { isa = PBXGroup; children = ( F572E4C421B2175D00C0D82F /* resolve_attempt_udp.cpp */, F572E4B521B2175C00C0D82F /* resolve_attempt_udp.h */, F572E4C321B2175D00C0D82F /* resolver_impl.cpp */, F572E4B921B2175C00C0D82F /* resolver_impl.h */, F572E4B221B2175C00C0D82F /* sample.cpp */, F572E4B621B2175C00C0D82F /* sample.h */, F572E4C221B2175D00C0D82F /* send_buffer.cpp */, F572E4B421B2175C00C0D82F /* send_buffer.h */, F572E4B121B2175C00C0D82F /* socket_utils.cpp */, F572E4B821B2175C00C0D82F /* socket_utils.h */, F572E4BA21B2175C00C0D82F /* stream_info_impl.cpp */, F572E4B721B2175C00C0D82F /* stream_info_impl.h */, F572E4BF21B2175D00C0D82F /* stream_inlet_impl.h */, F572E4BC21B2175D00C0D82F /* stream_outlet_impl.cpp */, F572E4BE21B2175D00C0D82F /* stream_outlet_impl.h */, F572E4BB21B2175C00C0D82F /* tcp_server.cpp */, F572E4BD21B2175D00C0D82F /* tcp_server.h */, F572E4C021B2175D00C0D82F /* time_postprocessor.cpp */, F572E4C121B2175D00C0D82F /* time_postprocessor.h */, F572E4B321B2175C00C0D82F /* time_receiver.cpp */, F572E4C721B2175D00C0D82F /* time_receiver.h */, F572E4C521B2175D00C0D82F /* udp_server.cpp */, F572E4C621B2175D00C0D82F /* udp_server.h */, F572E4AC21B2173200C0D82F /* info_receiver.cpp */, F572E4AA21B2173200C0D82F /* info_receiver.h */, F572E4A921B2173200C0D82F /* inlet_connection.cpp */, F572E4AB21B2173200C0D82F /* inlet_connection.h */, F572E49921B2170F00C0D82F /* api_config.cpp */, F572E49C21B2170F00C0D82F /* api_config.h */, F572E49D21B2170F00C0D82F /* cancellable_streambuf.h */, F572E49721B2170E00C0D82F /* cancellation.h */, F572E49821B2170E00C0D82F /* common.cpp */, F572E49A21B2170F00C0D82F /* common.h */, F572E49B21B2170F00C0D82F /* consumer_queue.cpp */, F572E49521B2170E00C0D82F /* consumer_queue.h */, F572E49E21B2170F00C0D82F /* data_receiver.cpp */, F572E49621B2170E00C0D82F /* data_receiver.h */, F572E48E21B216BF00C0D82F /* pugixml */, F572E42E21B214A600C0D82F /* boost */, ); name = Source; sourceTree = SOURCE_ROOT; }; F572E42E21B214A600C0D82F /* boost */ = { isa = PBXGroup; children = ( F572E48621B2163A00C0D82F /* thread */, F572E48321B2160D00C0D82F /* system */, F572E43221B2152C00C0D82F /* serialization */, F572E42F21B214C600C0D82F /* chrono */, ); name = boost; sourceTree = ""; }; F572E42F21B214C600C0D82F /* chrono */ = { isa = PBXGroup; children = ( F572E43021B214D800C0D82F /* chrono.cpp */, ); name = chrono; sourceTree = ""; }; F572E43221B2152C00C0D82F /* serialization */ = { isa = PBXGroup; children = ( F572E45021B2155200C0D82F /* archive_exception.cpp */, F572E43721B2155100C0D82F /* basic_archive.cpp */, F572E44221B2155100C0D82F /* basic_iarchive.cpp */, F572E43F21B2155100C0D82F /* basic_iserializer.cpp */, F572E43321B2155100C0D82F /* basic_oarchive.cpp */, F572E43421B2155100C0D82F /* basic_oserializer.cpp */, F572E43521B2155100C0D82F /* basic_serializer_map.cpp */, F572E48121B215EA00C0D82F /* extended_type_info_typeid.cpp */, F572E43921B2155100C0D82F /* extended_type_info.cpp */, F572E43E21B2155100C0D82F /* void_cast.cpp */, ); name = serialization; sourceTree = ""; }; F572E48321B2160D00C0D82F /* system */ = { isa = PBXGroup; children = ( F572E48421B2162800C0D82F /* error_code.cpp */, ); name = system; sourceTree = ""; }; F572E48621B2163A00C0D82F /* thread */ = { isa = PBXGroup; children = ( F572E48921B2165400C0D82F /* pthread */, F572E48721B2164F00C0D82F /* tss_null.cpp */, ); name = thread; sourceTree = ""; }; F572E48921B2165400C0D82F /* pthread */ = { isa = PBXGroup; children = ( F572E48A21B2167600C0D82F /* once.cpp */, F572E48B21B2167600C0D82F /* thread.cpp */, ); name = pthread; sourceTree = ""; }; F572E48E21B216BF00C0D82F /* pugixml */ = { isa = PBXGroup; children = ( F572E49021B216DE00C0D82F /* pugiconfig.hpp */, F572E49121B216DF00C0D82F /* pugixml.cpp */, F572E48F21B216DE00C0D82F /* pugixml.hpp */, ); name = pugixml; sourceTree = ""; }; F5EA8F9721B2080C00DE3C7B = { isa = PBXGroup; children = ( F572E42921B213AC00C0D82F /* Source */, F572E41821B211AE00C0D82F /* C/C++ API */, F5EA8FA121B2080C00DE3C7B /* Products */, ); sourceTree = ""; }; F5EA8FA121B2080C00DE3C7B /* Products */ = { isa = PBXGroup; children = ( F5EA8FA021B2080C00DE3C7B /* liblsl64.dylib */, F5C0A97721B5DD8A00168AAF /* liblsl_ios.dylib */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ F5C0A97321B5DD8A00168AAF /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( F5DA929B21B5E02A00348AC0 /* stream_info_impl.h in Headers */, F5DA92B521B5E03000348AC0 /* pugiconfig.hpp in Headers */, F5DA92B021B5E02A00348AC0 /* common.h in Headers */, F5DA92A621B5E02A00348AC0 /* udp_server.h in Headers */, F5DA92CE21B5E05800348AC0 /* lsl_cpp.h in Headers */, F5DA92AE21B5E02A00348AC0 /* cancellation.h in Headers */, F5DA92A821B5E02A00348AC0 /* info_receiver.h in Headers */, F5DA929C21B5E02A00348AC0 /* stream_inlet_impl.h in Headers */, F5DA92B721B5E03000348AC0 /* pugixml.hpp in Headers */, F5DA929321B5E02A00348AC0 /* resolver_impl.h in Headers */, F5DA929E21B5E02A00348AC0 /* stream_outlet_impl.h in Headers */, F5DA92A221B5E02A00348AC0 /* time_postprocessor.h in Headers */, F5DA92AA21B5E02A00348AC0 /* inlet_connection.h in Headers */, F5DA92B221B5E02A00348AC0 /* consumer_queue.h in Headers */, F5DA929521B5E02A00348AC0 /* sample.h in Headers */, F5DA929721B5E02A00348AC0 /* send_buffer.h in Headers */, F5DA92A021B5E02A00348AC0 /* tcp_server.h in Headers */, F5DA92AD21B5E02A00348AC0 /* cancellable_streambuf.h in Headers */, F5DA92A421B5E02A00348AC0 /* time_receiver.h in Headers */, F5DA92CD21B5E05800348AC0 /* lsl_c.h in Headers */, F5DA929121B5E02A00348AC0 /* resolve_attempt_udp.h in Headers */, F5DA92AC21B5E02A00348AC0 /* api_config.h in Headers */, F5DA929921B5E02A00348AC0 /* socket_utils.h in Headers */, F5DA92B421B5E02A00348AC0 /* data_receiver.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; F5EA8F9C21B2080C00DE3C7B /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( F572E49221B216DF00C0D82F /* pugixml.hpp in Headers */, F572E4CC21B2175D00C0D82F /* resolve_attempt_udp.h in Headers */, F572E4A421B2170F00C0D82F /* common.h in Headers */, F572E4DD21B2175D00C0D82F /* udp_server.h in Headers */, F572E4DE21B2175D00C0D82F /* time_receiver.h in Headers */, F572E4AE21B2173200C0D82F /* info_receiver.h in Headers */, F572E4D021B2175D00C0D82F /* resolver_impl.h in Headers */, F572E4CB21B2175D00C0D82F /* send_buffer.h in Headers */, F572E4D821B2175D00C0D82F /* time_postprocessor.h in Headers */, F572E49F21B2170F00C0D82F /* consumer_queue.h in Headers */, F572E4CE21B2175D00C0D82F /* stream_info_impl.h in Headers */, F572E4CF21B2175D00C0D82F /* socket_utils.h in Headers */, F572E49321B216DF00C0D82F /* pugiconfig.hpp in Headers */, F572E4A021B2170F00C0D82F /* data_receiver.h in Headers */, F572E4CD21B2175D00C0D82F /* sample.h in Headers */, F572E4D421B2175D00C0D82F /* tcp_server.h in Headers */, F572E4AF21B2173200C0D82F /* inlet_connection.h in Headers */, F572E4D621B2175D00C0D82F /* stream_inlet_impl.h in Headers */, F572E4A121B2170F00C0D82F /* cancellation.h in Headers */, F572E4A621B2170F00C0D82F /* api_config.h in Headers */, F572E41B21B211D500C0D82F /* lsl_cpp.h in Headers */, F572E41C21B211D500C0D82F /* lsl_c.h in Headers */, F572E4A721B2170F00C0D82F /* cancellable_streambuf.h in Headers */, F572E4D521B2175D00C0D82F /* stream_outlet_impl.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ F5C0A97621B5DD8A00168AAF /* lsl_ios */ = { isa = PBXNativeTarget; buildConfigurationList = F5C0A97F21B5DD8A00168AAF /* Build configuration list for PBXNativeTarget "lsl_ios" */; buildPhases = ( F5C0A97321B5DD8A00168AAF /* Headers */, F5C0A97421B5DD8A00168AAF /* Sources */, F5C0A97521B5DD8A00168AAF /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = lsl_ios; productName = lsl_ios; productReference = F5C0A97721B5DD8A00168AAF /* liblsl_ios.dylib */; productType = "com.apple.product-type.library.dynamic"; }; F5EA8F9F21B2080C00DE3C7B /* liblsl */ = { isa = PBXNativeTarget; buildConfigurationList = F5EA8FAB21B2080C00DE3C7B /* Build configuration list for PBXNativeTarget "liblsl" */; buildPhases = ( F5EA8F9C21B2080C00DE3C7B /* Headers */, F5EA8F9D21B2080C00DE3C7B /* Sources */, F5EA8F9E21B2080C00DE3C7B /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = liblsl; productName = liblsl; productReference = F5EA8FA021B2080C00DE3C7B /* liblsl64.dylib */; productType = "com.apple.product-type.library.dynamic"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ F5EA8F9821B2080C00DE3C7B /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Lab Streaming Layer"; TargetAttributes = { F5C0A97621B5DD8A00168AAF = { CreatedOnToolsVersion = 10.1; }; F5EA8F9F21B2080C00DE3C7B = { CreatedOnToolsVersion = 10.1; }; }; }; buildConfigurationList = F5EA8F9B21B2080C00DE3C7B /* Build configuration list for PBXProject "liblsl" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = F5EA8F9721B2080C00DE3C7B; productRefGroup = F5EA8FA121B2080C00DE3C7B /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( F5EA8F9F21B2080C00DE3C7B /* liblsl */, F5C0A97621B5DD8A00168AAF /* lsl_ios */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ F5C0A97421B5DD8A00168AAF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F5DA92A921B5E02A00348AC0 /* inlet_connection.cpp in Sources */, F5DA929021B5E02A00348AC0 /* resolve_attempt_udp.cpp in Sources */, F5DA92AB21B5E02A00348AC0 /* api_config.cpp in Sources */, F5DA92B121B5E02A00348AC0 /* consumer_queue.cpp in Sources */, F5DA92A321B5E02A00348AC0 /* time_receiver.cpp in Sources */, F5DA92C221B5E04D00348AC0 /* basic_serializer_map.cpp in Sources */, F5DA92C721B5E05800348AC0 /* lsl_continuous_resolver_c.cpp in Sources */, F5DA92BD21B5E04D00348AC0 /* basic_archive.cpp in Sources */, F5DA92AF21B5E02A00348AC0 /* common.cpp in Sources */, F5DA92BC21B5E04D00348AC0 /* archive_exception.cpp in Sources */, F5DA92C621B5E05100348AC0 /* chrono.cpp in Sources */, F5DA92B621B5E03000348AC0 /* pugixml.cpp in Sources */, F5DA92A721B5E02A00348AC0 /* info_receiver.cpp in Sources */, F5DA92C421B5E04D00348AC0 /* extended_type_info.cpp in Sources */, F5DA92A121B5E02A00348AC0 /* time_postprocessor.cpp in Sources */, F5DA929221B5E02A00348AC0 /* resolver_impl.cpp in Sources */, F5DA92C921B5E05800348AC0 /* lsl_inlet_c.cpp in Sources */, F5DA92C121B5E04D00348AC0 /* basic_oserializer.cpp in Sources */, F5DA92BF21B5E04D00348AC0 /* basic_iserializer.cpp in Sources */, F5DA929421B5E02A00348AC0 /* sample.cpp in Sources */, F5DA92CA21B5E05800348AC0 /* lsl_outlet_c.cpp in Sources */, F5DA92BB21B5E04100348AC0 /* error_code.cpp in Sources */, F5DA92BE21B5E04D00348AC0 /* basic_iarchive.cpp in Sources */, F5DA929D21B5E02A00348AC0 /* stream_outlet_impl.cpp in Sources */, F5DA92CC21B5E05800348AC0 /* lsl_xml_element_c.cpp in Sources */, F5DA929F21B5E02A00348AC0 /* tcp_server.cpp in Sources */, F5DA92B821B5E03C00348AC0 /* once.cpp in Sources */, F5DA92C321B5E04D00348AC0 /* extended_type_info_typeid.cpp in Sources */, F5DA92B321B5E02A00348AC0 /* data_receiver.cpp in Sources */, F5DA92BA21B5E03C00348AC0 /* tss_null.cpp in Sources */, F5DA929821B5E02A00348AC0 /* socket_utils.cpp in Sources */, F5DA92C521B5E04D00348AC0 /* void_cast.cpp in Sources */, F5DA92CB21B5E05800348AC0 /* lsl_streaminfo_c.cpp in Sources */, F5DA92A521B5E02A00348AC0 /* udp_server.cpp in Sources */, F5DA92C821B5E05800348AC0 /* lsl_freefuncs_c.cpp in Sources */, F5DA92C021B5E04D00348AC0 /* basic_oarchive.cpp in Sources */, F5DA929621B5E02A00348AC0 /* send_buffer.cpp in Sources */, F5DA929A21B5E02A00348AC0 /* stream_info_impl.cpp in Sources */, F5DA92B921B5E03C00348AC0 /* thread.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F5EA8F9D21B2080C00DE3C7B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F572E45B21B2155300C0D82F /* basic_oserializer.cpp in Sources */, F572E4DA21B2175D00C0D82F /* resolver_impl.cpp in Sources */, F572E48821B2164F00C0D82F /* tss_null.cpp in Sources */, F572E4B021B2173200C0D82F /* info_receiver.cpp in Sources */, F572E46921B2155300C0D82F /* basic_iarchive.cpp in Sources */, F572E42721B213A200C0D82F /* lsl_continuous_resolver_c.cpp in Sources */, F572E4DC21B2175D00C0D82F /* udp_server.cpp in Sources */, F572E4CA21B2175D00C0D82F /* time_receiver.cpp in Sources */, F572E4D921B2175D00C0D82F /* send_buffer.cpp in Sources */, F572E4DB21B2175D00C0D82F /* resolve_attempt_udp.cpp in Sources */, F572E4D221B2175D00C0D82F /* tcp_server.cpp in Sources */, F572E4A221B2170F00C0D82F /* common.cpp in Sources */, F572E4A321B2170F00C0D82F /* api_config.cpp in Sources */, F572E48221B215EA00C0D82F /* extended_type_info_typeid.cpp in Sources */, F572E4D321B2175D00C0D82F /* stream_outlet_impl.cpp in Sources */, F572E4A521B2170F00C0D82F /* consumer_queue.cpp in Sources */, F572E4AD21B2173200C0D82F /* inlet_connection.cpp in Sources */, F572E4C921B2175D00C0D82F /* sample.cpp in Sources */, F572E42621B213A200C0D82F /* lsl_streaminfo_c.cpp in Sources */, F572E49421B216DF00C0D82F /* pugixml.cpp in Sources */, F572E42821B213A200C0D82F /* lsl_freefuncs_c.cpp in Sources */, F572E42521B213A200C0D82F /* lsl_inlet_c.cpp in Sources */, F572E4A821B2170F00C0D82F /* data_receiver.cpp in Sources */, F572E4D721B2175D00C0D82F /* time_postprocessor.cpp in Sources */, F572E4D121B2175D00C0D82F /* stream_info_impl.cpp in Sources */, F572E45E21B2155300C0D82F /* basic_archive.cpp in Sources */, F572E43121B214D800C0D82F /* chrono.cpp in Sources */, F572E45A21B2155300C0D82F /* basic_oarchive.cpp in Sources */, F572E4C821B2175D00C0D82F /* socket_utils.cpp in Sources */, F572E48D21B2167600C0D82F /* thread.cpp in Sources */, F572E46021B2155300C0D82F /* extended_type_info.cpp in Sources */, F572E45C21B2155300C0D82F /* basic_serializer_map.cpp in Sources */, F572E42321B213A200C0D82F /* lsl_xml_element_c.cpp in Sources */, F572E46621B2155300C0D82F /* basic_iserializer.cpp in Sources */, F572E46521B2155300C0D82F /* void_cast.cpp in Sources */, F572E42421B213A200C0D82F /* lsl_outlet_c.cpp in Sources */, F572E48521B2162800C0D82F /* error_code.cpp in Sources */, F572E47721B2155300C0D82F /* archive_exception.cpp in Sources */, F572E48C21B2167600C0D82F /* once.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ F5C0A98021B5DD8A00168AAF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = EPZNVL784R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; }; name = Debug; }; F5C0A98121B5DD8A00168AAF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = EPZNVL784R; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; }; name = Release; }; F5EA8FA921B2080C00DE3C7B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", BOOST_ALL_NO_LIB, BOOST_THREAD_BUILD_LIB, BOOST_ASIO_ENABLE_OLD_SERVICES, ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../lslboost"; }; name = Debug; }; F5EA8FAA21B2080C00DE3C7B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( BOOST_ALL_NO_LIB, BOOST_THREAD_BUILD_LIB, BOOST_ASIO_ENABLE_OLD_SERVICES, ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; SYSTEM_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../../lslboost"; }; name = Release; }; F5EA8FAC21B2080C00DE3C7B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEPLOYMENT_POSTPROCESSING = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; PRODUCT_NAME = lsl64; SKIP_INSTALL = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(inherited)"; }; name = Debug; }; F5EA8FAD21B2080C00DE3C7B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; DEPLOYMENT_POSTPROCESSING = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; EXECUTABLE_PREFIX = lib; GCC_ENABLE_CPP_EXCEPTIONS = YES; GCC_ENABLE_CPP_RTTI = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; PRODUCT_NAME = lsl64; SKIP_INSTALL = YES; SYSTEM_HEADER_SEARCH_PATHS = "$(inherited)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ F5C0A97F21B5DD8A00168AAF /* Build configuration list for PBXNativeTarget "lsl_ios" */ = { isa = XCConfigurationList; buildConfigurations = ( F5C0A98021B5DD8A00168AAF /* Debug */, F5C0A98121B5DD8A00168AAF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F5EA8F9B21B2080C00DE3C7B /* Build configuration list for PBXProject "liblsl" */ = { isa = XCConfigurationList; buildConfigurations = ( F5EA8FA921B2080C00DE3C7B /* Debug */, F5EA8FAA21B2080C00DE3C7B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F5EA8FAB21B2080C00DE3C7B /* Build configuration list for PBXNativeTarget "liblsl" */ = { isa = XCConfigurationList; buildConfigurations = ( F5EA8FAC21B2080C00DE3C7B /* Debug */, F5EA8FAD21B2080C00DE3C7B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = F5EA8F9821B2080C00DE3C7B /* Project object */; } liblsl-1.17.7/project/code.blocks/000077500000000000000000000000001517625163100167335ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/GetAllStreams/000077500000000000000000000000001517625163100214425ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/GetAllStreams/GetAllStreams.cbp000066400000000000000000000031061517625163100246370ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/GetFullinfo/000077500000000000000000000000001517625163100211515ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/GetFullinfo/GetFullinfo.cbp000066400000000000000000000030761517625163100240630ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/GetTimeCorrection/000077500000000000000000000000001517625163100223215ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/GetTimeCorrection/GetTimeCorrection.cbp000066400000000000000000000031261517625163100263770ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/ReceiveData/000077500000000000000000000000001517625163100211075ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/ReceiveData/ReceiveData.cbp000066400000000000000000000030761517625163100237570ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/ReceiveDataC/000077500000000000000000000000001517625163100212125ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/ReceiveDataC/ReceiveDataC.cbp000066400000000000000000000031471517625163100241640ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/ReceiveDataInChunks/000077500000000000000000000000001517625163100225525ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/ReceiveDataInChunks/ReceiveDataInChunks.cbp000066400000000000000000000031361517625163100270620ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/ReceiveDataSimple/000077500000000000000000000000001517625163100222615ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/ReceiveDataSimple/ReceiveDataSimple.cbp000066400000000000000000000031261517625163100262770ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/ReceiveStringMarkers/000077500000000000000000000000001517625163100230315ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/ReceiveStringMarkers/ReceiveStringMarkers.cbp000066400000000000000000000031421517625163100276150ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/SendData/000077500000000000000000000000001517625163100204165ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/SendData/SendData.cbp000066400000000000000000000030621517625163100225700ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/SendDataC/000077500000000000000000000000001517625163100205215ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/SendDataC/SendDataC.cbp000066400000000000000000000032741517625163100230030ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/SendDataInChunks/000077500000000000000000000000001517625163100220615ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/SendDataInChunks/SendDataInChunks.cbp000066400000000000000000000031221517625163100256730ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/SendDataSimple/000077500000000000000000000000001517625163100215705ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/SendDataSimple/SendDataSimple.cbp000066400000000000000000000031121517625163100251100ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/SendStringMarkers/000077500000000000000000000000001517625163100223405ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/SendStringMarkers/SendStringMarkers.cbp000066400000000000000000000031261517625163100264350ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/StressTest/000077500000000000000000000000001517625163100210565ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/StressTest/StressTest.cbp000066400000000000000000000047131517625163100236740ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/liblsl.workspace000066400000000000000000000034021517625163100221330ustar00rootroot00000000000000 liblsl-1.17.7/project/code.blocks/liblsl/000077500000000000000000000000001517625163100202145ustar00rootroot00000000000000liblsl-1.17.7/project/code.blocks/liblsl/liblsl.cbp000066400000000000000000000132631517625163100221700ustar00rootroot00000000000000 liblsl-1.17.7/scripts/000077500000000000000000000000001517625163100145665ustar00rootroot00000000000000liblsl-1.17.7/scripts/apple_codesign.sh000077500000000000000000000120121517625163100200750ustar00rootroot00000000000000#!/bin/bash # ============================================================================= # Apple Code Signing Script for LSL Frameworks # ============================================================================= # Signs macOS and iOS frameworks with the appropriate code signing identity. # # Usage: # ./scripts/apple_codesign.sh [--platform macos|ios] # # Environment Variables: # APPLE_CODE_SIGN_IDENTITY_APP - Code signing identity (default: "Developer ID Application") # Set to "-" for ad-hoc signing (local development) # # Examples: # # Sign macOS framework (with hardened runtime and entitlements) # ./scripts/apple_codesign.sh install/Frameworks/lsl.framework --platform macos # # # Sign iOS framework # ./scripts/apple_codesign.sh build-iOS/Frameworks/lsl.framework --platform ios # # # Ad-hoc signing for local development # APPLE_CODE_SIGN_IDENTITY_APP="-" ./scripts/apple_codesign.sh install/Frameworks/lsl.framework # ============================================================================= set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Default configuration SIGN_IDENTITY="${APPLE_CODE_SIGN_IDENTITY_APP:-Developer ID Application}" ENTITLEMENTS_FILE="${ENTITLEMENTS_FILE:-$PROJECT_ROOT/lsl.entitlements}" PLATFORM="macos" # Parse arguments FRAMEWORK_PATH="" while [[ $# -gt 0 ]]; do case $1 in --platform) PLATFORM="$2" shift 2 ;; --identity) SIGN_IDENTITY="$2" shift 2 ;; --entitlements) ENTITLEMENTS_FILE="$2" shift 2 ;; -h|--help) echo "Usage: $0 [--platform macos|ios] [--identity ] [--entitlements ]" echo "" echo "Environment Variables:" echo " APPLE_CODE_SIGN_IDENTITY_APP - Code signing identity (default: 'Developer ID Application')" echo " ENTITLEMENTS_FILE - Path to entitlements file (default: lsl.entitlements)" exit 0 ;; -*) echo "Unknown option: $1" exit 1 ;; *) FRAMEWORK_PATH="$1" shift ;; esac done if [[ -z "$FRAMEWORK_PATH" ]]; then echo "Error: Framework path required" echo "Usage: $0 [--platform macos|ios]" exit 1 fi if [[ ! -d "$FRAMEWORK_PATH" ]]; then echo "Error: Framework not found at: $FRAMEWORK_PATH" exit 1 fi echo "=== Apple Code Signing ===" echo "Framework: $FRAMEWORK_PATH" echo "Platform: $PLATFORM" echo "Identity: $SIGN_IDENTITY" echo "" # Determine the binary path within the framework if [[ "$PLATFORM" == "macos" ]]; then # macOS framework: Versions/A/lsl BINARY_PATH="$FRAMEWORK_PATH/Versions/A/lsl" if [[ ! -f "$BINARY_PATH" ]]; then echo "Error: macOS framework binary not found at: $BINARY_PATH" exit 1 fi # Check for entitlements file ENTITLEMENTS_ARG="" if [[ -f "$ENTITLEMENTS_FILE" ]]; then ENTITLEMENTS_ARG="--entitlements $ENTITLEMENTS_FILE" echo "Entitlements: $ENTITLEMENTS_FILE" else echo "Warning: Entitlements file not found at $ENTITLEMENTS_FILE" echo " Signing without entitlements (may affect notarization)" fi echo "" # Sign the binary first (with hardened runtime for notarization) echo "Signing binary: $BINARY_PATH" codesign --force --deep --sign "$SIGN_IDENTITY" \ --options runtime \ $ENTITLEMENTS_ARG \ "$BINARY_PATH" # Verify binary signature echo "Verifying binary signature..." codesign --verify --verbose --strict "$BINARY_PATH" # Sign the entire framework bundle echo "" echo "Signing framework bundle: $FRAMEWORK_PATH" codesign --force --deep --sign "$SIGN_IDENTITY" \ --options runtime \ $ENTITLEMENTS_ARG \ "$FRAMEWORK_PATH" elif [[ "$PLATFORM" == "ios" || "$PLATFORM" == "ios-simulator" ]]; then # iOS framework: lsl (no Versions directory) BINARY_PATH="$FRAMEWORK_PATH/lsl" if [[ ! -f "$BINARY_PATH" ]]; then echo "Error: iOS framework binary not found at: $BINARY_PATH" exit 1 fi echo "" # Sign the binary (no hardened runtime for iOS) echo "Signing binary: $BINARY_PATH" codesign --force --deep --sign "$SIGN_IDENTITY" "$BINARY_PATH" # Verify binary signature echo "Verifying binary signature..." codesign --verify --verbose --strict "$BINARY_PATH" # Sign the entire framework bundle echo "" echo "Signing framework bundle: $FRAMEWORK_PATH" codesign --force --deep --sign "$SIGN_IDENTITY" "$FRAMEWORK_PATH" else echo "Error: Unknown platform: $PLATFORM" echo "Supported platforms: macos, ios, ios-simulator" exit 1 fi # Final verification echo "" echo "Verifying framework signature..." codesign --verify --verbose --deep --strict "$FRAMEWORK_PATH" echo "" echo "=== Code Signing Complete ===" liblsl-1.17.7/scripts/apple_create_xcframework.sh000077500000000000000000000142261517625163100221660ustar00rootroot00000000000000#!/bin/bash # ============================================================================= # Apple XCFramework Creation Script for LSL # ============================================================================= # Creates an XCFramework from macOS, iOS, and iOS Simulator frameworks. # # Usage: # ./scripts/apple_create_xcframework.sh --macos --ios --ios-simulator [--output ] # # Environment Variables: # APPLE_CODE_SIGN_IDENTITY_APP - Code signing identity (default: "Developer ID Application") # LSL_VERSION - Version string for output filename (optional) # # Examples: # # Create XCFramework from all three platforms # ./scripts/apple_create_xcframework.sh \ # --macos build-macOS/Frameworks/lsl.framework \ # --ios build-iOS/Frameworks/lsl.framework \ # --ios-simulator build-iOS-Simulator/Frameworks/lsl.framework # # # Create XCFramework with custom output directory # ./scripts/apple_create_xcframework.sh \ # --macos build-macOS/Frameworks/lsl.framework \ # --ios build-iOS/Frameworks/lsl.framework \ # --ios-simulator build-iOS-Simulator/Frameworks/lsl.framework \ # --output package/ # ============================================================================= set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Default configuration SIGN_IDENTITY="${APPLE_CODE_SIGN_IDENTITY_APP:-Developer ID Application}" OUTPUT_DIR="." MACOS_FRAMEWORK="" IOS_FRAMEWORK="" IOS_SIM_FRAMEWORK="" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --macos) MACOS_FRAMEWORK="$2" shift 2 ;; --ios) IOS_FRAMEWORK="$2" shift 2 ;; --ios-simulator) IOS_SIM_FRAMEWORK="$2" shift 2 ;; --output) OUTPUT_DIR="$2" shift 2 ;; --identity) SIGN_IDENTITY="$2" shift 2 ;; -h|--help) echo "Usage: $0 --macos --ios --ios-simulator [--output ]" echo "" echo "Options:" echo " --macos Path to macOS framework" echo " --ios Path to iOS device framework" echo " --ios-simulator Path to iOS simulator framework" echo " --output Output directory (default: current directory)" echo " --identity Override code signing identity" echo "" echo "Environment Variables:" echo " APPLE_CODE_SIGN_IDENTITY_APP - Code signing identity" echo " LSL_VERSION - Version for output filename" exit 0 ;; -*) echo "Unknown option: $1" exit 1 ;; *) echo "Unexpected argument: $1" exit 1 ;; esac done # Validate inputs - need at least two frameworks FRAMEWORK_COUNT=0 FRAMEWORK_ARGS=() if [[ -n "$MACOS_FRAMEWORK" ]]; then if [[ ! -d "$MACOS_FRAMEWORK" ]]; then echo "Error: macOS framework not found at: $MACOS_FRAMEWORK" exit 1 fi FRAMEWORK_ARGS+=("-framework" "$MACOS_FRAMEWORK") ((FRAMEWORK_COUNT++)) fi if [[ -n "$IOS_FRAMEWORK" ]]; then if [[ ! -d "$IOS_FRAMEWORK" ]]; then echo "Error: iOS framework not found at: $IOS_FRAMEWORK" exit 1 fi FRAMEWORK_ARGS+=("-framework" "$IOS_FRAMEWORK") ((FRAMEWORK_COUNT++)) fi if [[ -n "$IOS_SIM_FRAMEWORK" ]]; then if [[ ! -d "$IOS_SIM_FRAMEWORK" ]]; then echo "Error: iOS Simulator framework not found at: $IOS_SIM_FRAMEWORK" exit 1 fi FRAMEWORK_ARGS+=("-framework" "$IOS_SIM_FRAMEWORK") ((FRAMEWORK_COUNT++)) fi if [[ $FRAMEWORK_COUNT -lt 2 ]]; then echo "Error: At least two frameworks required to create XCFramework" echo "Provide --macos, --ios, and/or --ios-simulator paths" exit 1 fi # Determine version if [[ -z "$LSL_VERSION" ]]; then # Try to get from macOS framework Info.plist if [[ -n "$MACOS_FRAMEWORK" ]]; then INFO_PLIST="$MACOS_FRAMEWORK/Versions/A/Resources/Info.plist" if [[ -f "$INFO_PLIST" ]]; then LSL_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$INFO_PLIST" 2>/dev/null || echo "") fi fi # Try iOS framework if [[ -z "$LSL_VERSION" && -n "$IOS_FRAMEWORK" ]]; then INFO_PLIST="$IOS_FRAMEWORK/Info.plist" if [[ -f "$INFO_PLIST" ]]; then LSL_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$INFO_PLIST" 2>/dev/null || echo "") fi fi if [[ -z "$LSL_VERSION" ]]; then LSL_VERSION="unknown" fi fi # Create output directory mkdir -p "$OUTPUT_DIR" XCFRAMEWORK_PATH="$OUTPUT_DIR/lsl.xcframework" ZIP_PATH="$OUTPUT_DIR/lsl.xcframework.${LSL_VERSION}.zip" echo "=== XCFramework Creation ===" echo "Version: $LSL_VERSION" echo "Identity: $SIGN_IDENTITY" echo "Output: $XCFRAMEWORK_PATH" echo "" echo "Input frameworks:" [[ -n "$MACOS_FRAMEWORK" ]] && echo " macOS: $MACOS_FRAMEWORK" [[ -n "$IOS_FRAMEWORK" ]] && echo " iOS: $IOS_FRAMEWORK" [[ -n "$IOS_SIM_FRAMEWORK" ]] && echo " iOS Simulator: $IOS_SIM_FRAMEWORK" echo "" # Remove existing XCFramework rm -rf "$XCFRAMEWORK_PATH" # Create XCFramework echo "Creating XCFramework..." xcodebuild -create-xcframework \ "${FRAMEWORK_ARGS[@]}" \ -output "$XCFRAMEWORK_PATH" # Sign the XCFramework echo "" echo "Signing XCFramework..." codesign --force --deep --sign "$SIGN_IDENTITY" "$XCFRAMEWORK_PATH" # Verify signature echo "" echo "Verifying XCFramework signature..." codesign --verify --verbose --deep --strict "$XCFRAMEWORK_PATH" # Create zip archive echo "" echo "Creating zip archive: $ZIP_PATH" ditto -c -k --sequesterRsrc --keepParent "$XCFRAMEWORK_PATH" "$ZIP_PATH" echo "" echo "=== XCFramework Complete ===" echo "XCFramework: $XCFRAMEWORK_PATH" echo "Zip archive: $ZIP_PATH" # Export for use in CI if [[ -n "$GITHUB_ENV" ]]; then echo "XCFRAMEWORK_PATH=$XCFRAMEWORK_PATH" >> "$GITHUB_ENV" echo "XCFRAMEWORK_ZIP=$ZIP_PATH" >> "$GITHUB_ENV" fi liblsl-1.17.7/scripts/apple_package_notarize.sh000077500000000000000000000125151517625163100216200ustar00rootroot00000000000000#!/bin/bash # ============================================================================= # Apple Package and Notarize Script for LSL # ============================================================================= # Creates a macOS installer package (.pkg) and optionally notarizes it. # # Usage: # ./scripts/apple_package_notarize.sh [--notarize] [--output ] # # Environment Variables: # APPLE_CODE_SIGN_IDENTITY_INST - Installer signing identity (default: "Developer ID Installer") # APPLE_DEVELOPMENT_TEAM - Team ID for notarization (required for --notarize) # APPLE_NOTARIZE_USERNAME - Apple ID for notarization (required for --notarize) # APPLE_NOTARIZE_PASSWORD - App-specific password (required for --notarize) # # Examples: # # Create signed installer package (no notarization) # ./scripts/apple_package_notarize.sh install/Frameworks/lsl.framework # # # Create and notarize installer package # ./scripts/apple_package_notarize.sh install/Frameworks/lsl.framework --notarize # # # Specify output directory # ./scripts/apple_package_notarize.sh install/Frameworks/lsl.framework --output package/ # ============================================================================= set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # Default configuration SIGN_IDENTITY="${APPLE_CODE_SIGN_IDENTITY_INST:-Developer ID Installer}" TEAM_ID="${APPLE_DEVELOPMENT_TEAM:-}" NOTARIZE_USERNAME="${APPLE_NOTARIZE_USERNAME:-}" NOTARIZE_PASSWORD="${APPLE_NOTARIZE_PASSWORD:-}" OUTPUT_DIR="." DO_NOTARIZE=false # Parse arguments FRAMEWORK_PATH="" while [[ $# -gt 0 ]]; do case $1 in --notarize) DO_NOTARIZE=true shift ;; --output) OUTPUT_DIR="$2" shift 2 ;; --identity) SIGN_IDENTITY="$2" shift 2 ;; -h|--help) echo "Usage: $0 [--notarize] [--output ]" echo "" echo "Options:" echo " --notarize Submit to Apple for notarization and staple the ticket" echo " --output Output directory for the .pkg file (default: current directory)" echo " --identity Override installer signing identity" echo "" echo "Environment Variables:" echo " APPLE_CODE_SIGN_IDENTITY_INST - Installer signing identity" echo " APPLE_DEVELOPMENT_TEAM - Team ID for notarization" echo " APPLE_NOTARIZE_USERNAME - Apple ID for notarization" echo " APPLE_NOTARIZE_PASSWORD - App-specific password for notarization" exit 0 ;; -*) echo "Unknown option: $1" exit 1 ;; *) FRAMEWORK_PATH="$1" shift ;; esac done if [[ -z "$FRAMEWORK_PATH" ]]; then echo "Error: Framework path required" echo "Usage: $0 [--notarize] [--output ]" exit 1 fi if [[ ! -d "$FRAMEWORK_PATH" ]]; then echo "Error: Framework not found at: $FRAMEWORK_PATH" exit 1 fi # Get version from framework's Info.plist INFO_PLIST="$FRAMEWORK_PATH/Versions/A/Resources/Info.plist" if [[ ! -f "$INFO_PLIST" ]]; then # Try alternative location INFO_PLIST="$FRAMEWORK_PATH/Resources/Info.plist" fi if [[ -f "$INFO_PLIST" ]]; then LSL_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$INFO_PLIST") else echo "Warning: Could not find Info.plist, using 'unknown' as version" LSL_VERSION="unknown" fi # Create output directory mkdir -p "$OUTPUT_DIR" # Package filename PKG_NAME="liblsl-${LSL_VERSION}-Darwin-universal.pkg" PKG_PATH="$OUTPUT_DIR/$PKG_NAME" echo "=== Apple Package Creation ===" echo "Framework: $FRAMEWORK_PATH" echo "Version: $LSL_VERSION" echo "Output: $PKG_PATH" echo "Identity: $SIGN_IDENTITY" echo "" # Create the installer package echo "Creating installer package..." productbuild --sign "$SIGN_IDENTITY" \ --component "$FRAMEWORK_PATH" \ /Library/Frameworks \ "$PKG_PATH" echo "Package created: $PKG_PATH" # Notarization if [[ "$DO_NOTARIZE" == true ]]; then echo "" echo "=== Notarization ===" # Check required environment variables if [[ -z "$NOTARIZE_USERNAME" ]]; then echo "Error: APPLE_NOTARIZE_USERNAME not set" exit 1 fi if [[ -z "$NOTARIZE_PASSWORD" ]]; then echo "Error: APPLE_NOTARIZE_PASSWORD not set" exit 1 fi if [[ -z "$TEAM_ID" ]]; then echo "Error: APPLE_DEVELOPMENT_TEAM not set" exit 1 fi echo "Submitting to Apple notarization service..." echo " Apple ID: $NOTARIZE_USERNAME" echo " Team ID: $TEAM_ID" echo "" xcrun notarytool submit "$PKG_PATH" \ --apple-id "$NOTARIZE_USERNAME" \ --password "$NOTARIZE_PASSWORD" \ --team-id "$TEAM_ID" \ --wait echo "" echo "Stapling notarization ticket..." xcrun stapler staple "$PKG_PATH" echo "" echo "Validating stapled ticket..." xcrun stapler validate "$PKG_PATH" fi echo "" echo "=== Package Complete ===" echo "Output: $PKG_PATH" # Export for use in CI if [[ -n "$GITHUB_ENV" ]]; then echo "LSL_VERSION=$LSL_VERSION" >> "$GITHUB_ENV" echo "PKG_PATH=$PKG_PATH" >> "$GITHUB_ENV" fi liblsl-1.17.7/scripts/build_apple_frameworks.sh000077500000000000000000000216771517625163100216620ustar00rootroot00000000000000#!/bin/bash # ============================================================================= # Build Script for liblsl Apple Frameworks # ============================================================================= # Builds, signs, and optionally packages/notarizes LSL frameworks for Apple # platforms (macOS, iOS, iOS Simulator). # # This is a convenience wrapper that orchestrates the build process and calls # the individual signing/packaging scripts. # # Usage: # ./scripts/build_apple_frameworks.sh [macos|ios|xcframework|all] [--notarize] # # Environment Variables: # BUILD_TYPE - Build type (default: Release) # APPLE_CODE_SIGN_IDENTITY_APP - App signing identity (default: "Developer ID Application") # APPLE_CODE_SIGN_IDENTITY_INST - Installer signing identity (default: "Developer ID Installer") # APPLE_DEVELOPMENT_TEAM - Team ID for notarization # APPLE_NOTARIZE_USERNAME - Apple ID for notarization # APPLE_NOTARIZE_PASSWORD - App-specific password for notarization # # Examples: # # Build macOS framework only (ad-hoc signed) # APPLE_CODE_SIGN_IDENTITY_APP="-" ./scripts/build_apple_frameworks.sh macos # # # Build all frameworks and create XCFramework # ./scripts/build_apple_frameworks.sh all # # # Build macOS and notarize installer package # ./scripts/build_apple_frameworks.sh macos --notarize # ============================================================================= set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" BUILD_DIR="$PROJECT_ROOT/cmake-build-apple" INSTALL_DIR="$PROJECT_ROOT/install" PACKAGE_DIR="$PROJECT_ROOT/package" # Default configuration BUILD_TYPE="${BUILD_TYPE:-Release}" export APPLE_CODE_SIGN_IDENTITY_APP="${APPLE_CODE_SIGN_IDENTITY_APP:-Developer ID Application}" export APPLE_CODE_SIGN_IDENTITY_INST="${APPLE_CODE_SIGN_IDENTITY_INST:-Developer ID Installer}" # Parse arguments PLATFORM="${1:-all}" DO_NOTARIZE=false shift || true while [[ $# -gt 0 ]]; do case $1 in --notarize) DO_NOTARIZE=true shift ;; -h|--help) echo "Usage: $0 [macos|ios|xcframework|all] [--notarize]" echo "" echo "Platforms:" echo " macos Build macOS universal framework" echo " ios Build iOS device and simulator frameworks" echo " xcframework Create XCFramework from existing builds" echo " all Build all platforms and create XCFramework" echo "" echo "Options:" echo " --notarize Submit macOS installer for notarization" echo "" echo "Environment Variables:" echo " BUILD_TYPE - Release or Debug (default: Release)" echo " APPLE_CODE_SIGN_IDENTITY_APP - Code signing identity" echo " APPLE_CODE_SIGN_IDENTITY_INST - Installer signing identity" echo " APPLE_DEVELOPMENT_TEAM - Team ID for notarization" echo " APPLE_NOTARIZE_USERNAME - Apple ID for notarization" echo " APPLE_NOTARIZE_PASSWORD - App-specific password" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esac done # Show configuration echo "=== LSL Apple Framework Build ===" echo "Platform: $PLATFORM" echo "Build Type: $BUILD_TYPE" echo "Build Dir: $BUILD_DIR" echo "Install Dir: $INSTALL_DIR" echo "Code Sign Identity (App): $APPLE_CODE_SIGN_IDENTITY_APP" echo "Code Sign Identity (Inst): $APPLE_CODE_SIGN_IDENTITY_INST" echo "Notarize: $DO_NOTARIZE" echo "" # Build macOS framework build_macos() { echo "" echo "=========================================" echo "Building macOS Framework (Universal)" echo "=========================================" rm -rf "$BUILD_DIR/macos" mkdir -p "$BUILD_DIR/macos" cmake -B "$BUILD_DIR/macos" -S "$PROJECT_ROOT" \ -G Xcode \ -DLSL_FRAMEWORK=ON \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \ -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/macos" cmake --build "$BUILD_DIR/macos" --config "$BUILD_TYPE" -j cmake --build "$BUILD_DIR/macos" --config "$BUILD_TYPE" --target install echo "" echo "Signing macOS framework..." "$SCRIPT_DIR/apple_codesign.sh" "$INSTALL_DIR/macos/Frameworks/lsl.framework" --platform macos if [[ "$DO_NOTARIZE" == true ]]; then echo "" echo "Creating and notarizing macOS installer..." mkdir -p "$PACKAGE_DIR" NOTARIZE_ARG="--notarize" else NOTARIZE_ARG="" fi "$SCRIPT_DIR/apple_package_notarize.sh" "$INSTALL_DIR/macos/Frameworks/lsl.framework" \ --output "$PACKAGE_DIR" $NOTARIZE_ARG } # Build iOS device framework build_ios_device() { echo "" echo "=========================================" echo "Building iOS Device Framework" echo "=========================================" rm -rf "$BUILD_DIR/ios-device" mkdir -p "$BUILD_DIR/ios-device" cmake -B "$BUILD_DIR/ios-device" -S "$PROJECT_ROOT" \ -G Xcode \ -DLSL_FRAMEWORK=ON \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_TOOLCHAIN_FILE="$PROJECT_ROOT/cmake/ios.toolchain.cmake" \ -DPLATFORM=OS64 \ -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/ios-device" cmake --build "$BUILD_DIR/ios-device" --config "$BUILD_TYPE" -j cmake --build "$BUILD_DIR/ios-device" --config "$BUILD_TYPE" --target install echo "" echo "Signing iOS device framework..." "$SCRIPT_DIR/apple_codesign.sh" "$INSTALL_DIR/ios-device/Frameworks/lsl.framework" --platform ios } # Build iOS simulator framework build_ios_simulator() { echo "" echo "=========================================" echo "Building iOS Simulator Framework" echo "=========================================" rm -rf "$BUILD_DIR/ios-simulator" mkdir -p "$BUILD_DIR/ios-simulator" cmake -B "$BUILD_DIR/ios-simulator" -S "$PROJECT_ROOT" \ -G Xcode \ -DLSL_FRAMEWORK=ON \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_TOOLCHAIN_FILE="$PROJECT_ROOT/cmake/ios.toolchain.cmake" \ -DPLATFORM=SIMULATOR64COMBINED \ -DCMAKE_INSTALL_PREFIX="$INSTALL_DIR/ios-simulator" cmake --build "$BUILD_DIR/ios-simulator" --config "$BUILD_TYPE" -j cmake --build "$BUILD_DIR/ios-simulator" --config "$BUILD_TYPE" --target install echo "" echo "Signing iOS simulator framework..." "$SCRIPT_DIR/apple_codesign.sh" "$INSTALL_DIR/ios-simulator/Frameworks/lsl.framework" --platform ios-simulator } # Create XCFramework create_xcframework() { echo "" echo "=========================================" echo "Creating XCFramework" echo "=========================================" XCFW_ARGS=() if [[ -d "$INSTALL_DIR/macos/Frameworks/lsl.framework" ]]; then XCFW_ARGS+=("--macos" "$INSTALL_DIR/macos/Frameworks/lsl.framework") fi if [[ -d "$INSTALL_DIR/ios-device/Frameworks/lsl.framework" ]]; then XCFW_ARGS+=("--ios" "$INSTALL_DIR/ios-device/Frameworks/lsl.framework") fi if [[ -d "$INSTALL_DIR/ios-simulator/Frameworks/lsl.framework" ]]; then XCFW_ARGS+=("--ios-simulator" "$INSTALL_DIR/ios-simulator/Frameworks/lsl.framework") fi if [[ ${#XCFW_ARGS[@]} -lt 4 ]]; then # Each framework needs 2 args (--type and path) echo "Error: Need at least two frameworks to create XCFramework" echo "Build macos and/or ios first" exit 1 fi mkdir -p "$PACKAGE_DIR" "$SCRIPT_DIR/apple_create_xcframework.sh" "${XCFW_ARGS[@]}" --output "$PACKAGE_DIR" } # Main execution case "$PLATFORM" in "macos") build_macos ;; "ios") build_ios_device build_ios_simulator ;; "xcframework") create_xcframework ;; "all") build_macos build_ios_device build_ios_simulator create_xcframework ;; *) echo "Error: Unknown platform: $PLATFORM" echo "Use: macos, ios, xcframework, or all" exit 1 ;; esac echo "" echo "=========================================" echo "Build Complete!" echo "=========================================" echo "" echo "Output locations:" if [[ -d "$INSTALL_DIR/macos/Frameworks/lsl.framework" ]]; then echo " macOS Framework: $INSTALL_DIR/macos/Frameworks/lsl.framework" fi if [[ -d "$INSTALL_DIR/ios-device/Frameworks/lsl.framework" ]]; then echo " iOS Device Framework: $INSTALL_DIR/ios-device/Frameworks/lsl.framework" fi if [[ -d "$INSTALL_DIR/ios-simulator/Frameworks/lsl.framework" ]]; then echo " iOS Simulator Framework: $INSTALL_DIR/ios-simulator/Frameworks/lsl.framework" fi if [[ -d "$PACKAGE_DIR/lsl.xcframework" ]]; then echo " XCFramework: $PACKAGE_DIR/lsl.xcframework" fi if ls "$PACKAGE_DIR"/*.pkg 1> /dev/null 2>&1; then echo " Installer Package: $PACKAGE_DIR/*.pkg" fi liblsl-1.17.7/scripts/standalone_compilation_linux.sh000077500000000000000000000017401517625163100230740ustar00rootroot00000000000000#!/bin/sh # This script builds the liblsl.so for linux machines without bigger quirks # and no recent CMake version, i.e. ARM boards and PCs with old distributions. # For development, install a recent CMake version, either via pip # (pip install cmake) or as binary download from cmake.org set -e -x cd "$(dirname "$0")/.." # Try to read LSLGITREVISION from git if the variable isn't set echo ${LSLGITREVISION:="$(git describe --tags HEAD)"} ${CXX:-g++} -fPIC -fvisibility=hidden -O2 ${CFLAGS} ${CXXFLAGS} -Ilslboost \ -DBOOST_ALL_NO_LIB \ -DASIO_NO_DEPRECATED \ -DLOGURU_DEBUG_LOGGING=0 \ -DLSL_LIBRARY_INFO_STR=\"${LSLGITREVISION:-"built from standalone build script"}\" \ src/*.cpp src/util/*.cpp \ thirdparty/pugixml/pugixml.cpp -Ithirdparty/pugixml \ thirdparty/loguru/loguru.cpp -Ithirdparty/loguru \ -Ithirdparty/asio \ ${LDFLAGS} \ -shared -o liblsl.so -lpthread -lrt -ldl ${CC:-gcc} -O2 ${CFLAGS} -Iinclude testing/lslver.c -o lslver -L. -llsl LD_LIBRARY_PATH=. ./lslver liblsl-1.17.7/scripts/update_lslboost.sh000077500000000000000000000011641517625163100203320ustar00rootroot00000000000000# the absolute path to the extracted boost source archive (https://www.boost.org/users/download/) set -e set -x cd "$(dirname "$0")/.." BOOSTPATH=/tmp/boost_1_89_0 TMPPATH=/tmp/lslboost # copy all needed boost files and rename all mentions of boost to lslboost mkdir -p $TMPPATH bcp --unix-lines --boost=$BOOSTPATH --namespace=lslboost --scan `find src -regex ".+\.[ch]p*"` $TMPPATH # remove superfluous directories: rm -f $TMPPATH/Jamroot find $TMPPATH -type d -and \( -name build -o -name test -o -name edg -o -name dmc -o -name msvc?0 -o -name bcc* \) -print0 | xargs -0 rm -rf rsync -HAXavr --del $TMPPATH/boost lslboost liblsl-1.17.7/src/000077500000000000000000000000001517625163100136665ustar00rootroot00000000000000liblsl-1.17.7/src/api_config.cpp000066400000000000000000000303501517625163100164710ustar00rootroot00000000000000#include "api_config.h" #include "common.h" #include "util/cast.hpp" #include "util/strfuns.hpp" #include #include #include #include #include #include #include #include using namespace lsl; /// Substitute the "~" character by the full home directory (according to environment variables). std::string expand_tilde(const std::string &filename) { // NOLINTBEGIN(concurrency-mt-unsafe) // NOLINTBEGIN(bugprone-assignment-in-if-condition) const char *home, *path; if (!filename.empty() && filename[0] == '~') { std::string homedir; if ((home = getenv("HOME")) || (home = getenv("USERPROFILE"))) homedir = home; else if ((home = getenv("HOMEDRIVE")) && (path = getenv("HOMEPATH"))) homedir = std::string(home) + path; else { LOG_F(WARNING, "Cannot determine the user's home directory; config files in the home " "directory will not be discovered."); return filename; } return homedir + filename.substr(1); } return filename; // NOLINTEND(bugprone-assignment-in-if-condition) // NOLINTEND(concurrency-mt-unsafe) } /// Parse a set specifier (a string of the form {a, b, c, ...}) into a vector of strings. static std::vector parse_set(const std::string &setstr) { std::vector result; if ((setstr.size() > 2) && setstr[0] == '{' && setstr[setstr.size() - 1] == '}') { result = splitandtrim(setstr.substr(1, setstr.size() - 2), ',', false); } return result; } /// Returns true if the file exists and is openable for reading bool file_is_readable(const std::string &filename) { std::ifstream f(filename); return f.good(); } api_config::api_config() { // first check to see if a config content was provided if (!api_config_content_.empty()) { try { // if so, load it from the content load_from_content(api_config_content_); // free the content this can only be called once api_config_content_.clear(); // config loaded successfully, so return return; } catch (std::exception &e) { LOG_F(ERROR, "Error parsing config content: '%s', rolling back to defaults", e.what()); // clear the content, it was invalid anyway api_config_content_.clear(); } } // otherwise, load the config from a file // for each config file location under consideration... std::vector filenames; if (!api_config_filename_.empty()) { // if a config file name was set, use it if it is readable if (file_is_readable(api_config_filename_)) { filenames.insert(filenames.begin(), api_config_filename_); } else { LOG_F(ERROR, "Config file %s not found", api_config_filename_.c_str()); } } // The LSLAPICFG env var, if set, takes precedence over the API-set filename. // NOLINTNEXTLINE(concurrency-mt-unsafe) if (auto *cfgpath = getenv("LSLAPICFG")) { std::string envcfg(cfgpath); if (!file_is_readable(envcfg)) LOG_F(ERROR, "LSLAPICFG file %s not found", envcfg.c_str()); else filenames.insert(filenames.begin(), envcfg); } filenames.emplace_back("lsl_api.cfg"); filenames.push_back(expand_tilde("~/lsl_api/lsl_api.cfg")); filenames.emplace_back("/etc/lsl_api/lsl_api.cfg"); for (auto &filename : filenames) { try { if (file_is_readable(filename)) { // try to load it if the file exists load_from_file(filename); // successful: finished return; } } catch (std::exception &e) { LOG_F(ERROR, "Error trying to load config file %s: %s", filename.c_str(), e.what()); } } // unsuccessful: load default settings load_from_file(); } void api_config::load_from_file(const std::string &filename) { try { INI pt; if (!filename.empty()) { std::ifstream infile(filename); if (infile.good()) { pt.load(infile); } } api_config::load(pt); // log config filename only after setting the verbosity level and all config has been read if (!filename.empty()) LOG_F(INFO, "Configuration loaded from %s", filename.c_str()); else LOG_F(INFO, "Loaded default config"); } catch (std::exception &e) { LOG_F(ERROR, "Error parsing config file '%s': '%s', rolling back to defaults", filename.c_str(), e.what()); // any error: assign defaults load_from_file(); // and rethrow throw e; } } void api_config::load_from_content(const std::string &content) { // load the content into an INI object INI pt; if (!content.empty()) { std::istringstream content_stream(content); pt.load(content_stream); } api_config::load(pt); LOG_F(INFO, "Configuration loaded from content"); } void api_config::load(INI &pt) { // read the [log] settings #if LOGURU_DEBUG_LOGGING // When built with LSL_DEBUGLOG=ON, default to verbose logging int log_level = pt.get("log.level", 1); #else int log_level = pt.get("log.level", static_cast(loguru::Verbosity_INFO)); #endif if (log_level < -3 || log_level > 9) throw std::runtime_error("Invalid log.level (valid range: -3 to 9"); std::string log_file = pt.get("log.file", ""); if (!log_file.empty()) { loguru::add_file(log_file.c_str(), loguru::Append, log_level); // don't duplicate log to stderr loguru::g_stderr_verbosity = -9; } else loguru::g_stderr_verbosity = log_level; // read out the [ports] parameters multicast_port_ = pt.get("ports.MulticastPort", 16571); base_port_ = pt.get("ports.BasePort", 16572); port_range_ = pt.get("ports.PortRange", 32); allow_random_ports_ = pt.get("ports.AllowRandomPorts", true); std::string ipv6_str = pt.get("ports.IPv6", "allow"); allow_ipv4_ = true; allow_ipv6_ = true; // fix some common mis-spellings if (ipv6_str == "disabled" || ipv6_str == "disable") allow_ipv6_ = false; else if (ipv6_str == "allowed" || ipv6_str == "allow") allow_ipv6_ = true; else if (ipv6_str == "forced" || ipv6_str == "force") allow_ipv4_ = false; else throw std::runtime_error("Unsupported setting for the IPv6 parameter."); // read the [multicast] parameters resolve_scope_ = pt.get("multicast.ResolveScope", "site"); listen_address_ = pt.get("multicast.ListenAddress", ""); // Note about multicast addresses: IPv6 multicast addresses should be // FF0x::1 (see RFC2373, RFC1884) or a predefined multicast group std::string ipv6_multicast_group = pt.get("multicast.IPv6MulticastGroup", "113D:6FDD:2C17:A643:FFE2:1BD1:3CD2"); std::vector machine_group = parse_set(pt.get("multicast.MachineAddresses", "{127.0.0.1}")); // 224.0.0.1 is the group for all directly connected hosts (RFC1112) std::vector link_group = parse_set( pt.get("multicast.LinkAddresses", "{255.255.255.255, 224.0.0.1, 224.0.0.183}")); // Multicast groups defined by the organization (and therefore subject // to filtering / forwarding are in the 239.192.0.0/14 subnet (RFC2365) std::vector site_group = parse_set(pt.get("multicast.SiteAddresses", "{239.255.172.215}")); // Organization groups use the same broadcast addresses (IPv4), but // have a larger TTL. On the network site, it requires the routers // to forward the broadcast packets (both IGMP and UDP) std::vector organization_group = parse_set(pt.get("multicast.OrganizationAddresses", "{}")); std::vector global_group = parse_set(pt.get("multicast.GlobalAddresses", "{}")); enum { machine = 0, link, site, organization, global } scope; // construct list of addresses & TTL according to the ResolveScope. if (resolve_scope_ == "machine") scope = machine; else if (resolve_scope_ == "link") scope = link; else if (resolve_scope_ == "site") scope = site; else if (resolve_scope_ == "organization") scope = organization; else if (resolve_scope_ == "global") scope = global; else throw std::runtime_error("This ResolveScope setting is unsupported."); std::vector mcasttmp; mcasttmp.insert(mcasttmp.end(), machine_group.begin(), machine_group.end()); multicast_ttl_ = 0; if (scope >= link) { mcasttmp.insert(mcasttmp.end(), link_group.begin(), link_group.end()); mcasttmp.push_back("FF02:" + ipv6_multicast_group); multicast_ttl_ = 1; } if (scope >= site) { mcasttmp.insert(mcasttmp.end(), site_group.begin(), site_group.end()); mcasttmp.push_back("FF05:" + ipv6_multicast_group); multicast_ttl_ = 24; } if (scope >= organization) { mcasttmp.insert(mcasttmp.end(), organization_group.begin(), organization_group.end()); mcasttmp.push_back("FF08:" + ipv6_multicast_group); multicast_ttl_ = 32; } if (scope >= global) { mcasttmp.insert(mcasttmp.end(), global_group.begin(), global_group.end()); mcasttmp.push_back("FF0E:" + ipv6_multicast_group); multicast_ttl_ = 255; } // apply overrides, if any int ttl_override = pt.get("multicast.TTLOverride", -1); std::vector address_override = parse_set(pt.get("multicast.AddressesOverride", "{}")); if (ttl_override >= 0) multicast_ttl_ = ttl_override; if (!address_override.empty()) mcasttmp = address_override; // Parse, validate and store multicast addresses for (auto &it : mcasttmp) { ip::address addr = ip::make_address(it); if ((addr.is_v4() && allow_ipv4_) || (addr.is_v6() && allow_ipv6_)) multicast_addresses_.push_back(addr); } // The network stack requires the source interfaces for multicast packets to be // specified as IPv4 address or an IPv6 interface index // Try getting the interfaces from the configuration files using namespace asio::ip; std::vector netifs = parse_set(pt.get("multicast.Interfaces", "{}")); for (const auto &netifstr : netifs) { netif if_; if_.name = std::string("Configured in lslapi.cfg"); if_.addr = make_address(netifstr); if (if_.addr.is_v6()) if_.ifindex = if_.addr.to_v6().scope_id(); multicast_interfaces.push_back(if_); } // Try getting the interfaces from the OS if (multicast_interfaces.empty()) multicast_interfaces = get_local_interfaces(); // Otherwise, let the OS select an appropriate network interface if (multicast_interfaces.empty()) { LOG_F(ERROR, "No local network interface addresses found, resolving streams will likely " "only work for devices connected to the main network adapter\n"); // Add dummy interface with default settings netif dummy; dummy.name = "Dummy interface"; dummy.addr = address_v4::any(); multicast_interfaces.push_back(dummy); dummy.name = "IPv6 dummy interface"; dummy.addr = address_v6::any(); multicast_interfaces.push_back(dummy); } // read the [lab] settings known_peers_ = parse_set(pt.get("lab.KnownPeers", "{}")); session_id_ = pt.get("lab.SessionID", "default"); // read the [tuning] settings use_protocol_version_ = std::min( LSL_PROTOCOL_VERSION, pt.get("tuning.UseProtocolVersion", LSL_PROTOCOL_VERSION)); watchdog_check_interval_ = pt.get("tuning.WatchdogCheckInterval", 15.0); watchdog_time_threshold_ = pt.get("tuning.WatchdogTimeThreshold", 15.0); multicast_min_rtt_ = pt.get("tuning.MulticastMinRTT", 0.5); multicast_max_rtt_ = pt.get("tuning.MulticastMaxRTT", 3.0); unicast_min_rtt_ = pt.get("tuning.UnicastMinRTT", 0.75); unicast_max_rtt_ = pt.get("tuning.UnicastMaxRTT", 5.0); continuous_resolve_interval_ = pt.get("tuning.ContinuousResolveInterval", 0.5); timer_resolution_ = pt.get("tuning.TimerResolution", 1); max_cached_queries_ = pt.get("tuning.MaxCachedQueries", 100); time_update_interval_ = pt.get("tuning.TimeUpdateInterval", 2.0); time_update_minprobes_ = pt.get("tuning.TimeUpdateMinProbes", 6); time_probe_count_ = pt.get("tuning.TimeProbeCount", 8); time_probe_interval_ = pt.get("tuning.TimeProbeInterval", 0.064); time_probe_max_rtt_ = pt.get("tuning.TimeProbeMaxRTT", 0.128); outlet_buffer_reserve_ms_ = pt.get("tuning.OutletBufferReserveMs", 5000); outlet_buffer_reserve_samples_ = pt.get("tuning.OutletBufferReserveSamples", 128); socket_send_buffer_size_ = pt.get("tuning.SendSocketBufferSize", 0); inlet_buffer_reserve_ms_ = pt.get("tuning.InletBufferReserveMs", 5000); inlet_buffer_reserve_samples_ = pt.get("tuning.InletBufferReserveSamples", 128); socket_receive_buffer_size_ = pt.get("tuning.ReceiveSocketBufferSize", 0); smoothing_halftime_ = pt.get("tuning.SmoothingHalftime", 90.0F); force_default_timestamps_ = pt.get("tuning.ForceDefaultTimestamps", false); } static std::once_flag api_config_once_flag; const api_config *api_config::get_instance() { std::call_once(api_config_once_flag, []() { api_config::get_instance_internal(); }); return get_instance_internal(); } api_config *api_config::get_instance_internal() { static api_config cfg; return &cfg; } liblsl-1.17.7/src/api_config.h000066400000000000000000000320301517625163100161330ustar00rootroot00000000000000#ifndef API_CONFIG_H #define API_CONFIG_H #include "netinterfaces.h" #include "util/inireader.hpp" #include #include #include #include namespace ip = asio::ip; namespace lsl { /** * A configuration object: holds all the configurable settings of liblsl. * Settings are resolved in the following order of precedence (first match wins): * 1. Content set via `lsl_set_config_content()` (used directly, no file lookup) * 2. File named by the `LSLAPICFG` environment variable * 3. File set via `lsl_set_config_filename()` * 4. `lsl_api.cfg` in the current working directory * 5. `lsl_api.cfg` in the user's home directory (e.g. `~/lsl_api/lsl_api.cfg`) * 6. `lsl_api.cfg` in the system configuration directory (e.g. `/etc/lsl_api/lsl_api.cfg`) * 7. Built-in defaults * * Both `lsl_set_config_content()` and `lsl_set_config_filename()` must be called * before any other LSL function, since the configuration is loaded lazily on * first use and not re-read afterwards. * * Note that, while in some cases it might seem sufficient to override configurations * only for a subset of machines involved in a recording session (e.g., the servers), * it is recommended that the same settings are used by all machines (stream recipients * and providers) to avoid subtle bugs. */ class api_config { public: /// Get a pointer to this singleton. static const api_config *get_instance(); // === core parameters === /** * Lowest port used to provide data streams & service information. * * Up to port_range successively higher port numbers may be utilized, * depending on how many streams are being served on one machine. * If an outlet shall be reachable from outside a firewall, all TCP/UDP * ports starting from `base_port` up to `base_port+port_range-1`, as well as * the multicast_port should be open. If an inlet is behind a firewall, * the UDP ports starting from base_port up to base_port+port_range-1 should * be opened in order to allow for return packets in response to stream * discovery queries. */ uint16_t base_port() const { return base_port_; } /** Number of ports available on a machine for serving streams. * * This is the number of ports, starting from the base_port that can be allocated for * serving streams. This limits the number of outlets that can coexist on a single machine * to port_range; by increasing this number this limit can be expanded. */ uint16_t port_range() const { return port_range_; } /** * Whether to allow binding to a randomly assigned port. * * This can be used when the regular port range has been exhausted. */ int allow_random_ports() const { return allow_random_ports_; } /** * Port over which multi-cast communication is handled. * This is the communication medium for the announcement and discovery of streams * between inlets and outlets. Note that according to the router configuration some * multicast address ranges or ports may be blocked. */ uint16_t multicast_port() const { return multicast_port_; } /** * @brief How the IPv4 / IPv6 protocols should be handled. * * The option "ports.IPv6" can be "disable" (use only IPv4), "force" (use only IPv6), * or "allow" (use both protocol stacks). */ bool allow_ipv6() const { return allow_ipv6_; } bool allow_ipv4() const { return allow_ipv4_; } /** * @brief Set the configuration directly from an INI-formatted string. * * Allows passing in configuration content rather than reading from a file. * MUST be called before the first call to get_instance() to have any effect. */ static void set_api_config_content(const std::string &content) { api_config_content_ = content; } /** * @brief An additional settings path to load configuration from. */ const std::string &api_config_filename() const { return api_config_filename_; } /** * @brief Set the config file path used to load the settings. * * MUST be called before the first call to get_instance() to have any effect. */ static void set_api_config_filename(const std::string &filename) { api_config_filename_ = filename; } /** * @brief The range or scope of stream lookup when using multicast-based discovery * * determines the output of the member functions multicast_addresses() and multicast_ttl(). * Can take the values "machine", "link", "site", "organization", or "global". */ const std::string &resolve_scope() const { return resolve_scope_; } /** * List of multicast addresses on which inlets / outlets advertise/discover streams. * * This is merged from several other config file entries * (LocalAddresses,SiteAddresses,OrganizationAddresses, GlobalAddresses) * goverened according to the ResolveScope setting. * Each participant in the network is aware of all addresses in this list, and will try all * of them if necessary. * For smooth operation this list should ideally include both IPv4 and IPv6 addresses to * work on networks on which one of the two is disabled. * Specifically, the list should contain both the broadcast address * 255.255.255.255 and link-local multicast addresses. * To communicate across routers within a site (depending on local policy, e.g., the * department) or organization (e.g., the campus), or at larger scope, multicast addresses * with the according scope need to be included. */ const std::vector &multicast_addresses() const { return multicast_addresses_; } /** * @brief The address of the local interface on which to listen to multicast traffic. * * The default is an empty string, i.e. bind to the default interface(s). */ const std::string &listen_address() const { return listen_address_; } /** * A list of local interface addresses the multicast packets should be * sent from. * * The ini file may contain IPv4 addresses and/or IPv6 addresses with the * interface index as scope id, e.g. `1234:5678::2%3` **/ std::vector multicast_interfaces; /** * The TTL setting (time-to-live) for the multicast packets. * This is determined according to the ResolveScope setting if not overridden by the TTLOverride * setting. The higher this number (0-255), the broader their distribution. Routers (if * correctly configured) employ various thresholds below which packets are not further * forwarded. These are: 0: Restricted to the same host -- not forwarded by a network card. 1: * Restricted to the same subnet -- not forwarded by a router. 32: Restricted to the same site, * organization or department. 64: Restricted to the same region (definition of region varies). * 128: Restricted to the same continent. * 255: Not restricted in scope (global). */ int multicast_ttl() const { return multicast_ttl_; } /** * @brief The configured session ID. * Allows to keep recording operations isolated from each other (precluding unwanted * interference). */ const std::string &session_id() const { return session_id_; } /** * @brief List of known host names that may provide LSL streams. * Can serve as a fallback if multicast/broadcast communication fails on a given network. */ const std::vector &known_peers() const { return known_peers_; } // === tuning parameters === /// The network protocol version to use. int use_protocol_version() const { return use_protocol_version_; } /// The interval at which the watchdog checks if connections are still fine. double watchdog_check_interval() const { return watchdog_check_interval_; } /// The watchdog takes no action if not at least this much time has passed since the last /// receipt of data. In seconds. double watchdog_time_threshold() const { return watchdog_time_threshold_; } /// The minimum assumed round-trip-time for a multicast query. Any subsequent packet wave would /// be started no earlier than this. double multicast_min_rtt() const { return multicast_min_rtt_; } /// The maximum assumed round-trip-time for a multicast query. We will stop waiting for return /// packets for a wave after this time. double multicast_max_rtt() const { return multicast_max_rtt_; } /// The minimum assumed round-trip-time for a multi-peer/multi-port unicast query. Any /// subsequent packet wave would be started no earlier than this. double unicast_min_rtt() const { return unicast_min_rtt_; } /// The maximum assumed round-trip-time for a multi-peer/multi-port unicast query. double unicast_max_rtt() const { return unicast_max_rtt_; } /// The interval at which resolve queries are emitted for continuous/background resolve /// activities. This is in addition to the assumed RTT's. double continuous_resolve_interval() const { return continuous_resolve_interval_; } /// Desired timer resolution in ms (0 means no change). Currently only affects Windows operating /// systems, where values other than 1 can increase LSL transmission latency. int timer_resolution() const { return timer_resolution_; } /// The maximum number of most-recently-used queries that is cached. int max_cached_queries() const { return max_cached_queries_; } /// Interval between background time correction updates. double time_update_interval() const { return time_update_interval_; } /// Minimum number of probes that must have been successful to perform a time update. int time_update_minprobes() const { return time_update_minprobes_; } /// Number of time probes that are being sent for a single update. int time_probe_count() const { return time_probe_count_; } /// Interval between the individual time probes that are sent to calculate an update. double time_probe_interval() const { return time_probe_interval_; } /// Maximum assumed RTT of a time probe (= extra waiting time). double time_probe_max_rtt() const { return time_probe_max_rtt_; } /// Default pre-allocated buffer size for the outlet, in ms (regular streams). int outlet_buffer_reserve_ms() const { return outlet_buffer_reserve_ms_; } /// Default pre-allocated buffer size for the outlet, in samples (irregular streams). int outlet_buffer_reserve_samples() const { return outlet_buffer_reserve_samples_; } /// Default socket send buffer size, in bytes. int socket_send_buffer_size() const { return socket_send_buffer_size_; } /// Default pre-allocated buffer size for the inlet, in ms (regular streams). int inlet_buffer_reserve_ms() const { return inlet_buffer_reserve_ms_; } /// Default pre-allocated buffer size for the inlet, in samples (irregular streams). int inlet_buffer_reserve_samples() const { return inlet_buffer_reserve_samples_; } /// Default socket receive buffer size, in bytes. int socket_receive_buffer_size() const { return socket_receive_buffer_size_; } /// Default halftime of the time-stamp smoothing window (if enabled), in seconds. float smoothing_halftime() const { return smoothing_halftime_; } /// Override timestamps with lsl clock if True bool force_default_timestamps() const { return force_default_timestamps_; } /// Deleted copy constructor (noncopyable). api_config(const api_config &rhs) = delete; /// Deleted assignment operator (noncopyable). api_config &operator=(const api_config &rhs) = delete; private: /// Get the api_config singleton after thread-safe initialization if needed static api_config *get_instance_internal(); /** * Constructor. * Applies default settings and overrides them based on a config file (if present). */ api_config(); /** * @brief Load a configuration file (or use defaults if a filename is empty). * @param filename Platform-native config file name */ void load_from_file(const std::string &filename = std::string()); /** * @brief Load a configuration from a string. * @param content The configuration content to parse */ void load_from_content(const std::string &content); /** * @brief Load the configuration from an INI object. * @param pt The INI object to load the configuration from */ void load(INI &pt); // config overrides static std::string api_config_filename_; static std::string api_config_content_; // core parameters bool allow_ipv6_, allow_ipv4_; uint16_t base_port_; uint16_t port_range_; bool allow_random_ports_; uint16_t multicast_port_; std::string resolve_scope_; std::vector multicast_addresses_; int multicast_ttl_; std::string listen_address_; std::vector known_peers_; std::string session_id_; // tuning parameters int use_protocol_version_; double watchdog_time_threshold_; double watchdog_check_interval_; double multicast_min_rtt_; double multicast_max_rtt_; double unicast_min_rtt_; double unicast_max_rtt_; double continuous_resolve_interval_; int timer_resolution_; int max_cached_queries_; double time_update_interval_; int time_update_minprobes_; int time_probe_count_; double time_probe_interval_; double time_probe_max_rtt_; int outlet_buffer_reserve_ms_; int outlet_buffer_reserve_samples_; int socket_send_buffer_size_; int inlet_buffer_reserve_ms_; int inlet_buffer_reserve_samples_; int socket_receive_buffer_size_; float smoothing_halftime_; bool force_default_timestamps_; }; // initialize configuration file name inline std::string api_config::api_config_filename_ = ""; inline std::string api_config::api_config_content_ = ""; } // namespace lsl #endif liblsl-1.17.7/src/api_types.hpp000066400000000000000000000013611517625163100163750ustar00rootroot00000000000000#pragma once /** @file api_types.hpp API type forwards * * The C API uses pointers to opaque structs to hide implementation details. * * This header defines the actual typedefs so the C++ implementation doesn't * have to do error-prone type casts between internal and API types. */ #define LSL_TYPES namespace lsl { class continuous_resolver_impl; class resolver_impl; class stream_info_impl; class stream_inlet_impl; class stream_outlet_impl; } // namespace lsl namespace pugi { struct xml_node_struct; } using lsl_continuous_resolver = lsl::resolver_impl *; using lsl_streaminfo = lsl::stream_info_impl *; using lsl_outlet = lsl::stream_outlet_impl *; using lsl_inlet = lsl::stream_inlet_impl *; using lsl_xml_ptr = pugi::xml_node_struct *; liblsl-1.17.7/src/buildinfo.cpp000066400000000000000000000003331517625163100163440ustar00rootroot00000000000000extern "C" { #include "../include/lsl/common.h" LIBLSL_C_API const char *lsl_library_info(void) { #ifdef LSL_LIBRARY_INFO_STR return LSL_LIBRARY_INFO_STR; #else return "Unknown (not set by build system)"; #endif } } liblsl-1.17.7/src/cancellable_streambuf.h000066400000000000000000000145151517625163100203420ustar00rootroot00000000000000#pragma once // // cancellable_streambuf.hpp // ~~~~~~~~~~~~~~~~~~~~~~~~~~ // // This is a carbon copy of basic_socket_streambuf.hpp, adding a cancel() member function // (an removing support for unbuffered I/O). // // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) // Modified by Christian A. Kothe, 2012 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #define BOOST_ASIO_NO_DEPRECATED #include "cancellation.h" #include #include #include #include #include using asio::io_context; namespace lsl { using Protocol = asio::ip::tcp; using Socket = asio::basic_stream_socket; /// Iostream streambuf for a socket. class cancellable_streambuf final : public std::streambuf, private asio::io_context, private Socket, public lsl::cancellable_obj { public: /// Construct a cancellable_streambuf without establishing a connection. cancellable_streambuf() : io_context(1), Socket(as_context()) { init_buffers(); } /// Destructor flushes buffered data. ~cancellable_streambuf() override { // no cancel() can fire after this call unregister_from_all(); if (pptr() != pbase()) overflow(traits_type::eof()); } /** * Cancel the current stream operations destructively. * All blocking operations will fail after a cancel() has been issued, * and the stream buffer cannot be reused. */ void cancel() override { cancel_issued_ = true; std::lock_guard lock(cancel_mut_); cancel_started_ = false; asio::post(this->as_context(), [this]() { close_if_open(); }); } /// Establish a connection. /** * This function establishes a connection to the specified endpoint. * * @return \c this if a connection was successfully established, a null * pointer otherwise. */ cancellable_streambuf *connect(const Protocol::endpoint &endpoint) { { std::lock_guard lock(cancel_mut_); if (cancel_issued_) throw std::runtime_error( "Attempt to connect() a cancellable_streambuf after it has been cancelled."); init_buffers(); socket().close(ec_); socket().async_connect( endpoint, [this](const asio::error_code &ec) { this->ec_ = ec; }); this->as_context().restart(); } ec_ = asio::error::would_block; do as_context().run_one(); while (!cancel_issued_ && ec_ == asio::error::would_block); return !ec_ ? this : nullptr; } /// Close the connection. /** * @return \c this if a connection was successfully established, a null * pointer otherwise. */ cancellable_streambuf *close() { sync(); socket().close(ec_); if (!ec_) init_buffers(); return !ec_ ? this : nullptr; } /** Get the last error associated with the stream buffer. * @return An \c error_code corresponding to the last error from the stream * buffer. */ const asio::error_code &error() const { return ec_; } protected: /// Close the socket if it's open. void close_if_open() { if (!cancel_started_ && socket().is_open()) { cancel_started_ = true; socket().close(); } } /// Convenience method to call methods inherited from `Socket` Socket &socket() { return *this; } /// Convenience method to call methods inherited from `io_context` asio::io_context &as_context() { return *this; } /// Make sure that a cancellation, if issued, is not being eaten by `io_context::reset()` void protected_reset() { std::lock_guard lock(cancel_mut_); // if the cancel() comes between completion of a run_one() and this call, close will be // issued right here at the next opportunity if (cancel_issued_) close_if_open(); this->as_context().restart(); // if the cancel() comes between this call and a completion of run_one(), the posted close // will be processed by the run_one } int_type underflow() override { if (gptr() == egptr()) { std::size_t bytes_transferred_; socket().async_receive(asio::buffer(asio::buffer(get_buffer_) + putback_max), [this, &bytes_transferred_]( const asio::error_code &ec, std::size_t bytes_transferred = 0) { this->ec_ = ec; bytes_transferred_ = bytes_transferred; }); ec_ = asio::error::would_block; protected_reset(); // line changed for lsl do as_context().run_one(); while (!cancel_issued_ && ec_ == asio::error::would_block); if (ec_) return traits_type::eof(); setg(&get_buffer_[0], &get_buffer_[0] + putback_max, &get_buffer_[0] + putback_max + bytes_transferred_); return traits_type::to_int_type(*gptr()); } return traits_type::eof(); } int_type overflow(int_type c) override { // Send all data in the output buffer. asio::const_buffer buffer = asio::buffer(pbase(), pptr() - pbase()); while (asio::buffer_size(buffer) > 0) { std::size_t bytes_transferred_; socket().async_send( asio::buffer(buffer), [this, &bytes_transferred_](const asio::error_code &ec, std::size_t bytes_transferred) { this->ec_ = ec; bytes_transferred_ = bytes_transferred; }); ec_ = asio::error::would_block; protected_reset(); // line changed for lsl do as_context().run_one(); while (!cancel_issued_ && ec_ == asio::error::would_block); if (ec_) return traits_type::eof(); buffer = buffer + bytes_transferred_; } setp(&put_buffer_[0], &put_buffer_[0] + sizeof(put_buffer_)); // If the new character is eof then our work here is done. if (traits_type::eq_int_type(c, traits_type::eof())) return traits_type::not_eof(c); // Add the new character to the output buffer. *pptr() = traits_type::to_char_type(c); pbump(1); return c; } int sync() override { return overflow(traits_type::eof()); } std::streambuf *setbuf(char_type * /*unused*/, std::streamsize /*unused*/) override { // this feature was stripped out... return nullptr; } void init_buffers() { setg(&get_buffer_[0], &get_buffer_[0] + putback_max, &get_buffer_[0] + putback_max); setp(&put_buffer_[0], &put_buffer_[0] + sizeof(put_buffer_)); } enum { putback_max = 8 }; enum { buffer_size = 16384 }; char get_buffer_[buffer_size], put_buffer_[buffer_size]; asio::error_code ec_; std::atomic cancel_issued_{false}; bool cancel_started_{false}; std::recursive_mutex cancel_mut_; }; } // namespace lsl liblsl-1.17.7/src/cancellation.cpp000066400000000000000000000007741517625163100170360ustar00rootroot00000000000000#include "cancellation.h" #include #include lsl::cancellable_registry::~cancellable_registry() = default; lsl::cancellable_obj::~cancellable_obj() { unregister_from_all(); } void lsl::cancellable_obj::unregister_from_all() { try { for (auto *obj : registered_at_) obj->unregister_cancellable(this); registered_at_.clear(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error trying to unregister a cancellable object from its registry: %s", e.what()); } } liblsl-1.17.7/src/cancellation.h000066400000000000000000000055021517625163100164750ustar00rootroot00000000000000#ifndef CANCELLATION_H #define CANCELLATION_H #include #include #include #include namespace lsl { class cancellable_object; /// Exception class that indicates that an operation was performed on an registry that is being shut /// down. class shutdown_error : public std::runtime_error { public: explicit shutdown_error(const std::string &msg) : std::runtime_error(msg) {} explicit shutdown_error(const char *msg) : std::runtime_error(msg) {} }; /// A registry for objects that can be cancelled. class cancellable_registry { public: virtual ~cancellable_registry(); /// Invoke cancel() on all currently registered objects. void cancel_all_registered(); /// Invoke a cancel on all currently registered objects and prevent future object registration. void cancel_and_shutdown(); private: friend class cancellable_obj; /// Register a cancellable object. void register_cancellable(class cancellable_obj *o) { std::lock_guard lock(state_mut_); if (shutdown_issued_) throw shutdown_error( "The registry has begun to shut down; no new registrations possible."); cancellables_.insert(o); } /// Unregister a cancellable object. void unregister_cancellable(class cancellable_obj *o) { std::lock_guard lock(state_mut_); cancellables_.erase(o); } bool shutdown_issued_{false}; // whether a shutdown has been issued std::set cancellables_; // a set of objects that we have to cancel upon re-resolves & disengage std::recursive_mutex state_mut_; // mutex to protect the registry's state }; /// A cancellable object. class cancellable_obj { public: /// Cancel method. virtual void cancel() = 0; /// Unregister at destruction. virtual ~cancellable_obj(); /// Register at some registry. /// IMPORTANT: The registry must outlive the cancellable. void register_at(cancellable_registry *reg) { reg->register_cancellable(this); registered_at_.insert(reg); } /// IMPORTANT: If your registered cancel() operation may require some resources of your /// derived class(es), you must call unregister_from_all() *before* you destroy any of these /// resources... void unregister_from_all(); private: // set of registries where we are registered std::set registered_at_; }; /// Cancel all registered objects. inline void cancellable_registry::cancel_all_registered() { std::lock_guard lock(state_mut_); std::set copy(cancellables_); for (auto *obj : copy) if (cancellables_.find(obj) != cancellables_.end()) obj->cancel(); } /// Cancel and prevent future object registrations. inline void cancellable_registry::cancel_and_shutdown() { std::lock_guard lock(state_mut_); shutdown_issued_ = true; cancel_all_registered(); } } // namespace lsl #endif liblsl-1.17.7/src/common.cpp000066400000000000000000000052501517625163100156640ustar00rootroot00000000000000#include "common.h" #include "api_config.h" #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #if defined(_MSC_VER) && _MSC_VER < 1900 #error "MSVC 2013's std::chrono isn't supported. Please upgrade to VS2015 or later" #endif #include // include mmsystem.h after windows.h #include #endif int64_t lsl::lsl_local_clock_ns() { return std::chrono::nanoseconds(std::chrono::steady_clock::now().time_since_epoch()).count(); } extern "C" { LIBLSL_C_API int32_t lsl_protocol_version() { return lsl::api_config::get_instance()->use_protocol_version(); } LIBLSL_C_API void lsl_set_config_filename(const char *filename) { if (filename) { lsl::api_config::set_api_config_filename(filename); } } LIBLSL_C_API void lsl_set_config_content(const char *content) { if (content) { lsl::api_config::set_api_config_content(content); } } LIBLSL_C_API int32_t lsl_library_version() { return LSL_LIBRARY_VERSION; } LIBLSL_C_API double lsl_local_clock() { const auto ns_per_s = 1000000000; const auto seconds_since_epoch = std::lldiv(lsl::lsl_local_clock_ns(), ns_per_s); /* For large timestamps, converting to double and then dividing by 1e9 loses precision because double has only 53 bits of precision. So we calculate everything we can as integer and only cast to double at the end */ return static_cast(seconds_since_epoch.quot) + static_cast(seconds_since_epoch.rem) / ns_per_s; } LIBLSL_C_API void lsl_destroy_string(char *s) { if (s) free(s); } LIBLSL_C_API const char *lsl_last_error(void) { thread_local char last_error[LAST_ERROR_SIZE] = {0}; return last_error; } } // === implementation of misc functions === void lsl::ensure_lsl_initialized() { static bool is_initialized = false; if (!is_initialized) { is_initialized = true; #if LOGURU_DEBUG_LOGGING // Initialize loguru, mainly to print stacktraces on segmentation faults int argc = 1; const char *argv[] = {"liblsl", nullptr}; loguru::init(argc, const_cast(argv)); #else #endif LOG_F(INFO, "%s", lsl_library_info()); #ifdef _WIN32 // if a timer resolution other than 0 is requested (0 means don't override)... if (int desired_timer_resolution = lsl::api_config::get_instance()->timer_resolution()) { // then override it for the lifetime of this program struct override_timer_resolution_until_exit { override_timer_resolution_until_exit(int res) : res_(res) { timeBeginPeriod(res_); } ~override_timer_resolution_until_exit() { timeEndPeriod(res_); } int res_; }; static override_timer_resolution_until_exit overrider(desired_timer_resolution); } #endif } } liblsl-1.17.7/src/common.h000066400000000000000000000050471517625163100153350ustar00rootroot00000000000000#ifndef COMMON_H #define COMMON_H extern "C" { #include "api_types.hpp" // api_types.h defines LSL_TYPES so it needs to be included before the next header #include "../include/lsl/common.h" } #include #include #include #include #ifdef _WIN32 #define LIBLSL_CPP_API __declspec(dllexport) #else #define LIBLSL_CPP_API #endif #ifdef _MSC_VER #pragma warning(disable : 4275) #endif #ifndef BOOST_CSTDINT_HPP #include #endif #if BOOST_VERSION < 104500 #error \ "Please do not compile this with a lslboost version older than 1.45 because the library would otherwise not be protocol-compatible with builds using other versions." #endif // compiler hint that the given expression is likely or unlikely // (e.g., in conditional statements) #if defined(__clang__) || defined(__GNUC__) #define LIKELY(x) __builtin_expect(!!(x), 1) #define UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define LIKELY(x) (x) #define UNLIKELY(x) (x) #endif // the highest supported protocol version // * 100 is the original version, supported by library versions 1.00+ // * 110 is an alternative protocol that improves throughput, supported by library versions 1.10+ const int LSL_PROTOCOL_VERSION = 110; // the library version const int LSL_LIBRARY_VERSION = 117; /// size of the lsl_last_error() buffer size const int LAST_ERROR_SIZE = 512; namespace lsl { /// A very large time duration (> 1 year) for timeout values. const double FOREVER = LSL_FOREVER; /// Constant to indicate that a sample has the next successive time stamp. const double DEDUCED_TIMESTAMP = LSL_DEDUCED_TIMESTAMP; /// Constant to indicate that a stream has variable sampling rate. const double IRREGULAR_RATE = LSL_IRREGULAR_RATE; /// Obtain a local system time stamp in nanoseconds. int64_t lsl_local_clock_ns(); /// Obtain a local system time stamp in seconds. inline double lsl_clock() { return lsl_local_clock(); } /// Ensure that LSL is initialized. void ensure_lsl_initialized(); /// Exception class that indicates that a stream inlet's source has been irrecoverably lost. class LIBLSL_CPP_API lost_error : public std::runtime_error { public: explicit lost_error(const std::string &msg) : std::runtime_error(msg) {} }; /// Exception class that indicates that an operation failed due to a timeout. class LIBLSL_CPP_API timeout_error : public std::runtime_error { public: explicit timeout_error(const std::string &msg) : std::runtime_error(msg) {} }; } // namespace lsl #endif liblsl-1.17.7/src/consumer_queue.cpp000066400000000000000000000027241517625163100174360ustar00rootroot00000000000000#include "consumer_queue.h" #include "common.h" #include "send_buffer.h" #include #include #include using namespace lsl; consumer_queue::consumer_queue(std::size_t size, send_buffer_p registry) : buffer_(new item_t[size]), size_(size), // largest integer at which we can wrap correctly wrap_at_(std::numeric_limits::max() - size - std::numeric_limits::max() % size), registry_(std::move(registry)) { assert(size_ > 1); for (std::size_t i = 0; i < size_; ++i) buffer_[i].seq_state.store(i, std::memory_order_release); if (registry_) registry_->register_consumer(this); } consumer_queue::~consumer_queue() { try { if (registry_) registry_->unregister_consumer(this); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error while trying to unregister a consumer queue from its registry: %s", e.what()); } delete[] buffer_; } uint32_t consumer_queue::flush() noexcept { uint32_t n = 0; while (try_pop()) n++; return n; } std::size_t consumer_queue::read_available() const { std::size_t write_index = write_idx_.load(std::memory_order_acquire); std::size_t read_index = read_idx_.load(std::memory_order_relaxed); if (write_index >= read_index) return write_index - read_index; const std::size_t ret = write_index + size_ - read_index; return ret; } bool consumer_queue::empty() const { return write_idx_.load(std::memory_order_acquire) == read_idx_.load(std::memory_order_relaxed); } liblsl-1.17.7/src/consumer_queue.h000066400000000000000000000162741517625163100171100ustar00rootroot00000000000000#ifndef CONSUMER_QUEUE_H #define CONSUMER_QUEUE_H #include "common.h" #include "sample.h" #include #include #include #include namespace lsl { // size of a cache line #if defined(__s390__) || defined(__s390x__) constexpr int CACHELINE_BYTES = 256; #elif defined(powerpc) || defined(__powerpc__) || defined(__ppc__) constexpr int CACHELINE_BYTES = 128; #else constexpr int CACHELINE_BYTES = 64; #endif constexpr int _min(int a, int b) { return a > b ? b : a; } /// calculate the needed padding to separate two members E1 and E2 by at least CACHELINE_BYTES template constexpr std::size_t padding() { return CACHELINE_BYTES - _min(sizeof(std::tuple) - sizeof(E1) - sizeof(E2), CACHELINE_BYTES - 1); } template struct Padding { const char pad[padding()]{0}; }; /** * A thread-safe producer/consumer queue of unread samples. * * Erases the oldest samples if max capacity is exceeded. Implemented as a ring buffer (wait-free * unless the buffer is full or empty). */ class consumer_queue { public: /** * Create a new queue with a given capacity. * @param size The maximum number of samples that can be held by the queue. Beyond that, * the oldest samples are dropped. * @param registry Optionally a pointer to a registration facility, for multiple-reader * arrangements. */ explicit consumer_queue(std::size_t size, send_buffer_p registry = send_buffer_p()); /// Destructor. Unregisters from the send buffer, if any. ~consumer_queue(); /** * Push a new sample onto the queue. Can only be called by one thread (single-producer). * This deletes the oldest sample if the max capacity is exceeded. */ template void push_sample(T &&sample) { while (!try_push(std::forward(sample))) { // buffer full, drop oldest sample if (!done_sync_.load(std::memory_order_acquire)) { // synchronizes-with store to done_sync_ in ctor std::atomic_thread_fence(std::memory_order_acquire); done_sync_.store(true, std::memory_order_release); } try_pop(); } { // ensure that notify_one doesn't happen in between try_pop and wait_for std::lock_guard lk(mut_); cv_.notify_one(); } } /** * Pop a sample from the queue. Can be called by multiple threads (multi-consumer). * Blocks if empty and if a nonzero timeout is used. * @param timeout Timeout for the blocking, in seconds. If expired, an empty sample is returned. */ sample_p pop_sample(double timeout = FOREVER) { sample_p result; bool success = try_pop(result); if (!success && timeout > 0.0) { // only acquire mutex if we have to do a blocking wait with timeout std::chrono::duration sec(timeout); std::unique_lock lk(mut_); if (!try_pop(result)) cv_.wait_for(lk, sec, [&] { return this->try_pop(result); }); } return result; } /// Number of available samples. This is approximate unless called by the thread calling the /// pop_sample(). std::size_t read_available() const; /// Flush the queue, return the number of dropped samples. uint32_t flush() noexcept; /// Check whether the buffer is empty. This is approximate unless called by the thread calling /// the pop_sample(). bool empty() const; consumer_queue(const consumer_queue&) = delete; consumer_queue(consumer_queue &&) = delete; consumer_queue& operator=(const consumer_queue&) = delete; consumer_queue &operator=(consumer_queue &&) = delete; private: // an item stored in the queue struct item_t { std::atomic seq_state; sample_p value; }; // Push a new element to the queue. // Returns true if successful or false if queue full. template bool try_push(T &&sample) { std::size_t write_index = write_idx_.load(std::memory_order_acquire); std::size_t next_idx = add1_wrap(write_index); item_t &item = buffer_[write_index % size_]; if (UNLIKELY(write_index != item.seq_state.load(std::memory_order_acquire))) return false; // item currently occupied, queue full write_idx_.store(next_idx, std::memory_order_release); copy_or_move(item.value, std::forward(sample)); item.seq_state.store(next_idx, std::memory_order_release); return true; } // Pop an element from the queue (can be called with zero or one argument). Returns true if // successful or false if queue is empty. Uses the same method as Vyukov's bounded MPMC queue. template bool try_pop(T &...result) { item_t *item; std::size_t read_index = read_idx_.load(std::memory_order_relaxed); for (;;) { item = &buffer_[read_index % size_]; const std::size_t seq_state = item->seq_state.load(std::memory_order_acquire); const std::size_t next_idx = add1_wrap(read_index); // check if the item is ok to pop if (LIKELY(seq_state == next_idx)) { // yes, try to claim slot using CAS if (LIKELY(read_idx_.compare_exchange_weak( read_index, next_idx, std::memory_order_relaxed))) break; } else if (LIKELY(seq_state == read_index)) return false; // queue empty else // we're behind or ahead of another pop, try again read_index = read_idx_.load(std::memory_order_relaxed); } move_or_drop(item->value, result...); // mark item as free for next pass item->seq_state.store(add_wrap(read_index, size_), std::memory_order_release); return true; } // helper to either copy or move a value, depending on whether it's an rvalue ref inline static void copy_or_move(sample_p &dst, const sample_p &src) { dst = src; } inline static void copy_or_move(sample_p &dst, sample_p &&src) { dst = std::move(src); } // helper to either move or drop a value, depending on whether a dst argument is given inline static void move_or_drop(sample_p &src) { src.reset(); } inline static void move_or_drop(sample_p &src, sample_p &dst) { dst = std::move(src); } /// helper to add a delta to the given index and wrap correctly inline std::size_t add_wrap(std::size_t x, std::size_t delta) const noexcept { const std::size_t xp = x + delta; return xp >= wrap_at_ ? xp - wrap_at_ : xp; } /// helper to increment the given index, wrapping it if necessary inline std::size_t add1_wrap(std::size_t x) const noexcept { return ++x == wrap_at_ ? 0 : x; } /// current read position std::atomic read_idx_{0}; /// condition for waiting with timeout std::condition_variable cv_; /// the sample buffer item_t *buffer_; /// padding to ensure read_idx_ and write_idx_ don't share a cacheline Padding pad; /// current write position std::atomic write_idx_{0}; /// max number of elements in the queue const std::size_t size_; /// threshold at which to wrap read/write indices const std::size_t wrap_at_; /// for use with the condition variable std::mutex mut_; /// optional consumer registry send_buffer_p registry_; /// padding to ensure write_ix_ and done_sync_ don't share a cacheline #if UINTPTR_MAX <= 0xFFFFFFFF Padding pad2; #endif /// whether we have performed a sync on the data stored by the constructor std::atomic done_sync_{false}; }; } // namespace lsl #endif liblsl-1.17.7/src/data_receiver.cpp000066400000000000000000000347341517625163100172020ustar00rootroot00000000000000#include "data_receiver.h" #include "api_config.h" #include "cancellable_streambuf.h" #include "inlet_connection.h" #include "sample.h" #include "socket_utils.h" #include "util/cast.hpp" #include "util/endian.hpp" #include "util/strfuns.hpp" #include #include #include #include #include #include #include // a convention that applies when including portable_oarchive.h in multiple .cpp files. // otherwise, the templates are instantiated in this file and sample.cpp which leads // to errors like "multiple definition of `typeinfo name" #define NO_EXPLICIT_TEMPLATE_INSTANTIATION #include "portable_archive/portable_iarchive.hpp" namespace lsl { data_receiver::data_receiver(inlet_connection &conn, int max_buflen, int max_chunklen) : conn_(conn), sample_factory_( new factory(conn.type_info().channel_format(), conn.type_info().channel_count(), conn.type_info().nominal_srate() ? static_cast(conn.type_info().nominal_srate() * api_config::get_instance()->inlet_buffer_reserve_ms() / 1000) : api_config::get_instance()->inlet_buffer_reserve_samples())), check_thread_start_(true), closing_stream_(false), connected_(false), sample_queue_(max_buflen), max_buflen_(max_buflen), max_chunklen_(max_chunklen) { if (max_buflen < 0) throw std::invalid_argument("The max_buflen argument must not be smaller than 0."); if (max_chunklen < 0) throw std::invalid_argument("The max_chunklen argument must not be smaller than 0."); conn_.register_onlost(this, &connected_upd_); } data_receiver::~data_receiver() { try { conn_.unregister_onlost(this); if (data_thread_.joinable()) data_thread_.join(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error during destruction of a data_receiver: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during data receiver shutdown."); } } // === external access === void data_receiver::open_stream(double timeout) { closing_stream_ = false; std::unique_lock lock(connected_mut_); auto connection_completed = [this]() { return connected_ || conn_.lost(); }; if (!connection_completed()) { // start thread if not yet running if (check_thread_start_ && !data_thread_.joinable()) { data_thread_ = std::thread(&data_receiver::data_thread, this); check_thread_start_ = false; } // wait until the connection attempt completes (or we time out) if (timeout >= FOREVER) connected_upd_.wait(lock, connection_completed); else if (!connected_upd_.wait_for( lock, std::chrono::duration(timeout), connection_completed)) throw timeout_error("The open_stream() operation timed out."); } if (conn_.lost()) throw lost_error("The stream read by this inlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); } void data_receiver::close_stream() { check_thread_start_ = true; closing_stream_ = true; cancel_all_registered(); } sample_p lsl::data_receiver::try_get_next_sample(double timeout) { if (conn_.lost()) throw lost_error("The stream read by this outlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); // start data thread implicitly if necessary if (check_thread_start_ && !data_thread_.joinable()) { data_thread_ = std::thread(&data_receiver::data_thread, this); check_thread_start_ = false; } // get the sample with timeout if (sample_p s = sample_queue_.pop_sample(timeout)) return s; if (conn_.lost()) throw lost_error("The stream read by this inlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); return nullptr; } template double data_receiver::pull_sample_typed(T *buffer, uint32_t buffer_elements, double timeout) { if(sample_p s = try_get_next_sample(timeout)) { if (buffer_elements != conn_.type_info().channel_count()) throw std::range_error("The number of buffer elements provided does not match the " "number of channels in the sample."); s->retrieve_typed(buffer); return s->timestamp(); } return 0.0; } template double data_receiver::pull_sample_typed(char *, uint32_t, double); template double data_receiver::pull_sample_typed(int16_t *, uint32_t, double); template double data_receiver::pull_sample_typed(int32_t *, uint32_t, double); template double data_receiver::pull_sample_typed(int64_t *, uint32_t, double); template double data_receiver::pull_sample_typed(float *, uint32_t, double); template double data_receiver::pull_sample_typed(double *, uint32_t, double); template double data_receiver::pull_sample_typed(std::string *, uint32_t, double); double data_receiver::pull_sample_untyped(void *buffer, int buffer_bytes, double timeout) { if(sample_p s = try_get_next_sample(timeout)) { if (buffer_bytes != conn_.type_info().sample_bytes()) throw std::range_error("The size of the provided buffer does not match the number of " "bytes in the sample."); s->retrieve_untyped(buffer); return s->timestamp(); } return 0.0; } // === internal processing === void data_receiver::data_thread() { conn_.acquire_watchdog(); loguru::set_thread_name((std::string("R_") += conn_.type_info().name().substr(0, 12)).c_str()); // ensure that the sample factory persists for the lifetime of this thread factory_p factory(sample_factory_); try { while (!conn_.lost() && !conn_.shutdown() && !closing_stream_) { try { // --- connection setup --- // make a new stream buffer and a stream on top of it cancellable_streambuf buffer; buffer.register_at(&conn_); buffer.register_at(this); std::iostream server_stream(&buffer); std::unique_ptr inarch; // connect to endpoint buffer.connect(conn_.get_tcp_endpoint()); if (buffer.error()) throw buffer.error(); // --- protocol negotiation --- bool reverse_byte_order = false; // perform little <-> big endian conversion? int data_protocol_version = 100; // which protocol version we shall use for data // transmission (100=version 1.00) bool suppress_subnormals = false; // whether we shall suppress subnormal numbers // propose to use the highest protocol version supported by both parties int proposed_protocol_version = std::min(api_config::get_instance()->use_protocol_version(), conn_.type_info().version()); if (proposed_protocol_version >= 110) { // request line LSL:streamfeed/[ProtocolVersion] [UID]\r\n server_stream << "LSL:streamfeed/" << proposed_protocol_version << " " << conn_.current_uid() << "\r\n"; // transmit request parameters server_stream << "Native-Byte-Order: " << LSL_BYTE_ORDER << "\r\n"; server_stream << "Endian-Performance: " << std::floor(measure_endian_performance()) << "\r\n"; server_stream << "Has-IEEE754-Floats: " << (format_ieee754[cft_float32] && format_ieee754[cft_double64]) << "\r\n"; server_stream << "Supports-Subnormals: " << format_subnormal[conn_.type_info().channel_format()] << "\r\n"; server_stream << "Value-Size: " << conn_.type_info().channel_bytes() << "\r\n"; // 0 for strings server_stream << "Data-Protocol-Version: " << proposed_protocol_version << "\r\n"; server_stream << "Max-Buffer-Length: " << max_buflen_ << "\r\n"; server_stream << "Max-Chunk-Length: " << max_chunklen_ << "\r\n"; server_stream << "Hostname: " << conn_.type_info().hostname() << "\r\n"; server_stream << "Source-Id: " << conn_.type_info().source_id() << "\r\n"; server_stream << "Session-Id: " << conn_.type_info().session_id() << "\r\n"; server_stream << "\r\n" << std::flush; // check server response line (LSL/[Version] [StatusCode] [Message]) char buf[16384] = {0}; if (!server_stream.getline(buf, sizeof(buf))) throw lost_error("Connection lost."); std::vector parts = splitandtrim(buf, ' ', false); if (parts.size() < 3 || parts[0].compare(0, 4, "LSL/") != 0) throw std::runtime_error("Received a malformed response."); if (std::stoi(parts[0].substr(4)) / 100 > api_config::get_instance()->use_protocol_version() / 100) throw std::runtime_error( "The other party's protocol version is too new for this client; please " "upgrade your LSL library."); int status_code = std::stoi(parts[1]); if (status_code == 404) throw lost_error("The given address does not serve the resolved stream " "(likely outdated)."); if (status_code >= 400) throw std::runtime_error( "The other party sent an error: " + std::string(buf)); if (status_code >= 300) throw lost_error("The other party requested a redirect."); // receive response parameters while (server_stream.getline(buf, sizeof(buf)) && (buf[0] != '\r')) { std::string hdrline(buf); std::size_t colon = hdrline.find_first_of(':'); if (colon != std::string::npos) { // strip off comments auto semicolon = hdrline.find_first_of(';'); if (semicolon != std::string::npos) hdrline.erase(semicolon); // convert to lowercase for (auto &c : hdrline) c = ::tolower(c); // extract key & value std::string type = trim(hdrline.substr(0, colon)), rest = trim(hdrline.substr(colon + 1)); // get the header information if (type == "byte-order") { int use_byte_order = std::stoi(rest); // needed for interoperability with liblsl ~1.13 and data protocol 100 if(use_byte_order == 0) use_byte_order = LSL_BYTE_ORDER; auto value_size = format_sizes[conn_.type_info().channel_format()]; if (!lsl::can_convert_endian(use_byte_order, value_size)) throw std::runtime_error( "The byte order conversion requested by the other party is " "not supported."); reverse_byte_order = use_byte_order != LSL_BYTE_ORDER; } if (type == "suppress-subnormals") suppress_subnormals = lsl::from_string(rest); if (type == "uid" && rest != conn_.current_uid()) throw lost_error("The received UID does not match the current " "connection's UID."); if (type == "data-protocol-version") { data_protocol_version = std::stoi(rest); if (data_protocol_version > api_config::get_instance()->use_protocol_version()) throw std::runtime_error( "The protocol version requested by the other party is not " "supported by this client."); } } } if (!server_stream) throw lost_error("Server connection lost."); } else { // version 1.00: send request line and feed parameters server_stream << "LSL:streamfeed\r\n"; server_stream << max_buflen_ << " " << max_chunklen_ << "\r\n" << std::flush; } if (data_protocol_version == 100) { // portable binary archive (parse archive header) inarch = std::make_unique(*server_stream.rdbuf()); // receive stream_info message from server std::string infomsg; *inarch >> infomsg; stream_info_impl info; info.from_shortinfo_message(infomsg); // confirm that the UID matches, otherwise reconnect if (info.uid() != conn_.current_uid()) throw lost_error( "The received UID does not match the current connection's UID."); } // --- format validation --- { // receive and parse two subsequent test-pattern samples and check if they are // formatted as expected lsl::factory fac( conn_.type_info().channel_format(), conn_.type_info().channel_count(), 4); for (int test_pattern : {4, 2}) { lsl::sample_p expected(fac.new_sample(0.0, false)), received(fac.new_sample(0.0, false)); expected->assign_test_pattern(test_pattern); if (data_protocol_version >= 110) received->load_streambuf(buffer, data_protocol_version, reverse_byte_order, suppress_subnormals); else *inarch >> *received; if (*expected.get() != *received.get()) throw std::runtime_error( "The received test-pattern samples do not match the specification." " The protocol formats are likely incompatible."); } } // signal to accessor functions on other threads that the protocol negotiation has // been successful, so we're now connected (and remain to be even if we later // recover silently) { std::lock_guard lock(connected_mut_); connected_ = true; } connected_upd_.notify_all(); // --- transmission loop --- double last_timestamp = 0.0; double srate = conn_.current_srate(); for (int k = 0; !conn_.lost() && !conn_.shutdown() && !closing_stream_; k++) { // allocate and fetch a new sample sample_p samp(factory->new_sample(0.0, false)); if (data_protocol_version >= 110) samp->load_streambuf( buffer, data_protocol_version, reverse_byte_order, suppress_subnormals); else *inarch >> *samp; // deduce timestamp if necessary if (samp->timestamp() == DEDUCED_TIMESTAMP) { samp->timestamp() = last_timestamp; if (srate != IRREGULAR_RATE) samp->timestamp() += 1.0 / srate; } last_timestamp = samp->timestamp(); // push it into the sample queue sample_queue_.push_sample(samp); // periodically update the last receive time to keep the watchdog happy if (srate <= 16 || (k & 0xF) == 0) conn_.update_receive_time(lsl_clock()); } } catch (err_t) { // connection-level error: closed, reset, refused, etc. conn_.try_recover_from_error(); } catch (lost_error &) { // another type of connection error conn_.try_recover_from_error(); } catch (shutdown_error &) { // termination due to connection shutdown throw lost_error("The inlet has been disengaged."); } catch (std::exception &e) { // some perhaps more serious transmission or parsing error (could be indicative of a // protocol issue) if (!conn_.shutdown()) LOG_F(ERROR, "Stream transmission broke off (%s); re-connecting...", e.what()); conn_.try_recover_from_error(); } // wait for a few msec so as to not spam the provider with reconnects std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } catch (lost_error &) { // the connection was irrecoverably lost: since the pull_sample() function may // be waiting for the next sample we need to wake it up by passing a sentinel sample_queue_.push_sample(sample_p()); } conn_.release_watchdog(); } } // namespace lsl liblsl-1.17.7/src/data_receiver.h000066400000000000000000000111311517625163100166310ustar00rootroot00000000000000#ifndef DATA_RECEIVER_H #define DATA_RECEIVER_H #include "cancellation.h" #include "common.h" #include "consumer_queue.h" #include "forward.h" #include #include #include #include #include namespace lsl { class inlet_connection; // Forward declaration /** Internal class of an inlet that's retrieving the data (the samples) of the inlet. * * The actual communication runs in an internal background thread, while the public functions * (pull_sample_typed/untyped, open_stream, close_stream) wait for the thread to finish. * The public functions have an optional timeout after which they give up, while the background * thread continues to do its job (so the next public-function call may succeed within the timeout). * The background thread terminates only if the data_receiver is destroyed or the underlying * connection is lost or shut down. */ class data_receiver final : public cancellable_registry { public: /** * Construct a new data receiver from an info connection. * @param conn An inlet connection object. * @param max_buflen Optionally the maximum amount of data to buffer in samples (per-channel). * Recording applications want to use a fairly large buffer size here, while real-time * applications want to only buffer as much as they need to perform their next calculation. * @param max_chunklen Optionally the maximum size, in samples, at which chunks are transmitted * (the default corresponds to the chunk sizes used by the sender). Recording applications can * use a generous size here (leaving it to the network how to pack things), while real-time * applications may want a finer (perhaps 1-sample) granularity. */ data_receiver(inlet_connection &conn, int max_buflen = 360, int max_chunklen = 0); /// Destructor. Stops the background activities. ~data_receiver() final; /** * Open a new data stream. * All samples pushed in at the other end from this moment onwards will be queued and * eventually be delivered in response to pull_sample() or pull_chunk() calls. * A pull call without preceding open_stream() serves as an implicit open_stream(). * @param timeout Optional timeout of the operation (default: no timeout). * @throws lsl::timeout_error (if the timeout expires), or lsl::lost_error (if the stream * source has been lost). */ void open_stream(double timeout = FOREVER); /** * Close the current data stream. * All samples still buffered or in flight will be dropped and the source will halt its * buffering of data for this inlet. If an application stops being interested in data from a * source (temporarily or not), it should call close_stream() to not pressure the source outlet * to buffer unnecessarily large amounts of data (perhaps even running out of memory). */ void close_stream(); /// Retrieve a sample from the sample queue and assign its contents to the given typed buffer. template double pull_sample_typed(T *buffer, uint32_t buffer_elements, double timeout = FOREVER); /// Read sample from the inlet and read it into a pointer to raw data. double pull_sample_untyped(void *buffer, int buffer_bytes, double timeout = FOREVER); /// Check whether the underlying buffer is empty. This value may be inaccurate. bool empty() { return sample_queue_.empty(); } std::size_t samples_available() { return sample_queue_.read_available(); } /// Flush the queue, return the number of dropped samples uint32_t flush() noexcept { return sample_queue_.flush(); } private: /// The data reader thread. void data_thread(); sample_p try_get_next_sample(double timeout); /// the underlying connection inlet_connection &conn_; // fields related to the data reader thread /// a factory to create samples of appropriate type factory_p sample_factory_; /// background read thread std::thread data_thread_; /// whether we need to check whether the thread has been started bool check_thread_start_; /// indicates to the data thread that it a close has been requested std::atomic closing_stream_; /// whether the stream has been connected / opened bool connected_; /// queue of samples ready to be picked up (populated by the data thread) consumer_queue sample_queue_; /// mutex to protect the connected state std::mutex connected_mut_; /// condition variable to indicate that an update for the connected state is available std::condition_variable connected_upd_; // internal data used by the reader thread /// the maximum number of samples to be buffered for this inlet int max_buflen_; // the desired maximum chunklen for received samples int max_chunklen_; }; } // namespace lsl #endif liblsl-1.17.7/src/forward.h000066400000000000000000000014561517625163100155110ustar00rootroot00000000000000#pragma once #include #include #include namespace asio { namespace ip { class tcp; class udp; } // namespace ip class io_context; } // namespace asio namespace eos { class portable_oarchive; class portable_iarchive; } // namespace eos namespace lsl { /// shared pointers to various classes using factory_p = std::shared_ptr; using sample_p = lslboost::intrusive_ptr; using send_buffer_p = std::shared_ptr; using stream_info_impl_p = std::shared_ptr; using io_context_p = std::shared_ptr; using string_p = std::shared_ptr; using tcp_server_p = std::shared_ptr; using udp_server_p = std::shared_ptr; } // namespace lsl liblsl-1.17.7/src/info_receiver.cpp000066400000000000000000000057401517625163100172170ustar00rootroot00000000000000#include "info_receiver.h" #include "cancellable_streambuf.h" #include "inlet_connection.h" #include "stream_info_impl.h" #include #include #include #include #include #include #include lsl::info_receiver::info_receiver(inlet_connection &conn) : conn_(conn) { conn_.register_onlost(this, &fullinfo_upd_); } lsl::info_receiver::~info_receiver() { try { conn_.unregister_onlost(this); if (info_thread_.joinable()) info_thread_.join(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error during destruction of an info_receiver: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during info receiver shutdown."); } } const lsl::stream_info_impl &lsl::info_receiver::info(double timeout) { std::unique_lock lock(fullinfo_mut_); auto info_ready = [this]() { return fullinfo_ || conn_.lost(); }; if (!info_ready()) { // start thread if not yet running if (!info_thread_.joinable()) info_thread_ = std::thread(&info_receiver::info_thread, this); // wait until we are ready to return a result (or we time out) if (timeout >= FOREVER) fullinfo_upd_.wait(lock, info_ready); else if (!fullinfo_upd_.wait_for(lock, std::chrono::duration(timeout), info_ready)) throw timeout_error("The info() operation timed out."); } if (conn_.lost()) throw lost_error("The stream read by this inlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); return *fullinfo_; } void lsl::info_receiver::info_thread() { conn_.acquire_watchdog(); loguru::set_thread_name((std::string("I_") += conn_.type_info().name().substr(0, 12)).c_str()); try { while (!conn_.lost() && !conn_.shutdown()) { try { // make a new stream buffer & stream cancellable_streambuf buffer; buffer.register_at(&conn_); std::iostream server_stream(&buffer); // connect... if (nullptr == buffer.connect(conn_.get_tcp_endpoint())) { throw asio::system_error(buffer.error()); } // send the query server_stream << "LSL:fullinfo\r\n" << std::flush; // receive and parse the response std::ostringstream os; os << server_stream.rdbuf(); stream_info_impl info; std::string msg = os.str(); info.from_fullinfo_message(msg); // if this is not a valid streaminfo we retry if (info.created_at() == 0.0) continue; // store the result for pickup & return { std::lock_guard lock(fullinfo_mut_); fullinfo_ = std::make_shared(info); } fullinfo_upd_.notify_all(); break; } catch (err_t) { // connection-level error: closed, reset, refused, etc. conn_.try_recover_from_error(); } catch (std::exception &e) { // parsing-level error: intermittent disconnect or invalid protocol LOG_F(ERROR, "Error while receiving the stream info (%s); retrying...", e.what()); conn_.try_recover_from_error(); } } } catch (lost_error &) {} conn_.release_watchdog(); } liblsl-1.17.7/src/info_receiver.h000066400000000000000000000036131517625163100166610ustar00rootroot00000000000000#ifndef INFO_RECEIVER_H #define INFO_RECEIVER_H #include "common.h" #include "forward.h" #include #include #include namespace lsl { class inlet_connection; class stream_info_impl; /** Internal class of an inlet that is responsible for retrieving the info of the inlet. * * The actual communication runs in an internal background thread, while the public function * (info_receiver::info()) waits for the thread to finish. * The public function has an optional timeout after which it gives up, while the background thread * continues to do its job (so the next public-function call may succeed within the timeout). * The background thread terminates only if the info_receiver is destroyed or the underlying * connection is lost or shut down. */ class info_receiver { public: /// Construct a new info receiver for a given connection. info_receiver(inlet_connection &conn); /// Destructor. Stops the background activities. ~info_receiver(); /** * Retrieve the complete information of the given stream, including the extended description * (stream_info::desc() field). * @param timeout Timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been * lost). */ const stream_info_impl &info(double timeout = FOREVER); private: /// The info reader thread. void info_thread(); /// reference to the underlying connection inlet_connection &conn_; /// background reader thread and the data generated by it /// pulls the info in the background std::thread info_thread_; /// the full stream_info_impl object (retrieved by the info thread) stream_info_impl_p fullinfo_; /// mutex to protect the fullinfo std::mutex fullinfo_mut_; /// condition variable to indicate that an update for the fullinfo is available std::condition_variable fullinfo_upd_; }; } // namespace lsl #endif liblsl-1.17.7/src/inlet_connection.cpp000066400000000000000000000307361517625163100177350ustar00rootroot00000000000000#include "inlet_connection.h" #include "api_config.h" #include "resolver_impl.h" #include #include #include #include #include #include #include #include #include #include using namespace lsl; namespace ip = asio::ip; inlet_connection::inlet_connection(const stream_info_impl &info, bool recover) : type_info_(info), host_info_(info), tcp_protocol_(tcp::v4()), udp_protocol_(udp::v4()), recovery_enabled_(recover), lost_(false), shutdown_(false), last_receive_time_(lsl_clock()), active_transmissions_(0) { // if the given stream_info is already fully resolved... if (!host_info_.v4address().empty() || !host_info_.v6address().empty()) { // check LSL protocol version (we strictly forbid incompatible protocols instead of risking // silent failure) if (type_info_.version() / 100 > api_config::get_instance()->use_protocol_version() / 100) throw std::runtime_error( (std::string("The received stream (") += host_info_.name()) += ") uses a newer protocol version than this inlet. Please update."); // select TCP/UDP protocol versions if (!set_protocols(info, false)) { // otherwise use the protocol type that is selected in the config tcp_protocol_ = api_config::get_instance()->allow_ipv4() ? tcp::v4() : tcp::v6(); udp_protocol_ = api_config::get_instance()->allow_ipv4() ? udp::v4() : udp::v6(); } if (recovery_enabled_ && type_info_.source_id().empty()) { // we cannot correctly recover streams which don't have a unique source id LOG_F(WARNING, "The stream named '%s' can't be recovered automatically if its provider crashes " "because it doesn't have a unique source ID", host_info_.name().c_str()); recovery_enabled_ = false; } } else { // the actual endpoint is not known yet -- we need to discover it later on the fly // check that all the necessary information for this has been fully specified if (type_info_.name().empty() && type_info_.type().empty() && type_info_.source_id().empty()) throw std::invalid_argument( "When creating an inlet with a constructed (instead of resolved) stream_info, you " "must assign at least the name, type or source_id of the desired stream."); if (type_info_.channel_count() == 0) throw std::invalid_argument( "When creating an inlet with a constructed (instead of resolved) stream_info, you " "must assign a nonzero channel count."); if (type_info_.channel_format() == cft_undefined) throw std::invalid_argument("When creating an inlet with a constructed (instead of " "resolved) stream_info, you must assign a channel format."); // use the protocol that is specified in the config tcp_protocol_ = api_config::get_instance()->allow_ipv4() ? tcp::v4() : tcp::v6(); udp_protocol_ = api_config::get_instance()->allow_ipv4() ? udp::v4() : udp::v6(); // assign initial dummy endpoints host_info_.v4address("127.0.0.1"); host_info_.v6address("::1"); host_info_.v4data_port(49999); host_info_.v4service_port(49999); host_info_.v6data_port(49999); host_info_.v6service_port(49999); // recovery must generally be enabled recovery_enabled_ = true; } } void inlet_connection::engage() { if (recovery_enabled_) watchdog_thread_ = std::thread(&inlet_connection::watchdog_thread, this); } void inlet_connection::disengage() { // shut down the connection { std::lock_guard lock(shutdown_mut_); shutdown_ = true; } shutdown_cond_.notify_all(); // cancel all operations (resolver, streams, ...) resolver_.cancel(); cancel_and_shutdown(); // and wait for the watchdog to finish if (recovery_enabled_) watchdog_thread_.join(); } // === external accessors for connection properties === /// convert a IPv6 address or hostname into an non-link-local address ip::address resolve_v6_addr(const std::string &addr) { // Try to parse the IPv6 address asio::error_code ec; auto v6addr = ip::make_address_v6(addr, ec); if (!ec && !v6addr.is_link_local()) return v6addr; // This more complicated procedure is required when the address is an ipv6 link-local address. // Simplified from https://stackoverflow.com/a/10303761/73299 asio::io_context io; auto res = ip::tcp::resolver(io).resolve(addr, ""); if (res.empty()) throw lost_error("Unable to resolve tcp stream at address: " + addr); return res.begin()->endpoint().address(); } tcp::endpoint inlet_connection::get_tcp_endpoint() { shared_lock_t lock(host_info_mut_); if (tcp_protocol_ == tcp::v4()) return {ip::make_address(host_info_.v4address()), host_info_.v4data_port()}; std::string addr = host_info_.v6address(); uint16_t port = host_info_.v6data_port(); lock.unlock(); return {resolve_v6_addr(addr), port}; } udp::endpoint inlet_connection::get_udp_endpoint() { shared_lock_t lock(host_info_mut_); if (udp_protocol_ == udp::v4()) return {ip::make_address(host_info_.v4address()), host_info_.v4service_port()}; std::string addr = host_info_.v6address(); uint16_t port = host_info_.v6service_port(); lock.unlock(); return {resolve_v6_addr(addr), port}; } std::string inlet_connection::current_uid() { shared_lock_t lock(host_info_mut_); return host_info_.uid(); } double inlet_connection::current_srate() { shared_lock_t lock(host_info_mut_); return host_info_.nominal_srate(); } // === connection recovery logic === void inlet_connection::try_recover() { if (recovery_enabled_) { try { std::lock_guard lock(recovery_mut_); // first create the query string based on the known stream information std::ostringstream query; { shared_lock_t lock(host_info_mut_); // construct query according to the fields that are present in the stream_info const char *channel_format_strings[] = {"undefined", "float32", "double64", "string", "int32", "int16", "int8", "int64"}; query << "channel_count='" << host_info_.channel_count() << "'"; if (!host_info_.name().empty()) query << " and name='" << host_info_.name() << "'"; if (!host_info_.type().empty()) query << " and type='" << host_info_.type() << "'"; // for floating point values, str2double(double2str(fpvalue)) == fpvalue is most // likely wrong and might lead to streams not being resolved. // We accept that a lost stream might be replaced by a stream from the same host // with the same type, channel type and channel count but a different srate /*if (host_info_.nominal_srate() > 0) query << " and nominal_srate='" << host_info_.nominal_srate() << "'"; */ if (!host_info_.source_id().empty()) query << " and source_id='" << host_info_.source_id() << "'"; query << " and channel_format='" << channel_format_strings[host_info_.channel_format()] << "'"; } // attempt a recovery for (int attempt = 0;; attempt++) { // issue the resolve (blocks until it is either cancelled or got at least one // matching streaminfo and has waited for a certain timeout) std::vector infos = resolver_.resolve_oneshot(query.str(), 1, FOREVER, attempt == 0 ? 1.0 : 5.0); if (!infos.empty()) { // got a result unique_lock_t lock_recover_host_info(host_info_mut_); // check if any of the returned streams is the one that we're currently // connected to for (auto &info : infos) if (info.uid() == host_info_.uid()) return; // in this case there is no need to recover (we're still fine) // otherwise our stream is gone and we indeed need to recover: // ensure that the query result is unique (since someone might have used a // non-unique stream ID) if (infos.size() == 1) { // update the protocols from the stream info, // preferring IPv6 if previously used as well if (!set_protocols(infos[0], tcp_protocol_ == tcp::v6())) throw std::logic_error("No suitable protocol found in discovery"); // update the endpoint host_info_ = infos[0]; // cancel all cancellable operations registered with this connection cancel_all_registered(); // invoke any callbacks associated with a connection recovery std::lock_guard lock_onrecover_mut(onrecover_mut_); // unlock recover mutex because onrecover callbacks may acquire the lock themselves lock_recover_host_info.unlock(); for (auto &pair : onrecover_) (pair.second)(); } else { // there are multiple possible streams to connect to in a recovery attempt: // we warn and re-try this is because we don't want to randomly connect to // the wrong source without the user knowing about it; the correct action // (if this stream shall indeed have multiple instances) is to change the // user code and make its source_id unique, or remove the source_id // altogether if that's not possible (therefore disabling the ability to // recover) LOG_F(WARNING, "Found multiple streams with name='%s' and source_id='%s'. " "Cannot recover unless all but one are closed.", host_info_.name().c_str(), host_info_.source_id().c_str()); continue; } } else { // cancelled } break; } } catch (std::exception &e) { LOG_F(ERROR, "A recovery attempt encountered an unexpected error: %s", e.what()); } } } bool inlet_connection::set_protocols(const stream_info_impl &info, bool prefer_v6) { bool has_v4 = !info.v4address().empty() && info.v4data_port() && info.v4service_port(); bool has_v6 = !info.v6address().empty() && info.v6data_port() && info.v6service_port(); bool can_v4 = api_config::get_instance()->allow_ipv4() && has_v4; bool can_v6 = api_config::get_instance()->allow_ipv6() && has_v6; if ((prefer_v6 && can_v6) || !can_v4) { tcp_protocol_ = tcp::v6(); udp_protocol_ = udp::v6(); return true; } if (can_v4) { tcp_protocol_ = tcp::v4(); udp_protocol_ = udp::v4(); return true; } return false; } void inlet_connection::watchdog_thread() { loguru::set_thread_name((std::string("W_") += type_info().name().substr(0, 12)).c_str()); while (!lost_ && !shutdown_) { try { // we only try to recover if a) there are active transmissions and b) we haven't seen // new data for some time { std::unique_lock lock(client_status_mut_); if ((active_transmissions_ > 0) && (lsl_clock() - last_receive_time_ > api_config::get_instance()->watchdog_time_threshold())) { lock.unlock(); try_recover(); } } // instead of sleeping we're waiting on a condition variable for the sleep duration // so that the watchdog can be cancelled conveniently { std::unique_lock lock(shutdown_mut_); shutdown_cond_.wait_for(lock, std::chrono::duration( api_config::get_instance()->watchdog_check_interval()), [this]() { return shutdown(); }); } } catch (std::exception &e) { LOG_F(ERROR, "Unexpected hiccup in the watchdog thread: %s", e.what()); } } } void inlet_connection::try_recover_from_error() { if (!shutdown_) { if (!recovery_enabled_) { // if the stream is irrecoverable it is now lost, // so we need to notify the other inlet components lost_ = true; try { std::lock_guard lock(client_status_mut_); for (auto &pair : onlost_) pair.second->notify_all(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected problem while trying to issue a connection loss notification: %s", e.what()); } throw lost_error("The stream read by this inlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); } try_recover(); } } // === client status updates === void inlet_connection::acquire_watchdog() { std::lock_guard lock(client_status_mut_); active_transmissions_++; } void inlet_connection::release_watchdog() { std::lock_guard lock(client_status_mut_); active_transmissions_--; } void inlet_connection::update_receive_time(double t) { std::lock_guard lock(client_status_mut_); last_receive_time_ = t; } void inlet_connection::register_onlost(void *id, std::condition_variable *cond) { std::lock_guard lock(client_status_mut_); onlost_[id] = cond; } void inlet_connection::unregister_onlost(void *id) { std::lock_guard lock(client_status_mut_); onlost_.erase(id); } void inlet_connection::register_onrecover(void *id, const std::function &func) { std::lock_guard lock(onrecover_mut_); onrecover_[id] = func; } void inlet_connection::unregister_onrecover(void *id) { std::lock_guard lock(onrecover_mut_); onrecover_.erase(id); } liblsl-1.17.7/src/inlet_connection.h000066400000000000000000000171171517625163100174000ustar00rootroot00000000000000#ifndef INLET_CONNECTION_H #define INLET_CONNECTION_H #include "cancellation.h" #include "resolver_impl.h" #include "stream_info_impl.h" #include #include #include #include #include #include #include #include /* shared_mutex was added in C++17 so we fall back to a plain mutex when building for C++11 / C++14 or MSVC <= 2019 */ #if __cplusplus >= 201703L || _MSC_VER >= 1925 #include using shared_mutex_t = std::shared_mutex; using shared_lock_t = std::shared_lock; #else using shared_mutex_t = std::mutex; using shared_lock_t = std::unique_lock; #endif using unique_lock_t = std::unique_lock; using asio::ip::tcp; using asio::ip::udp; namespace lsl { /** * An inlet connection encapsulates the inlet's connection endpoint and provides a recovery * mechanism in case the connection breaks down (e.g., due to a remote computer crash). * * When a client of the connection (one of the other inlet components) experiences a connection loss * it invokes the function try_recover_from_error() which attempts to update the endpoint to a valid * state (possible once the stream is back online). * * Since in some cases a client might not be able to detect a connection loss and so would stall * forever, the inlet_connection maintains a watchdog thread that periodically checks and recovers * the connection state. * * Internally the recovery works by using the resolver to find the desired stream on the network * again and updating the endpoint information if it has changed. */ class inlet_connection : public cancellable_registry { public: /** * Construct a new inlet connection. * @param info A stream info object (either coming from one of the resolver functions or *constructed manually). * @param recover Try to silently recover lost streams that are recoverable (= those that that *have a unique source_id set). In all other cases (recover is false or the stream is not *recoverable) the stream is declared lost in case of a connection breakdown. */ inlet_connection(const stream_info_impl &info, bool recover = true); /** * Prepare the connection and its auto-recovery thread. * * This is called once by the inlet after all other initialization. */ void engage(); /** * Shut down the connection and all its running operations. * * This is called once by the inlet before any component is destroyed to ensure orderly * shutdown. */ void disengage(); // === endpoint information (might change if the connection is rehosted) === /// Get the current TCP endpoint from the info (according to our configured protocol). tcp::endpoint get_tcp_endpoint(); /// Get the current UDP endpoint from the info (according to our configured protocol). udp::endpoint get_udp_endpoint(); /// Get the UDP protocol type. udp udp_protocol() const { return udp_protocol_; } // === connection status info === /// True if the connection has been irretrievably lost. bool lost() const { return lost_; } /// True if the connection is being shut down. bool shutdown() const { return shutdown_; } // === recovery control === /** * Try to recover the connection. * * This either blocks until it succeeds or declares the connection as lost (if recovery is * disabled), and throws a lost error. * Only call this when the connection is found to have broken down (e.g., socket error). */ void try_recover_from_error(); // === client status info === /// Indicate that a transmission is now active and requesting the watchdog. /// The recovery watchdog will be inactive while no transmission is requested. void acquire_watchdog(); /// Indicate that a transmission has just completed. /// The recovery watchdog will be inactive while no transmission is requested. void release_watchdog(); /// Inform the connection that content was received from the source (using lsl::lsl_clock()). /// If a sufficient amount of time has passed since the last call the watchdog thread will /// try to recover the connection. void update_receive_time(double t); /// Register a condition variable that should be notified when a connection is lost void register_onlost(void *id, std::condition_variable *cond); /// Unregister a condition variable from the set that is notified on connection loss void unregister_onlost(void *id); /// Register a callback function that shall be called when a recovery has been performed void register_onrecover(void *id, const std::function &func); /// Unregister a recovery callback function void unregister_onrecover(void *id); // === misc properties of the connected stream === /// Get info about the connected stream's type/format. /// This information is constant and will never change over the lifetime of the connection. const stream_info_impl &type_info() const { return type_info_; } /// Get the current stream instance UID (which would be different after a crash and restart of /// the data source). std::string current_uid(); /// Get the nominal srate of the endpoint; we assume that this might possibly change between /// crashes/restarts of the data source under some circumstances (although such behavior would /// be strongly discouraged). double current_srate(); private: /// A thread that periodically checks whether the connection should be recovered. void watchdog_thread(); /// A (potentially speculative) resolve-and-recover operation. void try_recover(); /// Sets the endpoints from a stream info considering a previous connection bool set_protocols(const stream_info_impl &info, bool prefer_v6); // core connection properties /// static/read-only information of the stream (type & format) const stream_info_impl type_info_; /// the volatile information of the stream (addresses + ports); protected by a read/write mutex stream_info_impl host_info_; /// a mutex to protect the state of the host_info (single-write/multiple-reader) shared_mutex_t host_info_mut_; /// the TCP protocol used (according to api_config) tcp tcp_protocol_; /// the UDP protocol used (according to api_config) udp udp_protocol_; /// whether we would try to recover the stream if it is lost bool recovery_enabled_; /// is the stream irrecoverably lost (set by try_recover_from_error if recovery is disabled) std::atomic lost_; /// internal watchdog thread (to detect dead connections), re-resolves the current connection /// speculatively std::thread watchdog_thread_; // things related to the shutdown condition /// indicates to threads that we're shutting down std::atomic shutdown_; /// a mutex to protect the shutdown state std::mutex shutdown_mut_; /// condition variable indicating that we're shutting down std::condition_variable shutdown_cond_; // things related to recovery /// our resolver, in case we need it resolver_impl resolver_; /// we allow only one recovery operation at a time std::mutex recovery_mut_; // client status info for recovery & notification purposes /// a group of condition variables that should be notified when the connection is lost std::map onlost_; /// a group of callback functions that should be invoked once the connection has been recovered std::map> onrecover_; /// the last time when we received data from the server double last_receive_time_; /// the number of currently active transmissions (data or info) int active_transmissions_; /// protects the client status info std::mutex client_status_mut_; /// protects the onrecover callback map std::mutex onrecover_mut_; }; } // namespace lsl #endif liblsl-1.17.7/src/legacy/000077500000000000000000000000001517625163100151325ustar00rootroot00000000000000liblsl-1.17.7/src/legacy/legacy_abi.cpp000066400000000000000000000336501517625163100177240ustar00rootroot00000000000000#include "legacy_abi.h" #include "../common.h" #include "../resolver_impl.h" #include "../stream_info_impl.h" #include "../stream_inlet_impl.h" #include "../stream_outlet_impl.h" #include #include #define NO_EXPLICIT_TEMPLATE_INSTANTIATION using namespace lsl; using namespace pugi; // === Implementation of the stream_info class === // boilerplate code calling the private implementation stream_info::stream_info(): impl_(new stream_info_impl()) { } stream_info::stream_info(const stream_info &rhs): impl_(new stream_info_impl(*rhs.impl_)) { } stream_info::stream_info(const std::string &name, const std::string &type, int channel_count, double nominal_srate, lsl_channel_format_t channel_format, const std::string &source_id): impl_(new stream_info_impl(name,type,channel_count,nominal_srate,channel_format,source_id)) { } stream_info::stream_info(const stream_info_impl *impl): impl_(new stream_info_impl(*impl)) { } stream_info::stream_info(const stream_info_impl &impl): impl_(new stream_info_impl(impl)) { } stream_info::~stream_info() { delete impl_; } const std::string &stream_info::name() const { return impl_->name(); } const std::string &stream_info::type() const { return impl_->type(); } int stream_info::channel_count() const { return impl_->channel_count(); } double stream_info::nominal_srate() const { return impl_->nominal_srate(); } lsl_channel_format_t stream_info::channel_format() const { return impl_->channel_format(); } const std::string &stream_info::source_id() const { return impl_->source_id(); } int stream_info::version() const { return impl_->version(); } const std::string &stream_info::uid() const { return impl_->uid(); } double stream_info::created_at() const { return impl_->created_at(); } const std::string &stream_info::session_id() const { return impl_->session_id(); } const std::string &stream_info::hostname() const { return impl_->hostname(); } int stream_info::channel_bytes() const { return impl_->channel_bytes(); } int stream_info::sample_bytes() const { return impl_->sample_bytes(); } xml_element stream_info::desc() { return impl_->desc(); } xml_element stream_info::desc() const { return impl_->desc(); } std::string stream_info::as_xml() { return impl_->to_fullinfo_message(); } stream_info_impl *stream_info::impl() { return impl_; } const stream_info_impl *stream_info::impl() const { return impl_; } stream_info &stream_info::operator=(const stream_info &rhs) { if (this != &rhs) impl_ = new stream_info_impl(*rhs.impl_); return *this; } // === implementation of the xml_element class === // boilerplate code calling the private implementation xml_element::xml_element(): node_(0) {} xml_element::xml_element(const xml_node &node): node_(node.internal_object()) {} xml_element xml_element::first_child() const { return xml_node(node_).first_child(); } xml_element xml_element::last_child() const { return xml_node(node_).last_child(); } xml_element xml_element::next_sibling() const { return xml_node(node_).next_sibling(); } xml_element xml_element::previous_sibling() const { return xml_node(node_).previous_sibling(); } xml_element xml_element::parent() const { return xml_node(node_).parent(); } xml_element xml_element::child(const char *name) const { return xml_node(node_).child(name); } xml_element xml_element::next_sibling(const char *name) const { return xml_node(node_).next_sibling(name); } xml_element xml_element::previous_sibling(const char *name) const { return xml_node(node_).previous_sibling(name); } bool xml_element::empty() const { return xml_node(node_).empty(); } bool xml_element::is_text() const { return xml_node(node_).type() != node_element; } const char *xml_element::name() const { return xml_node(node_).name(); } const char *xml_element::value() const { return xml_node(node_).value(); } const char_t* xml_element::child_value() const { return xml_node(node_).child_value(); } const char_t* xml_element::child_value(const char *name) const { return xml_node(node_).child_value(name); } bool xml_element::set_name(const char *rhs) { return xml_node(node_).set_name(rhs); } bool xml_element::set_value(const char *rhs) { return xml_node(node_).set_value(rhs); } bool xml_element::set_child_value(const char *name,const char *rhs) { xml_node n = xml_node(node_).child(name); if (xml_node n2 = n.first_child()) return n2.set_value(rhs); else return n.append_child(node_pcdata).set_value(rhs); } xml_element xml_element::append_child(const char *name) { return xml_node(node_).append_child(name); } xml_element xml_element::prepend_child(const char *name) { return xml_node(node_).prepend_child(name); } xml_element xml_element::append_copy(const xml_element &e) { return xml_node(node_).append_copy(xml_node(e.node_)); } xml_element xml_element::prepend_copy(const xml_element &e) { return xml_node(node_).prepend_copy(xml_node(e.node_)); } void xml_element::remove_child(const char *name) { xml_node(node_).remove_child(name); } void xml_element::remove_child(const xml_element &e) { xml_node(node_).remove_child(xml_node(e.node_)); } pugi::xml_node_struct *xml_element::ptr() { return node_; } /// convenience function for document construction xml_element xml_element::append_child_value(const char *name, const char *value) { xml_node result = xml_node(node_).append_child(name); result.append_child(node_pcdata).set_value(value); return xml_node(node_); } /// convenience function for document construction xml_element xml_element::prepend_child_value(const char *name, const char *value) { xml_node result = xml_node(node_).prepend_child(name); result.append_child(node_pcdata).set_value(value); return xml_node(node_); } // === implementation of the stream_outlet class === // boilerplate code calling the private implementation stream_outlet::stream_outlet(const stream_info &info, int chunk_size, int max_buffered): impl_(new stream_outlet_impl(*info.impl(),chunk_size,info.nominal_srate()?(int)(info.nominal_srate()*max_buffered):max_buffered*100)) { } stream_outlet::~stream_outlet() { delete impl_; } stream_info stream_outlet::info() const { return stream_info(&impl_->info()); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::vector &data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const float *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const double *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const int64_t *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const int *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const short *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const char *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_sample(const std::string *data, double timestamp, bool pushthrough) { impl_->push_sample(data,timestamp,pushthrough); } void stream_outlet::push_numeric_raw(void *data, double timestamp, bool pushthrough) { impl_->push_numeric_raw(data,timestamp,pushthrough); } bool stream_outlet::have_consumers() { return impl_->have_consumers(); } bool stream_outlet::wait_for_consumers(double timeout) { return impl_->wait_for_consumers(timeout); } // === implementation of the stream_inlet class === // boilerplate code calling the private implementation stream_inlet::stream_inlet(const stream_info &info, int max_buflen, int max_chunklen, bool recover): impl_(new stream_inlet_impl(*info.impl(), info.nominal_srate()?(int)(info.nominal_srate()*max_buflen):max_buflen*100, max_chunklen, recover)) { } stream_inlet::~stream_inlet() { delete impl_; } stream_info stream_inlet::info(double timeout) { return stream_info(&impl_->info(timeout)); } void stream_inlet::open_stream(double timeout) { impl_->open_stream(timeout); } void stream_inlet::close_stream() { impl_->close_stream(); } double stream_inlet::time_correction(double timeout) { return impl_->time_correction(timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(std::vector &sample, double timeout) { return impl_->pull_sample(sample,timeout); } double stream_inlet::pull_sample(float *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(double *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(int64_t *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(int *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(short *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(char *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_sample(std::string *buffer, int buffer_elements, double timeout) { return impl_->pull_sample(buffer,buffer_elements,timeout); } double stream_inlet::pull_numeric_raw(void *sample, int buffer_bytes, double timeout) { return impl_->pull_numeric_raw(sample,buffer_bytes,timeout); } std::size_t stream_inlet::samples_available() { return impl_->samples_available(); }; bool stream_inlet::was_clock_reset() { return impl_->was_clock_reset(); }; // === Implementation of the free-standing functions in lsl_cpp.h === using std::string; using std::vector; LIBLSL_CPP_API int lsl::protocol_version() { return api_config::get_instance()->use_protocol_version(); } LIBLSL_CPP_API int lsl::library_version() { return LSL_LIBRARY_VERSION; } LIBLSL_CPP_API double lsl::local_clock() { return lsl_clock(); } LIBLSL_CPP_API std::vector lsl::resolve_streams(double wait_time) { // create a new resolver resolver_impl resolver; // build a new query std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "'"; // invoke it std::vector tmp = resolver.resolve_oneshot(os.str(),0,wait_time); return std::vector(tmp.begin(),tmp.end()); } LIBLSL_CPP_API std::vector lsl::resolve_stream(const std::string &prop, const std::string &value, int minimum, double timeout) { resolver_impl resolver; std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << prop << "='" << value << "'"; std::vector tmp = resolver.resolve_oneshot(os.str(),minimum,timeout); return std::vector(tmp.begin(),tmp.end()); } LIBLSL_CPP_API std::vector lsl::resolve_stream(const std::string &pred, int minimum, double timeout) { resolver_impl resolver; std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << pred; std::vector tmp = resolver.resolve_oneshot(os.str(),minimum,timeout); return std::vector(tmp.begin(),tmp.end()); } // === Implementation of the continuous_resolver class === continuous_resolver::continuous_resolver(double forget_after): impl_(new resolver_impl()) { std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "'"; impl_->resolve_continuous(os.str(),forget_after); } continuous_resolver::continuous_resolver(const std::string &prop, const std::string &value, double forget_after): impl_(new resolver_impl()) { std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << prop << "='" << value << "'"; impl_->resolve_continuous(os.str(),forget_after); } continuous_resolver::continuous_resolver(const std::string &pred, double forget_after): impl_(new resolver_impl()) { std::ostringstream os; os << "session_id='" << api_config::get_instance()->session_id() << "' and " << pred; impl_->resolve_continuous(os.str(),forget_after); } continuous_resolver::~continuous_resolver() { delete impl_; } std::vector continuous_resolver::results() { std::vector tmp = impl_->results(); return std::vector(tmp.begin(),tmp.end()); } liblsl-1.17.7/src/legacy/legacy_abi.h000066400000000000000000001016301517625163100173630ustar00rootroot00000000000000#ifndef LSL_CPP_H #define LSL_CPP_H #include "../common.h" /** * Header file for the legacy C++ ABI (application binary interface). This ABI has been superseded by * one that wraps the C ABI, and therefore is compatible across more compiler versions, e.g., MSVC20XX. */ #include #include #include #ifndef _WIN32 #pragma GCC visibility push(default) #endif namespace pugi { class xml_node; struct xml_node_struct; } namespace lsl { /** * Protocol version. * The major version is protocol_version() / 100; * The minor version is protocol_version() % 100; * Clients with different minor versions are protocol-compatible with each other * while clients with different major versions will refuse to work together. */ LIBLSL_CPP_API int protocol_version(); /** * Version of the liblsl library. * The major version is library_version() / 100; * The minor version is library_version() % 100; */ LIBLSL_CPP_API int library_version(); /** * Obtain a local system time stamp in seconds. The resolution is better than a millisecond. * This reading can be used to assign time stamps to samples as they are being acquired. * If the "age" of a sample is known at a particular time (e.g., from USB transmission * delays), it can be used as an offset to local_clock() to obtain a better estimate of * when a sample was actually captured. See stream_outlet::push_sample() for a use case. */ LIBLSL_CPP_API double local_clock(); // ========================== // === Stream Declaration === // ========================== /** * The stream_info object stores the declaration of a data stream. * Represents the following information: * a) stream data format (#channels, channel format) * b) core information (stream name, content type, sampling rate) * c) optional meta-data about the stream content (channel labels, measurement units, etc.) * * Whenever a program wants to provide a new stream on the lab network it will typically first * create a stream_info to describe its properties and then construct a stream_outlet with it to create * the stream on the network. Recipients who discover the outlet can query the stream_info; it is also * written to disk when recording the stream (playing a similar role as a file header). */ class LIBLSL_CPP_API xml_element; class LIBLSL_CPP_API stream_info { public: /** * Construct a new stream_info object. * Core stream information is specified here. Any remaining meta-data can be added later. * @param name Name of the stream. Describes the device (or product series) that this stream makes available * (for use by programs, experimenters or data analysts). Cannot be empty. * @param type Content type of the stream. Please see https://github.com/sccn/xdf/wiki/Meta-Data (or web search for: * XDF meta-data) for pre-defined content-type names, but you can also make up your own. * The content type is the preferred way to find streams (as opposed to searching by name). * @param channel_count Number of channels per sample. This stays constant for the lifetime of the stream. * @param nominal_srate The sampling rate (in Hz) as advertised by the data source, if regular (otherwise set to IRREGULAR_RATE). * @param channel_format Format/type of each channel. If your channels have different formats, consider supplying * multiple streams or use the largest type that can hold them all (such as cft_double64). * @param source_id Unique identifier of the device or source of the data, if available (such as the serial number). * This is critical for system robustness since it allows recipients to recover from failure even after the * serving app, device or computer crashes (just by finding a stream with the same source id on the network again). * Therefore, it is highly recommended to always try to provide whatever information can uniquely identify the data source itself. */ stream_info(const std::string &name, const std::string &type, int channel_count=1, double nominal_srate=IRREGULAR_RATE, lsl_channel_format_t channel_format=cft_float32, const std::string &source_id=std::string()); // ======================== // === Core Information === // ======================== // (these fields are assigned at construction) /** * Name of the stream. * This is a human-readable name. For streams offered by device modules, it refers to the type of device or product series * that is generating the data of the stream. If the source is an application, the name may be a more generic or specific identifier. * Multiple streams with the same name can coexist, though potentially at the cost of ambiguity (for the recording app or experimenter). */ const std::string &name() const; /** * Content type of the stream. * The content type is a short string such as "EEG", "Gaze" which describes the content carried by the channel (if known). * If a stream contains mixed content this value need not be assigned but may instead be stored in the description of channel types. * To be useful to applications and automated processing systems using the recommended content types is preferred. * Content types usually follow those pre-defined in https://github.com/sccn/xdf/wiki/Meta-Data (or web search for: XDF meta-data). */ const std::string &type() const; /** * Number of channels of the stream. * A stream has at least one channel; the channel count stays constant for all samples. */ int channel_count() const; /** * Sampling rate of the stream, according to the source (in Hz). * If a stream is irregularly sampled, this should be set to IRREGULAR_RATE. * * Note that no data will be lost even if this sampling rate is incorrect or if a device has temporary * hiccups, since all samples will be recorded anyway (except for those dropped by the device itself). However, * when the recording is imported into an application, a good importer may correct such errors more accurately * if the advertised sampling rate was close to the specs of the device. */ double nominal_srate() const; /** * Channel format of the stream. * All channels in a stream have the same format. However, a device might offer multiple time-synched streams * each with its own format. */ lsl_channel_format_t channel_format() const; /** * Unique identifier of the stream's source, if available. * The unique source (or device) identifier is an optional piece of information that, if available, allows that * endpoints (such as the recording program) can re-acquire a stream automatically once it is back online. */ const std::string &source_id() const; // ====================================== // === Additional Hosting Information === // ====================================== // (these fields are implicitly assigned once bound to an outlet/inlet) /// Protocol version used to deliver the stream. int version() const; /** * Creation time stamp of the stream. * This is the time stamp when the stream was first created * (as determined via local_clock() on the providing machine). */ double created_at() const; /** * Unique ID of the stream outlet instance (once assigned). * This is a unique identifier of the stream outlet, and is guaranteed to be different * across multiple instantiations of the same outlet (e.g., after a re-start). */ const std::string &uid() const; /** * Session ID for the given stream. * The session id is an optional human-assigned identifier of the recording session. * While it is rarely used, it can be used to prevent concurrent recording activitites * on the same sub-network (e.g., in multiple experiment areas) from seeing each other's streams * (assigned via a configuration file by the experimenter, see Network Connectivity in the LSL wiki). */ const std::string &session_id() const; /// Hostname of the providing machine. const std::string &hostname() const; // ======================== // === Data Description === // ======================== /** * Extended description of the stream. * It is highly recommended that at least the channel labels are described here. * See code examples on the LSL wiki. Other information, such as amplifier settings, * measurement units if deviating from defaults, setup information, subject information, etc., * can be specified here, as well. Meta-Data recommendations follow the XDF file format project * (github.com/sccn/xdf/wiki/Meta-Data or web search for: XDF meta-data). * * Important: if you use a stream content type for which meta-data recommendations exist, please * try to lay out your meta-data in agreement with these recommendations for compatibility with other applications. */ xml_element desc(); xml_element desc() const; // =============================== // === Miscellaneous Functions === // =============================== /** * Retrieve the entire stream_info in XML format. * This yields an XML document (in string form) whose top-level element is . The info element contains * one element for each field of the stream_info class, including: * a) the core elements , , , , , * b) the misc elements , , , , , , , , , * c) the extended description element with user-defined sub-elements. */ std::string as_xml(); /// Number of bytes occupied by a channel (0 for string-typed channels). int channel_bytes() const; /// Number of bytes occupied by a sample (0 for string-typed channels). int sample_bytes() const; /// Get the implementation handle. class stream_info_impl *impl(); const class stream_info_impl *impl() const; /// Construct from implementation handle. stream_info(const class stream_info_impl *impl); stream_info(const class stream_info_impl &impl); /// Default contructor. stream_info(); /// Copy constructor. stream_info(const stream_info &rhs); /// Assignment operator. stream_info &operator=(const stream_info &rhs); /// Destructor. ~stream_info(); private: class stream_info_impl *impl_; }; // ======================= // ==== Stream Outlet ==== // ======================= /** * A stream outlet. * Outlets are used to make streaming data (and the meta-data) available on the lab network. */ class LIBLSL_CPP_API stream_outlet { public: /** * Establish a new stream outlet. This makes the stream discoverable. * @param info The stream information to use for creating this stream. Stays constant over the lifetime of the outlet. * @param chunk_size Optionally the desired chunk granularity (in samples) for transmission. If unspecified, * each push operation yields one chunk. Inlets can override this setting. * @param max_buffered Optionally the maximum amount of data to buffer (in seconds if there is a nominal * sampling rate, otherwise x100 in samples). The default is 6 minutes of data. */ stream_outlet(const stream_info &info, int chunk_size=0, int max_buffered=360); // ======================================== // === Pushing a sample into the outlet === // ======================================== /** * Push a std vector of values as a sample into the outlet. * Each entry in the vector corresponds to one channel. The function handles type checking & conversion. * @param data A vector of values to push (one for each channel). * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering it with subsequent samples. * Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag. */ void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::vector &data, double timestamp=0.0, bool pushthrough=true); /** * Push a pointer to some values as a sample into the outlet. * This is a lower-level function for cases where data is available in some buffer. * Handles type checking & conversion. * @param data A pointer to values to push. The number of values pointed to must not be less than the number of channels in the sample. * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering it with subsequent samples. * Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag. */ void push_sample(const float *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const double *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const int64_t *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const int *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const short *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const char *data, double timestamp=0.0, bool pushthrough=true); void push_sample(const std::string *data, double timestamp=0.0, bool pushthrough=true); /** * Push a pointer to raw numeric data as one sample into the outlet. * This is the lowest-level function; performns no checking whatsoever. Can not be used for variable-size / string-formatted channels. * @param sample A pointer to the raw sample data to push. * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); if omitted, the current time is used. * @param pushthrough Whether to push the sample through to the receivers instead of buffering it with subsequent samples. * Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag. */ void push_numeric_raw(void *sample, double timestamp=0.0, bool pushthrough=true); // =============================== // === Miscellaneous Functions === // =============================== /** * Check whether consumers are currently registered. * While it does not hurt, there is technically no reason to push samples if there is no consumer. */ bool have_consumers(); /** * Wait until some consumer shows up (without wasting resources). * @return True if the wait was successful, false if the timeout expired. */ bool wait_for_consumers(double timeout); /** * Retrieve the stream info provided by this outlet. * This is what was used to create the stream (and also has the Additional Network Information fields assigned). */ stream_info info() const; /** * Destructor. * The stream will no longer be discoverable after destruction and all paired inlets will stop delivering data. */ ~stream_outlet(); private: // The outlet is a non-copyable object. stream_outlet(const stream_outlet &rhs); stream_outlet &operator=(const stream_outlet &rhs); class stream_outlet_impl *impl_; }; // =========================== // ==== Resolve Functions ==== // =========================== /** * Resolve all streams on the network. * This function returns all currently available streams from any outlet on the network. * The network is usually the subnet specified at the local router, but may also include * a multicast group of machines (given that the network supports it), or list of hostnames. * These details may optionally be customized by the experimenter in a configuration file * (see Network Connectivity in the LSL wiki). * This is the default mechanism used by the browsing programs and the recording program. * @param wait_time The waiting time for the operation, in seconds, to search for streams. * Warning: If this is too short (<0.5s) only a subset (or none) of the * outlets that are present on the network may be returned. * @return A vector of stream info objects (excluding their desc field), any of which can * subsequently be used to open an inlet. The full info can be retrieve from the inlet. */ LIBLSL_CPP_API std::vector resolve_streams(double wait_time=1.0); /** * Resolve all streams with a specific value for a given property. * If the goal is to resolve a specific stream, this method is preferred over resolving all streams and then selecting the desired one. * @param prop The stream_info property that should have a specific value (e.g., "name", "type", "source_id", or "desc/manufaturer"). * @param value The string value that the property should have (e.g., "EEG" as the type property). * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly none) will be returned. * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ LIBLSL_CPP_API std::vector resolve_stream(const std::string &prop, const std::string &value, int minimum=1, double timeout=FOREVER); /** * Resolve all streams that match a given predicate. * Advanced query that allows to impose more conditions on the retrieved streams; the given string is an XPath 1.0 * predicate for the node (omitting the surrounding []'s), see also http://en.wikipedia.org/w/index.php?title=XPath_1.0&oldid=474981951. * @param pred The predicate string, e.g. "name='BioSemi'" or "type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32" * @param minimum Return at least this number of streams. * @param timeout Optionally a timeout of the operation, in seconds (default: no timeout). * If the timeout expires, less than the desired number of streams (possibly none) will be returned. * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ LIBLSL_CPP_API std::vector resolve_stream(const std::string &pred, int minimum=1, double timeout=FOREVER); /** * A convenience class that resolves streams continuously in the background throughout * its lifetime and which can be queried at any time for the set of streams that are currently * visible on the network. */ class LIBLSL_CPP_API continuous_resolver { public: /** * Construct a new continuous_resolver that resolves all streams on the network. * This is analogous to the functionality offered by the free function resolve_streams(). * @param forget_after When a stream is no longer visible on the network (e.g., because it was shut down), * this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver(double forget_after=5.0); /** * Construct a new continuous_resolver that resolves all streams with a specific value for a given property. * This is analogous to the functionality provided by the free function resolve_stream(prop,value). * @param prop The stream_info property that should have a specific value (e.g., "name", "type", "source_id", or "desc/manufaturer"). * @param value The string value that the property should have (e.g., "EEG" as the type property). * @param forget_after When a stream is no longer visible on the network (e.g., because it was shut down), * this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver(const std::string &prop, const std::string &value, double forget_after=5.0); /** * Construct a new continuous_resolver that resolves all streams that match a given XPath 1.0 predicate. * This is analogous to the functionality provided by the free function resolve_stream(pred). * @param pred The predicate string, e.g. "name='BioSemi'" or "type='EEG' and starts-with(name,'BioSemi') and count(info/desc/channel)=32" * @param forget_after When a stream is no longer visible on the network (e.g., because it was shut down), * this is the time in seconds after which it is no longer reported by the resolver. */ continuous_resolver(const std::string &pred, double forget_after=5.0); /** * Obtain the set of currently present streams on the network (i.e. resolve result). * @return A vector of matching stream info objects (excluding their meta-data), any of * which can subsequently be used to open an inlet. */ std::vector results(); /** * Destructor. */ ~continuous_resolver(); private: class resolver_impl *impl_; }; // ====================== // ==== Stream Inlet ==== // ====================== /** * A stream inlet. * Inlets are used to receive streaming data (and meta-data) from the lab network. */ class LIBLSL_CPP_API stream_inlet { public: /** * Construct a new stream inlet from a resolved stream info. * @param info A resolved stream info object (as coming from one of the resolver functions). * Note: the stream_inlet may also be constructed with a fully-specified stream_info, * if the desired channel format and count is already known up-front, but this is * strongly discouraged and should only ever be done if there is no time to resolve the * stream up-front (e.g., due to limitations in the client program). * @param max_buflen Optionally the maximum amount of data to buffer (in seconds if there is a nominal * sampling rate, otherwise x100 in samples). Recording applications want to use a fairly * large buffer size here, while real-time applications would only buffer as much as * they need to perform their next calculation. * @param max_chunklen Optionally the maximum size, in samples, at which chunks are transmitted * (the default corresponds to the chunk sizes used by the sender). * Recording applications can use a generous size here (leaving it to the network how * to pack things), while real-time applications may want a finer (perhaps 1-sample) granularity. If left unspecified (=0), the sender determines the chunk granularity. * @param recover Try to silently recover lost streams that are recoverable (=those that that have a source_id set). * In all other cases (recover is false or the stream is not recoverable) functions may throw a * lost_error if the stream's source is lost (e.g., due to an app or computer crash). */ stream_inlet(const stream_info &info, int max_buflen=360, int max_chunklen=0, bool recover=true); /** * Destructor. * The inlet will automatically disconnect if destroyed. */ ~stream_inlet(); /** * Retrieve the complete information of the given stream, including the extended description. * Can be invoked at any time of the stream's lifetime. * @param timeout Timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been lost). */ stream_info info(double timeout=FOREVER); /** * Subscribe to the data stream. * All samples pushed in at the other end from this moment onwards will be queued and * eventually be delivered in response to pull_sample() or pull_chunk() calls. * Pulling a sample without some preceding open_stream is permitted (the stream will then be opened implicitly). * @param timeout Optional timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been lost). */ void open_stream(double timeout=FOREVER); /** * Drop the current data stream. * All samples that are still buffered or in flight will be dropped and transmission * and buffering of data for this inlet will be stopped. If an application stops being * interested in data from a source (temporarily or not) but keeps the outlet alive, * it should call close_stream() to not waste unnecessary system and network * resources. */ void close_stream(); /** * Retrieve an estimated time correction offset for the given stream. * The first call to this function takes several milliseconds until a reliable first estimate is obtained. * Subsequent calls are instantaneous (and rely on periodic background updates). * The precision of these estimates should be below 1 ms (empirically within +/-0.2 ms). * @timeout Timeout to acquire the first time-correction estimate (default: no timeout). * @return The time correction estimate. This is the number that needs to be added to a time stamp * that was remotely generated via lsl_local_clock() to map it into the local clock domain of this machine. * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been lost). */ double time_correction(double timeout=FOREVER); // ======================================= // === Pulling a sample from the inlet === // ======================================= /** * Pull a sample from the inlet and read it into a std vector of values. * Handles type checking & conversion and allocates the necessary memory in the vector if necessary. * @param sample An STL vector to hold the resulting values. * @param timeout The timeout for this operation, if any. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was available. * To remap this time stamp to the local clock, add the value returned by .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); double pull_sample(std::vector &sample, double timeout=FOREVER); /** * Pull a sample from the inlet and read it into a pointer to values. * Handles type checking & conversion. * @param buffer A pointer to hold the resulting values. * @param buffer_elements The number of samples allocated in the buffer. Note: it is the responsibility of the user to allocate enough memory. * @param timeout The timeout for this operation, if any. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was available. * To remap this time stamp to the local clock, add the value returned by .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_sample(float *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(double *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(int64_t *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(int *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(short *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(char *buffer, int buffer_elements, double timeout=FOREVER); double pull_sample(std::string *buffer, int buffer_elements, double timeout=FOREVER); /** * Pull a sample from the inlet and read it into a pointer to raw data. * No type checking or conversions are done (not recommended!). Do not use for variable-size/string-formatted streams. * @param buffer A pointer to hold the resulting raw sample data. * @param buffer_bytes The number of bytes allocated in the buffer. Note: it is the responsibility of the user to allocate enough memory. * @param timeout The timeout for this operation, if any. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was available. * To remap this time stamp to the local clock, add the value returned by .time_correction() to it. * @throws lost_error (if the stream source has been lost). */ double pull_numeric_raw(void *sample, int buffer_bytes, double timeout=FOREVER); /** * Query whether samples are currently available for immediate pickup. * Note that it is not a good idea to use samples_available() to determine whether * a pull_*() call would block: to be sure, set the pull timeout to 0.0 or an acceptably * low value. If the underlying implementation supports it, the value will be the number of * samples available (otherwise it will be 1 or 0). */ std::size_t samples_available(); /** * Query whether the clock was potentially reset since the last call to was_clock_reset(). * This is a rarely-used function that is only useful to applications that combine multiple time_correction * values to estimate precise clock drift; it allows to tolerate cases where the source machine was * hot-swapped or restarted in between two measurements. */ bool was_clock_reset(); private: // The inlet is a non-copyable object. stream_inlet(const stream_inlet &rhs); stream_inlet &operator=(const stream_inlet &rhs); class stream_inlet_impl *impl_; }; // ===================== // ==== XML Element ==== // ===================== /** * A lightweight XML element tree; models the .desc() field of stream_info. * Has a name and can have multiple named children or have text content as value; attributes are omitted. * Insider note: The interface is modeled after a subset of pugixml's node type and is compatible with it. * See also http://pugixml.googlecode.com/svn/tags/latest/docs/manual/access.html for additional documentation. */ class LIBLSL_CPP_API xml_element { public: /// Constructor. xml_element(); xml_element(const pugi::xml_node &node); // === Tree Navigation === /// Get the first child of the element. xml_element first_child() const; /// Get the last child of the element. xml_element last_child() const; /// Get the next sibling in the children list of the parent node. xml_element next_sibling() const; /// Get the previous sibling in the children list of the parent node. xml_element previous_sibling() const; /// Get the parent node. xml_element parent() const; // === Tree Navigation by Name === /// Get a child with a specified name. xml_element child(const char *name) const; /// Get the next sibling with the specified name. xml_element next_sibling(const char *name) const; /// Get the previous sibling with the specified name. xml_element previous_sibling(const char *name) const; // === Content Queries === /// Whether this node is empty. bool empty() const; /// Whether this is a text body (instead of an XML element). True both for plain char data and CData. bool is_text() const; /// Name of the element. const char *name() const; /// Value of the element. const char *value() const; /// Get child value (value of the first child that is text). const char* child_value() const; /// Get child value of a child with a specified name. const char* child_value(const char *name) const; // === Modification === /// Append a child node with a given name, which has a (nameless) plain-text child with the given text value. xml_element append_child_value(const char *name, const char *value); /// Prepend a child node with a given name, which has a (nameless) plain-text child with the given text value. xml_element prepend_child_value(const char *name, const char *value); /// Set the text value of the (nameless) plain-text child of a named child node. bool set_child_value(const char *name, const char *value); /** * Set the element's name. * @return False if the node is empty (or if out of memory). */ bool set_name(const char *rhs); /** * Set the element's value. * @return False if the node is empty (or if out of memory). */ bool set_value(const char *rhs); /// Append a child element with the specified name. xml_element append_child(const char *name); /// Prepend a child element with the specified name. xml_element prepend_child(const char *name); /// Append a copy of the specified element as a child. xml_element append_copy(const xml_element &e); /// Prepend a child element with the specified name. xml_element prepend_copy(const xml_element &e); /// Remove a child element with the specified name. void remove_child(const char *name); /// Remove a specified child element. void remove_child(const xml_element &e); /// Get access to the internal pointer pugi::xml_node_struct *ptr(); private: pugi::xml_node_struct *node_; }; } // end namespace #ifndef _WIN32 #pragma GCC visibility pop #endif #endif // LSL_CPP_H liblsl-1.17.7/src/legacy/readme.txt000066400000000000000000000006571517625163100171400ustar00rootroot00000000000000These two files are included to allow the liblsl library to export the same C++ binary interface as in previous versions. This allows the library to be copied into application installations that currently use an older version without the need for recompiling. This older C++ ABI has been superseded by a new one that wraps the C interface, which does not suffer from compiler compatibility issues as much, particularly on Windows.liblsl-1.17.7/src/lsl_c_api_helpers.hpp000066400000000000000000000070511517625163100200510ustar00rootroot00000000000000#pragma once #include "common.h" #include #include /// Helper for LSL_STORE_EXCEPTION_IN #define LSLCATCHANDSTORE(ecvar, Exception, code) \ catch (Exception & e) { \ strncpy(const_cast(lsl_last_error()), e.what(), LAST_ERROR_SIZE - 1); \ int32_t *__ec = ecvar; \ if (__ec != nullptr) *__ec = code; \ } /// Helper for LSL_STORE_EXCEPTION_IN #define LSLCATCHANDRETURN(Exception, code) \ catch (Exception & e) { \ strncpy(const_cast(lsl_last_error()), e.what(), LAST_ERROR_SIZE - 1); \ return code; \ } /** * C API exception helper. * * Inserts `catch()` statements for stl and liblsl exceptions and sets the * variable pointed to by `ec` to the corresponding value from * `lsl_error_code_t` and copies the error message to `last_error`. * * If you're trying to create a function `lsl_foobar_do_bar` that wraps the `FooBar`'s member * function `do_bar`, this allows you to write * * ``` * function lsl_foobar_do_bar(lsl_foobar obj, int x, double y, int32_t *ec) { * try { * obj->do_bar(x, y); * } LSL_STORE_EXCEPTION_IN(ec) * } * ``` * * instead of * * ``` * function lsl_foobar_do_bar(lsl_foobar obj, int x, double y, int32_t *ec) { * if(ec) *ec = lsl_no_error; * try { * return obj->do_bar(x, y); * } * catch(Ex1 &e) { * if(ec) *ec = lsl_error1; * strncpy(last_error, e.what(), sizeof(last_error-1); * } * catch(Ex2 &) { * if(ec) *ec = lsl_error2; * strncpy(last_error, e.what(), sizeof(last_error-1); * } * // etc. * return 0; * } * ``` */ #define LSL_STORE_EXCEPTION_IN(ecvar) \ LSLCATCHANDSTORE(ecvar, lsl::timeout_error, lsl_timeout_error) \ LSLCATCHANDSTORE(ecvar, lsl::lost_error, lsl_lost_error) \ LSLCATCHANDSTORE(ecvar, std::range_error, lsl_argument_error) \ LSLCATCHANDSTORE(ecvar, std::invalid_argument, lsl_argument_error) \ LSLCATCHANDSTORE(ecvar, std::exception, lsl_internal_error) #define LSL_STORE_EXCEPTION LSL_STORE_EXCEPTION_IN(nullptr) /// Catch possible exceptions and return an appropriate error code or `lsl_no_error` #define LSL_RETURN_CAUGHT_EC \ LSLCATCHANDRETURN(lsl::timeout_error, lsl_timeout_error) \ LSLCATCHANDRETURN(lsl::lost_error, lsl_lost_error) \ LSLCATCHANDRETURN(std::range_error, lsl_argument_error) \ LSLCATCHANDRETURN(std::invalid_argument, lsl_argument_error) \ LSLCATCHANDRETURN(std::exception, lsl_internal_error) \ return lsl_no_error /// Try to create a new T object and return a pointer to it or `nullptr` if an exception occured. template Type *create_object_noexcept(T &&...args) noexcept { try { return new Type(std::forward(args)...); } LSL_STORE_EXCEPTION_IN(nullptr) return nullptr; } liblsl-1.17.7/src/lsl_inlet_c.cpp000066400000000000000000000253721517625163100166720ustar00rootroot00000000000000#include "lsl_c_api_helpers.hpp" #include "stream_inlet_impl.h" #include #include #include #include #include #include extern "C" { #include "api_types.hpp" // include api_types before public API header #include "../include/lsl/inlet.h" using namespace lsl; LIBLSL_C_API lsl_inlet lsl_create_inlet_ex(lsl_streaminfo info, int32_t max_buflen, int32_t max_chunklen, int32_t recover, lsl_transport_options_t flags) { try { int32_t buf_samples = info->calc_transport_buf_samples(max_buflen, flags); return create_object_noexcept( *info, buf_samples, max_chunklen, recover != 0); } LSLCATCHANDSTORE(nullptr, std::invalid_argument, lsl_argument_error); return nullptr; } LIBLSL_C_API lsl_inlet lsl_create_inlet( lsl_streaminfo info, int32_t max_buflen, int32_t max_chunklen, int32_t recover) { return lsl_create_inlet_ex(info, max_buflen, max_chunklen, recover, transp_default); } LIBLSL_C_API void lsl_destroy_inlet(lsl_inlet in) { try { delete in; } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error in %s: %s", __func__, e.what()); } } LIBLSL_C_API lsl_streaminfo lsl_get_fullinfo(lsl_inlet in, double timeout, int32_t *ec) { try { return new stream_info_impl(in->info(timeout)); } LSL_STORE_EXCEPTION_IN(ec) return nullptr; } LIBLSL_C_API void lsl_open_stream(lsl_inlet in, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { in->open_stream(timeout); } LSL_STORE_EXCEPTION_IN(ec) } LIBLSL_C_API void lsl_close_stream(lsl_inlet in) { try { in->close_stream(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error in %s: %s", __func__, e.what()); } } LIBLSL_C_API double lsl_time_correction(lsl_inlet in, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { return in->time_correction(timeout); } LSL_STORE_EXCEPTION_IN(ec) return 0.0; } LIBLSL_C_API double lsl_time_correction_ex( lsl_inlet in, double *remote_time, double *uncertainty, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { double correction = in->time_correction(remote_time, uncertainty, timeout); return correction; } LSL_STORE_EXCEPTION_IN(ec) return 0.0; } LIBLSL_C_API int32_t lsl_set_postprocessing(lsl_inlet in, uint32_t flags) { try { in->set_postprocessing(flags); return lsl_no_error; } catch (std::invalid_argument &) { return lsl_argument_error; } catch (std::exception &) { return lsl_internal_error; } } /* === Pulling a sample from the inlet === */ LIBLSL_C_API double lsl_pull_sample_f( lsl_inlet in, float *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_d( lsl_inlet in, double *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_l( lsl_inlet in, int64_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_i( lsl_inlet in, int32_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_s( lsl_inlet in, int16_t *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_c( lsl_inlet in, char *buffer, int32_t buffer_elements, double timeout, int32_t *ec) { return in->pull_sample_noexcept(buffer, buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API double lsl_pull_sample_str( lsl_inlet in, char **buffer, int32_t buffer_elements, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { // capture output in a temporary string buffer std::vector tmp; double result = in->pull_sample(tmp, timeout); if (buffer_elements < (int)tmp.size()) throw std::range_error( "The provided buffer has fewer elements than the stream's number of channels."); // allocate memory and copy over into buffer for (std::size_t k = 0; k < tmp.size(); k++) { buffer[k] = (char *)malloc(tmp[k].size() + 1); if (buffer[k] == nullptr) { for (std::size_t k2 = 0; k2 < k; k2++) free(buffer[k2]); if (ec) *ec = lsl_internal_error; return 0.0; } memcpy(buffer[k], tmp[k].data(), tmp[k].size()); buffer[k][tmp[k].size()] = '\0'; } return result; } LSL_STORE_EXCEPTION_IN(ec) return 0.0; } LIBLSL_C_API double lsl_pull_sample_buf(lsl_inlet in, char **buffer, uint32_t *buffer_lengths, int32_t buffer_elements, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { // capture output in a temporary string buffer std::vector tmp; double result = in->pull_sample(tmp, timeout); if (buffer_elements < (int)tmp.size()) throw std::range_error( "The provided buffer has fewer elements than the stream's number of channels."); // allocate memory and copy over into buffer for (std::size_t k = 0; k < tmp.size(); k++) { buffer[k] = (char *)malloc(tmp[k].size()); if (buffer[k] == nullptr) { for (std::size_t k2 = 0; k2 < k; k2++) free(buffer[k2]); if (ec) *ec = lsl_internal_error; return 0.0; } buffer_lengths[k] = (uint32_t)tmp[k].size(); memcpy(buffer[k], tmp[k].data(), tmp[k].size()); } return result; } LSL_STORE_EXCEPTION_IN(ec) return 0.0; } LIBLSL_C_API double lsl_pull_sample_v( lsl_inlet in, void *buffer, int32_t buffer_bytes, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { return in->pull_numeric_raw(buffer, buffer_bytes, timeout); } LSL_STORE_EXCEPTION_IN(ec) return 0.0; } LIBLSL_C_API unsigned long lsl_pull_chunk_f(lsl_inlet in, float *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_d(lsl_inlet in, double *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_l(lsl_inlet in, int64_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_i(lsl_inlet in, int32_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_s(lsl_inlet in, int16_t *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_c(lsl_inlet in, char *data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { return in->pull_chunk_multiplexed_noexcept(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout, (lsl_error_code_t *)ec); } LIBLSL_C_API unsigned long lsl_pull_chunk_str(lsl_inlet in, char **data_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { // capture output in a temporary string buffer if (data_buffer_elements) { std::vector tmp(data_buffer_elements); uint32_t result = in->pull_chunk_multiplexed(tmp.data(), timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout); // allocate memory and copy over into buffer for (std::size_t k = 0; k < tmp.size(); k++) { data_buffer[k] = (char *)malloc(tmp[k].size() + 1); if (data_buffer[k] == nullptr) { for (std::size_t k2 = 0; k2 < k; k2++) free(data_buffer[k2]); if (ec) *ec = lsl_internal_error; return 0; } memcpy(data_buffer[k], tmp[k].data(), tmp[k].size()); data_buffer[k][tmp[k].size()] = '\0'; } return result; } return 0; } LSL_STORE_EXCEPTION_IN(ec) return 0; } LIBLSL_C_API unsigned long lsl_pull_chunk_buf(lsl_inlet in, char **data_buffer, uint32_t *lengths_buffer, double *timestamp_buffer, unsigned long data_buffer_elements, unsigned long timestamp_buffer_elements, double timeout, int32_t *ec) { if (ec) *ec = lsl_no_error; try { // capture output in a temporary string buffer if (data_buffer_elements) { std::vector tmp(data_buffer_elements); uint32_t result = in->pull_chunk_multiplexed(tmp.data(), timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout); // allocate memory and copy over into buffer for (uint32_t k = 0; k < tmp.size(); k++) { data_buffer[k] = (char *)malloc(tmp[k].size() + 1); if (data_buffer[k] == nullptr) { for (uint32_t k2 = 0; k2 < k; k2++) free(data_buffer[k2]); if (ec) *ec = lsl_internal_error; return 0; } lengths_buffer[k] = (uint32_t)tmp[k].size(); memcpy(data_buffer[k], tmp[k].data(), tmp[k].size()); data_buffer[k][tmp[k].size()] = '\0'; } return result; } return 0; } LSL_STORE_EXCEPTION_IN(ec) return 0; } LIBLSL_C_API uint32_t lsl_samples_available(lsl_inlet in) { try { return (uint32_t)in->samples_available(); } catch (std::exception &) { return 0; } } LIBLSL_C_API uint32_t lsl_inlet_flush(lsl_inlet in) { return in->flush(); } LIBLSL_C_API uint32_t lsl_was_clock_reset(lsl_inlet in) { try { return (uint32_t)in->was_clock_reset(); } catch (std::exception &) { return 0; } } LIBLSL_C_API int32_t lsl_smoothing_halftime(lsl_inlet in, float value) { try { in->smoothing_halftime(value); return lsl_no_error; } catch (std::invalid_argument &) { return lsl_argument_error; } catch (std::exception &) { return lsl_internal_error; } } } liblsl-1.17.7/src/lsl_outlet_c.cpp000066400000000000000000000374141517625163100170730ustar00rootroot00000000000000#include "lsl_c_api_helpers.hpp" #include "stream_outlet_impl.h" #include #include #include #include #include #include extern "C" { #include "api_types.hpp" // include api_types before public API header #include "../include/lsl/outlet.h" using namespace lsl; // boilerplate wrapper code LIBLSL_C_API lsl_outlet lsl_create_outlet_ex( lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered, lsl_transport_options_t flags) { return create_object_noexcept(*info, chunk_size, max_buffered, flags); } LIBLSL_C_API lsl_outlet lsl_create_outlet( lsl_streaminfo info, int32_t chunk_size, int32_t max_buffered) { return lsl_create_outlet_ex(info, chunk_size, max_buffered, transp_default); } LIBLSL_C_API void lsl_destroy_outlet(lsl_outlet out) { try { delete out; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during deletion of stream outlet: %s", e.what()); } } LIBLSL_C_API int32_t lsl_push_sample_f(lsl_outlet out, const float *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_ft(lsl_outlet out, const float *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_ftp( lsl_outlet out, const float *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_d(lsl_outlet out, const double *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_dt(lsl_outlet out, const double *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_dtp( lsl_outlet out, const double *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_l(lsl_outlet out, const int64_t *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_lt(lsl_outlet out, const int64_t *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_ltp( lsl_outlet out, const int64_t *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_i(lsl_outlet out, const int32_t *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_it(lsl_outlet out, const int32_t *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_itp( lsl_outlet out, const int32_t *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_s(lsl_outlet out, const int16_t *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_st(lsl_outlet out, const int16_t *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_stp( lsl_outlet out, const int16_t *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_c(lsl_outlet out, const char *data) { return out->push_sample_noexcept(data); } LIBLSL_C_API int32_t lsl_push_sample_ct(lsl_outlet out, const char *data, double timestamp) { return out->push_sample_noexcept(data, timestamp); } LIBLSL_C_API int32_t lsl_push_sample_ctp( lsl_outlet out, const char *data, double timestamp, int32_t pushthrough) { return out->push_sample_noexcept(data, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_sample_v(lsl_outlet out, const void *data) { return lsl_push_sample_vtp(out, data, 0.0, true); } LIBLSL_C_API int32_t lsl_push_sample_vt(lsl_outlet out, const void *data, double timestamp) { return lsl_push_sample_vtp(out, data, timestamp, true); } LIBLSL_C_API int32_t lsl_push_sample_vtp( lsl_outlet out, const void *data, double timestamp, int32_t pushthrough) { try { out->push_numeric_raw(data, timestamp, pushthrough != 0); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_push_sample_str(lsl_outlet out, const char **data) { return lsl_push_sample_strtp(out, data, 0.0, true); } LIBLSL_C_API int32_t lsl_push_sample_strt(lsl_outlet out, const char **data, double timestamp) { return lsl_push_sample_strtp(out, data, timestamp, true); } LIBLSL_C_API int32_t lsl_push_sample_strtp( lsl_outlet out, const char **data, double timestamp, int32_t pushthrough) { try { stream_outlet_impl *outimpl = out; std::vector tmp; for (uint32_t k = 0; k < (uint32_t)outimpl->info().channel_count(); k++) tmp.emplace_back(data[k]); return outimpl->push_sample_noexcept(tmp.data(), timestamp, pushthrough != 0); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during push_sample: %s", e.what()); return lsl_internal_error; } } LIBLSL_C_API int32_t lsl_push_sample_buf( lsl_outlet out, const char **data, const uint32_t *lengths) { return lsl_push_sample_buftp(out, data, lengths, 0.0, true); } LIBLSL_C_API int32_t lsl_push_sample_buft( lsl_outlet out, const char **data, const uint32_t *lengths, double timestamp) { return lsl_push_sample_buftp(out, data, lengths, timestamp, true); } LIBLSL_C_API int32_t lsl_push_sample_buftp(lsl_outlet out, const char **data, const uint32_t *lengths, double timestamp, int32_t pushthrough) { try { stream_outlet_impl *outimpl = out; std::vector tmp; for (uint32_t k = 0; k < (uint32_t)outimpl->info().channel_count(); k++) tmp.emplace_back(data[k], lengths[k]); return outimpl->push_sample_noexcept(tmp.data(), timestamp, pushthrough); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during push_sample: %s", e.what()); return lsl_internal_error; } } LIBLSL_C_API int32_t lsl_push_chunk_f( lsl_outlet out, const float *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_d( lsl_outlet out, const double *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_l( lsl_outlet out, const int64_t *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_i( lsl_outlet out, const int32_t *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_s( lsl_outlet out, const int16_t *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_c( lsl_outlet out, const char *data, unsigned long data_elements) { return out->push_chunk_multiplexed_noexcept(data, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_ft( lsl_outlet out, const float *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_dt( lsl_outlet out, const double *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_lt( lsl_outlet out, const int64_t *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_it( lsl_outlet out, const int32_t *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_st( lsl_outlet out, const int16_t *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_ct( lsl_outlet out, const char *data, unsigned long data_elements, double timestamp) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp); } LIBLSL_C_API int32_t lsl_push_chunk_ftp(lsl_outlet out, const float *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_dtp(lsl_outlet out, const double *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_ltp(lsl_outlet out, const int64_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_itp(lsl_outlet out, const int32_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_stp(lsl_outlet out, const int16_t *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_ctp(lsl_outlet out, const char *data, unsigned long data_elements, double timestamp, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, data_elements, timestamp, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_ftn( lsl_outlet out, const float *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_dtn( lsl_outlet out, const double *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_ltn( lsl_outlet out, const int64_t *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_itn( lsl_outlet out, const int32_t *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_stn( lsl_outlet out, const int16_t *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_ctn( lsl_outlet out, const char *data, unsigned long data_elements, const double *timestamps) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements); } LIBLSL_C_API int32_t lsl_push_chunk_ftnp(lsl_outlet out, const float *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_dtnp(lsl_outlet out, const double *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_ltnp(lsl_outlet out, const int64_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_itnp(lsl_outlet out, const int32_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_stnp(lsl_outlet out, const int16_t *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_ctnp(lsl_outlet out, const char *data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { return out->push_chunk_multiplexed_noexcept(data, timestamps, data_elements, pushthrough); } LIBLSL_C_API int32_t lsl_push_chunk_str( lsl_outlet out, const char **data, unsigned long data_elements) { return lsl_push_chunk_strtp(out, data, data_elements, 0.0, true); } LIBLSL_C_API int32_t lsl_push_chunk_strt( lsl_outlet out, const char **data, unsigned long data_elements, double timestamp) { return lsl_push_chunk_strtp(out, data, data_elements, timestamp, true); } LIBLSL_C_API int32_t lsl_push_chunk_strtp(lsl_outlet out, const char **data, unsigned long data_elements, double timestamp, int32_t pushthrough) { try { std::vector tmp; for (unsigned long k = 0; k < data_elements; k++) tmp.emplace_back(data[k]); if (data_elements) out->push_chunk_multiplexed(tmp.data(), tmp.size(), timestamp, pushthrough); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_push_chunk_strtn( lsl_outlet out, const char **data, unsigned long data_elements, const double *timestamps) { return lsl_push_chunk_strtnp(out, data, data_elements, timestamps, true); } LIBLSL_C_API int32_t lsl_push_chunk_strtnp(lsl_outlet out, const char **data, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { try { if (data_elements) { std::vector tmp; for (unsigned long k = 0; k < data_elements; k++) tmp.emplace_back(data[k]); out->push_chunk_multiplexed_noexcept( tmp.data(), timestamps, data_elements, pushthrough); } } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_push_chunk_buf( lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements) { return lsl_push_chunk_buftp(out, data, lengths, data_elements, 0.0, true); } LIBLSL_C_API int32_t lsl_push_chunk_buft(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, double timestamp) { return lsl_push_chunk_buftp(out, data, lengths, data_elements, timestamp, true); } LIBLSL_C_API int32_t lsl_push_chunk_buftp(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, double timestamp, int32_t pushthrough) { try { std::vector tmp; for (unsigned long k = 0; k < data_elements; k++) tmp.emplace_back(data[k], lengths[k]); if (data_elements) out->push_chunk_multiplexed(tmp.data(), tmp.size(), timestamp, pushthrough); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_push_chunk_buftn(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, const double *timestamps) { return lsl_push_chunk_buftnp(out, data, lengths, data_elements, timestamps, true); } LIBLSL_C_API int32_t lsl_push_chunk_buftnp(lsl_outlet out, const char **data, const uint32_t *lengths, unsigned long data_elements, const double *timestamps, int32_t pushthrough) { try { if (data_elements) { std::vector tmp; for (unsigned long k = 0; k < data_elements; k++) tmp.emplace_back(data[k], lengths[k]); out->push_chunk_multiplexed( tmp.data(), timestamps, (std::size_t)data_elements, pushthrough); } } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_have_consumers(lsl_outlet out) { try { return out->have_consumers(); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error in have_consumers: %s", e.what()); return 1; } } LIBLSL_C_API int32_t lsl_wait_for_consumers(lsl_outlet out, double timeout) { try { return out->wait_for_consumers(timeout); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error in wait_for_consumers: %s", e.what()); return 1; } } LIBLSL_C_API lsl_streaminfo lsl_get_info(lsl_outlet out) { return create_object_noexcept(out->info()); } } liblsl-1.17.7/src/lsl_resolver_c.cpp000066400000000000000000000066071517625163100174200ustar00rootroot00000000000000#include "api_config.h" #include "lsl_c_api_helpers.hpp" #include "resolver_impl.h" #include #include #include #include #include extern "C" { #include "api_types.hpp" // include api_types before public API header #include "../include/lsl/resolver.h" using namespace lsl; LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver(double forget_after) { return resolver_impl::create_resolver(forget_after); } LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_byprop( const char *prop, const char *value, double forget_after) { return resolver_impl::create_resolver(forget_after, prop, value); } LIBLSL_C_API lsl_continuous_resolver lsl_create_continuous_resolver_bypred( const char *pred, double forget_after) { return resolver_impl::create_resolver(forget_after, pred, nullptr); } LIBLSL_C_API int32_t lsl_resolver_results( lsl_continuous_resolver res, lsl_streaminfo *buffer, uint32_t buffer_elements) { try { std::vector tmp = res->results(buffer_elements); // allocate new stream_info_impl's and assign to the buffer for (uint32_t k = 0; k < tmp.size(); k++) buffer[k] = new stream_info_impl(tmp[k]); return static_cast(tmp.size()); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API void lsl_destroy_continuous_resolver(lsl_continuous_resolver res) { try { delete res; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected during destruction of a continuous_resolver: %s", e.what()); } } LIBLSL_C_API int32_t lsl_resolve_all( lsl_streaminfo *buffer, uint32_t buffer_elements, double wait_time) { try { // create a new resolver resolver_impl resolver; // invoke it (our only constraint is that the session id shall be the same as ours) std::string sess_id = api_config::get_instance()->session_id(); std::vector tmp = resolver.resolve_oneshot((std::string("session_id='") += sess_id) += "'", 0, wait_time); // allocate new stream_info_impl's and assign to the buffer uint32_t result = buffer_elements < tmp.size() ? buffer_elements : (uint32_t)tmp.size(); for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]); return static_cast(result); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_resolve_byprop(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *prop, const char *value, int32_t minimum, double timeout) { try { std::string query{resolver_impl::build_query(prop, value)}; auto tmp = resolver_impl().resolve_oneshot(query, minimum, timeout); // allocate new stream_info_impl's and assign to the buffer uint32_t result = buffer_elements < tmp.size() ? buffer_elements : (uint32_t)tmp.size(); for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]); return static_cast(result); } LSL_RETURN_CAUGHT_EC; } LIBLSL_C_API int32_t lsl_resolve_bypred(lsl_streaminfo *buffer, uint32_t buffer_elements, const char *pred, int32_t minimum, double timeout) { try { std::string query{resolver_impl::build_query(pred)}; auto tmp = resolver_impl().resolve_oneshot(query, minimum, timeout); // allocate new stream_info_impl's and assign to the buffer uint32_t result = buffer_elements < tmp.size() ? buffer_elements : (uint32_t)tmp.size(); for (uint32_t k = 0; k < result; k++) buffer[k] = new stream_info_impl(tmp[k]); return static_cast(result); } LSL_RETURN_CAUGHT_EC; } } liblsl-1.17.7/src/lsl_streaminfo_c.cpp000066400000000000000000000064051517625163100177220ustar00rootroot00000000000000#include "lsl_c_api_helpers.hpp" #include "common.h" #include "stream_info_impl.h" #include #include #include #include #include #include #include extern "C" { #include "api_types.hpp" // include api_types before public API header #include "../include/lsl/streaminfo.h" using namespace lsl; // boilerplate wrapper code LIBLSL_C_API lsl_streaminfo lsl_create_streaminfo(const char *name, const char *type, int32_t channel_count, double nominal_srate, lsl_channel_format_t channel_format, const char *source_id) { return create_object_noexcept( name, type, channel_count, nominal_srate, channel_format, source_id); } LIBLSL_C_API lsl_streaminfo lsl_copy_streaminfo(lsl_streaminfo info) { return create_object_noexcept(*info); } LIBLSL_C_API void lsl_destroy_streaminfo(lsl_streaminfo info) { try { delete info; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while destroying a streaminfo: %s", e.what()); } } LIBLSL_C_API const char *lsl_get_type(lsl_streaminfo info) { return info->type().c_str(); } LIBLSL_C_API const char *lsl_get_name(lsl_streaminfo info) { return info->name().c_str(); } LIBLSL_C_API int32_t lsl_get_channel_count(lsl_streaminfo info) { return info->channel_count(); } LIBLSL_C_API double lsl_get_nominal_srate(lsl_streaminfo info) { return info->nominal_srate(); } LIBLSL_C_API lsl_channel_format_t lsl_get_channel_format(lsl_streaminfo info) { return info->channel_format(); } LIBLSL_C_API const char *lsl_get_source_id(lsl_streaminfo info) { return info->source_id().c_str(); } LIBLSL_C_API int32_t lsl_get_version(lsl_streaminfo info) { return info->version(); } LIBLSL_C_API double lsl_get_created_at(lsl_streaminfo info) { return info->created_at(); } LIBLSL_C_API const char *lsl_get_uid(lsl_streaminfo info) { return info->uid().c_str(); } LIBLSL_C_API const char *lsl_get_session_id(lsl_streaminfo info) { return info->session_id().c_str(); } LIBLSL_C_API const char *lsl_get_hostname(lsl_streaminfo info) { return info->hostname().c_str(); } LIBLSL_C_API lsl_xml_ptr lsl_get_desc(lsl_streaminfo info) { return info->desc().internal_object(); } LIBLSL_C_API char *lsl_get_xml(lsl_streaminfo info) { try { std::string tmp = info->to_fullinfo_message(); char *result = (char *)malloc(tmp.size() + 1); if (result == nullptr) { LOG_F(ERROR, "Error allocating memory for xmlinfo"); return nullptr; } memcpy(result, tmp.data(), tmp.size()); result[tmp.size()] = '\0'; return result; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error in lsl_get_xml: %s", e.what()); return nullptr; } } LIBLSL_C_API int32_t lsl_get_channel_bytes(lsl_streaminfo info) { return info->channel_bytes(); } LIBLSL_C_API int32_t lsl_get_sample_bytes(lsl_streaminfo info) { return info->sample_bytes(); } LIBLSL_C_API int32_t lsl_stream_info_matches_query(lsl_streaminfo info, const char *query) { return info->matches_query(query); } LIBLSL_C_API lsl_streaminfo lsl_streaminfo_from_xml(const char *xml) { try { auto *impl = new stream_info_impl(); impl->from_fullinfo_message(xml); return impl; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during streaminfo construction: %s", e.what()); return nullptr; } } } liblsl-1.17.7/src/lsl_xml_element_c.cpp000066400000000000000000000063261517625163100200660ustar00rootroot00000000000000#include extern "C" { #include "api_types.hpp" // include api_types before public API header #include "../include/lsl/xml.h" using namespace pugi; LIBLSL_C_API lsl_xml_ptr lsl_first_child(lsl_xml_ptr e) { return xml_node(e).first_child().internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_last_child(lsl_xml_ptr e) { return xml_node(e).last_child().internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_next_sibling(lsl_xml_ptr e) { return xml_node(e).next_sibling().internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_previous_sibling(lsl_xml_ptr e) { return xml_node(e).previous_sibling().internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_parent(lsl_xml_ptr e) { return xml_node(e).parent().internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_child(lsl_xml_ptr e, const char *name) { return xml_node(e).child(name).internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_next_sibling_n(lsl_xml_ptr e, const char *name) { return xml_node(e).next_sibling(name).internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_previous_sibling_n(lsl_xml_ptr e, const char *name) { return xml_node(e).previous_sibling(name).internal_object(); } LIBLSL_C_API int32_t lsl_empty(lsl_xml_ptr e) { return xml_node(e).empty(); } LIBLSL_C_API int32_t lsl_is_text(lsl_xml_ptr e) { return xml_node(e).type() != node_element; } LIBLSL_C_API const char *lsl_name(lsl_xml_ptr e) { return xml_node(e).name(); } LIBLSL_C_API const char *lsl_value(lsl_xml_ptr e) { return xml_node(e).value(); } LIBLSL_C_API const char *lsl_child_value(lsl_xml_ptr e) { return xml_node(e).child_value(); } LIBLSL_C_API const char *lsl_child_value_n(lsl_xml_ptr e, const char *name) { return xml_node(e).child_value(name); } LIBLSL_C_API int32_t lsl_set_name(lsl_xml_ptr e, const char *rhs) { return xml_node(e).set_name(rhs); } LIBLSL_C_API int32_t lsl_set_value(lsl_xml_ptr e, const char *rhs) { return xml_node(e).set_value(rhs); } LIBLSL_C_API lsl_xml_ptr lsl_append_child(lsl_xml_ptr e, const char *name) { return xml_node(e).append_child(name).internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_prepend_child(lsl_xml_ptr e, const char *name) { return xml_node(e).prepend_child(name).internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_append_copy(lsl_xml_ptr e, lsl_xml_ptr e2) { return xml_node(e).append_copy(xml_node(e2)).internal_object(); } LIBLSL_C_API lsl_xml_ptr lsl_prepend_copy(lsl_xml_ptr e, lsl_xml_ptr e2) { return xml_node(e).prepend_copy(xml_node(e2)).internal_object(); } LIBLSL_C_API void lsl_remove_child_n(lsl_xml_ptr e, const char *name) { xml_node(e).remove_child(name); } LIBLSL_C_API void lsl_remove_child(lsl_xml_ptr e, lsl_xml_ptr e2) { xml_node(e).remove_child(xml_node(e2)); } LIBLSL_C_API int32_t lsl_set_child_value(lsl_xml_ptr e, const char *name, const char *value) { return xml_node(e).child(name).first_child().set_value(value); } LIBLSL_C_API lsl_xml_ptr lsl_append_child_value( lsl_xml_ptr e, const char *name, const char *value) { xml_node result = xml_node(e).append_child(name); result.append_child(node_pcdata).set_value(value); return e; } LIBLSL_C_API lsl_xml_ptr lsl_prepend_child_value( lsl_xml_ptr e, const char *name, const char *value) { xml_node result = xml_node(e).prepend_child(name); result.append_child(node_pcdata).set_value(value); return e; } } liblsl-1.17.7/src/netinterfaces.cpp000066400000000000000000000075371517625163100172400ustar00rootroot00000000000000#include "netinterfaces.h" #include #include asio::ip::address_v6 sinaddr_to_asio(sockaddr_in6 *addr) { asio::ip::address_v6::bytes_type buf; memcpy(buf.data(), addr->sin6_addr.s6_addr, sizeof(addr->sin6_addr)); return asio::ip::make_address_v6(buf, addr->sin6_scope_id); } #if defined(_WIN32) #undef UNICODE #include // Headers that need to be included after winsock2.h: #include #include typedef IP_ADAPTER_UNICAST_ADDRESS_LH Addr; typedef IP_ADAPTER_ADDRESSES *AddrList; std::vector lsl::get_local_interfaces() { // It's a windows machine, we assume it has 512KB free memory DWORD outBufLen = 1 << 19; AddrList ifaddrs = (AddrList) new char[outBufLen]; std::vector ret; ULONG res = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, ifaddrs, &outBufLen); if (res == NO_ERROR) { for (AddrList addr = ifaddrs; addr != 0; addr = addr->Next) { // Interface isn't up or doesn't support multicast? Skip it. LOG_F(INFO, "netif '%s' (status: %d, multicast: %d", addr->AdapterName, addr->OperStatus, !addr->NoMulticast); if (addr->OperStatus != IfOperStatusUp) continue; if (addr->NoMulticast) continue; lsl::netif if_; if_.name = addr->AdapterName; if_.ifindex = addr->IfIndex; // Find the first IPv4 address if (addr->Ipv4Enabled) { for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) { if (uaddr->Address.lpSockaddr->sa_family != AF_INET) continue; if_.addr = asio::ip::make_address_v4( ntohl(reinterpret_cast(uaddr->Address.lpSockaddr) ->sin_addr.s_addr)); ret.emplace_back(std::move(if_)); break; } } if (addr->Ipv6Enabled) { LOG_F(INFO, "\tIPv6 ifindex %d", if_.ifindex); for (Addr *uaddr = addr->FirstUnicastAddress; uaddr != 0; uaddr = uaddr->Next) { if (uaddr->Address.lpSockaddr->sa_family != AF_INET6) continue; if_.addr = sinaddr_to_asio( reinterpret_cast(uaddr->Address.lpSockaddr)); ret.emplace_back(std::move(if_)); break; } } } } else { LOG_F(ERROR, "Couldn't enumerate network interfaces: %d", res); } delete[]((char *)ifaddrs); return ret; } #elif defined(__APPLE__) || defined(__linux__) && (!defined(__ANDROID_API__) || __ANDROID_API__ >= 24) #include #include std::vector lsl::get_local_interfaces() { std::vector res; ifaddrs *ifs; if (getifaddrs(&ifs)) { LOG_F(ERROR, "Couldn't enumerate network interfaces: %d", errno); return res; } for (auto *addr = ifs; addr != nullptr; addr = addr->ifa_next) { // No address? Skip. if (addr->ifa_addr == nullptr) continue; LOG_F(1, "netif '%s' (status: %d, multicast: %d, broadcast: %d)", addr->ifa_name, addr->ifa_flags & IFF_UP, addr->ifa_flags & IFF_MULTICAST, addr->ifa_flags & IFF_BROADCAST); // Interface doesn't support multicast? Skip. if (!(addr->ifa_flags & IFF_MULTICAST)) continue; // Interface isn't active? Skip. if (!(addr->ifa_flags & IFF_UP)) continue; lsl::netif if_; if (addr->ifa_addr->sa_family == AF_INET) { if_.addr = asio::ip::make_address_v4( ntohl(reinterpret_cast(addr->ifa_addr)->sin_addr.s_addr)); LOG_F(1, "\tIPv4 addr: %x", if_.addr.to_v4().to_uint()); } else if (addr->ifa_addr->sa_family == AF_INET6) { if_.addr = sinaddr_to_asio(reinterpret_cast(addr->ifa_addr)); LOG_F(1, "\tIPv6 addr: %s", if_.addr.to_string().c_str()); } else continue; if_.ifindex = if_nametoindex(addr->ifa_name); res.emplace_back(std::move(if_)); } freeifaddrs(ifs); return res; } #else std::vector lsl::get_local_interfaces() { LOG_F(WARNING, "No implementation to enumerate network interfaces found."); return std::vector(); } #endif liblsl-1.17.7/src/netinterfaces.h000066400000000000000000000005311517625163100166700ustar00rootroot00000000000000#pragma once #include #include #include #include namespace lsl { class netif { public: asio::ip::address addr; uint32_t ifindex; std::string name; }; /// Enumerate all local interface addresses (IPv6 index, IPv4 address)-pairs std::vector get_local_interfaces(); } // namespace lsl liblsl-1.17.7/src/portable_archive/000077500000000000000000000000001517625163100171775ustar00rootroot00000000000000liblsl-1.17.7/src/portable_archive/change_log.txt000066400000000000000000000024451517625163100220330ustar00rootroot00000000000000IMPORTANT: This is the last release from me as EOS employee. I plan to contribute all of this work to the official boost libraries distribution and will continue to support users. Francois Mauger joined me recently and already added a valuable tutorial for you! Changelog: 07.11.2015 5.1 Small fix to get it running up to 1.59. 26.06.2012 5.0 Ported to boost versions up to 1.49. Added support for wstring, added tutorial by Francois Mauger. 01.04.2011 4.2 Ported to boost versions up to 1.46.1. Allow serialization of inf and nan values. 17.12.2009 4.1 Ported to boost versions up to 1.41. 4.0 Changes in inheritance make arrays work. 13.02.2009 3.1 Shared pointer serialization capabilities added Ported to recent boost versions (up to 1.38) 25.09.2008 3.0 Refactored, fixed and ported to recent boost versions Archives are now named eos::portable_[io]archive 17.09.2008 2.1 Improved floating point handling and error detection. Extracted the exception class into an extra file. 28.04.2008 2.0 Major Bugfix resolving negative number collision! 28.11.2007 1.1 Small Bugfix in portable_binary_archive_exception class: throwing specifiers did not match base class declaration 12.11.2007 1.0 Initial Release to boost-users! Christian Pfligersdorffer christian.pfligersdorffer@gmx.at liblsl-1.17.7/src/portable_archive/license.txt000066400000000000000000000024721517625163100213670ustar00rootroot00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. liblsl-1.17.7/src/portable_archive/portable_archive_exception.hpp000066400000000000000000000052631517625163100253050ustar00rootroot00000000000000/*****************************************************************************/ /** * \file portable_archive_exception.hpp * \brief Provides error handling and constants. * \author christian.pfligersdorffer@gmx.at * * Portable archive exceptions derive from the lslboost archive exceptions * and add failure causes specific to the portable binary usecase. * * Additionally this header serves as common include for important * constants or typedefs. */ /****************************************************************************/ #pragma once #include "slimarchive.hpp" namespace eos { // this value is written to the top of the stream const signed char magic_byte = 'e' | 'o' | 's'; // flag for fp serialization const unsigned no_infnan = 64; // integral type for the archive version using archive_version_type = lslboost::archive::library_version_type; // version of the linked lslboost archive library const archive_version_type archive_version(lslboost::archive::BOOST_ARCHIVE_VERSION()); /** * \brief Exception being thrown when serialization cannot proceed. * * There are several situations in which the portable archives may fail and * hence throw an exception: * -# deserialization of an integer value that exceeds the range of the type * -# (de)serialization of inf/nan through an archive with no_infnan flag set * -# deserialization of a denormalized value without the floating point type * supporting denormalized numbers * * Note that this exception will also be thrown if you mixed up your stream * position and accidentially interpret some value for size data (in this case * the reported size will be totally amiss most of the time). */ class portable_archive_exception : public lslboost::archive::archive_exception { std::string msg; public: //! type size is not large enough for deserialized number portable_archive_exception(signed char invalid_size) : lslboost::archive::archive_exception(other_exception), msg("requested integer size exceeds type size: ") { msg += std::to_string(invalid_size); } //! negative number in unsigned type portable_archive_exception() : lslboost::archive::archive_exception(other_exception), msg("cannot read a negative number into an unsigned type") {} //! serialization of inf, nan and denormals template portable_archive_exception(const T &abnormal) : lslboost::archive::archive_exception(other_exception), msg("serialization of illegal floating point value: ") { msg += std::to_string(abnormal); } //! override the base class function with our message const char *what() const noexcept override { return msg.c_str(); } ~portable_archive_exception() noexcept override = default; }; } // namespace eos liblsl-1.17.7/src/portable_archive/portable_archive_includes.hpp000066400000000000000000000153231517625163100251130ustar00rootroot00000000000000/*****************************************************************************/ /** * \brief Provides an archive to read from and create portable binary files. * \author christian.pfligersdorffer@gmx.at * \version 5.1+ * * This pair of archives brings the advantages of binary streams to the cross * platform lslboost::serialization user. While being almost as fast as the native * binary archive it allows its files to be exchanged between cpu architectures * using different byte order (endianness). Speaking of speed: in serializing * numbers the (portable) binary approach is approximately ten times faster than * the ascii implementation (that is inherently portable)! * * Based on the portable archive example by Robert Ramey this implementation * uses Beman Dawes endian library and fp_utilities from Johan Rade, both being * in boost since 1.36. Prior to that you need to add them both (header only) * to your boost directory before you're able to use the archives provided. * Our archives have been tested successfully for boost versions 1.33 to 1.49! * * \note This version adds support for the USE_SHRINKWRAPPED_BOOST define, * which allows to toggle between a vanilla boost distribution and one with * mangled names (custom to LSL). * * \note Correct behaviour has so far been confirmed using PowerPC-32, x86-32 * and x86-64 platforms featuring different byte order. So there is a good * chance it will instantly work for your specific setup. If you encounter * problems or have suggestions please contact the author. * * \note Version 5.1 is now compatible with boost up to version 1.59. Thanks to * ecotax for pointing to the issue with shared_ptr_helper. * * \note Version 5.0 is now compatible with boost up to version 1.49 and enables * serialization of std::wstring by converting it to/from utf8 (thanks to * Arash Abghari for this suggestion). With that all unit tests from the * serialization library pass again with the notable exception of user * defined primitive types. Those are not supported and as a result any * user defined type to be used with the portable archives are required * to be at least object_serializable. * * \note Oliver Putz pointed out that -0.0 was not serialized correctly, so * version 4.3 provides a fix for that. Thanks Ollie! * * \note Version 4.2 maintains compatibility with the latest boost 1.45 and adds * serialization of special floating point values inf and NaN as proposed * by Francois Mauger. * * \note Version 4.1 makes the archives work together with boost 1.40 and 1.41. * Thanks to Francois Mauger for his suggestions. * * \note Version 4 removes one level of the inheritance hierarchy and directly * builds upon binary primitive and basic binary archive, thereby fixing * the last open issue regarding array serialization. Thanks to Robert * Ramey for the hint. * * \note A few fixes introduced in version 3.1 let the archives pass all of the * serialization tests. Thanks to Sergey Morozov for running the tests. * Wouter Bijlsma pointed out where to find the fp_utilities and endian * libraries headers inside the boost distribution. I would never have * found them so thank him it works out of the box since boost 1.36. * * \note With Version 3.0 the archives have been made portable across different * boost versions. For that purpose a header is added to the data that * supplies the underlying serialization library version. Backwards * compatibility is maintained by assuming library version boost 1.33 if * the iarchive is created using the no_header flag. Whether a header is * present or not can be guessed by peeking into the stream: the header's * first byte is the magic number 127 coinciding with 'e'|'o'|'s' :-) * * \note Version 2.1 removes several compiler warnings and enhances floating * point diagnostics to inform the user if some preconditions are violated * on his platform. We do not strive for the universally portable solution * in binary floating point serialization as desired by some boost users. * Instead we support only the most widely used IEEE 754 format and try to * detect when requirements are not met and hence our approach must fail. * Contributions we made by Johan Rade and Ákos Maróy. * * \note Version 2.0 fixes a serious bug that effectively transformed most * of negative integral values into positive values! For example the two * numbers -12 and 234 were stored in the same 8-bit pattern and later * always restored to 234. This was fixed in this version in a way that * does not change the interpretation of existing archives that did work * because there were no negative numbers. The other way round archives * created by version 2.0 and containing negative numbers will raise an * integer type size exception when reading it with version 1.0. Thanks * to Markus Frohnmaier for testing the archives and finding the bug. * * \copyright The boost software license applies. */ /*****************************************************************************/ #pragma once // endian and fpclassify #include // generic type traits for numeric types #include "portable_archive_exception.hpp" #include #include #include namespace lsl { template int fpclassify(T val) { return std::fpclassify(val); } using std::isfinite; namespace detail { template struct fp_traits_consts {}; template <> struct fp_traits_consts { using bits = uint64_t; static constexpr bits sign = (0x80000000ull) << 32, exponent = (0x7ff00000ll) << 32, significand = ((0x000fffffll) << 32) + (0xfffffffful); static void get_bits(double src, bits &dst) { std::memcpy(&dst, &src, sizeof(src)); } static void set_bits(double &dst, bits src) { std::memcpy(&dst, &src, sizeof(src)); } }; template <> struct fp_traits_consts { using bits = uint32_t; static constexpr bits sign = 0x80000000u, exponent = 0x7f800000, significand = 0x007fffff; static void get_bits(float src, bits &dst) { std::memcpy(&dst, &src, sizeof(src)); } static void set_bits(float &dst, bits src) { std::memcpy(&dst, &src, sizeof(src)); } }; template struct fp_traits { using type = fp_traits_consts; static void get_bits(double src, typename type::bits &dst) { std::memcpy(&dst, &src, sizeof(src)); } static void set_bits(double &dst, typename type::bits src) { std::memcpy(&dst, &src, sizeof(src)); } }; } // namespace detail } // namespace lsl namespace fp = lsl; // namespace alias endian namespace endian = lslboost::endian; liblsl-1.17.7/src/portable_archive/portable_iarchive.hpp000066400000000000000000000207071517625163100234000ustar00rootroot00000000000000#pragma once #include "portable_archive_includes.hpp" #include "slimarchive.hpp" #include #include namespace eos { // forward declaration class portable_iarchive; using portable_iprimitive = lslboost::archive::basic_binary_iprimitive; /** * \brief Portable binary input archive using little endian format. * * This archive addresses integer size, endianness and floating point types so * that data can be transferred across different systems. There may still be * constraints as to what systems are compatible and the user will have to take * care that e.g. a very large int being saved on a 64 bit machine will result * in a portable_archive_exception if loaded into an int on a 32 bit system. * A possible workaround to this would be to use fixed types like * lslboost::uint64_t in your serialization structures. * * \note The class is based on the portable binary example by Robert Ramey and * uses Beman Dawes endian library plus fp_utilities by Johan Rade. */ class portable_iarchive : public portable_iprimitive // the example derives from common_oarchive but that lacks the // load_override functions so we chose to stay one level higher , public lslboost::archive::basic_binary_iarchive { // only needed for Robert's hack in basic_binary_iarchive::init friend class lslboost::archive::basic_binary_iarchive; // workaround for gcc: use a dummy struct // as additional argument type for overloading template struct dummy { dummy(int /*unused*/) {} }; // loads directly from stream inline signed char load_signed_char() { signed char c; portable_iprimitive::load(c); return c; } // archive initialization void init(unsigned flags) { using namespace lslboost::archive; archive_version_type input_library_version(3); // it is vital to have version information! // if we don't have any we assume boost 1.33 if (flags & no_header) set_library_version(input_library_version); // extract and check the magic eos byte else if (load_signed_char() != magic_byte) throw archive_exception(archive_exception::invalid_signature); else { // extract version information operator>>(input_library_version); // throw if file version is newer than we are if (input_library_version > archive_version) throw archive_exception(archive_exception::unsupported_version); // else set the library version accordingly else set_library_version(input_library_version); } } public: /** * \brief Constructor on a stream using ios::binary mode! * * We cannot call basic_binary_iprimitive::init which tries to detect * if the binary archive stems from a different platform by examining * type sizes. * * We could have called basic_binary_iarchive::init which would create * the lslboost::serialization standard archive header containing also the * library version. Due to efficiency we stick with our own. */ portable_iarchive(std::istream &is, unsigned flags = 0) : portable_iprimitive(*is.rdbuf(), flags & lslboost::archive::no_codecvt), lslboost::archive::basic_binary_iarchive(flags) { init(flags); } portable_iarchive(std::streambuf &sb, unsigned flags = 0) : portable_iprimitive(sb, flags & lslboost::archive::no_codecvt), lslboost::archive::basic_binary_iarchive(flags) { init(flags); } //! Load narrow strings. void load(std::string &s) { portable_iprimitive::load(s); } /** * \brief Loading bool type. * * Byte pattern is same as with integer types, so this function * is somewhat redundant but treating bool as integer generates * a lot of compiler warnings. * * \note If you cannot compile your application and it says something * about load(bool) cannot convert your type A& into bool& then you * should check your BOOST_CLASS_IMPLEMENTATION setting for A, as * portable_archive is not able to handle custom primitive types in * a general manner. */ void load(bool &b) { switch (signed char c = load_signed_char()) { case 0: b = false; break; case 1: b = load_signed_char(); break; default: throw portable_archive_exception(c); } } /** * \brief Load integer types. * * First we load the size information ie. the number of bytes that * hold the actual data. Then we retrieve the data and transform it * to the original value by using load_little_endian. */ template typename std::enable_if::value>::type load(T &t, dummy<2> = 0) { // get the number of bytes in the stream if (signed char size = load_signed_char()) { // check for negative value in unsigned type if (size < 0 && std::is_unsigned::value) throw portable_archive_exception(); // check that our type T is large enough else if ((unsigned)abs(size) > sizeof(T)) throw portable_archive_exception(size); // reconstruct the value T temp = size < 0 ? -1 : 0; load_binary(&temp, abs(size)); // load the value from little endian - it is then converted // to the target type T and fits it because size <= sizeof(T) t = endian::native_to_little(temp); } else t = 0; // zero optimization } /** * \brief Load floating point types. * * We simply rely on fp_traits to set the bit pattern from the (unsigned) * integral type that was stored in the stream. Francois Mauger provided * standardized behaviour for special values like inf and NaN, that need to * be serialized in his application. * * \note by Johan Rade (author of the floating point utilities library): * Be warned that the math::detail::fp_traits::type::get_bits() function * is *not* guaranteed to give you all bits of the floating point number. It * will give you all bits if and only if there is an integer type that has * the same size as the floating point you are copying from. It will not * give you all bits for double if there is no uint64_t. It will not give * you all bits for long double if sizeof(long double) > 8 or there is no * uint64_t. * * The member fp_traits::type::coverage will tell you whether all bits * are copied. This is a typedef for either math::detail::all_bits or * math::detail::not_all_bits. * * If the function does not copy all bits, then it will copy the most * significant bits. So if you serialize and deserialize the way you * describe, and fp_traits::type::coverage is math::detail::not_all_bits, * then your floating point numbers will be truncated. This will introduce * small rounding off errors. */ template typename std::enable_if::value>::type load(T &t, dummy<3> = 0) { using traits = typename fp::detail::fp_traits::type; // if you end here there are three possibilities: // 1. you're serializing a long double which is not portable // 2. you're serializing a double but have no 64 bit integer // 3. your machine is using an unknown floating point format // after reading the note above you still might decide to // deactivate this static assert and try if it works out. typename traits::bits bits; static_assert(sizeof(bits) == sizeof(T), "mismatching type sizes"); static_assert(std::numeric_limits::is_iec559, "floating point type does not conform to IEC 559 (IEEE 754)"); load(bits); traits::set_bits(t, bits); // if the no_infnan flag is set we must throw here if (get_flags() & no_infnan && !fp::isfinite(t)) throw portable_archive_exception(t); // if you end here your floating point type does not support // denormalized numbers. this might be the case even though // your type conforms to IEC 559 (and thus to IEEE 754) if (std::numeric_limits::has_denorm == std::denorm_absent && fp::fpclassify(t) == (int)FP_SUBNORMAL) // GCC4 throw portable_archive_exception(t); } // in boost 1.44 version_type was splitted into library_version_type and // item_version_type, plus a whole bunch of additional strong typedefs. template typename std::enable_if::value>::type load(T &t, dummy<4> = 0) { // we provide a generic load routine for all types that feature // conversion operators into an unsigned integer value like those // created through BOOST_STRONG_TYPEDEF(X, some unsigned int) like // library_version_type, collection_size_type, item_version_type, // class_id_type, object_id_type, version_type and tracking_type load((typename lslboost::uint_t::least &)(t)); } }; } // namespace eos liblsl-1.17.7/src/portable_archive/portable_oarchive.hpp000066400000000000000000000212541517625163100234040ustar00rootroot00000000000000#pragma once #include "portable_archive_includes.hpp" #include "slimarchive.hpp" #include #include namespace eos { // IMPORTANT: We are fixing the lslboost serialization archive version // at 9; if you upgrade your lslboost distribution // you may at some point pull in a breaking change which // will break LSL protocol version 1.00 compatibility. // This does not affect LSL protocols 1.10 or later. const archive_version_type FIXED_VERSION = archive_version_type(9); // forward declaration class portable_oarchive; using portable_oprimitive = lslboost::archive::basic_binary_oprimitive; /** * \brief Portable binary output archive using little endian format. * * This archive addresses integer size, endianness and floating point types so * that data can be transferred across different systems. The archive consists * primarily of three different save implementations for integral types, * floating point types and string types. Those functions are templates and use * enable_if to be correctly selected for overloading. * * \note The class is based on the portable binary example by Robert Ramey and * uses Beman Dawes endian library plus fp_utilities by Johan Rade. */ class portable_oarchive : public portable_oprimitive // the example derives from common_oarchive but that lacks the // save_override functions so we chose to stay one level higher , public lslboost::archive::basic_binary_oarchive { // workaround for gcc: use a dummy struct // as additional argument type for overloading template struct dummy { dummy(int) {} }; // stores a signed char directly to stream inline void save_signed_char(const signed char &c) { portable_oprimitive::save(c); } // archive initialization void init(unsigned flags) { // it is vital to have version information if the archive is // to be parsed with a newer version of lslboost::serialization // therefor we create a header, no header means lslboost 1.33 if (flags & lslboost::archive::no_header) assert(archive_version == 3); else { // write our minimalistic header (magic byte plus version) // the lslboost archives write a string instead - by calling // lslboost::archive::basic_binary_oarchive::init() save_signed_char(magic_byte); // write current version // save(archive_version); operator<<(FIXED_VERSION); } } public: /** * \brief Constructor on a stream using ios::binary mode! * * We cannot call basic_binary_oprimitive::init which stores type * sizes to the archive in order to detect transfers to non-compatible * platforms. * * We could have called basic_binary_oarchive::init which would create * the lslboost::serialization standard archive header containing also the * library version. Due to efficiency we stick with our own. */ portable_oarchive(std::ostream &os, unsigned flags = 0) : portable_oprimitive(*os.rdbuf(), flags & lslboost::archive::no_codecvt), lslboost::archive::basic_binary_oarchive(flags) { init(flags); } portable_oarchive(std::streambuf &sb, unsigned flags = 0) : portable_oprimitive(sb, flags & lslboost::archive::no_codecvt), lslboost::archive::basic_binary_oarchive(flags) { init(flags); } //! Save narrow strings. void save(const std::string &s) { portable_oprimitive::save(s); } /** * \brief Saving bool type. * * Saving bool directly, not by const reference * because of tracking_type's operator (bool). * * \note If you cannot compile your application and it says something * about save(bool) cannot convert your type const A& into bool then * you should check your BOOST_CLASS_IMPLEMENTATION setting for A, as * portable_archive is not able to handle custom primitive types in * a general manner. */ void save(const bool &b) { save_signed_char(b); if (b) save_signed_char('T'); } /** * \brief Save integer types. * * First we save the size information ie. the number of bytes that hold the * actual data. We subsequently transform the data using store_little_endian * and store non-zero bytes to the stream. */ template typename std::enable_if::value>::type save(const T &t, dummy<2> = 0) { if (T temp = t) { // examine the number of bytes // needed to represent the number signed char size = 0; if (sizeof(T) == 1) size = 1; else { do { temp >>= CHAR_BIT; ++size; } while (temp != 0 && temp != (T)-1); } // encode the sign bit into the size save_signed_char(t > 0 ? size : -size); BOOST_ASSERT(t > 0 || std::is_signed::value); // we choose to use little endian because this way we just // save the first size bytes to the stream and skip the rest temp = endian::native_to_little(t); save_binary(&temp, size); } // zero optimization else save_signed_char(0); } /** * \brief Save floating point types. * * We simply rely on fp_traits to extract the bit pattern into an (unsigned) * integral type and store that into the stream. Francois Mauger provided * standardized behaviour for special values like inf and NaN, that need to * be serialized in his application. * * \note by Johan Rade (author of the floating point utilities library): * Be warned that the math::detail::fp_traits::type::get_bits() function * is *not* guaranteed to give you all bits of the floating point number. It * will give you all bits if and only if there is an integer type that has * the same size as the floating point you are copying from. It will not * give you all bits for double if there is no uint64_t. It will not give * you all bits for long double if sizeof(long double) > 8 or there is no * uint64_t. * * The member fp_traits::type::coverage will tell you whether all bits * are copied. This is a typedef for either math::detail::all_bits or * math::detail::not_all_bits. * * If the function does not copy all bits, then it will copy the most * significant bits. So if you serialize and deserialize the way you * describe, and fp_traits::type::coverage is math::detail::not_all_bits, * then your floating point numbers will be truncated. This will introduce * small rounding off errors. */ template typename std::enable_if::value>::type save(const T &t, dummy<3> = 0) { using traits = typename fp::detail::fp_traits::type; using newtraits = typename lsl::detail::fp_traits::type; static_assert(std::is_same::value, "Wrong corresponding int type"); // if the no_infnan flag is set we must throw here if (get_flags() & no_infnan && !fp::isfinite(t)) throw portable_archive_exception(t); // if you end here there are three possibilities: // 1. you're serializing a long double which is not portable // 2. you're serializing a double but have no 64 bit integer // 3. your machine is using an unknown floating point format // after reading the note above you still might decide to // deactivate this static assert and try if it works out. typename traits::bits bits; static_assert(sizeof(bits) == sizeof(T), "mismatching type sizes"); static_assert( std::numeric_limits::is_iec559, "float representation differs from IEC559"); static_assert(traits::exponent == newtraits::exponent, "mismatching exponent"); static_assert(traits::significand == newtraits::significand, "mismatching significand"); static_assert(traits::sign == newtraits::sign, "mismatching sign bit"); switch (fp::fpclassify(t)) { case FP_NAN: bits = traits::exponent | traits::significand; break; case FP_INFINITE: bits = traits::exponent | (t < 0) * traits::sign; break; case FP_SUBNORMAL: assert(std::numeric_limits::has_denorm); // pass case FP_ZERO: // note that floats can be ±0.0 case FP_NORMAL: traits::get_bits(t, bits); break; default: throw portable_archive_exception(t); } save(bits); } // in lslboost 1.44 version_type was splitted into library_version_type and // item_version_type, plus a whole bunch of additional strong typedefs. template typename std::enable_if::value>::type save(const T &t, dummy<4> = 0) { // we provide a generic save routine for all types that feature // conversion operators into an unsigned integer value like those // created through BOOST_STRONG_TYPEDEF(X, some unsigned int) like // library_version_type, collection_size_type, item_version_type, // class_id_type, object_id_type, version_type and tracking_type save((typename lslboost::uint_t::least)(t)); } }; } // namespace eos liblsl-1.17.7/src/portable_archive/release_notes.txt000066400000000000000000000027761517625163100226040ustar00rootroot00000000000000IMPORTANT: This is the last release from me as EOS employee. I plan to contribute all of this work to the official boost libraries distribution and will continue to support users. Francois Mauger joined me recently and already added a valuable tutorial for you! Dear user, I am proud to announce the very first release of our portable_binary_[io]archive which we use here at EOS to move data between different platforms. It really is a conglomerate of pieces that already were there - as is most often the case in OO world - we simply put them together in a way that seemed to make sense. I know a lot of people were interested in portable binary archives, so here you are - give it a try and let me know what you think about it! We rely heavily on boost::serialization and really appreciate the amount of time and knowledge that went into it. By publishing this small missing piece we hope to contribute our mite to this great library. The work extends the portable binary example which was done by Robert Ramey and uses Beman Dawes' endian library plus the fp_utilities by Johan Rade. You will need to get those two libraries in order to use our classes - look for them at the boost vault (http://www.boost-consulting.com/vault/) in categories 'integer' and 'math - numerics'. Finally you will find the portable binary archive in category 'serialization' as well. Regards, Christian Pfligersdorffer Munich, End of 2007 -- christian.pfligersdorffer@eos.info christian.pfligersdorffer@gmx.at http://www.eos.infoliblsl-1.17.7/src/portable_archive/slimarchive.hpp000066400000000000000000000136371517625163100222300ustar00rootroot00000000000000#pragma once /// Small shim implementing the needed Boost serialization classes for liblsl #include #include #include #include #include /// dummy #defines to prevent portable archive from loading additional boost archive parts #define NO_EXPLICIT_TEMPLATE_INSTANTIATION #define BOOST_ARCHIVE_SERIALIZER_INCLUDED #define BOOST_SERIALIZATION_REGISTER_ARCHIVE(typename) // forward declaration namespace lsl { class sample; } /// Maps LSL types to an index without needing full RTTI template struct lsl_type_index {}; template <> struct lsl_type_index { static const int idx = 0; }; /// two classes used in unit tests template <> struct lsl_type_index { static const int idx = 1; }; template <> struct lsl_type_index { static const int idx = 2; }; /// keep track of classes already seen once, needed for a field in Boost.Archive class serialized_class_tracker { bool seen_[3]{false}; public: /// Return true /if an instance of this class has already been (de)serialized before template inline bool already_seen(const T &) { if (seen_[lsl_type_index::idx]) return true; seen_[lsl_type_index::idx] = true; return false; } }; namespace lslboost { #ifndef BOOST_INTEGER_HPP /// Maps a type size in bits to the corresponding uintXY_t type template struct uint_t {}; template <> struct uint_t<8> { using least = uint8_t; }; template <> struct uint_t<16> { using least = uint16_t; }; template <> struct uint_t<32> { using least = uint32_t; }; template <> struct uint_t<64> { using least = uint64_t; }; #endif namespace archive { using library_version_type = uint8_t; inline library_version_type BOOST_ARCHIVE_VERSION() { return 17; } enum flags { no_header = 1, no_codecvt = 2 }; struct archive_exception : std::exception { archive_exception(int) {} enum errors { invalid_signature, unsupported_version, other_exception }; }; /// Common class to store flags class flagstore { int flags_; public: flagstore(int flags) : flags_(flags) {} int get_flags() const { return flags_; } }; template struct basic_binary_oarchive : public flagstore { basic_binary_oarchive(int flags_) : flagstore(flags_) {} }; template struct basic_binary_iarchive : public flagstore { basic_binary_iarchive(int flags_) : flagstore(flags_) {} }; /// Helper base class for calling methods of an descendant class, i.e. Archive::fn, not this->fn template class archive_upcaster { public: archive_upcaster() { static_assert( std::is_base_of::type, Archive>::value, "Archive must inherit from basic_binary_i/oprimitive"); } /** Return a reference to the actual archive implementation instead of the base class * * This is needed for most operations to call Archive::fn, not basic_binary_primitive::fn */ inline Archive &archive_impl() { return *((Archive *)this); } }; /* template class basic_binary_primitive { std::streambuf &sb; }*/ template class basic_binary_oprimitive : private serialized_class_tracker, private archive_upcaster { std::streambuf &sb; /// calls Archive::serialize instead of this->serialize() template Archive &delegate_save(const T &t) { this->archive_impl().save(t); return this->archive_impl(); } public: basic_binary_oprimitive(std::streambuf &sbuf, int) : archive_upcaster(), sb(sbuf) {} template void save_binary(const T *data, std::size_t bytes) { sb.sputn(reinterpret_cast(data), bytes); } template void save(const T &t) { save_binary(&t, sizeof(T)); } void save(const std::string &s) { delegate_save(s.size()); save_binary(s.data(), s.size()); } template std::enable_if_t::value, Archive &> operator<<(const T &t) { return delegate_save(t); } Archive &operator<<(const std::string &t) { return delegate_save(t); } /// calls the `serialize()` member function if it exists template Archive &operator<<(const T &t) { if (!this->already_seen(t)) save(0); t.serialize(this->archive_impl(), 0); return this->archive_impl(); } template Archive &operator&(const T &t) { return this->archive_impl() << t; } }; template class basic_binary_iprimitive : private serialized_class_tracker, private archive_upcaster { std::streambuf &sb; template Archive &delegate_load(T &t) { this->archive_impl().load(t); return this->archive_impl(); } public: basic_binary_iprimitive(std::streambuf &sbuf, int) : archive_upcaster(), sb(sbuf) {} /// Load raw binary data from the stream template void load_binary(T *data, std::size_t bytes) { sb.sgetn(reinterpret_cast(data), bytes); } template void load(T &t) { load_binary(&t, sizeof(T)); } void load(std::string &s) { std::size_t size; delegate_load(size); char *data = new char[size]; load_binary(data, size); s.assign(data, size); delete[] data; } Archive &operator>>(std::string &t) { return delegate_load(t); } template std::enable_if_t::value, Archive &> operator>>(T &t) { return delegate_load(t); } /// calls the `serialize()` member function if it exists template Archive &operator>>(T &t) { if (!this->already_seen(t)) { uint16_t dummy; load(dummy); } t.serialize(this->archive_impl(), 0); return this->archive_impl(); } template Archive &operator&(T &t) { return this->archive_impl() >> t; } void set_library_version(int) {} }; } // namespace archive } // namespace lslboost liblsl-1.17.7/src/resolve_attempt_udp.cpp000066400000000000000000000166231517625163100204670ustar00rootroot00000000000000#include "resolve_attempt_udp.h" #include "api_config.h" #include "netinterfaces.h" #include "resolver_impl.h" #include "socket_utils.h" #include "util/strfuns.hpp" #include #include #include #include #include #include using namespace lsl; using err_t = const asio::error_code &; using asio::ip::multicast::outbound_interface; resolve_attempt_udp::resolve_attempt_udp(asio::io_context &io, const udp &protocol, const std::vector &targets, const std::string &query, resolver_impl &resolver, double cancel_after) : io_(io), resolver_(resolver), cancel_after_(cancel_after), cancelled_(false), targets_(targets), query_(query), unicast_socket_(io), broadcast_socket_(io), multicast_socket_(io), multicast_interfaces(api_config::get_instance()->multicast_interfaces), recv_socket_(io), cancel_timer_(io) { // open the sockets that we might need recv_socket_.open(protocol); try { bind_port_in_range(recv_socket_, protocol); } catch (std::exception &e) { LOG_F(WARNING, "Could not bind to a port in the configured port range; using a randomly assigned one: " "%s", e.what()); } unicast_socket_.open(protocol); try { broadcast_socket_.open(protocol); broadcast_socket_.set_option(asio::socket_base::broadcast(true)); } catch (std::exception &e) { LOG_F(WARNING, "Cannot open UDP broadcast socket for resolves: %s", e.what()); } try { multicast_socket_.open(protocol); multicast_socket_.set_option( asio::ip::multicast::hops(api_config::get_instance()->multicast_ttl())); } catch (std::exception &e) { LOG_F(WARNING, "Cannot open UDP multicast socket for resolves: %s", e.what()); } // precalc the query id (hash of the query string, as string) query_id_ = std::to_string(std::hash()(query)); // precalc the query message std::ostringstream os; os.precision(16); os << "LSL:shortinfo\r\n"; os << query_ << "\r\n"; os << recv_socket_.local_endpoint().port() << " " << query_id_ << "\r\n"; query_msg_ = os.str(); DLOG_F(2, "Waiting for query results (port %d) for %s", recv_socket_.local_endpoint().port(), query_msg_.c_str()); // register ourselves as a candidate for cancellation register_at(&resolver); } resolve_attempt_udp::~resolve_attempt_udp() { // make sure that the cancel is unregistered before the resolve attempt is being deleted... unregister_from_all(); } // === externally-triggered asynchronous commands === void resolve_attempt_udp::begin() { // initiate the result gathering chain receive_next_result(); // initiate the send chain send_next_query(targets_.begin(), multicast_interfaces.begin()); // also initiate the cancel event, if desired if (cancel_after_ != FOREVER) { cancel_timer_.expires_after(timeout_sec(cancel_after_)); cancel_timer_.async_wait([shared_this = shared_from_this(), this](err_t err) { if (!err) do_cancel(); }); } } void resolve_attempt_udp::cancel() { post(io_, [shared_this = shared_from_this()]() { shared_this->do_cancel(); }); } // === receive loop === void resolve_attempt_udp::receive_next_result() { recv_socket_.async_receive_from(asio::buffer(resultbuf_), remote_endpoint_, [shared_this = shared_from_this()]( err_t err, size_t len) { shared_this->handle_receive_outcome(err, len); }); } void resolve_attempt_udp::handle_receive_outcome(err_t err, std::size_t len) { if (cancelled_ || err == asio::error::operation_aborted || err == asio::error::not_connected || err == asio::error::not_socket) return; if (!err) { try { // first parse & check the query id char *bufend = resultbuf_ + len; char *newlinepos = resultbuf_; // find the end of the line while (newlinepos != bufend && *newlinepos != '\n') ++newlinepos; std::string returned_id(resultbuf_, trim_end(resultbuf_, newlinepos)); if (returned_id == query_id_ && newlinepos != bufend) { // parse the rest of the query into a stream_info stream_info_impl info; info.from_shortinfo_message(std::string(newlinepos, bufend)); std::string uid = info.uid(); { // update the results std::lock_guard lock(resolver_.results_mut_); auto it = resolver_.results_.find(uid); if (it == resolver_.results_.end()) // insert new result, store iterator in it it = resolver_.results_.emplace(uid, std::make_pair(info, lsl_clock())) .first; else it->second.second = lsl_clock(); // update only the receive time auto &stored_info = it->second.first; // ... also update the address associated with the result (but don't // override the address of an earlier record for this stream since this // would be the faster route) if (remote_endpoint_.address().is_v4()) { if (stored_info.v4address().empty()) stored_info.v4address(remote_endpoint_.address().to_string()); } else { if (stored_info.v6address().empty()) stored_info.v6address(remote_endpoint_.address().to_string()); } } // prepone the next cancellation check, i.e. when all needed streams are found, // cancel immediately rather than when a wave timer is due half a second later if (resolver_.check_cancellation_criteria()) resolver_.cancel_ongoing_resolve(); } } catch (std::exception &e) { LOG_F(WARNING, "resolve_attempt_udp: hiccup while processing the received data: %s", e.what()); } } // ask for the next result receive_next_result(); } // === send loop === void resolve_attempt_udp::send_next_query( endpoint_list::const_iterator next, mcast_interface_list::const_iterator mcit) { if (cancelled_ || mcit == multicast_interfaces.end()) return; auto proto = recv_socket_.local_endpoint().protocol(); if (next == targets_.begin()) { // Mismatching protocols? Skip this round if (mcit->addr.is_v4() != (proto == asio::ip::udp::v4())) next = targets_.end(); else multicast_socket_.set_option(mcit->addr.is_v4() ? outbound_interface(mcit->addr.to_v4()) : outbound_interface(mcit->ifindex)); } if (next != targets_.end()) { udp::endpoint ep(*next++); // endpoint matches our active protocol? if (ep.protocol() == recv_socket_.local_endpoint().protocol()) { // select socket to use udp_socket &sock = (ep.address() == asio::ip::address_v4::broadcast()) ? broadcast_socket_ : (ep.address().is_multicast() ? multicast_socket_ : unicast_socket_); // and send the query over it auto keepalive(shared_from_this()); sock.async_send_to(asio::buffer(query_msg_), ep, [shared_this = shared_from_this(), next, mcit](err_t err, size_t /*unused*/) { if (!shared_this->cancelled_ && err != asio::error::operation_aborted && err != asio::error::not_connected && err != asio::error::not_socket) shared_this->send_next_query(next, mcit); }); } else // otherwise just go directly to the next query send_next_query(next, mcit); } else // Restart from the next interface send_next_query(targets_.begin(), ++mcit); } void resolve_attempt_udp::do_cancel() { try { cancelled_ = true; if (unicast_socket_.is_open()) unicast_socket_.close(); if (multicast_socket_.is_open()) multicast_socket_.close(); if (broadcast_socket_.is_open()) broadcast_socket_.close(); if (recv_socket_.is_open()) recv_socket_.close(); cancel_timer_.cancel(); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while trying to cancel operations of resolve_attempt_udp: %s", e.what()); } } liblsl-1.17.7/src/resolve_attempt_udp.h000066400000000000000000000122241517625163100201250ustar00rootroot00000000000000#ifndef RESOLVE_ATTEMPT_UDP_H #define RESOLVE_ATTEMPT_UDP_H #include "cancellation.h" #include "forward.h" #include "netinterfaces.h" #include "stream_info_impl.h" #include "socket_utils.h" #include #include #include #include #include #include using asio::ip::udp; using err_t = const asio::error_code &; namespace lsl { using steady_timer = asio::basic_waitable_timer, asio::io_context::executor_type>; /// A container for resolve results (map from stream instance UID onto (stream_info,receive-time)). using result_container = std::map>; /// A container for outgoing multicast interfaces using mcast_interface_list = std::vector; /** * An asynchronous resolve attempt for a single query targeted at a set of endpoints, via UDP. * * A resolve attempt is an asynchronous operation submitted to an IO object, which amounts to a * sequence of query packet sends (one for each endpoint in the list) and a sequence of result * packet receives. The operation will wait for return packets until either a particular timeout has * been reached or until it is cancelled via the cancel() method. */ class resolve_attempt_udp final : public cancellable_obj, public std::enable_shared_from_this { using endpoint_list = std::vector; public: /** * Instantiate and set up a new resolve attempt. * * @param io The io_context that will run the async operations. * @param protocol The protocol (either udp::v4() or udp::v6()) to use for communications; * only the subset of target addresses matching this protocol will be considered. * @param targets A list of udp::endpoint that should be targeted by this query. * @param query The query string to send (usually a set of conditions on the properties of the * stream info that should be searched, for example `name='BioSemi' and type='EEG'`. * See lsl_stream_info_matches_query for the definition of a query. * @param results Reference to a container into which results are stored; potentially shared * with other parallel resolve operations. Since this is not thread-safe all operations * modifying this must run on the same single-threaded IO service. * @param results_mut Reference to a mutex that protects the container. * @param cancel_after The time duration after which the attempt is automatically cancelled, * i.e. the receives are ended. * @param registry A registry where the attempt can register itself as active so it can be * cancelled during shutdown. */ resolve_attempt_udp(asio::io_context &io, const udp &protocol, const std::vector &targets, const std::string &query, resolver_impl &resolver, double cancel_after = 5.0); /// Destructor ~resolve_attempt_udp() final; /// Start the attempt asynchronously. void begin(); /// Cancel operations asynchronously, and destructively. /// Note that this mostly serves to expedite the destruction of the object, /// which would happen anyway after some time. /// As the attempt instance is owned by the handler chains /// the cancellation eventually leads to the destruction of the object. void cancel() override; private: // === send and receive handlers === /// This function asks to receive the next result packet. void receive_next_result(); /// Thos function starts an async send operation for the given current endpoint. void send_next_query( endpoint_list::const_iterator next, mcast_interface_list::const_iterator mcit); /// Handler that gets called when a receive has completed. void handle_receive_outcome(err_t err, std::size_t len); // === cancellation === /// Cancel the outstanding operations. void do_cancel(); // data shared with the resolver_impl /// reference to the IO service that executes our actions asio::io_context &io_; /// the resolver associated with this attempt resolver_impl &resolver_; // constant over the lifetime of this attempt /// the timeout for giving up double cancel_after_; /// whether the operation has been cancelled bool cancelled_; /// list of endpoints that should receive the query std::vector targets_; /// the query string std::string query_; /// the query message that we're sending std::string query_msg_; /// the (more or less) unique id for this query std::string query_id_; // data maintained/modified across handler invocations /// the endpoint from which we received the last result udp::endpoint remote_endpoint_; /// holds a single result received from the net char resultbuf_[65536]; // IO objects /// socket to send data over (for unicasts) udp_socket unicast_socket_; /// socket to send data over (for broadcasts) udp_socket broadcast_socket_; /// socket to send data over (for multicasts) udp_socket multicast_socket_; /// Interface addresses to send multicast packets from const mcast_interface_list &multicast_interfaces; /// socket to receive replies (always unicast) udp_socket recv_socket_; /// timer to schedule the cancel action steady_timer cancel_timer_; }; } // namespace lsl #endif liblsl-1.17.7/src/resolver_impl.cpp000066400000000000000000000203751517625163100172630ustar00rootroot00000000000000#include "resolver_impl.h" #include "api_config.h" #include "resolve_attempt_udp.h" #include "socket_utils.h" #include "stream_info_impl.h" #include #include #include #include #include #include #include #include #include // === implementation of the resolver_impl class === using namespace lsl; resolver_impl::resolver_impl() : cfg_(api_config::get_instance()), cancelled_(false), expired_(false), forget_after_(FOREVER), fast_mode_(true), io_(std::make_shared()), resolve_timeout_expired_(*io_), wave_timer_(*io_), unicast_timer_(*io_) { // parse the multicast addresses into endpoints and store them uint16_t mcast_port = cfg_->multicast_port(); for (const auto &mcast_addr : cfg_->multicast_addresses()) { try { mcast_endpoints_.emplace_back(mcast_addr, mcast_port); } catch (std::exception &) {} } // parse the per-host addresses into endpoints, and store them, too udp::resolver udp_resolver(*io_); // for each known peer... for (const auto &peer : cfg_->known_peers()) { try { // resolve the name // for each endpoint... for (const auto &res : udp_resolver.resolve(peer, std::to_string(cfg_->base_port()))) { // for each port in the range... for (int p = cfg_->base_port(); p < cfg_->base_port() + cfg_->port_range(); p++) // add a record ucast_endpoints_.emplace_back(res.endpoint().address(), p); } } catch (std::exception &) {} } // generate the list of protocols to use if (cfg_->allow_ipv6()) { udp_protocols_.push_back(udp::v6()); } if (cfg_->allow_ipv4()) { udp_protocols_.push_back(udp::v4()); } } void check_query(const std::string &query) { try { pugi::xpath_query(query.c_str()); } catch (std::exception &e) { throw std::invalid_argument((("Invalid query '" + query) += "': ") += e.what()); } } std::string resolver_impl::build_query(const char *pred_or_prop, const char *value) { std::string query("session_id='"); query += api_config::get_instance()->session_id(); query += '\''; if (pred_or_prop) (query += " and ") += pred_or_prop; if (value) ((query += "='") += value) += '\''; return query; } resolver_impl *resolver_impl::create_resolver( double forget_after, const char *pred_or_prop, const char *value) noexcept { try { auto *resolver = new resolver_impl(); resolver->resolve_continuous(build_query(pred_or_prop, value), forget_after); return resolver; } catch (std::exception &e) { LOG_F(ERROR, "Error while creating a continuous_resolver: %s", e.what()); return nullptr; } } // === resolve functions === std::vector resolver_impl::resolve_oneshot( const std::string &query, int minimum, double timeout, double minimum_time) { if(status == resolver_status::running_continuous) throw std::logic_error("resolve_oneshot called during continuous operation"); check_query(query); // reset the IO service & set up the query parameters io_->restart(); query_ = query; minimum_ = minimum; wait_until_ = lsl_clock() + minimum_time; results_.clear(); forget_after_ = FOREVER; fast_mode_ = true; expired_ = false; // start a timer that cancels all outstanding IO operations and wave schedules after the timeout // has expired if (timeout != FOREVER) { resolve_timeout_expired_.expires_after(timeout_sec(timeout)); resolve_timeout_expired_.async_wait([this](err_t err) { if (err != asio::error::operation_aborted) cancel_ongoing_resolve(); }); } // start the first wave of resolve packets next_resolve_wave(); status = resolver_status::started_oneshot; // run the IO operations until finished if (!cancelled_) { io_->run(); // collect output std::vector output; for (auto &result : results_) output.push_back(result.second.first); return output; } return {}; } void resolver_impl::resolve_continuous(const std::string &query, double forget_after) { if(status == resolver_status::running_continuous) throw std::logic_error("resolve_continuous called during another continuous operation"); check_query(query); // reset the IO service & set up the query parameters io_->restart(); query_ = query; minimum_ = 0; wait_until_ = 0; results_.clear(); forget_after_ = forget_after; fast_mode_ = false; expired_ = false; // start a wave of resolve packets next_resolve_wave(); // spawn a thread that runs the IO operations background_io_ = std::make_shared([shared_io = io_]() { shared_io->run(); }); status = resolver_status::running_continuous; } std::vector resolver_impl::results(uint32_t max_results) { if (status == resolver_status::empty) throw std::logic_error("results() called before starting a resolve operation"); std::vector output; std::lock_guard lock(results_mut_); double expired_before = lsl_clock() - forget_after_; for (auto it = results_.begin(); it != results_.end();) { if (it->second.second < expired_before) it = results_.erase(it); else { if (output.size() < max_results) output.push_back(it->second.first); it++; } } return output; } // === timer-driven async handlers === void resolver_impl::next_resolve_wave() { if (check_cancellation_criteria()) { // stopping criteria satisfied: cancel the ongoing operations cancel_ongoing_resolve(); } else { // start a new multicast wave udp_multicast_burst(); auto wave_timer_timeout = (fast_mode_ ? 0 : cfg_->continuous_resolve_interval()) + cfg_->multicast_min_rtt(); if (!ucast_endpoints_.empty()) { // we have known peer addresses: we spawn a unicast wave unicast_timer_.expires_after(timeout_sec(cfg_->multicast_min_rtt())); unicast_timer_.async_wait([this](err_t ec) { this->udp_unicast_burst(ec); }); // delay the next multicast wave wave_timer_timeout += cfg_->unicast_min_rtt(); } wave_timer_.expires_after(timeout_sec(wave_timer_timeout)); wave_timer_.async_wait([this](err_t err) { if (err != asio::error::operation_aborted) next_resolve_wave(); }); } } void resolver_impl::udp_multicast_burst() { // start one per IP stack under consideration unsigned int failures = 0; for (auto protocol: udp_protocols_) { try { std::make_shared( *io_, protocol, mcast_endpoints_, query_, *this, cfg_->multicast_max_rtt()) ->begin(); } catch (std::exception &e) { if (++failures == udp_protocols_.size()) LOG_F(ERROR, "Could not start a multicast resolve attempt for any of the allowed " "protocol stacks: %s", e.what()); } } } void resolver_impl::udp_unicast_burst(err_t err) { if (err == asio::error::operation_aborted) return; unsigned int failures = 0; // start one per IP stack under consideration for (auto protocol: udp_protocols_) { try { std::make_shared( *io_, protocol, ucast_endpoints_, query_, *this, cfg_->unicast_max_rtt()) ->begin(); } catch (std::exception &e) { if (++failures == udp_protocols_.size()) LOG_F(WARNING, "Could not start a unicast resolve attempt for any of the allowed protocol " "stacks: %s", e.what()); } } } // === cancellation and teardown === void resolver_impl::cancel() { cancelled_ = true; cancel_ongoing_resolve(); } bool resolver_impl::check_cancellation_criteria() { std::size_t num_results = 0; { std::lock_guard lock(results_mut_); num_results = results_.size(); } if (cancelled_ || expired_) return true; if (minimum_ && (num_results >= (std::size_t)minimum_) && lsl_clock() >= wait_until_) return true; return false; } void resolver_impl::cancel_ongoing_resolve() { // make sure that ongoing handler loops terminate expired_ = true; // timer fires: cancel the next wave schedule post(*io_, [this]() { wave_timer_.cancel(); }); post(*io_, [this]() { unicast_timer_.cancel(); }); // and cancel the timeout, too post(*io_, [this]() { resolve_timeout_expired_.cancel(); }); // cancel all currently active resolve attempts cancel_all_registered(); } resolver_impl::~resolver_impl() { try { if (background_io_) { cancel(); background_io_->join(); } } catch (std::exception &e) { LOG_F(WARNING, "Error during destruction of a resolver_impl: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during destruction of a resolver_impl."); } } liblsl-1.17.7/src/resolver_impl.h000066400000000000000000000171571517625163100167340ustar00rootroot00000000000000#ifndef RESOLVER_IMPL_H #define RESOLVER_IMPL_H #include "cancellation.h" #include "common.h" #include "forward.h" #include "stream_info_impl.h" #include #include #include #include #include #include #include #include #include #include using asio::ip::tcp; using asio::ip::udp; using err_t = const asio::error_code &; namespace lsl { class api_config; using steady_timer = asio::basic_waitable_timer, asio::io_context::executor_type>; /// A container for resolve results (map from stream instance UID onto (stream_info,receive-time)). using result_container = std::map>; /** * A stream resolver object. * * Maintains the necessary resources for a resolve process, * used by the free-standing resolve functions, the continuous_resolver class, and the inlets. * * A resolver instance can be operated in two different ways: * * 1) In one shot: The resolver is queried one or more times by calling resolve_oneshot(). * * 2) Continuously: First a background query process is started that keeps updating a results list * by calling resolve_continuous() and the list is retrieved in parallel when desired via results(). * In this case a new resolver instance must be created to issue a new query. */ class resolver_impl final : public cancellable_registry { public: /** * Instantiate a new resolver and configure timing parameters. * * @note Resolution logic: If api_config::known_peers is empty, a new multicast wave will be * scheduled every mcast_min_rtt (until a timeout expires or the desired number of streams has * been resolved).If api_config::known_peers is non-empty, a multicast wave and a unicast wave * will be scheduled in alternation. * The spacing between waves will be no shorter than the respective minimum RTTs. * In continuous mode a special, somewhat more lax, set of timings is used (see API config). */ resolver_impl(); /** * Build a query string * * @param pred_or_prop an entire predicate if value isn't set or the name of the property, e.g. * `foo='bar'` / `foo` (+value set as "bar") * @param value the value for the property parameter */ static std::string build_query(const char *pred_or_prop = nullptr, const char *value = nullptr); /** * Create a resolver object with optionally a predicate or property + value * * @param forget_after Seconds since last response after which a stream isn't considered to be * online any more. * @param pred_or_prop an entire predicate of value isn't set or the name of the property, e.g. * `foo='bar'` / `foo` (+value set as "bar") * @param value the value for the property parameter * @return A pointer to the resolver on success or nullptr on error */ static resolver_impl *create_resolver(double forget_after, const char *pred_or_prop = nullptr, const char *value = nullptr) noexcept; /// Destructor. Cancels any ongoing processes and waits until they finish. ~resolver_impl() final; resolver_impl(const resolver_impl &) = delete; /** * Resolve a query string into a list of matching stream_info's on the network. * * Blocks until at least the minimum number of streams has been resolved, or the timeout fires, * or the resolve has been cancelled. * @param query The query string to send (usually a set of conditions on the properties of the * stream info that should be searched, for example "name='BioSemi' and type='EEG'" (without the * outer ""). See lsl_stream_info_matches_query() for the definition of a query. * @param minimum The minimum number of unique streams that should be resolved before this * function may to return. * @param timeout The timeout after which this function is forced to return (even if it did not * produce the desired number of results). * @param minimum_time Search for matching streams for at least this much time (e.g., if * multiple streams may be present). */ std::vector resolve_oneshot(const std::string &query, int minimum = 0, double timeout = FOREVER, double minimum_time = 0.0); /** * Starts a background thread that resolves a query string and periodically updates the list of * present streams. * * After this, the resolver can *not* be repurposed for other queries or for * oneshot operation (a new instance needs to be created for that). * @param query The query string to send (usually a set of conditions on the properties of the * stream info that should be searched, for example `name='BioSemi' and type='EEG'` * See stream_info_impl::matches_query() for the definition of a query. * @param forget_after If a stream vanishes from the network (e.g., because it was shut down), * it will be pruned from the list this many seconds after it was last seen. */ void resolve_continuous(const std::string &query, double forget_after = 5.0); /// Get the current set of results (e.g., during continuous operation). std::vector results(uint32_t max_results = 4294967295); /** * Tear down any ongoing operations and render the resolver unusable. * * This can be used to cancel a blocking resolve_oneshot() from another thread (e.g., to * initiate teardown of the object). */ void cancel(); enum class resolver_status { empty, started_oneshot, running_continuous }; private: friend class resolve_attempt_udp; /// This function starts a new wave of resolves. void next_resolve_wave(); /// Start a new resolver attempt on the multicast hosts. void udp_multicast_burst(); /// Start a new resolver attempt on the known peers. void udp_unicast_burst(err_t err); /// Check if cancellation criteria (minimum number of results, timeout) are met bool check_cancellation_criteria(); /// Cancel the currently ongoing resolve, if any. void cancel_ongoing_resolve(); // constants (mostly config-deduced) /// pointer to our configuration object const api_config *cfg_; /// UDP protocols under consideration std::vector udp_protocols_; /// the list of multicast endpoints under consideration std::vector mcast_endpoints_; /// the list of per-host UDP endpoints under consideration std::vector ucast_endpoints_; // things related to cancellation /// if set, no more resolves can be started (destructively cancelled). std::atomic cancelled_; /// if set, ongoing operations will finished quickly std::atomic expired_; // reinitialized for each query resolver_status status{resolver_status::empty}; /// our current query string std::string query_; /// the minimum number of results that we want int minimum_{0}; /// forget results that are older than this (continuous operation only) double forget_after_; /// wait until this point in time before returning results (optional to allow for returning /// potentially more than a minimum number of results) double wait_until_{0}; /// whether this is a fast resolve: determines the rate at which the query is repeated bool fast_mode_; /// results are stored here result_container results_; /// a mutex that protects the results map std::mutex results_mut_; // io objects /// our IO service io_context_p io_; /// a thread that runs background IO if we are performing a resolve_continuous std::shared_ptr background_io_; /// the overall timeout for a query steady_timer resolve_timeout_expired_; /// a timer that fires when a new wave should be scheduled steady_timer wave_timer_; /// a timer that fires when the unicast wave should be scheduled steady_timer unicast_timer_; }; } // namespace lsl #endif liblsl-1.17.7/src/sample.cpp000066400000000000000000000417731517625163100156670ustar00rootroot00000000000000#include #define BOOST_MATH_DISABLE_STD_FPCLASSIFY #include "sample.h" #include "common.h" #include "portable_archive/portable_iarchive.hpp" #include "portable_archive/portable_oarchive.hpp" #include "util/cast.hpp" #include using namespace lsl; using lslboost::endian::endian_reverse_inplace; #ifdef _MSC_VER #pragma warning(suppress : 4291) #endif /// range-for helper class for `values()` template class dataiter { T *begin_, *end_; public: dataiter(void *begin, std::size_t n) : begin_(reinterpret_cast(begin)), end_(begin_ + n) {} dataiter(const void *begin, std::size_t n) : begin_(reinterpret_cast(begin)), end_(begin_ + n) {} // called from `f(auto val: dataiter)` T *begin() const noexcept { return begin_; } T *end() const noexcept { return end_; } }; /// range-for helper. Sample usage: `for(auto &val: samplevalues()) val = 2;` template inline dataiter samplevals(sample &s) noexcept { return dataiter(iterhelper(s), s.num_channels()); } template inline dataiter samplevals(const sample &s) noexcept { return dataiter(iterhelper(s), s.num_channels()); } /// Copy an array, converting between LSL types if needed template inline void copyconvert_array(const T *src, U *dst, std::size_t n) noexcept { for (const T *end = src + n; src < end;) *dst++ = static_cast(*src++); // NOLINT(bugprone-signed-char-misuse) } /// Copy an array, special case: source and destination have the same type template inline void copyconvert_array(const T *src, T *dst, std::size_t n) noexcept { memcpy(dst, src, n * sizeof(T)); } /// Copy an array, special case: destination is a string array template inline void copyconvert_array(const T *src, std::string *dst, std::size_t n) { for (const T *end = src + n; src < end;) *dst++ = lsl::to_string(*src++); } /// Copy an array, special case: source is a string array template inline void copyconvert_array(const std::string *src, U *dst, std::size_t n) { for (const auto *end = src + n; src < end;) *dst++ = lsl::from_string(*src++); } /// Copy an array, special case: both arrays are string arrays inline void copyconvert_array(const std::string *src, std::string *dst, std::size_t n) { std::copy_n(src, n, dst); } template void lsl::sample::conv_from(const U *src) { copyconvert_array(src, reinterpret_cast(&data_), num_channels_); } template void lsl::sample::conv_into(U *dst) { copyconvert_array(reinterpret_cast(&data_), dst, num_channels_); } void sample::operator delete(void *x) noexcept { if (x == nullptr) return; lsl::factory *factory = reinterpret_cast(x)->factory_; // delete the underlying memory only if it wasn't allocated in the factory's storage area if (x < factory->storage_ || x >= factory->storage_ + factory->storage_size_) delete[] (char *)x; } /// ensure that a given value is a multiple of some base, round up if necessary constexpr uint32_t ensure_multiple(uint32_t v, unsigned base) { return (v % base) ? v - (v % base) + base : v; } // Sample functions lsl::sample::~sample() noexcept { if (format_ == cft_string) for (auto &val : samplevals(*this)) val.~basic_string(); } bool sample::operator==(const sample &rhs) const noexcept { if ((timestamp_ != rhs.timestamp_) || (format_ != rhs.format_) || (num_channels_ != rhs.num_channels_)) return false; if (format_ != cft_string) return memcmp(&(rhs.data_), &data_, datasize()) == 0; // For string values, each value has to be compared individually auto thisvals = samplevals(*this); return std::equal(thisvals.begin(), thisvals.end(), samplevals(rhs).begin()); } template void lsl::sample::assign_typed(const T *src) { switch (format_) { case cft_float32: conv_from(src); break; case cft_double64: conv_from(src); break; case cft_int8: conv_from(src); break; case cft_int16: conv_from(src); break; case cft_int32: conv_from(src); break; #ifndef BOOST_NO_INT64_T case cft_int64: conv_from(src); break; #endif case cft_string: conv_from(src); break; default: throw std::invalid_argument("Unsupported channel format."); } } template void lsl::sample::retrieve_typed(T *dst) { switch (format_) { case cft_float32: conv_into(dst); break; case cft_double64: conv_into(dst); break; case cft_int8: conv_into(dst); break; case cft_int16: conv_into(dst); break; case cft_int32: conv_into(dst); break; #ifndef BOOST_NO_INT64_T case cft_int64: conv_into(dst); break; #endif case cft_string: conv_into(dst); break; default: throw std::invalid_argument("Unsupported channel format."); } } void lsl::sample::assign_untyped(const void *newdata) { if (format_ != cft_string) memcpy(&data_, newdata, datasize()); else throw std::invalid_argument("Cannot assign untyped data to a string-formatted sample."); } void lsl::sample::retrieve_untyped(void *newdata) { if (format_ != cft_string) memcpy(newdata, &data_, datasize()); else throw std::invalid_argument("Cannot retrieve untyped data from a string-formatted sample."); } /// Helper function to save raw binary data to a stream buffer. void save_raw(std::streambuf &sb, const void *address, std::size_t count) { if ((std::size_t)sb.sputn((const char *)address, (std::streamsize)count) != count) throw std::runtime_error("Output stream error."); } void save_byte(std::streambuf &sb, uint8_t v) { if (sb.sputc(*reinterpret_cast(&v)) == std::streambuf::traits_type::eof()) throw std::runtime_error("Output stream error."); } /// Helper function to load raw binary data from a stream buffer. void load_raw(std::streambuf &sb, void *address, std::size_t count) { if ((std::size_t)sb.sgetn((char *)address, (std::streamsize)count) != count) throw std::runtime_error("Input stream error."); } uint8_t load_byte(std::streambuf &sb) { auto res = sb.sbumpc(); if (res == std::streambuf::traits_type::eof()) throw std::runtime_error("Input stream error."); return static_cast(res); } /// Load a value from a stream buffer with correct endian treatment. template T load_value(std::streambuf &sb, bool reverse_byte_order) { T tmp; load_raw(sb, &tmp, sizeof(T)); if (sizeof(T) > 1 && reverse_byte_order) endian_reverse_inplace(tmp); return tmp; } /// Save a value to a stream buffer with correct endian treatment. template void save_value(std::streambuf &sb, T v, bool reverse_byte_order) { if (sizeof(T) > 1 && reverse_byte_order) endian_reverse_inplace(v); save_raw(sb, &v, sizeof(T)); } void sample::save_streambuf( std::streambuf &sb, int /*protocol_version*/, bool reverse_byte_order, void *scratchpad) const { // write sample header if (timestamp_ == DEDUCED_TIMESTAMP) { save_byte(sb, TAG_DEDUCED_TIMESTAMP); } else { save_byte(sb, TAG_TRANSMITTED_TIMESTAMP); save_value(sb, timestamp_, reverse_byte_order); } // write channel data if (format_ == cft_string) { for (const auto &str : samplevals(*this)) { // write string length as variable-length integer if (str.size() <= 0xFF) { save_byte(sb, static_cast(sizeof(uint8_t))); save_byte(sb, static_cast(str.size())); } else { if (str.size() <= 0xFFFFFFFF) { save_byte(sb, static_cast(sizeof(uint32_t))); save_value(sb, static_cast(str.size()), reverse_byte_order); } else { save_byte(sb, static_cast(sizeof(uint64_t))); save_value(sb, static_cast(str.size()), reverse_byte_order); } } // write string contents if (!str.empty()) save_raw(sb, str.data(), str.size()); } } else { // write numeric data in binary if (!reverse_byte_order || format_sizes[format_] == 1) { save_raw(sb, &data_, datasize()); } else { memcpy(scratchpad, &data_, datasize()); convert_endian(scratchpad, num_channels_, format_sizes[format_]); save_raw(sb, scratchpad, datasize()); } } } void sample::load_streambuf( std::streambuf &sb, int /*unused*/, bool reverse_byte_order, bool suppress_subnormals) { // read sample header if (load_byte(sb) == TAG_DEDUCED_TIMESTAMP) // deduce the timestamp timestamp_ = DEDUCED_TIMESTAMP; else // read the time stamp timestamp_ = load_value(sb, reverse_byte_order); // read channel data if (format_ == cft_string) { for (auto &str : samplevals(*this)) { // read string length as variable-length integer std::size_t len = 0; auto lenbytes = load_byte(sb); if (sizeof(std::size_t) < 8 && lenbytes > sizeof(std::size_t)) throw std::runtime_error( "This platform does not support strings of 64-bit length."); switch (lenbytes) { case sizeof(uint8_t): len = load_byte(sb); break; case sizeof(uint16_t): len = load_value(sb, reverse_byte_order); break; case sizeof(uint32_t): len = load_value(sb, reverse_byte_order); break; #ifndef BOOST_NO_INT64_T case sizeof(uint64_t): len = load_value(sb, reverse_byte_order); break; #endif default: throw std::runtime_error("Stream contents corrupted (invalid varlen int)."); } // read string contents str.resize(len); if (len > 0) load_raw(sb, (void*) str.data(), len); } } else { // read numeric channel data load_raw(sb, &data_, datasize()); if (reverse_byte_order && format_sizes[format_] > 1) convert_endian(&data_, num_channels(), format_sizes[format_]); if (suppress_subnormals && format_float[format_]) { if (format_ == cft_float32) { for (auto &val : samplevals(*this)) if (val && ((val & UINT32_C(0x7fffffff)) <= UINT32_C(0x007fffff))) val &= UINT32_C(0x80000000); } else { #ifndef BOOST_NO_INT64_T for (auto &val : samplevals(*this)) if (val && ((val & UINT64_C(0x7fffffffffffffff)) <= UINT64_C(0x000fffffffffffff))) val &= UINT64_C(0x8000000000000000); #endif } } } } void lsl::sample::convert_endian(void *data, uint32_t n, uint32_t width) { void *dataptr = reinterpret_cast(data); switch (width) { case 1: break; case sizeof(int16_t): for (auto &val : dataiter(dataptr, n)) endian_reverse_inplace(val); break; case sizeof(int32_t): for (auto &val : dataiter(dataptr, n)) endian_reverse_inplace(val); break; case sizeof(int64_t): for (auto &val : dataiter(dataptr, n)) endian_reverse_inplace(val); break; default: throw std::runtime_error("Unsupported channel format for endian conversion."); } } template void sample::serialize_channels(Archive &ar, const uint32_t /*unused*/) { switch (format_) { case cft_float32: for (auto &val : samplevals(*this)) ar &val; break; case cft_double64: for (auto &val : samplevals(*this)) ar &val; break; case cft_string: for (auto &val : samplevals(*this)) ar &val; break; case cft_int8: for (auto &val : samplevals(*this)) ar &val; break; case cft_int16: for (auto &val : samplevals(*this)) ar &val; break; case cft_int32: for (auto &val : samplevals(*this)) ar &val; break; #ifndef BOOST_NO_INT64_T case cft_int64: for (auto &val : samplevals(*this)) ar &val; break; #endif default: throw std::runtime_error("Unsupported channel format."); } } void lsl::sample::serialize(eos::portable_oarchive &ar, const uint32_t archive_version) const { // write sample header if (timestamp_ == DEDUCED_TIMESTAMP) { ar &TAG_DEDUCED_TIMESTAMP; } else { ar &TAG_TRANSMITTED_TIMESTAMP ×tamp_; } // write channel data const_cast(this)->serialize_channels(ar, archive_version); } void lsl::sample::serialize(eos::portable_iarchive &ar, const uint32_t archive_version) { // read sample header char tag; ar &tag; if (tag == TAG_DEDUCED_TIMESTAMP) { // deduce the timestamp timestamp_ = DEDUCED_TIMESTAMP; } else { // read the time stamp ar ×tamp_; } // read channel data serialize_channels(ar, archive_version); } template void test_pattern(T *data, uint32_t num_channels, int offset) { for (std::size_t k = 0; k < num_channels; k++) { std::size_t val = k + static_cast(offset); if (std::is_integral::value) val %= static_cast(std::numeric_limits::max()); data[k] = (k % 2 == 0) ? static_cast(val) : -static_cast(val); } } sample &sample::assign_test_pattern(int offset) { pushthrough = true; timestamp_ = 123456.789; switch (format_) { case cft_float32: test_pattern(samplevals(*this).begin(), num_channels_, offset + 0); break; case cft_double64: test_pattern(samplevals(*this).begin(), num_channels_, offset + 16777217); break; case cft_string: { std::string *data = samplevals(*this).begin(); for (int32_t k = 0U; k < (int)num_channels_; k++) data[k] = to_string((k + 10) * (k % 2 == 0 ? 1 : -1)); break; } case cft_int32: test_pattern(samplevals(*this).begin(), num_channels_, offset + 65537); break; case cft_int16: test_pattern(samplevals(*this).begin(), num_channels_, offset + 257); break; case cft_int8: test_pattern(samplevals(*this).begin(), num_channels_, offset + 1); break; #ifndef BOOST_NO_INT64_T case cft_int64: { int64_t *data = samplevals(*this).begin(); int64_t offset64 = 2147483649LL + offset; for (uint32_t k = 0; k < num_channels_; k++) { data[k] = (k + offset64); if (k % 2 == 1) data[k] = -data[k]; } break; } #endif default: throw std::invalid_argument("Unsupported channel format used to construct a sample."); } return *this; } lsl::sample::sample(lsl_channel_format_t fmt, uint32_t num_channels, factory *fact) : format_(fmt), num_channels_(num_channels), refcount_(0), next_(nullptr), factory_(fact) { // construct std::strings in the data section via placement new if (format_ == cft_string) for (auto &val : samplevals(*this)) new (&val) std::string(); } factory::factory(lsl_channel_format_t fmt, uint32_t num_chans, uint32_t num_reserve) : fmt_(fmt), num_chans_(num_chans), sample_size_(ensure_multiple( sizeof(sample) - sizeof(sample::data_) + format_sizes[fmt] * num_chans, 16)), storage_size_(sample_size_ * std::max(2U, num_reserve + 1)), storage_(new char[storage_size_]), head_(sentinel()), tail_(sentinel()) { // pre-construct an array of samples in the storage area and chain into a freelist // this is functionally identical to calling `reclaim_sample()` for each sample, but alters // the head_/tail_ positions only once sample *s = sample_by_index(0); for (char *p = storage_, *e = p + storage_size_; p < e;) { s = new (reinterpret_cast(p)) sample(fmt, num_chans, this); s->next_ = reinterpret_cast(p += sample_size_); } s->next_ = nullptr; head_.store(s); } sample_p factory::new_sample(double timestamp, bool pushthrough) { sample *result; // try to retrieve a free sample, adding fresh samples until it succeeds while ((result = pop_freelist()) == nullptr) reclaim_sample(new (new char[sample_size_]) sample(fmt_, num_chans_, this)); result->timestamp_ = timestamp; result->pushthrough = pushthrough; return {result}; } sample *factory::pop_freelist() { sample *tail = tail_, *next = tail->next_.load(std::memory_order_acquire); if (tail == sentinel()) { // no samples available if (!next) return nullptr; tail_.store(next, std::memory_order_relaxed); tail = next; next = next->next_.load(std::memory_order_acquire); } if (next) { tail_.store(next, std::memory_order_relaxed); return tail; } sample *head = head_.load(std::memory_order_acquire); // if (tail != head) return nullptr; reclaim_sample(sentinel()); next = tail->next_.load(std::memory_order_acquire); if (next) { tail_ = next; return tail; } return nullptr; } factory::~factory() { for (sample *cur = tail_, *next = cur->next_;; cur = next, next = next->next_) { if (cur != sentinel()) delete cur; if (!next) break; } delete[] storage_; } void factory::reclaim_sample(sample *s) { s->next_.store(nullptr, std::memory_order_release); // TODO: might be _relaxed? sample *prev = head_.exchange(s, std::memory_order_acq_rel); prev->next_.store(s, std::memory_order_release); } // template instantiations template void lsl::sample::assign_typed(float const *); template void lsl::sample::assign_typed(double const *); template void lsl::sample::assign_typed(char const *); template void lsl::sample::assign_typed(int16_t const *); template void lsl::sample::assign_typed(int32_t const *); template void lsl::sample::assign_typed(int64_t const *); template void lsl::sample::assign_typed(std::string const *); template void lsl::sample::retrieve_typed(float *); template void lsl::sample::retrieve_typed(double *); template void lsl::sample::retrieve_typed(char *); template void lsl::sample::retrieve_typed(int16_t *); template void lsl::sample::retrieve_typed(int32_t *); template void lsl::sample::retrieve_typed(int64_t *); template void lsl::sample::retrieve_typed(std::string *); liblsl-1.17.7/src/sample.h000066400000000000000000000150211517625163100153170ustar00rootroot00000000000000#ifndef SAMPLE_H #define SAMPLE_H #include "common.h" #include "forward.h" #include #include #include #include #include #include #include namespace lsl { // assert that the target CPU can represent the double-precision timestamp format required by LSL static_assert(sizeof(double) == 8, "Target arch has unexpected double size (!=8)"); // constants used in the network protocol const uint8_t TAG_DEDUCED_TIMESTAMP = 1; const uint8_t TAG_TRANSMITTED_TIMESTAMP = 2; /// channel format properties const uint8_t format_sizes[] = {0, sizeof(float), sizeof(double), sizeof(std::string), sizeof(int32_t), sizeof(int16_t), sizeof(int8_t), 8}; const bool format_ieee754[] = {false, std::numeric_limits::is_iec559, std::numeric_limits::is_iec559, false, false, false, false, false}; const bool format_subnormal[] = {false, std::numeric_limits::has_denorm != std::denorm_absent, std::numeric_limits::has_denorm != std::denorm_absent, false, false, false, false, false}; const bool format_integral[] = {false, false, false, false, true, true, true, true}; const bool format_float[] = {false, true, true, false, false, false, false, false}; /// A factory to create samples of a given format/size. Must outlive all of its created samples. class factory { public: /** * Create a new factory and optionally pre-allocate samples. * @param fmt Sample format * @param num_chans nr of channels * @param num_reserve nr of samples to pre-allocate in the storage pool */ factory(lsl_channel_format_t fmt, uint32_t num_chans, uint32_t num_reserve); /// Destroy the factory and delete all of its samples. ~factory(); /// Create a new sample with a given timestamp and pushthrough flag. /// Only one thread may call this function for a given factory object. sample_p new_sample(double timestamp, bool pushthrough); /// Reclaim a sample that's no longer used. void reclaim_sample(sample *s); private: /// Pop a sample from the freelist (multi-producer/single-consumer queue by Dmitry Vjukov) sample *pop_freelist(); /// Return the address of a pre-allocated sample, #0 is the sentinel value sample *sample_by_index(std::size_t idx) const { return reinterpret_cast(storage_ + idx * sample_size_); } /// Return the address of the sentinel value sample *sentinel() const { return sample_by_index(0); } friend class sample; /// the channel format to construct samples with const lsl_channel_format_t fmt_; /// the number of channels to construct samples with const uint32_t num_chans_; /// size of a sample, in bytes const uint32_t sample_size_; /// size of the allocated storage, in bytes const uint32_t storage_size_; /// a slab of storage for pre-allocated samples char *const storage_; /// head of the freelist std::atomic head_; /// tail of the freelist std::atomic tail_; }; /** * The sample data type. * Used to represent samples across the library's various buffers and can be serialized (e.g., over * the network). */ class sample { public: friend class factory; /// whether the sample shall be buffered or pushed through bool pushthrough{false}; private: /// the channel format const lsl_channel_format_t format_; /// number of channels const uint32_t num_channels_; /// reference count used by sample_p std::atomic refcount_; /// linked list of samples, for use in a freelist std::atomic next_; /// the factory used to reclaim this sample factory *const factory_; /// time-stamp of the sample double timestamp_{0.0}; /// the data payload begins here alignas(8) int32_t data_{0}; public: // === Construction === /// Destructor for a sample. ~sample() noexcept; double ×tamp() { return timestamp_; } /// Delete a sample. void operator delete(void *x) noexcept; /// Test for equality with another sample. bool operator==(const sample &rhs) const noexcept; bool operator!=(const sample &rhs) const noexcept { return !(*this == rhs); } std::size_t datasize() const { return format_sizes[format_] * static_cast(num_channels_); } uint32_t num_channels() const { return num_channels_; } // === type-safe accessors === /// Assign an array of numeric values (with type conversions). template void assign_typed(const T *s); /// Retrieve an array of numeric values (with type conversions). template void retrieve_typed(T *d); // === untyped accessors === /// Assign numeric data to the sample. void assign_untyped(const void *newdata); /// Retrieve numeric data from the sample. void retrieve_untyped(void *newdata); // === serialization functions === /// Serialize a sample to a stream buffer (protocol 1.10). void save_streambuf(std::streambuf &sb, int protocol_version, bool reverse_byte_order, void *scratchpad = nullptr) const; /// Deserialize a sample from a stream buffer (protocol 1.10). void load_streambuf(std::streambuf &sb, int protocol_version, bool reverse_byte_order, bool suppress_subnormals); /// Convert the endianness of channel data in-place. static void convert_endian(void *data, uint32_t n, uint32_t width); /// Serialize a sample into a portable archive (protocol 1.00). void serialize(eos::portable_oarchive &ar, uint32_t archive_version) const; /// Deserialize a sample from a portable archive (protocol 1.00). void serialize(eos::portable_iarchive &ar, uint32_t archive_version); /// Serialize (read/write) the channel data. template void serialize_channels(Archive &ar, uint32_t archive_version); /// Assign a test pattern to the sample (for protocol validation) sample &assign_test_pattern(int offset = 1); private: /// Construct a new sample for a given channel format/count combination. sample(lsl_channel_format_t fmt, uint32_t num_channels, factory *fact); /// Increment ref count. friend void intrusive_ptr_add_ref(sample *s) { s->refcount_.fetch_add(1, std::memory_order_relaxed); } /// Decrement ref count and reclaim if unreferenced. friend void intrusive_ptr_release(sample *s) { if (s->refcount_.fetch_sub(1, std::memory_order_release) == 1) { std::atomic_thread_fence(std::memory_order_acquire); s->factory_->reclaim_sample(s); } } friend void *iterhelper(sample &s) noexcept { return reinterpret_cast(&s.data_); } friend const void *iterhelper(const sample &s) noexcept { return reinterpret_cast(&s.data_); } template void conv_from(const U *src); template void conv_into(U *dst); }; } // namespace lsl #endif liblsl-1.17.7/src/send_buffer.cpp000066400000000000000000000037701517625163100166630ustar00rootroot00000000000000#include "send_buffer.h" #include "consumer_queue.h" #include #include #include #include using namespace lsl; std::shared_ptr send_buffer::new_consumer(int max_buffered) { max_buffered = max_buffered ? std::min(max_buffered, max_capacity_) : max_capacity_; return std::make_shared(max_buffered, shared_from_this()); } /** * Push a sample onto the send buffer. * Will subsequently be seen by all consumers. */ void send_buffer::push_sample(const sample_p &s) { std::lock_guard lock(consumers_mut_); for (auto &consumer : consumers_) consumer->push_sample(s); } /// Registered a new consumer. void send_buffer::register_consumer(consumer_queue *q) { { std::lock_guard lock(consumers_mut_); if (std::find(consumers_.begin(), consumers_.end(), q) != consumers_.end()) LOG_F(WARNING, "Duplicate consumer queue in send buffer"); else consumers_.push_back(q); } some_registered_.notify_all(); } /// Unregister a previously registered consumer. void send_buffer::unregister_consumer(consumer_queue *q) { std::lock_guard lock(consumers_mut_); auto pos = std::find(consumers_.begin(), consumers_.end(), q); // If not found, log an error and return if (pos == consumers_.end()) { LOG_F(ERROR, "Trying to remove consumer queue not in send buffer"); return; } // Put the element to be removed at the end (if it isn't there already) and // remove the last element if (*pos != consumers_.back()) std::swap(*pos, consumers_.back()); consumers_.pop_back(); } /// Check whether there currently are consumers. bool send_buffer::have_consumers() { std::lock_guard lock(consumers_mut_); return some_registered(); } /// Wait until some consumers are present. bool send_buffer::wait_for_consumers(double timeout) { std::unique_lock lock(consumers_mut_); return some_registered_.wait_for( lock, std::chrono::duration(timeout), [this]() { return some_registered(); }); } liblsl-1.17.7/src/send_buffer.h000066400000000000000000000052031517625163100163210ustar00rootroot00000000000000#ifndef SEND_BUFFER_H #define SEND_BUFFER_H #include "common.h" #include "forward.h" #include #include #include #include namespace lsl { /** * A thread-safe single-producer multiple-consumer queue where each consumer gets every pushed * sample. If the bounded capacity is exhausted, the oldest samples will be erased. * * @note The send_buffer is actually just a dispatcher that distributes the data to * producer-consumer queues (each of which can have its own capacity preferences). * The ownership of the send_buffer is shared between the consumer_queues and the owner of the * send_buffer. */ class send_buffer : public std::enable_shared_from_this { using consumer_set = std::vector; public: /** * Create a new send buffer. * @param max_capacity Hard upper bound on queue capacity beyond which the oldest samples will * be dropped. */ send_buffer(int max_capacity) : max_capacity_(max_capacity) {} /** * Add a new consumer queue to the buffer. * * Each consumer will get all samples (although the oldest samples will be dropped when the * buffer capacity is overrun). The consumer is automatically removed upon destruction. * @param max_buffered If non-zero, the queue size for this consumer will be constrained to be * no larger than this value. Note that the actual queue size will never exceed the max_capacity * of the send_buffer (so this is a global limit). * @return Shared pointer to the newly created consumer. */ std::shared_ptr new_consumer(int max_buffered = 0); /// Push a sample onto the send buffer that will subsequently be received by all consumers. void push_sample(const sample_p &s); /// Wait until some consumers are present. bool wait_for_consumers(double timeout = FOREVER); /// Check whether any consumer is currently registered. bool have_consumers(); private: friend class consumer_queue; /// Registered a new consumer (called by the consumer_queue). void register_consumer(consumer_queue *q); /// Unregister a previously registered consumer (called by the consumer_queue). void unregister_consumer(consumer_queue *q); /// wait_for_consumers is waiting for this bool some_registered() const { return !consumers_.empty(); } /// maximum capacity beyond which the oldest samples will be dropped int max_capacity_; /// a set of registered consumer queues consumer_set consumers_; /// mutex to protect the integrity of consumers_ std::mutex consumers_mut_; /// condition variable signaling that a consumer has registered std::condition_variable some_registered_; }; } // namespace lsl #endif liblsl-1.17.7/src/socket_utils.cpp000066400000000000000000000025441517625163100171070ustar00rootroot00000000000000#include "socket_utils.h" #include "api_config.h" #include "common.h" template uint16_t bind_port_in_range_(Socket &sock, Protocol protocol) { const auto *cfg = lsl::api_config::get_instance(); asio::error_code ec; for (uint16_t port = cfg->base_port(), e = port + cfg->port_range(); port < e; port++) { sock.bind(typename Protocol::endpoint(protocol, port), ec); if (ec == asio::error::address_in_use) continue; if (!ec) return port; } if (cfg->allow_random_ports()) { // bind to port 0, i.e. let the operating system select a free port sock.bind(typename Protocol::endpoint(protocol, 0), ec); // query and return the port the socket was bound to if (!ec) return sock.local_endpoint().port(); } throw std::runtime_error( "All local ports were found occupied. You may have more open outlets on this machine " "than your PortRange setting allows (see " "https://labstreaminglayer.readthedocs.io/info/network-connectivity.html" ") or you have a problem with your network configuration."); } uint16_t lsl::bind_port_in_range(udp_socket &sock, asio::ip::udp protocol) { return bind_port_in_range_(sock, protocol); } uint16_t lsl::bind_and_listen_to_port_in_range( tcp_acceptor &acc, asio::ip::tcp protocol, int backlog) { uint16_t port = bind_port_in_range_(acc, protocol); acc.listen(backlog); return port; } liblsl-1.17.7/src/socket_utils.h000066400000000000000000000016761517625163100165610ustar00rootroot00000000000000#ifndef SOCKET_UTILS_H #define SOCKET_UTILS_H #include #include using udp_socket = asio::basic_datagram_socket; using tcp_socket = asio::basic_stream_socket; using tcp_acceptor = asio::basic_socket_acceptor; namespace lsl { inline asio::chrono::milliseconds timeout_sec(double timeout_seconds) { return asio::chrono::milliseconds(static_cast(1000 * timeout_seconds)); } /// Bind a socket to a free port in the configured port range or throw an error otherwise. uint16_t bind_port_in_range(udp_socket &sock, asio::ip::udp protocol); /// Bind and listen to an acceptor on a free port in the configured port range or throw an error. uint16_t bind_and_listen_to_port_in_range( tcp_acceptor &acc, asio::ip::tcp protocol, int backlog); } // namespace lsl #endif liblsl-1.17.7/src/stream_info_impl.cpp000066400000000000000000000314401517625163100177230ustar00rootroot00000000000000#include "stream_info_impl.h" #include "api_config.h" #include "util/cast.hpp" #include "util/uuid.hpp" #include #include #include #include #include #include #include #include namespace lsl { using namespace pugi; using lsl::to_string; // Helper to support both pugixml < 1.15 (no string_view) and >= 1.15 #if PUGIXML_VERSION >= 1150 inline std::string_view pugi_str(const std::string &value) { return value; } #else inline const char *pugi_str(const std::string &value) { return value.c_str(); } #endif stream_info_impl::stream_info_impl() : channel_count_(0), nominal_srate_(0), channel_format_(cft_undefined), version_(0), v4data_port_(0), v4service_port_(0), v6data_port_(0), v6service_port_(0), created_at_(0) { // initialize XML document write_xml(doc_); } stream_info_impl::stream_info_impl(const std::string &name, std::string type, int channel_count, double nominal_srate, lsl_channel_format_t channel_format, std::string source_id) : name_(name), type_(std::move(type)), channel_count_(channel_count), nominal_srate_(nominal_srate), channel_format_(channel_format), source_id_(std::move(source_id)), version_(api_config::get_instance()->use_protocol_version()), v4data_port_(0), v4service_port_(0), v6data_port_(0), v6service_port_(0), created_at_(0) { if (name.empty()) throw std::invalid_argument("The name of a stream must be non-empty."); if (channel_count < 0) throw std::invalid_argument("The channel_count of a stream must be nonnegative."); if (nominal_srate < 0) throw std::invalid_argument("The nominal sampling rate of a stream must be nonnegative."); if (channel_format < 0 || channel_format > 7) throw std::invalid_argument("The stream info was created with an unknown channel format " + to_string(static_cast(channel_format))); // initialize XML document write_xml(doc_); } template void append_text_node(xml_node &node, const char *name, const T &value) { node.append_child(name).append_child(node_pcdata).text().set(value); } template <> void append_text_node(xml_node &node, const char *name, const std::string &value) { node.append_child(name).append_child(node_pcdata).set_value(pugi_str(value)); } void stream_info_impl::write_xml(xml_document &doc) { const char *channel_format_strings[] = { "undefined", "float32", "double64", "string", "int32", "int16", "int8", "int64"}; xml_node info = doc.append_child("info"); append_text_node(info, "name", name_); append_text_node(info, "type", type_); append_text_node(info, "channel_count", channel_count_); append_text_node(info, "channel_format", channel_format_strings[channel_format_]); append_text_node(info, "source_id", source_id_); // floating point fields: use locale independent to_string function append_text_node(info, "nominal_srate", to_string(nominal_srate_)); append_text_node(info, "version", to_string(version_ / 100.)); append_text_node(info, "created_at", to_string(created_at_)); append_text_node(info, "uid", uid_); append_text_node(info, "session_id", session_id_); append_text_node(info, "hostname", hostname_); append_text_node(info, "v4address", v4address_); append_text_node(info, "v4data_port", v4data_port_); append_text_node(info, "v4service_port", v4service_port_); append_text_node(info, "v6address", v6address_); append_text_node(info, "v6data_port", v6data_port_); append_text_node(info, "v6service_port", v6service_port_); info.append_child("desc"); } template void get_bounded_child_val(xml_node &node, const char *child_name, T &target, int min, int max = 0) { const auto *value = node.child_value(child_name); int intval = std::stoi(value); if (intval < min || (max != 0 && intval > max)) { std::string errmsg{child_name}; errmsg+=" must be >="; errmsg+=std::to_string(min); if(max) errmsg+= " and <=" + std::to_string(max); throw std::runtime_error(errmsg); } target = static_cast(intval); } void stream_info_impl::read_xml(xml_document &doc) { try { xml_node info = doc.child("info"); // name name_ = info.child_value("name"); if (name_.empty()) throw std::runtime_error("Received a stream info with empty field."); // type type_ = info.child_value("type"); get_bounded_child_val(info, "channel_count", channel_count_, 0); get_bounded_child_val(info, "nominal_srate", nominal_srate_, 0); nominal_srate_ = std::stod(info.child_value("nominal_srate")); std::string fmt(info.child_value("channel_format")); if (fmt == "float32") channel_format_ = cft_float32; else if (fmt == "double64") channel_format_ = cft_double64; else if (fmt == "string") channel_format_ = cft_string; else if (fmt == "int32") channel_format_ = cft_int32; else if (fmt == "int16") channel_format_ = cft_int16; else if (fmt == "int8") channel_format_ = cft_int8; else if (fmt == "int64") channel_format_ = cft_int64; else throw std::runtime_error("Invalid channel format " + fmt); // source_id source_id_ = info.child_value("source_id"); version_ = (int)(std::stod(info.child_value("version")) * 100); if (version_ <= 0) throw std::runtime_error("The version of the given stream info is invalid."); // created_at created_at_ = std::stod(info.child_value("created_at")); // uid uid_ = info.child_value("uid"); if (uid_.empty()) throw std::runtime_error("The UID of the given stream info is empty."); // session_id session_id_ = info.child_value("session_id"); // hostname hostname_ = info.child_value("hostname"); v4address_ = info.child_value("v4address"); get_bounded_child_val(info, "v4data_port", v4data_port_, 0, 65535); get_bounded_child_val(info, "v4service_port", v4service_port_, 0, 65535); v6address_ = info.child_value("v6address"); get_bounded_child_val(info, "v6data_port", v6data_port_, 0, 65535); get_bounded_child_val(info, "v6service_port", v6service_port_, 0, 65535); } catch (std::exception &e) { // reset the stream info to blank state *this = stream_info_impl(); name_ = (std::string("(invalid: ") += e.what()) += ')'; } } // === Protocol Support Operations Implementation === std::string stream_info_impl::to_shortinfo_message() { // make a new document (with an empty field) xml_document tmp; write_xml(tmp); // write it to a stream std::ostringstream os; tmp.save(os); // and get the string return os.str(); } void stream_info_impl::from_shortinfo_message(const std::string &m) { // load the doc from the message string doc_.load_buffer(m.c_str(), m.size()); // and assign all the struct fields, too... read_xml(doc_); } std::string stream_info_impl::to_fullinfo_message() { // write the doc to a stream std::ostringstream os; doc_.save(os); // and get the string return os.str(); } void stream_info_impl::from_fullinfo_message(const std::string &m) { // load the doc from the message string doc_.load_buffer(m.c_str(), m.size()); // and assign all the struct fields, too... read_xml(doc_); } bool stream_info_impl::matches_query(const std::string &query, bool nocache) { return cached_.matches_query(doc_, query, nocache); } bool query_cache::matches_query(const xml_document &doc, const std::string &query, bool nocache) { if (query.empty()) return true; std::lock_guard lock(cache_mut_); decltype(cache)::iterator it; if (!nocache && (it = cache.find(query)) != cache.end()) { // the sign bit encodes if the query matches or not bool matches = it->second > 0; // update the last-use time stamp it->second = ++query_cache_age * (matches ? 1 : -1); // return cached match return matches; } // not found in cache try { // compute whether it matches bool matched = pugi::xpath_query(query.c_str()).evaluate_boolean(doc.first_child()); auto max_cached = (std::size_t)api_config::get_instance()->max_cached_queries(); if (nocache || max_cached == 0) return matched; cache.insert(std::make_pair(query, ++query_cache_age * (matched ? 1 : -1))); // remove n/2 oldest results to make room for new entries if (cache.size() > max_cached) { // Find out the median cache entry age std::vector agevec; agevec.reserve(cache.size()); for (auto &val : cache) agevec.push_back(std::abs(val.second)); auto middle = agevec.begin() + max_cached / 2; std::nth_element(agevec.begin(), middle, agevec.end()); auto oldest_to_keep = *middle; // Remove all elements older than the median age for (auto it = cache.begin(); it != cache.end();) if (abs(it->second) <= oldest_to_keep) it = cache.erase(it); else ++it; } return matched; } catch (std::exception &e) { LOG_F(WARNING, "Query \"%s\" error: %s", query.c_str(), e.what()); return false; } } int stream_info_impl::channel_bytes() const { const int channel_format_sizes[] = {0, sizeof(float), sizeof(double), sizeof(std::string), sizeof(int32_t), sizeof(int16_t), sizeof(int8_t), 8}; return channel_format_sizes[channel_format_]; } xml_node stream_info_impl::desc() { return doc_.child("info").child("desc"); } xml_node stream_info_impl::desc() const { return doc_.child("info").child("desc"); } uint32_t lsl::stream_info_impl::calc_transport_buf_samples( int32_t requested_len, lsl_transport_options_t flags) const { if ((flags & transp_bufsize_samples) && (flags & transp_bufsize_thousandths)) throw std::invalid_argument( "transp_bufsize_samples and transp_bufsize_thousandths are mutually exclusive"); int32_t buf_samples; if (flags & transp_bufsize_samples) buf_samples = requested_len; else if (nominal_srate() == LSL_IRREGULAR_RATE) buf_samples = requested_len * 100; else buf_samples = static_cast(nominal_srate() * requested_len); if (flags & transp_bufsize_thousandths) buf_samples /= 1000; buf_samples = (buf_samples > 0) ? buf_samples : 1; return buf_samples; } void stream_info_impl::version(int v) { version_ = v; doc_.child("info").child("version").first_child().set_value(pugi_str(to_string(version_ / 100.))); } void stream_info_impl::created_at(double v) { created_at_ = v; doc_.child("info").child("created_at").first_child().set_value(pugi_str(to_string(created_at_))); } void stream_info_impl::uid(const std::string &v) { uid_ = v; doc_.child("info").child("uid").first_child().set_value(pugi_str(uid_)); } const std::string& stream_info_impl::reset_uid() { uid(UUID::random().to_string()); return uid_; } void stream_info_impl::session_id(const std::string &v) { session_id_ = v; doc_.child("info").child("session_id").first_child().set_value(pugi_str(session_id_)); } void stream_info_impl::hostname(const std::string &v) { hostname_ = v; doc_.child("info").child("hostname").first_child().set_value(pugi_str(hostname_)); } void stream_info_impl::v4address(const std::string &v) { v4address_ = v; doc_.child("info").child("v4address").first_child().set_value(pugi_str(v4address_)); } void stream_info_impl::v4data_port(uint16_t v) { v4data_port_ = v; doc_.child("info").child("v4data_port").first_child().text().set(v4data_port_); } void stream_info_impl::v4service_port(uint16_t v) { v4service_port_ = v; doc_.child("info").child("v4service_port").first_child().text().set(v4service_port_); } void stream_info_impl::v6address(const std::string &v) { v6address_ = v; doc_.child("info").child("v6address").first_child().set_value(pugi_str(v6address_)); } void stream_info_impl::v6data_port(uint16_t v) { v6data_port_ = v; doc_.child("info").child("v6data_port").first_child().text().set(v6data_port_); } void stream_info_impl::v6service_port(uint16_t v) { v6service_port_ = v; doc_.child("info").child("v6service_port").first_child().text().set(v6service_port_); } stream_info_impl &stream_info_impl::operator=(stream_info_impl const &rhs) { if (this == &rhs) return *this; name_ = rhs.name_; type_ = rhs.type_; channel_count_ = rhs.channel_count_; nominal_srate_ = rhs.nominal_srate_; channel_format_ = rhs.channel_format_; source_id_ = rhs.source_id_; version_ = rhs.version_; v4address_ = rhs.v4address_; v4data_port_ = rhs.v4data_port_; v4service_port_ = rhs.v4service_port_; v6address_ = rhs.v6address_; v6data_port_ = rhs.v6data_port_; v6service_port_ = rhs.v6service_port_; uid_ = rhs.uid_; created_at_ = rhs.created_at_; session_id_ = rhs.session_id_; hostname_ = rhs.hostname_; doc_.reset(rhs.doc_); return *this; } stream_info_impl::stream_info_impl(const stream_info_impl &rhs) : name_(rhs.name_), type_(rhs.type_), channel_count_(rhs.channel_count_), nominal_srate_(rhs.nominal_srate_), channel_format_(rhs.channel_format_), source_id_(rhs.source_id_), version_(rhs.version_), v4address_(rhs.v4address_), v4data_port_(rhs.v4data_port_), v4service_port_(rhs.v4service_port_), v6address_(rhs.v6address_), v6data_port_(rhs.v6data_port_), v6service_port_(rhs.v6service_port_), uid_(rhs.uid_), created_at_(rhs.created_at_), session_id_(rhs.session_id_), hostname_(rhs.hostname_) { doc_.reset(rhs.doc_); } } // namespace lsl liblsl-1.17.7/src/stream_info_impl.h000066400000000000000000000212011517625163100173620ustar00rootroot00000000000000#ifndef STREAM_INFO_IMPL_H #define STREAM_INFO_IMPL_H #include "common.h" #include #include #include #include #include namespace lsl { /// LRU cache for queries class query_cache { std::unordered_map cache; int query_cache_age{0}; std::mutex cache_mut_; public: bool matches_query(const pugi::xml_document &doc, const std::string &query, bool nocache); }; /** * Actual implementation of the stream_info class. * * The stream_info class forwards all operations to an instance of this class. */ class stream_info_impl { public: stream_info_impl(); /** * @brief Construct a new stream_info object. * Here is where the core format and identity information of a stream is specified. * @param name Name of the stream (usually a CamelCase word). * Describes the device (or product series) that this stream makes available (for use by * programs, experimenters, or data analysts). * @param type Content type of the stream. Please see https://github.com/sccn/xdf/wiki/Meta-Data * (or web search for: XDF meta-data) for pre-defined content-type names, but you can also make * up your own. * @param channel_count Number of channels in a sample. Each channel in a sample has the same * format and each sample in a stream has the same number of channels. * @param nominal_srate The sampling rate (in Hz) advertised by the device, if regular * (otherwise set to ::IRREGULAR_RATE). * @param channel_format Format/type of each channel. If your channels have different formats, * use the largest type that can hold them all (e.g., double) or consider supplying multiple * streams. * @param source_id Unique identifier of the source or device if available (e.g., serial * number). Allows for advanced failure recovery. */ stream_info_impl(const std::string &name, std::string type, int channel_count, double nominal_srate, lsl_channel_format_t channel_format, std::string source_id); /// Copy constructor. Needs special handling because xml_document is non-copyable. stream_info_impl(const stream_info_impl &rhs); /// Assignment operator. Needs extra handling because xml_document is non-copyable. stream_info_impl &operator=(const stream_info_impl &rhs); // === Protocol Support Operations === /** * Get the short-info message according to this stream_info. * * The short-info message is a shortened xml representation of the stream_info, excluding the * .desc() field (which can be megabytes in size). * This message is sent by a stream outlet in response to a variety of queries. */ std::string to_shortinfo_message(); /** * Initialize a stream_info from a short-info message. * * This functions resets all fields of the stream_info accoridng to the message. The .desc() * field will be empty. */ void from_shortinfo_message(const std::string &m); /** * Get the full-info message for this stream_info. * * This is a complete XML representation of the stream_info. */ std::string to_fullinfo_message(); /** * Initialize a stream_info from a full-info message. * * This functions resets all fields of the stream_info accoridng to the message. */ void from_fullinfo_message(const std::string &m); /** * Test whether this stream info matches the given query string. * * The info "matches" if the given XPath 1.0 query string returns a non-empty node set. * @return Whether stream info is matched by the query string. */ bool matches_query(const std::string &query, bool nocache = false); // // === Data Information Getters === // /// Get the name of a stream. const std::string &name() const { return name_; } /// Get the content type of the stream. const std::string &type() const { return type_; } /// Get the number of channels of a stream. uint32_t channel_count() const { return channel_count_; } /// Get the sampling rate of a stream (in Hz) as advertised by the device. double nominal_srate() const { return nominal_srate_; } /// Get the channel format of a stream. lsl_channel_format_t channel_format() const { return channel_format_; } /// Get the unique source identifier of a stream, if any. const std::string &source_id() const { return source_id_; } /// Get the number of bytes per channel (returns 0 for string-typed channels). int channel_bytes() const; /// Get the number of bytes per sample (returns 0 for string-typed channels). int sample_bytes() const { return channel_count_ * channel_bytes(); } // // === Network Identity Information Getters/Setters === // /// Get/Set the info / protocol version used by the stream. int version() const { return version_; } void version(int v); /** * Get/Set the creation time stamp of a stream. * * This is the time stamp (via local_clock()) of when the stream was first created (in the time * domain of the providing machine). */ double created_at() const { return created_at_; } void created_at(double v); /** * Get/Set the UID of a stream instance (once assigned). * * This is a unique identifier of the stream instance, and is guaranteed to be different across * multiple instantiations of the same stream (e.g., after a re-start). */ const std::string &uid() const { return uid_; } void uid(const std::string &v); /// Reset the UID to a randomly generated UUID4 const std::string &reset_uid(); /** * Get/Set the session id for the given stream. * * The session ID is an optional human-assigned identifier of the recording session; only inlets * and outlets that have the same session id can be paired with each other to avoid accidentally * recording from an unrelated concurrent session on the same network. * The session id can be set via the configuration file (api_config::session_id). */ const std::string &session_id() const { return session_id_; } void session_id(const std::string &v); /// Get/Set the provider hostname for the given stream. const std::string &hostname() const { return hostname_; } void hostname(const std::string &v); /** * Get/Set the host name or IP address where the stream is hosted. * * This may be a fully resolved address (such as testing.uscd.edu) or an IPv4 or IPv6 * address in string form. */ const std::string &v4address() const { return v4address_; } void v4address(const std::string &v); /** * @brief Get/Set the TCP data port where the stream is hosted (once assigned). * This port is internally used to obtain data and meta-data from a stream. */ uint16_t v4data_port() const { return v4data_port_; } void v4data_port(uint16_t v); /** * Get/Set the UDP service port where the stream is hosted (once assigned). * This port is internally used to obtain time correction information for a stream. */ uint16_t v4service_port() const { return v4service_port_; } void v4service_port(uint16_t v); /** * Get/Set the host name or IP address where the stream is hosted. * This may be a fully resolved address (such as testing.uscd.edu) or an IPv4 or IPv6 address in * string form. */ const std::string &v6address() const { return v6address_; } void v6address(const std::string &v); /** * Get/Set the TCP data port where the stream is hosted (once assigned). * This port is internally used to obtain data and meta-data from a stream. */ uint16_t v6data_port() const { return v6data_port_; } void v6data_port(uint16_t v); /** * Get/Set the UDP service port where the stream is hosted (once assigned). * This port is internally used to obtain time correction information for a stream. */ uint16_t v6service_port() const { return v6service_port_; } void v6service_port(uint16_t v); /// Get the (editable) XML description of a stream. pugi::xml_node desc(); pugi::xml_node desc() const; /// helper function to calculate the buffer size in samples for inlets and outlets uint32_t calc_transport_buf_samples(int32_t requested_len, lsl_transport_options_t flags) const; protected: /// Create and assign the XML DOM structure based on the class fields. void write_xml(pugi::xml_document &doc); /// Read the class fields from an XML DOM structure. void read_xml(pugi::xml_document &doc); private: // data information std::string name_; std::string type_; uint32_t channel_count_; double nominal_srate_; lsl_channel_format_t channel_format_; std::string source_id_; // auto-generated network information int version_; std::string v4address_; uint16_t v4data_port_; uint16_t v4service_port_; std::string v6address_; uint16_t v6data_port_; uint16_t v6service_port_; std::string uid_; double created_at_; std::string session_id_; std::string hostname_; // XML representation pugi::xml_document doc_; // cached query results query_cache cached_; }; } // namespace lsl #endif liblsl-1.17.7/src/stream_inlet_impl.h000066400000000000000000000371721517625163100175600ustar00rootroot00000000000000#ifndef STREAM_INLET_IMPL_H #define STREAM_INLET_IMPL_H #include "common.h" #include "data_receiver.h" #include "info_receiver.h" #include "inlet_connection.h" #include "time_postprocessor.h" #include "time_receiver.h" #include namespace lsl { /** * A stream inlet. * * Inlets are used to receive streaming data (and meta-data) from the lab network. * This class is just a thin wrapper (or facade) around its four components to which it adds no * extra functionality. */ class stream_inlet_impl { public: /** * Construct a new stream inlet from a resolved stream info. * * @param info A resolved stream info object (as coming from one of the resolver functions). * @param max_buflen Optionally the maximum amount of data to buffer in samples (per-channel). * Recording applications want to use a fairly large buffer size here, while real-time * applications want to only buffer as much as they need to perform their next calculation. * @param max_chunklen Optionally the maximum size, in samples, at which chunks are transmitted * (the default corresponds to the chunk sizes used by the sender). * Recording applications can use a generous size here (leaving it to the network how to pack * things), while real-time applications may want a finer (perhaps 1-sample) granularity. * @param recover Try to silently recover lost streams that are recoverable (=those that that * have a source_id set). * In all other cases (recover is false or the stream is not recoverable) a lsl::lost_error * is thrown where indicated if the stream's source is lost (e.g. due to an app or computer * crash). */ stream_inlet_impl(const stream_info_impl &info, int32_t max_buflen = 360, int32_t max_chunklen = 0, bool recover = true) : conn_(info, recover), info_receiver_(conn_), time_receiver_(conn_), data_receiver_(conn_, max_buflen, max_chunklen), postprocessor_([this]() { return time_receiver_.time_correction(5); }, [this]() { return conn_.current_srate(); }, [this]() { return time_receiver_.was_reset(); }) { ensure_lsl_initialized(); conn_.engage(); } /// Destructor. The stream will stop reading from the source if destroyed. ~stream_inlet_impl() { try { conn_.disengage(); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during inlet shutdown: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during stream inlet shutdown."); } } /** * Pull a sample from the inlet and read it into a vector of values. * * Handles type checking & conversion, allocates the memory in the vector if necessary. * @param sample An STL vector to hold the resulting values. * @param block If true, the function will block until a sample is available; otherwise, it will * return 0.0 and no new data. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. This is only necessary if the clocks of the source and destination * machine are not synchronized to high enough precision. */ double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } double pull_sample(std::vector &data, double timeout = FOREVER) { data.resize(conn_.type_info().channel_count()); return pull_sample(data.data(), (int32_t)data.size(), timeout); } /** * Pull a sample from the inlet and read it into a pointer to values. * * Handles type checking & conversion. * @param buffer A pointer to hold the resulting values. * @param buffer_elements The number of samples allocated in the buffer. * @note it is the responsibility of the user to allocate enough memory. * @param block If true, the function will block until a sample is available; otherwise, it will * return 0.0 and no new data. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. This is only necessary if the clocks of the source and destination * machine are not synchronized to high enough precision. */ double pull_sample(float *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(double *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(int64_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(int32_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(int16_t *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(char *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } double pull_sample(std::string *buffer, int32_t buffer_elements, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } template double pull_sample_noexcept(T *buffer, int32_t buffer_elements, double timeout = FOREVER, lsl_error_code_t *ec = nullptr) noexcept { lsl_error_code_t dummy; if (!ec) ec = &dummy; *ec = lsl_no_error; try { return postprocess(data_receiver_.pull_sample_typed(buffer, buffer_elements, timeout)); } catch (timeout_error &) { *ec = lsl_timeout_error; } catch (lost_error &) { *ec = lsl_lost_error; } catch (std::invalid_argument &) { *ec = lsl_argument_error; } catch (std::range_error &) { *ec = lsl_argument_error; } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error in %s: %s", __func__, e.what()); *ec = lsl_internal_error; } return 0.0; } /** * Pull a sample from the inlet and read it into a pointer to raw data. * * No type checking or conversions are done (not recommended!). Do not use for * variable-size/string-formatted streams. * @param sample A pointer to hold the resulting raw sample data. * @param buffer_bytes The number of bytes allocated in the buffer. Note: it is the * responsibility of the user to allocate enough memory. * @param block If true, the function will block until a sample is available; otherwise, it will * return 0.0 and no data. * @return The capture time of the sample on the remote machine, or 0.0 if no new sample was * available. To remap this time stamp to the local clock, add the value returned by * .time_correction() to it. This is only necessary if the clocks of the source and destination * machine are not synchronized to high enough precision. */ double pull_numeric_raw(void *sample, int32_t buffer_bytes, double timeout = FOREVER) { return postprocess(data_receiver_.pull_sample_untyped(sample, buffer_bytes, timeout)); } /** * Pull a chunk of data from the inlet. * * @warning The provided buffer size is measured in channel values (e.g., floats), not samples. * @param data_buffer A pointer to a buffer of data values where the results shall be stored. * @param timestamp_buffer A pointer to a buffer of timestamp values where time stamps shall be * stored. If this is NULL, no time stamps will be returned. * @param data_buffer_elements The size of the data buffer, in channel data elements (of type * T). Must be a multiple of the stream's channel count. * @param timestamp_buffer_elements The size of the timestamp buffer. If a timestamp buffer is * provided then this must correspond to the same number of samples as data_buffer_elements. * @param timeout The timeout for this operation, if any. When the timeout expires, the function * may return before the entire buffer is filled. The default value of 0.0 will retrieve only * data available for immediate pickup. * @return data_elements_written Number of channel data elements written to the data buffer. * @throws lost_error (if the stream source has been lost). */ template uint32_t pull_chunk_multiplexed(T *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0) { std::size_t samples_written = 0, num_chans = info().channel_count(), max_samples = data_buffer_elements / num_chans; if (data_buffer_elements % num_chans != 0) throw std::runtime_error( "The number of buffer elements must be a multiple of the stream's channel count."); if (timestamp_buffer && max_samples != timestamp_buffer_elements) throw std::runtime_error( "The timestamp buffer must hold the same number of samples as the data buffer."); double end_time = timeout ? lsl_clock() + timeout : 0.0; for (samples_written = 0; samples_written < max_samples; samples_written++) { if (double ts = pull_sample(&data_buffer[samples_written * num_chans], (int) num_chans, timeout ? end_time - lsl_clock() : 0.0)) { if (timestamp_buffer) timestamp_buffer[samples_written] = ts; } else break; } return static_cast(samples_written * num_chans); } template uint32_t pull_chunk_multiplexed_noexcept(T *data_buffer, double *timestamp_buffer, std::size_t data_buffer_elements, std::size_t timestamp_buffer_elements, double timeout = 0.0, lsl_error_code_t *ec = nullptr) noexcept { lsl_error_code_t dummy; if (!ec) ec = &dummy; *ec = lsl_no_error; try { return pull_chunk_multiplexed(data_buffer, timestamp_buffer, data_buffer_elements, timestamp_buffer_elements, timeout); } catch (timeout_error &) { *ec = lsl_timeout_error; } catch (lost_error &) { *ec = lsl_lost_error; } catch (std::invalid_argument &) { *ec = lsl_argument_error; } catch (std::range_error &) { *ec = lsl_argument_error; } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error in %s: %s", __func__, e.what()); *ec = lsl_internal_error; } return 0; } /** * Retrieve the complete information of the given stream, including the extended description. * * Can be invoked at any time of the stream's lifetime. * @param timeout Timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been * lost). */ const stream_info_impl &info(double timeout = FOREVER) { return info_receiver_.info(timeout); } /** * Retrieve an estimated time correction offset for the given stream. * * The first call to this function takes several msec for an initial estimate, subsequent calls * are instantaneous. The correction offset is periodically re-estimated in the background (once * every few sec.). * @param remote_time The current time of the remote computer that was used to generate this * time_correction. * @param uncertainty. The maximum uncertainty of the given time correction. * @timeout Timeout for first time-correction estimate. * @return The time correction estimate. * @throws timeout_error If the initial estimate times out. */ double time_correction(double timeout = 2) { return time_receiver_.time_correction(timeout); } double time_correction(double *remote_time, double *uncertainty, double timeout = 2) { return time_receiver_.time_correction(remote_time, uncertainty, timeout); } /** * Set post-processing flags to use. * * By default, the inlet performs NO post-processing and returns the ground-truth time stamps, * which can then be manually synchronized using time_correction(), and then smoothed/dejittered * if desired. This function allows automating these two and possibly more operations. * * @warning when you enable this, you will no longer receive or be able to recover the original * time stamps. * @param flags An integer that is the result of bitwise OR'ing one or more options from * processing_options_t together (e.g., proc_clocksync|proc_dejitter); the default is to enable * all options. */ void set_postprocessing(uint32_t flags = proc_ALL) { postprocessor_.set_options(flags); } /** * Open a new data stream. * * All samples pushed in at the other end from this moment onwards will be queued and * eventually be delivered in response to pull_sample() or pull_chunk() calls. * A pull call without preceding begin_feed serves as an implicit begin_feed. * @param timeout Optional timeout of the operation (default: no timeout). * @throws timeout_error (if the timeout expires), or lost_error (if the stream source has been * lost). */ void open_stream(double timeout = FOREVER) { data_receiver_.open_stream(timeout); } /** * Close the current data stream. * * All samples still buffered or in flight will be dropped and the source will halt its * buffering of data for this inlet. If an application stops being interested in data from a * source (temporarily or not), it should call drop_stream() to not pressure the source outlet * to buffer unnecessarily large amounts of data (perhaps even running out of memory). */ void close_stream() { data_receiver_.close_stream(); } /** * Query the current size of the buffer, i.e. the number of samples that are buffered. * Note that this value may be inaccurate and should not be relied on for program logic. */ std::size_t samples_available() { return data_receiver_.samples_available(); } /// Flush the queue, return the number of dropped samples uint32_t flush() { int nskipped = data_receiver_.flush(); postprocessor_.skip_samples(nskipped); return nskipped; } /** Query whether the clock was potentially reset since the last call to was_clock_reset(). * * This is only interesting for applications that combine multiple time_correction values to * estimate clock drift and which should tolerate (rare) cases where the source machine was * hot-swapped or restarted. */ bool was_clock_reset() { return time_receiver_.was_reset(); } /// Override the half-time (forget factor) of the time-stamp smoothing. void smoothing_halftime(float value) { postprocessor_.smoothing_halftime(value); } private: /// post-process a time stamp double postprocess(double stamp) { return stamp ? postprocessor_.process_timestamp(stamp) : stamp; } /// the inlet connection inlet_connection conn_; // the content receiver classes info_receiver info_receiver_; time_receiver time_receiver_; data_receiver data_receiver_; /// class for post-processing time stamps time_postprocessor postprocessor_; }; } // namespace lsl #endif liblsl-1.17.7/src/stream_outlet_impl.cpp000066400000000000000000000157531517625163100203150ustar00rootroot00000000000000#include "stream_outlet_impl.h" #include "api_config.h" #include "sample.h" #include "send_buffer.h" #include "stream_info_impl.h" #include "tcp_server.h" #include "udp_server.h" #include #include #include namespace lsl { stream_outlet_impl::stream_outlet_impl(const stream_info_impl &info, int32_t chunk_size, int32_t requested_bufsize, lsl_transport_options_t flags) : sample_factory_(std::make_shared(info.channel_format(), info.channel_count(), static_cast( info.nominal_srate() ? info.nominal_srate() * api_config::get_instance()->outlet_buffer_reserve_ms() / 1000 : api_config::get_instance()->outlet_buffer_reserve_samples()))), chunk_size_(info.calc_transport_buf_samples(requested_bufsize, flags)), info_(std::make_shared(info)), send_buffer_(std::make_shared(chunk_size_)), io_ctx_data_(std::make_shared(1)), io_ctx_service_(std::make_shared(1)) { ensure_lsl_initialized(); const api_config *cfg = api_config::get_instance(); // instantiate IPv4 and/or IPv6 stacks (depending on settings) if (cfg->allow_ipv4()) try { instantiate_stack(udp::v4()); } catch (std::exception &e) { LOG_F(WARNING, "Could not instantiate IPv4 stack: %s", e.what()); } if (cfg->allow_ipv6()) try { instantiate_stack(udp::v6()); } catch (std::exception &e) { LOG_F(WARNING, "Could not instantiate IPv6 stack: %s", e.what()); } // create TCP data server tcp_server_ = std::make_shared(info_, io_ctx_data_, send_buffer_, sample_factory_, chunk_size_, cfg->allow_ipv4(), cfg->allow_ipv6()); // fail if both stacks failed to instantiate if (udp_servers_.empty()) throw std::runtime_error("Neither the IPv4 nor the IPv6 stack could be instantiated."); // get the async request chains set up tcp_server_->begin_serving(); for (auto &udp_server : udp_servers_) udp_server->begin_serving(); for (auto &responder : responders_) responder->begin_serving(); // and start the IO threads to handle them const std::string name{"IO_" + this->info().name().substr(0, 11)}; for (const auto &io : {io_ctx_data_, io_ctx_service_}) io_threads_.emplace_back(std::make_shared([io, name]() { loguru::set_thread_name(name.c_str()); while (!io->stopped()) { try { io->run(); return; } catch (std::exception &e) { LOG_F(ERROR, "Error during io_context processing: %s", e.what()); } } })); } void stream_outlet_impl::instantiate_stack(udp udp_protocol) { // get api_config const api_config *cfg = api_config::get_instance(); std::string listen_address = cfg->listen_address(); int multicast_ttl = cfg->multicast_ttl(); uint16_t multicast_port = cfg->multicast_port(); LOG_F(2, "%s: Trying to listen at address '%s'", info().name().c_str(), listen_address.c_str()); // create UDP time server udp_servers_.push_back(std::make_shared(info_, *io_ctx_service_, udp_protocol)); // create UDP multicast responders for (const auto &address : cfg->multicast_addresses()) { try { // use only addresses for the protocol that we're supposed to use here if (udp_protocol == udp::v4() ? address.is_v4() : address.is_v6()) responders_.push_back(std::make_shared( info_, *io_ctx_service_, address, multicast_port, multicast_ttl, listen_address)); } catch (std::exception &e) { LOG_F(WARNING, "Couldn't create multicast responder for %s (%s)", address.to_string().c_str(), e.what()); } } } stream_outlet_impl::~stream_outlet_impl() { try { // cancel all request chains tcp_server_->end_serving(); for (auto &udp_server : udp_servers_) udp_server->end_serving(); for (auto &responder : responders_) responder->end_serving(); // In theory, an io context should end quickly, but in practice it // might take a while. So we // 1. ask them to stop after they've finished their current task // 2. wait a bit // 3. stop the io contexts from our thread. Not ideal, but better than // 4. waiting a bit and // 5. detaching thread, i.e. letting it hang and continue tearing down // the outlet asio::post(*io_ctx_data_, [io = io_ctx_data_]() { io->stop(); }); asio::post(*io_ctx_service_, [io = io_ctx_service_]() { io->stop(); }); const char *name = this->info().name().c_str(); for (int try_nr = 0; try_nr <= 100; ++try_nr) { switch (try_nr) { case 0: DLOG_F(INFO, "Trying to join IO threads for %s", name); break; case 20: LOG_F(INFO, "Waiting for %s's IO threads to end", name); break; case 80: LOG_F(WARNING, "Stopping io_contexts for %s", name); io_ctx_data_->stop(); io_ctx_service_->stop(); for (std::size_t k = 0; k < io_threads_.size(); k++) { if (!io_threads_[k]->joinable()) { LOG_F(ERROR, "%s's io thread #%lu still running", name, k); } } break; case 100: LOG_F(ERROR, "Detaching io_threads for %s", name); for (auto &thread : io_threads_) thread->detach(); return; default: break; } std::this_thread::sleep_for(std::chrono::milliseconds(25)); if (std::all_of(io_threads_.begin(), io_threads_.end(), [](const thread_p &thread) { return thread->joinable(); })) { for (auto &thread : io_threads_) thread->join(); DLOG_F(INFO, "All of %s's IO threads were joined succesfully", name); break; } } } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during destruction of a stream outlet: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during stream outlet shutdown."); } } void stream_outlet_impl::push_numeric_raw(const void *data, double timestamp, bool pushthrough) { if (lsl::api_config::get_instance()->force_default_timestamps()) timestamp = 0.0; sample_p smp( sample_factory_->new_sample(timestamp == 0.0 ? lsl_clock() : timestamp, pushthrough)); smp->assign_untyped(data); send_buffer_->push_sample(smp); } bool stream_outlet_impl::have_consumers() { return send_buffer_->have_consumers(); } bool stream_outlet_impl::wait_for_consumers(double timeout) { return send_buffer_->wait_for_consumers(timeout); } template void stream_outlet_impl::enqueue(const T *data, double timestamp, bool pushthrough) { if (lsl::api_config::get_instance()->force_default_timestamps()) timestamp = 0.0; sample_p smp( sample_factory_->new_sample(timestamp == 0.0 ? lsl_clock() : timestamp, pushthrough)); smp->assign_typed(data); send_buffer_->push_sample(smp); } template void stream_outlet_impl::enqueue(const char *data, double, bool); template void stream_outlet_impl::enqueue(const int16_t *data, double, bool); template void stream_outlet_impl::enqueue(const int32_t *data, double, bool); template void stream_outlet_impl::enqueue(const int64_t *data, double, bool); template void stream_outlet_impl::enqueue(const float *data, double, bool); template void stream_outlet_impl::enqueue(const double *data, double, bool); template void stream_outlet_impl::enqueue(const std::string *data, double, bool); } // namespace lsl liblsl-1.17.7/src/stream_outlet_impl.h000066400000000000000000000323751517625163100177610ustar00rootroot00000000000000#ifndef STREAM_OUTLET_IMPL_H #define STREAM_OUTLET_IMPL_H #include "common.h" #include "forward.h" #include "stream_info_impl.h" #include #include #include #include #include #include using asio::ip::tcp; using asio::ip::udp; namespace lsl { /// pointer to a thread using thread_p = std::shared_ptr; /** * A stream outlet. * Outlets are used to make streaming data (and the meta-data) available on the lab network. */ class stream_outlet_impl { public: /** * Establish a new stream outlet. This makes the stream discoverable. * @param info The stream information to use for creating this stream stays constant over the * lifetime of the outlet. * @param chunk_size The preferred chunk size, in samples, at which data shall be transmitted * over the network. Can be selectively overridden by the inlet. If 0 (=default), the chunk size * is determined by the pushthrough flag in push_sample or push_chunk. * @param requested_size The maximum number of seconds/samples buffered for unresponsive * receivers. If more samples get pushed, the oldest will be dropped. The default is sufficient * to hold a bit more than 15 minutes of data at 512Hz, while consuming not more than ca. 512MB * of RAM. Depends on `flags` as calculated in `stream_info_impl::calc_transport_buf_samples()` * @param flags Bitwise-OR'd flags from lsl_transport_options_t */ stream_outlet_impl(const stream_info_impl &info, int32_t chunk_size = 0, int32_t requested_bufsize = 900, lsl_transport_options_t flags = transp_default); /** * Destructor. * The stream will no longer be discoverable after destruction and all paired inlets will stop * delivering data. */ ~stream_outlet_impl(); stream_outlet_impl(const stream_outlet_impl &) = delete; // // === Pushing a sample into the outlet. === // /** * Push a std vector of values as a sample into the outlet. * * Each entry in the vector corresponds to one channel. * The function handles type checking & conversion. * @param data A vector of values to push (one for each channel). * @param timestamp Optionally the capture time of the sample, in agreement with local_clock(); * if omitted, the current time is assumed. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it into a chunk according to network speeds. */ void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } void push_sample( const std::vector &data, double timestamp = 0.0, bool pushthrough = true) { check_numchan((int32_t)data.size()); enqueue(data.data(), timestamp, pushthrough); } /** * Push a pointer to some values as a sample into the outlet. * * This is a lower-level function when data is available in some buffer. * Handles type checking & conversion. The user needs to ensure that he/she has as many values * as channels in the pointed-to location. * @param data A pointer to values to push. The number of values pointed to must not be less * than the number of channels in the sample. * @param timestamp Optionally the capture time of the sample, in agreement with lsl_clock(); if * omitted, the current time is assumed. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it into a chunk according to network speeds. */ void push_sample(const float *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const double *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const int64_t *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const int32_t *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const int16_t *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const char *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } void push_sample(const std::string *data, double timestamp = 0.0, bool pushthrough = true) { enqueue(data, timestamp, pushthrough); } template inline lsl_error_code_t push_sample_noexcept( const T *data, double timestamp = 0.0, bool pushthrough = true) noexcept { try { enqueue(data, timestamp, pushthrough); return lsl_no_error; } catch (std::range_error &e) { LOG_F(WARNING, "Error during push_sample: %s", e.what()); return lsl_argument_error; } catch (std::invalid_argument &e) { LOG_F(WARNING, "Error during push_sample: %s", e.what()); return lsl_argument_error; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during push_sample: %s", e.what()); return lsl_internal_error; } } /** * Push a pointer to raw numeric data as one sample into the outlet. * This is the lowest-level function; does no checking of any kind. Do not use for * variable-size/string data-formatted channels. * @param sample A pointer to the raw sample data to push. * @param timestamp Optionally the capture time of the sample, in agreement with lsl_clock(); if * omitted, the current time is assumed. * @param pushthrough Whether to push the sample through to the receivers instead of buffering * it into a chunk according to network speeds. */ void push_numeric_raw(const void *data, double timestamp = 0.0, bool pushthrough = true); // // === Pushing an chunk of samples into the outlet === // /** * Push a chunk of multiplexed samples into the send buffer. * * One timestamp per sample is provided. * * @warning The provided buffer size is measured in channel values (e.g. floats) rather than in * samples. * @param data_buffer A buffer of channel values holding the data for zero or more successive * samples to send. * @param timestamp_buffer A buffer of timestamp values holding time stamps for each sample in * the data buffer. * @param data_buffer_elements The number of data values (of type T) in the data buffer. Must be * a multiple of the channel count. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_multiplexed(const T *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) { std::size_t num_chans = info().channel_count(), num_samples = data_buffer_elements / num_chans; if (data_buffer_elements % num_chans != 0) throw std::runtime_error("The number of buffer elements to send is not a multiple of " "the stream's channel count."); if (!data_buffer) throw std::runtime_error("The data buffer pointer must not be NULL."); if (!timestamp_buffer) throw std::runtime_error("The timestamp buffer pointer must not be NULL."); for (std::size_t k = 0; k < num_samples; k++) enqueue(&data_buffer[k * num_chans], timestamp_buffer[k], pushthrough && k == num_samples - 1); } template int32_t push_chunk_multiplexed_noexcept(const T *data_buffer, const double *timestamp_buffer, std::size_t data_buffer_elements, bool pushthrough = true) noexcept { try { push_chunk_multiplexed( data_buffer, timestamp_buffer, data_buffer_elements, pushthrough); return lsl_no_error; } catch (std::range_error &e) { LOG_F(WARNING, "Error during push_chunk: %s", e.what()); return lsl_argument_error; } catch (std::invalid_argument &e) { LOG_F(WARNING, "Error during push_chunk: %s", e.what()); return lsl_argument_error; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during push_chunk: %s", e.what()); return lsl_internal_error; } } /** * Push a chunk of multiplexed samples into the send buffer. Single timestamp provided. * * @warning The provided buffer size is measured in channel values (e.g., floats) rather than in * samples. * @param buffer A buffer of channel values holding the data for zero or more successive samples * to send. * @param buffer_elements The number of channel values (of type T) in the buffer. Must be a * multiple of the channel count. * @param timestamp Optionally the capture time of the most recent sample, in agreement with * lsl_clock(); if omitted, the current time is used. The time stamps of other samples are * automatically derived based on the sampling rate of the stream. * @param pushthrough Whether to push the chunk through to the receivers instead of buffering it * with subsequent samples. Note that the chunk_size, if specified at outlet construction, takes * precedence over the pushthrough flag. */ template void push_chunk_multiplexed(const T *buffer, std::size_t buffer_elements, double timestamp = 0.0, bool pushthrough = true) { std::size_t num_chans = info().channel_count(), num_samples = buffer_elements / num_chans; if (buffer_elements % num_chans != 0) throw std::runtime_error("The number of buffer elements to send is not a multiple of " "the stream's channel count."); if (!buffer) throw std::runtime_error("The number of buffer elements to send is not a multiple of " "the stream's channel count."); if (num_samples > 0) { if (timestamp == 0.0) timestamp = lsl_clock(); if (info().nominal_srate() != IRREGULAR_RATE) timestamp = timestamp - (num_samples - 1) / info().nominal_srate(); push_sample(buffer, timestamp, pushthrough && (num_samples == 1)); for (std::size_t k = 1; k < num_samples; k++) push_sample(&buffer[k * num_chans], DEDUCED_TIMESTAMP, pushthrough && (k == num_samples - 1)); } } template int32_t push_chunk_multiplexed_noexcept(const T *data, std::size_t data_elements, double timestamp = 0.0, bool pushthrough = true) noexcept { try { push_chunk_multiplexed(data, data_elements, timestamp, pushthrough); return lsl_no_error; } catch (std::range_error &e) { LOG_F(WARNING, "Error during push_chunk: %s", e.what()); return lsl_argument_error; } catch (std::invalid_argument &e) { LOG_F(WARNING, "Error during push_chunk: %s", e.what()); return lsl_argument_error; } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error during push_chunk: %s", e.what()); return lsl_internal_error; } } // === Misc Features === /** * Retrieve the stream info associated with this outlet. * * This is the constant meta-data header that was used to create the stream. */ const stream_info_impl &info() const { return *info_; } /// Check whether consumers are currently registered. bool have_consumers(); /// Wait until some consumer shows up. bool wait_for_consumers(double timeout = FOREVER); private: /// Instantiate a new server stack. void instantiate_stack(udp udp_protocol); /// Allocate and enqueue a new sample into the send buffer. template void enqueue(const T *data, double timestamp, bool pushthrough); /** * Check whether some given number of channels matches the stream's channel_count. * Throws an error if not. */ void check_numchan(uint32_t chns) { if (chns != info_->channel_count()) throw std::range_error("The provided sample data has a different length than the " "stream's number of channels."); } /// a factory for samples of appropriate type factory_p sample_factory_; /// the preferred chunk size int32_t chunk_size_; /// stream_info shared between the various server instances stream_info_impl_p info_; /// the single-producer, multiple-receiver send buffer send_buffer_p send_buffer_; /// the IO service objects io_context_p io_ctx_data_, io_ctx_service_; /// the threaded TCP data server tcp_server_p tcp_server_; /// the UDP timing & ident service(s); two if using both IP stacks std::vector udp_servers_; /// UDP multicast responders for service discovery (time features disabled); /// also using only the allowed IP stacks std::vector responders_; /// threads that handle the I/O operations (two per stack: one for UDP and one for TCP) std::vector io_threads_; }; } // namespace lsl #endif liblsl-1.17.7/src/tcp_server.cpp000066400000000000000000000565711517625163100165640ustar00rootroot00000000000000#include "tcp_server.h" #include "api_config.h" #include "consumer_queue.h" #include "sample.h" #include "send_buffer.h" #include "socket_utils.h" #include "stream_info_impl.h" #include "util/cast.hpp" #include "util/endian.hpp" #include "util/strfuns.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER // (inefficiently converting int to bool in portable_oarchive instantiation...) #pragma warning(disable : 4800) #endif // a convention that applies when including portable_oarchive.h in multiple .cpp files. // otherwise, the templates are instantiated in this file and sample.cpp which leads // to errors like "multiple definition of `typeinfo name" #define NO_EXPLICIT_TEMPLATE_INSTANTIATION #include "portable_archive/portable_oarchive.hpp" using std::size_t; namespace lsl { /** * Active session with a TCP client. * * A note on memory ownership: * - Generally, the stream_outlet maintains shared ownership of the `tcp_server`, `io_context`s, * and `stream_info`. * - At any point in time there are likely multiple request/handler chains in flight somewhere * between the operating system, asio, and the various handlers below. * The handlers are set up such that any memory that may be referred to by them in the future is * owned (shared) by the handler/callback function objects (this is what is encapsulated by the * client_session instance). * Their lifetime is managed by asio and ends when the handler chain ends (e.g., is aborted). * Since the TCP server is referred to (occasionally) by handler code, the `client_session`s store * a std::weak_ptr to the tcp_server that's upgraded to a std::shared_ptr as needed * - There is a per-session transfer thread (client_session::transfer_samples_thread()) that owns * the respective `client_session` which goes out of scope once the server is being shut down. * - The TCP server and client session also have shared ownership of the io_context (since in * some cases some transfer threads can outlive the stream outlet, and so the io_context is still * kept around until all sockets have been properly released). * - So memory is generally owned by the code (functors and stack frames) that needs to refer to * it for the duration of the execution. */ class client_session : public std::enable_shared_from_this { public: /// Instantiate a new session & its socket. client_session(const tcp_server_p &serv, tcp_socket &&sock) : io_(serv->io_), serv_(serv), sock_(std::move(sock)), requeststream_(&requestbuf_) {} /// Destructor. ~client_session(); /// Get the socket of this session. tcp_socket &socket() { return sock_; } /// Begin processing this session (i.e., data transmission over the socket). void begin_processing(); private: /// Handler that gets called when the reading of the 1st line (command line) of the inbound /// message finished. void handle_read_command_outcome(err_t err); /// Handler that gets called after finishing reading of the query line. void handle_read_query_outcome(err_t err); /// Helper function to send a status message to the connected party. void send_status_message(const std::string &msg); /// Handler that gets called after finishing the reading of feedparameters. void handle_read_feedparams( int request_protocol_version, const std::string &request_uid, err_t err); /// Handler that gets called sending the feedheader has completed. void handle_send_feedheader_outcome(err_t err, std::size_t n); /// Transfers samples from the server's send buffer into the async send queues of IO threads void transfer_samples_thread(std::shared_ptr /*keepalive*/, std::shared_ptr &&queue, int max_samples_per_chunk); /// Handler that gets called when a sample transfer has been completed. void handle_chunk_transfer_outcome(err_t err, std::size_t len); /// shared pointer to IO service; ensures that the IO is still around by the time the serv_ and /// sock_ need to be destroyed io_context_p io_; /// the server that is associated with this connection std::weak_ptr serv_; /// connection socket tcp_socket sock_; // data used by the transfer thread (and some other handlers) /// this buffer holds the data feed generated by us asio::streambuf feedbuf_; /// this buffer holds the request as received from the client (incrementally filled) asio::streambuf requestbuf_; /// output archive (wrapped around the feed buffer) std::unique_ptr outarch_; /// this is a stream on top of the request buffer for convenient parsing std::istream requeststream_; /// scratchpad memory (e.g., for endianness conversion) char *scratch_{nullptr}; /// protocol version to use for transmission int data_protocol_version_{100}; /// is the client's endianness reversed (big<->little endian) bool reverse_byte_order_{false}; /// our chunk granularity int chunk_granularity_{0}; /// maximum number of samples buffered int max_buffered_{0}; // data exchanged between the transfer completion handler and the transfer thread /// whether the current transfer has finished (possibly with an error) bool transfer_completed_; /// the outcome of the last chunk transfer asio::error_code transfer_error_; /// the amount of bytes transferred std::size_t transfer_amount_; /// a mutex that protects the completion data std::mutex completion_mut_; /// a condition variable that signals completion std::condition_variable completion_cond_; }; tcp_server::tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p sendbuf, factory_p factory, int chunk_size, bool allow_v4, bool allow_v6) : chunk_size_(chunk_size), info_(std::move(info)), io_(std::move(io)), factory_(std::move(factory)), send_buffer_(std::move(sendbuf)) { // assign connection-dependent fields info_->session_id(api_config::get_instance()->session_id()); info_->reset_uid(); info_->created_at(lsl_clock()); info_->hostname(asio::ip::host_name()); if (allow_v4) { try { acceptor_v4_ = std::make_unique(*io_, asio::ip::tcp::v4()); auto port = bind_and_listen_to_port_in_range(*acceptor_v4_, asio::ip::tcp::v4(), 10); info_->v4data_port(port); LOG_F(1, "Created IPv%d TCP acceptor for %s @ port %d", 4, info_->name().c_str(), port); } catch (std::exception &e) { LOG_F(WARNING, "Failed to create IPv%d acceptor: %s", 4, e.what()); acceptor_v4_.reset(); } } if (allow_v6) { try { acceptor_v6_ = std::make_unique(*io_, asio::ip::tcp::v6()); auto port = bind_and_listen_to_port_in_range(*acceptor_v6_, asio::ip::tcp::v6(), 10); info_->v6data_port(port); LOG_F(1, "Created IPv%d TCP acceptor for %s @ port %d", 6, info_->name().c_str(), port); } catch (std::exception &e) { LOG_F(WARNING, "Failed to create IPv%d acceptor: %s", 6, e.what()); acceptor_v6_.reset(); } } if (!acceptor_v4_ && !acceptor_v6_) throw std::runtime_error("Failed to instantiate socket acceptors for the TCP server"); } // === externally issued asynchronous commands === void tcp_server::begin_serving() { // pre-generate the info's messages shortinfo_msg_ = info_->to_shortinfo_message(); fullinfo_msg_ = info_->to_fullinfo_message(); // start accepting connections if (acceptor_v4_) accept_next_connection(acceptor_v4_); if (acceptor_v6_) accept_next_connection(acceptor_v6_); } void tcp_server::end_serving() { // issue closure of the server socket; this will result in a cancellation of the associated IO // operations post(*io_, [this, shared_this = shared_from_this()]() { if (acceptor_v4_) acceptor_v4_->close(); if (acceptor_v6_) acceptor_v6_->close(); }); // issue closure of all active client session sockets; cancels the related outstanding IO jobs close_inflight_sessions(); // also notify any transfer threads that are blocked waiting for a sample by pushing an empty // sample pointer (the threads check for !samp and skip it, see transfer_samples_thread) send_buffer_->push_sample(sample_p()); } // === accept loop === void tcp_server::accept_next_connection(tcp_acceptor_p &acceptor) { try { if (!acceptor || !acceptor->is_open()) return; // Select the IO context for handling the socket auto &sock_io_ctx = *io_; // accept a connection on the session's socket acceptor->async_accept(sock_io_ctx, [shared_this = shared_from_this(), &acceptor]( err_t err, tcp_socket sock) { if (err == asio::error::operation_aborted || err == asio::error::shut_down) return; // no error: create a new session and start processing if (!err) std::make_shared(shared_this, std::move(sock))->begin_processing(); else LOG_F(WARNING, "Unhandled accept error: %s", err.message().c_str()); // move on to the next connection if the acceptor is still open if (acceptor && acceptor->is_open()) shared_this->accept_next_connection(acceptor); }); } catch (std::exception &e) { LOG_F(ERROR, "Error during tcp_server::accept_next_connection: %s", e.what()); } } // === graceful cancellation of in-flight sockets === void tcp_server::register_inflight_session(const std::shared_ptr &session) { std::lock_guard lock(inflight_mut_); inflight_.insert(std::make_pair(session.get(), session)); } void tcp_server::unregister_inflight_session(client_session *session) { std::lock_guard lock(inflight_mut_); auto pos = inflight_.find(session); if (pos != inflight_.end()) inflight_.erase(pos); } void tcp_server::close_inflight_sessions() { std::lock_guard lock(inflight_mut_); for (auto &pair : inflight_) { auto session = pair.second.lock(); // session has already expired on its own if (!session) continue; post(session->socket().get_executor(), [session]() { asio::error_code ec; auto &sock = session->socket(); if (sock.is_open()) { sock.shutdown(tcp_socket::shutdown_both, ec); sock.close(ec); if (ec) LOG_F(WARNING, "Error during shutdown_and_close: %s", ec.message().c_str()); } }); } inflight_.clear(); } // === implementation of the client_session class === client_session::~client_session() { LOG_F(1, "Destructing session %p", this); delete[] scratch_; if (auto serv = serv_.lock()) serv->unregister_inflight_session(this); } void client_session::begin_processing() { try { sock_.set_option(asio::ip::tcp::no_delay(true)); if (api_config::get_instance()->socket_send_buffer_size() > 0) sock_.set_option(asio::socket_base::send_buffer_size( api_config::get_instance()->socket_send_buffer_size())); if (api_config::get_instance()->socket_receive_buffer_size() > 0) sock_.set_option(asio::socket_base::receive_buffer_size( api_config::get_instance()->socket_receive_buffer_size())); // register this socket as "in-flight" with the server (so that any subsequent ops on it can // be aborted if necessary) if (auto serv = serv_.lock()) { serv->register_inflight_session(shared_from_this()); // read the request line async_read_until(sock_, requestbuf_, "\r\n", [shared_this = shared_from_this()](err_t err, std::size_t /*unused*/) { shared_this->handle_read_command_outcome(err); }); } else { throw std::runtime_error("server disappeared before start client session"); } } catch (std::exception &e) { LOG_F(ERROR, "Error during client_session::begin_processing: %s", e.what()); } } void client_session::handle_read_command_outcome(err_t read_err) { try { if (read_err) return; // parse request method std::string method; getline(requeststream_, method); method = trim(method); if (method == "LSL:shortinfo") // shortinfo request: read the content query string async_read_until(sock_, requestbuf_, "\r\n", [shared_this = shared_from_this()](err_t err, std::size_t /*unused*/) { shared_this->handle_read_query_outcome(err); }); else if (method == "LSL:fullinfo") { // fullinfo request: reply right away auto serv = serv_.lock(); if (serv) async_write(sock_, asio::buffer(serv->fullinfo_msg_), [shared_this = shared_from_this(), serv]( err_t /*unused*/, std::size_t /*unused*/) {}); } else if (method == "LSL:streamfeed") // streamfeed request (1.00): read feed parameters async_read_until(sock_, requestbuf_, "\r\n", [shared_this = shared_from_this()](err_t err, std::size_t /*unused*/) { shared_this->handle_read_feedparams(100, "", err); }); else if (method.compare(0, 15, "LSL:streamfeed/") == 0) { // streamfeed request with version: read feed parameters std::vector parts = splitandtrim(method, ' ', true); async_read_until(sock_, requestbuf_, "\r\n\r\n", [shared_this = shared_from_this(), request_protocol_version = std::stoi(parts[0].substr(15)), request_uid = (parts.size() > 1) ? parts[1] : ""]( err_t err, std::size_t /*unused*/) { shared_this->handle_read_feedparams(request_protocol_version, request_uid, err); }); } } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while parsing a client command: %s", e.what()); } } void client_session::handle_read_query_outcome(err_t err) { try { if (err) return; // read the query line std::string query; getline(requeststream_, query); query = trim(query); auto serv = serv_.lock(); if (!serv) return; if (serv->info_->matches_query(query)) { // matches: reply (otherwise just close the stream) async_write(sock_, asio::buffer(serv->shortinfo_msg_), [serv](err_t /*unused*/, std::size_t /*unused*/) { /* keep the tcp_server alive until the shortinfo is sent completely*/ }); } else { DLOG_F(INFO, "%p got a shortinfo query response for the wrong query", this); } } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while parsing a client request: %s", e.what()); } } void client_session::send_status_message(const std::string &msg) { auto buf(std::make_shared(msg)); async_write(sock_, asio::buffer(*buf), [buf, shared_this = shared_from_this()](err_t /*unused*/, std::size_t /*unused*/) { /* keep objects alive until the message is sent */ }); } void client_session::handle_read_feedparams( int request_protocol_version, const std::string &request_uid, err_t err) { try { if (err) return; DLOG_F(2, "%p got a streamfeed request", this); // --- protocol negotiation --- // check request validity auto cfg_proto_version = api_config::get_instance()->use_protocol_version(); if (request_protocol_version / 100 > cfg_proto_version / 100) { send_status_message( "LSL/" + std::to_string(cfg_proto_version) + " 505 Version not supported"); DLOG_F(WARNING, "%p Got a request for a too new protocol version", this); return; } auto serv = serv_.lock(); if (!serv) return; auto &info = serv->info_; if (!request_uid.empty() && request_uid != info->uid()) { send_status_message("LSL/" + to_string(cfg_proto_version) + " 404 Not found"); return; } if (request_protocol_version >= 110) { int client_byte_order = 1234; // assume little endian double client_endian_performance = 0; // the other party's endian conversion performance bool client_has_ieee754_floats = true; // the client has IEEE-754 compliant floating point formats bool client_supports_subnormals = true; // the client supports subnormal numbers int client_protocol_version = request_protocol_version; // assume that the client wants to use the same // version for data transmission int client_value_size = info->channel_bytes(); // assume that the client has a standard // size for the relevant data type lsl_channel_format_t format = info->channel_format(); // read feed parameters char buf[16384] = {0}; while (requeststream_.getline(buf, sizeof(buf)) && (buf[0] != '\r')) { std::string hdrline(buf); std::size_t colon = hdrline.find_first_of(':'); if (colon != std::string::npos) { // strip off comments auto semicolon = hdrline.find_first_of(';'); if (semicolon != std::string::npos) hdrline.erase(semicolon); // convert to lowercase for (auto &c : hdrline) c = ::tolower(c); // extract key & value std::string type = trim(hdrline.substr(0, colon)), rest = trim(hdrline.substr(colon + 1)); // get the header information if (type == "native-byte-order") client_byte_order = std::stoi(rest); if (type == "endian-performance") client_endian_performance = std::stod(rest); if (type == "has-ieee754-floats") client_has_ieee754_floats = from_string(rest); if (type == "supports-subnormals") client_supports_subnormals = from_string(rest); if (type == "value-size") client_value_size = std::stoi(rest); if (type == "max-buffer-length") max_buffered_ = std::stoi(rest); if (type == "max-chunk-length") chunk_granularity_ = std::stoi(rest); if (type == "protocol-version") client_protocol_version = std::stoi(rest); } else { DLOG_F(WARNING, "%p Request line '%s' contained no key-value pair", this, hdrline.c_str()); } } // determine the parameters for data transmission bool client_suppress_subnormals = false; lsl::Endianness use_byte_order = LSL_BYTE_ORDER; // use least common denominator data protocol version data_protocol_version_ = std::min(cfg_proto_version, client_protocol_version); // downgrade to 1.00 (portable binary format) if an unsupported binary conversion is // involved if (info->channel_format() != cft_string && info->channel_bytes() != client_value_size) data_protocol_version_ = 100; if (!format_ieee754[cft_double64] || (format == cft_float32 && !format_ieee754[cft_float32]) || !client_has_ieee754_floats) data_protocol_version_ = 100; if (data_protocol_version_ >= 110) { // enable endian conversion when // 1. our byte ordering is different from the client's *and* // 2. we can actually perform the conversion *and* // 3. the sample format is wide enough for endianness to matter *and* // 4. we're faster at converting than the client if (LSL_BYTE_ORDER != client_byte_order && // (1) lsl::can_convert_endian(client_byte_order, client_value_size) // (2) && client_value_size > 1 && // (3) (measure_endian_performance() > client_endian_performance)) // (4) { use_byte_order = static_cast(client_byte_order); reverse_byte_order_ = true; } // determine if subnormal suppression needs to be enabled client_suppress_subnormals = (format_subnormal[format] && !client_supports_subnormals); } // send the response std::ostream response_stream(&feedbuf_); response_stream << "LSL/" << cfg_proto_version << " 200 OK\r\n"; response_stream << "UID: " << info->uid() << "\r\n"; response_stream << "Byte-Order: " << use_byte_order << "\r\n"; response_stream << "Suppress-Subnormals: " << client_suppress_subnormals << "\r\n"; response_stream << "Data-Protocol-Version: " << data_protocol_version_ << "\r\n"; response_stream << "\r\n" << std::flush; } else { // read feed parameters requeststream_ >> max_buffered_ >> chunk_granularity_; } // --- validation --- if (data_protocol_version_ == 100) { // create a portable output archive to write to outarch_ = std::make_unique(feedbuf_); // serialize the shortinfo message into an archive *outarch_ << serv->shortinfo_msg_; } else { // allocate scratchpad memory for endian conversion, etc. scratch_ = new char[format_sizes[info->channel_format()] * info->channel_count()]; } // send test pattern samples lsl::factory fac(info->channel_format(), info->channel_count(), 4); for (int test_pattern : {4, 2}) { lsl::sample_p temp(fac.new_sample(0.0, false)); temp->assign_test_pattern(test_pattern); if (data_protocol_version_ >= 110) temp->save_streambuf( feedbuf_, data_protocol_version_, reverse_byte_order_, scratch_); else *outarch_ << *temp; } // send off the newly created feedheader async_write( sock_, feedbuf_.data(), [shared_this = shared_from_this()](err_t err, std::size_t len) { shared_this->handle_send_feedheader_outcome(err, len); }); DLOG_F(2, "%p sent test pattern samples", this); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while serializing the feed header: %s", e.what()); } } void client_session::handle_send_feedheader_outcome(err_t err, std::size_t n) { try { if (err) return; feedbuf_.consume(n); auto serv = serv_.lock(); if (!serv) return; // quit if max_buffered_ is 0. This is a bit unexpected, but backwards compatible and quite // convenient for unit tests if (max_buffered_ <= 0) return; // determine transfer parameters auto queue = serv->send_buffer_->new_consumer(max_buffered_); // determine the maximum chunk size int max_samples_per_chunk = std::numeric_limits::max(); if (chunk_granularity_) max_samples_per_chunk = chunk_granularity_; else if (serv->chunk_size_) max_samples_per_chunk = serv->chunk_size_; // spawn a sample transfer thread. std::thread(&client_session::transfer_samples_thread, this, shared_from_this(), std::move(queue), max_samples_per_chunk) .detach(); } catch (std::exception &e) { LOG_F(WARNING, "Unexpected error while handling the feedheader send outcome: %s", e.what()); } } void client_session::transfer_samples_thread(std::shared_ptr /* keepalive */, std::shared_ptr &&queue, int max_samples_per_chunk) { int samples_in_current_chunk = 0; while (!serv_.expired()) { try { // get next sample from the sample queue (blocking) sample_p samp(queue->pop_sample()); // ignore blank samples (they are basically wakeup notifiers from someone's // end_serving()) if (!samp) continue; // serialize the sample into the stream if (data_protocol_version_ >= 110) samp->save_streambuf( feedbuf_, data_protocol_version_, reverse_byte_order_, scratch_); else *outarch_ << *samp; // if the sample is marked as force-push or the configured chunk size is reached if (samp->pushthrough || ++samples_in_current_chunk >= max_samples_per_chunk) { // send off the chunk that we aggregated so far std::unique_lock lock(completion_mut_); transfer_completed_ = false; async_write(sock_, feedbuf_.data(), [shared_this = shared_from_this()](err_t err, std::size_t len) { shared_this->handle_chunk_transfer_outcome(err, len); }); // wait for the completion condition completion_cond_.wait(lock, [this]() { return transfer_completed_; }); // handle transfer outcome if (!transfer_error_) { feedbuf_.consume(transfer_amount_); } else break; samples_in_current_chunk = 0; } } catch (std::exception &e) { LOG_F(WARNING, "Unexpected glitch in transfer_samples_thread: %s", e.what()); } } } void client_session::handle_chunk_transfer_outcome(err_t err, std::size_t len) { try { { std::lock_guard lock(completion_mut_); // assign the transfer outcome transfer_error_ = err; transfer_amount_ = len; transfer_completed_ = true; } // notify the server thread completion_cond_.notify_all(); } catch (std::exception &e) { LOG_F(WARNING, "Catastrophic error in handling the chunk transfer outcome (in tcp_server): %s", e.what()); } } } // namespace lsl liblsl-1.17.7/src/tcp_server.h000066400000000000000000000100641517625163100162140ustar00rootroot00000000000000#ifndef TCP_SERVER_H #define TCP_SERVER_H #include "forward.h" #include "socket_utils.h" #include #include #include #include #include using asio::ip::tcp; using err_t = const asio::error_code &; namespace lsl { /// shared pointer to a socket using tcp_socket_p = std::shared_ptr; /// shared pointer to an acceptor socket using tcp_acceptor_p = std::unique_ptr; /** * The TCP data server. * * Acts as a TCP server on a free port (in the configured port range), and understands the following * messages: * - `LSL:streamfeed`: A request to receive streaming data on the connection. The server responds * with the shortinfo, two samples filled with a test pattern, followed by samples until the server * outlet goes out of existence. * - `LSL:fullinfo`: A request for the stream_info served by this server. * - `LSL:shortinfo`: A request for the stream_info served by this server if matching the provided * query string. The short version of the stream_info (empty `` element) is returned. */ class tcp_server : public std::enable_shared_from_this { public: /** * Construct a new TCP server for a stream outlet. * * This opens a new TCP server port (in the allowed range) and, if successful, updates the * stream_info object with the data of this connection. To have it serve connection requests, * the member function begin_serving() must be called once. The latter should ideally not be * done before the UDP service port has been successfully initialized, as well. * @param info A stream_info that is shared with other server objects. * @param io An io_context that is shared with other server objects. * @param sendbuf A send buffer that is shared with other server objects. * @param factory A sample factory that is shared with other server objects. * @param protocol The protocol (IPv4 or IPv6) that shall be serviced by this server. * @param chunk_size The preferred chunk size, in samples. If 0, the pushthrough flag determines * the effective chunking. */ tcp_server(stream_info_impl_p info, io_context_p io, send_buffer_p sendbuf, factory_p factory, int chunk_size, bool allow_v4, bool allow_v6); /** * Begin serving TCP connections. * * Should not be called before info_ has been fully initialized by all involved parties * (tcp_server, udp_server) since no modifications to the stream_info thereafter are permitted. */ void begin_serving(); /** * Initiate teardown of IO processes. * * The actual teardown will be performed by the IO thread that runs the operations of * this server. */ void end_serving(); private: friend class client_session; /// Start accepting a new connection. void accept_next_connection(tcp_acceptor_p &acceptor); /// Register an in-flight (active) session with the server (so that we can close it when /// a shutdown is requested externally). void register_inflight_session(const std::shared_ptr &session); void unregister_inflight_session(client_session *session); /// Post a close of all in-flight sockets. void close_inflight_sessions(); // data used by the transfer threads int chunk_size_; // the chunk size to use (or 0) // data shared with the outlet stream_info_impl_p info_; // shared stream_info object io_context_p io_; // shared ptr to IO service; ensures that the IO is still around by the time // the acceptor needs to be destroyed factory_p factory_; // reference to the sample factory (which owns the samples) send_buffer_p send_buffer_; // the send buffer, shared with other TCP's and the outlet // acceptor socket tcp_acceptor_p acceptor_v4_, acceptor_v6_; // our server socket // registry of in-flight asessions (for cancellation) std::map> inflight_; std::recursive_mutex inflight_mut_; // mutex protecting the registry from concurrent access // some cached data std::string shortinfo_msg_; // pre-computed short-info server response std::string fullinfo_msg_; // pre-computed full-info server response }; } // namespace lsl #endif liblsl-1.17.7/src/time_postprocessor.cpp000066400000000000000000000105611517625163100203400ustar00rootroot00000000000000#include "time_postprocessor.h" #include "api_config.h" #include #include #include #if defined(__GNUC__) #pragma GCC diagnostic ignored "-Wdouble-promotion" #elif defined(__clang__) #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" #endif // === implementation of the time_postprocessor class === using namespace lsl; /// how many samples have to be seen between clocksyncs? const uint8_t samples_between_clocksyncs = 50; time_postprocessor::time_postprocessor(postproc_callback_t query_correction, postproc_callback_t query_srate, reset_callback_t query_reset) : samples_since_last_clocksync(samples_between_clocksyncs), query_srate_(std::move(query_srate)), options_(proc_none), halftime_(api_config::get_instance()->smoothing_halftime()), query_correction_(std::move(query_correction)), query_reset_(std::move(query_reset)), next_query_time_(0.0), last_offset_(0.0), last_value_(std::numeric_limits::lowest()) { } void time_postprocessor::set_options(uint32_t options) { // bitmask which options actually changed (XOR) auto changed = options_ ^ options; // dejitter option changed? -> Reset it // in case it got enabled, it'll be initialized with the correct t0 when // the next sample comes in if(changed & proc_dejitter) dejitter = postproc_dejitterer(); if(changed & proc_monotonize) last_value_ = std::numeric_limits::lowest(); options_ = options; } double time_postprocessor::process_timestamp(double value) { if (options_ & proc_threadsafe) { std::lock_guard lock(processing_mut_); return process_internal(value); } return process_internal(value); } void time_postprocessor::skip_samples(uint32_t skipped_samples) { if (options_ & proc_dejitter && dejitter.smoothing_applicable()) dejitter.samples_since_t0_ += skipped_samples; } double time_postprocessor::process_internal(double value) { // --- clock synchronization --- if (options_ & proc_clocksync) { // update last correction value if needed (we do this every 50 samples and at most twice per // second) if (++samples_since_last_clocksync > samples_between_clocksyncs && lsl_clock() > next_query_time_) { last_offset_ = query_correction_(); samples_since_last_clocksync = 0; if (query_reset_()) { // reset state to unitialized last_offset_ = query_correction_(); last_value_ = std::numeric_limits::lowest(); // reset the dejitterer to an uninitialized state so it's // initialized on the next use dejitter = postproc_dejitterer(); } next_query_time_ = lsl_clock() + 0.5; } // perform clock synchronization; this is done by adding the last-measured clock offset // value (typically this is used to map the value from the sender's clock to our local // clock) value += last_offset_; } // --- jitter removal --- if (options_ & proc_dejitter) { // initialize the smoothing state if not yet done so if (!dejitter.is_initialized()) { double srate = query_srate_(); dejitter = postproc_dejitterer(value, srate, halftime_); } value = dejitter.dejitter(value); } // --- force monotonic timestamps --- if (options_ & proc_monotonize) { if (value < last_value_) value = last_value_; else last_value_ = value; } return value; } postproc_dejitterer::postproc_dejitterer(double t0, double srate, double halftime) : t0_(static_cast(t0)) { if (srate > 0) { w1_ = 1. / srate; lam_ = pow(2, -1 / (srate * halftime)); } } double postproc_dejitterer::dejitter(double t) noexcept { if (!smoothing_applicable()) return t; // remove baseline for better numerical accuracy t -= t0_; // RLS update const double u1 = samples_since_t0_++, // u = np.matrix([[1.0], [samples_seen]]) pi0 = P00_ + u1 * P01_, // pi = u.T * P pi1 = P01_ + u1 * P11_, // .. al = t - (w0_ + u1 * w1_), // α = t - w.T * u # prediction error g_inv = 1 / (lam_ + pi0 + pi1 * u1), // g_inv = 1/(lam_ + pi * u) il_ = 1 / lam_; // ... P00_ = il_ * (P00_ - pi0 * pi0 * g_inv); // P = (P - k*pi) / lam_ P01_ = il_ * (P01_ - pi0 * pi1 * g_inv); // ... P11_ = il_ * (P11_ - pi1 * pi1 * g_inv); // ... w0_ += al * (P00_ + P01_ * u1); // w += k*α w1_ += al * (P01_ + P11_ * u1); // ... return w0_ + u1 * w1_ + t0_; // t = float(w.T * u) + t0 } void postproc_dejitterer::skip_samples(uint_fast32_t skipped_samples) noexcept { samples_since_t0_ += skipped_samples; } liblsl-1.17.7/src/time_postprocessor.h000066400000000000000000000072031517625163100200040ustar00rootroot00000000000000#ifndef TIME_POSTPROCESSOR_H #define TIME_POSTPROCESSOR_H #include "common.h" #include #include #include namespace lsl { /// A callback function that allows the post-processor to query state from other objects if needed using postproc_callback_t = std::function; using reset_callback_t = std::function; /// Dejitter / smooth timestamps with a first order recursive least squares filter (RLS). struct postproc_dejitterer { /// first observed time-stamp value, used as a baseline to improve numerics uint_fast32_t t0_; /// number of samples since t0, i.e. the x in y=ax+b uint_fast32_t samples_since_t0_{0}; /// linear regression model coefficients (intercept, slope) double w0_{0}, w1_{0}; /// inverse covariance matrix elements (P00, P11, off diagonal element P01=P10) double P00_{1e10}, P11_{1e10}, P01_{0}; /// forget factor lambda in RLS calculation double lam_{0}; /// constructor postproc_dejitterer(double t0 = 0, double srate = 0, double halftime = 0); /// dejitter a timestamp and update RLS parameters double dejitter(double t) noexcept; /// adjust RLS parameters to account for samples not seen void skip_samples(uint_fast32_t skipped_samples) noexcept; bool is_initialized() const noexcept { return t0_ != 0; } bool smoothing_applicable() const noexcept { return lam_ > 0; } }; /// Internal class of an inlet that is responsible for post-processing time stamps. class time_postprocessor { public: /// Construct a new time post-processor given some callback functions. time_postprocessor(postproc_callback_t query_correction, postproc_callback_t query_srate, reset_callback_t query_reset); /** * Set post-processing options to use. * * By default, this class performs NO post-processing and returns the ground-truth time stamps, * which can then be manually synchronized using time_correction(), and then smoothed/dejittered * if desired. This function allows automating these two and possibly more operations. * @param flags An integer that is the result of bitwise OR'ing one or more options from * processing_options_t together (e.g., proc_clocksync|proc_dejitter); the default is to enable * all options. */ void set_options(uint32_t options = proc_ALL); /// Post-process the given time stamp and return the new time-stamp. double process_timestamp(double value); /// Override the half-time (forget factor) of the time-stamp smoothing. void smoothing_halftime(float value) { halftime_ = value; } /// Inform the post processor some samples were skipped void skip_samples(uint32_t skipped_samples); private: /// Internal function to process a time stamp. double process_internal(double value); /// number of samples seen since last clocksync uint8_t samples_since_last_clocksync; // configuration parameters /// a callback function that returns the current nominal sampling rate postproc_callback_t query_srate_; /// current processing options uint32_t options_; /// smoothing half-time float halftime_; // handling of time corrections /// a callback function that returns the current time-correction offset postproc_callback_t query_correction_; /// a callback function that returns whether the clock was reset reset_callback_t query_reset_; /// the next time when we query the time-correction offset double next_query_time_; /// last queried correction offset double last_offset_; postproc_dejitterer dejitter; // runtime parameters for monotonize /// last observed time-stamp value, to force monotonically increasing stamps double last_value_; /// a mutex that protects the runtime data structures std::mutex processing_mut_; }; } // namespace lsl #endif liblsl-1.17.7/src/time_receiver.cpp000066400000000000000000000170631517625163100172230ustar00rootroot00000000000000#include "time_receiver.h" #include "api_config.h" #include "inlet_connection.h" #include "socket_utils.h" #include #include #include #include #include #include #include #include /// internally used constant to represent an unassigned time offset const double NOT_ASSIGNED = std::numeric_limits::max(); using namespace lsl; time_receiver::time_receiver(inlet_connection &conn) : conn_(conn), was_reset_(false), timeoffset_(std::numeric_limits::max()), remote_time_(std::numeric_limits::max()), uncertainty_(std::numeric_limits::max()), cfg_(api_config::get_instance()), time_sock_(time_io_), outlet_addr_(conn_.get_udp_endpoint()), next_estimate_(time_io_), aggregate_results_(time_io_), next_packet_(time_io_) { conn_.register_onlost(this, &timeoffset_upd_); conn_.register_onrecover(this, [this]() { reset_timeoffset_on_recovery(); outlet_addr_ = conn_.get_udp_endpoint(); DLOG_F(INFO, "Set new time service address: %s", outlet_addr_.address().to_string().c_str()); // handle outlet switching between IPv4 and IPv6 time_sock_.close(); time_sock_.open(outlet_addr_.protocol()); }); time_sock_.open(outlet_addr_.protocol()); } time_receiver::~time_receiver() { try { conn_.unregister_onrecover(this); conn_.unregister_onlost(this); time_io_.stop(); if (time_thread_.joinable()) time_thread_.join(); } catch (std::exception &e) { LOG_F(ERROR, "Unexpected error during destruction of a time_receiver: %s", e.what()); } catch (...) { LOG_F(ERROR, "Severe error during time receiver shutdown."); } } // === external data acccess === double time_receiver::time_correction(double timeout) { double remote_time, uncertainty; return time_correction(&remote_time, &uncertainty, timeout); } double time_receiver::time_correction(double *remote_time, double *uncertainty, double timeout) { std::unique_lock lock(timeoffset_mut_); auto timeoffset_available = [this]() { return (timeoffset_ != std::numeric_limits::max()) || conn_.lost(); }; if (!timeoffset_available()) { // start thread if not yet running if (!time_thread_.joinable()) time_thread_ = std::thread(&time_receiver::time_thread, this); // wait until the timeoffset becomes available (or we time out) if (timeout >= FOREVER) timeoffset_upd_.wait(lock, timeoffset_available); else if (!timeoffset_upd_.wait_for( lock, std::chrono::duration(timeout), timeoffset_available)) throw timeout_error("The time_correction() operation timed out."); } if (conn_.lost()) throw lost_error("The stream read by this inlet has been lost. To recover, you need to " "re-resolve the source and re-create the inlet."); *remote_time = remote_time_; *uncertainty = uncertainty_; return timeoffset_; } bool time_receiver::was_reset() { std::unique_lock lock(timeoffset_mut_); bool result = was_reset_; was_reset_ = false; return result; } // === internal processing === void time_receiver::time_thread() { conn_.acquire_watchdog(); loguru::set_thread_name((std::string("T_") += conn_.type_info().name()).c_str()); DLOG_F(2, "Started time receiver thread"); try { // start an async time estimation start_time_estimation(); // start the IO object (will keep running until cancelled) while (true) { try { time_io_.run(); break; } catch (std::exception &e) { LOG_F(WARNING, "Hiccup during time_thread io_context processing: %s", e.what()); } } } catch (std::exception &e) { LOG_F(WARNING, "time_thread failed unexpectedly with message: %s", e.what()); } conn_.release_watchdog(); } void time_receiver::start_time_estimation() { // clear the estimates buffer estimates_.clear(); estimate_times_.clear(); // generate a new wave id so that we don't confuse packets from earlier (or mis-guided) // estimations current_wave_id_ = std::rand(); // start the packet exchange chains send_next_packet(1); receive_next_packet(); // schedule the aggregation of results (by the time when all replies should have been received) aggregate_results_.expires_after(timeout_sec( cfg_->time_probe_max_rtt() + cfg_->time_probe_interval() * cfg_->time_probe_count())); aggregate_results_.async_wait([this](err_t err) { result_aggregation_scheduled(err); }); // schedule the next estimation step next_estimate_.expires_after(timeout_sec(cfg_->time_update_interval())); next_estimate_.async_wait([this](err_t err) { if (err != asio::error::operation_aborted) start_time_estimation(); }); } void time_receiver::send_next_packet(int packet_num) { try { // form the request & send it std::ostringstream request; request.precision(16); request << "LSL:timedata\r\n" << current_wave_id_ << " " << lsl_clock() << "\r\n"; auto msg_buffer = std::make_shared(request.str()); time_sock_.async_send_to(asio::buffer(*msg_buffer), outlet_addr_, [msg_buffer](err_t /*unused*/, std::size_t /*unused*/) { /* Do nothing, but keep the msg_buffer alive until async_send is completed */ }); } catch (std::exception &e) { LOG_F(WARNING, "Error trying to send a time packet: %s", e.what()); } // schedule next packet if (packet_num < cfg_->time_probe_count()) { next_packet_.expires_after(timeout_sec(cfg_->time_probe_interval())); next_packet_.async_wait([this, packet_num](err_t err) { if (!err) send_next_packet(packet_num + 1); }); } } void time_receiver::receive_next_packet() { time_sock_.async_receive_from(asio::buffer(recv_buffer_), remote_endpoint_, [this](err_t err, std::size_t len) { handle_receive_outcome(err, len); }); } void time_receiver::handle_receive_outcome(err_t err, std::size_t len) { try { if (!err) { // parse the buffer contents std::istringstream is(std::string(recv_buffer_, len)); int wave_id; is >> wave_id; if (wave_id == current_wave_id_) { double t0, t1, t2, t3 = lsl_clock(); is >> t0 >> t1 >> t2; // calculate RTT and offset double rtt = (t3 - t0) - (t2 - t1); // round trip time (time passed here - time passed there) double offset = ((t1 - t0) + (t2 - t3)) / 2; // averaged clock offset (other clock - my clock) with rtt bias averaged out // store it estimates_.emplace_back(rtt, offset); // local_time, remote_time estimate_times_.emplace_back((t3 + t0) / 2.0, (t2 + t1) / 2.0); } } } catch (std::exception &e) { LOG_F(WARNING, "Error while processing a time estimation return packet: %s", e.what()); } if (err != asio::error::operation_aborted) receive_next_packet(); } void time_receiver::result_aggregation_scheduled(err_t err) { if (err) return; if ((int)estimates_.size() >= cfg_->time_update_minprobes()) { // take the estimate with the lowest error bound (=rtt), as in NTP double best_offset = 0, best_rtt = FOREVER; double best_remote_time = 0; for (std::size_t k = 0; k < estimates_.size(); k++) { if (estimates_[k].first < best_rtt) { best_rtt = estimates_[k].first; best_offset = estimates_[k].second; best_remote_time = estimate_times_[k].second; } } // and notify that the result is available { std::lock_guard lock(timeoffset_mut_); uncertainty_ = best_rtt; timeoffset_ = -best_offset; remote_time_ = best_remote_time; } timeoffset_upd_.notify_all(); } } void time_receiver::reset_timeoffset_on_recovery() { std::lock_guard lock(timeoffset_mut_); if (timeoffset_ != NOT_ASSIGNED) // this will only be set to true if the reset may have caused a possible interruption in the // obtained time offsets was_reset_ = true; timeoffset_ = NOT_ASSIGNED; } liblsl-1.17.7/src/time_receiver.h000066400000000000000000000112671517625163100166700ustar00rootroot00000000000000#ifndef TIME_RECEIVER_H #define TIME_RECEIVER_H #include "socket_utils.h" #include #include #include #include #include #include #include using asio::ip::udp; using err_t = const asio::error_code &; namespace lsl { using steady_timer = asio::basic_waitable_timer, asio::io_context::executor_type>; class inlet_connection; class api_config; /// list of time estimates with error bounds using estimate_list = std::vector>; /** * Internal class of an inlet that's responsible for retrieving time-correction data of the inlet. * The actual communication runs in an internal background thread, while the public function * (time_correction()) waits for the thread to finish. * The public function has an optional timeout after which it gives up, while the background thread * continues to do its job (so the next public-function call may succeed within the timeout). * The background thread terminates only if the time_receiver is destroyed or the underlying * connection is lost or shut down. */ class time_receiver { public: /// Construct a new time receiver for a given connection. time_receiver(inlet_connection &conn); /// Destructor. Stops the background activities. ~time_receiver(); /** * Retrieve an estimated time correction offset for the given stream. * * The first call to this function takes several msec for an initial estimate, subsequent calls * are instantaneous. * The correction offset is periodically re-estimated in the background (once every few sec.). * @param remote_time Time of this measurment on remote computer * @param uncertainty Maximum uncertainty of this measurement (maps to round-trip-time). * @param timeout Timeout for first time-correction estimate. * @return The time correction estimate. * @throws timeout_error If the initial estimate times out. */ double time_correction(double timeout = 2); double time_correction(double *remote_time, double *uncertainty, double timeout); /** * Determine whether the clock was (potentially) reset since the last call to was_reset() * * This can happen if the stream got lost (e.g., app crash) and the computer got restarted or * swapped out */ bool was_reset(); private: /// The time reader / updater thread. void time_thread(); /// Start a new multi-packet exchange for time estimation void start_time_estimation(); /// Send the next packet in an exchange void send_next_packet(int packet_num); /// Request reception of the next time packet void receive_next_packet(); /// Handler that gets called once reception of a time packet has completed void handle_receive_outcome(err_t err, std::size_t len); /// Handlers that gets called once the time estimation results shall be aggregated. void result_aggregation_scheduled(err_t err); /// Ensures that the time-offset is reset when the underlying connection is recovered (e.g., /// switches to another host) void reset_timeoffset_on_recovery(); /// the underlying connection inlet_connection &conn_; // background reader thread and the data generated by it /// updates time offset std::thread time_thread_; /// whether the clock was reset bool was_reset_; /// the current time offset (or NOT_ASSIGNED if not yet assigned) double timeoffset_; /// remote computer time at the specified timeoffset_ double remote_time_; /// round trip time (a.k.a. uncertainty) at the specficied timeoffset_ double uncertainty_; /// mutex to protect the time offset std::mutex timeoffset_mut_; /// condition variable to indicate that an update for the time offset is available std::condition_variable timeoffset_upd_; // data used internally by the background thread /// the configuration object const api_config *cfg_; /// an IO service for async time operations asio::io_context time_io_; /// a buffer to hold inbound packet contents char recv_buffer_[1024]{0}; /// the socket through which the time thread communicates udp_socket time_sock_; /// current outlet address udp::endpoint outlet_addr_; /// schedule the next time estimate steady_timer next_estimate_; /// schedules result aggregation steady_timer aggregate_results_; /// schedules the next packet transfer steady_timer next_packet_; /// a dummy endpoint udp::endpoint remote_endpoint_; /// a vector of time estimates collected so far during the current exchange estimate_list estimates_; /// a vector of the local time and the remote time at a given estimate estimate_list estimate_times_; /// an id for the current wave of time packets int current_wave_id_{0}; }; } // namespace lsl #endif liblsl-1.17.7/src/udp_server.cpp000066400000000000000000000165071517625163100165610ustar00rootroot00000000000000#include "udp_server.h" #include "api_config.h" #include "socket_utils.h" #include "stream_info_impl.h" #include "util/strfuns.hpp" #include #include #include #include #include #include #include #include #include namespace ip = asio::ip; namespace lsl { udp_server::udp_server(stream_info_impl_p info, asio::io_context &io, udp protocol) : info_(std::move(info)), io_(io), socket_(std::make_shared(io)), time_services_enabled_(true) { // open the socket for the specified protocol socket_->open(protocol); // bind to a free port uint16_t port = bind_port_in_range(*socket_, protocol); // assign the service port field if (protocol == udp::v4()) info_->v4service_port(port); else info_->v6service_port(port); LOG_F(2, "%s: Started unicast udp server at port %d (addr %p)", info_->name().c_str(), port, (void *)this); } udp_server::udp_server(stream_info_impl_p info, asio::io_context &io, ip::address addr, uint16_t port, int ttl, const std::string &listen_address) : info_(std::move(info)), io_(io), socket_(std::make_shared(io)), time_services_enabled_(false) { bool is_broadcast = addr == ip::address_v4::broadcast(); // set up the endpoint where we listen (note: this is not yet the multicast address) udp::endpoint listen_endpoint; if (listen_address.empty()) { // pick the default endpoint if (addr.is_v4()) listen_endpoint = udp::endpoint(udp::v4(), port); else listen_endpoint = udp::endpoint(udp::v6(), port); } else { // choose an endpoint explicitly ip::address listen_addr = ip::make_address(listen_address); listen_endpoint = udp::endpoint(listen_addr, (uint16_t)port); } // open the socket and make sure that we can reuse the address, and bind it socket_->open(listen_endpoint.protocol()); socket_->set_option(udp::socket::reuse_address(true)); // set the multicast TTL if (addr.is_multicast() && !is_broadcast) socket_->set_option(ip::multicast::hops(ttl)); // bind to the listen endpoint socket_->bind(listen_endpoint); // join the multicast groups if (addr.is_multicast() && !is_broadcast) { bool joined_anywhere = false; asio::error_code err; for (auto &if_ : api_config::get_instance()->multicast_interfaces) { DLOG_F( INFO, "Joining %s to %s", if_.addr.to_string().c_str(), addr.to_string().c_str()); if (addr.is_v4() && if_.addr.is_v4()) socket_->set_option(ip::multicast::join_group(addr.to_v4(), if_.addr.to_v4()), err); else if (addr.is_v6() && if_.addr.is_v6()) socket_->set_option( ip::multicast::join_group(addr.to_v6(), if_.addr.to_v6().scope_id()), err); if (err) LOG_F(1, "Could not bind multicast responder for %s to interface %s (%s)", addr.to_string().c_str(), if_.addr.to_string().c_str(), err.message().c_str()); else joined_anywhere = true; } if (!joined_anywhere) throw std::runtime_error("Could not join any multicast group"); } LOG_F(2, "%s: Started multicast udp server at %s port %d (addr %p)", this->info_->name().c_str(), addr.to_string().c_str(), port, (void *)this); } // === externally issued asynchronous commands === void udp_server::begin_serving() { // pre-calculate the shortinfo message (now that everyone should have initialized their part). shortinfo_msg_ = info_->to_shortinfo_message(); // start asking for a packet request_next_packet(); } void udp_server::end_serving() { // gracefully close the socket; this will eventually lead to the cancellation of the IO // operation(s) tied to its socket auto sock(socket_); // socket shared ptr to be kept alive const char *fn = __func__; post(io_, [sock, fn]() { try { if (sock->is_open()) sock->close(); } catch (std::exception &e) { LOG_F(ERROR, "Error during %s: %s", fn, e.what()); } }); } // === receive / reply loop === void udp_server::request_next_packet() { DLOG_F(5, "udp_server::request_next_packet"); socket_->async_receive_from(asio::buffer(buffer_), remote_endpoint_, [shared_this = shared_from_this()]( err_t err, std::size_t len) { shared_this->handle_receive_outcome(err, len); }); } void udp_server::process_shortinfo_request(std::istream& request_stream) { std::string query; getline(request_stream, query); query = trim(query); // parse return address, port, and query ID uint16_t return_port; request_stream >> return_port; std::string query_id; request_stream >> query_id; DLOG_F(2, "%p shortinfo req from %s for %s", (void *)this, remote_endpoint_.address().to_string().c_str(), query.c_str()); // check query if (info_->matches_query(query)) { LOG_F(3, "%p query matches, replying to port %d", (void *)this, return_port); // query matches: send back reply udp::endpoint return_endpoint(remote_endpoint_.address(), return_port); string_p replymsg( std::make_shared((query_id += "\r\n") += shortinfo_msg_)); socket_->async_send_to(asio::buffer(*replymsg), return_endpoint, [shared_this = shared_from_this(), replymsg](err_t err_, std::size_t /*unused*/) { if (err_ != asio::error::operation_aborted && err_ != asio::error::shut_down) shared_this->request_next_packet(); }); } else { DLOG_F(2, "%p query didn't match", (void *)this); request_next_packet(); } } void udp_server::process_timedata_request(std::istream &request_stream, double t1) { int wave_id; request_stream >> wave_id; double t0; request_stream >> t0; // send it off (including the time of packet submission and a shared ptr to the // message content owned by the handler) std::ostringstream reply; reply.precision(16); reply << ' ' << wave_id << ' ' << t0 << ' ' << t1 << ' ' << lsl_clock(); string_p replymsg(std::make_shared(reply.str())); socket_->async_send_to(asio::buffer(*replymsg), remote_endpoint_, [shared_this = shared_from_this(), replymsg](err_t err_, std::size_t /*unused*/) { if (err_ != asio::error::operation_aborted && err_ != asio::error::shut_down) shared_this->request_next_packet(); }); } void udp_server::handle_receive_outcome(err_t err, std::size_t len) { DLOG_F(6, "udp_server::handle_receive_outcome (%lub)", len); if (err) { // non-critical error? Wait for the next packet if the socket is still open if (err != asio::error::operation_aborted && err != asio::error::shut_down && socket_ && socket_->is_open()) request_next_packet(); return; } try { // remember the time of packet reception for possible later use double t1 = time_services_enabled_ ? lsl_clock() : 0.0; // wrap received packet into a request stream and parse the method from it std::istringstream request_stream(std::string(buffer_, buffer_ + len)); std::string method; getline(request_stream, method); method = trim(method); if (method == "LSL:shortinfo") { // shortinfo request: parse content query string process_shortinfo_request(request_stream); return; } if (time_services_enabled_ && method == "LSL:timedata") { // timedata request: parse time of original transmission process_timedata_request(request_stream, t1); return; } DLOG_F(INFO, "%p Unknown method '%s' received by udp-server", (void *)this, method.c_str()); } catch (std::exception &e) { LOG_F( WARNING, "%p udp_server: hiccup during request processing: %s", (void *)this, e.what()); } request_next_packet(); } } // namespace lsl liblsl-1.17.7/src/udp_server.h000066400000000000000000000057701517625163100162260ustar00rootroot00000000000000#ifndef UDP_SERVER_H #define UDP_SERVER_H #include "forward.h" #include "socket_utils.h" #include #include #include #include #include using asio::ip::udp; using err_t = const asio::error_code &; namespace lsl { /// shared pointer to a socket using udp_socket_p = std::shared_ptr; /** * A lightweight UDP responder service. * * Understands the following messages: * - `LSL:shortinfo`. This is a request for the stream_info that comes with a query string (and a * return address). A packet is returned only if the query matches. * - `LSL:timedata`. This is a request for time synchronization info that comes with a time stamp * (t0). The t0 stamp and two more time stamps (t1 and t2) are returned (similar to the NTP packet * exchange). */ class udp_server : public std::enable_shared_from_this { public: /** * Create a UDP responder that listens "side by side" with a TCP server. * * This server will listen on a free local port for timedata and shortinfo requests -- mainly * for timing information (unless shortinfo is needed by clients). * @param info The stream_info of the stream to serve (shared). After success, the appropriate * service port will be assigned. * @param io asio::io_context that runs the server's async operations * @param protocol The protocol stack to use (tcp::v4() or tcp::v6()). */ udp_server(stream_info_impl_p info, asio::io_context &io, udp protocol); /** * Create a new UDP server in multicast mode. * * This server will listen on a multicast address and responds only to LSL:shortinfo requests. * This is for multicast/broadcast (and optionally unicast) local service discovery. */ udp_server(stream_info_impl_p info, asio::io_context &io, asio::ip::address addr, uint16_t port, int ttl, const std::string &listen_address); /// Start serving UDP traffic. /// Call this only after the (shared) info object has been initialized by every involved party. void begin_serving(); /// Initiate teardown of UDP traffic. void end_serving(); private: /// Initiate next packet request. /// The result of the operation will eventually trigger the handle_receive_outcome() handler. void request_next_packet(); /// Handler that gets called when a the next packet was received (or the op was cancelled). void handle_receive_outcome(err_t err, std::size_t len); /// Parse and process a LSL::shortinfo request void process_shortinfo_request(std::istream& request_stream); /// Parse and process a LSL::timedata request void process_timedata_request(std::istream& request_stream, double t1); /// stream_info reference stream_info_impl_p info_; /// IO service reference asio::io_context &io_; udp_socket_p socket_; /// a buffer of data (we're receiving on it) char buffer_[65536]{0}; bool time_services_enabled_; /// the endpoint that we're currently talking to) udp::endpoint remote_endpoint_; /// pre-computed server response std::string shortinfo_msg_; }; } // namespace lsl #endif liblsl-1.17.7/src/util/000077500000000000000000000000001517625163100146435ustar00rootroot00000000000000liblsl-1.17.7/src/util/cast.cpp000066400000000000000000000022261517625163100163030ustar00rootroot00000000000000#include "cast.hpp" #include #include #include #include namespace lsl { template <> std::string to_string(double val) { std::ostringstream os; os.imbue(std::locale::classic()); os << std::setprecision(16) << std::showpoint << val; return os.str(); } template <> std::string to_string(float val) { std::ostringstream os; os.imbue(std::locale::classic()); os << std::setprecision(8) << std::showpoint << val; return os.str(); } template T from_string(const std::string &str) { std::istringstream is(str); is.imbue(std::locale::classic()); T res; is >> res; return res; } // Explicit template instantiations so from_string doesn't have to be compiled // in every compilation unit template float from_string(const std::string &); template double from_string(const std::string &); template signed char from_string(const std::string &); template char from_string(const std::string &); template int16_t from_string(const std::string &); template int32_t from_string(const std::string &); template uint32_t from_string(const std::string &); template int64_t from_string(const std::string &); } // namespace lsl liblsl-1.17.7/src/util/cast.hpp000066400000000000000000000006051517625163100163070ustar00rootroot00000000000000#pragma once #include namespace lsl { template std::string to_string(T val) { return std::to_string(val); } template <> std::string to_string(double val); template <> std::string to_string(float val); template T from_string(const std::string &str); template <> inline bool from_string(const std::string &str) { return str == "1"; } } // namespace lsl liblsl-1.17.7/src/util/endian.cpp000066400000000000000000000005341517625163100166070ustar00rootroot00000000000000#include "endian.hpp" #include "../common.h" double lsl::measure_endian_performance() { const double measure_duration = 0.01; const double t_end = lsl_clock() + measure_duration; uint64_t data = 0x01020304; double k; for (k = 0; ((int)k & 0xFF) != 0 || lsl_clock() < t_end; k++) lslboost::endian::endian_reverse_inplace(data); return k; } liblsl-1.17.7/src/util/endian.hpp000066400000000000000000000024321517625163100166130ustar00rootroot00000000000000#pragma once #include namespace lsl { using lslboost::endian::endian_reverse_inplace; using byteorder = lslboost::endian::order; enum Endianness { LSL_PORTABLE_ENDIAN = 0, LSL_LITTLE_ENDIAN_BUT_BIG_FLOAT = 1, LSL_BIG_ENDIAN_BUT_LITTLE_FLOAT = 2, LSL_LITTLE_ENDIAN = 1234, LSL_BIG_ENDIAN = 4321, LSL_PDP11 = 2134 // deprecated, not used nowadays }; /// the host native byte order const Endianness LSL_BYTE_ORDER = (byteorder::native == byteorder::little) ? LSL_LITTLE_ENDIAN : LSL_BIG_ENDIAN; inline bool can_convert_endian(Endianness requested, int value_size) { if(value_size == 1) return true; if (requested != LSL_LITTLE_ENDIAN && requested != LSL_BIG_ENDIAN) return false; if (LSL_BYTE_ORDER != LSL_LITTLE_ENDIAN && LSL_BYTE_ORDER != LSL_BIG_ENDIAN) return false; return true; } inline bool can_convert_endian(int requested, int value_size) { return can_convert_endian(static_cast(requested), value_size); } /// Measure the endian conversion performance of this machine. double measure_endian_performance(); // Determine target byte order / endianness using byteorder = lslboost::endian::order; static_assert(byteorder::native == byteorder::little || byteorder::native == byteorder::big, "Unsupported byteorder"); } // namespace lsl liblsl-1.17.7/src/util/inireader.cpp000066400000000000000000000041561517625163100173170ustar00rootroot00000000000000#include "inireader.hpp" #include #include void INI::load(std::istream &infile) { std::string line; std::string section; int linenr = 0; static const char ws[] = " \t\r\n"; while (std::getline(infile, line)) { linenr++; // Comment / empty line if (line[0] == ';' || line.find_first_not_of(ws) == std::string::npos) continue; // Section if (line[0] == '[') { std::size_t closingbracket = line.find(']'); if (closingbracket == std::string::npos) throw std::runtime_error( "No closing bracket ] found in line " + std::to_string(linenr)); line[closingbracket] = '.'; section = line.substr(1, closingbracket); continue; } // Key / Value - Pair std::size_t eqpos = line.find('='); if (eqpos == std::string::npos) throw std::runtime_error("No Key-Value pair in line " + std::to_string(linenr)); auto keybegin = line.find_first_not_of(ws), keyend = line.find_last_not_of(ws, eqpos - 1), valbegin = line.find_first_not_of(ws, eqpos + 1), valend = line.find_last_not_of(ws); if (keyend == std::string::npos) throw std::runtime_error("Empty key in line " + std::to_string(linenr)); if (valbegin == std::string::npos || valend == eqpos) throw std::runtime_error("Empty value in line " + std::to_string(linenr)); std::string key = section; key += line.substr(keybegin, keyend - keybegin + 1); if (values.find(key) != values.end()) throw std::runtime_error("Duplicate key " + key); values.insert(std::make_pair(key, line.substr(valbegin, valend - valbegin + 1))); } } template T INI::get(const char *key, T defaultval) { auto it = values.find(key); if (it == values.end()) return defaultval; return lsl::from_string(it->second); } template float INI::get(const char *, float); template int INI::get(const char *, int); template double INI::get(const char *, double); template bool INI::get(const char *, bool); template <> const char *INI::get(const char *key, const char *defaultval) { static const char empty[] = ""; auto it = values.find(key); return it == values.end() ? (defaultval ? defaultval : empty) : it->second.c_str(); } liblsl-1.17.7/src/util/inireader.hpp000066400000000000000000000007531517625163100173230ustar00rootroot00000000000000#include "cast.hpp" #include #include #include // Reads an INI file from a stream into a map class INI { std::unordered_map values; public: void load(std::istream &infile); template T get(const char *key, T defaultval = T()); }; // specialization for const char*, returns an empty string ("") by default instead of nullptr template <> const char *INI::get(const char *key, const char *defaultval); liblsl-1.17.7/src/util/strfuns.cpp000066400000000000000000000027011517625163100170530ustar00rootroot00000000000000#include "strfuns.hpp" std::vector lsl::splitandtrim( const std::string &input, char separator, bool keepempty) { std::vector parts; auto it = input.cbegin(); while (true) { // Skip whitespace in the beginning it = lsl::trim_begin(it, input.cend()); // find the next separator or end of string auto endit = it; while (endit != input.end() && *endit != separator) ++endit; // mark beginning of next part if not at the end auto next = endit; // shrink the range so it doesn't include whitespace at the end endit = trim_end(it, endit); if (endit != it || keepempty) parts.emplace_back(it, endit); if (next != input.cend()) it = next + 1; else break; } return parts; } bool lsl::split_headerline(char *buf, std::size_t bufsize, std::string &type, std::string &value) { char *end = buf + bufsize, *middle_it = nullptr; buf = trim_begin(buf, end); // find the end of the header line, i.e. the end of the buffer or the start of a comment for (auto *it = buf; it != end; ++it) { if(!*it || *it == ';') { end = it; break; } if (*it == ':') middle_it = it; } // no separator found? if (middle_it == nullptr) return false; auto *value_begin = middle_it + 1; trim(value_begin, end); // convert to lowercase for (auto *it = buf; it != end; ++it) if (*it >= 'A' && *it <= 'Z') *it += 'a' - 'A'; type.assign(buf, trim_end(buf, middle_it)); value.assign(value_begin, end); return true; } liblsl-1.17.7/src/util/strfuns.hpp000066400000000000000000000030611517625163100170600ustar00rootroot00000000000000#pragma once #include #include namespace lsl { inline bool isspace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } /// trim whitespace at the beginning of a sequence, return position of first non-whitespace character template IteratorType trim_begin(IteratorType begin, IteratorType end) { while (begin != end && lsl::isspace(*begin)) ++begin; return begin; } /// trim whitespace at the end of a sequence, return position one past the last non-whitespace character template IteratorType trim_end(IteratorType begin, IteratorType end) { while (end > begin && lsl::isspace(*(end - 1))) --end; return end; } /// remove whitespace from the beginning and end of a sequence, modifying both start and end iterators template void trim(IteratorType &begin, IteratorType &end) { end = trim_end(begin, end); begin = trim_begin(begin, end); } /// split a header line ("Foo-Bar: 512 ; some comment") into type ("foo-bar") and value ("512") bool split_headerline(char *buf, std::size_t bufsize, std::string &type, std::string &value); /// return a string with whitespace at the beginning and end trimmed inline std::string trim(const std::string &input) { std::string::const_iterator begin = input.begin(), end = input.end(); lsl::trim(begin, end); return {begin, end}; } /// split a separated string like "this,is a,list" into its parts std::vector splitandtrim( const std::string &input, char separator = ',', bool keepempty = false); } // namespace lsl liblsl-1.17.7/src/util/uuid.hpp000066400000000000000000000025301517625163100163220ustar00rootroot00000000000000#pragma once #include #include struct UUID { uint8_t data[16]{0}; void set_version(uint8_t version = 4) { data[6] = (data[6] & 0x0f) | static_cast(version << 4); } void set_variant2() { data[8] = (data[8] & 0x3f) | 0x80; } /// return the UUID formatted as RFC 4122 UUID std::string to_string() const { std::string out(36, '0'); auto pos = out.begin(); const uint8_t *curbyte = data; /// helper: write `n` hex-formatted bytes as hex to a string, incrementing the positions const auto copybytesashex = [&pos, &curbyte](int n) { const char tbl[] = "0123456789abcdef"; for (int i = 0; i < n; ++i) { *pos++ = tbl[*curbyte / 16]; *pos++ = tbl[*curbyte++ & 0x0f]; } }; copybytesashex(2); // 4x "abcd-" for (int i = 0; i < 4; ++i) { copybytesashex(2); *pos++ = '-'; } copybytesashex(6); return out; } /// generate a random UUID4 static UUID random() { UUID uuid; // cast data to an array of the RNGs native type, write random bytes directly to it std::random_device rng; using rand_t = std::random_device::result_type; constexpr int rand_elems = sizeof(UUID::data) / sizeof(rand_t); auto *data_view = reinterpret_cast(uuid.data); for (int i = 0; i < rand_elems; ++i) data_view[i] = rng(); uuid.set_version(4); uuid.set_variant2(); return uuid; } }; liblsl-1.17.7/testing/000077500000000000000000000000001517625163100145545ustar00rootroot00000000000000liblsl-1.17.7/testing/BounceTest/000077500000000000000000000000001517625163100166275ustar00rootroot00000000000000liblsl-1.17.7/testing/BounceTest/BounceTest.cpp000066400000000000000000000052701517625163100214120ustar00rootroot00000000000000#include #include #include #include "../../include/lsl_cpp.h" #include #include #include #include /* Test the latency between sending a sample and receiving it*/ int main(int argc, char *argv[]) { /* if (argc != 2) std::cout << "Usage: bounce [sender|receiver]" << std::endl; return 1; } std::string role(argv[1]); bool sender; if(role=="sender") sender=true; else if(role=="receiver") sender=false; else { std::cout << "Usage: bounce [sender|receiver]" << std::endl; return 1; }*/ //constexpr is not available in Visual Studio 2013 /*constexpr*/ int numBounces = 10000; double timestamps[10000/*numBounces*/][2]; auto streaminfo = lsl::stream_info("Sender", "Bounce", 1, lsl::IRREGULAR_RATE, lsl::cf_int32); lsl::stream_outlet outlet(streaminfo); auto found_stream_info = lsl::resolve_stream("name", "Sender"); if(found_stream_info.empty()) throw std::runtime_error("Sender outlet not found!"); lsl::stream_info si = found_stream_info[0]; std::cout << "Found " << si.name() << '@' << si.hostname() << std::endl; lsl::stream_inlet inlet(found_stream_info[0]); // push a single sample first int dummy=0; outlet.push_sample(&dummy, 0, true); inlet.pull_sample(&dummy, 1, 2); std::cout << "Starting bounce loop" << std::endl; for(int32_t counter=0; counter < numBounces; counter++) { int32_t received_counter; timestamps[counter][0] = lsl::local_clock(); outlet.push_sample(&counter, timestamps[counter][0], true); inlet.pull_sample(&received_counter, 1, 2); timestamps[counter][1] = lsl::local_clock(); if(received_counter != counter) throw std::runtime_error("Got the wrong sample!"); } double latencies[10000/*numBounces*/]; double meanLatency = 0; for(int i=1; i < numBounces; i++) { latencies[i] = (timestamps[i][1] - timestamps[i][0]); meanLatency += latencies[i] / numBounces; } double sdLatency = 0; for(double x_i: latencies) sdLatency+=((x_i-meanLatency)*(x_i-meanLatency))/numBounces; sdLatency = std::sqrt(sdLatency); /*std::cout << "LSL " << lsl::library_version() << ", Debug: " << LSLDEBUG << ", System Boost: " << LSL_USE_SYSTEM_BOOST << std::endl;*/ std::cout << "Bounced " << numBounces << " samples, latency M=" << (meanLatency*1000) << "ms, SD=" << (sdLatency*1000) << "ms" << std::endl; std::ofstream tscsv("bounce.csv"); if(tscsv.is_open()) { tscsv << "t1\tt2\n" << std::setprecision(15) << std::fixed; for(int i=0;i ) target_link_libraries(common PUBLIC Catch2::Catch2) add_executable(lsl_test_exported ext/DataType.cpp ext/discovery.cpp ext/move.cpp ext/streaminfo.cpp ext/time.cpp ) target_link_libraries(lsl_test_exported PRIVATE lsl common catch_main) set(LSL_TEST_INTERNAL_SRCS int/inireader.cpp int/network.cpp int/stringfuncs.cpp int/streaminfo.cpp int/samples.cpp int/postproc.cpp int/serialization_v100.cpp int/tcpserver.cpp int/sendbuffer.cpp ) if(NOT MINGW) LIST(APPEND LSL_INTERNAL_SRCS int/loguruthreadnames.cpp) endif() message(STATUS ${LSL_TEST_INTERNAL_SRCS}) add_executable(lsl_test_internal ${LSL_TEST_INTERNAL_SRCS}) target_link_libraries(lsl_test_internal PRIVATE lslobj lslboost common catch_main) # Internal tests need pugixml headers (lslobj links pugixml privately) if(LSL_PUGIXML_IS_FETCHED) target_include_directories(lsl_test_internal PRIVATE ${pugixml_SOURCE_DIR}/src) else() target_link_libraries(lsl_test_internal PRIVATE pugixml::pugixml) endif() # Pretend that lsl_test_internal is part of a shared lib so that it can use the __export symbols lslobj. REQ by MinGW. target_compile_definitions(lsl_test_internal PRIVATE LIBLSL_EXPORTS) # runtime_config test needs a fresh api_config singleton, so it lives in its own executable. add_executable(lsl_test_runtime_config int/runtime_config.cpp) target_link_libraries(lsl_test_runtime_config PRIVATE lslobj lslboost common catch_main) target_compile_definitions(lsl_test_runtime_config PRIVATE LIBLSL_EXPORTS) if(LSL_BENCHMARKS) # to get somewhat reproducible performance numbers: # /usr/bin/time -v testing/lsl_test_exported --benchmark-samples 100 bounce # [unix only] | binary | nr. of samples | test name target_sources(lsl_test_exported PRIVATE ext/bench_bounce.cpp ext/bench_common.cpp ext/bench_pushpull.cpp ) target_sources(lsl_test_internal PRIVATE int/bench_sleep.cpp int/bench_timesync.cpp ) endif() set(LSL_TESTS lsl_test_exported lsl_test_internal lsl_test_runtime_config) foreach(lsltest ${LSL_TESTS}) add_test(NAME ${lsltest} COMMAND ${lsltest} --wait-for-keypress never) if(LSL_INSTALL) install(TARGETS ${lsltest} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") endif() # # Uncomment me if debugging tests on Windows. Commented because it does not work well on CI runners. # if(WIN32) # # Copy dependencies into build directory to enable debugging builds in Windows. # add_custom_command(TARGET ${lsltest} # POST_BUILD # COMMAND # ${CMAKE_COMMAND} -E copy_if_different # $ # $ # COMMAND_EXPAND_LISTS # ) # endif(WIN32) endforeach() if(LSL_INSTALL) install(DIRECTORY lslcfgs DESTINATION "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}") endif() liblsl-1.17.7/testing/SpeedTest/000077500000000000000000000000001517625163100164545ustar00rootroot00000000000000liblsl-1.17.7/testing/SpeedTest/SpeedTest.cpp000066400000000000000000000131131517625163100210570ustar00rootroot00000000000000#include #include "../../include/lsl_cpp.h" #include #include #include #include #include #include #include #include using namespace std; // set to true by the main program when we're ready to quit bool stop_inlet = false, stop_outlet = false, start_outlet = false; // get the current time double now() { return lsl::local_clock(); } // initialize a sample with data template vector init_sample(int numchan) { vector sample(numchan); for (int c=0;c void init_sample(int numchan, vector &sample) { sample.resize(numchan); for (int c=0;c void run_outlet(string name, string type, int numchan, lsl::channel_format_t fmt, double srate, int chunk_len, int max_samples) { try { // create a new streaminfo and outlet std::ostringstream uid; uid << name << type << numchan << fmt; lsl::stream_info info(name,type,numchan,srate,fmt,uid.str()); lsl::stream_outlet outlet(info); cout << "outlet started." << endl; while (!start_outlet) lslboost::this_thread::sleep(lslboost::posix_time::milliseconds(1)); // initialize data to send vector sample,chunk; init_sample(numchan,sample); init_sample((int)(numchan*(chunk_len*srate/1000*5)),chunk); // send in bursts double start_time = lsl::local_clock(); for (int target,diff,written=0;written void run_inlet(string type, bool in_chunks, int buffer_len) { try { vector > chunk; vector sample; // resolve by type and create inlet vector results = lsl::resolve_stream("type",type); lsl::stream_inlet inlet(results[0],buffer_len); cout << "inlet started." << endl; start_outlet = true; // variables for data rate testing double starttime = lsl::local_clock(), next_display = starttime+1; double ts, last_ts; // run for (int k=0,num_samples=0;!stop_inlet;k++) { lslboost::this_thread::sleep(lslboost::posix_time::milliseconds(10)); if (in_chunks) { inlet.pull_chunk(chunk); num_samples += chunk.size(); } else { while (ts = inlet.pull_sample(sample,0.0)) { last_ts = ts; num_samples++; } } // display code if (k % 50 == 0) { double now = lsl::local_clock(); if (now>next_display) { cout << num_samples/(now-starttime) << " samples/sec" << endl; // " samples/sec; latency = " << 1000*(now-last_ts) << " ms." << endl; // NOTE: testing latency requires that data is sent with a burst length of 0 next_display = now+1; } } } cout << "inlet finished." << endl; } catch(lslboost::thread_interrupted &) {} catch(std::exception &e) { std::cerr << "ERROR during run_inlet() Stress-test function: " << e.what() << std::endl; } } int main(int argc, char* argv[]) { double srate = 500000; int numchans = 1; lsl::channel_format_t format = lsl::cf_int8; int burstlen = 10; int maxsamples = 10000000; int bufferlen = 10; if (argc > 1) srate = lslboost::lexical_cast(argv[1]); if (argc > 2) numchans = lslboost::lexical_cast(argv[2]); if (argc > 3) { std::map m; m["int8"]=lsl::cf_int8;m["int16"]=lsl::cf_int16;m["int32"]=lsl::cf_int32;m["int64"]=lsl::cf_int64;m["float"]=lsl::cf_float32;m["double"]=lsl::cf_double64;m["float32"]=lsl::cf_float32;m["double64"]=lsl::cf_double64; m["string"]=lsl::cf_string; format = m[argv[3]]; } if (argc > 4) burstlen = lslboost::lexical_cast(argv[4]); if (argc > 5) maxsamples = lslboost::lexical_cast(argv[5]); if (argc > 6) bufferlen = lslboost::lexical_cast(argv[6]); { // fast serial transmission (1-channel char) lslboost::thread outlet(lslboost::bind(&run_outlet,"FastSerial","Serial",numchans,format,srate,burstlen,maxsamples)); lslboost::thread inlet(lslboost::bind(&run_inlet,"Serial",false,bufferlen)); // typical audio transmission (44KHz, 2-ch, 16-bit, 512-sample chunks) //boost::thread a(boost::bind(&run_outlet,"TypicalAudio","Audio",2,lsl::cf_int16,44100,512,1000000)); //boost::thread b(boost::bind(&run_inlet,"Audio",true,10)); // HD video transmission (30Hz, 2-ch, 16-bit, 512-sample chunks) //boost::thread outlet(boost::bind(&run_outlet,"HDVideo","Video",640*480*3,lsl::cf_int8,10,0,1000000)); //boost::thread inlet(boost::bind(&run_inlet,"Video",false,1)); //boost::thread highchan_outlet_thread(boost::bind(&run_outlet,1000000,"HighChanStream","Video",1920*1080*3,lsl::cf_int8,60,1000000,-1)); //boost::thread highchan_inlet_thread(boost::bind(&run_inlet,1000000,"HighChanStream","Video",false,false,false,1000000)); //cout << "Press [Enter] to exit: " << endl; cin.get(); // take'em down outlet.join(); stop_inlet = true; inlet.join(); } cout << "exiting..." << endl; return 0; } liblsl-1.17.7/testing/StressTest/000077500000000000000000000000001517625163100166775ustar00rootroot00000000000000liblsl-1.17.7/testing/StressTest/StressTest.cpp000066400000000000000000000230131517625163100215250ustar00rootroot00000000000000#include #include "../../include/lsl_cpp.h" #include #include #include #include #include #include #include #include #include #include #include using namespace std; // key stress constants int max_outlets = 15; // was 20 int max_inlets = 20; // was 30 int min_chunk_len_ms = 1; int max_chunk_len_ms = 100; int max_inlet_poll_interval_ms = 100; int inlet_max_failure_interval_ms = 2000; int outlet_max_failure_interval_ms = 2000; int inlet_min_failure_interval_ms = 1; int outlet_min_failure_interval_ms = 1; int max_outlet_duration = 10; int max_inlet_duration = 10; double spawn_inlet_interval = 0.5; double spawn_outlet_interval = 0.5; int max_srate = 1000; int max_channels = 10; int max_buffered = 6; // misc parameters int max_chunk_oversize_factor = 5; int max_samples = 10000000; lslboost::atomic num_outlets(0); lslboost::atomic num_inlets(0); // some random names, types and formats lsl::channel_format_t fmts[] = {lsl::cf_int8,lsl::cf_int16,lsl::cf_int32,lsl::cf_float32,lsl::cf_double64,lsl::cf_string}; const char *names[] = {"Test1","Test2","Test3","Test4"}; const char *types[] = {"EEG","Audio","MoCap"}; // set to true by the main program when we're ready to quit bool stop_inlet = false, stop_outlet = false, start_outlet = false; // initialize a sample with data template void init_sample(int numchan, vector &sample) { sample.resize(numchan); for (int c=0;c()(s.str())); try { // choose random parameters if desired double duration = (duration_ == 0.0) ? 1.0+rand()%(max_outlet_duration-1) : duration_; string name = name_.empty() ? names[rand()%(sizeof(names)/sizeof(names[0]))] : name_; string type = type_.empty() ? types[rand()%(sizeof(types)/sizeof(types[0]))] : type_; int numchan = (numchan_ == 0) ? 1+(rand()%(max_channels-1)) : numchan_; double srate = (srate_ == 0.0) ? 1.0 + (rand()%(max_srate-1)) : srate_; lsl::channel_format_t fmt = (fmt_ == lsl::cf_undefined) ? fmts[rand()%6] : fmt_; double seconds_between_failures = (seconds_between_failures_ == 0.0) ? (inlet_min_failure_interval_ms+rand()%outlet_max_failure_interval_ms)/1000.0 : seconds_between_failures_; int chunk_len = (chunk_len_ == 0) ? std::max(min_chunk_len_ms,(rand()%max_chunk_len_ms)) : chunk_len_; // create a new streaminfo lsl::stream_info info(name,type,numchan,srate,fmt,lslboost::uuids::to_string(lslboost::uuids::random_generator()())); // initialize data to send vector chunk; init_sample((int)(numchan*floor(chunk_len*srate/1000*max_chunk_oversize_factor)),chunk); // and run... for (double endtime = lsl::local_clock()+duration;lsl::local_clock()fail_at) break; target = (int)floor((now-start_time)*srate); int num_elements = (int)std::min((std::size_t)((target-written)*numchan),chunk.size()); if (num_elements) outlet.push_chunk_multiplexed(&chunk[0],num_elements); diff = num_elements/numchan; } } cout << "del outlet(" << name << "," << type << "," << numchan << "," << fmt << "," << srate << ")" << endl; // downtime lslboost::this_thread::sleep(lslboost::posix_time::millisec(100*(rand()%50))); } } catch(std::exception &e) { std::cerr << "ERROR during run_outlet() Stress-test function: " << e.what() << std::endl; } num_outlets.fetch_sub(1); } // run an inlet for some time (optionally with sporadic interruptions in between) void run_inlet(const double duration_=0.0, const string name_=string(), const string type_=string(), const int in_chunks_=-1, const int request_info_=-1, const int request_time_=-1, const double seconds_between_failures_=0.0) { num_inlets.fetch_add(1); std::ostringstream s; s << lslboost::this_thread::get_id(); srand((unsigned)lslboost::hash()(s.str())); try { // choose random parameters if desired double duration = (duration_ == 0.0) ? 1.0+rand()%(max_outlet_duration-1) : duration_; string name = name_.empty() ? names[rand()%(sizeof(names)/sizeof(names[0]))] : name_; string type = type_.empty() ? types[rand()%(sizeof(types)/sizeof(types[0]))] : type_; int request_info = (request_info_==-1) ? rand()%3 == 0 : request_info_; int request_time = (request_time_==-1) ? rand()%3 == 0 : request_time_; double seconds_between_failures = (seconds_between_failures_ == 0.0) ? (inlet_min_failure_interval_ms+rand()%outlet_max_failure_interval_ms)/1000.0 : seconds_between_failures_; // resolve by type... vector results = lsl::resolve_stream("type",type,1,5); if (results.empty()) throw lsl::lost_error("No stream found."); lsl::stream_info result = results[rand()%results.size()]; vector chunk; // and run... double t=0.0; for (double endtime = lsl::local_clock()+duration;lsl::local_clock()1 && string(argv[1]) == "-f") || tolower(cin.get()) == 'y') { lslboost::thread outlets(&random_outlets,0.0,0.0,string(),string(),0,lsl::cf_undefined,0.0,0.0,0); lslboost::thread inlets(&random_inlets,0.0,0.0,string(),string(),-1,-1,-1,0.0); cout << "Press ENTER to exit. " << endl; cin.get();cin.get(); } return 0; } liblsl-1.17.7/testing/SyncTest/000077500000000000000000000000001517625163100163305ustar00rootroot00000000000000liblsl-1.17.7/testing/SyncTest/main.cpp000066400000000000000000000216741517625163100177720ustar00rootroot00000000000000#include #include #include #include #include #include /// Processing options for the time_postprocessor. enum processing_options_t { post_none = 0, // No automatic post-processing; return the ground-truth time stamps for manual post-processing // (this is the default behavior of the inlet). post_clocksync = 1, // Perform automatic clock synchronization; equivalent to manually adding the time_correction() value // to the received time stamps. post_dejitter = 2, // Remove jitter from time stamps. This will apply a smoothing algorithm to the received time stamps; // the smoothing needs to see a minimum number of samples (1-2 minutes) until the remaining jitter // is consistently below 1ms. post_monotonize = 4, // Force the time-stamps to be monotonically ascending (only makes sense if timestamps are dejittered). post_threadsafe = 8, // Post-processing is thread-safe (same inlet can be read from by multiple threads); uses somewhat more CPU. post_ALL = 1|2|4|8 // The combination of all possible post-processing options. }; /// Internal class of an inlet that is responsible for post-processing time stamps. /// (This is a version of time_postprocessor.cpp with the callback functions/hooks removed for easier testing) class time_postprocessor { public: /// Construct a new time post-processor given a callback function that /// returns the time-correction offset for the current data point. time_postprocessor(double nominal_rate): nominal_rate_(nominal_rate), next_query_time_(0.0), last_offset_(0.0), samples_seen_(0.0), options_(post_none), halftime_(90.0), smoothing_initialized_(false), last_value_(-std::numeric_limits::infinity()) { } // dummy implementations double query_correction_() { return 0.0; } double query_srate_() { return nominal_rate_; } double lsl_clock() { return 0.0; } /** * Set post-processing options to use. By default, this class performs NO post-processing and returns the * ground-truth time stamps, which can then be manually synchronized using time_correction(), and then * smoothed/dejittered if desired. This function allows automating these two and possibly more operations. * @param flags An integer that is the result of bitwise OR'ing one or more options from processing_options_t * together (e.g., post_clocksync|post_dejitter); the default is to enable all options. */ void set_options(unsigned options=post_ALL) { options_ = options; } /// Post-process the given time stamp and return the new time-stamp. double process_timestamp(double value) { if (options_ & post_threadsafe) { lslboost::lock_guard lock(processing_mut_); return process_internal(value); } else return process_internal(value); } /** * Override the half-time (forget factor) of the time-stamp smoothing. * The default is 30 seconds unless a different value is set in the config file. * Using a longer window will yield lower jitter in the time stamps, but very * long windows (>2 minutes) will not track rapid changes in the true clock rate * (e.g., due to room temperature changes); a rule of thumb is that an n second * window will be able to track changes happening within 10*n seconds or longer. */ void smoothing_halftime(float value) { halftime_ = value; } private: // Internal function to process a time stamp. double process_internal(double value) { // --- clock synchronization --- if (options_ & post_clocksync) { // update last correction value if needed (we do this every 100 samples and at most once per second) if ((fmod(samples_seen_,100.0) == 0.0) && lsl_clock() > next_query_time_) { last_offset_ = query_correction_(); next_query_time_ = lsl_clock()+1.0; } // perform clock synchronization; this is done by adding the last-measured clock offset value // (typically this is used to map the value from the sender's clock to our local clock) value += last_offset_; } // --- jitter removal --- if (options_ & post_dejitter) { // initialize the smoothing state if not yet done so if (!smoothing_initialized_) { double srate = query_srate_(); smoothing_applicable_ = (srate > 0.0); if (smoothing_applicable_) { // linear regression model coefficients (intercept, slope) w0_ = 0.0; w1_ = 1.0/srate; // forget factor lambda in RLS calculation & its inverse lam_ = pow(2.0, -1.0/(srate*halftime_)); il_ = 1.0/lam_; // inverse autocovariance matrix of predictors u P00_ = P11_ = 1e10; P01_ = P10_ = 0.0; // numeric baseline baseline_value_ = value; } smoothing_initialized_ = true; } if (smoothing_applicable_) { value -= baseline_value_; // RLS update double u1 = samples_seen_; // u = np.matrix([[1.0], [samples_seen]]) double pi0 = P00_ + u1*P10_; // pi = u.T * P double pi1 = P01_ + u1*P11_; // ... (ct'd) double al = value - w0_ - w1_ * u1; // al = value - w.T * u (prediction error) double gam = lam_ + pi0 + pi1 * u1; // gam = lam + pi * u P00_ = il_ * (P00_ - ((pi0*pi0)/gam)); // Pp = k * pi; P = il * (P - Pp) P01_ = il_ * (P01_ - ((pi0*pi1)/gam)); // ... P10_ = il_ * (P10_ - ((pi1*pi0)/gam)); // ... P11_ = il_ * (P11_ - ((pi1*pi1)/gam)); // ... w0_ += al * (P00_ + P10_ * u1); // w = w + k*al w1_ += al * (P01_ + P11_ * u1); // ... value = w0_ + w1_*u1; // value = float(w.T * u) value += baseline_value_; } } // --- force monotonic timestamps --- if (options_ & post_monotonize) { if (value < last_value_) value = last_value_; } samples_seen_ += 1.0; last_value_ = value; return value; } double nominal_rate_; // holds the current nominal sampling rate double samples_seen_; // number of samples seen so far // configuration parameters unsigned options_; // current processing options float halftime_; // smoothing half-time // handling of time corrections double next_query_time_; // the next time when we query the time-correction offset double last_offset_; // last queried correction offset // runtime parameters for smoothing double baseline_value_; // first observed time-stamp value, used as a baseline to improve numerics double w0_, w1_; // linear regression model coefficients double P00_, P01_, P10_, P11_; // inverse covariance matrix double lam_, il_; // forget factor lambda in RLS calculation & its inverse bool smoothing_applicable_; // whether smoothing can be applied to these data (false if irregular srate) bool smoothing_initialized_; // whether the smoothing parameters have been initialized already // runtime parameters for monotonize double last_value_; // last observed time-stamp value, to force monotonically increasing stamps // mutex for thread safety lslboost::mutex processing_mut_; // a mutex that protects the runtime data structures }; // the scripts that regenerate these buffers (and evaluate results) can be found at: ftp://sccn.ucsd.edu/pub/christian/SyncTest const char* const datapath = "C:\\Synched\\Misc\\JitterRemovalTestBuffers\\TestBuffer\\"; const int rates[] = {50,100,200,500,1000,2000}; //,2000,3000,4000,5000}; const int n_chunks = 100; void main() { for (int k=0; k(rate)); // run test double sum_errors = 0.0; int n_samples = 0; for (int chunk=1; chunk<=n_chunks; chunk++) { std::string true_data = loadpath + "_YMatrix_" + lslboost::lexical_cast(chunk) + ".bin"; std::string jittered_data = loadpath + "_ZMatrix_" + lslboost::lexical_cast(chunk) + ".bin"; std::string estimated_data = loadpath + "_WMatrix_" + lslboost::lexical_cast(chunk) + ".bin"; std::ifstream true_stream(true_data.c_str(), std::ifstream::binary); std::ifstream jittered_stream(jittered_data.c_str(), std::ifstream::binary); std::ofstream estimated_stream(estimated_data.c_str(), std::ofstream::binary | std::ofstream::trunc); time_postprocessor processor(rate); processor.set_options(post_ALL & ~post_threadsafe & ~post_clocksync); while (true) { // read new sample, process, also get ground truth double jittered_value = 0.0; jittered_stream.read((char*)&jittered_value, sizeof(jittered_value)); if (!jittered_stream) break; double estimated_value = processor.process_timestamp(jittered_value); double true_value = 0.0; true_stream.read((char*)&true_value, sizeof(true_value)); double error = estimated_value - true_value; // update statistics sum_errors += abs(error); n_samples++; // write resulting sample as well... estimated_stream.write((char*)&estimated_value, sizeof(estimated_value)); } } std::cout << "average error: " << sum_errors/n_samples << std::endl; } std::cin.get(); }liblsl-1.17.7/testing/blackhole.cpp000066400000000000000000000110601517625163100172020ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include // LSL outlet stress tester. Run with `./blackhole --help` for more information. using namespace std::chrono_literals; using Endpoint = asio::ip::tcp::endpoint; using socket_t = asio::basic_stream_socket; using Clock = std::chrono::high_resolution_clock; static char buf[1024 * 1024]; const char handshake[] = "LSL:streamfeed/110 \nMax-buffer-length: 360\r\n\r\n"; class inlet_socket { private: socket_t sock; public: std::size_t bytes_read{0}; inlet_socket(asio::io_context &ctx, Endpoint endpoint) : sock(ctx) { sock.async_connect(endpoint, [this](const asio::error_code &ec) { switch (ec.value()) { case 0: asio::write(sock, asio::const_buffer(handshake, sizeof(handshake) - 1)); schedule_receive(0); break; case asio::error::interrupted: std::cout << "Connect was interrupted…" << std::endl; break; case asio::error::connection_refused: default: throw std::runtime_error("Connection refused"); } }); } void schedule_receive(std::size_t read) { bytes_read += read; sock.async_read_some( asio::buffer(buf, sizeof(buf)), [this](const asio::error_code &ec, std::size_t read) { if (!ec) this->schedule_receive(read); }); } }; class BlackHoleContainer { asio::io_context &ctx; asio::signal_set signals; Clock::time_point last_print; std::list sockets; Endpoint endpoint; public: BlackHoleContainer(asio::io_context &ctx, Endpoint endpoint) : ctx(ctx), signals(ctx), last_print(Clock::now()), endpoint(endpoint) { for (int signal : {SIGUSR1, SIGUSR2, SIGTERM, SIGINT, SIGCONT}) signals.add(signal); add_socket(); handle_signal(0); } void add_socket() { sockets.emplace_back(ctx, endpoint); } void handle_signal(int signal) { auto t = std::chrono::high_resolution_clock::now(); if (signal) { auto time_passed = std::chrono::duration_cast(t - last_print); std::cout.precision(1); std::cout.setf(std::ios::fixed, std::ios::floatfield); std::cout << time_passed.count() << ' '; double total_bandwidth = 0; for (auto &sock : sockets) { double inlet_bandwidth = (sock.bytes_read / 1.024 / 1024 / time_passed.count()); total_bandwidth += inlet_bandwidth; std::cout << inlet_bandwidth << ' '; sock.bytes_read = 0; } std::cout << total_bandwidth << std::endl; if (signal == SIGUSR2) add_socket(); if (signal == SIGTERM || signal == SIGINT) exit(0); } last_print = t; signals.async_wait( [this](const asio::error_code &ec, int signal) { this->handle_signal(signal); }); } }; /// Parse an address specification like "10.0.0.1:16572", "::1:16574" /// or (for link-local addresses with scope IDs) " /// or just a port (16573) into an endpoint Endpoint parse_addr(std::string addr, asio::ip::address default_address = asio::ip::address_v4::loopback(), uint16_t default_port = 16572) { if (!addr.empty()) { auto pos = addr.find_last_of(':'); if (pos == std::string::npos) default_port = std::stoi(addr); else { asio::error_code ec; auto addr_part = addr.substr(0, pos); default_address = asio::ip::make_address(addr_part, ec); if (ec) throw std::invalid_argument("Invalid IP address " + addr_part); default_port = std::stoi(addr.substr(pos + 1)); } } return {default_address, default_port}; } int main(int argc, char **argv) { if (argc > 1 && (argv[1] == std::string("-h") || argv[1] == std::string("--help"))) { std::cout << "LSL outlet stress tester\n" << "Usage: " << argv[0] << " [address=16572] [inlet_count=1]\n\n" << "address can be either a port or an address and port (127.0.0.1:16572)\n" << "Link-local addresses need a scope id, e.g. fe80::264b:feff:fe2d:f4bc%3:16573\n\n" << "While running, several signals are handled:" << "SIGCONT (Ctrl+Q)\n" << "\tSIGUSR1\t\tPrint current stats\n" << "\tSIGUSR2\t\tAdd another inlet and print current stats\n" << "\tSIGINT (Ctrl+C)\tPrint stats & quit\n"; } Endpoint endpoint = parse_addr(argc > 1 ? argv[1] : ""); const int startnum = argc > 2 ? std::stoi(argv[2]) : 1; std::cout << "Connecting to " << endpoint << std::endl; asio::io_context ctx; std::list sockets; BlackHoleContainer cnt(ctx, endpoint); for (int i = 1; i < startnum; ++i) cnt.add_socket(); ctx.run(); } liblsl-1.17.7/testing/catch_main.cpp000066400000000000000000000007461517625163100173550ustar00rootroot00000000000000#define CATCH_CONFIG_RUNNER #include extern "C" { const char *lsl_library_info() { return LSL_VERSION_INFO; } } int main(int argc, char *argv[]) { Catch::Session session; #ifdef _WIN32 session.configData().waitForKeypress = Catch::WaitForKeypress::BeforeExit; #endif session.configData().runOrder = Catch::TestRunOrder::Randomized; int returnCode = session.applyCommandLine(argc, argv); if (returnCode != 0) return returnCode; return session.run(); } liblsl-1.17.7/testing/common/000077500000000000000000000000001517625163100160445ustar00rootroot00000000000000liblsl-1.17.7/testing/common/bytecmp.cpp000066400000000000000000000034331517625163100202160ustar00rootroot00000000000000#include "bytecmp.hpp" #include #include #include #include #include #include std::string bytes_to_hexstr(const std::string &str) { const std::map shortcuts{{0x07, 'a'}, {0x08, 'b'}, {0x09, 't'}, {0x0a, 'n'}, {0x0b, 'v'}, {0x0c, 'f'}, {0x0d, 'r'}, {0x5c, '\\'}, {0x22, '"'}, {0x27, '\''}, {0x0a, 'n'}}; std::ostringstream out; out << std::setfill('0'); for (auto it = str.cbegin(); it != str.cend(); ++it) { char c = *it; char next = (it + 1) == str.cend() ? '\0' : *(it + 1); auto scit = shortcuts.find(c); if (scit != shortcuts.end()) out << '\\' << scit->second; else if (std::isprint(c)) out << c; else if (std::isxdigit(next)) out << '\\' << std::oct << std::setw(3) << static_cast(c); else if (c >= 0 && c < 8) out << '\\' << std::oct << std::setw(0) << static_cast(c); else out << "\\x" << std::hex << std::setw(0) << (static_cast(c) & 0xff); } return out.str(); } void cmp_binstr(const std::string &a, const std::string b) { CHECK(a.size() == b.size()); const int context_bytes = 8; auto diff = std::mismatch(a.begin(), a.end(), b.begin(), b.end()); if (diff.first == a.end() && diff.second == b.end()) return; auto pos = diff.first - a.begin(); if (pos < context_bytes) REQUIRE(bytes_to_hexstr(a.substr(0, context_bytes)) == bytes_to_hexstr(b.substr(0, context_bytes))); else { INFO("First mismatch offset: " << pos); std::string cmp_a = bytes_to_hexstr(a.substr(0, context_bytes)) + " ... " + bytes_to_hexstr(a.substr(pos - context_bytes, 2 * context_bytes)); std::string cmp_b = bytes_to_hexstr(b.substr(0, context_bytes)) + " ... " + bytes_to_hexstr(b.substr(pos - context_bytes, 2 * context_bytes)); REQUIRE(cmp_a == cmp_b); } } liblsl-1.17.7/testing/common/bytecmp.hpp000066400000000000000000000004751517625163100202260ustar00rootroot00000000000000#pragma once #include /// convert a binary string to the equivalent C-escaped string, e.g. "Hello\xaf\xbcWorld\0" std::string bytes_to_hexstr(const std::string &str); /// compare two binary strings, printing the location around the first mismatch void cmp_binstr(const std::string &a, const std::string b); liblsl-1.17.7/testing/common/create_streampair.hpp000066400000000000000000000011531517625163100222470ustar00rootroot00000000000000#pragma once #include struct Streampair { lsl::stream_outlet out_; lsl::stream_inlet in_; Streampair(lsl::stream_outlet &&out, lsl::stream_inlet &&in) : out_(std::move(out)), in_(std::move(in)) {} }; inline Streampair create_streampair(const lsl::stream_info &info) { lsl::stream_outlet out(info); auto found_stream_info(lsl::resolve_stream("name", info.name(), 1, 2.0)); if (found_stream_info.empty()) throw std::runtime_error("outlet not found"); lsl::stream_inlet in(found_stream_info[0]); in.open_stream(2); out.wait_for_consumers(2); return Streampair(std::move(out), std::move(in)); } liblsl-1.17.7/testing/common/lsltypes.hpp000066400000000000000000000024701517625163100204370ustar00rootroot00000000000000#pragma once extern "C" { #include "../include/lsl/common.h" } #include template struct SampleType { static const lsl_channel_format_t chan_fmt = cft_undefined; static const char *fmt_string(); }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_int8; static const char *fmt_string() { return "cf_int8"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_int16; static const char *fmt_string() { return "cf_int16"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_int32; static const char *fmt_string() { return "cf_int32"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_int64; static const char *fmt_string() { return "cf_int64"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_float32; static const char *fmt_string() { return "cf_float32"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_double64; static const char *fmt_string() { return "cf_double64"; } }; template <> struct SampleType { static const lsl_channel_format_t chan_fmt = cft_string; static const char *fmt_string() { return "cf_string"; } }; liblsl-1.17.7/testing/ext/000077500000000000000000000000001517625163100153545ustar00rootroot00000000000000liblsl-1.17.7/testing/ext/DataType.cpp000066400000000000000000000070211517625163100175730ustar00rootroot00000000000000#include "../common/create_streampair.hpp" #include "../common/lsltypes.hpp" #include #include #include #include #include #include // clazy:excludeall=non-pod-global-static TEMPLATE_TEST_CASE( "datatransfer", "[datatransfer][basic]", char, int16_t, int32_t, int64_t, float, double) { const int numBounces = sizeof(TestType) * 8; double timestamps[numBounces][2]; const char *name = SampleType::fmt_string(); auto cf = static_cast(SampleType::chan_fmt); Streampair sp(create_streampair( lsl::stream_info(name, "Bounce", 2, lsl::IRREGULAR_RATE, cf, "streamid"))); lsl::stream_inlet &inlet = sp.in_; lsl::stream_outlet &outlet = sp.out_; inlet.open_stream(2); outlet.wait_for_consumers(2); TestType sent_data[2] = {0x1, 0x1}; for (int32_t counter = 0; counter < numBounces; counter++) { TestType received_data[2]; timestamps[counter][0] = lsl::local_clock(); sent_data[1] = -sent_data[0] + 1; outlet.push_sample(sent_data, timestamps[counter][0], true); CHECK(inlet.pull_sample(received_data, 2, .5) != 0.0); timestamps[counter][1] = lsl::local_clock(); CHECK(received_data[0] == Catch::Approx(sent_data[0])); CHECK(received_data[1] == Catch::Approx(sent_data[1])); sent_data[0] = static_cast(static_cast(sent_data[0]) << 1); } } TEST_CASE("data datatransfer", "[datatransfer][multi][string]") { const std::size_t numChannels = 3; Streampair sp(create_streampair(lsl::stream_info( "cf_string", "DataType", numChannels, lsl::IRREGULAR_RATE, lsl::cf_string, "streamid"))); std::vector sent_data, received_data(numChannels); const char nullstr[] = "\0Test\0string\0with\0nulls"; sent_data.emplace_back(nullstr, sizeof(nullstr)); sent_data.emplace_back(200, 'x'); sent_data.emplace_back(1 << 20, 'x'); sp.out_.push_sample(sent_data); CHECK(sp.in_.pull_sample(received_data, 5.) != 0.0); CHECK(received_data[0] == sent_data[0]); // Manually check second string for equality to avoid printing the entire test string if (received_data[1] != sent_data[1]) FAIL("Sent large string data doesn't match received data"); } TEST_CASE("TypeConversion", "[datatransfer][types][basic]") { Streampair sp{create_streampair( lsl::stream_info("TypeConversion", "int2str2int", 1, 1, lsl::cf_string, "TypeConversion"))}; const int num_bounces = 31; std::vector data(num_bounces); for (int i = 0; i < num_bounces; ++i) data[i] = 1 << i; sp.out_.push_chunk_multiplexed(data); for (int32_t val : data) { int32_t result; sp.in_.pull_sample(&result, 1, 1.); CHECK(result == val); } } TEST_CASE("Flush", "[datatransfer][basic]") { Streampair sp{create_streampair( lsl::stream_info("FlushTest", "flush", 1, 1, lsl::cf_double64, "FlushTest"))}; sp.in_.set_postprocessing(lsl::post_dejitter); const int n=20; std::thread pusher([&](){ double data = lsl::local_clock(); for(int i=0; i #include // clazy:excludeall=non-pod-global-static TEST_CASE("bounce", "[basic][latency]") { auto sp = create_streampair(lsl::stream_info("bounce", "Test")); float data = .0; BENCHMARK("single bounce") { sp.out_.push_sample(&data); sp.in_.pull_sample(&data, 1.); }; sp.out_.push_sample(&data); BENCHMARK("primed bounce") { sp.out_.push_sample(&data); sp.in_.pull_sample(&data, 1.); }; } liblsl-1.17.7/testing/ext/bench_common.cpp000066400000000000000000000002561517625163100205120ustar00rootroot00000000000000#include #include // clazy:excludeall=non-pod-global-static TEST_CASE("common") { BENCHMARK("lsl_clock") { return lsl_local_clock(); }; } liblsl-1.17.7/testing/ext/bench_pushpull.cpp000066400000000000000000000047701517625163100211030ustar00rootroot00000000000000#include "../common/create_streampair.hpp" #include "../common/lsltypes.hpp" #include #include #include #include #include #include // clazy:excludeall=non-pod-global-static template struct sample_value { static const T val; }; template <> const char sample_value::val = 122; template <> const int64_t sample_value::val = 1LL << 62; template <> const double sample_value::val = 17324412.552; template <> const std::string sample_value::val(200, 'a'); TEMPLATE_TEST_CASE("pushpull", "[basic][throughput]", char, double, std::string) { const std::size_t max_nchan = 128, chunk_size = 128; const std::size_t param_nchan[] = {1, max_nchan}; const std::size_t param_inlets[] = {0, 1, 10}; const TestType data[max_nchan * chunk_size] = {sample_value::val}; const char *name = SampleType::fmt_string(); lsl::channel_format_t cf = (lsl::channel_format_t)SampleType::chan_fmt; for (auto nchan : param_nchan) { lsl::stream_outlet out( lsl::stream_info(name, "PushPull", (int)nchan, chunk_size, cf, "streamid")); auto found_stream_info(lsl::resolve_stream("name", name, 1, 2.0)); REQUIRE(!found_stream_info.empty()); std::list inlet_list; for (auto n_inlets : param_inlets) { while (inlet_list.size() < n_inlets) { inlet_list.emplace_front(found_stream_info[0], 300, false); inlet_list.front().open_stream(.5); } std::string suffix(std::to_string(nchan) + "_inlets_" + std::to_string(n_inlets)); BENCHMARK("push_sample_nchan_" + suffix) { for (size_t s = 0; s < chunk_size; s++) out.push_sample(data); for (auto &inlet : inlet_list) inlet.flush(); }; BENCHMARK("push_chunk_nchan_" + suffix) { out.push_chunk_multiplexed(data, chunk_size); for (auto &inlet : inlet_list) inlet.flush(); }; } } } TEMPLATE_TEST_CASE("stringconversion", "[basic][throughput]", int64_t, double) { const auto nchan = 16u, chunksize = 100u, nitems = chunksize * nchan; Streampair sp{create_streampair(lsl::stream_info("TypeConversionBench", "int2str2int", (int)nchan, chunksize, lsl::cf_string, "TypeConversion"))}; std::vector data(nitems, sample_value::val); BENCHMARK("push") { sp.out_.push_chunk_multiplexed(data.data(), nitems); sp.in_.flush(); }; BENCHMARK("pushpull") { sp.out_.push_chunk_multiplexed(data.data(), nitems); sp.in_.pull_chunk_multiplexed(data.data(), nullptr, nitems, 0, 5.0); }; } liblsl-1.17.7/testing/ext/discovery.cpp000066400000000000000000000043561517625163100200770ustar00rootroot00000000000000#include #include #include // clazy:excludeall=non-pod-global-static namespace { TEST_CASE("resolve multiple streams", "[resolver][basic]") { // First create the outlets (to avoid extra waiting for // multiple resolves) std::vector outlets; const int n = 3; for (int i = 0; i < n; i++) outlets.emplace_back(lsl::stream_info("resolvetest_" + std::to_string(i), "Resolve")); lsl::continuous_resolver resolver("type", "Resolve", 50.); // Verify with one-time resolve auto found_stream_info = lsl::resolve_stream("type", "Resolve", n, 2.0); REQUIRE(found_stream_info.size() == n); // Allow for enough time (interval, min_rtt) std::this_thread::sleep_for(std::chrono::milliseconds(1600)); REQUIRE(resolver.results().size() == n); } TEST_CASE("resolve from streaminfo", "[resolver][streaminfo][basic]") { lsl::stream_outlet outlet(lsl::stream_info("resolvetest", "from_streaminfo")); lsl::stream_inlet(outlet.info()); } TEST_CASE("Invalid queries are caught before sending the query", "[resolver][streaminfo][basic]") { REQUIRE_THROWS(lsl::resolve_stream("invalid'query", 0, 0.1)); } TEST_CASE("fullinfo", "[inlet][fullinfo][basic]") { lsl::stream_info info("fullinfo", "unittest", 1, 1, lsl::cf_int8, "fullinfo1234"); const std::string extinfo("contents\nwith\n\tnewlines"); info.desc().append_child_value("info", extinfo); lsl::stream_outlet outlet(info); auto found_streams = lsl::resolve_stream("name", info.name(), 1, 2); REQUIRE(!found_streams.empty()); INFO(found_streams[0].as_xml()); CHECK(found_streams[0].desc().first_child().empty()); auto fullinfo = lsl::stream_inlet(found_streams[0]).info(2); INFO(fullinfo.as_xml()); CHECK(fullinfo.desc().child_value("info") == extinfo); } TEST_CASE("downed outlet deadlock", "[inlet][streaminfo]") { // This test verifies that calling info on a resolved inlet that has become disconnected // does not get locked waiting on a response. auto outlet = std::make_unique(lsl::stream_info("deadtest", "type")); auto resolved = lsl::resolve_streams(); REQUIRE(!resolved.empty()); lsl::stream_inlet inlet(resolved[0]); outlet.reset(); // this would previously deadlock CHECK_THROWS(inlet.info()); } } // namespace liblsl-1.17.7/testing/ext/move.cpp000066400000000000000000000043741517625163100170360ustar00rootroot00000000000000#include #include #include // clazy:excludeall=non-pod-global-static namespace { TEST_CASE("move C++ API types", "[move][basic]") { lsl::stream_info info("movetest", "test", 1, lsl::IRREGULAR_RATE, lsl::cf_int32); lsl::stream_outlet outlet(info); lsl::continuous_resolver resolver("name", "movetest"); auto found_stream_info = lsl::resolve_stream("name", "movetest", 1, 2.0); REQUIRE(found_stream_info.size() == 1); lsl::stream_inlet inlet(found_stream_info[0]); inlet.open_stream(2); outlet.wait_for_consumers(2); int32_t data = 1; { // move the outlet via the move constructor lsl::stream_outlet outlet2(std::move(outlet)); CHECK(outlet2.have_consumers()); outlet2.push_sample(&data); // Move outlet2 back into outlet via the copy constructor outlet = std::move(outlet2); data++; outlet.push_sample(&data); // End of scope, destructor for outlet2 is called // Since the stream_outlet is alive in outlet, it's not deconstructed } { // move the inlet via the move constructor lsl::stream_inlet inlet2(std::move(inlet)); REQUIRE(inlet2.get_channel_count() == 1); inlet2.pull_sample(&data, 1); CHECK(data == 1); // Move inlet2 back into inlet via the copy constructor inlet = std::move(inlet2); inlet.pull_sample(&data, 1); CHECK(data == 2); // End of scope, destructor for inlet2 is called // Since the stream_inlet is alive in inlet, it's not deconstructed } { lsl::continuous_resolver resolver2(std::move(resolver)); resolver2.results(); resolver = std::move(resolver2); resolver.results(); } { lsl::stream_outlet outlet3(std::move(outlet)); lsl::stream_inlet inlet3(std::move(inlet)); // End of scope, destructors for inlet3 and outlet3 are called } // Since the outlet has been destructed in the previous block, it shouldn't // be there anymore found_stream_info = lsl::resolve_stream("name", "movetest", 1, 2.0); REQUIRE(found_stream_info.empty()); // stream_info copies are cheap, for independent copies clone() has to be used lsl::stream_info copy1(info), copy2(info), clone(info.clone()); copy1.desc().append_child("Dummy"); REQUIRE(copy2.desc().first_child().name() == std::string("Dummy")); REQUIRE(clone.desc().first_child().empty()); } } // namespace liblsl-1.17.7/testing/ext/streaminfo.cpp000066400000000000000000000017161517625163100202340ustar00rootroot00000000000000#include #include #include // clazy:excludeall=non-pod-global-static namespace { TEST_CASE("streaminfo", "[streaminfo][basic]") { CHECK_THROWS(lsl::stream_info("", "emptyname")); REQUIRE(std::string("The name of a stream must be non-empty.") == lsl_last_error()); } TEST_CASE("multithreaded lsl_last_error", "[threading][basic]") { CHECK_THROWS(lsl::stream_info("", "emptyname")); std::thread([](){ CHECK_THROWS(lsl::stream_info("hasname","type", -1)); REQUIRE(std::string("The channel_count of a stream must be nonnegative.") == lsl_last_error()); }).join(); REQUIRE(std::string("The name of a stream must be non-empty.") == lsl_last_error()); } /// Ensure that an overly long error message won't overflow the buffer TEST_CASE("lsl_last_error size", "[basic]") { std::string invalidquery(511, '\''); CHECK_THROWS(lsl::resolve_stream(invalidquery, 1, 0.1)); REQUIRE(lsl_last_error()[511] == 0); } } // namespace liblsl-1.17.7/testing/ext/time.cpp000066400000000000000000000023221517625163100170150ustar00rootroot00000000000000#include "../common/create_streampair.hpp" #include #include #include #include // clazy:excludeall=non-pod-global-static namespace { TEST_CASE("simple timesync", "[timesync][basic]") { auto sp = create_streampair(lsl::stream_info("timesync", "Test")); double offset = sp.in_.time_correction(5.) * 1000; CHECK(offset < 1); CAPTURE(offset); double remote_time, uncertainty; offset = sp.in_.time_correction(&remote_time, &uncertainty, 5.) * 1000; CHECK(offset < 1); CHECK(uncertainty * 1000 < 1); CHECK(remote_time < lsl::local_clock()); } TEST_CASE("timeouts", "[pull][basic]") { auto sp = create_streampair( lsl::stream_info("timeouts", "Test", 1, lsl::IRREGULAR_RATE, lsl::cf_int8, "timeouts")); std::atomic done{false}; // Push a sample after some time so the test can continue even if the timeout isn't honored std::thread saver([&]() { char val; auto end = lsl::local_clock() + 2; while (!done && lsl::local_clock() < end) std::this_thread::sleep_for(std::chrono::milliseconds(50)); sp.out_.push_sample(&val); }); char val; REQUIRE(sp.in_.pull_sample(&val, 1, 0.5) == Catch::Approx(0.0)); done = true; saver.join(); } } // namespace liblsl-1.17.7/testing/int/000077500000000000000000000000001517625163100153465ustar00rootroot00000000000000liblsl-1.17.7/testing/int/bench_sleep.cpp000066400000000000000000000007001517625163100203160ustar00rootroot00000000000000#include #include #include #include // clazy:excludeall=non-pod-global-static TEST_CASE("sleep") { BENCHMARK("sleep1ms") { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }; } TEST_CASE("read system clock"){ BENCHMARK("lsl_local_clock") { return lsl_local_clock(); }; BENCHMARK("lsl_local_clock_ns") { return lsl::lsl_local_clock_ns(); }; } liblsl-1.17.7/testing/int/bench_timesync.cpp000066400000000000000000000020641517625163100210460ustar00rootroot00000000000000#include "stream_info_impl.h" #include "udp_server.h" #include #include #include #include // clazy:excludeall=non-pod-global-static using namespace asio::ip; TEST_CASE("timesync", "[basic][latency]") { auto info = std::make_shared("Dummy", "dummy", 1, 1., cft_int8, "abcdef123"); asio::io_context ctx; auto udp_server = std::make_shared(info, ctx, udp::v4()); udp::endpoint ep(address_v4(0x7f000001), info->v4service_port()); INFO(info->to_shortinfo_message()); udp_server->begin_serving(); std::thread iothread([&ctx]() { ctx.run(); }); const char request[] = "LSL:timedata\n1 0.0"; asio::basic_datagram_socket sock(ctx, udp::endpoint()); char buf[500]={0}; BENCHMARK("timedata") { sock.send_to(asio::const_buffer(request, sizeof(request) - 1), ep); REQUIRE(sock.receive(asio::mutable_buffer(buf, sizeof(buf) - 1)) > 0); }; udp_server->end_serving(); ctx.stop(); iothread.join(); } liblsl-1.17.7/testing/int/inireader.cpp000066400000000000000000000021161517625163100200140ustar00rootroot00000000000000#include "util/inireader.hpp" #include #include // clazy:excludeall=non-pod-global-static void try_load(INI &pt, const char *contents) { std::istringstream stream{std::string(contents)}; pt.load(stream); } TEST_CASE("ini files are parsed correctly", "[ini][basic]") { INI pt; try_load(pt, "x=5\n" "y=2\n" "[foo]\n" "foo=bar\n" "; foo=commented out\n" "double=equals=sign\n" "[white space]\n" "\tfoo =\t bar\r\n"); CHECK(pt.get("doesntexist", 0) == 0); CHECK(pt.get("defaultval") == 0); CHECK(pt.get("x") == 5); CHECK(pt.get("foo.foo", "") == std::string("bar")); CHECK(pt.get("white space.foo") == std::string("bar")); CHECK(pt.get("emptydefault") == std::string("")); } TEST_CASE("bad ini files are rejected", "[ini][basic]") { INI pt; CHECK_THROWS(try_load(pt, "[badsection")); CHECK_THROWS(try_load(pt, "duplicate=1\nduplicate=2")); CHECK_THROWS(try_load(pt, "missingval")); CHECK_THROWS(try_load(pt, "missingval= ")); CHECK_THROWS(try_load(pt, " = missingkey")); } liblsl-1.17.7/testing/int/loguruthreadnames.cpp000066400000000000000000000007541517625163100216110ustar00rootroot00000000000000#include #include // Include loguru before catch #include // clazy:excludeall=non-pod-global-static TEST_CASE("loguru_thread_local_storage", "[threading]") { char name[16] = "0"; loguru::set_thread_name("1"); std::thread([&]() { loguru::set_thread_name("2"); loguru::get_thread_name(name, sizeof(name), false); }).join(); REQUIRE(name[0] == '2'); loguru::get_thread_name(name, sizeof(name), false); REQUIRE(name[0] == '1'); } liblsl-1.17.7/testing/int/network.cpp000066400000000000000000000325421517625163100175510ustar00rootroot00000000000000#include "../src/cancellable_streambuf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // clazy:excludeall=non-pod-global-static using namespace asio; using namespace std::chrono_literals; using err_t = const asio::error_code &; static uint16_t port = 28812; static const char hello[] = "Hello World"; static const std::string hellostr(hello); static std::mutex output_mutex; asio::const_buffer hellobuf() { return asio::const_buffer(hello, sizeof(hello)); } /// launches a task and waits for the underlying thread to have started template std::future launch_task(Fun &&fun) { std::promise started; auto started_fut = started.get_future(); std::future done_fut = std::async(std::launch::async, [&started, fn = std::forward(fun)]() { started.set_value(); fn(); }); started_fut.wait(); return done_fut; } #define MINFO(str) \ { \ std::unique_lock out_lock(output_mutex); \ UNSCOPED_INFO(str); \ } // Check if a background operation (`task`) on a streambuf `sb` can be cancelled safely template void cancel_streambuf(T &&task, lsl::cancellable_streambuf &sb) { std::condition_variable cv; std::mutex mut; bool status{false}; std::future future = std::async(std::launch::async, [&]() { std::unique_lock lock(mut); MINFO("Thread 1: started") status = true; lock.unlock(); cv.notify_all(); MINFO("Thread 1: starting socket operation") task(); MINFO("Thread 1: socket operation finished") }); // We need to wait until sb_blockconnect.connect() was called, but the // thread is blocked connecting so we can't let it signal it's ready // So we wait 200ms immediately after connect() is supposed to be called { std::unique_lock lock(mut); cv.wait(lock, [&] { return status; }); } if (future.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready) FAIL("Thread 1 finished too soon, couldn't test cancellation"); MINFO("Thread 0: Closing socket…") sb.cancel(); // Double cancel, shouldn't do anything dramatic sb.cancel(); // Allow the thread 2 seconds to finish if (future.wait_for(std::chrono::seconds(2)) != std::future_status::ready) { FAIL("Cancellation timed out"); sb.close(); } } TEST_CASE("streambuf cancel connect()", "[streambuf][basic][network]") { asio::io_context io_ctx; lsl::cancellable_streambuf sb_connect; INFO("Thread 0: Binding remote socket and keeping it busy…"); ip::tcp::endpoint ep(ip::address_v4::loopback(), port++); ip::tcp::acceptor remote(io_ctx, ip::tcp::v4()); remote.bind(ep); // Create a socket that keeps connect()ing sockets hanging // On Windows, this requires an additional socket option, on Unix // a backlog size of 0 and a socket waiting for the connection to be accept()ed // On macOS, backlog 0 uses SOMAXCONN instead and 1 is correct #ifdef _WIN32 remote.set_option(asio::detail::socket_option::integer(1)); remote.listen(0); #else #ifdef __APPLE__ const int backlog = 1; #else const int backlog = 0; #endif remote.listen(backlog); lsl::cancellable_streambuf busykeeper; CHECK(busykeeper.connect(ep) != nullptr); #endif INFO("Thread 0: Remote socket should be busy"); cancel_streambuf( [&sb_connect, ep]() { sb_connect.connect(ep); MINFO(sb_connect.error().message()); REQUIRE(sb_connect.sbumpc() == std::char_traits::eof()); }, sb_connect); } TEST_CASE("unconnected streambufs don't crash", "[streambuf][basic][network]") { asio::io_context io_ctx; lsl::cancellable_streambuf sb_failedconnect; ip::tcp::endpoint ep(ip::address_v4::loopback(), 1); sb_failedconnect.connect(ep); sb_failedconnect.cancel(); lsl::cancellable_streambuf().cancel(); } TEST_CASE("cancel streambuf reads", "[streambuf][network][!mayfail]") { asio::io_context io_ctx; lsl::cancellable_streambuf sb_read; ip::tcp::endpoint ep(ip::address_v4::loopback(), port++); ip::tcp::acceptor remote(io_ctx, ep, true); remote.listen(1); INFO("Thread 0: Connecting…"); sb_read.connect(ep); INFO("Thread 0: Connected (" << sb_read.error().message() << ')'); ip::tcp::socket sock(remote.accept()); sock.send(asio::buffer(hello, 1)); REQUIRE(sb_read.sbumpc() == hello[0]); cancel_streambuf( [&sb_read]() { auto data = sb_read.sbumpc(); MINFO(sb_read.error().message()) CHECK(data == std::char_traits::eof()); }, sb_read); } TEST_CASE("streambuf split reads", "[streambuf][network]") { asio::io_context io_ctx; lsl::cancellable_streambuf sb_read; ip::tcp::endpoint ep(ip::address_v4::loopback(), port++); ip::tcp::acceptor remote(io_ctx, ep, true); remote.listen(1); REQUIRE(sb_read.connect(ep) != nullptr); ip::tcp::socket sock(remote.accept()); REQUIRE(sock.send(asio::buffer(hello, 3)) == 3); REQUIRE(sb_read.sbumpc() == hello[0]); auto done = launch_task([&]() { char buf[sizeof(hello)] = {0}; auto bytes_read = sb_read.sgetn(buf, sizeof(hello) - 2); REQUIRE(bytes_read != std::streambuf::traits_type::eof()); CHECK(bytes_read == sizeof(hello) - 2); REQUIRE(std::string(buf) == hellostr.substr(1)); }); sock.send(asio::buffer(hello + 3, 8)); done.wait(); std::vector in_(65536 * 16), out_(65536 * 16); for (std::size_t i = 0; i < out_.size(); ++i) out_[i] = (i >> 8 ^ i) % 127; done = launch_task([&sb_read, &in_](){ auto *dataptr = in_.data(), *endptr = dataptr + in_.size(); while(dataptr != endptr) { std::streamsize bytes_read = sb_read.sgetn(dataptr, std::min(endptr - dataptr, 54)); if(bytes_read == std::streambuf::traits_type::eof()) break; dataptr += bytes_read; } }); for(const char*outptr = out_.data(), *endptr = outptr + out_.size(); outptr != endptr; outptr+=64) sock.send(asio::buffer(outptr, 64)); done.wait(); REQUIRE(std::equal(in_.begin(), in_.end(), out_.begin())); } TEST_CASE("receive v4 packets on v6 socket", "[ipv6][network]") { const uint16_t test_port = port++; asio::io_context io_ctx; ip::udp::socket sock(io_ctx, ip::udp::v6()); sock.set_option(ip::v6_only(false)); sock.bind(ip::udp::endpoint(ip::address_v6::any(), test_port)); ip::udp::socket sender_v4(io_ctx, ip::udp::v4()), sender_v6(io_ctx, ip::udp::v6()); asio::const_buffer sbuf(hellobuf()); char recvbuf[64] = {0}; sender_v4.send_to(sbuf, ip::udp::endpoint(ip::address_v4::loopback(), test_port)); auto recv_len = sock.receive(asio::buffer(recvbuf, sizeof(recvbuf) - 1)); CHECK(recv_len == sizeof(hello)); CHECK(hellostr == recvbuf); std::fill_n(recvbuf, recv_len, 0); sender_v6.send_to(sbuf, ip::udp::endpoint(ip::address_v6::loopback(), test_port)); recv_len = sock.receive(asio::buffer(recvbuf, sizeof(recvbuf) - 1)); CHECK(hellostr == recvbuf); std::fill_n(recvbuf, recv_len, 0); } TEST_CASE("ipaddresses", "[ipv6][network][basic]") { ip::address_v4 v4addr(ip::make_address_v4("192.168.172.1")), mcastv4(ip::make_address_v4("239.0.0.183")); ip::address_v6 v6addr = ip::make_address_v6(ip::v4_mapped_t(), v4addr); ip::address addr(v4addr), addr_mapped(v6addr); CHECK(!v4addr.is_multicast()); CHECK(mcastv4.is_multicast()); // mapped IPv4 multicast addresses aren't considered IPv6 multicast addresses CHECK(!ip::make_address_v6(ip::v4_mapped, mcastv4).is_multicast()); CHECK(v6addr.is_v4_mapped()); CHECK(addr != addr_mapped); CHECK(addr == ip::address(ip::make_address_v4(ip::v4_mapped, v6addr))); auto scoped = ip::make_address_v6("::1%3"); CHECK(scoped.scope_id() == 3); } /// Can multiple sockets bind to the same port and receive all broad-/multicast packets? TEST_CASE("reuseport", "[network][basic][!mayfail]") { // Linux: sudo ip link set lo multicast on; sudo ip mroute show table all #if __APPLE__ auto addrstr = GENERATE((const char *)"224.0.0.1", "255.255.255.255"); #else auto addrstr = GENERATE((const char *)"224.0.0.1", "255.255.255.255", "ff03::1"); #endif SECTION(addrstr) { const uint16_t test_port = port++; INFO("Test port " + std::to_string(test_port)); asio::io_context io_ctx(1); auto addr = ip::make_address(addrstr); if (!addr.is_multicast()) REQUIRE((addr.is_v4() && addr.to_v4() == ip::address_v4::broadcast())); auto proto = addr.is_v4() ? ip::udp::v4() : ip::udp::v6(); std::vector socks; for (int i = 0; i < 2; ++i) { socks.emplace_back(io_ctx, proto); auto &sock = socks.back(); sock.set_option(ip::udp::socket::reuse_address(true)); sock.bind(ip::udp::endpoint(proto, test_port)); if (addr.is_multicast()) { asio::error_code ec; sock.set_option(ip::multicast::join_group(addr), ec); if (ec == error::no_such_device || ec == std::errc::address_not_available) FAIL("Couldn't join multicast group: " + ec.message()); } } { ip::udp::socket outsock(io_ctx, proto); if (addr.is_multicast()) outsock.set_option(ip::multicast::join_group(addr)); else outsock.set_option(ip::udp::socket::broadcast(true)); auto sent = outsock.send_to(hellobuf(), ip::udp::endpoint(addr, test_port)); REQUIRE(sent == sizeof(hello)); // outsock.close(); } std::size_t received = 0; // set a timeout asio::steady_timer timeout(io_ctx, std::chrono::seconds(2)); timeout.async_wait([&received](err_t err) { if (err == asio::error::operation_aborted) return; UNSCOPED_INFO(received); throw std::runtime_error("Test didn't finish in time"); }); char inbuf[sizeof(hello)] = {0}; for (auto &insock : socks) insock.async_receive(asio::buffer(inbuf), [&](err_t err, std::size_t len) { CHECK(err.value() == 0); CHECK(len == sizeof(hello)); CHECK(hellostr == inbuf); if (++received == socks.size()) timeout.cancel(); }); io_ctx.run(); } } TEST_CASE("bindzero", "[network][basic]") { asio::io_context ctx; asio::ip::udp::socket sock(ctx, asio::ip::udp::v4()); sock.bind(asio::ip::udp::endpoint(asio::ip::address_v4::any(), 0)); REQUIRE(sock.local_endpoint().port() != 0); } #ifdef CATCH_CONFIG_ENABLE_BENCHMARKING TEST_CASE("streambuf throughput", "[streambuf][network]") { asio::io_context io_ctx; asio::executor_work_guard work(io_ctx.get_executor()); auto background_io = launch_task([&]() { io_ctx.run(); }); lsl::cancellable_streambuf sb_bench; ip::tcp::endpoint ep(ip::address_v4::loopback(), port++); ip::tcp::acceptor remote(io_ctx, ep, true); remote.listen(); ip::tcp::socket sock(io_ctx); auto accept_fut = remote.async_accept(sock, asio::use_future); REQUIRE(sb_bench.connect(ep) != nullptr); REQUIRE(accept_fut.wait_for(2s) == std::future_status::ready); char buf_small[16] = "!Hello World!", buf_medium[256]{'\xab'}, buf_large[4096]{'\xab'}; asio::mutable_buffer bufs[] = { asio::buffer(buf_small), asio::buffer(buf_medium), asio::buffer(buf_large)}; std::vector dummy_buffer; for (const auto &buf : bufs) { for (std::size_t chunksize : {1U, 16U, 256U}) { BENCHMARK_ADVANCED("Send;nchunk=" + std::to_string(chunksize) + ";buf=" + std::to_string(buf.size()) + ";n=" + std::to_string(chunksize * buf.size())) (Catch::Benchmark::Chronometer meter) { const auto total_bytes = buf.size() * chunksize * meter.runs(); if (dummy_buffer.size() < total_bytes) dummy_buffer.resize(total_bytes); auto fut = asio::async_read( sock, asio::buffer(dummy_buffer.data(), total_bytes), asio::use_future); asio::steady_timer t(io_ctx, 5s); t.async_wait([&](err_t ec) { REQUIRE(ec == asio::error::operation_aborted); }); meter.measure([&]() { for (auto chunk = 0U; chunk < chunksize; ++chunk) { auto res = sb_bench.sputn(reinterpret_cast(buf.data()), buf.size()); REQUIRE(res != std::streambuf::traits_type::eof()); } sb_bench.pubsync(); }); // Wait for the read operations to finish fut.wait(); t.cancel(); }; } } for (const auto &buf : bufs) { for (int chunksize : {1, 16, 256}) { BENCHMARK_ADVANCED("Recv;nchunk=" + std::to_string(chunksize) + ";buf=" + std::to_string(buf.size()) + ";n=" + std::to_string(chunksize * buf.size())) (Catch::Benchmark::Chronometer meter) { const auto total_bytes = buf.size() * chunksize * meter.runs(); if (dummy_buffer.size() < total_bytes) dummy_buffer.resize(total_bytes); asio::async_write(sock, asio::buffer(dummy_buffer.data(), total_bytes), [](err_t err, std::size_t /* unused */) { REQUIRE(!err); }); std::this_thread::sleep_for(10ms); asio::steady_timer t(io_ctx, 5s); t.async_wait([&](err_t ec) { REQUIRE(ec == asio::error::operation_aborted); }); meter.measure([&]() { for (int chunk = 0; chunk < chunksize; ++chunk) { auto res = sb_bench.sgetn(reinterpret_cast(buf.data()), buf.size()); REQUIRE(res != std::streambuf::traits_type::eof()); } }); t.cancel(); }; } } asio::post(io_ctx, [&]() { io_ctx.stop(); }); background_io.wait(); } #endif liblsl-1.17.7/testing/int/postproc.cpp000066400000000000000000000046131517625163100177270ustar00rootroot00000000000000#include "time_postprocessor.h" #include #include #include // include loguru before catch #include #include // clazy:excludeall=non-pod-global-static template inline void test_postproc_array( lsl::time_postprocessor &pp, const double (&in)[N], const double (&expected)[N]) { for (std::size_t i = 0; i < N; ++i) CHECK(pp.process_timestamp(in[i]) == Catch::Approx(expected[i])); } TEST_CASE("postprocessing", "[basic]") { double time_offset = -50.0, srate = 1.; bool was_reset = false; lsl::time_postprocessor pp( [&]() { UNSCOPED_INFO("time_offset " << time_offset); return time_offset; }, [&]() { return srate; }, [&]() { return was_reset; }); double nopostproc[] = {2, 3.1, 3, 5, 5.9, 7.1}; { INFO("proc_clocksync"); pp.set_options(proc_clocksync); for (double t : nopostproc) CHECK(pp.process_timestamp(t) == Catch::Approx(t + time_offset)); } { INFO("proc_clocksync + clock_reset"); pp.set_options(proc_clocksync); // std::this_thread::sleep_for(std::chrono::milliseconds(600)); was_reset = true; time_offset = -200; // for (double t : nopostproc) CHECK(pp.process_timestamp(t) == Approx(t + time_offset)); } { INFO("proc_monotonize"); double monotonized[] = {2, 3.1, 3.1, 5, 5.9, 7.1}; pp.set_options(proc_monotonize); test_postproc_array(pp, nopostproc, monotonized); } { INFO("reset with proc_none"); pp.set_options(proc_none); test_postproc_array(pp, nopostproc, nopostproc); } } TEST_CASE("rls_smoothing", "[basic]") { const int n = 100000, warmup_samples = 1000; const double t0 = 5000, latency = 0.05, srate = 100., halftime = 90; lsl::postproc_dejitterer pp(t0, srate, halftime); std::default_random_engine rng; std::normal_distribution jitter(latency, .005); pp.dejitter(t0); int n_outlier = 0; for (int i = 0; i < n; ++i) { const double t = t0 + i / srate, e = jitter(rng), dejittered = pp.dejitter(t + e), est_error = dejittered - t - latency; if (i > warmup_samples && fabs(est_error) > std::max(e, .001)) n_outlier++; } LOG_F(INFO, "\nP:\t%f\t%f\n\t%f\t%f\nw:\t%f\t%f", pp.P00_, pp.P01_, pp.P01_, pp.P11_, pp.w0_, pp.w1_); CHECK(n_outlier < n / 100); CHECK(pp.dejitter(t0 + latency + n / srate) == Catch::Approx(t0 + latency + n / srate)); CHECK(fabs(pp.w0_ - latency) < .1); CHECK(fabs(pp.w1_ - 1 / srate) < 1e-6); } liblsl-1.17.7/testing/int/runtime_config.cpp000066400000000000000000000015251517625163100210650ustar00rootroot00000000000000#include "api_config.h" #include // Verifies that configuration provided via the runtime-config API is picked up // on singleton initialization. Because api_config::get_instance() is guarded by // std::call_once, only the first configuration wins per-process. This test // therefore lives in its own executable so the singleton starts fresh. TEST_CASE("runtime config content overrides defaults", "[api_config][runtime_config]") { lsl::api_config::set_api_config_content( "[lab]\n" "SessionID = runtime_config_test\n" "[ports]\n" "BasePort = 30000\n" "[tuning]\n" "UseProtocolVersion = 100\n"); const auto *cfg = lsl::api_config::get_instance(); REQUIRE(cfg != nullptr); CHECK(cfg->session_id() == "runtime_config_test"); CHECK(cfg->base_port() == 30000); CHECK(cfg->use_protocol_version() == 100); } liblsl-1.17.7/testing/int/samples.cpp000066400000000000000000000040101517625163100175110ustar00rootroot00000000000000#include "../src/consumer_queue.h" #include "../src/sample.h" #include #include #include // clazy:excludeall=non-pod-global-static TEST_CASE("consumer_queue", "[queue][basic]") { const int size = 10; lsl::factory fac(lsl_channel_format_t::cft_int8, 4, size / 2); lsl::consumer_queue queue(size); for (int i = 0; i <= size; ++i) queue.push_sample(fac.new_sample(i, true)); // Does the queue respect the capacity? CHECK(queue.read_available() == size); // Are the right samples dropped when going over capacity? CHECK(static_cast(queue.pop_sample()->timestamp()) == 1); // Does flush() return the correct count? CHECK(queue.flush() == size - 1); // Is the queue empty after flush()ing? CHECK(queue.empty()); } TEST_CASE("consumer_queue_threaded", "[queue][threads]") { const unsigned int size = 100000; lsl::factory fac(lsl_channel_format_t::cft_int8, 4, 1); auto sample = fac.new_sample(0.0, true); lsl::consumer_queue queue(size); std::atomic done{false}; std::thread pusher([&]() { for (unsigned int i = 0; i < size; ++i) queue.push_sample(sample); done = true; }); unsigned flushes = 0, pulled = 0; // Pull samples until the pusher is done and the queue is empty while (true) { unsigned int n = queue.flush(); if (n) { flushes++; pulled += n; } else { if(done && queue.read_available() == 0) break; std::this_thread::yield(); } } INFO(flushes); CHECK(pulled == size); pusher.join(); } TEST_CASE("sample conversion", "[basic]") { lsl::factory fac(lsl_channel_format_t::cft_int64, 2, 1); double values[2] = {1, -1}; int64_t buf[2]; std::string strbuf[2]; for (int i = 0; i < 30; ++i) { auto sample = fac.new_sample(0.0, true); sample->assign_typed(values); sample->retrieve_untyped(buf); sample->retrieve_typed(strbuf); for (int j = 0; j < 1; ++j) { CHECK(values[j] == static_cast(buf[j])); CHECK(strbuf[j] == std::to_string(buf[j])); } values[0] = (double)(buf[0] << 1); values[1] = (double)(-buf[0]); } } liblsl-1.17.7/testing/int/sendbuffer.cpp000066400000000000000000000134711517625163100202030ustar00rootroot00000000000000/** * Tests for consumer_queue refcount corruption bug. * * These tests demonstrate the bug in consumer_queue::move_or_drop where * explicit destructor call causes double-release of samples, corrupting * the reference count when samples are shared across multiple queues. * * The bug mechanism: * 1. Sample pushed to multiple queues via send_buffer (refcount = N) * 2. One queue overflows, calls move_or_drop(slot.value) * 3. Explicit destructor decrements refcount (N -> N-1) * 4. slot.value.px still contains old pointer (garbage, not zeroed) * 5. try_push assigns new sample, assignment's temp destructor * calls intrusive_ptr_release on garbage pointer * 6. This decrements refcount AGAIN (N-1 -> N-2) * * Each overflow event decrements refcount twice instead of once! * This causes refcount corruption that eventually leads to crashes. * * Key insight: The bug requires SHARED samples via send_buffer. * Single-queue tests don't trigger visible crashes because samples * have refcount=1, and double-release just corrupts a single sample. * * Related: PR #246, commit 11b6207e (2016 fix for cf_string double delete) */ #include "consumer_queue.h" #include "sample.h" #include "send_buffer.h" #include #include #include #include // clazy:excludeall=non-pod-global-static /** * Test: Rapid queue create-destroy with send_buffer * * It simulates the scenario where inlets connect and disconnect rapidly * while samples are being pushed through send_buffer. The key elements: * * 1. Uses send_buffer to share samples across multiple queues * 2. Small buffer size forces frequent overflow (triggers move_or_drop) * 3. Rapid create/destroy cycles accumulate refcount corruption * 4. Queue destruction with corrupted refcounts causes crash */ TEST_CASE("rapid queue lifecycle with send_buffer", "[queue][regression][send_buffer]") { const int buffer_size = 4; const int num_iterations = 50; lsl::factory fac(lsl_channel_format_t::cft_int32, 2, buffer_size); auto sendbuf = std::make_shared(buffer_size); for (int iter = 0; iter < num_iterations; ++iter) { // Create queues that will share samples via send_buffer std::vector> queues; for (int i = 0; i < 5; ++i) { queues.push_back(sendbuf->new_consumer(buffer_size)); } // Push samples through send_buffer - each sample goes to ALL 5 queues // This means each sample has refcount = 5 // Buffer overflow causes move_or_drop which double-releases for (int i = 0; i < buffer_size * 3; ++i) { auto sample = fac.new_sample(static_cast(i), true); sendbuf->push_sample(sample); } // Push null sample (the PR #246 scenario) sendbuf->push_sample(lsl::sample_p()); // Queues are destroyed here when vector goes out of scope // Corrupted refcounts cause crash during cleanup } REQUIRE(true); } /** * Test: String sample overflow with send_buffer * * String samples were specifically mentioned in commit 11b6207e (2016): * "Fixed double pointer delete error when destroying cf_string stream" * * String samples have embedded std::string objects that require proper * lifetime management. This test verifies the bug affects string samples * when shared via send_buffer. */ TEST_CASE("string sample send_buffer overflow", "[queue][regression][send_buffer][string]") { const int buffer_size = 4; const int num_queues = 5; const int num_samples = 50; const int num_channels = 2; lsl::factory fac(lsl_channel_format_t::cft_string, num_channels, buffer_size); auto sendbuf = std::make_shared(buffer_size); std::vector> queues; for (int i = 0; i < num_queues; ++i) { queues.push_back(sendbuf->new_consumer(buffer_size)); } // Push string samples - shared across all queues for (int i = 0; i < num_samples; ++i) { auto sample = fac.new_sample(static_cast(i), true); std::string data[num_channels]; data[0] = "test_" + std::to_string(i); data[1] = "string_data_" + std::to_string(i * 100); sample->assign_typed(data); sendbuf->push_sample(sample); } // Push null (PR #246 scenario) sendbuf->push_sample(lsl::sample_p()); // Flush and verify no crash for (auto &q : queues) { q->flush(); } REQUIRE(true); } /** * Test: Multi-threaded send_buffer stress test * * Multiple threads push to send_buffer concurrently to maximize * race condition probability. This tests the concurrent scenario * that occurs in real-world usage with multiple outlet threads. */ TEST_CASE("multi-threaded send_buffer stress", "[queue][regression][send_buffer][concurrent]") { const int buffer_size = 4; const int num_queues = 5; const int num_producer_threads = 4; const int iterations_per_thread = 200; lsl::factory fac(lsl_channel_format_t::cft_float32, 4, buffer_size * 2); auto sendbuf = std::make_shared(buffer_size); std::vector> queues; for (int i = 0; i < num_queues; ++i) { queues.push_back(sendbuf->new_consumer(buffer_size)); } std::atomic push_count{0}; // Producer threads push samples concurrently auto producer = [&]() { for (int i = 0; i < iterations_per_thread; ++i) { auto sample = fac.new_sample(static_cast(i), true); sendbuf->push_sample(sample); push_count.fetch_add(1, std::memory_order_relaxed); } }; // Start producer threads std::vector threads; for (int i = 0; i < num_producer_threads; ++i) { threads.emplace_back(producer); } // Wait for all producers to finish for (auto &t : threads) { t.join(); } // Push null to signal end sendbuf->push_sample(lsl::sample_p()); // Flush all queues for (auto &q : queues) { q->flush(); } INFO("Pushed " << push_count.load() << " samples"); REQUIRE(push_count.load() == num_producer_threads * iterations_per_thread); } liblsl-1.17.7/testing/int/serialization_v100.cpp000066400000000000000000000133611517625163100215010ustar00rootroot00000000000000#include "sample.h" #include #include #include #include #include // clazy:excludeall=non-pod-global-static #define NO_EXPLICIT_TEMPLATE_INSTANTIATION #include "portable_archive/portable_iarchive.hpp" #include "portable_archive/portable_oarchive.hpp" static lsl::factory doublefac{cft_double64, 4, 1}, strfac{cft_string, 4, 1}; const float test_floats[] = {0.0, -0.0, std::numeric_limits::denorm_min(), std::numeric_limits::max(), std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; const double test_doubles[] = {0.0, -0.0, std::numeric_limits::denorm_min(), std::numeric_limits::max(), std::numeric_limits::infinity(), -std::numeric_limits::infinity()}; struct Testclass { std::string teststr; double testdouble{0}; uint64_t testbigint{0}; int32_t negativeint{0}, testint{0}; lsl::sample_p s1, s2; char testchar{0}; template void serialize_(Archive &a) { a &testchar &testint &negativeint &testbigint &testdouble &teststr &*s1 &*s2; } void serialize(eos::portable_iarchive &a, uint32_t) { serialize_(a); } void serialize(eos::portable_oarchive &a, uint32_t) const { const_cast(this)->serialize_(a); } Testclass() : s1(doublefac.new_sample(0.0, false)), s2(strfac.new_sample(0.0, false)) {} Testclass(bool /*dummy*/) : teststr("String\x00with\x00nulls"), testdouble(17.3), testbigint(0xff), negativeint(-1), testint(0x00abcdef), s1(doublefac.new_sample(17.3, true)), s2(strfac.new_sample(18.3, true)), testchar('a') { s1->assign_test_pattern(2); s2->assign_test_pattern(4); } }; struct Testclass2 { uint64_t i; void serialize(eos::portable_iarchive &a, uint32_t) { a &i; } void serialize(eos::portable_oarchive &a, uint32_t) const { a &i; } }; TEST_CASE("v100 protocol serialization", "[basic][serialization]") { Testclass out1(true); Testclass2 out2, out3; out2.i = out3.i = 0x00ab00cd00ef0012u; std::stringbuf osb(std::ios::out); { eos::portable_oarchive outarch(osb); outarch << out2 << out3; outarch << std::string("Testclass") << out1 << 0x00abcd; outarch << std::string("Floats"); outarch << std::numeric_limits::quiet_NaN(); for (float f : test_floats) outarch << f; outarch << std::string(" Doubles"); outarch << std::numeric_limits::quiet_NaN(); for (double d : test_doubles) outarch << d; outarch << std::string("Ints"); for (int64_t i = 0; i < 64; i += 8) outarch << (1LL << i) << ((1LL << 62) - (1LL << i)); } // Preserialized, generate with // python -c "import re;print(re.sub('(?<=x[0-9a-f]{2})[0-9a-f]', r'\"\\n\"\\g<0>', // str(open('serialization_found.bin','rb').read())))" static const char binpacket[] = "\x7f\x01\t\x00\x00\x07\x12\x00\xef\x00\xcd\x00\xab\x07\x12\x00\xef" "\x00\xcd\x00\xab\x01\t" "Testclass\x00\x00\x01" "a\x03\xef\xcd\xab\xff\xff\x01\xff\x08\xcd\xcc\xcc\xcc\xccL1@\x01\x06" "String\x00\x00\x01\x02\x08\xc9v\xbe\x9f\x0c$\xfe@\x08\x00\x00\x00" "0\x00\x00pA\x08\x00\x00\x00@\x00\x00p\xc1\x08\x00\x00\x00P\x00\x00pA" "\x8\x0\x0\x0`\x0\x0p\xc1\x1\x2\x8\xc9v\xbe\x9f\x0c$\xfe@\x1\x2" "10\x01\x03-11\x01\x02" "12\x01\x03-13" "\x02\xcd\xab\x01\x06" "Floats\x4\xff\xff\xff\x7f\x0\x4\x0\x0\x0\x80\x1\x1" "\x4\xff\xff\x7f\x7f\x4\x0\x0\x80\x7f\x4\x0\x0\x80\xff" "\x1\xb Doubles\x8\xff\xff\xff\xff\xff\xff\xff\x7f" "\x0\x8\x0\x0\x0\x0\x0\x0\x0\x80\x1\x1" "\x8\xff\xff\xff\xff\xff\xff\xef\x7f" "\x8\x0\x0\x0\x0\x0\x0\xf0\x7f" "\x8\x0\x0\x0\x0\x0\x0\xf0\xff" "\x1\x4Ints\x1\x1\x8\xff\xff\xff\xff\xff\xff\xff?\x2\x0\x1" "\x8\x0\xff\xff\xff\xff\xff\xff?" "\x3\x0\x0\x1\x8\x0\x0\xff\xff\xff\xff\xff?\x4\x0\x0\x0\x1" "\x8\x0\x0\x0\xff\xff\xff\xff?\x5\x0\x0\x0\x0\x1" "\x8\x0\x0\x0\x0\xff\xff\xff?\x6\x0\x0\x0\x0\x0\x1" "\x8\x0\x0\x0\x0\x0\xff\xff?\x7\x0\x0\x0\x0\x0\x0\x1" "\x8\x0\x0\x0\x0\x0\x0\xff?\x8\x0\x0\x0\x0\x0\x0\x0\x1" "\x8\x0\x0\x0\x0\x0\x0\x0?"; const std::string preserialized(binpacket, sizeof(binpacket) - 1); std::string res(osb.str()); if (res.size() != preserialized.size() || res != preserialized) { // dump mismatching data for further analysis std::ofstream("serialization_expected.bin", std::ios::binary) << preserialized; std::ofstream("serialization_found.bin", std::ios::binary) << res; FAIL(); } std::stringbuf isb(preserialized, std::ios::in); try { eos::portable_iarchive inarch(isb); Testclass in1; Testclass2 in2, in3; inarch >> in2 >> in3; REQUIRE(in2.i == 0x00ab00cd00ef0012u); REQUIRE(in3.i == 0x00ab00cd00ef0012u); std::string teststr; inarch >> teststr >> in1; REQUIRE(teststr == std::string("Testclass")); REQUIRE(in1.testdouble == Catch::Approx(out1.testdouble)); REQUIRE(in1.testbigint == out1.testbigint); REQUIRE(in1.testchar == out1.testchar); REQUIRE(in1.negativeint == out1.negativeint); REQUIRE(in1.testint == out1.testint); REQUIRE(in1.teststr == out1.teststr); inarch >> in1.testint >> teststr; float f_; inarch >> f_; REQUIRE(std::isnan(f_)); for (float f : test_floats) { inarch >> f_; REQUIRE(*reinterpret_cast(&f) == *reinterpret_cast(&f_)); } inarch >> teststr; double d_; inarch >> d_; REQUIRE(std::isnan(d_)); for (double d : test_doubles) { inarch >> d_; REQUIRE(*reinterpret_cast(&d) == *reinterpret_cast(&d_)); } inarch >> teststr; REQUIRE(teststr == std::string("Ints")); int64_t i_; for (int64_t i = 0; i < 64; i += 8) { inarch >> i_; REQUIRE(i_ == (1LL << i)); inarch >> i_; REQUIRE(i_ == ((1LL << 62) - (1LL << i))); } if (*in1.s1 != *out1.s1) FAIL("Sample 1 serialization mismatch"); if (*in1.s2 != *out1.s2) FAIL("Sample 2 serialization mismatch"); } catch (std::exception &e) { FAIL(e.what()); } } liblsl-1.17.7/testing/int/streaminfo.cpp000066400000000000000000000054741517625163100202330ustar00rootroot00000000000000#include "../src/api_config.h" #include "../src/stream_info_impl.h" #include #include // include loguru before catch #include // clazy:excludeall=non-pod-global-static template bool contains(const T(&valid)[N], const T target) { for(T e: valid) if(e==target) return true; return false; } TEST_CASE("uid", "[basic][streaminfo]") { lsl::stream_info_impl info; const std::string uid = info.reset_uid(); INFO(uid); REQUIRE(uid.length() == 36); for(auto i=0u; i 5")); REQUIRE(info.matches_query("nominal_srate >= 499")); REQUIRE(info.matches_query("count(desc/channels/channel[type='EEG'])>3")); LOG_F(INFO, "The following warning is harmless and expected"); REQUIRE(!info.matches_query("in'va'lid")); REQUIRE(!info.matches_query("name='othername'")); #ifdef CATCH_CONFIG_ENABLE_BENCHMARKING // Append lots of dummy channels for performance tests for (int i = 0; i < 50000; ++i) channels.append_child("chn") .append_child("type") .append_child(pugi::node_pcdata) .set_value("foobar"); for (int i = 0; i < 2000; ++i) { channels = channels.append_child("chn"); channels.append_child(pugi::node_pcdata).set_value("1"); } BENCHMARK("trivial query") { return info.matches_query("name='streamname' and type='streamtype'", true); }; BENCHMARK("complicated query") { return info.matches_query("count(desc/channels/channel[type='EEG'])>3", true); }; BENCHMARK("Cached query") { return info.matches_query("count(desc/channels/channel[type='EEG'])>3", false); }; // test how well the cache copes with lots of different queries BENCHMARK("partially cached queries (x1000)") { int matches = 0; for (int j = 0; j < 1000; ++j) matches += info.matches_query(("0<=" + std::to_string(j)).c_str()); return matches; }; #endif } liblsl-1.17.7/testing/int/stringfuncs.cpp000066400000000000000000000040761517625163100204260ustar00rootroot00000000000000#include "util/strfuns.hpp" #include // clazy:excludeall=non-pod-global-static using vec = std::vector; TEST_CASE("strings are split correctly", "[string][basic]") { CHECK(lsl::splitandtrim(" ", ',', true) == vec{""}); CHECK(lsl::splitandtrim(" ", ',', false) == vec{}); CHECK(lsl::splitandtrim(" , ", ',', true) == vec{"", ""}); CHECK(lsl::splitandtrim(" , ", ',', false) == vec{}); CHECK(lsl::splitandtrim(" a ", ',', false) == vec{"a"}); CHECK(lsl::splitandtrim("a,b", ',', true) == vec{"a", "b"}); CHECK(lsl::splitandtrim(",a,,", ',', false) == vec{"a"}); CHECK(lsl::splitandtrim("a, b \t,\t c ", ',', true) == vec{"a", "b", "c"}); } TEST_CASE("trim functions", "[string][basic]") { std::string testcase = "\nHello World\t\n 123"; CHECK(*lsl::trim_begin(testcase.begin(), testcase.end()) == 'H'); CHECK(*lsl::trim_end(testcase.begin(), testcase.begin() + 16) == '\t'); std::string trimmed = "Hello World"; CHECK(lsl::trim_begin(trimmed.begin(), trimmed.end()) == trimmed.begin()); CHECK(lsl::trim_end(trimmed.begin(), trimmed.end()) == trimmed.end()); const char test[] = "Hello World"; CHECK(*lsl::trim_end(test, test + sizeof(test) - 1) == 0); auto begin = testcase.begin(), end = testcase.end(); lsl::trim(begin, end); CHECK(*begin == 'H'); CHECK(*(end - 1) == '3'); CHECK(lsl::trim(testcase) == "Hello World\t\n 123"); CHECK(lsl::trim("") == ""); } inline bool test_split_headerline( std::string str, const char *expected_key, const char *expected_value) { std::string key, value; bool result = lsl::split_headerline(&str[0], str.size(), key, value); INFO(str); CHECK(key == expected_key); CHECK(value == expected_value); return result; } TEST_CASE("split_headerline", "[string][basic]") { std::string basic[] = {{"a:b"}, {" a : b \r\n"}, {"a: b;not c"}}; for(auto &str: basic) CHECK(test_split_headerline(str, "a", "b")); // empty line CHECK(!test_split_headerline("", "", "")); // empty key+val CHECK(test_split_headerline(":", "", "")); // comment in key (?!) CHECK(!test_split_headerline("wha;t:??", "", "")); } liblsl-1.17.7/testing/int/tcpserver.cpp000066400000000000000000000164241517625163100200760ustar00rootroot00000000000000#include "../common/bytecmp.hpp" #include "sample.h" #include "send_buffer.h" #include "stream_info_impl.h" #include "tcp_server.h" #include #include #include #include #include #include #include // clazy:excludeall=non-pod-global-static using namespace asio::ip; using err_t = const asio::error_code &; using sock_t = tcp::socket; using sock_p = std::shared_ptr; /// RAII wrapper that takes care of shutting down the IO thread when an exception happens class tcp_server_wrapper { std::shared_ptr srv; std::shared_ptr srv_ctx; std::unique_ptr thread; public: tcp_server_wrapper(std::shared_ptr info) { auto sendbuf = std::make_shared(10); srv_ctx = std::make_shared(1); auto factory = std::make_shared(info->channel_format(), info->channel_count(), 10); srv = std::make_shared(info, srv_ctx, sendbuf, factory, 5, true, true); srv->begin_serving(); } ~tcp_server_wrapper() noexcept { srv->end_serving(); if (thread) thread->join(); else try { srv_ctx->run(); } catch (std::exception &e) { INFO(e.what()); } } void run() { thread = std::make_unique([this]() { try { this->srv_ctx->run(); } catch (std::exception &e) { INFO(e.what()); } }); } lsl::tcp_server* operator->() { return srv.get(); } }; // testpattern timestamp 123456.789 #define TESTPAT_TIMESTAMP "\xc9v\xbe\x9f\f$\xfe@" // testpattern for two strings #define TESTPAT_STR "\1\00210\1\3-11" void send_request(asio::io_context &ctx, const tcp::endpoint &ep, asio::const_buffer request, std::function write_cb) { auto sock = std::make_shared(ctx); sock->async_connect(ep, [=](err_t connect_err) { INFO(connect_err.message()); REQUIRE(connect_err.value() == 0); asio::async_write(*sock, request, [=, expected_bytes = request.size()](err_t write_err, std::size_t sent_bytes) { INFO("Sent " << sent_bytes << " bytes, outcome: " << write_err.message()); REQUIRE(write_err.value() == 0); REQUIRE(sent_bytes == expected_bytes); write_cb(sock); }); }); } auto with_read_callback(const char *name, std::function fun) { return [name, fun = std::move(fun)](sock_p sock) { auto buf = std::make_shared(); asio::async_read(*sock, asio::dynamic_buffer(*buf), [sock, buf, name, fun #if !defined(_MSC_VER) || _MSC_VER > 1930 = std::move(fun) #endif ](err_t read_err, std::size_t len) { INFO("Test " << name << "\t– read " << len << " bytes, outcome: " << read_err.message()); if (read_err) REQUIRE(read_err == asio::error::eof); fun(*buf); }); }; } void check_streamfeed_100_response(const std::string &res) { REQUIRE(res.substr(0, 4) == "\x7f\x01\x09\2"); INFO(bytes_to_hexstr(res.substr(0, 10))); auto info_len = *reinterpret_cast(res.data() + 4); REQUIRE(static_cast(res.size()) > 6 + info_len); auto info = lsl::stream_info_impl(); info.from_fullinfo_message(res.substr(6, info_len)); REQUIRE(info.source_id() == "abc123"); // precomputed string pattern for 2 string channels, serialized with Boost.Archive const char pat_str[] = "\0\0" "\1\2\b" TESTPAT_TIMESTAMP TESTPAT_STR // first sample "\1\2\b" TESTPAT_TIMESTAMP TESTPAT_STR; // second, identical sample const char pat_f32[] = "\0\0\1\2\b" TESTPAT_TIMESTAMP // first sample "\4\0\0\x80@" // 4 bytes, raw float data "\4\0\0\xa0\xc0" "\4\0\0\xc0@" "\1\2\b" TESTPAT_TIMESTAMP // second sample "\4\0\0\0@" "\4\0\0@\xc0" "\4\0\0\x80@"; const std::string pat = info.channel_format() == cft_string ? std::string(pat_str, sizeof(pat_str) - 1) : std::string(pat_f32, sizeof(pat_f32) - 1); cmp_binstr(res.substr(6 + info_len), pat); } TEST_CASE("tcpserver", "[network]") { asio::io_context ctx(1); auto info = std::make_shared("TCP_str", "", 2, 4., cft_string, "abc123"); tcp_server_wrapper tcp_server(info); tcp::endpoint ep(address_v4(0x7f000001), info->v4data_port()); send_request(ctx, ep, asio::buffer("LSL:streamfeed/110 \n\r\n\r\n"), with_read_callback("basic", [](const std::string &res) { REQUIRE(res.substr(0, 14) == "LSL/110 200 OK"); auto endofheader = res.find("\r\n\r\n"); REQUIRE(endofheader != std::string::npos); std::string received_pattern = res.substr(endofheader + 4); std::string expected = "\2" TESTPAT_TIMESTAMP TESTPAT_STR; expected += expected; cmp_binstr(expected, received_pattern); })); send_request(ctx, ep, asio::buffer("LSL:streamfeed/199 \nNative-byte-order:4321\r\n\r\n"), with_read_callback("endian", [](const std::string &res) { REQUIRE(res.substr(0, 14) == "LSL/110 200 OK"); REQUIRE(res.find("Byte-Order: 4321") != std::string::npos); })); send_request(ctx, ep, asio::buffer("LSL:streamfeed\n0 0\r\n"), with_read_callback("streamfeed 100", check_streamfeed_100_response)); send_request(ctx, ep, asio::buffer("LSL:fullinfo\r\n"), with_read_callback("fullinfo", [expected = info->to_fullinfo_message()]( const std::string &res) { REQUIRE(res == expected); })); tcp_server.run(); ctx.run(); } TEST_CASE("tcpserver_float", "[network]") { auto srv_ctx = std::make_shared(1); asio::io_context ctx(1); auto info = std::make_shared("TCP_f32", "", 3, 4., cft_float32, "abc123"); tcp_server_wrapper tcp_server(info); tcp::endpoint ep(address_v4(0x7f000001), info->v4data_port()); send_request(ctx, ep, asio::buffer("LSL:streamfeed/199 \nsupports-subnormals: 0\r\n\r\n"), with_read_callback("suppress-subnormals", [](const std::string &res) { REQUIRE(res.substr(0, 14) == "LSL/110 200 OK"); REQUIRE(res.find("Suppress-Subnormals: 1") != std::string::npos); auto endofheader = res.find("\r\n\r\n"); REQUIRE(endofheader != std::string::npos); std::string received_pattern = res.substr(endofheader + 4); const char expected[] = "\2" TESTPAT_TIMESTAMP // sample 1 "\0\0\x80@" "\0\0\xa0\xc0" "\0\0\xc0@" "\2" TESTPAT_TIMESTAMP // sample 2 "\0\0\0@" "\0\0@\xc0" "\0\0\x80@"; cmp_binstr(std::string(expected, sizeof(expected) - 1), received_pattern); })); send_request(ctx, ep, asio::buffer("LSL:streamfeed/110 \nNative-byte-order:4321\r\n\r\n"), with_read_callback("endian", [](const std::string &res) { REQUIRE(res.substr(0, 14) == "LSL/110 200 OK"); REQUIRE(res.find("Byte-Order: 4321") != std::string::npos); auto endofheader = res.find("\r\n\r\n"); REQUIRE(endofheader != std::string::npos); std::string received_pattern = res.substr(endofheader + 4); const char expected[] = "\2" "@\xfe$\f\x9f\xbev\xc9" // sample 1 "@\x80\0\0" "\xc0\xa0\0\0" "@\xc0\0\0" "\2" "@\xfe$\f\x9f\xbev\xc9" // sample 2 "@\0\0\0" "\xc0@\0\0" "@\x80\0\0"; cmp_binstr(std::string(expected, sizeof(expected) - 1), received_pattern); })); send_request(ctx, ep, asio::buffer("LSL:streamfeed\n0 0\r\n"), with_read_callback("streamfeed 100", check_streamfeed_100_response)); tcp_server.run(); ctx.run(); } liblsl-1.17.7/testing/lsl.lua000066400000000000000000000040431517625163100160520ustar00rootroot00000000000000-- Wireshark dissector for labstreamerlayer stream discovery packets -- Place this file in $WIRESHARK_CONFIG_DIR/plugins/lsl.lua -- (C) 2020 Tristan Stenner do local lsl_proto = Proto("lsldiscovery", "Labstreaminglayer Discovery") -- source fields local udp_src_f = Field.new("udp.srcport") local udp_dst_f = Field.new("udp.dstport") -- fields to be shown in the packet analysis pane local cmd_F = ProtoField.string("lsl.cmd", "Command") local dst_F = ProtoField.string("lsl.dst", "Destination") local query_F = ProtoField.string("lsl.query", "QueryID", "QueryID") local content_F = ProtoField.string("lsl.content", "Content", "Content") local streaminfo_F = ProtoField.string("lsl.streaminfo", "Streaminfo", "Streaminfo") local ansport_F = ProtoField.string('lsl.answerport', 'Answer port', 'Answer port') lsl_proto.fields = {cmd_F, dst_F, query_F, ansport_F, streaminfo_F, content_F} function lsl_proto.dissector(buffer, pinfo, tree) if udp_dst_f().value == 16571 then if buffer(0, 4):string()~='LSL:' then return end local subtree = tree:add(lsl_proto, buffer) local data = buffer():string() local newlinepos = data:find('\r\n') if newlinepos == nil then return end subtree:add(cmd_F, buffer(0, newlinepos-1)) buffer = buffer(newlinepos + 1) newlinepos = buffer():string():find('\r\n', newlinepos+1, true) if newlinepos == nil then return end subtree:add(content_F, buffer(0, newlinepos-1)) buffer = buffer(newlinepos + 1) local wspos = buffer():string():find(' ') subtree:add(ansport_F, buffer(0, wspos-1)) subtree:add(query_F, buffer(wspos)) elseif udp_src_f().value == 16571 then local subtree = tree:add(lsl_proto, buffer) subtree:add(cmd_F, 'shortinfo reply') local data = buffer():string() local newlinepos = data:find('\r\n') if newlinepos == nil then return end subtree:add(query_F, buffer(0, newlinepos-1)) buffer = buffer(newlinepos + 1) subtree:add(streaminfo_F, buffer(newlinepos+1)) end end DissectorTable.get('udp.port'):add(16571, lsl_proto) end liblsl-1.17.7/testing/lslcfgs/000077500000000000000000000000001517625163100162115ustar00rootroot00000000000000liblsl-1.17.7/testing/lslcfgs/debuglog.cfg000066400000000000000000000001771517625163100204670ustar00rootroot00000000000000; log all debug log messages if enabled in build to "lsldebuglog.txt" [log] file=lsldebuglog.txt ; raise the log level level=3 liblsl-1.17.7/testing/lslcfgs/default.cfg000066400000000000000000000000741517625163100203170ustar00rootroot00000000000000[ports] IPv6=allow [lab] KnownPeers=127.0.0.1 [log] level=9 liblsl-1.17.7/testing/lslcfgs/ipv4_lsl100.cfg000066400000000000000000000001431517625163100206450ustar00rootroot00000000000000[ports] IPv6=disable [multicast] ResolveScope=link [tuning] use_protocol_version=100 [log] level=9 liblsl-1.17.7/testing/lslcfgs/ipv4only.cfg000066400000000000000000000001011517625163100204460ustar00rootroot00000000000000[ports] IPv6=disable [multicast] ResolveScope=link [log] level=9 liblsl-1.17.7/testing/lslcfgs/ipv6_lsl100.cfg000066400000000000000000000001411517625163100206450ustar00rootroot00000000000000[ports] IPv6=force [multicast] ResolveScope=link [tuning] use_protocol_version=100 [log] level=9 liblsl-1.17.7/testing/lslcfgs/ipv6only.cfg000066400000000000000000000000771517625163100204640ustar00rootroot00000000000000[ports] IPv6=force [multicast] ResolveScope=link [log] level=9 liblsl-1.17.7/testing/lslver.c000066400000000000000000000002601517625163100162250ustar00rootroot00000000000000#include "../include/lsl_c.h" #include int main() { printf("LSL version: %d\n%s\n%f\n", lsl_library_version(), lsl_library_info(), lsl_local_clock()); return 0; } liblsl-1.17.7/thirdparty/000077500000000000000000000000001517625163100152715ustar00rootroot00000000000000liblsl-1.17.7/thirdparty/loguru/000077500000000000000000000000001517625163100166065ustar00rootroot00000000000000liblsl-1.17.7/thirdparty/loguru/LICENSE000066400000000000000000000004711517625163100176150ustar00rootroot00000000000000This software is in the public domain. Where that dedication is not recognized, you are granted a perpetual, irrevocable license to copy, modify and distribute it as you see fit. That being said, I would appreciate credit! If you find Loguru useful, tweet me at @ernerfeldt mail me at emil.ernerfeldt@gmail.com. liblsl-1.17.7/thirdparty/loguru/loguru.cpp000066400000000000000000001673451517625163100206470ustar00rootroot00000000000000#if defined(__GNUC__) || defined(__clang__) // Disable all warnings from gcc/clang: #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wc++98-compat" #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" #pragma GCC diagnostic ignored "-Wexit-time-destructors" #pragma GCC diagnostic ignored "-Wformat-nonliteral" #pragma GCC diagnostic ignored "-Wglobal-constructors" #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #pragma GCC diagnostic ignored "-Wmissing-prototypes" #pragma GCC diagnostic ignored "-Wpadded" #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wunused-macros" #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" #elif defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4365) // conversion from 'X' to 'Y', signed/unsigned mismatch #endif #include "loguru.hpp" #ifndef LOGURU_HAS_BEEN_IMPLEMENTED #define LOGURU_HAS_BEEN_IMPLEMENTED #define LOGURU_PREAMBLE_WIDTH (53 + LOGURU_THREADNAME_WIDTH + LOGURU_FILENAME_WIDTH) #undef min #undef max #include #include #include #include #include #include #include #include #include #include #include #include #include #if LOGURU_SYSLOG #include #else #define LOG_USER 0 #endif #ifdef _WIN32 #include #include //_SH_DENYNO #define localtime_r(a, b) localtime_s(b, a) // No localtime_r with MSVC, but arguments are swapped for localtime_s #else #include #include // mkdir #include // STDERR_FILENO #endif #ifdef __linux__ #include // PATH_MAX #elif !defined(_WIN32) #include // PATH_MAX #endif #ifndef PATH_MAX #define PATH_MAX 1024 #endif #ifdef __APPLE__ #include "TargetConditionals.h" #endif // TODO: use defined(_POSIX_VERSION) for some of these things? #if defined(_WIN32) || defined(__CYGWIN__) #define LOGURU_PTHREADS 0 #define LOGURU_WINTHREADS 1 #ifndef LOGURU_STACKTRACES #define LOGURU_STACKTRACES 0 #endif #else #define LOGURU_PTHREADS 1 #define LOGURU_WINTHREADS 0 #ifdef __GLIBC__ #ifndef LOGURU_STACKTRACES #define LOGURU_STACKTRACES 1 #endif #else #ifndef LOGURU_STACKTRACES #define LOGURU_STACKTRACES 0 #endif #endif #endif #if LOGURU_STACKTRACES #include // for __cxa_demangle #include // for dladdr #include // for backtrace #endif // LOGURU_STACKTRACES #if LOGURU_PTHREADS #include #if defined(__FreeBSD__) #include #include #elif defined(__OpenBSD__) #include #endif #ifdef __linux__ /* On Linux, the default thread name is the same as the name of the binary. Additionally, all new threads inherit the name of the thread it got forked from. For this reason, Loguru use the pthread Thread Local Storage for storing thread names on Linux. */ #ifndef LOGURU_PTLS_NAMES #define LOGURU_PTLS_NAMES 1 #endif #endif #endif #if LOGURU_WINTHREADS #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0502 #endif #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #endif #ifndef LOGURU_PTLS_NAMES #define LOGURU_PTLS_NAMES 0 #endif LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace loguru { using namespace std::chrono; #if LOGURU_WITH_FILEABS struct FileAbs { char path[PATH_MAX]; char mode_str[4]; Verbosity verbosity; struct stat st; FILE* fp; bool is_reopening = false; // to prevent recursive call in file_reopen. decltype(steady_clock::now()) last_check_time = steady_clock::now(); }; #else typedef FILE* FileAbs; #endif struct Callback { std::string id; log_handler_t callback; void* user_data; Verbosity verbosity; // Does not change! close_handler_t close; flush_handler_t flush; unsigned indentation; }; using CallbackVec = std::vector; using StringPair = std::pair; using StringPairList = std::vector; const auto s_start_time = steady_clock::now(); Verbosity g_stderr_verbosity = Verbosity_0; bool g_colorlogtostderr = true; unsigned g_flush_interval_ms = 0; bool g_preamble_header = true; bool g_preamble = true; Verbosity g_internal_verbosity = Verbosity_0; // Preamble details bool g_preamble_date = true; bool g_preamble_time = true; bool g_preamble_uptime = true; bool g_preamble_thread = true; bool g_preamble_file = true; bool g_preamble_verbose = true; bool g_preamble_pipe = true; static std::recursive_mutex s_mutex; static Verbosity s_max_out_verbosity = Verbosity_OFF; static std::string s_argv0_filename; static std::string s_arguments; static char s_current_dir[PATH_MAX]; static CallbackVec s_callbacks; static fatal_handler_t s_fatal_handler = nullptr; static verbosity_to_name_t s_verbosity_to_name_callback = nullptr; static name_to_verbosity_t s_name_to_verbosity_callback = nullptr; static StringPairList s_user_stack_cleanups; static bool s_strip_file_path = true; static std::atomic s_stderr_indentation { 0 }; // For periodic flushing: static std::thread* s_flush_thread = nullptr; static bool s_needs_flushing = false; static SignalOptions s_signal_options = SignalOptions::none(); static const bool s_terminal_has_color = [](){ #ifdef _WIN32 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 #endif HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut != INVALID_HANDLE_VALUE) { DWORD dwMode = 0; GetConsoleMode(hOut, &dwMode); dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; return SetConsoleMode(hOut, dwMode) != 0; } return false; #else if (!isatty(STDERR_FILENO)) { return false; } if (const char* term = getenv("TERM")) { return 0 == strcmp(term, "cygwin") || 0 == strcmp(term, "linux") || 0 == strcmp(term, "rxvt-unicode-256color") || 0 == strcmp(term, "screen") || 0 == strcmp(term, "screen-256color") || 0 == strcmp(term, "screen.xterm-256color") || 0 == strcmp(term, "tmux-256color") || 0 == strcmp(term, "xterm") || 0 == strcmp(term, "xterm-256color") || 0 == strcmp(term, "xterm-termite") || 0 == strcmp(term, "xterm-color"); } else { return false; } #endif }(); static void print_preamble_header(char* out_buff, size_t out_buff_size); // ------------------------------------------------------------------------------ // Colors bool terminal_has_color() { return s_terminal_has_color; } // Colors #ifdef _WIN32 #define VTSEQ(ID) ("\x1b[1;" #ID "m") #else #define VTSEQ(ID) ("\x1b[" #ID "m") #endif const char* terminal_black() { return s_terminal_has_color ? VTSEQ(30) : ""; } const char* terminal_red() { return s_terminal_has_color ? VTSEQ(31) : ""; } const char* terminal_green() { return s_terminal_has_color ? VTSEQ(32) : ""; } const char* terminal_yellow() { return s_terminal_has_color ? VTSEQ(33) : ""; } const char* terminal_blue() { return s_terminal_has_color ? VTSEQ(34) : ""; } const char* terminal_purple() { return s_terminal_has_color ? VTSEQ(35) : ""; } const char* terminal_cyan() { return s_terminal_has_color ? VTSEQ(36) : ""; } const char* terminal_light_gray() { return s_terminal_has_color ? VTSEQ(37) : ""; } const char* terminal_white() { return s_terminal_has_color ? VTSEQ(37) : ""; } const char* terminal_light_red() { return s_terminal_has_color ? VTSEQ(91) : ""; } const char* terminal_dim() { return s_terminal_has_color ? VTSEQ(2) : ""; } // Formating const char* terminal_bold() { return s_terminal_has_color ? VTSEQ(1) : ""; } const char* terminal_underline() { return s_terminal_has_color ? VTSEQ(4) : ""; } // You should end each line with this! const char* terminal_reset() { return s_terminal_has_color ? VTSEQ(0) : ""; } // ------------------------------------------------------------------------------ #if LOGURU_WITH_FILEABS void file_reopen(void* user_data); inline FILE* to_file(void* user_data) { return reinterpret_cast(user_data)->fp; } #else inline FILE* to_file(void* user_data) { return reinterpret_cast(user_data); } #endif void file_log(void* user_data, const Message& message) { #if LOGURU_WITH_FILEABS FileAbs* file_abs = reinterpret_cast(user_data); if (file_abs->is_reopening) { return; } // It is better checking file change every minute/hour/day, // instead of doing this every time we log. // Here check_interval is set to zero to enable checking every time; const auto check_interval = seconds(0); if (duration_cast(steady_clock::now() - file_abs->last_check_time) > check_interval) { file_abs->last_check_time = steady_clock::now(); file_reopen(user_data); } FILE* file = to_file(user_data); if (!file) { return; } #else FILE* file = to_file(user_data); #endif fprintf(file, "%s%s%s%s\n", message.preamble, message.indentation, message.prefix, message.message); if (g_flush_interval_ms == 0) { fflush(file); } } void file_close(void* user_data) { FILE* file = to_file(user_data); if (file) { fclose(file); } #if LOGURU_WITH_FILEABS delete reinterpret_cast(user_data); #endif } void file_flush(void* user_data) { FILE* file = to_file(user_data); fflush(file); } #if LOGURU_WITH_FILEABS void file_reopen(void* user_data) { FileAbs * file_abs = reinterpret_cast(user_data); struct stat st; int ret; if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) { file_abs->is_reopening = true; if (file_abs->fp) { fclose(file_abs->fp); } if (!file_abs->fp) { VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to previous error", file_abs->path); } else if (ret < 0) { const auto why = errno_as_text(); VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to '" LOGURU_FMT(s) "'", file_abs->path, why.c_str()); } else { VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to file changed", file_abs->path); } // try reopen current file. if (!create_directories(file_abs->path)) { LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", file_abs->path); } file_abs->fp = fopen(file_abs->path, file_abs->mode_str); if (!file_abs->fp) { LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", file_abs->path); } else { stat(file_abs->path, &file_abs->st); } file_abs->is_reopening = false; } } #endif // ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------ #if LOGURU_SYSLOG void syslog_log(void* /*user_data*/, const Message& message) { /* Level 0: Is reserved for kernel panic type situations. Level 1: Is for Major resource failure. Level 2->7 Application level failures */ int level; if (message.verbosity < Verbosity_FATAL) { level = 1; // System Alert } else { switch(message.verbosity) { case Verbosity_FATAL: level = 2; break; // System Critical case Verbosity_ERROR: level = 3; break; // System Error case Verbosity_WARNING: level = 4; break; // System Warning case Verbosity_INFO: level = 5; break; // System Notice case Verbosity_1: level = 6; break; // System Info default: level = 7; break; // System Debug } } // Note: We don't add the time info. // This is done automatically by the syslog deamon. // Otherwise log all information that the file log does. syslog(level, "%s%s%s", message.indentation, message.prefix, message.message); } void syslog_close(void* /*user_data*/) { closelog(); } void syslog_flush(void* /*user_data*/) {} #endif // ------------------------------------------------------------------------------ // Helpers: Text::~Text() { free(_str); } #if LOGURU_USE_FMTLIB Text vtextprintf(const char* format, fmt::format_args args) { return Text(STRDUP(fmt::vformat(format, args).c_str())); } #else LOGURU_PRINTF_LIKE(1, 0) static Text vtextprintf(const char* format, va_list vlist) { #ifdef _WIN32 int bytes_needed = _vscprintf(format, vlist); CHECK_F(bytes_needed >= 0, "Bad string format: '%s'", format); char* buff = (char*)malloc(bytes_needed+1); vsnprintf(buff, bytes_needed+1, format, vlist); return Text(buff); #else char* buff = nullptr; int result = vasprintf(&buff, format, vlist); CHECK_F(result >= 0, "Bad string format: '" LOGURU_FMT(s) "'", format); return Text(buff); #endif } Text textprintf(const char* format, ...) { va_list vlist; va_start(vlist, format); auto result = vtextprintf(format, vlist); va_end(vlist); return result; } #endif // Overloaded for variadic template matching. Text textprintf() { return Text(static_cast(calloc(1, 1))); } static const char* indentation(unsigned depth) { static const char buff[] = ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . " ". . . . . . . . . . "; static const size_t INDENTATION_WIDTH = 4; static const size_t NUM_INDENTATIONS = (sizeof(buff) - 1) / INDENTATION_WIDTH; depth = std::min(depth, NUM_INDENTATIONS); return buff + INDENTATION_WIDTH * (NUM_INDENTATIONS - depth); } static void parse_args(int& argc, char* argv[], const char* verbosity_flag) { int arg_dest = 1; int out_argc = argc; for (int arg_it = 1; arg_it < argc; ++arg_it) { auto cmd = argv[arg_it]; auto arg_len = strlen(verbosity_flag); bool last_is_alpha = false; #if LOGURU_USE_LOCALE try { // locale variant of isalpha will throw on error last_is_alpha = std::isalpha(cmd[arg_len], std::locale("")); } catch (...) { last_is_alpha = std::isalpha(static_cast(cmd[arg_len])); } #else last_is_alpha = std::isalpha(static_cast(cmd[arg_len])); #endif if (strncmp(cmd, verbosity_flag, arg_len) == 0 && !last_is_alpha) { out_argc -= 1; auto value_str = cmd + arg_len; if (value_str[0] == '\0') { // Value in separate argument arg_it += 1; CHECK_LT_F(arg_it, argc, "Missing verbosiy level after " LOGURU_FMT(s) "", verbosity_flag); value_str = argv[arg_it]; out_argc -= 1; } if (*value_str == '=') { value_str += 1; } auto req_verbosity = get_verbosity_from_name(value_str); if (req_verbosity != Verbosity_INVALID) { g_stderr_verbosity = req_verbosity; } else { char* end = 0; g_stderr_verbosity = static_cast(strtol(value_str, &end, 10)); CHECK_F(end && *end == '\0', "Invalid verbosity. Expected integer, INFO, WARNING, ERROR or OFF, got '" LOGURU_FMT(s) "'", value_str); } } else { argv[arg_dest++] = argv[arg_it]; } } argc = out_argc; argv[argc] = nullptr; } static long long now_ns() { return duration_cast(high_resolution_clock::now().time_since_epoch()).count(); } // Returns the part of the path after the last / or \ (if any). const char* filename(const char* path) { for (auto ptr = path; *ptr; ++ptr) { if (*ptr == '/' || *ptr == '\\') { path = ptr + 1; } } return path; } // ------------------------------------------------------------------------------ static void on_atexit() { VLOG_F(g_internal_verbosity, "atexit"); flush(); } static void install_signal_handlers(const SignalOptions& signal_options); static void write_hex_digit(std::string& out, unsigned num) { DCHECK_LT_F(num, 16u); if (num < 10u) { out.push_back(char('0' + num)); } else { out.push_back(char('A' + num - 10)); } } static void write_hex_byte(std::string& out, uint8_t n) { write_hex_digit(out, n >> 4u); write_hex_digit(out, n & 0x0f); } static void escape(std::string& out, const std::string& str) { for (char c : str) { /**/ if (c == '\a') { out += "\\a"; } else if (c == '\b') { out += "\\b"; } else if (c == '\f') { out += "\\f"; } else if (c == '\n') { out += "\\n"; } else if (c == '\r') { out += "\\r"; } else if (c == '\t') { out += "\\t"; } else if (c == '\v') { out += "\\v"; } else if (c == '\\') { out += "\\\\"; } else if (c == '\'') { out += "\\\'"; } else if (c == '\"') { out += "\\\""; } else if (c == ' ') { out += "\\ "; } else if (0 <= c && c < 0x20) { // ASCI control character: // else if (c < 0x20 || c != (c & 127)) { // ASCII control character or UTF-8: out += "\\x"; write_hex_byte(out, static_cast(c)); } else { out += c; } } } Text errno_as_text() { char buff[256]; #if defined(__GLIBC__) && defined(_GNU_SOURCE) // GNU Version return Text(STRDUP(strerror_r(errno, buff, sizeof(buff)))); #elif defined(__APPLE__) || _POSIX_C_SOURCE >= 200112L // XSI Version strerror_r(errno, buff, sizeof(buff)); return Text(strdup(buff)); #elif defined(_WIN32) strerror_s(buff, sizeof(buff), errno); return Text(STRDUP(buff)); #else // Not thread-safe. return Text(STRDUP(strerror(errno))); #endif } void init(int& argc, char* argv[], const Options& options) { CHECK_GT_F(argc, 0, "Expected proper argc/argv"); CHECK_EQ_F(argv[argc], nullptr, "Expected proper argc/argv"); s_argv0_filename = filename(argv[0]); #ifdef _WIN32 #define getcwd _getcwd #endif if (!getcwd(s_current_dir, sizeof(s_current_dir))) { const auto error_text = errno_as_text(); LOG_F(WARNING, "Failed to get current working directory: " LOGURU_FMT(s) "", error_text.c_str()); } s_arguments = ""; for (int i = 0; i < argc; ++i) { escape(s_arguments, argv[i]); if (i + 1 < argc) { s_arguments += " "; } } if (options.verbosity_flag) { parse_args(argc, argv, options.verbosity_flag); } if (const auto main_thread_name = options.main_thread_name) { #if LOGURU_PTLS_NAMES || LOGURU_WINTHREADS set_thread_name(main_thread_name); #elif LOGURU_PTHREADS char old_thread_name[16] = {0}; auto this_thread = pthread_self(); #if defined(__APPLE__) || defined(__linux__) || defined(__sun) pthread_getname_np(this_thread, old_thread_name, sizeof(old_thread_name)); #endif if (old_thread_name[0] == 0) { #ifdef __APPLE__ pthread_setname_np(main_thread_name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(this_thread, main_thread_name); #elif defined(__linux__) || defined(__sun) pthread_setname_np(this_thread, main_thread_name); #endif } #endif // LOGURU_PTHREADS } if (g_stderr_verbosity >= Verbosity_INFO) { if (g_preamble_header) { char preamble_explain[LOGURU_PREAMBLE_WIDTH]; print_preamble_header(preamble_explain, sizeof(preamble_explain)); if (g_colorlogtostderr && s_terminal_has_color) { fprintf(stderr, "%s%s%s\n", terminal_reset(), terminal_dim(), preamble_explain); } else { fprintf(stderr, "%s\n", preamble_explain); } } fflush(stderr); } VLOG_F(g_internal_verbosity, "arguments: " LOGURU_FMT(s) "", s_arguments.c_str()); if (strlen(s_current_dir) != 0) { VLOG_F(g_internal_verbosity, "Current dir: " LOGURU_FMT(s) "", s_current_dir); } VLOG_F(g_internal_verbosity, "stderr verbosity: " LOGURU_FMT(d) "", g_stderr_verbosity); VLOG_F(g_internal_verbosity, "-----------------------------------"); install_signal_handlers(options.signal_options); atexit(on_atexit); } void shutdown() { VLOG_F(g_internal_verbosity, "loguru::shutdown()"); remove_all_callbacks(); set_fatal_handler(nullptr); set_verbosity_to_name_callback(nullptr); set_name_to_verbosity_callback(nullptr); } void write_date_time(char* buff, unsigned long long buff_size) { auto now = system_clock::now(); long long ms_since_epoch = duration_cast(now.time_since_epoch()).count(); time_t sec_since_epoch = time_t(ms_since_epoch / 1000); tm time_info; localtime_r(&sec_since_epoch, &time_info); snprintf(buff, buff_size, "%04d%02d%02d_%02d%02d%02d.%03lld", 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday, time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); } const char* argv0_filename() { return s_argv0_filename.c_str(); } const char* arguments() { return s_arguments.c_str(); } const char* current_dir() { return s_current_dir; } const char* home_dir() { #ifdef __MINGW32__ auto home = getenv("USERPROFILE"); CHECK_F(home != nullptr, "Missing USERPROFILE"); return home; #elif defined(_WIN32) char* user_profile; size_t len; errno_t err = _dupenv_s(&user_profile, &len, "USERPROFILE"); CHECK_F(err == 0, "Missing USERPROFILE"); return user_profile; #else // _WIN32 auto home = getenv("HOME"); CHECK_F(home != nullptr, "Missing HOME"); return home; #endif // _WIN32 } void suggest_log_path(const char* prefix, char* buff, unsigned long long buff_size) { if (prefix[0] == '~') { snprintf(buff, buff_size - 1, "%s%s", home_dir(), prefix + 1); } else { snprintf(buff, buff_size - 1, "%s", prefix); } // Check for terminating / size_t n = strlen(buff); if (n != 0) { if (buff[n - 1] != '/') { CHECK_F(n + 2 < buff_size, "Filename buffer too small"); buff[n] = '/'; buff[n + 1] = '\0'; } } #ifdef _WIN32 strncat_s(buff, buff_size - strlen(buff) - 1, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); strncat_s(buff, buff_size - strlen(buff) - 1, "/", buff_size - strlen(buff) - 1); write_date_time(buff + strlen(buff), buff_size - strlen(buff)); strncat_s(buff, buff_size - strlen(buff) - 1, ".log", buff_size - strlen(buff) - 1); #else strncat(buff, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); strncat(buff, "/", buff_size - strlen(buff) - 1); write_date_time(buff + strlen(buff), buff_size - strlen(buff)); strncat(buff, ".log", buff_size - strlen(buff) - 1); #endif } bool create_directories(const char* file_path_const) { CHECK_F(file_path_const && *file_path_const); char* file_path = STRDUP(file_path_const); for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { *p = '\0'; #ifdef _WIN32 if (_mkdir(file_path) == -1) { #else if (mkdir(file_path, 0755) == -1) { #endif if (errno != EEXIST) { LOG_F(ERROR, "Failed to create directory '" LOGURU_FMT(s) "'", file_path); LOG_IF_F(ERROR, errno == EACCES, "EACCES"); LOG_IF_F(ERROR, errno == ENAMETOOLONG, "ENAMETOOLONG"); LOG_IF_F(ERROR, errno == ENOENT, "ENOENT"); LOG_IF_F(ERROR, errno == ENOTDIR, "ENOTDIR"); LOG_IF_F(ERROR, errno == ELOOP, "ELOOP"); *p = '/'; free(file_path); return false; } } *p = '/'; } free(file_path); return true; } bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) { char path[PATH_MAX]; if (path_in[0] == '~') { snprintf(path, sizeof(path) - 1, "%s%s", home_dir(), path_in + 1); } else { snprintf(path, sizeof(path) - 1, "%s", path_in); } if (!create_directories(path)) { LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", path); } const char* mode_str = (mode == FileMode::Truncate ? "w" : "a"); FILE* file; #ifdef _WIN32 file = _fsopen(path, mode_str, _SH_DENYNO); #else file = fopen(path, mode_str); #endif if (!file) { LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", path); return false; } #if LOGURU_WITH_FILEABS FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path); snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str); stat(file_abs->path, &file_abs->st); file_abs->fp = file; file_abs->verbosity = verbosity; add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush); #else add_callback(path_in, file_log, file, verbosity, file_close, file_flush); #endif if (mode == FileMode::Append) { fprintf(file, "\n\n\n\n\n"); } if (!s_arguments.empty()) { fprintf(file, "arguments: %s\n", s_arguments.c_str()); } if (strlen(s_current_dir) != 0) { fprintf(file, "Current dir: %s\n", s_current_dir); } fprintf(file, "File verbosity level: %d\n", verbosity); if (g_preamble_header) { char preamble_explain[LOGURU_PREAMBLE_WIDTH]; print_preamble_header(preamble_explain, sizeof(preamble_explain)); fprintf(file, "%s\n", preamble_explain); } fflush(file); VLOG_F(g_internal_verbosity, "Logging to '" LOGURU_FMT(s) "', mode: '" LOGURU_FMT(s) "', verbosity: " LOGURU_FMT(d) "", path, mode_str, verbosity); return true; } /* Will add syslog as a standard sink for log messages Any logging message with a verbosity lower or equal to the given verbosity will be included. This works for Unix like systems (i.e. Linux/Mac) There is no current implementation for Windows (as I don't know the equivalent calls or have a way to test them). If you know please add and send a pull request. The code should still compile under windows but will only generate a warning message that syslog is unavailable. Search for LOGURU_SYSLOG to find and fix. */ bool add_syslog(const char* app_name, Verbosity verbosity) { return add_syslog(app_name, verbosity, LOG_USER); } bool add_syslog(const char* app_name, Verbosity verbosity, int facility) { #if LOGURU_SYSLOG if (app_name == nullptr) { app_name = argv0_filename(); } openlog(app_name, 0, facility); add_callback("'syslog'", syslog_log, nullptr, verbosity, syslog_close, syslog_flush); VLOG_F(g_internal_verbosity, "Logging to 'syslog' , verbosity: " LOGURU_FMT(d) "", verbosity); return true; #else (void)app_name; (void)verbosity; (void)facility; VLOG_F(g_internal_verbosity, "syslog not implemented on this system. Request to install syslog logging ignored."); return false; #endif } // Will be called right before abort(). void set_fatal_handler(fatal_handler_t handler) { s_fatal_handler = handler; } fatal_handler_t get_fatal_handler() { return s_fatal_handler; } void set_verbosity_to_name_callback(verbosity_to_name_t callback) { s_verbosity_to_name_callback = callback; } void set_name_to_verbosity_callback(name_to_verbosity_t callback) { s_name_to_verbosity_callback = callback; } void add_stack_cleanup(const char* find_this, const char* replace_with_this) { if (strlen(find_this) <= strlen(replace_with_this)) { LOG_F(WARNING, "add_stack_cleanup: the replacement should be shorter than the pattern!"); return; } s_user_stack_cleanups.push_back(StringPair(find_this, replace_with_this)); } static void on_callback_change() { s_max_out_verbosity = Verbosity_OFF; for (const auto& callback : s_callbacks) { s_max_out_verbosity = std::max(s_max_out_verbosity, callback.verbosity); } } void add_callback( const char* id, log_handler_t callback, void* user_data, Verbosity verbosity, close_handler_t on_close, flush_handler_t on_flush) { std::lock_guard lock(s_mutex); s_callbacks.push_back(Callback{id, callback, user_data, verbosity, on_close, on_flush, 0}); on_callback_change(); } // Returns a custom verbosity name if one is available, or nullptr. // See also set_verbosity_to_name_callback. const char* get_verbosity_name(Verbosity verbosity) { auto name = s_verbosity_to_name_callback ? (*s_verbosity_to_name_callback)(verbosity) : nullptr; // Use standard replacements if callback fails: if (!name) { if (verbosity <= Verbosity_FATAL) { name = "FATL"; } else if (verbosity == Verbosity_ERROR) { name = "ERR"; } else if (verbosity == Verbosity_WARNING) { name = "WARN"; } else if (verbosity == Verbosity_INFO) { name = "INFO"; } } return name; } // Returns Verbosity_INVALID if the name is not found. // See also set_name_to_verbosity_callback. Verbosity get_verbosity_from_name(const char* name) { auto verbosity = s_name_to_verbosity_callback ? (*s_name_to_verbosity_callback)(name) : Verbosity_INVALID; // Use standard replacements if callback fails: if (verbosity == Verbosity_INVALID) { if (strcmp(name, "OFF") == 0) { verbosity = Verbosity_OFF; } else if (strcmp(name, "INFO") == 0) { verbosity = Verbosity_INFO; } else if (strcmp(name, "WARNING") == 0) { verbosity = Verbosity_WARNING; } else if (strcmp(name, "ERROR") == 0) { verbosity = Verbosity_ERROR; } else if (strcmp(name, "FATAL") == 0) { verbosity = Verbosity_FATAL; } } return verbosity; } bool remove_callback(const char* id) { std::lock_guard lock(s_mutex); auto it = std::find_if(begin(s_callbacks), end(s_callbacks), [&](const Callback& c) { return c.id == id; }); if (it != s_callbacks.end()) { if (it->close) { it->close(it->user_data); } s_callbacks.erase(it); on_callback_change(); return true; } else { LOG_F(ERROR, "Failed to locate callback with id '" LOGURU_FMT(s) "'", id); return false; } } void remove_all_callbacks() { std::lock_guard lock(s_mutex); for (auto& callback : s_callbacks) { if (callback.close) { callback.close(callback.user_data); } } s_callbacks.clear(); on_callback_change(); } // Returns the maximum of g_stderr_verbosity and all file/custom outputs. Verbosity current_verbosity_cutoff() { return g_stderr_verbosity > s_max_out_verbosity ? g_stderr_verbosity : s_max_out_verbosity; } // ------------------------------------------------------------------------ // Threads names #if LOGURU_PTLS_NAMES static pthread_once_t s_pthread_key_once = PTHREAD_ONCE_INIT; static pthread_key_t s_pthread_key_name; void make_pthread_key_name() { (void)pthread_key_create(&s_pthread_key_name, free); } #endif #if LOGURU_WINTHREADS // Where we store the custom thread name set by `set_thread_name` char* thread_name_buffer() { __declspec( thread ) static char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; return &thread_name[0]; } #endif // LOGURU_WINTHREADS void set_thread_name(const char* name) { #if LOGURU_PTLS_NAMES // Store thread name in thread-local storage at `s_pthread_key_name` (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); (void)pthread_setspecific(s_pthread_key_name, STRDUP(name)); #elif LOGURU_PTHREADS // Tell the OS the thread name #ifdef __APPLE__ pthread_setname_np(name); #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); #elif defined(__linux__) || defined(__sun) pthread_setname_np(pthread_self(), name); #endif #elif LOGURU_WINTHREADS // Store thread name in a thread-local storage: strncpy_s(thread_name_buffer(), LOGURU_THREADNAME_WIDTH + 1, name, _TRUNCATE); #else // LOGURU_PTHREADS // TODO: on these weird platforms we should also store the thread name // in a generic thread-local storage. (void)name; #endif // LOGURU_PTHREADS } void get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id) { CHECK_NE_F(length, 0u, "Zero length buffer in get_thread_name"); CHECK_NOTNULL_F(buffer, "nullptr in get_thread_name"); #if LOGURU_PTLS_NAMES (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); if (const char* name = static_cast(pthread_getspecific(s_pthread_key_name))) { snprintf(buffer, static_cast(length), "%s", name); } else { buffer[0] = 0; } #elif LOGURU_PTHREADS // Ask the OS about the thread name. // This is what we *want* to do on all platforms, but // only some platforms support it (currently). pthread_getname_np(pthread_self(), buffer, length); #elif LOGURU_WINTHREADS snprintf(buffer, static_cast(length), "%s", thread_name_buffer()); #else // Thread names unsupported buffer[0] = 0; #endif if (buffer[0] == 0) { // We failed to get a readable thread name. // Write a HEX thread ID instead. // We try to get an ID that is the same as the ID you could // read in your debugger, system monitor etc. #ifdef __APPLE__ uint64_t thread_id; pthread_threadid_np(pthread_self(), &thread_id); #elif defined(__FreeBSD__) long thread_id; (void)thr_self(&thread_id); #elif LOGURU_PTHREADS uint64_t thread_id = pthread_self(); #else // This ID does not correllate to anything we can get from the OS, // so this is the worst way to get the ID. const auto thread_id = std::hash{}(std::this_thread::get_id()); #endif if (right_align_hex_id) { snprintf(buffer, static_cast(length), "%*X", static_cast(length - 1), static_cast(thread_id)); } else { snprintf(buffer, static_cast(length), "%X", static_cast(thread_id)); } } } // ------------------------------------------------------------------------ // Stack traces #if LOGURU_STACKTRACES Text demangle(const char* name) { int status = -1; char* demangled = abi::__cxa_demangle(name, 0, 0, &status); Text result{status == 0 ? demangled : STRDUP(name)}; return result; } #if LOGURU_RTTI template std::string type_name() { auto demangled = demangle(typeid(T).name()); return demangled.c_str(); } #endif // LOGURU_RTTI static const StringPairList REPLACE_LIST = { #if LOGURU_RTTI { type_name(), "std::string" }, { type_name(), "std::wstring" }, { type_name(), "std::u16string" }, { type_name(), "std::u32string" }, #endif // LOGURU_RTTI { "std::__1::", "std::" }, { "__thiscall ", "" }, { "__cdecl ", "" }, }; void do_replacements(const StringPairList& replacements, std::string& str) { for (auto&& p : replacements) { if (p.first.size() <= p.second.size()) { // On gcc, "type_name()" is "std::string" continue; } size_t it; while ((it=str.find(p.first)) != std::string::npos) { str.replace(it, p.first.size(), p.second); } } } std::string prettify_stacktrace(const std::string& input) { std::string output = input; do_replacements(s_user_stack_cleanups, output); do_replacements(REPLACE_LIST, output); try { std::regex std_allocator_re(R"(,\s*std::allocator<[^<>]+>)"); output = std::regex_replace(output, std_allocator_re, std::string("")); std::regex template_spaces_re(R"(<\s*([^<> ]+)\s*>)"); output = std::regex_replace(output, template_spaces_re, std::string("<$1>")); } catch (std::regex_error&) { // Probably old GCC. } return output; } std::string stacktrace_as_stdstring(int skip) { // From https://gist.github.com/fmela/591333 void* callstack[128]; const auto max_frames = sizeof(callstack) / sizeof(callstack[0]); int num_frames = backtrace(callstack, max_frames); char** symbols = backtrace_symbols(callstack, num_frames); std::string result; // Print stack traces so the most relevant ones are written last // Rationale: http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html for (int i = num_frames - 1; i >= skip; --i) { char buf[1024]; Dl_info info; if (dladdr(callstack[i], &info) && info.dli_sname) { char* demangled = NULL; int status = -1; if (info.dli_sname[0] == '_') { demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status); } snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", i - skip, int(2 + sizeof(void*) * 2), callstack[i], status == 0 ? demangled : info.dli_sname == 0 ? symbols[i] : info.dli_sname, static_cast(callstack[i]) - static_cast(info.dli_saddr)); free(demangled); } else { snprintf(buf, sizeof(buf), "%-3d %*p %s\n", i - skip, int(2 + sizeof(void*) * 2), callstack[i], symbols[i]); } result += buf; } free(symbols); if (num_frames == max_frames) { result = "[truncated]\n" + result; } if (!result.empty() && result[result.size() - 1] == '\n') { result.resize(result.size() - 1); } return prettify_stacktrace(result); } #else // LOGURU_STACKTRACES Text demangle(const char* name) { return Text(STRDUP(name)); } std::string stacktrace_as_stdstring(int) { // No stacktraces available on this platform" return ""; } #endif // LOGURU_STACKTRACES Text stacktrace(int skip) { auto str = stacktrace_as_stdstring(skip + 1); return Text(STRDUP(str.c_str())); } // ------------------------------------------------------------------------ static void print_preamble_header(char* out_buff, size_t out_buff_size) { if (out_buff_size == 0) { return; } out_buff[0] = '\0'; size_t pos = 0; if (g_preamble_date && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "date "); if (bytes > 0) { pos += bytes; } } if (g_preamble_time && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "time "); if (bytes > 0) { pos += bytes; } } if (g_preamble_uptime && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "( uptime ) "); if (bytes > 0) { pos += bytes; } } if (g_preamble_thread && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", LOGURU_THREADNAME_WIDTH, " thread name/id"); if (bytes > 0) { pos += bytes; } } if (g_preamble_file && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "%*s:line ", LOGURU_FILENAME_WIDTH, "file"); if (bytes > 0) { pos += bytes; } } if (g_preamble_verbose && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, " v"); if (bytes > 0) { pos += bytes; } } if (g_preamble_pipe && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "| "); if (bytes > 0) { pos += bytes; } } } static void print_preamble(char* out_buff, size_t out_buff_size, Verbosity verbosity, const char* file, unsigned line) { if (out_buff_size == 0) { return; } out_buff[0] = '\0'; if (!g_preamble) { return; } long long ms_since_epoch = duration_cast(system_clock::now().time_since_epoch()).count(); time_t sec_since_epoch = time_t(ms_since_epoch / 1000); tm time_info; localtime_r(&sec_since_epoch, &time_info); auto uptime_ms = duration_cast(steady_clock::now() - s_start_time).count(); auto uptime_sec = static_cast (uptime_ms) / 1000.0; char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; get_thread_name(thread_name, LOGURU_THREADNAME_WIDTH + 1, true); if (s_strip_file_path) { file = filename(file); } char level_buff[6]; const char* custom_level_name = get_verbosity_name(verbosity); if (custom_level_name) { snprintf(level_buff, sizeof(level_buff) - 1, "%s", custom_level_name); } else { snprintf(level_buff, sizeof(level_buff) - 1, "% 4d", static_cast(verbosity)); } size_t pos = 0; if (g_preamble_date && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "%04d-%02d-%02d ", 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday); if (bytes > 0) { pos += bytes; } } if (g_preamble_time && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "%02d:%02d:%02d.%03lld ", time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); if (bytes > 0) { pos += bytes; } } if (g_preamble_uptime && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "(%8.3fs) ", uptime_sec); if (bytes > 0) { pos += bytes; } } if (g_preamble_thread && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", LOGURU_THREADNAME_WIDTH, thread_name); if (bytes > 0) { pos += bytes; } } if (g_preamble_file && pos < out_buff_size) { char shortened_filename[LOGURU_FILENAME_WIDTH + 1]; snprintf(shortened_filename, LOGURU_FILENAME_WIDTH + 1, "%s", file); int bytes = snprintf(out_buff + pos, out_buff_size - pos, "%*s:%-5u ", LOGURU_FILENAME_WIDTH, shortened_filename, line); if (bytes > 0) { pos += bytes; } } if (g_preamble_verbose && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "%4s", level_buff); if (bytes > 0) { pos += bytes; } } if (g_preamble_pipe && pos < out_buff_size) { int bytes = snprintf(out_buff + pos, out_buff_size - pos, "| "); if (bytes > 0) { pos += bytes; } } } // stack_trace_skip is just if verbosity == FATAL. static void log_message(int stack_trace_skip, Message& message, bool with_indentation, bool abort_if_fatal) { const auto verbosity = message.verbosity; std::lock_guard lock(s_mutex); if (message.verbosity == Verbosity_FATAL) { auto st = loguru::stacktrace(stack_trace_skip + 2); if (!st.empty()) { RAW_LOG_F(ERROR, "Stack trace:\n" LOGURU_FMT(s) "", st.c_str()); } auto ec = loguru::get_error_context(); if (!ec.empty()) { RAW_LOG_F(ERROR, "" LOGURU_FMT(s) "", ec.c_str()); } } if (with_indentation) { message.indentation = indentation(s_stderr_indentation); } if (verbosity <= g_stderr_verbosity) { if (g_colorlogtostderr && s_terminal_has_color) { if (verbosity > Verbosity_WARNING) { fprintf(stderr, "%s%s%s%s%s%s%s%s\n", terminal_reset(), terminal_dim(), message.preamble, message.indentation, verbosity == Verbosity_INFO ? terminal_reset() : "", // un-dim for info message.prefix, message.message, terminal_reset()); } else { fprintf(stderr, "%s%s%s%s%s%s%s\n", terminal_reset(), verbosity == Verbosity_WARNING ? terminal_yellow() : terminal_red(), message.preamble, message.indentation, message.prefix, message.message, terminal_reset()); } } else { fprintf(stderr, "%s%s%s%s\n", message.preamble, message.indentation, message.prefix, message.message); } if (g_flush_interval_ms == 0) { fflush(stderr); } else { s_needs_flushing = true; } } for (auto& p : s_callbacks) { if (verbosity <= p.verbosity) { if (with_indentation) { message.indentation = indentation(p.indentation); } p.callback(p.user_data, message); if (g_flush_interval_ms == 0) { if (p.flush) { p.flush(p.user_data); } } else { s_needs_flushing = true; } } } if (g_flush_interval_ms > 0 && !s_flush_thread) { s_flush_thread = new std::thread([](){ for (;;) { if (s_needs_flushing) { flush(); } std::this_thread::sleep_for(std::chrono::milliseconds(g_flush_interval_ms)); } }); } if (message.verbosity == Verbosity_FATAL) { flush(); if (s_fatal_handler) { s_fatal_handler(message); flush(); } if (abort_if_fatal) { #if !defined(_WIN32) if (s_signal_options.sigabrt) { // Make sure we don't catch our own abort: signal(SIGABRT, SIG_DFL); } #endif abort(); } } } // stack_trace_skip is just if verbosity == FATAL. void log_to_everywhere(int stack_trace_skip, Verbosity verbosity, const char* file, unsigned line, const char* prefix, const char* buff) { char preamble_buff[LOGURU_PREAMBLE_WIDTH]; print_preamble(preamble_buff, sizeof(preamble_buff), verbosity, file, line); auto message = Message{verbosity, file, line, preamble_buff, "", prefix, buff}; log_message(stack_trace_skip + 1, message, true, true); } #if LOGURU_USE_FMTLIB void vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) { auto formatted = fmt::vformat(format, args); log_to_everywhere(1, verbosity, file, line, "", formatted.c_str()); } void raw_vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args) { auto formatted = fmt::vformat(format, args); auto message = Message{verbosity, file, line, "", "", "", formatted.c_str()}; log_message(1, message, false, true); } #else void log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) { va_list vlist; va_start(vlist, format); vlog(verbosity, file, line, format, vlist); va_end(vlist); } void vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, va_list vlist) { auto buff = vtextprintf(format, vlist); log_to_everywhere(1, verbosity, file, line, "", buff.c_str()); } void raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) { va_list vlist; va_start(vlist, format); auto buff = vtextprintf(format, vlist); auto message = Message{verbosity, file, line, "", "", "", buff.c_str()}; log_message(1, message, false, true); va_end(vlist); } #endif void flush() { std::lock_guard lock(s_mutex); fflush(stderr); for (const auto& callback : s_callbacks) { if (callback.flush) { callback.flush(callback.user_data); } } s_needs_flushing = false; } LogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, va_list vlist) : _verbosity(verbosity), _file(file), _line(line) { this->Init(format, vlist); } LogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) : _verbosity(verbosity), _file(file), _line(line) { va_list vlist; va_start(vlist, format); this->Init(format, vlist); va_end(vlist); } LogScopeRAII::~LogScopeRAII() { if (_file) { std::lock_guard lock(s_mutex); if (_indent_stderr && s_stderr_indentation > 0) { --s_stderr_indentation; } for (auto& p : s_callbacks) { // Note: Callback indentation cannot change! if (_verbosity <= p.verbosity) { // in unlikely case this callback is new if (p.indentation > 0) { --p.indentation; } } } #if LOGURU_VERBOSE_SCOPE_ENDINGS auto duration_sec = static_cast(now_ns() - _start_time_ns) / 1e9; #if LOGURU_USE_FMTLIB auto buff = textprintf("{:.{}f} s: {:s}", duration_sec, LOGURU_SCOPE_TIME_PRECISION, _name); #else auto buff = textprintf("%.*f s: %s", LOGURU_SCOPE_TIME_PRECISION, duration_sec, _name); #endif log_to_everywhere(1, _verbosity, _file, _line, "} ", buff.c_str()); #else log_to_everywhere(1, _verbosity, _file, _line, "}", ""); #endif } } void LogScopeRAII::Init(const char* format, va_list vlist) { if (_verbosity <= current_verbosity_cutoff()) { std::lock_guard lock(s_mutex); _indent_stderr = (_verbosity <= g_stderr_verbosity); _start_time_ns = now_ns(); vsnprintf(_name, sizeof(_name), format, vlist); log_to_everywhere(1, _verbosity, _file, _line, "{ ", _name); if (_indent_stderr) { ++s_stderr_indentation; } for (auto& p : s_callbacks) { if (_verbosity <= p.verbosity) { ++p.indentation; } } } else { _file = nullptr; } } #if LOGURU_USE_FMTLIB void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, fmt::format_args args) { auto formatted = fmt::vformat(format, args); log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, formatted.c_str()); abort(); // log_to_everywhere already does this, but this makes the analyzer happy. } #else void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, ...) { va_list vlist; va_start(vlist, format); auto buff = vtextprintf(format, vlist); log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, buff.c_str()); va_end(vlist); abort(); // log_to_everywhere already does this, but this makes the analyzer happy. } #endif void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line) { log_and_abort(stack_trace_skip + 1, expr, file, line, " "); } // ---------------------------------------------------------------------------- // Streams: #if LOGURU_USE_FMTLIB template std::string vstrprintf(const char* format, const Args&... args) { auto text = textprintf(format, args...); std::string result = text.c_str(); return result; } template std::string strprintf(const char* format, const Args&... args) { return vstrprintf(format, args...); } #else std::string vstrprintf(const char* format, va_list vlist) { auto text = vtextprintf(format, vlist); std::string result = text.c_str(); return result; } std::string strprintf(const char* format, ...) { va_list vlist; va_start(vlist, format); auto result = vstrprintf(format, vlist); va_end(vlist); return result; } #endif #if LOGURU_WITH_STREAMS StreamLogger::~StreamLogger() noexcept(false) { auto message = _ss.str(); log(_verbosity, _file, _line, LOGURU_FMT(s), message.c_str()); } AbortLogger::~AbortLogger() noexcept(false) { auto message = _ss.str(); loguru::log_and_abort(1, _expr, _file, _line, LOGURU_FMT(s), message.c_str()); } #endif // LOGURU_WITH_STREAMS // ---------------------------------------------------------------------------- // 888888 88""Yb 88""Yb dP"Yb 88""Yb dP""b8 dP"Yb 88b 88 888888 888888 Yb dP 888888 // 88__ 88__dP 88__dP dP Yb 88__dP dP `" dP Yb 88Yb88 88 88__ YbdP 88 // 88"" 88"Yb 88"Yb Yb dP 88"Yb Yb Yb dP 88 Y88 88 88"" dPYb 88 // 888888 88 Yb 88 Yb YbodP 88 Yb YboodP YbodP 88 Y8 88 888888 dP Yb 88 // ---------------------------------------------------------------------------- struct StringStream { std::string str; }; // Use this in your EcPrinter implementations. void stream_print(StringStream& out_string_stream, const char* text) { out_string_stream.str += text; } // ---------------------------------------------------------------------------- using ECPtr = EcEntryBase*; #if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IPHONE) #ifdef __APPLE__ #define LOGURU_THREAD_LOCAL __thread #else #define LOGURU_THREAD_LOCAL thread_local #endif static LOGURU_THREAD_LOCAL ECPtr thread_ec_ptr = nullptr; ECPtr& get_thread_ec_head_ref() { return thread_ec_ptr; } #else // !thread_local static pthread_once_t s_ec_pthread_once = PTHREAD_ONCE_INIT; static pthread_key_t s_ec_pthread_key; void free_ec_head_ref(void* io_error_context) { delete reinterpret_cast(io_error_context); } void ec_make_pthread_key() { (void)pthread_key_create(&s_ec_pthread_key, free_ec_head_ref); } ECPtr& get_thread_ec_head_ref() { (void)pthread_once(&s_ec_pthread_once, ec_make_pthread_key); auto ec = reinterpret_cast(pthread_getspecific(s_ec_pthread_key)); if (ec == nullptr) { ec = new ECPtr(nullptr); (void)pthread_setspecific(s_ec_pthread_key, ec); } return *ec; } #endif // !thread_local // ---------------------------------------------------------------------------- EcHandle get_thread_ec_handle() { return get_thread_ec_head_ref(); } Text get_error_context() { return get_error_context_for(get_thread_ec_head_ref()); } Text get_error_context_for(const EcEntryBase* ec_head) { std::vector stack; while (ec_head) { stack.push_back(ec_head); ec_head = ec_head->_previous; } std::reverse(stack.begin(), stack.end()); StringStream result; if (!stack.empty()) { result.str += "------------------------------------------------\n"; for (auto entry : stack) { const auto description = std::string(entry->_descr) + ":"; #if LOGURU_USE_FMTLIB auto prefix = textprintf("[ErrorContext] {.{}s}:{:-5u} {:-20s} ", filename(entry->_file), LOGURU_FILENAME_WIDTH, entry->_line, description.c_str()); #else auto prefix = textprintf("[ErrorContext] %*s:%-5u %-20s ", LOGURU_FILENAME_WIDTH, filename(entry->_file), entry->_line, description.c_str()); #endif result.str += prefix.c_str(); entry->print_value(result); result.str += "\n"; } result.str += "------------------------------------------------"; } return Text(STRDUP(result.str.c_str())); } EcEntryBase::EcEntryBase(const char* file, unsigned line, const char* descr) : _file(file), _line(line), _descr(descr) { EcEntryBase*& ec_head = get_thread_ec_head_ref(); _previous = ec_head; ec_head = this; } EcEntryBase::~EcEntryBase() { get_thread_ec_head_ref() = _previous; } // ------------------------------------------------------------------------ Text ec_to_text(const char* value) { // Add quotes around the string to make it obvious where it begin and ends. // This is great for detecting erroneous leading or trailing spaces in e.g. an identifier. auto str = "\"" + std::string(value) + "\""; return Text{STRDUP(str.c_str())}; } Text ec_to_text(char c) { // Add quotes around the character to make it obvious where it begin and ends. std::string str = "'"; auto write_hex_digit = [&](unsigned num) { if (num < 10u) { str += char('0' + num); } else { str += char('a' + num - 10); } }; auto write_hex_16 = [&](uint16_t n) { write_hex_digit((n >> 12u) & 0x0f); write_hex_digit((n >> 8u) & 0x0f); write_hex_digit((n >> 4u) & 0x0f); write_hex_digit((n >> 0u) & 0x0f); }; if (c == '\\') { str += "\\\\"; } else if (c == '\"') { str += "\\\""; } else if (c == '\'') { str += "\\\'"; } else if (c == '\0') { str += "\\0"; } else if (c == '\b') { str += "\\b"; } else if (c == '\f') { str += "\\f"; } else if (c == '\n') { str += "\\n"; } else if (c == '\r') { str += "\\r"; } else if (c == '\t') { str += "\\t"; } else if (0 <= c && c < 0x20) { str += "\\u"; write_hex_16(static_cast(c)); } else { str += c; } str += "'"; return Text{STRDUP(str.c_str())}; } #define DEFINE_EC(Type) \ Text ec_to_text(Type value) \ { \ auto str = std::to_string(value); \ return Text{STRDUP(str.c_str())}; \ } DEFINE_EC(int) DEFINE_EC(unsigned int) DEFINE_EC(long) DEFINE_EC(unsigned long) DEFINE_EC(long long) DEFINE_EC(unsigned long long) DEFINE_EC(float) DEFINE_EC(double) DEFINE_EC(long double) #undef DEFINE_EC Text ec_to_text(EcHandle ec_handle) { Text parent_ec = get_error_context_for(ec_handle); size_t buffer_size = strlen(parent_ec.c_str()) + 2; char* with_newline = reinterpret_cast(malloc(buffer_size)); with_newline[0] = '\n'; #ifdef _WIN32 strncpy_s(with_newline + 1, buffer_size, parent_ec.c_str(), buffer_size - 2); #else strcpy(with_newline + 1, parent_ec.c_str()); #endif return Text(with_newline); } // ---------------------------------------------------------------------------- } // namespace loguru // ---------------------------------------------------------------------------- // .dP"Y8 88 dP""b8 88b 88 db 88 .dP"Y8 // `Ybo." 88 dP `" 88Yb88 dPYb 88 `Ybo." // o.`Y8b 88 Yb "88 88 Y88 dP__Yb 88 .o o.`Y8b // 8bodP' 88 YboodP 88 Y8 dP""""Yb 88ood8 8bodP' // ---------------------------------------------------------------------------- #ifdef _WIN32 namespace loguru { void install_signal_handlers(const SignalOptions& signal_options) { (void)signal_options; // TODO: implement signal handlers on windows } } // namespace loguru #else // _WIN32 namespace loguru { void write_to_stderr(const char* data, size_t size) { auto result = write(STDERR_FILENO, data, size); (void)result; // Ignore errors. } void write_to_stderr(const char* data) { write_to_stderr(data, strlen(data)); } void call_default_signal_handler(int signal_number) { struct sigaction sig_action; memset(&sig_action, 0, sizeof(sig_action)); sigemptyset(&sig_action.sa_mask); sig_action.sa_handler = SIG_DFL; sigaction(signal_number, &sig_action, NULL); kill(getpid(), signal_number); } void signal_handler(int signal_number, siginfo_t*, void*) { const char* signal_name = "UNKNOWN SIGNAL"; if (signal_number == SIGABRT) { signal_name = "SIGABRT"; } if (signal_number == SIGBUS) { signal_name = "SIGBUS"; } if (signal_number == SIGFPE) { signal_name = "SIGFPE"; } if (signal_number == SIGILL) { signal_name = "SIGILL"; } if (signal_number == SIGINT) { signal_name = "SIGINT"; } if (signal_number == SIGSEGV) { signal_name = "SIGSEGV"; } if (signal_number == SIGTERM) { signal_name = "SIGTERM"; } // -------------------------------------------------------------------- /* There are few things that are safe to do in a signal handler, but writing to stderr is one of them. So we first print out what happened to stderr so we're sure that gets out, then we do the unsafe things, like logging the stack trace. */ if (g_colorlogtostderr && s_terminal_has_color) { write_to_stderr(terminal_reset()); write_to_stderr(terminal_bold()); write_to_stderr(terminal_light_red()); } write_to_stderr("\n"); write_to_stderr("Loguru caught a signal: "); write_to_stderr(signal_name); write_to_stderr("\n"); if (g_colorlogtostderr && s_terminal_has_color) { write_to_stderr(terminal_reset()); } // -------------------------------------------------------------------- if (s_signal_options.unsafe_signal_handler) { // -------------------------------------------------------------------- /* Now we do unsafe things. This can for example lead to deadlocks if the signal was triggered from the system's memory management functions and the code below tries to do allocations. */ flush(); char preamble_buff[LOGURU_PREAMBLE_WIDTH]; print_preamble(preamble_buff, sizeof(preamble_buff), Verbosity_FATAL, "", 0); auto message = Message{Verbosity_FATAL, "", 0, preamble_buff, "", "Signal: ", signal_name}; try { log_message(1, message, false, false); } catch (...) { // This can happed due to s_fatal_handler. write_to_stderr("Exception caught and ignored by Loguru signal handler.\n"); } flush(); // -------------------------------------------------------------------- } call_default_signal_handler(signal_number); } void install_signal_handlers(const SignalOptions& signal_options) { s_signal_options = signal_options; struct sigaction sig_action; memset(&sig_action, 0, sizeof(sig_action)); sigemptyset(&sig_action.sa_mask); sig_action.sa_flags |= SA_SIGINFO; sig_action.sa_sigaction = &signal_handler; if (signal_options.sigabrt) { CHECK_F(sigaction(SIGABRT, &sig_action, NULL) != -1, "Failed to install handler for SIGABRT"); } if (signal_options.sigbus) { CHECK_F(sigaction(SIGBUS, &sig_action, NULL) != -1, "Failed to install handler for SIGBUS"); } if (signal_options.sigfpe) { CHECK_F(sigaction(SIGFPE, &sig_action, NULL) != -1, "Failed to install handler for SIGFPE"); } if (signal_options.sigill) { CHECK_F(sigaction(SIGILL, &sig_action, NULL) != -1, "Failed to install handler for SIGILL"); } if (signal_options.sigint) { CHECK_F(sigaction(SIGINT, &sig_action, NULL) != -1, "Failed to install handler for SIGINT"); } if (signal_options.sigsegv) { CHECK_F(sigaction(SIGSEGV, &sig_action, NULL) != -1, "Failed to install handler for SIGSEGV"); } if (signal_options.sigterm) { CHECK_F(sigaction(SIGTERM, &sig_action, NULL) != -1, "Failed to install handler for SIGTERM"); } } } // namespace loguru #endif // _WIN32 #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #elif defined(_MSC_VER) #pragma warning(pop) #endif LOGURU_ANONYMOUS_NAMESPACE_END #endif // LOGURU_IMPLEMENTATION liblsl-1.17.7/thirdparty/loguru/loguru.hpp000066400000000000000000001572441517625163100206510ustar00rootroot00000000000000/* Loguru logging library for C++, by Emil Ernerfeldt. www.github.com/emilk/loguru If you find Loguru useful, please let me know on twitter or in a mail! Twitter: @ernerfeldt Mail: emil.ernerfeldt@gmail.com Website: www.ilikebigbits.com # License This software is in the public domain. Where that dedication is not recognized, you are granted a perpetual, irrevocable license to copy, modify and distribute it as you see fit. # Inspiration Much of Loguru was inspired by GLOG, https://code.google.com/p/google-glog/. The choice of public domain is fully due Sean T. Barrett and his wonderful stb libraries at https://github.com/nothings/stb. # Version history * Version 0.1.0 - 2015-03-22 - Works great on Mac. * Version 0.2.0 - 2015-09-17 - Removed the only dependency. * Version 0.3.0 - 2015-10-02 - Drop-in replacement for most of GLOG * Version 0.4.0 - 2015-10-07 - Single-file! * Version 0.5.0 - 2015-10-17 - Improved file logging * Version 0.6.0 - 2015-10-24 - Add stack traces * Version 0.7.0 - 2015-10-27 - Signals * Version 0.8.0 - 2015-10-30 - Color logging. * Version 0.9.0 - 2015-11-26 - ABORT_S and proper handling of FATAL * Version 1.0.0 - 2016-02-14 - ERROR_CONTEXT * Version 1.1.0 - 2016-02-19 - -v OFF, -v INFO etc * Version 1.1.1 - 2016-02-20 - textprintf vs strprintf * Version 1.1.2 - 2016-02-22 - Remove g_alsologtostderr * Version 1.1.3 - 2016-02-29 - ERROR_CONTEXT as linked list * Version 1.2.0 - 2016-03-19 - Add get_thread_name() * Version 1.2.1 - 2016-03-20 - Minor fixes * Version 1.2.2 - 2016-03-29 - Fix issues with set_fatal_handler throwing an exception * Version 1.2.3 - 2016-05-16 - Log current working directory in loguru::init(). * Version 1.2.4 - 2016-05-18 - Custom replacement for -v in loguru::init() by bjoernpollex * Version 1.2.5 - 2016-05-18 - Add ability to print ERROR_CONTEXT of parent thread. * Version 1.2.6 - 2016-05-19 - Bug fix regarding VLOG verbosity argument lacking (). * Version 1.2.7 - 2016-05-23 - Fix PATH_MAX problem. * Version 1.2.8 - 2016-05-26 - Add shutdown() and remove_all_callbacks() * Version 1.2.9 - 2016-06-09 - Use a monotonic clock for uptime. * Version 1.3.0 - 2016-07-20 - Fix issues with callback flush/close not being called. * Version 1.3.1 - 2016-07-20 - Add LOGURU_UNSAFE_SIGNAL_HANDLER to toggle stacktrace on signals. * Version 1.3.2 - 2016-07-20 - Add loguru::arguments() * Version 1.4.0 - 2016-09-15 - Semantic versioning + add loguru::create_directories * Version 1.4.1 - 2016-09-29 - Customize formating with LOGURU_FILENAME_WIDTH * Version 1.5.0 - 2016-12-22 - LOGURU_USE_FMTLIB by kolis and LOGURU_WITH_FILEABS by scinart * Version 1.5.1 - 2017-08-08 - Terminal colors on Windows 10 thanks to looki * Version 1.6.0 - 2018-01-03 - Add LOGURU_RTTI and LOGURU_STACKTRACES settings * Version 1.7.0 - 2018-01-03 - Add ability to turn off the preamble with loguru::g_preamble * Version 1.7.1 - 2018-04-05 - Add function get_fatal_handler * Version 1.7.2 - 2018-04-22 - Fix a bug where large file names could cause stack corruption (thanks @ccamporesi) * Version 1.8.0 - 2018-04-23 - Shorten long file names to keep preamble fixed width * Version 1.9.0 - 2018-09-22 - Adjust terminal colors, add LOGURU_VERBOSE_SCOPE_ENDINGS, add LOGURU_SCOPE_TIME_PRECISION, add named log levels * Version 2.0.0 - 2018-09-22 - Split loguru.hpp into loguru.hpp and loguru.cpp * Version 2.1.0 - 2019-09-23 - Update fmtlib + add option to loguru::init to NOT set main thread name. * Version 2.2.0 - 2020-07-31 - Replace LOGURU_CATCH_SIGABRT with struct SignalOptions # Compiling Just include where you want to use Loguru. Then, in one .cpp file #include Make sure you compile with -std=c++11 -lstdc++ -lpthread -ldl # Usage For details, please see the official documentation at emilk.github.io/loguru #include int main(int argc, char* argv[]) { loguru::init(argc, argv); // Put every log message in "everything.log": loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX); LOG_F(INFO, "The magic number is %d", 42); } */ #if defined(LOGURU_IMPLEMENTATION) #error "You are defining LOGURU_IMPLEMENTATION. This is for older versions of Loguru. You should now instead include loguru.cpp (or build it and link with it)" #endif // Disable all warnings from gcc/clang: #if defined(__clang__) #pragma clang system_header #elif defined(__GNUC__) #pragma GCC system_header #endif #ifndef LOGURU_HAS_DECLARED_FORMAT_HEADER #define LOGURU_HAS_DECLARED_FORMAT_HEADER // Semantic versioning. Loguru version can be printed with printf("%d.%d.%d", LOGURU_VERSION_MAJOR, LOGURU_VERSION_MINOR, LOGURU_VERSION_PATCH); #define LOGURU_VERSION_MAJOR 2 #define LOGURU_VERSION_MINOR 1 #define LOGURU_VERSION_PATCH 0 #if defined(_MSC_VER) #include // Needed for _In_z_ etc annotations #endif #if defined(__linux__) || defined(__APPLE__) #define LOGURU_SYSLOG 1 #else #define LOGURU_SYSLOG 0 #endif // ---------------------------------------------------------------------------- #ifndef LOGURU_EXPORT // Define to your project's export declaration if needed for use in a shared library. #define LOGURU_EXPORT #endif #ifndef LOGURU_SCOPE_TEXT_SIZE // Maximum length of text that can be printed by a LOG_SCOPE. // This should be long enough to get most things, but short enough not to clutter the stack. #define LOGURU_SCOPE_TEXT_SIZE 196 #endif #ifndef LOGURU_FILENAME_WIDTH // Width of the column containing the file name #define LOGURU_FILENAME_WIDTH 23 #endif #ifndef LOGURU_THREADNAME_WIDTH // Width of the column containing the thread name #define LOGURU_THREADNAME_WIDTH 16 #endif #ifndef LOGURU_SCOPE_TIME_PRECISION // Resolution of scope timers. 3=ms, 6=us, 9=ns #define LOGURU_SCOPE_TIME_PRECISION 3 #endif #ifdef LOGURU_CATCH_SIGABRT #error "You are defining LOGURU_CATCH_SIGABRT. This is for older versions of Loguru. You should now instead set the options passed to loguru::init" #endif #ifndef LOGURU_VERBOSE_SCOPE_ENDINGS // Show milliseconds and scope name at end of scope. #define LOGURU_VERBOSE_SCOPE_ENDINGS 1 #endif #ifndef LOGURU_REDEFINE_ASSERT #define LOGURU_REDEFINE_ASSERT 0 #endif #ifndef LOGURU_WITH_STREAMS #define LOGURU_WITH_STREAMS 0 #endif #ifndef LOGURU_REPLACE_GLOG #define LOGURU_REPLACE_GLOG 0 #endif #if LOGURU_REPLACE_GLOG #undef LOGURU_WITH_STREAMS #define LOGURU_WITH_STREAMS 1 #endif #if defined(LOGURU_UNSAFE_SIGNAL_HANDLER) #error "You are defining LOGURU_UNSAFE_SIGNAL_HANDLER. This is for older versions of Loguru. You should now instead set the unsafe_signal_handler option when you call loguru::init." #endif #if LOGURU_IMPLEMENTATION #undef LOGURU_WITH_STREAMS #define LOGURU_WITH_STREAMS 1 #endif #ifndef LOGURU_USE_FMTLIB #define LOGURU_USE_FMTLIB 0 #endif #ifndef LOGURU_USE_LOCALE #define LOGURU_USE_LOCALE 0 #endif #ifndef LOGURU_WITH_FILEABS #define LOGURU_WITH_FILEABS 0 #endif #ifndef LOGURU_RTTI #if defined(__clang__) #if __has_feature(cxx_rtti) #define LOGURU_RTTI 1 #endif #elif defined(__GNUG__) #if defined(__GXX_RTTI) #define LOGURU_RTTI 1 #endif #elif defined(_MSC_VER) #if defined(_CPPRTTI) #define LOGURU_RTTI 1 #endif #endif #endif #ifdef LOGURU_USE_ANONYMOUS_NAMESPACE #define LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace { #define LOGURU_ANONYMOUS_NAMESPACE_END } #else #define LOGURU_ANONYMOUS_NAMESPACE_BEGIN #define LOGURU_ANONYMOUS_NAMESPACE_END #endif // -------------------------------------------------------------------- // Utility macros #define LOGURU_CONCATENATE_IMPL(s1, s2) s1 ## s2 #define LOGURU_CONCATENATE(s1, s2) LOGURU_CONCATENATE_IMPL(s1, s2) #ifdef __COUNTER__ # define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __COUNTER__) #else # define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __LINE__) #endif #if defined(__clang__) || defined(__GNUC__) // Helper macro for declaring functions as having similar signature to printf. // This allows the compiler to catch format errors at compile-time. #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) __attribute__((__format__ (__printf__, fmtarg, firstvararg))) #define LOGURU_FORMAT_STRING_TYPE const char* #elif defined(_MSC_VER) #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) #define LOGURU_FORMAT_STRING_TYPE _In_z_ _Printf_format_string_ const char* #else #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) #define LOGURU_FORMAT_STRING_TYPE const char* #endif // Used to mark log_and_abort for the benefit of the static analyzer and optimizer. #if defined(_MSC_VER) #define LOGURU_NORETURN __declspec(noreturn) #else #define LOGURU_NORETURN __attribute__((noreturn)) #endif #if defined(_MSC_VER) #define LOGURU_PREDICT_FALSE(x) (x) #define LOGURU_PREDICT_TRUE(x) (x) #else #define LOGURU_PREDICT_FALSE(x) (__builtin_expect(x, 0)) #define LOGURU_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) #endif #if LOGURU_USE_FMTLIB #include #define LOGURU_FMT(x) "{:" #x "}" #else #define LOGURU_FMT(x) "%" #x #endif #ifdef _WIN32 #define STRDUP(str) _strdup(str) #else #define STRDUP(str) strdup(str) #endif #include // -------------------------------------------------------------------- LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace loguru { // Simple RAII ownership of a char*. class LOGURU_EXPORT Text { public: explicit Text(char* owned_str) : _str(owned_str) {} ~Text(); Text(Text&& t) { _str = t._str; t._str = nullptr; } Text(Text& t) = delete; Text& operator=(Text& t) = delete; void operator=(Text&& t) = delete; const char* c_str() const { return _str; } bool empty() const { return _str == nullptr || *_str == '\0'; } char* release() { auto result = _str; _str = nullptr; return result; } private: char* _str; }; // Like printf, but returns the formated text. #if LOGURU_USE_FMTLIB LOGURU_EXPORT Text vtextprintf(const char* format, fmt::format_args args); template LOGURU_EXPORT Text textprintf(LOGURU_FORMAT_STRING_TYPE format, const Args&... args) { return vtextprintf(format, fmt::make_format_args(args...)); } #else LOGURU_EXPORT Text textprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); #endif // Overloaded for variadic template matching. LOGURU_EXPORT Text textprintf(); using Verbosity = int; #undef FATAL #undef ERROR #undef WARNING #undef INFO #undef MAX enum NamedVerbosity : Verbosity { // Used to mark an invalid verbosity. Do not log to this level. Verbosity_INVALID = -10, // Never do LOG_F(INVALID) // You may use Verbosity_OFF on g_stderr_verbosity, but for nothing else! Verbosity_OFF = -9, // Never do LOG_F(OFF) // Prefer to use ABORT_F or ABORT_S over LOG_F(FATAL) or LOG_S(FATAL). Verbosity_FATAL = -3, Verbosity_ERROR = -2, Verbosity_WARNING = -1, // Normal messages. By default written to stderr. Verbosity_INFO = 0, // Same as Verbosity_INFO in every way. Verbosity_0 = 0, // Verbosity levels 1-9 are generally not written to stderr, but are written to file. Verbosity_1 = +1, Verbosity_2 = +2, Verbosity_3 = +3, Verbosity_4 = +4, Verbosity_5 = +5, Verbosity_6 = +6, Verbosity_7 = +7, Verbosity_8 = +8, Verbosity_9 = +9, // Do not use higher verbosity levels, as that will make grepping log files harder. Verbosity_MAX = +9, }; struct Message { // You would generally print a Message by just concatenating the buffers without spacing. // Optionally, ignore preamble and indentation. Verbosity verbosity; // Already part of preamble const char* filename; // Already part of preamble unsigned line; // Already part of preamble const char* preamble; // Date, time, uptime, thread, file:line, verbosity. const char* indentation; // Just a bunch of spacing. const char* prefix; // Assertion failure info goes here (or ""). const char* message; // User message goes here. }; /* Everything with a verbosity equal or greater than g_stderr_verbosity will be written to stderr. You can set this in code or via the -v argument. Set to loguru::Verbosity_OFF to write nothing to stderr. Default is 0, i.e. only log ERROR, WARNING and INFO are written to stderr. */ LOGURU_EXPORT extern Verbosity g_stderr_verbosity; LOGURU_EXPORT extern bool g_colorlogtostderr; // True by default. LOGURU_EXPORT extern unsigned g_flush_interval_ms; // 0 (unbuffered) by default. LOGURU_EXPORT extern bool g_preamble_header; // Prepend each log start by a descriptions line with all columns name? True by default. LOGURU_EXPORT extern bool g_preamble; // Prefix each log line with date, time etc? True by default. /* Specify the verbosity used by loguru to log its info messages including the header logged when logged::init() is called or on exit. Default is 0 (INFO). */ LOGURU_EXPORT extern Verbosity g_internal_verbosity; // Turn off individual parts of the preamble LOGURU_EXPORT extern bool g_preamble_date; // The date field LOGURU_EXPORT extern bool g_preamble_time; // The time of the current day LOGURU_EXPORT extern bool g_preamble_uptime; // The time since init call LOGURU_EXPORT extern bool g_preamble_thread; // The logging thread LOGURU_EXPORT extern bool g_preamble_file; // The file from which the log originates from LOGURU_EXPORT extern bool g_preamble_verbose; // The verbosity field LOGURU_EXPORT extern bool g_preamble_pipe; // The pipe symbol right before the message // May not throw! typedef void (*log_handler_t)(void* user_data, const Message& message); typedef void (*close_handler_t)(void* user_data); typedef void (*flush_handler_t)(void* user_data); // May throw if that's how you'd like to handle your errors. typedef void (*fatal_handler_t)(const Message& message); // Given a verbosity level, return the level's name or nullptr. typedef const char* (*verbosity_to_name_t)(Verbosity verbosity); // Given a verbosity level name, return the verbosity level or // Verbosity_INVALID if name is not recognized. typedef Verbosity (*name_to_verbosity_t)(const char* name); struct SignalOptions { /// Make Loguru try to do unsafe but useful things, /// like printing a stack trace, when catching signals. /// This may lead to bad things like deadlocks in certain situations. bool unsafe_signal_handler = true; /// Should Loguru catch SIGABRT ? bool sigabrt = true; /// Should Loguru catch SIGBUS ? bool sigbus = true; /// Should Loguru catch SIGFPE ? bool sigfpe = true; /// Should Loguru catch SIGILL ? bool sigill = true; /// Should Loguru catch SIGINT ? bool sigint = true; /// Should Loguru catch SIGSEGV ? bool sigsegv = true; /// Should Loguru catch SIGTERM ? bool sigterm = true; static SignalOptions none() { SignalOptions options; options.unsafe_signal_handler = false; options.sigabrt = false; options.sigbus = false; options.sigfpe = false; options.sigill = false; options.sigint = false; options.sigsegv = false; options.sigterm = false; return options; } }; // Runtime options passed to loguru::init struct Options { // This allows you to use something else instead of "-v" via verbosity_flag. // Set to nullptr if you don't want Loguru to parse verbosity from the args. const char* verbosity_flag = "-v"; // loguru::init will set the name of the calling thread to this. // If you don't want Loguru to set the name of the main thread, // set this to nullptr. // NOTE: on SOME platforms loguru::init will only overwrite the thread name // if a thread name has not already been set. // To always set a thread name, use loguru::set_thread_name instead. const char* main_thread_name = "main thread"; SignalOptions signal_options; }; /* Should be called from the main thread. You don't *need* to call this, but if you do you get: * Signal handlers installed * Program arguments logged * Working dir logged * Optional -v verbosity flag parsed * Main thread name set to "main thread" * Explanation of the preamble (date, thread name, etc) logged loguru::init() will look for arguments meant for loguru and remove them. Arguments meant for loguru are: -v n Set loguru::g_stderr_verbosity level. Examples: -v 3 Show verbosity level 3 and lower. -v 0 Only show INFO, WARNING, ERROR, FATAL (default). -v INFO Only show INFO, WARNING, ERROR, FATAL (default). -v WARNING Only show WARNING, ERROR, FATAL. -v ERROR Only show ERROR, FATAL. -v FATAL Only show FATAL. -v OFF Turn off logging to stderr. Tip: You can set g_stderr_verbosity before calling loguru::init. That way you can set the default but have the user override it with the -v flag. Note that -v does not affect file logging (see loguru::add_file). You can you something other than the -v flag by setting the verbosity_flag option. */ LOGURU_EXPORT void init(int& argc, char* argv[], const Options& options = {}); // Will call remove_all_callbacks(). After calling this, logging will still go to stderr. // You generally don't need to call this. LOGURU_EXPORT void shutdown(); // What ~ will be replaced with, e.g. "/home/your_user_name/" LOGURU_EXPORT const char* home_dir(); /* Returns the name of the app as given in argv[0] but without leading path. That is, if argv[0] is "../foo/app" this will return "app". */ LOGURU_EXPORT const char* argv0_filename(); // Returns all arguments given to loguru::init(), but escaped with a single space as separator. LOGURU_EXPORT const char* arguments(); // Returns the path to the current working dir when loguru::init() was called. LOGURU_EXPORT const char* current_dir(); // Returns the part of the path after the last / or \ (if any). LOGURU_EXPORT const char* filename(const char* path); // e.g. "foo/bar/baz.ext" will create the directories "foo/" and "foo/bar/" LOGURU_EXPORT bool create_directories(const char* file_path_const); // Writes date and time with millisecond precision, e.g. "20151017_161503.123" LOGURU_EXPORT void write_date_time(char* buff, unsigned long long buff_size); // Helper: thread-safe version strerror LOGURU_EXPORT Text errno_as_text(); /* Given a prefix of e.g. "~/loguru/" this might return "/home/your_username/loguru/app_name/20151017_161503.123.log" where "app_name" is a sanitized version of argv[0]. */ LOGURU_EXPORT void suggest_log_path(const char* prefix, char* buff, unsigned long long buff_size); enum FileMode { Truncate, Append }; /* Will log to a file at the given path. Any logging message with a verbosity lower or equal to the given verbosity will be included. The function will create all directories in 'path' if needed. If path starts with a ~, it will be replaced with loguru::home_dir() To stop the file logging, just call loguru::remove_callback(path) with the same path. */ LOGURU_EXPORT bool add_file(const char* path, FileMode mode, Verbosity verbosity); LOGURU_EXPORT // Send logs to syslog with LOG_USER facility (see next call) bool add_syslog(const char* app_name, Verbosity verbosity); LOGURU_EXPORT // Send logs to syslog with your own choice of facility (LOG_USER, LOG_AUTH, ...) // see loguru.cpp: syslog_log() for more details. bool add_syslog(const char* app_name, Verbosity verbosity, int facility); /* Will be called right before abort(). You can for instance use this to print custom error messages, or throw an exception. Feel free to call LOG:ing function from this, but not FATAL ones! */ LOGURU_EXPORT void set_fatal_handler(fatal_handler_t handler); // Get the current fatal handler, if any. Default value is nullptr. LOGURU_EXPORT fatal_handler_t get_fatal_handler(); /* Will be called on each log messages with a verbosity less or equal to the given one. Useful for displaying messages on-screen in a game, for example. The given on_close is also expected to flush (if desired). */ LOGURU_EXPORT void add_callback( const char* id, log_handler_t callback, void* user_data, Verbosity verbosity, close_handler_t on_close = nullptr, flush_handler_t on_flush = nullptr); /* Set a callback that returns custom verbosity level names. If callback is nullptr or returns nullptr, default log names will be used. */ LOGURU_EXPORT void set_verbosity_to_name_callback(verbosity_to_name_t callback); /* Set a callback that returns the verbosity level matching a name. The callback should return Verbosity_INVALID if the name is not recognized. */ LOGURU_EXPORT void set_name_to_verbosity_callback(name_to_verbosity_t callback); /* Get a custom name for a specific verbosity, if one exists, or nullptr. */ LOGURU_EXPORT const char* get_verbosity_name(Verbosity verbosity); /* Get the verbosity enum value from a custom 4-character level name, if one exists. If the name does not match a custom level name, Verbosity_INVALID is returned. */ LOGURU_EXPORT Verbosity get_verbosity_from_name(const char* name); // Returns true iff the callback was found (and removed). LOGURU_EXPORT bool remove_callback(const char* id); // Shut down all file logging and any other callback hooks installed. LOGURU_EXPORT void remove_all_callbacks(); // Returns the maximum of g_stderr_verbosity and all file/custom outputs. LOGURU_EXPORT Verbosity current_verbosity_cutoff(); #if LOGURU_USE_FMTLIB // Internal functions LOGURU_EXPORT void vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args); LOGURU_EXPORT void raw_vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args); // Actual logging function. Use the LOG macro instead of calling this directly. template LOGURU_EXPORT void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) { vlog(verbosity, file, line, format, fmt::make_format_args(args...)); } // Log without any preamble or indentation. template LOGURU_EXPORT void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) { raw_vlog(verbosity, file, line, format, fmt::make_format_args(args...)); } #else // LOGURU_USE_FMTLIB? // Actual logging function. Use the LOG macro instead of calling this directly. LOGURU_EXPORT void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); // Actual logging function. LOGURU_EXPORT void vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(4, 0); // Log without any preamble or indentation. LOGURU_EXPORT void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); #endif // !LOGURU_USE_FMTLIB // Helper class for LOG_SCOPE_F class LOGURU_EXPORT LogScopeRAII { public: LogScopeRAII() : _file(nullptr) {} // No logging LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, va_list vlist) LOGURU_PRINTF_LIKE(5, 0); LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); ~LogScopeRAII(); void Init(LOGURU_FORMAT_STRING_TYPE format, va_list vlist) LOGURU_PRINTF_LIKE(2, 0); #if defined(_MSC_VER) && _MSC_VER > 1800 // older MSVC default move ctors close the scope on move. See // issue #43 LogScopeRAII(LogScopeRAII&& other) : _verbosity(other._verbosity) , _file(other._file) , _line(other._line) , _indent_stderr(other._indent_stderr) , _start_time_ns(other._start_time_ns) { // Make sure the tmp object's destruction doesn't close the scope: other._file = nullptr; for (unsigned int i = 0; i < LOGURU_SCOPE_TEXT_SIZE; ++i) { _name[i] = other._name[i]; } } #else LogScopeRAII(LogScopeRAII&&) = default; #endif private: LogScopeRAII(const LogScopeRAII&) = delete; LogScopeRAII& operator=(const LogScopeRAII&) = delete; void operator=(LogScopeRAII&&) = delete; Verbosity _verbosity; const char* _file; // Set to null if we are disabled due to verbosity unsigned _line; bool _indent_stderr; // Did we? long long _start_time_ns; char _name[LOGURU_SCOPE_TEXT_SIZE]; }; // Marked as 'noreturn' for the benefit of the static analyzer and optimizer. // stack_trace_skip is the number of extrace stack frames to skip above log_and_abort. #if LOGURU_USE_FMTLIB LOGURU_EXPORT LOGURU_NORETURN void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args); template LOGURU_EXPORT LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args&... args) { vlog_and_abort(stack_trace_skip, expr, file, line, format, fmt::make_format_args(args...)); } #else LOGURU_EXPORT LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); #endif LOGURU_EXPORT LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line); // Flush output to stderr and files. // If g_flush_interval_ms is set to non-zero, this will be called automatically this often. // If not set, you do not need to call this at all. LOGURU_EXPORT void flush(); template inline Text format_value(const T&) { return textprintf("N/A"); } template<> inline Text format_value(const char& v) { return textprintf(LOGURU_FMT(c), v); } template<> inline Text format_value(const int& v) { return textprintf(LOGURU_FMT(d), v); } template<> inline Text format_value(const float& v) { return textprintf(LOGURU_FMT(f), v); } template<> inline Text format_value(const double& v) { return textprintf(LOGURU_FMT(f), v); } #if LOGURU_USE_FMTLIB template<> inline Text format_value(const unsigned int& v) { return textprintf(LOGURU_FMT(d), v); } template<> inline Text format_value(const long& v) { return textprintf(LOGURU_FMT(d), v); } template<> inline Text format_value(const unsigned long& v) { return textprintf(LOGURU_FMT(d), v); } template<> inline Text format_value(const long long& v) { return textprintf(LOGURU_FMT(d), v); } template<> inline Text format_value(const unsigned long long& v) { return textprintf(LOGURU_FMT(d), v); } #else template<> inline Text format_value(const unsigned int& v) { return textprintf(LOGURU_FMT(u), v); } template<> inline Text format_value(const long& v) { return textprintf(LOGURU_FMT(lu), v); } template<> inline Text format_value(const unsigned long& v) { return textprintf(LOGURU_FMT(ld), v); } template<> inline Text format_value(const long long& v) { return textprintf(LOGURU_FMT(llu), v); } template<> inline Text format_value(const unsigned long long& v) { return textprintf(LOGURU_FMT(lld), v); } #endif /* Thread names can be set for the benefit of readable logs. If you do not set the thread name, a hex id will be shown instead. These thread names may or may not be the same as the system thread names, depending on the system. Try to limit the thread name to 15 characters or less. */ LOGURU_EXPORT void set_thread_name(const char* name); /* Returns the thread name for this thread. On most *nix systems this will return the system thread name (settable from both within and without Loguru). On other systems it will return whatever you set in `set_thread_name()`; If no thread name is set, this will return a hexadecimal thread id. `length` should be the number of bytes available in the buffer. 17 is a good number for length. `right_align_hex_id` means any hexadecimal thread id will be written to the end of buffer. */ LOGURU_EXPORT void get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id); /* Generates a readable stacktrace as a string. 'skip' specifies how many stack frames to skip. For instance, the default skip (1) means: don't include the call to loguru::stacktrace in the stack trace. */ LOGURU_EXPORT Text stacktrace(int skip = 1); /* Add a string to be replaced with something else in the stack output. For instance, instead of having a stack trace look like this: 0x41f541 some_function(std::basic_ofstream >&) You can clean it up with: auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name()); loguru::add_stack_cleanup(verbose_type_name.c_str(); "std::ofstream"); So the next time you will instead see: 0x41f541 some_function(std::ofstream&) `replace_with_this` must be shorter than `find_this`. */ LOGURU_EXPORT void add_stack_cleanup(const char* find_this, const char* replace_with_this); // Example: demangle(typeid(std::ofstream).name()) -> "std::basic_ofstream >" LOGURU_EXPORT Text demangle(const char* name); // ------------------------------------------------------------------------ /* Not all terminals support colors, but if they do, and g_colorlogtostderr is set, Loguru will write them to stderr to make errors in red, etc. You also have the option to manually use them, via the function below. Note, however, that if you do, the color codes could end up in your logfile! This means if you intend to use them functions you should either: * Use them on the stderr/stdout directly (bypass Loguru). * Don't add file outputs to Loguru. * Expect some \e[1m things in your logfile. Usage: printf("%sRed%sGreen%sBold green%sClear again\n", loguru::terminal_red(), loguru::terminal_green(), loguru::terminal_bold(), loguru::terminal_reset()); If the terminal at hand does not support colors the above output will just not have funky \e[1m things showing. */ // Do the output terminal support colors? LOGURU_EXPORT bool terminal_has_color(); // Colors LOGURU_EXPORT const char* terminal_black(); LOGURU_EXPORT const char* terminal_red(); LOGURU_EXPORT const char* terminal_green(); LOGURU_EXPORT const char* terminal_yellow(); LOGURU_EXPORT const char* terminal_blue(); LOGURU_EXPORT const char* terminal_purple(); LOGURU_EXPORT const char* terminal_cyan(); LOGURU_EXPORT const char* terminal_light_gray(); LOGURU_EXPORT const char* terminal_light_red(); LOGURU_EXPORT const char* terminal_white(); // Formating LOGURU_EXPORT const char* terminal_bold(); LOGURU_EXPORT const char* terminal_underline(); // You should end each line with this! LOGURU_EXPORT const char* terminal_reset(); // -------------------------------------------------------------------- // Error context related: struct StringStream; // Use this in your EcEntryBase::print_value overload. LOGURU_EXPORT void stream_print(StringStream& out_string_stream, const char* text); class LOGURU_EXPORT EcEntryBase { public: EcEntryBase(const char* file, unsigned line, const char* descr); ~EcEntryBase(); EcEntryBase(const EcEntryBase&) = delete; EcEntryBase(EcEntryBase&&) = delete; EcEntryBase& operator=(const EcEntryBase&) = delete; EcEntryBase& operator=(EcEntryBase&&) = delete; virtual void print_value(StringStream& out_string_stream) const = 0; EcEntryBase* previous() const { return _previous; } // private: const char* _file; unsigned _line; const char* _descr; EcEntryBase* _previous; }; template class EcEntryData : public EcEntryBase { public: using Printer = Text(*)(T data); EcEntryData(const char* file, unsigned line, const char* descr, T data, Printer&& printer) : EcEntryBase(file, line, descr), _data(data), _printer(printer) {} virtual void print_value(StringStream& out_string_stream) const override { const auto str = _printer(_data); stream_print(out_string_stream, str.c_str()); } private: T _data; Printer _printer; }; // template // class EcEntryLambda : public EcEntryBase // { // public: // EcEntryLambda(const char* file, unsigned line, const char* descr, Printer&& printer) // : EcEntryBase(file, line, descr), _printer(std::move(printer)) {} // virtual void print_value(StringStream& out_string_stream) const override // { // const auto str = _printer(); // stream_print(out_string_stream, str.c_str()); // } // private: // Printer _printer; // }; // template // EcEntryLambda make_ec_entry_lambda(const char* file, unsigned line, const char* descr, Printer&& printer) // { // return {file, line, descr, std::move(printer)}; // } template struct decay_char_array { using type = T; }; template struct decay_char_array { using type = const char*; }; template struct make_const_ptr { using type = T; }; template struct make_const_ptr { using type = const T*; }; template struct make_ec_type { using type = typename make_const_ptr::type>::type; }; /* A stack trace gives you the names of the function at the point of a crash. With ERROR_CONTEXT, you can also get the values of select local variables. Usage: void process_customers(const std::string& filename) { ERROR_CONTEXT("Processing file", filename.c_str()); for (int customer_index : ...) { ERROR_CONTEXT("Customer index", customer_index); ... } } The context is in effect during the scope of the ERROR_CONTEXT. Use loguru::get_error_context() to get the contents of the active error contexts. Example result: ------------------------------------------------ [ErrorContext] main.cpp:416 Processing file: "customers.json" [ErrorContext] main.cpp:417 Customer index: 42 ------------------------------------------------ Error contexts are printed automatically on crashes, and only on crashes. This makes them much faster than logging the value of a variable. */ #define ERROR_CONTEXT(descr, data) \ const loguru::EcEntryData::type> \ LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ __FILE__, __LINE__, descr, data, \ static_cast::type>::Printer>(loguru::ec_to_text) ) // For better error messages /* #define ERROR_CONTEXT(descr, data) \ const auto LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ loguru::make_ec_entry_lambda(__FILE__, __LINE__, descr, \ [=](){ return loguru::ec_to_text(data); })) */ using EcHandle = const EcEntryBase*; /* Get a light-weight handle to the error context stack on this thread. The handle is valid as long as the current thread has no changes to its error context stack. You can pass the handle to loguru::get_error_context on another thread. This can be very useful for when you have a parent thread spawning several working threads, and you want the error context of the parent thread to get printed (too) when there is an error on the child thread. You can accomplish this thusly: void foo(const char* parameter) { ERROR_CONTEXT("parameter", parameter) const auto parent_ec_handle = loguru::get_thread_ec_handle(); std::thread([=]{ loguru::set_thread_name("child thread"); ERROR_CONTEXT("parent context", parent_ec_handle); dangerous_code(); }.join(); } */ LOGURU_EXPORT EcHandle get_thread_ec_handle(); // Get a string describing the current stack of error context. Empty string if there is none. LOGURU_EXPORT Text get_error_context(); // Get a string describing the error context of the given thread handle. LOGURU_EXPORT Text get_error_context_for(EcHandle ec_handle); // ------------------------------------------------------------------------ LOGURU_EXPORT Text ec_to_text(const char* data); LOGURU_EXPORT Text ec_to_text(char data); LOGURU_EXPORT Text ec_to_text(int data); LOGURU_EXPORT Text ec_to_text(unsigned int data); LOGURU_EXPORT Text ec_to_text(long data); LOGURU_EXPORT Text ec_to_text(unsigned long data); LOGURU_EXPORT Text ec_to_text(long long data); LOGURU_EXPORT Text ec_to_text(unsigned long long data); LOGURU_EXPORT Text ec_to_text(float data); LOGURU_EXPORT Text ec_to_text(double data); LOGURU_EXPORT Text ec_to_text(long double data); LOGURU_EXPORT Text ec_to_text(EcHandle); /* You can add ERROR_CONTEXT support for your own types by overloading ec_to_text. Here's how: some.hpp: namespace loguru { Text ec_to_text(MySmallType data) Text ec_to_text(const MyBigType* data) } // namespace loguru some.cpp: namespace loguru { Text ec_to_text(MySmallType small_value) { // Called only when needed, i.e. on a crash. std::string str = small_value.as_string(); // Format 'small_value' here somehow. return Text{STRDUP(str.c_str())}; } Text ec_to_text(const MyBigType* big_value) { // Called only when needed, i.e. on a crash. std::string str = big_value->as_string(); // Format 'big_value' here somehow. return Text{STRDUP(str.c_str())}; } } // namespace loguru Any file that include some.hpp: void foo(MySmallType small, const MyBigType& big) { ERROR_CONTEXT("Small", small); // Copy ´small` by value. ERROR_CONTEXT("Big", &big); // `big` should not change during this scope! .... } */ } // namespace loguru LOGURU_ANONYMOUS_NAMESPACE_END // -------------------------------------------------------------------- // Logging macros // LOG_F(2, "Only logged if verbosity is 2 or higher: %d", some_number); #define VLOG_F(verbosity, ...) \ ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) // LOG_F(INFO, "Foo: %d", some_number); #define LOG_F(verbosity_name, ...) VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) #define VLOG_IF_F(verbosity, cond, ...) \ ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ ? (void)0 \ : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) #define LOG_IF_F(verbosity_name, cond, ...) \ VLOG_IF_F(loguru::Verbosity_ ## verbosity_name, cond, __VA_ARGS__) #define VLOG_SCOPE_F(verbosity, ...) \ loguru::LogScopeRAII LOGURU_ANONYMOUS_VARIABLE(error_context_RAII_) = \ ((verbosity) > loguru::current_verbosity_cutoff()) ? loguru::LogScopeRAII() : \ loguru::LogScopeRAII(verbosity, __FILE__, __LINE__, __VA_ARGS__) // Raw logging - no preamble, no indentation. Slightly faster than full logging. #define RAW_VLOG_F(verbosity, ...) \ ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ : loguru::raw_log(verbosity, __FILE__, __LINE__, __VA_ARGS__) #define RAW_LOG_F(verbosity_name, ...) RAW_VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) // Use to book-end a scope. Affects logging on all threads. #define LOG_SCOPE_F(verbosity_name, ...) \ VLOG_SCOPE_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) #define LOG_SCOPE_FUNCTION(verbosity_name) LOG_SCOPE_F(verbosity_name, __func__) // ----------------------------------------------- // ABORT_F macro. Usage: ABORT_F("Cause of error: %s", error_str); // Message is optional #define ABORT_F(...) loguru::log_and_abort(0, "ABORT: ", __FILE__, __LINE__, __VA_ARGS__) // -------------------------------------------------------------------- // CHECK_F macros: #define CHECK_WITH_INFO_F(test, info, ...) \ LOGURU_PREDICT_TRUE((test) == true) ? (void)0 : loguru::log_and_abort(0, "CHECK FAILED: " info " ", __FILE__, \ __LINE__, ##__VA_ARGS__) /* Checked at runtime too. Will print error, then call fatal_handler (if any), then 'abort'. Note that the test must be boolean. CHECK_F(ptr); will not compile, but CHECK_F(ptr != nullptr); will. */ #define CHECK_F(test, ...) CHECK_WITH_INFO_F(test, #test, ##__VA_ARGS__) #define CHECK_NOTNULL_F(x, ...) CHECK_WITH_INFO_F((x) != nullptr, #x " != nullptr", ##__VA_ARGS__) #define CHECK_OP_F(expr_left, expr_right, op, ...) \ do \ { \ auto val_left = expr_left; \ auto val_right = expr_right; \ if (! LOGURU_PREDICT_TRUE(val_left op val_right)) \ { \ auto str_left = loguru::format_value(val_left); \ auto str_right = loguru::format_value(val_right); \ auto fail_info = loguru::textprintf("CHECK FAILED: " LOGURU_FMT(s) " " LOGURU_FMT(s) " " LOGURU_FMT(s) " (" LOGURU_FMT(s) " " LOGURU_FMT(s) " " LOGURU_FMT(s) ") ", \ #expr_left, #op, #expr_right, str_left.c_str(), #op, str_right.c_str()); \ auto user_msg = loguru::textprintf(__VA_ARGS__); \ loguru::log_and_abort(0, fail_info.c_str(), __FILE__, __LINE__, \ LOGURU_FMT(s), user_msg.c_str()); \ } \ } while (false) #ifndef LOGURU_DEBUG_LOGGING #ifndef NDEBUG #define LOGURU_DEBUG_LOGGING 1 #else #define LOGURU_DEBUG_LOGGING 0 #endif #endif #if LOGURU_DEBUG_LOGGING // Debug logging enabled: #define DLOG_F(verbosity_name, ...) LOG_F(verbosity_name, __VA_ARGS__) #define DVLOG_F(verbosity, ...) VLOG_F(verbosity, __VA_ARGS__) #define DLOG_IF_F(verbosity_name, ...) LOG_IF_F(verbosity_name, __VA_ARGS__) #define DVLOG_IF_F(verbosity, ...) VLOG_IF_F(verbosity, __VA_ARGS__) #define DRAW_LOG_F(verbosity_name, ...) RAW_LOG_F(verbosity_name, __VA_ARGS__) #define DRAW_VLOG_F(verbosity, ...) RAW_VLOG_F(verbosity, __VA_ARGS__) #else // Debug logging disabled: #define DLOG_F(verbosity_name, ...) #define DVLOG_F(verbosity, ...) #define DLOG_IF_F(verbosity_name, ...) #define DVLOG_IF_F(verbosity, ...) #define DRAW_LOG_F(verbosity_name, ...) #define DRAW_VLOG_F(verbosity, ...) #endif #define CHECK_EQ_F(a, b, ...) CHECK_OP_F(a, b, ==, ##__VA_ARGS__) #define CHECK_NE_F(a, b, ...) CHECK_OP_F(a, b, !=, ##__VA_ARGS__) #define CHECK_LT_F(a, b, ...) CHECK_OP_F(a, b, < , ##__VA_ARGS__) #define CHECK_GT_F(a, b, ...) CHECK_OP_F(a, b, > , ##__VA_ARGS__) #define CHECK_LE_F(a, b, ...) CHECK_OP_F(a, b, <=, ##__VA_ARGS__) #define CHECK_GE_F(a, b, ...) CHECK_OP_F(a, b, >=, ##__VA_ARGS__) #ifndef LOGURU_DEBUG_CHECKS #ifndef NDEBUG #define LOGURU_DEBUG_CHECKS 1 #else #define LOGURU_DEBUG_CHECKS 0 #endif #endif #if LOGURU_DEBUG_CHECKS // Debug checks enabled: #define DCHECK_F(test, ...) CHECK_F(test, ##__VA_ARGS__) #define DCHECK_NOTNULL_F(x, ...) CHECK_NOTNULL_F(x, ##__VA_ARGS__) #define DCHECK_EQ_F(a, b, ...) CHECK_EQ_F(a, b, ##__VA_ARGS__) #define DCHECK_NE_F(a, b, ...) CHECK_NE_F(a, b, ##__VA_ARGS__) #define DCHECK_LT_F(a, b, ...) CHECK_LT_F(a, b, ##__VA_ARGS__) #define DCHECK_LE_F(a, b, ...) CHECK_LE_F(a, b, ##__VA_ARGS__) #define DCHECK_GT_F(a, b, ...) CHECK_GT_F(a, b, ##__VA_ARGS__) #define DCHECK_GE_F(a, b, ...) CHECK_GE_F(a, b, ##__VA_ARGS__) #else // Debug checks disabled: #define DCHECK_F(test, ...) #define DCHECK_NOTNULL_F(x, ...) #define DCHECK_EQ_F(a, b, ...) #define DCHECK_NE_F(a, b, ...) #define DCHECK_LT_F(a, b, ...) #define DCHECK_LE_F(a, b, ...) #define DCHECK_GT_F(a, b, ...) #define DCHECK_GE_F(a, b, ...) #endif // NDEBUG #if LOGURU_REDEFINE_ASSERT #undef assert #ifndef NDEBUG // Debug: #define assert(test) CHECK_WITH_INFO_F(!!(test), #test) // HACK #else #define assert(test) #endif #endif // LOGURU_REDEFINE_ASSERT #endif // LOGURU_HAS_DECLARED_FORMAT_HEADER // ---------------------------------------------------------------------------- // .dP"Y8 888888 88""Yb 888888 db 8b d8 .dP"Y8 // `Ybo." 88 88__dP 88__ dPYb 88b d88 `Ybo." // o.`Y8b 88 88"Yb 88"" dP__Yb 88YbdP88 o.`Y8b // 8bodP' 88 88 Yb 888888 dP""""Yb 88 YY 88 8bodP' #if LOGURU_WITH_STREAMS #ifndef LOGURU_HAS_DECLARED_STREAMS_HEADER #define LOGURU_HAS_DECLARED_STREAMS_HEADER /* This file extends loguru to enable std::stream-style logging, a la Glog. It's an optional feature behind the LOGURU_WITH_STREAMS settings because including it everywhere will slow down compilation times. */ #include #include // Adds about 38 kLoC on clang. #include LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace loguru { // Like sprintf, but returns the formated text. LOGURU_EXPORT std::string strprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); // Like vsprintf, but returns the formated text. LOGURU_EXPORT std::string vstrprintf(LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(1, 0); class LOGURU_EXPORT StreamLogger { public: StreamLogger(Verbosity verbosity, const char* file, unsigned line) : _verbosity(verbosity), _file(file), _line(line) {} ~StreamLogger() noexcept(false); template StreamLogger& operator<<(const T& t) { _ss << t; return *this; } // std::endl and other iomanip:s. StreamLogger& operator<<(std::ostream&(*f)(std::ostream&)) { f(_ss); return *this; } private: Verbosity _verbosity; const char* _file; unsigned _line; std::ostringstream _ss; }; class LOGURU_EXPORT AbortLogger { public: AbortLogger(const char* expr, const char* file, unsigned line) : _expr(expr), _file(file), _line(line) { } LOGURU_NORETURN ~AbortLogger() noexcept(false); template AbortLogger& operator<<(const T& t) { _ss << t; return *this; } // std::endl and other iomanip:s. AbortLogger& operator<<(std::ostream&(*f)(std::ostream&)) { f(_ss); return *this; } private: const char* _expr; const char* _file; unsigned _line; std::ostringstream _ss; }; class LOGURU_EXPORT Voidify { public: Voidify() {} // This has to be an operator with a precedence lower than << but higher than ?: void operator&(const StreamLogger&) { } void operator&(const AbortLogger&) { } }; /* Helper functions for CHECK_OP_S macro. GLOG trick: The (int, int) specialization works around the issue that the compiler will not instantiate the template version of the function on values of unnamed enum type. */ #define DEFINE_CHECK_OP_IMPL(name, op) \ template \ inline std::string* name(const char* expr, const T1& v1, const char* op_str, const T2& v2) \ { \ if (LOGURU_PREDICT_TRUE(v1 op v2)) { return NULL; } \ std::ostringstream ss; \ ss << "CHECK FAILED: " << expr << " (" << v1 << " " << op_str << " " << v2 << ") "; \ return new std::string(ss.str()); \ } \ inline std::string* name(const char* expr, int v1, const char* op_str, int v2) \ { \ return name(expr, v1, op_str, v2); \ } DEFINE_CHECK_OP_IMPL(check_EQ_impl, ==) DEFINE_CHECK_OP_IMPL(check_NE_impl, !=) DEFINE_CHECK_OP_IMPL(check_LE_impl, <=) DEFINE_CHECK_OP_IMPL(check_LT_impl, < ) DEFINE_CHECK_OP_IMPL(check_GE_impl, >=) DEFINE_CHECK_OP_IMPL(check_GT_impl, > ) #undef DEFINE_CHECK_OP_IMPL /* GLOG trick: Function is overloaded for integral types to allow static const integrals declared in classes and not defined to be used as arguments to CHECK* macros. */ template inline const T& referenceable_value(const T& t) { return t; } inline char referenceable_value(char t) { return t; } inline unsigned char referenceable_value(unsigned char t) { return t; } inline signed char referenceable_value(signed char t) { return t; } inline short referenceable_value(short t) { return t; } inline unsigned short referenceable_value(unsigned short t) { return t; } inline int referenceable_value(int t) { return t; } inline unsigned int referenceable_value(unsigned int t) { return t; } inline long referenceable_value(long t) { return t; } inline unsigned long referenceable_value(unsigned long t) { return t; } inline long long referenceable_value(long long t) { return t; } inline unsigned long long referenceable_value(unsigned long long t) { return t; } } // namespace loguru LOGURU_ANONYMOUS_NAMESPACE_END // ----------------------------------------------- // Logging macros: // usage: LOG_STREAM(INFO) << "Foo " << std::setprecision(10) << some_value; #define VLOG_IF_S(verbosity, cond) \ ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ ? (void)0 \ : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) #define LOG_IF_S(verbosity_name, cond) VLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) #define VLOG_S(verbosity) VLOG_IF_S(verbosity, true) #define LOG_S(verbosity_name) VLOG_S(loguru::Verbosity_ ## verbosity_name) // ----------------------------------------------- // ABORT_S macro. Usage: ABORT_S() << "Causo of error: " << details; #define ABORT_S() loguru::Voidify() & loguru::AbortLogger("ABORT: ", __FILE__, __LINE__) // ----------------------------------------------- // CHECK_S macros: #define CHECK_WITH_INFO_S(cond, info) \ LOGURU_PREDICT_TRUE((cond) == true) \ ? (void)0 \ : loguru::Voidify() & loguru::AbortLogger("CHECK FAILED: " info " ", __FILE__, __LINE__) #define CHECK_S(cond) CHECK_WITH_INFO_S(cond, #cond) #define CHECK_NOTNULL_S(x) CHECK_WITH_INFO_S((x) != nullptr, #x " != nullptr") #define CHECK_OP_S(function_name, expr1, op, expr2) \ while (auto error_string = loguru::function_name(#expr1 " " #op " " #expr2, \ loguru::referenceable_value(expr1), #op, \ loguru::referenceable_value(expr2))) \ loguru::AbortLogger(error_string->c_str(), __FILE__, __LINE__) #define CHECK_EQ_S(expr1, expr2) CHECK_OP_S(check_EQ_impl, expr1, ==, expr2) #define CHECK_NE_S(expr1, expr2) CHECK_OP_S(check_NE_impl, expr1, !=, expr2) #define CHECK_LE_S(expr1, expr2) CHECK_OP_S(check_LE_impl, expr1, <=, expr2) #define CHECK_LT_S(expr1, expr2) CHECK_OP_S(check_LT_impl, expr1, < , expr2) #define CHECK_GE_S(expr1, expr2) CHECK_OP_S(check_GE_impl, expr1, >=, expr2) #define CHECK_GT_S(expr1, expr2) CHECK_OP_S(check_GT_impl, expr1, > , expr2) #if LOGURU_DEBUG_LOGGING // Debug logging enabled: #define DVLOG_IF_S(verbosity, cond) VLOG_IF_S(verbosity, cond) #define DLOG_IF_S(verbosity_name, cond) LOG_IF_S(verbosity_name, cond) #define DVLOG_S(verbosity) VLOG_S(verbosity) #define DLOG_S(verbosity_name) LOG_S(verbosity_name) #else // Debug logging disabled: #define DVLOG_IF_S(verbosity, cond) \ (true || (verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ ? (void)0 \ : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) #define DLOG_IF_S(verbosity_name, cond) DVLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) #define DVLOG_S(verbosity) DVLOG_IF_S(verbosity, true) #define DLOG_S(verbosity_name) DVLOG_S(loguru::Verbosity_ ## verbosity_name) #endif #if LOGURU_DEBUG_CHECKS // Debug checks enabled: #define DCHECK_S(cond) CHECK_S(cond) #define DCHECK_NOTNULL_S(x) CHECK_NOTNULL_S(x) #define DCHECK_EQ_S(a, b) CHECK_EQ_S(a, b) #define DCHECK_NE_S(a, b) CHECK_NE_S(a, b) #define DCHECK_LT_S(a, b) CHECK_LT_S(a, b) #define DCHECK_LE_S(a, b) CHECK_LE_S(a, b) #define DCHECK_GT_S(a, b) CHECK_GT_S(a, b) #define DCHECK_GE_S(a, b) CHECK_GE_S(a, b) #else // Debug checks disabled: #define DCHECK_S(cond) CHECK_S(true || (cond)) #define DCHECK_NOTNULL_S(x) CHECK_S(true || (x) != nullptr) #define DCHECK_EQ_S(a, b) CHECK_S(true || (a) == (b)) #define DCHECK_NE_S(a, b) CHECK_S(true || (a) != (b)) #define DCHECK_LT_S(a, b) CHECK_S(true || (a) < (b)) #define DCHECK_LE_S(a, b) CHECK_S(true || (a) <= (b)) #define DCHECK_GT_S(a, b) CHECK_S(true || (a) > (b)) #define DCHECK_GE_S(a, b) CHECK_S(true || (a) >= (b)) #endif #if LOGURU_REPLACE_GLOG #undef LOG #undef VLOG #undef LOG_IF #undef VLOG_IF #undef CHECK #undef CHECK_NOTNULL #undef CHECK_EQ #undef CHECK_NE #undef CHECK_LT #undef CHECK_LE #undef CHECK_GT #undef CHECK_GE #undef DLOG #undef DVLOG #undef DLOG_IF #undef DVLOG_IF #undef DCHECK #undef DCHECK_NOTNULL #undef DCHECK_EQ #undef DCHECK_NE #undef DCHECK_LT #undef DCHECK_LE #undef DCHECK_GT #undef DCHECK_GE #undef VLOG_IS_ON #define LOG LOG_S #define VLOG VLOG_S #define LOG_IF LOG_IF_S #define VLOG_IF VLOG_IF_S #define CHECK(cond) CHECK_S(!!(cond)) #define CHECK_NOTNULL CHECK_NOTNULL_S #define CHECK_EQ CHECK_EQ_S #define CHECK_NE CHECK_NE_S #define CHECK_LT CHECK_LT_S #define CHECK_LE CHECK_LE_S #define CHECK_GT CHECK_GT_S #define CHECK_GE CHECK_GE_S #define DLOG DLOG_S #define DVLOG DVLOG_S #define DLOG_IF DLOG_IF_S #define DVLOG_IF DVLOG_IF_S #define DCHECK DCHECK_S #define DCHECK_NOTNULL DCHECK_NOTNULL_S #define DCHECK_EQ DCHECK_EQ_S #define DCHECK_NE DCHECK_NE_S #define DCHECK_LT DCHECK_LT_S #define DCHECK_LE DCHECK_LE_S #define DCHECK_GT DCHECK_GT_S #define DCHECK_GE DCHECK_GE_S #define VLOG_IS_ON(verbosity) ((verbosity) <= loguru::current_verbosity_cutoff()) #endif // LOGURU_REPLACE_GLOG #endif // LOGURU_WITH_STREAMS #endif // LOGURU_HAS_DECLARED_STREAMS_HEADER